source: root_commands.c @ e252d8c

Last change on this file since e252d8c was f93fd2d, checked in by dequis <dx@…>, at 2014-07-24T03:51:07Z

Allow filtering of blist output

Add an (optional) second parameter to blist. If present, it is treated
as regex and used to filter the result list.

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