source: root_commands.c @ 7a99a0c

Last change on this file since 7a99a0c was 16ea00d, checked in by dequis <dx@…>, at 2018-02-11T21:27:17Z

root_commands: Improve 'chat add' error/success messages

This handles the most common error case with an obvious error message
(channel already exists, join it, etc), and the rarer ones with vague
but different wording. For those cases:

  • "Could not add chatroom." This wording is not present in this version anymore, likely means the channel already exists.
  • "Error creating channel for chatroom." irc_channel_new() failed but the channel doesn't exist already. This could be that some name validation failed, but this function already generates a suitable name.
  • "Error adding chatroom." irc_channel_new() succeeded, but one of the set_setstr() failed. There are account checks earlier, and the room setting is not normally validated at this point.

Can't really imagine situations where these vague errors would still
show up, but at least now you only get those for unknown cases.

  • Property mode set to 100644
File size: 43.3 KB
Line 
1/********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2013 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* User manager (root) commands                                         */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
23  Fifth Floor, Boston, MA  02110-1301  USA
24*/
25
26#define BITLBEE_CORE
27#include "commands.h"
28#include "bitlbee.h"
29#include "help.h"
30#include "ipc.h"
31
32void root_command_string(irc_t *irc, char *command)
33{
34        root_command(irc, split_command_parts(command, 0));
35}
36
37#define MIN_ARGS(x, y ...)                                                    \
38        do                                                                     \
39        {                                                                      \
40                int blaat;                                                     \
41                for (blaat = 0; blaat <= x; blaat++) {                         \
42                        if (cmd[blaat] == NULL)                               \
43                        {                                                      \
44                                irc_rootmsg(irc, "Not enough parameters given (need %d).", x); \
45                                return y;                                      \
46                        } }                                                      \
47        } while (0)
48
49void root_command(irc_t *irc, char *cmd[])
50{
51        int i, len;
52
53        if (!cmd[0]) {
54                return;
55        }
56
57        len = strlen(cmd[0]);
58        for (i = 0; root_commands[i].command; i++) {
59                if (g_strncasecmp(root_commands[i].command, cmd[0], len) == 0) {
60                        if (root_commands[i + 1].command &&
61                            g_strncasecmp(root_commands[i + 1].command, cmd[0], len) == 0) {
62                                /* Only match on the first letters if the match is unique. */
63                                break;
64                        }
65
66                        MIN_ARGS(root_commands[i].required_parameters);
67
68                        root_commands[i].execute(irc, cmd);
69                        return;
70                }
71        }
72
73        irc_rootmsg(irc, "Unknown command: %s. Please use \x02help commands\x02 to get a list of available commands.",
74                    cmd[0]);
75}
76
77static void cmd_help(irc_t *irc, char **cmd)
78{
79        char param[80];
80        int i;
81        char *s;
82
83        memset(param, 0, sizeof(param));
84        for (i = 1; (cmd[i] != NULL && (strlen(param) < (sizeof(param) - 1))); i++) {
85                if (i != 1) {   // prepend space except for the first parameter
86                        strcat(param, " ");
87                }
88                strncat(param, cmd[i], sizeof(param) - strlen(param) - 1);
89        }
90
91        s = help_get(&(global.help), param);
92        if (!s) {
93                s = help_get(&(global.help), "");
94        }
95
96        if (s) {
97                irc_rootmsg(irc, "%s", s);
98                g_free(s);
99        } else {
100                irc_rootmsg(irc, "Error opening helpfile.");
101        }
102}
103
104static void cmd_account(irc_t *irc, char **cmd);
105static void bitlbee_whatsnew(irc_t *irc);
106
107static void cmd_identify(irc_t *irc, char **cmd)
108{
109        storage_status_t status;
110        gboolean load = TRUE;
111        char *password = cmd[1];
112
113        if (irc->status & USTATUS_IDENTIFIED) {
114                irc_rootmsg(irc, "You're already logged in.");
115                return;
116        }
117
118        if (cmd[1] == NULL) {
119        } else if (strncmp(cmd[1], "-no", 3) == 0) {
120                load = FALSE;
121                password = cmd[2];
122                if (password == NULL) {
123                        irc->status |= OPER_HACK_IDENTIFY_NOLOAD;
124                }
125        } else if (strncmp(cmd[1], "-force", 6) == 0) {
126                password = cmd[2];
127                if (password == NULL) {
128                        irc->status |= OPER_HACK_IDENTIFY_FORCE;
129                }
130        } else if (irc->b->accounts != NULL) {
131                irc_rootmsg(irc,
132                            "You're trying to identify yourself, but already have "
133                            "at least one IM account set up. "
134                            "Use \x02identify -noload\x02 or \x02identify -force\x02 "
135                            "instead (see \x02help identify\x02).");
136                return;
137        }
138
139        if (password == NULL) {
140                irc_rootmsg(irc, "About to identify, use /OPER to enter the password");
141                irc->status |= OPER_HACK_IDENTIFY;
142                return;
143        }
144
145        status = auth_check_pass(irc, irc->user->nick, password);
146        if (load && (status == STORAGE_OK)) {
147                status = storage_load(irc, password);
148        }
149
150        switch (status) {
151        case STORAGE_INVALID_PASSWORD:
152                irc_rootmsg(irc, "Incorrect password");
153                break;
154        case STORAGE_NO_SUCH_USER:
155                irc_rootmsg(irc, "The nick is (probably) not registered");
156                break;
157        case STORAGE_OK:
158                irc_rootmsg(irc, "Password accepted%s",
159                            load ? ", settings and accounts loaded" : "");
160                irc->status |= USTATUS_IDENTIFIED;
161                irc_umode_set(irc, "+R", 1);
162
163                if (irc->caps & CAP_SASL) {
164                        irc_user_t *iu = irc->user;
165                        irc_send_num(irc, 900, "%s!%s@%s %s :You are now logged in as %s",
166                                iu->nick, iu->user, iu->host, iu->nick, iu->nick);
167                }
168
169                bitlbee_whatsnew(irc);
170
171                /* The following code is a bit hairy now. With takeover
172                   support, we shouldn't immediately auto_connect in case
173                   we're going to offer taking over an existing session.
174                   Do it in 200ms since that should give the parent process
175                   enough time to come back to us. */
176                if (load) {
177                        irc_channel_auto_joins(irc, NULL);
178                        if (!set_getbool(&irc->default_channel->set, "auto_join")) {
179                                irc_channel_del_user(irc->default_channel, irc->user,
180                                                     IRC_CDU_PART, "auto_join disabled "
181                                                     "for this channel.");
182                        }
183                        if (set_getbool(&irc->b->set, "auto_connect")) {
184                                irc->login_source_id = b_timeout_add(200,
185                                                                     cmd_identify_finish, irc);
186                        }
187                }
188
189                /* If ipc_child_identify() returns FALSE, it means we're
190                   already sure that there's no takeover target (only
191                   possible in 1-process daemon mode). Start auto_connect
192                   immediately. */
193                if (!ipc_child_identify(irc) && load) {
194                        cmd_identify_finish(irc, 0, 0);
195                }
196
197                break;
198        case STORAGE_OTHER_ERROR:
199        default:
200                irc_rootmsg(irc, "Unknown error while loading configuration");
201                break;
202        }
203}
204
205gboolean cmd_identify_finish(gpointer data, gint fd, b_input_condition cond)
206{
207        char *account_on[] = { "account", "on", NULL };
208        irc_t *irc = data;
209
210        if (set_getbool(&irc->b->set, "auto_connect")) {
211                cmd_account(irc, account_on);
212        }
213
214        b_event_remove(irc->login_source_id);
215        irc->login_source_id = -1;
216        return FALSE;
217}
218
219static void cmd_register(irc_t *irc, char **cmd)
220{
221        char s[16];
222
223        if (global.conf->authmode == AUTHMODE_REGISTERED) {
224                irc_rootmsg(irc, "This server does not allow registering new accounts");
225                return;
226        }
227
228        if (cmd[1] == NULL) {
229                irc_rootmsg(irc, "About to register, use /OPER to enter the password");
230                irc->status |= OPER_HACK_REGISTER;
231                return;
232        }
233
234        switch (storage_save(irc, cmd[1], FALSE)) {
235        case STORAGE_ALREADY_EXISTS:
236                irc_rootmsg(irc, "Nick is already registered");
237                break;
238
239        case STORAGE_OK:
240                irc_rootmsg(irc, "Account successfully created");
241                irc_setpass(irc, cmd[1]);
242                irc->status |= USTATUS_IDENTIFIED;
243                irc_umode_set(irc, "+R", 1);
244
245                if (irc->caps & CAP_SASL) {
246                        irc_user_t *iu = irc->user;
247                        irc_send_num(irc, 900, "%s!%s@%s %s :You are now logged in as %s",
248                                iu->nick, iu->user, iu->host, iu->nick, iu->nick);
249                }
250
251                /* Set this var now, or anyone who logs in to his/her
252                   newly created account for the first time gets the
253                   whatsnew story. */
254                g_snprintf(s, sizeof(s), "%d", BITLBEE_VERSION_CODE);
255                set_setstr(&irc->b->set, "last_version", s);
256                break;
257
258        default:
259                irc_rootmsg(irc, "Error registering");
260                break;
261        }
262}
263
264static void cmd_drop(irc_t *irc, char **cmd)
265{
266        storage_status_t status;
267
268        status = auth_check_pass(irc, irc->user->nick, cmd[1]);
269        if (status == STORAGE_OK) {
270                status = storage_remove(irc->user->nick);
271        }
272
273        switch (status) {
274        case STORAGE_NO_SUCH_USER:
275                irc_rootmsg(irc, "That account does not exist");
276                break;
277        case STORAGE_INVALID_PASSWORD:
278                irc_rootmsg(irc, "Password invalid");
279                break;
280        case STORAGE_OK:
281                irc_setpass(irc, NULL);
282                irc->status &= ~USTATUS_IDENTIFIED;
283                irc_umode_set(irc, "-R", 1);
284                irc_rootmsg(irc, "Account `%s' removed", irc->user->nick);
285                break;
286        default:
287                irc_rootmsg(irc, "Error: `%d'", status);
288                break;
289        }
290}
291
292static void cmd_save(irc_t *irc, char **cmd)
293{
294        if ((irc->status & USTATUS_IDENTIFIED) == 0) {
295                irc_rootmsg(irc, "Please create an account first (see \x02help register\x02)");
296        } else if (storage_save(irc, NULL, TRUE) == STORAGE_OK) {
297                irc_rootmsg(irc, "Configuration saved");
298        } else {
299                irc_rootmsg(irc, "Configuration could not be saved!");
300        }
301}
302
303static void cmd_showset(irc_t *irc, set_t **head, char *key)
304{
305        set_t *set;
306        char *val;
307
308        if ((val = set_getstr(head, key))) {
309                irc_rootmsg(irc, "%s = `%s'", key, val);
310        } else if (!(set = set_find(head, key))) {
311                irc_rootmsg(irc, "Setting `%s' does not exist.", key);
312                if (*head == irc->b->set) {
313                        irc_rootmsg(irc, "It might be an account or channel setting. "
314                                    "See \x02help account set\x02 and \x02help channel set\x02.");
315                }
316        } else if (set->flags & SET_PASSWORD) {
317                irc_rootmsg(irc, "%s = `********' (hidden)", key);
318        } else {
319                irc_rootmsg(irc, "%s is empty", key);
320        }
321}
322
323typedef set_t** (*cmd_set_findhead)(irc_t*, char*);
324typedef int (*cmd_set_checkflags)(irc_t*, set_t *set);
325
326static int cmd_set_real(irc_t *irc, char **cmd, set_t **head, cmd_set_checkflags checkflags)
327{
328        char *set_name = NULL, *value = NULL;
329        gboolean del = FALSE;
330
331        if (cmd[1] && g_strncasecmp(cmd[1], "-del", 4) == 0) {
332                MIN_ARGS(2, 0);
333                set_name = cmd[2];
334                del = TRUE;
335        } else {
336                set_name = cmd[1];
337                value = cmd[2];
338        }
339
340        if (set_name && (value || del)) {
341                set_t *s = set_find(head, set_name);
342                int st;
343
344                if (s && s->flags & SET_LOCKED) {
345                        irc_rootmsg(irc, "This setting can not be changed");
346                        return 0;
347                }
348                if (s && checkflags && checkflags(irc, s) == 0) {
349                        return 0;
350                }
351
352                if (del) {
353                        st = set_reset(head, set_name);
354                } else {
355                        st = set_setstr(head, set_name, value);
356                }
357
358                if (set_getstr(head, set_name) == NULL &&
359                    set_find(head, set_name)) {
360                        /* This happens when changing the passwd, for example.
361                           Showing these msgs instead gives slightly clearer
362                           feedback. */
363                        if (st) {
364                                irc_rootmsg(irc, "Setting changed successfully");
365                        } else {
366                                irc_rootmsg(irc, "Failed to change setting");
367                        }
368                } else {
369                        cmd_showset(irc, head, set_name);
370                }
371        } else if (set_name) {
372                cmd_showset(irc, head, set_name);
373        } else {
374                set_t *s = *head;
375                while (s) {
376                        if (set_isvisible(s)) {
377                                cmd_showset(irc, &s, s->key);
378                        }
379                        s = s->next;
380                }
381        }
382
383        return 1;
384}
385
386static int cmd_account_set_checkflags(irc_t *irc, set_t *s)
387{
388        account_t *a = s->data;
389
390        if (a->ic && s && s->flags & ACC_SET_OFFLINE_ONLY) {
391                irc_rootmsg(irc, "This setting can only be changed when the account is %s-line", "off");
392                return 0;
393        } else if (!a->ic && s && s->flags & ACC_SET_ONLINE_ONLY) {
394                irc_rootmsg(irc, "This setting can only be changed when the account is %s-line", "on");
395                return 0;
396        } else if (a->flags & ACC_FLAG_LOCKED && s && s->flags & ACC_SET_LOCKABLE) {
397                irc_rootmsg(irc, "This setting can not be changed for locked accounts");
398                return 0;
399        }
400
401        return 1;
402}
403
404static void cmd_account(irc_t *irc, char **cmd)
405{
406        account_t *a;
407        int len;
408
409        if (global.conf->authmode == AUTHMODE_REGISTERED && !(irc->status & USTATUS_IDENTIFIED)) {
410                irc_rootmsg(irc, "This server only accepts registered users");
411                return;
412        }
413
414        len = strlen(cmd[1]);
415
416        if (len >= 1 && g_strncasecmp(cmd[1], "add", len) == 0) {
417                struct prpl *prpl;
418
419                MIN_ARGS(3);
420
421                if (!global.conf->allow_account_add) {
422                        irc_rootmsg(irc, "This server does not allow adding new accounts");
423                        return;
424                }
425
426                if (cmd[4] == NULL) {
427                        for (a = irc->b->accounts; a; a = a->next) {
428                                if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
429                                        irc_rootmsg(irc, "Enter password for account %s "
430                                                    "first (use /OPER)", a->tag);
431                                        return;
432                                }
433                        }
434
435                        irc->status |= OPER_HACK_ACCOUNT_PASSWORD;
436                }
437
438                prpl = find_protocol(cmd[2]);
439
440                if (prpl == NULL) {
441                        char *msg = explain_unknown_protocol(cmd[2]);
442                        irc_rootmsg(irc, "Unknown protocol");
443                        irc_rootmsg(irc, msg);
444                        g_free(msg);
445                        return;
446                }
447
448                for (a = irc->b->accounts; a; a = a->next) {
449                        if (a->prpl == prpl && prpl->handle_cmp(a->user, cmd[3]) == 0) {
450                                irc_rootmsg(irc, "Warning: You already have an account with "
451                                            "protocol `%s' and username `%s'. Are you accidentally "
452                                            "trying to add it twice?", prpl->name, cmd[3]);
453                        }
454                }
455
456                a = account_add(irc->b, prpl, cmd[3], cmd[4] ? cmd[4] : PASSWORD_PENDING);
457                if (cmd[5]) {
458                        irc_rootmsg(irc, "Warning: Passing a servername/other flags to `account add' "
459                                    "is now deprecated. Use `account set' instead.");
460                        set_setstr(&a->set, "server", cmd[5]);
461                }
462
463                irc_rootmsg(irc, "Account successfully added with tag %s", a->tag);
464
465                if (cmd[4] == NULL) {
466                        set_t *oauth = set_find(&a->set, "oauth");
467                        if (oauth && bool2int(set_value(oauth))) {
468                                *a->pass = '\0';
469                                irc_rootmsg(irc, "No need to enter a password for this "
470                                            "account since it's using OAuth");
471                        } else if (prpl->options & PRPL_OPT_NO_PASSWORD) {
472                                *a->pass = '\0';
473                        } else if (prpl->options & PRPL_OPT_PASSWORD_OPTIONAL) {
474                                *a->pass = '\0';
475                                irc_rootmsg(irc, "Passwords are optional for this account. "
476                                            "If you wish to enter the password with /OPER, do "
477                                            "account %s set -del password", a->tag);
478                        } else {
479                                irc_rootmsg(irc, "You can now use the /OPER command to "
480                                            "enter the password");
481                                if (oauth) {
482                                        irc_rootmsg(irc, "Alternatively, enable OAuth if "
483                                                    "the account supports it: account %s "
484                                                    "set oauth on", a->tag);
485                                }
486                        }
487                } else if (prpl->options & PRPL_OPT_NO_PASSWORD) {
488                        irc_rootmsg(irc, "Note: this account doesn't use password for login");
489                }
490
491                return;
492        } else if (len >= 1 && g_strncasecmp(cmd[1], "list", len) == 0) {
493                int i = 0;
494
495                if (strchr(irc->umode, 'b')) {
496                        irc_rootmsg(irc, "Account list:");
497                }
498
499                for (a = irc->b->accounts; a; a = a->next) {
500                        char *con = NULL, *protocol = NULL;
501
502                        if (a->ic && (a->ic->flags & OPT_LOGGED_IN)) {
503                                con = " (connected)";
504                        } else if (a->ic) {
505                                con = " (connecting)";
506                        } else if (a->reconnect) {
507                                con = " (awaiting reconnect)";
508                        } else {
509                                con = "";
510                        }
511
512                        if (a->prpl == &protocol_missing) {
513                                protocol = g_strdup_printf("%s (missing!)", set_getstr(&a->set, "_protocol_name"));
514                        } else {
515                                protocol = g_strdup(a->prpl->name);
516                        }
517
518                        irc_rootmsg(irc, "%2d (%s): %s, %s%s", i, a->tag, protocol, a->user, con);
519                        g_free(protocol);
520
521                        i++;
522                }
523                irc_rootmsg(irc, "End of account list");
524
525                return;
526        } else if (cmd[2]) {
527                /* Try the following two only if cmd[2] == NULL */
528        } else if (len >= 2 && g_strncasecmp(cmd[1], "on", len) == 0) {
529                if (irc->b->accounts) {
530                        irc_rootmsg(irc, "Trying to get all accounts connected...");
531
532                        for (a = irc->b->accounts; a; a = a->next) {
533                                if (!a->ic && a->auto_connect && a->prpl != &protocol_missing) {
534                                        if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
535                                                irc_rootmsg(irc, "Enter password for account %s "
536                                                            "first (use /OPER)", a->tag);
537                                        } else {
538                                                account_on(irc->b, a);
539                                        }
540                                }
541                        }
542                } else {
543                        irc_rootmsg(irc, "No accounts known. Use `account add' to add one.");
544                }
545
546                return;
547        } else if (len >= 2 && g_strncasecmp(cmd[1], "off", len) == 0) {
548                irc_rootmsg(irc, "Deactivating all active (re)connections...");
549
550                for (a = irc->b->accounts; a; a = a->next) {
551                        if (a->ic) {
552                                account_off(irc->b, a);
553                        } else if (a->reconnect) {
554                                cancel_auto_reconnect(a);
555                        }
556                }
557
558                return;
559        }
560
561        MIN_ARGS(2);
562        len = strlen(cmd[2]);
563
564        /* At least right now, don't accept on/off/set/del as account IDs even
565           if they're a proper match, since people not familiar with the new
566           syntax yet may get a confusing/nasty surprise. */
567        if (g_strcasecmp(cmd[1], "on") == 0 ||
568            g_strcasecmp(cmd[1], "off") == 0 ||
569            g_strcasecmp(cmd[1], "set") == 0 ||
570            g_strcasecmp(cmd[1], "del") == 0 ||
571            (a = account_get(irc->b, cmd[1])) == NULL) {
572                irc_rootmsg(irc, "Could not find account `%s'.", cmd[1]);
573
574                return;
575        }
576
577        if (len >= 1 && g_strncasecmp(cmd[2], "del", len) == 0) {
578                if (a->flags & ACC_FLAG_LOCKED) {
579                        irc_rootmsg(irc, "Account is locked, can't delete");
580                }
581                else if (a->ic) {
582                        irc_rootmsg(irc, "Account is still logged in, can't delete");
583                } else {
584                        account_del(irc->b, a);
585                        irc_rootmsg(irc, "Account deleted");
586                }
587        } else if (len >= 2 && g_strncasecmp(cmd[2], "on", len) == 0) {
588                if (a->ic) {
589                        irc_rootmsg(irc, "Account already online");
590                } else if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
591                        irc_rootmsg(irc, "Enter password for account %s "
592                                    "first (use /OPER)", a->tag);
593                } else if (a->prpl == &protocol_missing) {
594                        char *proto = set_getstr(&a->set, "_protocol_name");
595                        char *msg = explain_unknown_protocol(proto);
596                        irc_rootmsg(irc, "Unknown protocol `%s'", proto);
597                        irc_rootmsg(irc, msg);
598                        g_free(msg);
599                } else {
600                        account_on(irc->b, a);
601                }
602        } else if (len >= 2 && g_strncasecmp(cmd[2], "off", len) == 0) {
603                if (a->ic) {
604                        account_off(irc->b, a);
605                } else if (a->reconnect) {
606                        cancel_auto_reconnect(a);
607                        irc_rootmsg(irc, "Reconnect cancelled");
608                } else {
609                        irc_rootmsg(irc, "Account already offline");
610                }
611        } else if (len >= 1 && g_strncasecmp(cmd[2], "set", len) == 0) {
612                cmd_set_real(irc, cmd + 2, &a->set, cmd_account_set_checkflags);
613        } else {
614                irc_rootmsg(irc,
615                            "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "account",
616                            cmd[2]);
617        }
618}
619
620static void cmd_channel(irc_t *irc, char **cmd)
621{
622        irc_channel_t *ic;
623        int len;
624
625        len = strlen(cmd[1]);
626
627        if (len >= 1 && g_strncasecmp(cmd[1], "list", len) == 0) {
628                GSList *l;
629                int i = 0;
630
631                if (strchr(irc->umode, 'b')) {
632                        irc_rootmsg(irc, "Channel list:");
633                }
634
635                for (l = irc->channels; l; l = l->next) {
636                        irc_channel_t *ic = l->data;
637
638                        irc_rootmsg(irc, "%2d. %s, %s channel%s", i, ic->name,
639                                    set_getstr(&ic->set, "type"),
640                                    ic->flags & IRC_CHANNEL_JOINED ? " (joined)" : "");
641
642                        i++;
643                }
644                irc_rootmsg(irc, "End of channel list");
645
646                return;
647        }
648
649        if ((ic = irc_channel_get(irc, cmd[1])) == NULL) {
650                /* If this doesn't match any channel, maybe this is the short
651                   syntax (only works when used inside a channel). */
652                if ((ic = irc->root->last_channel) &&
653                    (len = strlen(cmd[1])) &&
654                    g_strncasecmp(cmd[1], "set", len) == 0) {
655                        cmd_set_real(irc, cmd + 1, &ic->set, NULL);
656                } else {
657                        irc_rootmsg(irc, "Could not find channel `%s'", cmd[1]);
658                }
659
660                return;
661        }
662
663        MIN_ARGS(2);
664        len = strlen(cmd[2]);
665
666        if (len >= 1 && g_strncasecmp(cmd[2], "set", len) == 0) {
667                cmd_set_real(irc, cmd + 2, &ic->set, NULL);
668        } else if (len >= 1 && g_strncasecmp(cmd[2], "del", len) == 0) {
669                if (!(ic->flags & IRC_CHANNEL_JOINED) &&
670                    ic != ic->irc->default_channel) {
671                        irc_rootmsg(irc, "Channel %s deleted.", ic->name);
672                        irc_channel_free(ic);
673                } else {
674                        irc_rootmsg(irc, "Couldn't remove channel (main channel %s or "
675                                    "channels you're still in cannot be deleted).",
676                                    irc->default_channel->name);
677                }
678        } else {
679                irc_rootmsg(irc,
680                            "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "channel",
681                            cmd[1]);
682        }
683}
684
685static void cmd_add(irc_t *irc, char **cmd)
686{
687        account_t *a;
688        int add_on_server = 1;
689        char *handle = NULL, *s;
690
691        if (g_strcasecmp(cmd[1], "-tmp") == 0) {
692                MIN_ARGS(3);
693                add_on_server = 0;
694                cmd++;
695        }
696
697        if (!(a = account_get(irc->b, cmd[1]))) {
698                irc_rootmsg(irc, "Invalid account");
699                return;
700        } else if (!(a->ic && (a->ic->flags & OPT_LOGGED_IN))) {
701                irc_rootmsg(irc, "That account is not on-line");
702                return;
703        }
704
705        if (cmd[3]) {
706                if (!nick_ok(irc, cmd[3])) {
707                        irc_rootmsg(irc, "The requested nick `%s' is invalid", cmd[3]);
708                        return;
709                } else if (irc_user_by_name(irc, cmd[3])) {
710                        irc_rootmsg(irc, "The requested nick `%s' already exists", cmd[3]);
711                        return;
712                } else {
713                        nick_set_raw(a, cmd[2], cmd[3]);
714                }
715        }
716
717        if ((a->flags & ACC_FLAG_HANDLE_DOMAINS) && cmd[2][0] != '_' &&
718            (!(s = strchr(cmd[2], '@')) || s[1] == '\0')) {
719                /* If there's no @ or it's the last char, append the user's
720                   domain name now. Exclude handles starting with a _ so
721                   adding _xmlconsole will keep working. */
722                if (s) {
723                        *s = '\0';
724                }
725                if ((s = strchr(a->user, '@'))) {
726                        cmd[2] = handle = g_strconcat(cmd[2], s, NULL);
727                }
728        }
729
730        if (add_on_server) {
731                irc_channel_t *ic;
732                char *s, *group = NULL;;
733
734                if ((ic = irc->root->last_channel) &&
735                    (s = set_getstr(&ic->set, "fill_by")) &&
736                    strcmp(s, "group") == 0 &&
737                    (group = set_getstr(&ic->set, "group"))) {
738                        irc_rootmsg(irc, "Adding `%s' to contact list (group %s)",
739                                    cmd[2], group);
740                } else {
741                        irc_rootmsg(irc, "Adding `%s' to contact list", cmd[2]);
742                }
743
744                a->prpl->add_buddy(a->ic, cmd[2], group);
745        } else {
746                bee_user_t *bu;
747                irc_user_t *iu;
748
749                /* Only for add -tmp. For regular adds, this callback will
750                   be called once the IM server confirms. */
751                if ((bu = bee_user_new(irc->b, a->ic, cmd[2], BEE_USER_LOCAL)) &&
752                    (iu = bu->ui_data)) {
753                        irc_rootmsg(irc, "Temporarily assigned nickname `%s' "
754                                    "to contact `%s'", iu->nick, cmd[2]);
755                }
756        }
757
758        g_free(handle);
759}
760
761static void cmd_remove(irc_t *irc, char **cmd)
762{
763        irc_user_t *iu;
764        bee_user_t *bu;
765        char *s;
766
767        if (!(iu = irc_user_by_name(irc, cmd[1])) || !(bu = iu->bu)) {
768                irc_rootmsg(irc, "Buddy `%s' not found", cmd[1]);
769                return;
770        }
771        s = g_strdup(bu->handle);
772
773        bu->ic->acc->prpl->remove_buddy(bu->ic, bu->handle, NULL);
774        nick_del(bu);
775        if (g_slist_find(irc->users, iu)) {
776                bee_user_free(irc->b, bu);
777        }
778
779        irc_rootmsg(irc, "Buddy `%s' (nick %s) removed from contact list", s, cmd[1]);
780        g_free(s);
781
782        return;
783}
784
785static void cmd_info(irc_t *irc, char **cmd)
786{
787        struct im_connection *ic;
788        account_t *a;
789
790        if (!cmd[2]) {
791                irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
792                if (!iu || !iu->bu) {
793                        irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
794                        return;
795                }
796                ic = iu->bu->ic;
797                cmd[2] = iu->bu->handle;
798        } else if (!(a = account_get(irc->b, cmd[1]))) {
799                irc_rootmsg(irc, "Invalid account");
800                return;
801        } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
802                irc_rootmsg(irc, "That account is not on-line");
803                return;
804        }
805
806        if (!ic->acc->prpl->get_info) {
807                irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
808        } else {
809                ic->acc->prpl->get_info(ic, cmd[2]);
810        }
811}
812
813static void cmd_rename(irc_t *irc, char **cmd)
814{
815        irc_user_t *iu, *old;
816        gboolean del = g_strcasecmp(cmd[1], "-del") == 0;
817
818        iu = irc_user_by_name(irc, cmd[del ? 2 : 1]);
819
820        if (iu == NULL) {
821                irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
822        } else if (del) {
823                if (iu->bu) {
824                        bee_irc_user_nick_reset(iu);
825                }
826                irc_rootmsg(irc, "Nickname reset to `%s'", iu->nick);
827        } else if (iu == irc->user) {
828                irc_rootmsg(irc, "Use /nick to change your own nickname");
829        } else if (!nick_ok(irc, cmd[2])) {
830                irc_rootmsg(irc, "Nick `%s' is invalid", cmd[2]);
831        } else if ((old = irc_user_by_name(irc, cmd[2])) && old != iu) {
832                irc_rootmsg(irc, "Nick `%s' already exists", cmd[2]);
833        } else {
834                if (!irc_user_set_nick(iu, cmd[2])) {
835                        irc_rootmsg(irc, "Error while changing nick");
836                        return;
837                }
838
839                if (iu == irc->root) {
840                        /* If we're called internally (user did "set root_nick"),
841                           let's not go O(INF). :-) */
842                        if (strcmp(cmd[0], "set_rename") != 0) {
843                                set_setstr(&irc->b->set, "root_nick", cmd[2]);
844                        }
845                } else if (iu->bu) {
846                        nick_set(iu->bu, cmd[2]);
847                }
848
849                irc_rootmsg(irc, "Nick successfully changed");
850        }
851}
852
853char *set_eval_root_nick(set_t *set, char *new_nick)
854{
855        irc_t *irc = set->data;
856
857        if (strcmp(irc->root->nick, new_nick) != 0) {
858                char *cmd[] = { "set_rename", irc->root->nick, new_nick, NULL };
859
860                cmd_rename(irc, cmd);
861        }
862
863        return strcmp(irc->root->nick, new_nick) == 0 ? new_nick : SET_INVALID;
864}
865
866static void cmd_block(irc_t *irc, char **cmd)
867{
868        struct im_connection *ic;
869        account_t *a;
870
871        if (!cmd[2] && (a = account_get(irc->b, cmd[1])) && a->ic) {
872                char *format;
873                GSList *l;
874
875                if (strchr(irc->umode, 'b') != NULL) {
876                        format = "%s\t%s";
877                } else {
878                        format = "%-32.32s  %-16.16s";
879                }
880
881                irc_rootmsg(irc, format, "Handle", "Nickname");
882                for (l = a->ic->deny; l; l = l->next) {
883                        bee_user_t *bu = bee_user_by_handle(irc->b, a->ic, l->data);
884                        irc_user_t *iu = bu ? bu->ui_data : NULL;
885                        irc_rootmsg(irc, format, l->data, iu ? iu->nick : "(none)");
886                }
887                irc_rootmsg(irc, "End of list.");
888
889                return;
890        } else if (!cmd[2]) {
891                irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
892                if (!iu || !iu->bu) {
893                        irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
894                        return;
895                }
896                ic = iu->bu->ic;
897                cmd[2] = iu->bu->handle;
898        } else if (!(a = account_get(irc->b, cmd[1]))) {
899                irc_rootmsg(irc, "Invalid account");
900                return;
901        } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
902                irc_rootmsg(irc, "That account is not on-line");
903                return;
904        }
905
906        if (!ic->acc->prpl->add_deny || !ic->acc->prpl->rem_permit) {
907                irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
908        } else {
909                imc_rem_allow(ic, cmd[2]);
910                imc_add_block(ic, cmd[2]);
911                irc_rootmsg(irc, "Buddy `%s' moved from allow- to block-list", cmd[2]);
912        }
913}
914
915static void cmd_allow(irc_t *irc, char **cmd)
916{
917        struct im_connection *ic;
918        account_t *a;
919
920        if (!cmd[2] && (a = account_get(irc->b, cmd[1])) && a->ic) {
921                char *format;
922                GSList *l;
923
924                if (strchr(irc->umode, 'b') != NULL) {
925                        format = "%s\t%s";
926                } else {
927                        format = "%-32.32s  %-16.16s";
928                }
929
930                irc_rootmsg(irc, format, "Handle", "Nickname");
931                for (l = a->ic->permit; l; l = l->next) {
932                        bee_user_t *bu = bee_user_by_handle(irc->b, a->ic, l->data);
933                        irc_user_t *iu = bu ? bu->ui_data : NULL;
934                        irc_rootmsg(irc, format, l->data, iu ? iu->nick : "(none)");
935                }
936                irc_rootmsg(irc, "End of list.");
937
938                return;
939        } else if (!cmd[2]) {
940                irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
941                if (!iu || !iu->bu) {
942                        irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
943                        return;
944                }
945                ic = iu->bu->ic;
946                cmd[2] = iu->bu->handle;
947        } else if (!(a = account_get(irc->b, cmd[1]))) {
948                irc_rootmsg(irc, "Invalid account");
949                return;
950        } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
951                irc_rootmsg(irc, "That account is not on-line");
952                return;
953        }
954
955        if (!ic->acc->prpl->rem_deny || !ic->acc->prpl->add_permit) {
956                irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
957        } else {
958                imc_rem_block(ic, cmd[2]);
959                imc_add_allow(ic, cmd[2]);
960
961                irc_rootmsg(irc, "Buddy `%s' moved from block- to allow-list", cmd[2]);
962        }
963}
964
965static void cmd_yesno(irc_t *irc, char **cmd)
966{
967        query_t *q = NULL;
968        int numq = 0;
969
970        if (irc->queries == NULL) {
971                /* Alright, alright, let's add a tiny easter egg here. */
972                static irc_t *last_irc = NULL;
973                static time_t last_time = 0;
974                static int times = 0;
975                static const char *msg[] = {
976                        "Oh yeah, that's right.",
977                        "Alright, alright. Now go back to work.",
978                        "Buuuuuuuuuuuuuuuurp... Excuse me!",
979                        "Yes?",
980                        "No?",
981                };
982
983                if (last_irc == irc && time(NULL) - last_time < 15) {
984                        if ((++times >= 3)) {
985                                irc_rootmsg(irc, "%s", msg[rand() % (sizeof(msg) / sizeof(char*))]);
986                                last_irc = NULL;
987                                times = 0;
988                                return;
989                        }
990                } else {
991                        last_time = time(NULL);
992                        last_irc = irc;
993                        times = 0;
994                }
995
996                irc_rootmsg(irc, "Did I ask you something?");
997                return;
998        }
999
1000        /* If there's an argument, the user seems to want to answer another question than the
1001           first/last (depending on the query_order setting) one. */
1002        if (cmd[1]) {
1003                if (sscanf(cmd[1], "%d", &numq) != 1) {
1004                        irc_rootmsg(irc, "Invalid query number");
1005                        return;
1006                }
1007
1008                for (q = irc->queries; q; q = q->next, numq--) {
1009                        if (numq == 0) {
1010                                break;
1011                        }
1012                }
1013
1014                if (!q) {
1015                        irc_rootmsg(irc, "Uhm, I never asked you something like that...");
1016                        return;
1017                }
1018        }
1019
1020        if (g_strcasecmp(cmd[0], "yes") == 0) {
1021                query_answer(irc, q, 1);
1022        } else if (g_strcasecmp(cmd[0], "no") == 0) {
1023                query_answer(irc, q, 0);
1024        }
1025}
1026
1027static void cmd_set(irc_t *irc, char **cmd)
1028{
1029        cmd_set_real(irc, cmd, &irc->b->set, NULL);
1030}
1031
1032static void cmd_blist(irc_t *irc, char **cmd)
1033{
1034        int online = 0, away = 0, offline = 0, ismatch = 0;
1035        GSList *l;
1036        GRegex *regex = NULL;
1037        GError *error = NULL;
1038        char s[256];
1039        char *format;
1040        int n_online = 0, n_away = 0, n_offline = 0;
1041
1042        if (cmd[1] && g_strcasecmp(cmd[1], "all") == 0) {
1043                online = offline = away = 1;
1044        } else if (cmd[1] && g_strcasecmp(cmd[1], "offline") == 0) {
1045                offline = 1;
1046        } else if (cmd[1] && g_strcasecmp(cmd[1], "away") == 0) {
1047                away = 1;
1048        } else if (cmd[1] && g_strcasecmp(cmd[1], "online") == 0) {
1049                online = 1;
1050        } else {
1051                online = away = 1;
1052        }
1053
1054        if (cmd[2]) {
1055                regex = g_regex_new(cmd[2], G_REGEX_CASELESS, 0, &error);
1056        }
1057
1058        if (error) {
1059                irc_rootmsg(irc, error->message);
1060                g_error_free(error);
1061        }
1062
1063        if (strchr(irc->umode, 'b') != NULL) {
1064                format = "%s\t%s\t%s";
1065        } else {
1066                format = "%-24.24s  %-40.40s  %s";
1067        }
1068
1069        irc_rootmsg(irc, format, "Nick", "Handle/Account", "Status");
1070
1071        if (irc->root->last_channel &&
1072            strcmp(set_getstr(&irc->root->last_channel->set, "type"), "control") != 0) {
1073                irc->root->last_channel = NULL;
1074        }
1075
1076        for (l = irc->users; l; l = l->next) {
1077                irc_user_t *iu = l->data;
1078                bee_user_t *bu = iu->bu;
1079
1080                if (!regex || g_regex_match(regex, iu->nick, 0, NULL)) {
1081                        ismatch = 1;
1082                } else {
1083                        ismatch = 0;
1084                }
1085
1086                if (!bu || (irc->root->last_channel && !irc_channel_wants_user(irc->root->last_channel, iu))) {
1087                        continue;
1088                }
1089
1090                if ((bu->flags & (BEE_USER_ONLINE | BEE_USER_AWAY)) == BEE_USER_ONLINE) {
1091                        if (ismatch == 1 && online == 1) {
1092                                char st[256] = "Online";
1093
1094                                if (bu->status_msg) {
1095                                        g_snprintf(st, sizeof(st) - 1, "Online (%s)", bu->status_msg);
1096                                }
1097
1098                                g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
1099                                irc_rootmsg(irc, format, iu->nick, s, st);
1100                        }
1101
1102                        n_online++;
1103                }
1104
1105                if ((bu->flags & BEE_USER_ONLINE) && (bu->flags & BEE_USER_AWAY)) {
1106                        if (ismatch == 1 && away == 1) {
1107                                g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
1108                                irc_rootmsg(irc, format, iu->nick, s, irc_user_get_away(iu));
1109                        }
1110                        n_away++;
1111                }
1112
1113                if (!(bu->flags & BEE_USER_ONLINE)) {
1114                        if (ismatch == 1 && offline == 1) {
1115                                g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
1116                                irc_rootmsg(irc, format, iu->nick, s, "Offline");
1117                        }
1118                        n_offline++;
1119                }
1120        }
1121
1122        irc_rootmsg(irc, "%d buddies (%d available, %d away, %d offline)", n_online + n_away + n_offline, n_online,
1123                    n_away, n_offline);
1124
1125        if (regex) {
1126                g_regex_unref(regex);
1127        }
1128}
1129
1130static gint prplcmp(gconstpointer a, gconstpointer b)
1131{
1132        const struct prpl *pa = a;
1133        const struct prpl *pb = b;
1134
1135        return g_strcasecmp(pa->name, pb->name);
1136}
1137
1138static void prplstr(GList *prpls, GString *gstr)
1139{
1140        const char *last = NULL;
1141        GList *l;
1142        struct prpl *p;
1143
1144        prpls = g_list_copy(prpls);
1145        prpls = g_list_sort(prpls, prplcmp);
1146
1147        for (l = prpls; l; l = l->next) {
1148                p = l->data;
1149
1150                if (last && g_strcasecmp(p->name, last) == 0) {
1151                        /* Ignore duplicates (mainly for libpurple) */
1152                        continue;
1153                }
1154
1155                if (gstr->len != 0) {
1156                        g_string_append(gstr, ", ");
1157                }
1158
1159                g_string_append(gstr, p->name);
1160                last = p->name;
1161        }
1162
1163        g_list_free(prpls);
1164}
1165
1166static void cmd_plugins_info(irc_t *irc, char **cmd)
1167{
1168        GList *l;
1169        struct plugin_info *info;
1170
1171        MIN_ARGS(2);
1172
1173        for (l = get_plugins(); l; l = l->next) {
1174                info = l->data;
1175                if (g_strcasecmp(cmd[2], info->name) == 0) {
1176                        break;
1177                }
1178        }
1179
1180        if (!l) {
1181                return;
1182        }
1183
1184        irc_rootmsg(irc, "%s:", info->name);
1185        irc_rootmsg(irc, "  Version: %s", info->version);
1186
1187        if (info->description) {
1188                irc_rootmsg(irc, "  Description: %s", info->description);
1189        }
1190
1191        if (info->author) {
1192                irc_rootmsg(irc, "  Author: %s", info->author);
1193        }
1194
1195        if (info->url) {
1196                irc_rootmsg(irc, "  URL: %s", info->url);
1197        }
1198}
1199
1200static void cmd_plugins(irc_t *irc, char **cmd)
1201{
1202        GList *prpls;
1203        GString *gstr;
1204
1205        if (cmd[1] && g_strcasecmp(cmd[1], "info") == 0) {
1206                cmd_plugins_info(irc, cmd);
1207                return;
1208        }
1209
1210#ifdef WITH_PLUGINS
1211        GList *l;
1212        struct plugin_info *info;
1213        char *format;
1214
1215        if (strchr(irc->umode, 'b') != NULL) {
1216                format = "%s\t%s";
1217        } else {
1218                format = "%-30s  %s";
1219        }
1220
1221        irc_rootmsg(irc, format, "Plugin", "Version");
1222
1223        for (l = get_plugins(); l; l = l->next) {
1224                char *c;
1225                info = l->data;
1226
1227                /* some purple plugins like to include several versions separated by newlines... */
1228                if ((c = strchr(info->version, '\n'))) {
1229                        char *version = g_strndup(info->version, c - info->version);
1230                        irc_rootmsg(irc, format, info->name, version);
1231                        g_free(version);
1232                } else {
1233                        irc_rootmsg(irc, format, info->name, info->version);
1234                }
1235        }
1236#endif
1237
1238        irc_rootmsg(irc, " ");
1239
1240        gstr = g_string_new(NULL);
1241        prpls = get_protocols();
1242
1243        if (prpls) {
1244                prplstr(prpls, gstr);
1245                irc_rootmsg(irc, "Enabled Protocols: %s", gstr->str);
1246                g_string_truncate(gstr, 0);
1247        }
1248
1249        prpls = get_protocols_disabled();
1250
1251        if (prpls) {
1252                prplstr(prpls, gstr);
1253                irc_rootmsg(irc, "Disabled Protocols: %s", gstr->str);
1254        }
1255
1256        g_string_free(gstr, TRUE);
1257}
1258
1259static void cmd_qlist(irc_t *irc, char **cmd)
1260{
1261        query_t *q = irc->queries;
1262        int num;
1263
1264        if (!q) {
1265                irc_rootmsg(irc, "There are no pending questions.");
1266                return;
1267        }
1268
1269        irc_rootmsg(irc, "Pending queries:");
1270
1271        for (num = 0; q; q = q->next, num++) {
1272                if (q->ic) { /* Not necessary yet, but it might come later */
1273                        irc_rootmsg(irc, "%d, %s: %s", num, q->ic->acc->tag, q->question);
1274                } else {
1275                        irc_rootmsg(irc, "%d, BitlBee: %s", num, q->question);
1276                }
1277        }
1278}
1279
1280static void cmd_chat(irc_t *irc, char **cmd)
1281{
1282        account_t *acc;
1283
1284        if (g_strcasecmp(cmd[1], "add") == 0) {
1285                bee_chat_info_t *ci;
1286                char *channel, *room, *s;
1287                struct irc_channel *ic;
1288                guint i;
1289
1290                MIN_ARGS(3);
1291
1292                if (!(acc = account_get(irc->b, cmd[2]))) {
1293                        irc_rootmsg(irc, "Invalid account");
1294                        return;
1295                } else if (!acc->prpl->chat_join) {
1296                        irc_rootmsg(irc, "Named chatrooms not supported on that account.");
1297                        return;
1298                }
1299
1300                if (cmd[3][0] == '!') {
1301                        if (!acc->ic || !(acc->ic->flags & OPT_LOGGED_IN)) {
1302                                irc_rootmsg(irc, "Not logged in to account.");
1303                                return;
1304                        } else if (!acc->prpl->chat_list) {
1305                                irc_rootmsg(irc, "Listing chatrooms not supported on that account.");
1306                                return;
1307                        }
1308
1309                        i = g_ascii_strtoull(cmd[3] + 1, NULL, 10);
1310                        ci = g_slist_nth_data(acc->ic->chatlist, i - 1);
1311
1312                        if (ci == NULL) {
1313                                irc_rootmsg(irc, "Invalid chatroom index");
1314                                return;
1315                        }
1316
1317                        room = ci->title;
1318                } else {
1319                        room = cmd[3];
1320                }
1321
1322                if (cmd[4] == NULL) {
1323                        channel = g_strdup(room);
1324                        if ((s = strchr(channel, '@'))) {
1325                                *s = 0;
1326                        }
1327                } else {
1328                        channel = g_strdup(cmd[4]);
1329                }
1330
1331                if (strchr(CTYPES, channel[0]) == NULL) {
1332                        s = g_strdup_printf("#%s", channel);
1333                        g_free(channel);
1334                        channel = s;
1335
1336                        irc_channel_name_strip(channel);
1337                }
1338
1339                if ((ic = irc_channel_new(irc, channel)) &&
1340                    set_setstr(&ic->set, "type", "chat") &&
1341                    set_setstr(&ic->set, "chat_type", "room") &&
1342                    set_setstr(&ic->set, "account", cmd[2]) &&
1343                    set_setstr(&ic->set, "room", room)) {
1344                        irc_rootmsg(irc, "Chatroom successfully added, join with \002/join %s\002", channel);
1345                } else {
1346                        if (ic) {
1347                                irc_channel_free(ic);
1348
1349                                irc_rootmsg(irc, "Error adding chatroom.");
1350                        } else if (irc_channel_by_name(irc, channel)) {
1351                                irc_rootmsg(irc, "A channel named `%s' already exists. "
1352                                            "Join with \002/join %s\002 or see \002help channel\002 to modify it",
1353                                            channel, channel);
1354                        } else {
1355                                irc_rootmsg(irc, "Error creating channel for chatroom.");
1356                        }
1357                }
1358                g_free(channel);
1359        } else if (g_strcasecmp(cmd[1], "list") == 0) {
1360                MIN_ARGS(2);
1361
1362                if (!(acc = account_get(irc->b, cmd[2]))) {
1363                        irc_rootmsg(irc, "Invalid account");
1364                        return;
1365                } else if (!acc->ic || !(acc->ic->flags & OPT_LOGGED_IN)) {
1366                        irc_rootmsg(irc, "Not logged in to account.");
1367                        return;
1368                } else if (!acc->prpl->chat_list) {
1369                        irc_rootmsg(irc, "Listing chatrooms not supported on that account.");
1370                        return;
1371                }
1372
1373                acc->prpl->chat_list(acc->ic, cmd[3]);
1374        } else if (g_strcasecmp(cmd[1], "with") == 0) {
1375                irc_user_t *iu;
1376
1377                MIN_ARGS(2);
1378
1379                if ((iu = irc_user_by_name(irc, cmd[2])) &&
1380                    iu->bu && iu->bu->ic->acc->prpl->chat_with) {
1381                        if (!iu->bu->ic->acc->prpl->chat_with(iu->bu->ic, iu->bu->handle)) {
1382                                irc_rootmsg(irc, "(Possible) failure while trying to open "
1383                                            "a groupchat with %s.", iu->nick);
1384                        }
1385                } else {
1386                        irc_rootmsg(irc, "Can't open a groupchat with %s.", cmd[2]);
1387                }
1388        } else if (g_strcasecmp(cmd[1], "set") == 0 ||
1389                   g_strcasecmp(cmd[1], "del") == 0) {
1390                irc_rootmsg(irc, "Unknown command: chat %s. Did you mean \002channel %s\002?", cmd[1], cmd[1]);
1391        } else {
1392                irc_rootmsg(irc,
1393                            "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "chat",
1394                            cmd[1]);
1395        }
1396}
1397
1398/* some arbitrary numbers */
1399#define CHAT_TITLE_LEN_MIN 20
1400#define CHAT_TITLE_LEN_MAX 100
1401
1402void cmd_chat_list_finish(struct im_connection *ic)
1403{
1404        account_t *acc = ic->acc;
1405        bee_chat_info_t *ci;
1406        char *hformat, *iformat, *topic, *padded;
1407        GSList *l;
1408        guint i = 0;
1409        long title_len, new_len;
1410        irc_t *irc = ic->bee->ui_data;
1411
1412        if (ic->chatlist == NULL) {
1413                irc_rootmsg(irc, "No existing chatrooms");
1414                return;
1415        }
1416
1417        /* find a reasonable width for the table */
1418        title_len = CHAT_TITLE_LEN_MIN;
1419
1420        for (l = ic->chatlist; l; l = l->next) {
1421                ci = l->data;
1422                new_len = g_utf8_strlen(ci->title, -1);
1423
1424                if (new_len >= CHAT_TITLE_LEN_MAX) {
1425                        title_len = CHAT_TITLE_LEN_MAX;
1426                        break;
1427                } else if (title_len < new_len) {
1428                        title_len = new_len;
1429                }
1430        }
1431
1432        if (strchr(irc->umode, 'b') != NULL) {
1433                hformat = "%s\t%s\t%s";
1434                iformat = "%u\t%s\t%s";
1435        } else {
1436                hformat = "%s  %s  %s";
1437                iformat = "%5u  %s  %s";
1438        }
1439
1440        padded = str_pad_and_truncate("Title", title_len, NULL);
1441        irc_rootmsg(irc, hformat, "Index", padded, "Topic");
1442        g_free(padded);
1443
1444        for (l = ic->chatlist; l; l = l->next) {
1445                ci = l->data;
1446                topic = ci->topic ? ci->topic : "";
1447
1448                padded = str_pad_and_truncate(ci->title ? ci->title : "", title_len, "[...]");
1449                irc_rootmsg(irc, iformat, ++i, padded, topic);
1450                g_free(padded);
1451        }
1452
1453        irc_rootmsg(irc, "%u %s chatrooms", i, acc->tag);
1454}
1455
1456static void cmd_group(irc_t *irc, char **cmd)
1457{
1458        GSList *l;
1459        int len;
1460
1461        len = strlen(cmd[1]);
1462        if (g_strncasecmp(cmd[1], "list", len) == 0) {
1463                int n = 0;
1464
1465                if (strchr(irc->umode, 'b')) {
1466                        irc_rootmsg(irc, "Group list:");
1467                }
1468
1469                for (l = irc->b->groups; l; l = l->next) {
1470                        bee_group_t *bg = l->data;
1471                        irc_rootmsg(irc, "%d. %s", n++, bg->name);
1472                }
1473                irc_rootmsg(irc, "End of group list");
1474        } else if (g_strncasecmp(cmd[1], "info", len) == 0) {
1475                bee_group_t *bg;
1476                int n = 0;
1477
1478                MIN_ARGS(2);
1479                bg = bee_group_by_name(irc->b, cmd[2], FALSE);
1480
1481                if (bg) {
1482                        if (strchr(irc->umode, 'b')) {
1483                                irc_rootmsg(irc, "Members of %s:", cmd[2]);
1484                        }
1485                        for (l = irc->b->users; l; l = l->next) {
1486                                bee_user_t *bu = l->data;
1487                                if (bu->group == bg) {
1488                                        irc_rootmsg(irc, "%d. %s", n++, bu->nick ? : bu->handle);
1489                                }
1490                        }
1491                        irc_rootmsg(irc, "End of member list");
1492                } else {
1493                        irc_rootmsg(irc,
1494                                    "Unknown group: %s. Please use \x02group list\x02 to get a list of available groups.",
1495                                    cmd[2]);
1496                }
1497        } else {
1498                irc_rootmsg(irc,
1499                            "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "group",
1500                            cmd[1]);
1501        }
1502}
1503
1504static void cmd_transfer(irc_t *irc, char **cmd)
1505{
1506        GSList *files = irc->file_transfers;
1507        GSList *next;
1508
1509        enum { LIST, REJECT, CANCEL };
1510        int subcmd = LIST;
1511        int fid;
1512
1513        if (!files) {
1514                irc_rootmsg(irc, "No pending transfers");
1515                return;
1516        }
1517
1518        if (cmd[1] && (strcmp(cmd[1], "reject") == 0)) {
1519                subcmd = REJECT;
1520        } else if (cmd[1] && (strcmp(cmd[1], "cancel") == 0) &&
1521                   cmd[2] && (sscanf(cmd[2], "%d", &fid) == 1)) {
1522                subcmd = CANCEL;
1523        }
1524
1525        for (; files; files = next) {
1526                next = files->next;
1527                file_transfer_t *file = files->data;
1528
1529                switch (subcmd) {
1530                case LIST:
1531                        if (file->status == FT_STATUS_LISTENING) {
1532                                irc_rootmsg(irc,
1533                                            "Pending file(id %d): %s (Listening...)", file->local_id, file->file_name);
1534                        } else {
1535                                int kb_per_s = 0;
1536                                time_t diff = time(NULL) - file->started ? : 1;
1537                                if ((file->started > 0) && (file->bytes_transferred > 0)) {
1538                                        kb_per_s = file->bytes_transferred / 1024 / diff;
1539                                }
1540
1541                                irc_rootmsg(irc,
1542                                            "Pending file(id %d): %s (%10zd/%zd kb, %d kb/s)", file->local_id,
1543                                            file->file_name,
1544                                            file->bytes_transferred / 1024, file->file_size / 1024, kb_per_s);
1545                        }
1546                        break;
1547                case REJECT:
1548                        if (file->status == FT_STATUS_LISTENING) {
1549                                irc_rootmsg(irc, "Rejecting file transfer for %s", file->file_name);
1550                                imcb_file_canceled(file->ic, file, "Denied by user");
1551                        }
1552                        break;
1553                case CANCEL:
1554                        if (file->local_id == fid) {
1555                                irc_rootmsg(irc, "Canceling file transfer for %s", file->file_name);
1556                                imcb_file_canceled(file->ic, file, "Canceled by user");
1557                        }
1558                        break;
1559                }
1560        }
1561}
1562
1563static void cmd_nick(irc_t *irc, char **cmd)
1564{
1565        irc_rootmsg(irc, "This command is deprecated. Try: account %s set display_name", cmd[1]);
1566}
1567
1568/* Maybe this should be a stand-alone command as well? */
1569static void bitlbee_whatsnew(irc_t *irc)
1570{
1571        int last = set_getint(&irc->b->set, "last_version");
1572        char s[16], *msg;
1573
1574        if (last >= BITLBEE_VERSION_CODE) {
1575                return;
1576        }
1577
1578        msg = help_get_whatsnew(&(global.help), last);
1579
1580        if (msg) {
1581                irc_rootmsg(irc, "%s: This seems to be your first time using this "
1582                            "this version of BitlBee. Here's a list of new "
1583                            "features you may like to know about:\n\n%s\n",
1584                            irc->user->nick, msg);
1585        }
1586
1587        g_free(msg);
1588
1589        g_snprintf(s, sizeof(s), "%d", BITLBEE_VERSION_CODE);
1590        set_setstr(&irc->b->set, "last_version", s);
1591}
1592
1593/* IMPORTANT: Keep this list sorted! The short command logic needs that. */
1594command_t root_commands[] = {
1595        { "account",        1, cmd_account,        0 },
1596        { "add",            2, cmd_add,            0 },
1597        { "allow",          1, cmd_allow,          0 },
1598        { "blist",          0, cmd_blist,          0 },
1599        { "block",          1, cmd_block,          0 },
1600        { "channel",        1, cmd_channel,        0 },
1601        { "chat",           1, cmd_chat,           0 },
1602        { "drop",           1, cmd_drop,           0 },
1603        { "ft",             0, cmd_transfer,       0 },
1604        { "group",          1, cmd_group,          0 },
1605        { "help",           0, cmd_help,           0 },
1606        { "identify",       0, cmd_identify,       0 },
1607        { "info",           1, cmd_info,           0 },
1608        { "nick",           1, cmd_nick,           0 },
1609        { "no",             0, cmd_yesno,          0 },
1610        { "plugins",        0, cmd_plugins,        0 },
1611        { "qlist",          0, cmd_qlist,          0 },
1612        { "register",       0, cmd_register,       0 },
1613        { "remove",         1, cmd_remove,         0 },
1614        { "rename",         2, cmd_rename,         0 },
1615        { "save",           0, cmd_save,           0 },
1616        { "set",            0, cmd_set,            0 },
1617        { "transfer",       0, cmd_transfer,       0 },
1618        { "yes",            0, cmd_yesno,          0 },
1619        /* Not expecting too many plugins adding root commands so just make a
1620           dumb array with some empty entried at the end. */
1621        { NULL },
1622        { NULL },
1623        { NULL },
1624        { NULL },
1625        { NULL },
1626        { NULL },
1627        { NULL },
1628        { NULL },
1629        { NULL },
1630};
1631static const int num_root_commands = sizeof(root_commands) / sizeof(command_t);
1632
1633gboolean root_command_add(const char *command, int params, void (*func)(irc_t *, char **args), int flags)
1634{
1635        int i;
1636
1637        if (root_commands[num_root_commands - 2].command) {
1638                /* Planning fail! List is full. */
1639                return FALSE;
1640        }
1641
1642        for (i = 0; root_commands[i].command; i++) {
1643                if (g_strcasecmp(root_commands[i].command, command) == 0) {
1644                        return FALSE;
1645                } else if (g_strcasecmp(root_commands[i].command, command) > 0) {
1646                        break;
1647                }
1648        }
1649        memmove(root_commands + i + 1, root_commands + i,
1650                sizeof(command_t) * (num_root_commands - i - 1));
1651
1652        root_commands[i].command = g_strdup(command);
1653        root_commands[i].required_parameters = params;
1654        root_commands[i].execute = func;
1655        root_commands[i].flags = flags;
1656
1657        return TRUE;
1658}
Note: See TracBrowser for help on using the repository browser.