source: protocols/purple/purple.c @ e618d85

Last change on this file since e618d85 was 877686b, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-12-11T12:54:40Z

Read both alias and server_alias properties for libpurple contacts. TBH I
don't even know what's the difference, but this fixes Facebook nicks when
using libpurple.

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