source: protocols/purple/purple.c @ 7add7ec

Last change on this file since 7add7ec was 7add7ec, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-03-08T06:48:39Z

Fixed proxy support with libpurple.

  • Property mode set to 100644
File size: 37.4 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_usermsg( 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               
753                if( group )
754                        imcb_add_buddy( ic, bud->name, purple_group_get_name( group ) );
755               
756                flags |= purple_presence_is_online( bud->presence ) ? OPT_LOGGED_IN : 0;
757                flags |= purple_presence_is_available( bud->presence ) ? 0 : OPT_AWAY;
758               
759                as = purple_presence_get_active_status( bud->presence );
760               
761                imcb_buddy_status( ic, bud->name, flags, purple_status_get_name( as ),
762                                   purple_status_get_attr_string( as, "message" ) );
763               
764                imcb_buddy_times( ic, bud->name,
765                                  purple_presence_get_login_time( bud->presence ),
766                                  purple_presence_get_idle_time( bud->presence ) );
767        }
768}
769
770static void prplcb_blist_new( PurpleBlistNode *node )
771{
772        if( node->type == PURPLE_BLIST_BUDDY_NODE )
773        {
774                PurpleBuddy *bud = (PurpleBuddy*) node;
775                struct im_connection *ic = purple_ic_by_pa( bud->account );
776               
777                if( ic == NULL )
778                        return;
779               
780                imcb_add_buddy( ic, bud->name, NULL );
781               
782                prplcb_blist_update( NULL, node );
783        }
784}
785
786static void prplcb_blist_remove( PurpleBuddyList *list, PurpleBlistNode *node )
787{
788/*
789        PurpleBuddy *bud = (PurpleBuddy*) node;
790       
791        if( node->type == PURPLE_BLIST_BUDDY_NODE )
792        {
793                struct im_connection *ic = purple_ic_by_pa( bud->account );
794               
795                if( ic == NULL )
796                        return;
797               
798                imcb_remove_buddy( ic, bud->name, NULL );
799        }
800*/
801}
802
803static PurpleBlistUiOps bee_blist_uiops =
804{
805        NULL,
806        prplcb_blist_new,
807        NULL,
808        prplcb_blist_update,
809        prplcb_blist_remove,
810};
811
812void prplcb_conv_new( PurpleConversation *conv )
813{
814        if( conv->type == PURPLE_CONV_TYPE_CHAT )
815        {
816                struct im_connection *ic = purple_ic_by_pa( conv->account );
817                struct groupchat *gc;
818               
819                gc = imcb_chat_new( ic, conv->name );
820                conv->ui_data = gc;
821                gc->data = conv;
822               
823                /* libpurple brokenness: Whatever. Show that we join right away,
824                   there's no clear "This is you!" signaling in _add_users so
825                   don't even try. */
826                imcb_chat_add_buddy( gc, gc->ic->acc->user );
827        }
828}
829
830void prplcb_conv_free( PurpleConversation *conv )
831{
832        struct groupchat *gc = conv->ui_data;
833       
834        imcb_chat_free( gc );
835}
836
837void prplcb_conv_add_users( PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals )
838{
839        struct groupchat *gc = conv->ui_data;
840        GList *b;
841       
842        for( b = cbuddies; b; b = b->next )
843        {
844                PurpleConvChatBuddy *pcb = b->data;
845               
846                imcb_chat_add_buddy( gc, pcb->name );
847        }
848}
849
850void prplcb_conv_del_users( PurpleConversation *conv, GList *cbuddies )
851{
852        struct groupchat *gc = conv->ui_data;
853        GList *b;
854       
855        for( b = cbuddies; b; b = b->next )
856                imcb_chat_remove_buddy( gc, b->data, "" );
857}
858
859void prplcb_conv_chat_msg( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime )
860{
861        struct groupchat *gc = conv->ui_data;
862        PurpleBuddy *buddy;
863       
864        /* ..._SEND means it's an outgoing message, no need to echo those. */
865        if( flags & PURPLE_MESSAGE_SEND )
866                return;
867       
868        buddy = purple_find_buddy( conv->account, who );
869        if( buddy != NULL )
870                who = purple_buddy_get_name( buddy );
871       
872        imcb_chat_msg( gc, who, (char*) message, 0, mtime );
873}
874
875static void prplcb_conv_im( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime )
876{
877        struct im_connection *ic = purple_ic_by_pa( conv->account );
878        PurpleBuddy *buddy;
879       
880        /* ..._SEND means it's an outgoing message, no need to echo those. */
881        if( flags & PURPLE_MESSAGE_SEND )
882                return;
883       
884        buddy = purple_find_buddy( conv->account, who );
885        if( buddy != NULL )
886                who = purple_buddy_get_name( buddy );
887       
888        imcb_buddy_msg( ic, (char*) who, (char*) message, 0, mtime );
889}
890
891/* No, this is not a ui_op but a signal. */
892static void prplcb_buddy_typing( PurpleAccount *account, const char *who, gpointer null )
893{
894        PurpleConversation *conv;
895        PurpleConvIm *im;
896        int state;
897       
898        if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM, who, account ) ) == NULL )
899                return;
900       
901        im = PURPLE_CONV_IM(conv);
902        switch( purple_conv_im_get_typing_state( im ) )
903        {
904        case PURPLE_TYPING:
905                state = OPT_TYPING;
906                break;
907        case PURPLE_TYPED:
908                state = OPT_THINKING;
909                break;
910        default:
911                state = 0;
912        }
913       
914        imcb_buddy_typing( purple_ic_by_pa( account ), who, state );
915}
916
917static PurpleConversationUiOps bee_conv_uiops = 
918{
919        prplcb_conv_new,           /* create_conversation  */
920        prplcb_conv_free,          /* destroy_conversation */
921        prplcb_conv_chat_msg,      /* write_chat           */
922        prplcb_conv_im,            /* write_im             */
923        NULL,                      /* write_conv           */
924        prplcb_conv_add_users,     /* chat_add_users       */
925        NULL,                      /* chat_rename_user     */
926        prplcb_conv_del_users,     /* chat_remove_users    */
927        NULL,                      /* chat_update_user     */
928        NULL,                      /* present              */
929        NULL,                      /* has_focus            */
930        NULL,                      /* custom_smiley_add    */
931        NULL,                      /* custom_smiley_write  */
932        NULL,                      /* custom_smiley_close  */
933        NULL,                      /* send_confirm         */
934};
935
936struct prplcb_request_action_data
937{
938        void *user_data, *bee_data;
939        PurpleRequestActionCb yes, no;
940        int yes_i, no_i;
941};
942
943static void prplcb_request_action_yes( void *data )
944{
945        struct prplcb_request_action_data *pqad = data;
946       
947        if( pqad->yes )
948                pqad->yes( pqad->user_data, pqad->yes_i );
949        g_free( pqad );
950}
951
952static void prplcb_request_action_no( void *data )
953{
954        struct prplcb_request_action_data *pqad = data;
955       
956        if( pqad->no )
957                pqad->no( pqad->user_data, pqad->no_i );
958        g_free( pqad );
959}
960
961static void *prplcb_request_action( const char *title, const char *primary, const char *secondary,
962                                    int default_action, PurpleAccount *account, const char *who,
963                                    PurpleConversation *conv, void *user_data, size_t action_count,
964                                    va_list actions )
965{
966        struct prplcb_request_action_data *pqad; 
967        int i;
968        char *q;
969       
970        pqad = g_new0( struct prplcb_request_action_data, 1 );
971       
972        for( i = 0; i < action_count; i ++ )
973        {
974                char *caption;
975                void *fn;
976               
977                caption = va_arg( actions, char* );
978                fn = va_arg( actions, void* );
979               
980                if( strstr( caption, "Accept" ) || strstr( caption, "OK" ) )
981                {
982                        pqad->yes = fn;
983                        pqad->yes_i = i;
984                }
985                else if( strstr( caption, "Reject" ) || strstr( caption, "Cancel" ) )
986                {
987                        pqad->no = fn;
988                        pqad->no_i = i;
989                }
990        }
991       
992        pqad->user_data = user_data;
993       
994        /* TODO: IRC stuff here :-( */
995        q = g_strdup_printf( "Request: %s\n\n%s\n\n%s", title, primary, secondary );
996        pqad->bee_data = query_add( local_bee->ui_data, purple_ic_by_pa( account ), q,
997                prplcb_request_action_yes, prplcb_request_action_no, g_free, pqad );
998       
999        g_free( q );
1000       
1001        return pqad;
1002}
1003
1004/*
1005static void prplcb_request_test()
1006{
1007        fprintf( stderr, "bla\n" );
1008}
1009*/
1010
1011static PurpleRequestUiOps bee_request_uiops =
1012{
1013        NULL,
1014        NULL,
1015        prplcb_request_action,
1016        NULL,
1017        NULL,
1018        NULL,
1019        NULL,
1020};
1021
1022static void prplcb_privacy_permit_added( PurpleAccount *account, const char *name )
1023{
1024        struct im_connection *ic = purple_ic_by_pa( account );
1025       
1026        if( !g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) )
1027                ic->permit = g_slist_prepend( ic->permit, g_strdup( name ) );
1028}
1029
1030static void prplcb_privacy_permit_removed( PurpleAccount *account, const char *name )
1031{
1032        struct im_connection *ic = purple_ic_by_pa( account );
1033        void *n;
1034       
1035        n = g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp );
1036        ic->permit = g_slist_remove( ic->permit, n );
1037}
1038
1039static void prplcb_privacy_deny_added( PurpleAccount *account, const char *name )
1040{
1041        struct im_connection *ic = purple_ic_by_pa( account );
1042       
1043        if( !g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) )
1044                ic->deny = g_slist_prepend( ic->deny, g_strdup( name ) );
1045}
1046
1047static void prplcb_privacy_deny_removed( PurpleAccount *account, const char *name )
1048{
1049        struct im_connection *ic = purple_ic_by_pa( account );
1050        void *n;
1051       
1052        n = g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp );
1053        ic->deny = g_slist_remove( ic->deny, n );
1054}
1055
1056static PurplePrivacyUiOps bee_privacy_uiops =
1057{
1058        prplcb_privacy_permit_added,
1059        prplcb_privacy_permit_removed,
1060        prplcb_privacy_deny_added,
1061        prplcb_privacy_deny_removed,
1062};
1063
1064static void prplcb_debug_print( PurpleDebugLevel level, const char *category, const char *arg_s )
1065{
1066        fprintf( stderr, "DEBUG %s: %s", category, arg_s );
1067}
1068
1069static PurpleDebugUiOps bee_debug_uiops =
1070{
1071        prplcb_debug_print,
1072};
1073
1074static guint prplcb_ev_timeout_add( guint interval, GSourceFunc func, gpointer udata )
1075{
1076        return b_timeout_add( interval, (b_event_handler) func, udata );
1077}
1078
1079static guint prplcb_ev_input_add( int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata )
1080{
1081        return b_input_add( fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata );
1082}
1083
1084static gboolean prplcb_ev_remove( guint id )
1085{
1086        b_event_remove( (gint) id );
1087        return TRUE;
1088}
1089
1090static PurpleEventLoopUiOps glib_eventloops = 
1091{
1092        prplcb_ev_timeout_add,
1093        prplcb_ev_remove,
1094        prplcb_ev_input_add,
1095        prplcb_ev_remove,
1096};
1097
1098static void *prplcb_notify_email( PurpleConnection *gc, const char *subject, const char *from,
1099                                  const char *to, const char *url )
1100{
1101        struct im_connection *ic = purple_ic_by_gc( gc );
1102       
1103        imcb_log( ic, "Received e-mail from %s for %s: %s <%s>", from, to, subject, url );
1104       
1105        return NULL;
1106}
1107
1108static void *prplcb_notify_userinfo( PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info )
1109{
1110        struct im_connection *ic = purple_ic_by_gc( gc );
1111        GString *info = g_string_new( "" );
1112        GList *l = purple_notify_user_info_get_entries( user_info );
1113        char *key;
1114        const char *value;
1115        int n;
1116       
1117        while( l )
1118        {
1119                PurpleNotifyUserInfoEntry *e = l->data;
1120               
1121                switch( purple_notify_user_info_entry_get_type( e ) )
1122                {
1123                case PURPLE_NOTIFY_USER_INFO_ENTRY_PAIR:
1124                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_HEADER:
1125                        key = g_strdup( purple_notify_user_info_entry_get_label( e ) );
1126                        value = purple_notify_user_info_entry_get_value( e );
1127                       
1128                        if( key )
1129                        {
1130                                strip_html( key );
1131                                g_string_append_printf( info, "%s: ", key );
1132                               
1133                                if( value )
1134                                {
1135                                        n = strlen( value ) - 1;
1136                                        while( isspace( value[n] ) )
1137                                                n --;
1138                                        g_string_append_len( info, value, n + 1 );
1139                                }
1140                                g_string_append_c( info, '\n' );
1141                                g_free( key );
1142                        }
1143                       
1144                        break;
1145                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK:
1146                        g_string_append( info, "------------------------\n" );
1147                        break;
1148                }
1149               
1150                l = l->next;
1151        }
1152       
1153        imcb_log( ic, "User %s info:\n%s", who, info->str );
1154        g_string_free( info, TRUE );
1155       
1156        return NULL;
1157}
1158
1159static PurpleNotifyUiOps bee_notify_uiops =
1160{
1161        NULL,
1162        prplcb_notify_email,
1163        NULL,
1164        NULL,
1165        NULL,
1166        NULL,
1167        prplcb_notify_userinfo,
1168};
1169
1170static void *prplcb_account_request_authorize( PurpleAccount *account, const char *remote_user,
1171        const char *id, const char *alias, const char *message, gboolean on_list,
1172        PurpleAccountRequestAuthorizationCb authorize_cb, PurpleAccountRequestAuthorizationCb deny_cb, void *user_data )
1173{
1174        struct im_connection *ic = purple_ic_by_pa( account );
1175        char *q;
1176       
1177        if( alias )
1178                q = g_strdup_printf( "%s (%s) wants to add you to his/her contact "
1179                                     "list. (%s)", alias, remote_user, message );
1180        else
1181                q = g_strdup_printf( "%s wants to add you to his/her contact "
1182                                     "list. (%s)", remote_user, message );
1183       
1184        imcb_ask_with_free( ic, q, user_data, authorize_cb, deny_cb, NULL );
1185        g_free( q );
1186       
1187        return NULL;
1188}
1189
1190static PurpleAccountUiOps bee_account_uiops =
1191{
1192        NULL,
1193        NULL,
1194        NULL,
1195        prplcb_account_request_authorize,
1196        NULL,
1197};
1198
1199extern PurpleXferUiOps bee_xfer_uiops;
1200
1201static void purple_ui_init()
1202{
1203        purple_connections_set_ui_ops( &bee_conn_uiops );
1204        purple_blist_set_ui_ops( &bee_blist_uiops );
1205        purple_conversations_set_ui_ops( &bee_conv_uiops );
1206        purple_request_set_ui_ops( &bee_request_uiops );
1207        purple_privacy_set_ui_ops( &bee_privacy_uiops );
1208        purple_notify_set_ui_ops( &bee_notify_uiops );
1209        purple_accounts_set_ui_ops( &bee_account_uiops );
1210        purple_xfers_set_ui_ops( &bee_xfer_uiops );
1211       
1212        if( getenv( "BITLBEE_DEBUG" ) )
1213                purple_debug_set_ui_ops( &bee_debug_uiops );
1214}
1215
1216void purple_initmodule()
1217{
1218        struct prpl funcs;
1219        GList *prots;
1220        GString *help;
1221        char *dir;
1222       
1223        if( B_EV_IO_READ != PURPLE_INPUT_READ ||
1224            B_EV_IO_WRITE != PURPLE_INPUT_WRITE )
1225        {
1226                /* FIXME FIXME FIXME FIXME FIXME :-) */
1227                exit( 1 );
1228        }
1229       
1230        dir = g_strdup_printf( "%s/purple", global.conf->configdir );
1231        purple_util_set_user_dir( dir );
1232        g_free( dir );
1233       
1234        purple_debug_set_enabled( FALSE );
1235        purple_core_set_ui_ops( &bee_core_uiops );
1236        purple_eventloop_set_ui_ops( &glib_eventloops );
1237        if( !purple_core_init( "BitlBee") )
1238        {
1239                /* Initializing the core failed. Terminate. */
1240                fprintf( stderr, "libpurple initialization failed.\n" );
1241                abort();
1242        }
1243       
1244        if( proxytype != PROXY_NONE )
1245        {
1246                PurpleProxyInfo *pi = purple_proxy_info_new();
1247                switch( proxytype )
1248                {
1249                case PROXY_SOCKS4:
1250                        purple_proxy_info_set_type( pi, PURPLE_PROXY_SOCKS4 );
1251                        break;
1252                case PROXY_SOCKS5:
1253                        purple_proxy_info_set_type( pi, PURPLE_PROXY_SOCKS5 );
1254                        break;
1255                case PROXY_HTTP:
1256                        purple_proxy_info_set_type( pi, PURPLE_PROXY_HTTP );
1257                        break;
1258                } 
1259                purple_proxy_info_set_host( pi, proxyhost );
1260                purple_proxy_info_set_port( pi, proxyport );
1261                purple_proxy_info_set_username( pi, proxyuser );
1262                purple_proxy_info_set_password( pi, proxypass );
1263                purple_global_proxy_set_info( pi );
1264        }
1265       
1266        purple_set_blist( purple_blist_new() );
1267       
1268        /* No, really. So far there were ui_ops for everything, but now suddenly
1269           one needs to use signals for typing notification stuff. :-( */
1270        purple_signal_connect( purple_conversations_get_handle(), "buddy-typing",
1271                               &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL );
1272        purple_signal_connect( purple_conversations_get_handle(), "buddy-typed",
1273                               &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL );
1274        purple_signal_connect( purple_conversations_get_handle(), "buddy-typing-stopped",
1275                               &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL );
1276       
1277        memset( &funcs, 0, sizeof( funcs ) );
1278        funcs.login = purple_login;
1279        funcs.init = purple_init;
1280        funcs.logout = purple_logout;
1281        funcs.buddy_msg = purple_buddy_msg;
1282        funcs.away_states = purple_away_states;
1283        funcs.set_away = purple_set_away;
1284        funcs.add_buddy = purple_add_buddy;
1285        funcs.remove_buddy = purple_remove_buddy;
1286        funcs.add_permit = purple_add_permit;
1287        funcs.add_deny = purple_add_deny;
1288        funcs.rem_permit = purple_rem_permit;
1289        funcs.rem_deny = purple_rem_deny;
1290        funcs.get_info = purple_get_info;
1291        funcs.keepalive = purple_keepalive;
1292        funcs.send_typing = purple_send_typing;
1293        funcs.handle_cmp = g_strcasecmp;
1294        /* TODO(wilmer): Set these only for protocols that support them? */
1295        funcs.chat_msg = purple_chat_msg;
1296        funcs.chat_with = purple_chat_with;
1297        funcs.chat_invite = purple_chat_invite;
1298        funcs.chat_leave = purple_chat_leave;
1299        funcs.chat_join = purple_chat_join;
1300        funcs.transfer_request = purple_transfer_request;
1301       
1302        help = g_string_new( "BitlBee libpurple module supports the following IM protocols:\n" );
1303       
1304        /* Add a protocol entry to BitlBee's structures for every protocol
1305           supported by this libpurple instance. */     
1306        for( prots = purple_plugins_get_protocols(); prots; prots = prots->next )
1307        {
1308                PurplePlugin *prot = prots->data;
1309                struct prpl *ret;
1310               
1311                /* If we already have this one (as a native module), don't
1312                   add a libpurple duplicate. */
1313                if( find_protocol( prot->info->id ) )
1314                        continue;
1315               
1316                ret = g_memdup( &funcs, sizeof( funcs ) );
1317                ret->name = ret->data = prot->info->id;
1318                if( strncmp( ret->name, "prpl-", 5 ) == 0 )
1319                        ret->name += 5;
1320                register_protocol( ret );
1321               
1322                g_string_append_printf( help, "\n* %s (%s)", ret->name, prot->info->name );
1323               
1324                /* libpurple doesn't define a protocol called OSCAR, but we
1325                   need it to be compatible with normal BitlBee. */
1326                if( g_strcasecmp( prot->info->id, "prpl-aim" ) == 0 )
1327                {
1328                        ret = g_memdup( &funcs, sizeof( funcs ) );
1329                        ret->name = "oscar";
1330                        ret->data = prot->info->id;
1331                        register_protocol( ret );
1332                }
1333        }
1334       
1335        g_string_append( help, "\n\nFor used protocols, more information about available "
1336                         "settings can be found using \x02help purple <protocol name>\x02 "
1337                         "(create an account using that protocol first!)" );
1338       
1339        /* Add a simple dynamically-generated help item listing all
1340           the supported protocols. */
1341        help_add_mem( &global.help, "purple", help->str );
1342        g_string_free( help, TRUE );
1343}
Note: See TracBrowser for help on using the repository browser.