source: protocols/purple/purple.c @ 8d96b78

Last change on this file since 8d96b78 was 8d96b78, checked in by Wilmer van der Gaast <wilmer@…>, at 2012-01-30T21:29:04Z

Report mysterious unsupported libpurple options in "help purple $PROTOCOL".
Mostly an attempt to explain what's up with #898.

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