source: protocols/purple/purple.c @ 0ca1d79

Last change on this file since 0ca1d79 was e41cc40, checked in by dequis <dx@…>, at 2015-01-30T04:07:40Z

purple: Implement /kick

  • Property mode set to 100644
File size: 38.4 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  libpurple module - Main file                                             *
5*                                                                           *
6*  Copyright 2009-2012 Wilmer van der Gaast <wilmer@gaast.net>              *
7*                                                                           *
8*  This program is free software; you can redistribute it and/or modify     *
9*  it under the terms of the GNU General Public License as published by     *
10*  the Free Software Foundation; either version 2 of the License, or        *
11*  (at your option) any later version.                                      *
12*                                                                           *
13*  This program is distributed in the hope that it will be useful,          *
14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
16*  GNU General Public License for more details.                             *
17*                                                                           *
18*  You should have received a copy of the GNU General Public License along  *
19*  with this program; if not, write to the Free Software Foundation, Inc.,  *
20*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
21*                                                                           *
22\***************************************************************************/
23
24#include "bitlbee.h"
25#include "help.h"
26
27#include <stdarg.h>
28
29#include <glib.h>
30#include <purple.h>
31
32GSList *purple_connections;
33
34/* This makes me VERY sad... :-( But some libpurple callbacks come in without
35   any context so this is the only way to get that. Don't want to support
36   libpurple in daemon mode anyway. */
37static bee_t *local_bee;
38
39static char *set_eval_display_name( set_t *set, char *value );
40
41struct im_connection *purple_ic_by_pa( PurpleAccount *pa )
42{
43        GSList *i;
44       
45        for( i = purple_connections; i; i = i->next )
46                if( ((struct im_connection *)i->data)->proto_data == pa )
47                        return i->data;
48       
49        return NULL;
50}
51
52static struct im_connection *purple_ic_by_gc( PurpleConnection *gc )
53{
54        return purple_ic_by_pa( purple_connection_get_account( gc ) );
55}
56
57static gboolean purple_menu_cmp( const char *a, const char *b )
58{
59        while( *a && *b )
60        {
61                while( *a == '_' ) a ++;
62                while( *b == '_' ) b ++;
63                if( g_ascii_tolower( *a ) != g_ascii_tolower( *b ) )
64                        return FALSE;
65               
66                a ++;
67                b ++;
68        }
69       
70        return ( *a == '\0' && *b == '\0' );
71}
72
73static void purple_init( account_t *acc )
74{
75        PurplePlugin *prpl = purple_plugins_find_with_id( (char*) acc->prpl->data );
76        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
77        PurpleAccount *pa;
78        GList *i, *st;
79        set_t *s;
80        char help_title[64];
81        GString *help;
82        static gboolean dir_fixed = FALSE;
83       
84        /* Layer violation coming up: Making an exception for libpurple here.
85           Dig in the IRC state a bit to get a username. Ideally we should
86           check if s/he identified but this info doesn't seem *that* important.
87           It's just that fecking libpurple can't *not* store this shit.
88           
89           Remember that libpurple is not really meant to be used on public
90           servers anyway! */
91        if( !dir_fixed )
92        {
93                irc_t *irc = acc->bee->ui_data;
94                char *dir;
95               
96                dir = g_strdup_printf( "%s/purple/%s", global.conf->configdir, irc->user->nick );
97                purple_util_set_user_dir( dir );
98                g_free( dir );
99               
100                purple_blist_load();
101                purple_prefs_load();
102                dir_fixed = TRUE;
103        }
104       
105        help = g_string_new( "" );
106        g_string_printf( help, "BitlBee libpurple module %s (%s).\n\nSupported settings:",
107                                (char*) acc->prpl->name, prpl->info->name );
108       
109        if( pi->user_splits )
110        {
111                GList *l;
112                g_string_append_printf( help, "\n* username: Username" );
113                for( l = pi->user_splits; l; l = l->next )
114                        g_string_append_printf( help, "%c%s",
115                                                purple_account_user_split_get_separator( l->data ),
116                                                purple_account_user_split_get_text( l->data ) );
117        }
118       
119        /* Convert all protocol_options into per-account setting variables. */
120        for( i = pi->protocol_options; i; i = i->next )
121        {
122                PurpleAccountOption *o = i->data;
123                const char *name;
124                char *def = NULL;
125                set_eval eval = NULL;
126                void *eval_data = NULL;
127                GList *io = NULL;
128                GSList *opts = NULL;
129               
130                name = purple_account_option_get_setting( o );
131               
132                switch( purple_account_option_get_type( o ) )
133                {
134                case PURPLE_PREF_STRING:
135                        def = g_strdup( purple_account_option_get_default_string( o ) );
136                       
137                        g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
138                                                name, purple_account_option_get_text( o ),
139                                                "string", def );
140                       
141                        break;
142               
143                case PURPLE_PREF_INT:
144                        def = g_strdup_printf( "%d", purple_account_option_get_default_int( o ) );
145                        eval = set_eval_int;
146                       
147                        g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
148                                                name, purple_account_option_get_text( o ),
149                                                "integer", def );
150                       
151                        break;
152               
153                case PURPLE_PREF_BOOLEAN:
154                        if( purple_account_option_get_default_bool( o ) )
155                                def = g_strdup( "true" );
156                        else
157                                def = g_strdup( "false" );
158                        eval = set_eval_bool;
159                       
160                        g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
161                                                name, purple_account_option_get_text( o ),
162                                                "boolean", def );
163                       
164                        break;
165               
166                case PURPLE_PREF_STRING_LIST:
167                        def = g_strdup( purple_account_option_get_default_list_value( o ) );
168                       
169                        g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
170                                                name, purple_account_option_get_text( o ),
171                                                "list", def );
172                        g_string_append( help, "\n  Possible values: " );
173                       
174                        for( io = purple_account_option_get_list( o ); io; io = io->next )
175                        {
176                                PurpleKeyValuePair *kv = io->data;
177                                opts = g_slist_append( opts, kv->value );
178                                /* TODO: kv->value is not a char*, WTF? */
179                                if( strcmp( kv->value, kv->key ) != 0 )
180                                        g_string_append_printf( help, "%s (%s), ", (char*) kv->value, kv->key );
181                                else
182                                        g_string_append_printf( help, "%s, ", (char*) kv->value );
183                        }
184                        g_string_truncate( help, help->len - 2 );
185                        eval = set_eval_list;
186                        eval_data = opts;
187                       
188                        break;
189                       
190                default:
191                        /** No way to talk to the user right now, invent one when
192                        this becomes important.
193                        irc_rootmsg( acc->irc, "Setting with unknown type: %s (%d) Expect stuff to break..\n",
194                                     name, purple_account_option_get_type( o ) );
195                        */
196                        g_string_append_printf( help, "\n* [%s] UNSUPPORTED (type %d)",
197                                                name, purple_account_option_get_type( o ) );
198                        name = NULL;
199                }
200               
201                if( name != NULL )
202                {
203                        s = set_add( &acc->set, name, def, eval, acc );
204                        s->flags |= ACC_SET_OFFLINE_ONLY;
205                        s->eval_data = eval_data;
206                        g_free( def );
207                }
208        }
209       
210        g_snprintf( help_title, sizeof( help_title ), "purple %s", (char*) acc->prpl->name );
211        help_add_mem( &global.help, help_title, help->str );
212        g_string_free( help, TRUE );
213       
214        s = set_add( &acc->set, "display_name", NULL, set_eval_display_name, acc );
215        s->flags |= ACC_SET_ONLINE_ONLY;
216       
217        if( pi->options & OPT_PROTO_MAIL_CHECK )
218        {
219                s = set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc );
220                s->flags |= ACC_SET_OFFLINE_ONLY;
221        }
222       
223        if( strcmp( prpl->info->name, "Gadu-Gadu" ) == 0 )
224                s = set_add( &acc->set, "gg_sync_contacts", "true", set_eval_bool, acc );
225       
226        /* Go through all away states to figure out if away/status messages
227           are possible. */
228        pa = purple_account_new( acc->user, (char*) acc->prpl->data );
229        for( st = purple_account_get_status_types( pa ); st; st = st->next )
230        {
231                PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data );
232               
233                if( prim == PURPLE_STATUS_AVAILABLE )
234                {
235                        if( purple_status_type_get_attr( st->data, "message" ) )
236                                acc->flags |= ACC_FLAG_STATUS_MESSAGE;
237                }
238                else if( prim != PURPLE_STATUS_OFFLINE )
239                {
240                        if( purple_status_type_get_attr( st->data, "message" ) )
241                                acc->flags |= ACC_FLAG_AWAY_MESSAGE;
242                }
243        }
244        purple_accounts_remove( pa );
245}
246
247static void purple_sync_settings( account_t *acc, PurpleAccount *pa )
248{
249        PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id );
250        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
251        GList *i;
252       
253        for( i = pi->protocol_options; i; i = i->next )
254        {
255                PurpleAccountOption *o = i->data;
256                const char *name;
257                set_t *s;
258               
259                name = purple_account_option_get_setting( o );
260                s = set_find( &acc->set, name );
261                if( s->value == NULL )
262                        continue;
263               
264                switch( purple_account_option_get_type( o ) )
265                {
266                case PURPLE_PREF_STRING:
267                case PURPLE_PREF_STRING_LIST:
268                        purple_account_set_string( pa, name, set_getstr( &acc->set, name ) );
269                        break;
270               
271                case PURPLE_PREF_INT:
272                        purple_account_set_int( pa, name, set_getint( &acc->set, name ) );
273                        break;
274               
275                case PURPLE_PREF_BOOLEAN:
276                        purple_account_set_bool( pa, name, set_getbool( &acc->set, name ) );
277                        break;
278               
279                default:
280                        break;
281                }
282        }
283       
284        if( pi->options & OPT_PROTO_MAIL_CHECK )
285                purple_account_set_check_mail( pa, set_getbool( &acc->set, "mail_notifications" ) );
286}
287
288static void purple_login( account_t *acc )
289{
290        struct im_connection *ic = imcb_new( acc );
291        PurpleAccount *pa;
292       
293        if( ( local_bee != NULL && local_bee != acc->bee ) ||
294            ( global.conf->runmode == RUNMODE_DAEMON && !getenv( "BITLBEE_DEBUG" ) ) )
295        {
296                imcb_error( ic,  "Daemon mode detected. Do *not* try to use libpurple in daemon mode! "
297                                 "Please use inetd or ForkDaemon mode instead." );
298                imc_logout( ic, FALSE );
299                return;
300        }
301        local_bee = acc->bee;
302       
303        /* For now this is needed in the _connected() handlers if using
304           GLib event handling, to make sure we're not handling events
305           on dead connections. */
306        purple_connections = g_slist_prepend( purple_connections, ic );
307       
308        ic->proto_data = pa = purple_account_new( acc->user, (char*) acc->prpl->data );
309        purple_account_set_password( pa, acc->pass );
310        purple_sync_settings( acc, pa );
311       
312        purple_account_set_enabled( pa, "BitlBee", TRUE );
313}
314
315static void purple_logout( struct im_connection *ic )
316{
317        PurpleAccount *pa = ic->proto_data;
318       
319        purple_account_set_enabled( pa, "BitlBee", FALSE );
320        purple_connections = g_slist_remove( purple_connections, ic );
321        purple_accounts_remove( pa );
322}
323
324static int purple_buddy_msg( struct im_connection *ic, char *who, char *message, int flags )
325{
326        PurpleConversation *conv;
327       
328        if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM,
329                                                            who, ic->proto_data ) ) == NULL )
330        {
331                conv = purple_conversation_new( PURPLE_CONV_TYPE_IM,
332                                                ic->proto_data, who );
333        }
334       
335        purple_conv_im_send( purple_conversation_get_im_data( conv ), message );
336       
337        return 1;
338}
339
340static GList *purple_away_states( struct im_connection *ic )
341{
342        PurpleAccount *pa = ic->proto_data;
343        GList *st, *ret = NULL;
344       
345        for( st = purple_account_get_status_types( pa ); st; st = st->next )
346        {
347                PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data );
348                if( prim != PURPLE_STATUS_AVAILABLE && prim != PURPLE_STATUS_OFFLINE )
349                        ret = g_list_append( ret, (void*) purple_status_type_get_name( st->data ) );
350        }
351       
352        return ret;
353}
354
355static void purple_set_away( struct im_connection *ic, char *state_txt, char *message )
356{
357        PurpleAccount *pa = ic->proto_data;
358        GList *status_types = purple_account_get_status_types( pa ), *st;
359        PurpleStatusType *pst = NULL;
360        GList *args = NULL;
361       
362        for( st = status_types; st; st = st->next )
363        {
364                pst = st->data;
365               
366                if( state_txt == NULL &&
367                    purple_status_type_get_primitive( pst ) == PURPLE_STATUS_AVAILABLE )
368                        break;
369
370                if( state_txt != NULL &&
371                    g_strcasecmp( state_txt, purple_status_type_get_name( pst ) ) == 0 )
372                        break;
373        }
374       
375        if( message && purple_status_type_get_attr( pst, "message" ) )
376        {
377                args = g_list_append( args, "message" );
378                args = g_list_append( args, message );
379        }
380       
381        purple_account_set_status_list( pa, st ? purple_status_type_get_id( pst ) : "away",
382                                        TRUE, args );
383
384        g_list_free( args );
385}
386
387static char *set_eval_display_name( set_t *set, char *value )
388{
389        account_t *acc = set->data;
390        struct im_connection *ic = acc->ic;
391       
392        if( ic )
393                imcb_log( ic, "Changing display_name not currently supported with libpurple!" );
394       
395        return NULL;
396}
397
398/* Bad bad gadu-gadu, not saving buddy list by itself */
399static void purple_gg_buddylist_export( PurpleConnection *gc )
400{
401        struct im_connection *ic = purple_ic_by_gc( gc );
402       
403        if( set_getstr( &ic->acc->set, "gg_sync_contacts" ) )
404        {
405                GList *actions = gc->prpl->info->actions( gc->prpl, gc );
406                GList *p;
407                for( p = g_list_first(actions); p; p = p->next )
408                {
409                        if( ((PurplePluginAction*)p->data) &&
410                            purple_menu_cmp( ((PurplePluginAction*)p->data)->label, "Upload buddylist to Server" ) == 0)
411                        {
412                                PurplePluginAction action;
413                                action.plugin = gc->prpl;
414                                action.context = gc;
415                                action.user_data = NULL;
416                                ((PurplePluginAction*)p->data)->callback(&action);
417                                break;
418                        }
419                }
420                g_list_free( actions );
421        }
422}
423
424static void purple_gg_buddylist_import( PurpleConnection *gc )
425{
426        struct im_connection *ic = purple_ic_by_gc( gc );
427       
428        if( set_getstr( &ic->acc->set, "gg_sync_contacts" ) )
429        {
430                GList *actions = gc->prpl->info->actions( gc->prpl, gc );
431                GList *p;
432                for( p = g_list_first(actions); p; p = p->next )
433                {
434                        if( ((PurplePluginAction*)p->data) &&
435                            purple_menu_cmp( ((PurplePluginAction*)p->data)->label, "Download buddylist from Server" ) == 0 )
436                        {
437                                PurplePluginAction action;
438                                action.plugin = gc->prpl;
439                                action.context = gc;
440                                action.user_data = NULL;
441                                ((PurplePluginAction*)p->data)->callback(&action);
442                                break;
443                        }
444                }
445                g_list_free( actions );
446        }
447}
448
449static void purple_add_buddy( struct im_connection *ic, char *who, char *group )
450{
451        PurpleBuddy *pb;
452        PurpleGroup *pg = NULL;
453       
454        if( group && !( pg = purple_find_group( group ) ) )
455        {
456                pg = purple_group_new( group );
457                purple_blist_add_group( pg, NULL );
458        }
459       
460        pb = purple_buddy_new( (PurpleAccount*) ic->proto_data, who, NULL );
461        purple_blist_add_buddy( pb, NULL, pg, NULL );
462        purple_account_add_buddy( (PurpleAccount*) ic->proto_data, pb );
463
464        purple_gg_buddylist_export( ((PurpleAccount*)ic->proto_data)->gc );
465}
466
467static void purple_remove_buddy( struct im_connection *ic, char *who, char *group )
468{
469        PurpleBuddy *pb;
470       
471        pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who );
472        if( pb != NULL )
473        {
474                PurpleGroup *group;
475               
476                group = purple_buddy_get_group( pb );
477                purple_account_remove_buddy( (PurpleAccount*) ic->proto_data, pb, group );
478               
479                purple_blist_remove_buddy( pb );
480        }
481
482        purple_gg_buddylist_export( ((PurpleAccount*)ic->proto_data)->gc );
483}
484
485static void purple_add_permit( struct im_connection *ic, char *who )
486{
487        PurpleAccount *pa = ic->proto_data;
488       
489        purple_privacy_permit_add( pa, who, FALSE );
490}
491
492static void purple_add_deny( struct im_connection *ic, char *who )
493{
494        PurpleAccount *pa = ic->proto_data;
495       
496        purple_privacy_deny_add( pa, who, FALSE );
497}
498
499static void purple_rem_permit( struct im_connection *ic, char *who )
500{
501        PurpleAccount *pa = ic->proto_data;
502       
503        purple_privacy_permit_remove( pa, who, FALSE );
504}
505
506static void purple_rem_deny( struct im_connection *ic, char *who )
507{
508        PurpleAccount *pa = ic->proto_data;
509       
510        purple_privacy_deny_remove( pa, who, FALSE );
511}
512
513static void purple_get_info( struct im_connection *ic, char *who )
514{
515        serv_get_info( purple_account_get_connection( ic->proto_data ), who );
516}
517
518static void purple_keepalive( struct im_connection *ic )
519{
520}
521
522static int purple_send_typing( struct im_connection *ic, char *who, int flags )
523{
524        PurpleTypingState state = PURPLE_NOT_TYPING;
525        PurpleAccount *pa = ic->proto_data;
526       
527        if( flags & OPT_TYPING )
528                state = PURPLE_TYPING;
529        else if( flags & OPT_THINKING )
530                state = PURPLE_TYPED;
531       
532        serv_send_typing( purple_account_get_connection( pa ), who, state );
533       
534        return 1;
535}
536
537static void purple_chat_msg( struct groupchat *gc, char *message, int flags )
538{
539        PurpleConversation *pc = gc->data;
540       
541        purple_conv_chat_send( purple_conversation_get_chat_data( pc ), message );
542}
543
544struct groupchat *purple_chat_with( struct im_connection *ic, char *who )
545{
546        /* No, "of course" this won't work this way. Or in fact, it almost
547           does, but it only lets you send msgs to it, you won't receive
548           any. Instead, we have to click the virtual menu item.
549        PurpleAccount *pa = ic->proto_data;
550        PurpleConversation *pc;
551        PurpleConvChat *pcc;
552        struct groupchat *gc;
553       
554        gc = imcb_chat_new( ic, "BitlBee-libpurple groupchat" );
555        gc->data = pc = purple_conversation_new( PURPLE_CONV_TYPE_CHAT, pa, "BitlBee-libpurple groupchat" );
556        pc->ui_data = gc;
557       
558        pcc = PURPLE_CONV_CHAT( pc );
559        purple_conv_chat_add_user( pcc, ic->acc->user, "", 0, TRUE );
560        purple_conv_chat_invite_user( pcc, who, "Please join my chat", FALSE );
561        //purple_conv_chat_add_user( pcc, who, "", 0, TRUE );
562        */
563       
564        /* There went my nice afternoon. :-( */
565       
566        PurpleAccount *pa = ic->proto_data;
567        PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id );
568        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
569        PurpleBuddy *pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who );
570        PurpleMenuAction *mi;
571        GList *menu;
572        void (*callback)(PurpleBlistNode *, gpointer); /* FFFFFFFFFFFFFUUUUUUUUUUUUUU */
573       
574        if( !pb || !pi || !pi->blist_node_menu )
575                return NULL;
576       
577        menu = pi->blist_node_menu( &pb->node );
578        while( menu )
579        {
580                mi = menu->data;
581                if( purple_menu_cmp( mi->label, "initiate chat" ) ||
582                    purple_menu_cmp( mi->label, "initiate conference" ) )
583                        break;
584                menu = menu->next;
585        }
586       
587        if( menu == NULL )
588                return NULL;
589       
590        /* Call the fucker. */
591        callback = (void*) mi->callback;
592        callback( &pb->node, menu->data );
593       
594        return NULL;
595}
596
597void purple_chat_invite( struct groupchat *gc, char *who, char *message )
598{
599        PurpleConversation *pc = gc->data;
600        PurpleConvChat *pcc = PURPLE_CONV_CHAT( pc );
601       
602        serv_chat_invite( purple_account_get_connection( gc->ic->proto_data ),
603                          purple_conv_chat_get_id( pcc ), 
604                          message && *message ? message : "Please join my chat",
605                          who );
606}
607
608void purple_chat_kick( struct groupchat *gc, char *who, const char *message )
609{
610        PurpleConversation *pc = gc->data;
611        char *str = g_strdup_printf( "kick %s %s", who, message );
612        purple_conversation_do_command( pc, str, NULL, NULL );
613        g_free( str );
614}
615
616void purple_chat_leave( struct groupchat *gc )
617{
618        PurpleConversation *pc = gc->data;
619       
620        purple_conversation_destroy( pc );
621}
622
623struct groupchat *purple_chat_join( struct im_connection *ic, const char *room, const char *nick, const char *password, set_t **sets )
624{
625        PurpleAccount *pa = ic->proto_data;
626        PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id );
627        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
628        GHashTable *chat_hash;
629        PurpleConversation *conv;
630        GList *info, *l;
631       
632        if( !pi->chat_info || !pi->chat_info_defaults ||
633            !( info = pi->chat_info( purple_account_get_connection( pa ) ) ) )
634        {
635                imcb_error( ic, "Joining chatrooms not supported by this protocol" );
636                return NULL;
637        }
638       
639        if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_CHAT, room, pa ) ) )
640                purple_conversation_destroy( conv );
641       
642        chat_hash = pi->chat_info_defaults( purple_account_get_connection( pa ), room );
643       
644        for( l = info; l; l = l->next )
645        {
646                struct proto_chat_entry *pce = l->data;
647               
648                if( strcmp( pce->identifier, "handle" ) == 0 )
649                        g_hash_table_replace( chat_hash, "handle", g_strdup( nick ) );
650                else if( strcmp( pce->identifier, "password" ) == 0 )
651                        g_hash_table_replace( chat_hash, "password", g_strdup( password ) );
652                else if( strcmp( pce->identifier, "passwd" ) == 0 )
653                        g_hash_table_replace( chat_hash, "passwd", g_strdup( password ) );
654        }
655       
656        serv_join_chat( purple_account_get_connection( pa ), chat_hash );
657       
658        return NULL;
659}
660
661void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle );
662
663static void purple_ui_init();
664
665GHashTable *prplcb_ui_info()
666{
667        static GHashTable *ret;
668       
669        if( ret == NULL )
670        {
671                ret = g_hash_table_new(g_str_hash, g_str_equal);
672                g_hash_table_insert( ret, "name", "BitlBee" );
673                g_hash_table_insert( ret, "version", BITLBEE_VERSION );
674        }
675       
676        return ret;
677}
678
679static PurpleCoreUiOps bee_core_uiops = 
680{
681        NULL,
682        NULL,
683        purple_ui_init,
684        NULL,
685        prplcb_ui_info,
686};
687
688static void prplcb_conn_progress( PurpleConnection *gc, const char *text, size_t step, size_t step_count )
689{
690        struct im_connection *ic = purple_ic_by_gc( gc );
691       
692        imcb_log( ic, "%s", text );
693}
694
695static void prplcb_conn_connected( PurpleConnection *gc )
696{
697        struct im_connection *ic = purple_ic_by_gc( gc );
698        const char *dn;
699        set_t *s;
700       
701        imcb_connected( ic );
702       
703        if( ( dn = purple_connection_get_display_name( gc ) ) &&
704            ( s = set_find( &ic->acc->set, "display_name" ) ) )
705        {
706                g_free( s->value );
707                s->value = g_strdup( dn );
708        }
709
710        // user list needs to be requested for Gadu-Gadu
711        purple_gg_buddylist_import( gc );
712       
713        if( gc->flags & PURPLE_CONNECTION_HTML )
714                ic->flags |= OPT_DOES_HTML;
715}
716
717static void prplcb_conn_disconnected( PurpleConnection *gc )
718{
719        struct im_connection *ic = purple_ic_by_gc( gc );
720       
721        if( ic != NULL )
722        {
723                imc_logout( ic, !gc->wants_to_die );
724        }
725}
726
727static void prplcb_conn_notice( PurpleConnection *gc, const char *text )
728{
729        struct im_connection *ic = purple_ic_by_gc( gc );
730       
731        if( ic != NULL )
732                imcb_log( ic, "%s", text );
733}
734
735static void prplcb_conn_report_disconnect_reason( PurpleConnection *gc, PurpleConnectionError reason, const char *text )
736{
737        struct im_connection *ic = purple_ic_by_gc( gc );
738       
739        /* PURPLE_CONNECTION_ERROR_NAME_IN_USE means concurrent login,
740           should probably handle that. */
741        if( ic != NULL )
742                imcb_error( ic, "%s", text );
743}
744
745static PurpleConnectionUiOps bee_conn_uiops =
746{
747        prplcb_conn_progress,
748        prplcb_conn_connected,
749        prplcb_conn_disconnected,
750        prplcb_conn_notice,
751        NULL,
752        NULL,
753        NULL,
754        prplcb_conn_report_disconnect_reason,
755};
756
757static void prplcb_blist_update( PurpleBuddyList *list, PurpleBlistNode *node )
758{
759        if( node->type == PURPLE_BLIST_BUDDY_NODE )
760        {
761                PurpleBuddy *bud = (PurpleBuddy*) node;
762                PurpleGroup *group = purple_buddy_get_group( bud );
763                struct im_connection *ic = purple_ic_by_pa( bud->account );
764                PurpleStatus *as;
765                int flags = 0;
766               
767                if( ic == NULL )
768                        return;
769               
770                if( bud->server_alias )
771                        imcb_rename_buddy( ic, bud->name, bud->server_alias );
772                else if( bud->alias )
773                        imcb_rename_buddy( ic, bud->name, bud->alias );
774               
775                if( group )
776                        imcb_add_buddy( ic, bud->name, purple_group_get_name( group ) );
777               
778                flags |= purple_presence_is_online( bud->presence ) ? OPT_LOGGED_IN : 0;
779                flags |= purple_presence_is_available( bud->presence ) ? 0 : OPT_AWAY;
780               
781                as = purple_presence_get_active_status( bud->presence );
782               
783                imcb_buddy_status( ic, bud->name, flags, purple_status_get_name( as ),
784                                   purple_status_get_attr_string( as, "message" ) );
785               
786                imcb_buddy_times( ic, bud->name,
787                                  purple_presence_get_login_time( bud->presence ),
788                                  purple_presence_get_idle_time( bud->presence ) );
789        }
790}
791
792static void prplcb_blist_new( PurpleBlistNode *node )
793{
794        if( node->type == PURPLE_BLIST_BUDDY_NODE )
795        {
796                PurpleBuddy *bud = (PurpleBuddy*) node;
797                struct im_connection *ic = purple_ic_by_pa( bud->account );
798               
799                if( ic == NULL )
800                        return;
801               
802                imcb_add_buddy( ic, bud->name, NULL );
803               
804                prplcb_blist_update( NULL, node );
805        }
806}
807
808static void prplcb_blist_remove( PurpleBuddyList *list, PurpleBlistNode *node )
809{
810/*
811        PurpleBuddy *bud = (PurpleBuddy*) node;
812       
813        if( node->type == PURPLE_BLIST_BUDDY_NODE )
814        {
815                struct im_connection *ic = purple_ic_by_pa( bud->account );
816               
817                if( ic == NULL )
818                        return;
819               
820                imcb_remove_buddy( ic, bud->name, NULL );
821        }
822*/
823}
824
825static PurpleBlistUiOps bee_blist_uiops =
826{
827        NULL,
828        prplcb_blist_new,
829        NULL,
830        prplcb_blist_update,
831        prplcb_blist_remove,
832};
833
834void prplcb_conv_new( PurpleConversation *conv )
835{
836        if( conv->type == PURPLE_CONV_TYPE_CHAT )
837        {
838                struct im_connection *ic = purple_ic_by_pa( conv->account );
839                struct groupchat *gc;
840               
841                gc = imcb_chat_new( ic, conv->name );
842                if( conv->title != NULL )
843                {
844                        imcb_chat_name_hint( gc, conv->title );
845                        imcb_chat_topic( gc, NULL, conv->title, 0 );
846                }
847
848                conv->ui_data = gc;
849                gc->data = conv;
850               
851                /* libpurple brokenness: Whatever. Show that we join right away,
852                   there's no clear "This is you!" signaling in _add_users so
853                   don't even try. */
854                imcb_chat_add_buddy( gc, gc->ic->acc->user );
855        }
856}
857
858void prplcb_conv_free( PurpleConversation *conv )
859{
860        struct groupchat *gc = conv->ui_data;
861       
862        imcb_chat_free( gc );
863}
864
865void prplcb_conv_add_users( PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals )
866{
867        struct groupchat *gc = conv->ui_data;
868        GList *b;
869       
870        for( b = cbuddies; b; b = b->next )
871        {
872                PurpleConvChatBuddy *pcb = b->data;
873               
874                imcb_chat_add_buddy( gc, pcb->name );
875        }
876}
877
878void prplcb_conv_del_users( PurpleConversation *conv, GList *cbuddies )
879{
880        struct groupchat *gc = conv->ui_data;
881        GList *b;
882       
883        for( b = cbuddies; b; b = b->next )
884                imcb_chat_remove_buddy( gc, b->data, "" );
885}
886
887void prplcb_conv_chat_msg( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime )
888{
889        struct groupchat *gc = conv->ui_data;
890        PurpleBuddy *buddy;
891       
892        /* ..._SEND means it's an outgoing message, no need to echo those. */
893        if( flags & PURPLE_MESSAGE_SEND )
894                return;
895       
896        buddy = purple_find_buddy( conv->account, who );
897        if( buddy != NULL )
898                who = purple_buddy_get_name( buddy );
899       
900        imcb_chat_msg( gc, who, (char*) message, 0, mtime );
901}
902
903static void prplcb_conv_im( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime )
904{
905        struct im_connection *ic = purple_ic_by_pa( conv->account );
906        PurpleBuddy *buddy;
907       
908        /* ..._SEND means it's an outgoing message, no need to echo those. */
909        if( flags & PURPLE_MESSAGE_SEND )
910                return;
911       
912        buddy = purple_find_buddy( conv->account, who );
913        if( buddy != NULL )
914                who = purple_buddy_get_name( buddy );
915       
916        imcb_buddy_msg( ic, (char*) who, (char*) message, 0, mtime );
917}
918
919/* No, this is not a ui_op but a signal. */
920static void prplcb_buddy_typing( PurpleAccount *account, const char *who, gpointer null )
921{
922        PurpleConversation *conv;
923        PurpleConvIm *im;
924        int state;
925       
926        if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM, who, account ) ) == NULL )
927                return;
928       
929        im = PURPLE_CONV_IM(conv);
930        switch( purple_conv_im_get_typing_state( im ) )
931        {
932        case PURPLE_TYPING:
933                state = OPT_TYPING;
934                break;
935        case PURPLE_TYPED:
936                state = OPT_THINKING;
937                break;
938        default:
939                state = 0;
940        }
941       
942        imcb_buddy_typing( purple_ic_by_pa( account ), who, state );
943}
944
945static PurpleConversationUiOps bee_conv_uiops = 
946{
947        prplcb_conv_new,           /* create_conversation  */
948        prplcb_conv_free,          /* destroy_conversation */
949        prplcb_conv_chat_msg,      /* write_chat           */
950        prplcb_conv_im,            /* write_im             */
951        NULL,                      /* write_conv           */
952        prplcb_conv_add_users,     /* chat_add_users       */
953        NULL,                      /* chat_rename_user     */
954        prplcb_conv_del_users,     /* chat_remove_users    */
955        NULL,                      /* chat_update_user     */
956        NULL,                      /* present              */
957        NULL,                      /* has_focus            */
958        NULL,                      /* custom_smiley_add    */
959        NULL,                      /* custom_smiley_write  */
960        NULL,                      /* custom_smiley_close  */
961        NULL,                      /* send_confirm         */
962};
963
964struct prplcb_request_action_data
965{
966        void *user_data, *bee_data;
967        PurpleRequestActionCb yes, no;
968        int yes_i, no_i;
969};
970
971static void prplcb_request_action_yes( void *data )
972{
973        struct prplcb_request_action_data *pqad = data;
974       
975        if( pqad->yes )
976                pqad->yes( pqad->user_data, pqad->yes_i );
977        g_free( pqad );
978}
979
980static void prplcb_request_action_no( void *data )
981{
982        struct prplcb_request_action_data *pqad = data;
983       
984        if( pqad->no )
985                pqad->no( pqad->user_data, pqad->no_i );
986        g_free( pqad );
987}
988
989static void *prplcb_request_action( const char *title, const char *primary, const char *secondary,
990                                    int default_action, PurpleAccount *account, const char *who,
991                                    PurpleConversation *conv, void *user_data, size_t action_count,
992                                    va_list actions )
993{
994        struct prplcb_request_action_data *pqad; 
995        int i;
996        char *q;
997       
998        pqad = g_new0( struct prplcb_request_action_data, 1 );
999       
1000        for( i = 0; i < action_count; i ++ )
1001        {
1002                char *caption;
1003                void *fn;
1004               
1005                caption = va_arg( actions, char* );
1006                fn = va_arg( actions, void* );
1007               
1008                if( strstr( caption, "Accept" ) || strstr( caption, "OK" ) )
1009                {
1010                        pqad->yes = fn;
1011                        pqad->yes_i = i;
1012                }
1013                else if( strstr( caption, "Reject" ) || strstr( caption, "Cancel" ) )
1014                {
1015                        pqad->no = fn;
1016                        pqad->no_i = i;
1017                }
1018        }
1019       
1020        pqad->user_data = user_data;
1021       
1022        /* TODO: IRC stuff here :-( */
1023        q = g_strdup_printf( "Request: %s\n\n%s\n\n%s", title, primary, secondary );
1024        pqad->bee_data = query_add( local_bee->ui_data, purple_ic_by_pa( account ), q,
1025                prplcb_request_action_yes, prplcb_request_action_no, g_free, pqad );
1026       
1027        g_free( q );
1028       
1029        return pqad;
1030}
1031
1032/*
1033static void prplcb_request_test()
1034{
1035        fprintf( stderr, "bla\n" );
1036}
1037*/
1038
1039static PurpleRequestUiOps bee_request_uiops =
1040{
1041        NULL,
1042        NULL,
1043        prplcb_request_action,
1044        NULL,
1045        NULL,
1046        NULL,
1047        NULL,
1048};
1049
1050static void prplcb_privacy_permit_added( PurpleAccount *account, const char *name )
1051{
1052        struct im_connection *ic = purple_ic_by_pa( account );
1053       
1054        if( !g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) )
1055                ic->permit = g_slist_prepend( ic->permit, g_strdup( name ) );
1056}
1057
1058static void prplcb_privacy_permit_removed( PurpleAccount *account, const char *name )
1059{
1060        struct im_connection *ic = purple_ic_by_pa( account );
1061        void *n;
1062       
1063        n = g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp );
1064        ic->permit = g_slist_remove( ic->permit, n );
1065}
1066
1067static void prplcb_privacy_deny_added( PurpleAccount *account, const char *name )
1068{
1069        struct im_connection *ic = purple_ic_by_pa( account );
1070       
1071        if( !g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) )
1072                ic->deny = g_slist_prepend( ic->deny, g_strdup( name ) );
1073}
1074
1075static void prplcb_privacy_deny_removed( PurpleAccount *account, const char *name )
1076{
1077        struct im_connection *ic = purple_ic_by_pa( account );
1078        void *n;
1079       
1080        n = g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp );
1081        ic->deny = g_slist_remove( ic->deny, n );
1082}
1083
1084static PurplePrivacyUiOps bee_privacy_uiops =
1085{
1086        prplcb_privacy_permit_added,
1087        prplcb_privacy_permit_removed,
1088        prplcb_privacy_deny_added,
1089        prplcb_privacy_deny_removed,
1090};
1091
1092static void prplcb_debug_print( PurpleDebugLevel level, const char *category, const char *arg_s )
1093{
1094        fprintf( stderr, "DEBUG %s: %s", category, arg_s );
1095}
1096
1097static PurpleDebugUiOps bee_debug_uiops =
1098{
1099        prplcb_debug_print,
1100};
1101
1102static guint prplcb_ev_timeout_add( guint interval, GSourceFunc func, gpointer udata )
1103{
1104        return b_timeout_add( interval, (b_event_handler) func, udata );
1105}
1106
1107static guint prplcb_ev_input_add( int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata )
1108{
1109        return b_input_add( fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata );
1110}
1111
1112static gboolean prplcb_ev_remove( guint id )
1113{
1114        b_event_remove( (gint) id );
1115        return TRUE;
1116}
1117
1118static PurpleEventLoopUiOps glib_eventloops = 
1119{
1120        prplcb_ev_timeout_add,
1121        prplcb_ev_remove,
1122        prplcb_ev_input_add,
1123        prplcb_ev_remove,
1124};
1125
1126static void *prplcb_notify_email( PurpleConnection *gc, const char *subject, const char *from,
1127                                  const char *to, const char *url )
1128{
1129        struct im_connection *ic = purple_ic_by_gc( gc );
1130       
1131        imcb_log( ic, "Received e-mail from %s for %s: %s <%s>", from, to, subject, url );
1132       
1133        return NULL;
1134}
1135
1136static void *prplcb_notify_userinfo( PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info )
1137{
1138        struct im_connection *ic = purple_ic_by_gc( gc );
1139        GString *info = g_string_new( "" );
1140        GList *l = purple_notify_user_info_get_entries( user_info );
1141        char *key;
1142        const char *value;
1143        int n;
1144       
1145        while( l )
1146        {
1147                PurpleNotifyUserInfoEntry *e = l->data;
1148               
1149                switch( purple_notify_user_info_entry_get_type( e ) )
1150                {
1151                case PURPLE_NOTIFY_USER_INFO_ENTRY_PAIR:
1152                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_HEADER:
1153                        key = g_strdup( purple_notify_user_info_entry_get_label( e ) );
1154                        value = purple_notify_user_info_entry_get_value( e );
1155                       
1156                        if( key )
1157                        {
1158                                strip_html( key );
1159                                g_string_append_printf( info, "%s: ", key );
1160                               
1161                                if( value )
1162                                {
1163                                        n = strlen( value ) - 1;
1164                                        while( g_ascii_isspace( value[n] ) )
1165                                                n --;
1166                                        g_string_append_len( info, value, n + 1 );
1167                                }
1168                                g_string_append_c( info, '\n' );
1169                                g_free( key );
1170                        }
1171                       
1172                        break;
1173                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK:
1174                        g_string_append( info, "------------------------\n" );
1175                        break;
1176                }
1177               
1178                l = l->next;
1179        }
1180       
1181        imcb_log( ic, "User %s info:\n%s", who, info->str );
1182        g_string_free( info, TRUE );
1183       
1184        return NULL;
1185}
1186
1187static PurpleNotifyUiOps bee_notify_uiops =
1188{
1189        NULL,
1190        prplcb_notify_email,
1191        NULL,
1192        NULL,
1193        NULL,
1194        NULL,
1195        prplcb_notify_userinfo,
1196};
1197
1198static void *prplcb_account_request_authorize( PurpleAccount *account, const char *remote_user,
1199        const char *id, const char *alias, const char *message, gboolean on_list,
1200        PurpleAccountRequestAuthorizationCb authorize_cb, PurpleAccountRequestAuthorizationCb deny_cb, void *user_data )
1201{
1202        struct im_connection *ic = purple_ic_by_pa( account );
1203        char *q;
1204       
1205        if( alias )
1206                q = g_strdup_printf( "%s (%s) wants to add you to his/her contact "
1207                                     "list. (%s)", alias, remote_user, message );
1208        else
1209                q = g_strdup_printf( "%s wants to add you to his/her contact "
1210                                     "list. (%s)", remote_user, message );
1211       
1212        imcb_ask_with_free( ic, q, user_data, authorize_cb, deny_cb, NULL );
1213        g_free( q );
1214       
1215        return NULL;
1216}
1217
1218static PurpleAccountUiOps bee_account_uiops =
1219{
1220        NULL,
1221        NULL,
1222        NULL,
1223        prplcb_account_request_authorize,
1224        NULL,
1225};
1226
1227extern PurpleXferUiOps bee_xfer_uiops;
1228
1229static void purple_ui_init()
1230{
1231        purple_connections_set_ui_ops( &bee_conn_uiops );
1232        purple_blist_set_ui_ops( &bee_blist_uiops );
1233        purple_conversations_set_ui_ops( &bee_conv_uiops );
1234        purple_request_set_ui_ops( &bee_request_uiops );
1235        purple_privacy_set_ui_ops( &bee_privacy_uiops );
1236        purple_notify_set_ui_ops( &bee_notify_uiops );
1237        purple_accounts_set_ui_ops( &bee_account_uiops );
1238        purple_xfers_set_ui_ops( &bee_xfer_uiops );
1239       
1240        if( getenv( "BITLBEE_DEBUG" ) )
1241                purple_debug_set_ui_ops( &bee_debug_uiops );
1242}
1243
1244void purple_initmodule()
1245{
1246        struct prpl funcs;
1247        GList *prots;
1248        GString *help;
1249        char *dir;
1250       
1251        if( B_EV_IO_READ != PURPLE_INPUT_READ ||
1252            B_EV_IO_WRITE != PURPLE_INPUT_WRITE )
1253        {
1254                /* FIXME FIXME FIXME FIXME FIXME :-) */
1255                exit( 1 );
1256        }
1257       
1258        dir = g_strdup_printf( "%s/purple", global.conf->configdir );
1259        purple_util_set_user_dir( dir );
1260        g_free( dir );
1261       
1262        purple_debug_set_enabled( FALSE );
1263        purple_core_set_ui_ops( &bee_core_uiops );
1264        purple_eventloop_set_ui_ops( &glib_eventloops );
1265        if( !purple_core_init( "BitlBee") )
1266        {
1267                /* Initializing the core failed. Terminate. */
1268                fprintf( stderr, "libpurple initialization failed.\n" );
1269                abort();
1270        }
1271       
1272        if( proxytype != PROXY_NONE )
1273        {
1274                PurpleProxyInfo *pi = purple_global_proxy_get_info();
1275                switch( proxytype )
1276                {
1277                case PROXY_SOCKS4:
1278                        purple_proxy_info_set_type( pi, PURPLE_PROXY_SOCKS4 );
1279                        break;
1280                case PROXY_SOCKS5:
1281                        purple_proxy_info_set_type( pi, PURPLE_PROXY_SOCKS5 );
1282                        break;
1283                case PROXY_HTTP:
1284                        purple_proxy_info_set_type( pi, PURPLE_PROXY_HTTP );
1285                        break;
1286                } 
1287                purple_proxy_info_set_host( pi, proxyhost );
1288                purple_proxy_info_set_port( pi, proxyport );
1289                purple_proxy_info_set_username( pi, proxyuser );
1290                purple_proxy_info_set_password( pi, proxypass );
1291        }
1292       
1293        purple_set_blist( purple_blist_new() );
1294       
1295        /* No, really. So far there were ui_ops for everything, but now suddenly
1296           one needs to use signals for typing notification stuff. :-( */
1297        purple_signal_connect( purple_conversations_get_handle(), "buddy-typing",
1298                               &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL );
1299        purple_signal_connect( purple_conversations_get_handle(), "buddy-typed",
1300                               &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL );
1301        purple_signal_connect( purple_conversations_get_handle(), "buddy-typing-stopped",
1302                               &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL );
1303       
1304        memset( &funcs, 0, sizeof( funcs ) );
1305        funcs.login = purple_login;
1306        funcs.init = purple_init;
1307        funcs.logout = purple_logout;
1308        funcs.buddy_msg = purple_buddy_msg;
1309        funcs.away_states = purple_away_states;
1310        funcs.set_away = purple_set_away;
1311        funcs.add_buddy = purple_add_buddy;
1312        funcs.remove_buddy = purple_remove_buddy;
1313        funcs.add_permit = purple_add_permit;
1314        funcs.add_deny = purple_add_deny;
1315        funcs.rem_permit = purple_rem_permit;
1316        funcs.rem_deny = purple_rem_deny;
1317        funcs.get_info = purple_get_info;
1318        funcs.keepalive = purple_keepalive;
1319        funcs.send_typing = purple_send_typing;
1320        funcs.handle_cmp = g_strcasecmp;
1321        /* TODO(wilmer): Set these only for protocols that support them? */
1322        funcs.chat_msg = purple_chat_msg;
1323        funcs.chat_with = purple_chat_with;
1324        funcs.chat_invite = purple_chat_invite;
1325        funcs.chat_kick = purple_chat_kick;
1326        funcs.chat_leave = purple_chat_leave;
1327        funcs.chat_join = purple_chat_join;
1328        funcs.transfer_request = purple_transfer_request;
1329       
1330        help = g_string_new( "BitlBee libpurple module supports the following IM protocols:\n" );
1331       
1332        /* Add a protocol entry to BitlBee's structures for every protocol
1333           supported by this libpurple instance. */     
1334        for( prots = purple_plugins_get_protocols(); prots; prots = prots->next )
1335        {
1336                PurplePlugin *prot = prots->data;
1337                struct prpl *ret;
1338               
1339                /* If we already have this one (as a native module), don't
1340                   add a libpurple duplicate. */
1341                if( find_protocol( prot->info->id ) )
1342                        continue;
1343               
1344                ret = g_memdup( &funcs, sizeof( funcs ) );
1345                ret->name = ret->data = prot->info->id;
1346                if( strncmp( ret->name, "prpl-", 5 ) == 0 )
1347                        ret->name += 5;
1348                register_protocol( ret );
1349               
1350                g_string_append_printf( help, "\n* %s (%s)", ret->name, prot->info->name );
1351               
1352                /* libpurple doesn't define a protocol called OSCAR, but we
1353                   need it to be compatible with normal BitlBee. */
1354                if( g_strcasecmp( prot->info->id, "prpl-aim" ) == 0 )
1355                {
1356                        ret = g_memdup( &funcs, sizeof( funcs ) );
1357                        ret->name = "oscar";
1358                        ret->data = prot->info->id;
1359                        register_protocol( ret );
1360                }
1361        }
1362       
1363        g_string_append( help, "\n\nFor used protocols, more information about available "
1364                         "settings can be found using \x02help purple <protocol name>\x02 "
1365                         "(create an account using that protocol first!)" );
1366       
1367        /* Add a simple dynamically-generated help item listing all
1368           the supported protocols. */
1369        help_add_mem( &global.help, "purple", help->str );
1370        g_string_free( help, TRUE );
1371}
Note: See TracBrowser for help on using the repository browser.