source: protocols/purple/purple.c @ 9066974

Last change on this file since 9066974 was 3c9b095, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-09-06T09:26:39Z

libpurple insists on storing its own version of reality somewhere. I was
using /tmp so far but this wasn't a good idea. Try to use something saner.

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