source: protocols/purple/purple.c @ ca0981a

Last change on this file since ca0981a was d8acfd3, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-05-09T23:23:34Z

Purple lists mix up key and value; key == what the user sees, *value* is
what the module understands. This should hopefully resolve QQ issues.

  • Property mode set to 100644
File size: 30.3 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 irc_t *local_irc;
38
39static struct im_connection *purple_ic_by_pa( PurpleAccount *pa )
40{
41        GSList *i;
42       
43        for( i = purple_connections; i; i = i->next )
44                if( ((struct im_connection *)i->data)->proto_data == pa )
45                        return i->data;
46       
47        return NULL;
48}
49
50static struct im_connection *purple_ic_by_gc( PurpleConnection *gc )
51{
52        return purple_ic_by_pa( purple_connection_get_account( gc ) );
53}
54
55static gboolean purple_menu_cmp( const char *a, const char *b )
56{
57        while( *a && *b )
58        {
59                while( *a == '_' ) a ++;
60                while( *b == '_' ) b ++;
61                if( tolower( *a ) != tolower( *b ) )
62                        return FALSE;
63               
64                a ++;
65                b ++;
66        }
67       
68        return ( *a == '\0' && *b == '\0' );
69}
70
71static void purple_init( account_t *acc )
72{
73        PurplePlugin *prpl = purple_plugins_find_with_id( (char*) acc->prpl->data );
74        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
75        PurpleAccount *pa;
76        GList *i, *st;
77        set_t *s;
78        char help_title[64];
79        GString *help;
80       
81        help = g_string_new( "" );
82        g_string_printf( help, "BitlBee libpurple module %s (%s).\n\nSupported settings:",
83                                (char*) acc->prpl->name, prpl->info->name );
84       
85        /* Convert all protocol_options into per-account setting variables. */
86        for( i = pi->protocol_options; i; i = i->next )
87        {
88                PurpleAccountOption *o = i->data;
89                const char *name;
90                char *def = NULL;
91                set_eval eval = NULL;
92                void *eval_data = NULL;
93                GList *io = NULL;
94                GSList *opts = NULL;
95               
96                name = purple_account_option_get_setting( o );
97               
98                switch( purple_account_option_get_type( o ) )
99                {
100                case PURPLE_PREF_STRING:
101                        def = g_strdup( purple_account_option_get_default_string( o ) );
102                       
103                        g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
104                                                name, purple_account_option_get_text( o ),
105                                                "string", def );
106                       
107                        break;
108               
109                case PURPLE_PREF_INT:
110                        def = g_strdup_printf( "%d", purple_account_option_get_default_int( o ) );
111                        eval = set_eval_int;
112                       
113                        g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
114                                                name, purple_account_option_get_text( o ),
115                                                "integer", def );
116                       
117                        break;
118               
119                case PURPLE_PREF_BOOLEAN:
120                        if( purple_account_option_get_default_bool( o ) )
121                                def = g_strdup( "true" );
122                        else
123                                def = g_strdup( "false" );
124                        eval = set_eval_bool;
125                       
126                        g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
127                                                name, purple_account_option_get_text( o ),
128                                                "boolean", def );
129                       
130                        break;
131               
132                case PURPLE_PREF_STRING_LIST:
133                        def = g_strdup( purple_account_option_get_default_list_value( o ) );
134                       
135                        g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
136                                                name, purple_account_option_get_text( o ),
137                                                "list", def );
138                        g_string_append( help, "\n  Possible values: " );
139                       
140                        for( io = purple_account_option_get_list( o ); io; io = io->next )
141                        {
142                                PurpleKeyValuePair *kv = io->data;
143                                opts = g_slist_append( opts, kv->value );
144                                if( strcmp( kv->value, kv->key ) != 0 )
145                                        g_string_append_printf( help, "%s (%s), ", kv->value, kv->key );
146                                else
147                                        g_string_append_printf( help, "%s, ", kv->value );
148                        }
149                        g_string_truncate( help, help->len - 2 );
150                        eval = set_eval_list;
151                        eval_data = opts;
152                       
153                        break;
154                       
155                default:
156                        irc_usermsg( acc->irc, "Setting with unknown type: %s (%d) Expect stuff to break..\n",
157                                     name, purple_account_option_get_type( o ) );
158                        name = NULL;
159                }
160               
161                if( name != NULL )
162                {
163                        s = set_add( &acc->set, name, def, eval, acc );
164                        s->flags |= ACC_SET_OFFLINE_ONLY;
165                        s->eval_data = eval_data;
166                        g_free( def );
167                }
168        }
169       
170        g_snprintf( help_title, sizeof( help_title ), "purple %s", (char*) acc->prpl->name );
171        help_add_mem( &global.help, help_title, help->str );
172        g_string_free( help, TRUE );
173       
174        if( pi->options & OPT_PROTO_MAIL_CHECK )
175        {
176                s = set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc );
177                s->flags |= ACC_SET_OFFLINE_ONLY;
178        }
179       
180        /* Go through all away states to figure out if away/status messages
181           are possible. */
182        pa = purple_account_new( acc->user, (char*) acc->prpl->data );
183        for( st = purple_account_get_status_types( pa ); st; st = st->next )
184        {
185                PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data );
186               
187                if( prim == PURPLE_STATUS_AVAILABLE )
188                {
189                        if( purple_status_type_get_attr( st->data, "message" ) )
190                                acc->flags |= ACC_FLAG_STATUS_MESSAGE;
191                }
192                else if( prim != PURPLE_STATUS_OFFLINE )
193                {
194                        if( purple_status_type_get_attr( st->data, "message" ) )
195                                acc->flags |= ACC_FLAG_AWAY_MESSAGE;
196                }
197        }
198        purple_accounts_remove( pa );
199}
200
201static void purple_sync_settings( account_t *acc, PurpleAccount *pa )
202{
203        PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id );
204        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
205        GList *i;
206       
207        for( i = pi->protocol_options; i; i = i->next )
208        {
209                PurpleAccountOption *o = i->data;
210                const char *name;
211                set_t *s;
212               
213                name = purple_account_option_get_setting( o );
214                s = set_find( &acc->set, name );
215                if( s->value == NULL )
216                        continue;
217               
218                switch( purple_account_option_get_type( o ) )
219                {
220                case PURPLE_PREF_STRING:
221                case PURPLE_PREF_STRING_LIST:
222                        purple_account_set_string( pa, name, set_getstr( &acc->set, name ) );
223                        break;
224               
225                case PURPLE_PREF_INT:
226                        purple_account_set_int( pa, name, set_getint( &acc->set, name ) );
227                        break;
228               
229                case PURPLE_PREF_BOOLEAN:
230                        purple_account_set_bool( pa, name, set_getbool( &acc->set, name ) );
231                        break;
232               
233                default:
234                        break;
235                }
236        }
237       
238        if( pi->options & OPT_PROTO_MAIL_CHECK )
239                purple_account_set_check_mail( pa, set_getbool( &acc->set, "mail_notifications" ) );
240}
241
242static void purple_login( account_t *acc )
243{
244        struct im_connection *ic = imcb_new( acc );
245        PurpleAccount *pa;
246       
247        if( local_irc != NULL && local_irc != acc->irc )
248        {
249                irc_usermsg( acc->irc, "Daemon mode detected. Do *not* try to use libpurple in daemon mode! "
250                                       "Please use inetd or ForkDaemon mode instead." );
251                return;
252        }
253        local_irc = acc->irc;
254       
255        /* For now this is needed in the _connected() handlers if using
256           GLib event handling, to make sure we're not handling events
257           on dead connections. */
258        purple_connections = g_slist_prepend( purple_connections, ic );
259       
260        ic->proto_data = pa = purple_account_new( acc->user, (char*) acc->prpl->data );
261        purple_account_set_password( pa, acc->pass );
262        purple_sync_settings( acc, pa );
263       
264        purple_account_set_enabled( pa, "BitlBee", TRUE );
265}
266
267static void purple_logout( struct im_connection *ic )
268{
269        PurpleAccount *pa = ic->proto_data;
270       
271        purple_account_set_enabled( pa, "BitlBee", FALSE );
272        purple_connections = g_slist_remove( purple_connections, ic );
273        purple_accounts_remove( pa );
274}
275
276static int purple_buddy_msg( struct im_connection *ic, char *who, char *message, int flags )
277{
278        PurpleConversation *conv;
279       
280        if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM,
281                                                            who, ic->proto_data ) ) == NULL )
282        {
283                conv = purple_conversation_new( PURPLE_CONV_TYPE_IM,
284                                                ic->proto_data, who );
285        }
286       
287        purple_conv_im_send( purple_conversation_get_im_data( conv ), message );
288       
289        return 1;
290}
291
292static GList *purple_away_states( struct im_connection *ic )
293{
294        PurpleAccount *pa = ic->proto_data;
295        GList *st, *ret = NULL;
296       
297        for( st = purple_account_get_status_types( pa ); st; st = st->next )
298        {
299                PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data );
300                if( prim != PURPLE_STATUS_AVAILABLE && prim != PURPLE_STATUS_OFFLINE )
301                        ret = g_list_append( ret, (void*) purple_status_type_get_name( st->data ) );
302        }
303       
304        return ret;
305}
306
307static void purple_set_away( struct im_connection *ic, char *state_txt, char *message )
308{
309        PurpleAccount *pa = ic->proto_data;
310        GList *status_types = purple_account_get_status_types( pa ), *st;
311        PurpleStatusType *pst = NULL;
312        GList *args = NULL;
313       
314        for( st = status_types; st; st = st->next )
315        {
316                pst = st->data;
317               
318                if( state_txt == NULL &&
319                    purple_status_type_get_primitive( pst ) == PURPLE_STATUS_AVAILABLE )
320                        break;
321
322                if( state_txt != NULL &&
323                    g_strcasecmp( state_txt, purple_status_type_get_name( pst ) ) == 0 )
324                        break;
325        }
326       
327        if( message && purple_status_type_get_attr( pst, "message" ) )
328        {
329                args = g_list_append( args, "message" );
330                args = g_list_append( args, message );
331        }
332       
333        purple_account_set_status_list( pa, st ? purple_status_type_get_id( pst ) : "away",
334                                        TRUE, args );
335
336        g_list_free( args );
337}
338
339static void purple_add_buddy( struct im_connection *ic, char *who, char *group )
340{
341        PurpleBuddy *pb;
342       
343        pb = purple_buddy_new( (PurpleAccount*) ic->proto_data, who, NULL );
344        purple_blist_add_buddy( pb, NULL, NULL, NULL );
345        purple_account_add_buddy( (PurpleAccount*) ic->proto_data, pb );
346}
347
348static void purple_remove_buddy( struct im_connection *ic, char *who, char *group )
349{
350        PurpleBuddy *pb;
351       
352        pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who );
353        if( pb != NULL )
354        {
355                purple_account_remove_buddy( (PurpleAccount*) ic->proto_data, pb, NULL );
356                purple_blist_remove_buddy( pb );
357        }
358}
359
360static void purple_keepalive( struct im_connection *ic )
361{
362}
363
364static int purple_send_typing( struct im_connection *ic, char *who, int flags )
365{
366        PurpleTypingState state = PURPLE_NOT_TYPING;
367        PurpleConversation *conv;
368       
369        if( flags & OPT_TYPING )
370                state = PURPLE_TYPING;
371        else if( flags & OPT_THINKING )
372                state = PURPLE_TYPED;
373       
374        if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM,
375                                                            who, ic->proto_data ) ) == NULL )
376        {
377                purple_conv_im_set_typing_state( purple_conversation_get_im_data( conv ), state );
378                return 1;
379        }
380        else
381        {
382                return 0;
383        }
384}
385
386static void purple_chat_msg( struct groupchat *gc, char *message, int flags )
387{
388        PurpleConversation *pc = gc->data;
389       
390        purple_conv_chat_send( purple_conversation_get_chat_data( pc ), message );
391}
392
393struct groupchat *purple_chat_with( struct im_connection *ic, char *who )
394{
395        /* No, "of course" this won't work this way. Or in fact, it almost
396           does, but it only lets you send msgs to it, you won't receive
397           any. Instead, we have to click the virtual menu item.
398        PurpleAccount *pa = ic->proto_data;
399        PurpleConversation *pc;
400        PurpleConvChat *pcc;
401        struct groupchat *gc;
402       
403        gc = imcb_chat_new( ic, "BitlBee-libpurple groupchat" );
404        gc->data = pc = purple_conversation_new( PURPLE_CONV_TYPE_CHAT, pa, "BitlBee-libpurple groupchat" );
405        pc->ui_data = gc;
406       
407        pcc = PURPLE_CONV_CHAT( pc );
408        purple_conv_chat_add_user( pcc, ic->acc->user, "", 0, TRUE );
409        purple_conv_chat_invite_user( pcc, who, "Please join my chat", FALSE );
410        //purple_conv_chat_add_user( pcc, who, "", 0, TRUE );
411        */
412       
413        /* There went my nice afternoon. :-( */
414       
415        PurpleAccount *pa = ic->proto_data;
416        PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id );
417        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
418        PurpleBuddy *pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who );
419        PurpleMenuAction *mi;
420        GList *menu;
421        void (*callback)(PurpleBlistNode *, gpointer); /* FFFFFFFFFFFFFUUUUUUUUUUUUUU */
422       
423        if( !pb || !pi || !pi->blist_node_menu )
424                return NULL;
425       
426        menu = pi->blist_node_menu( &pb->node );
427        while( menu )
428        {
429                mi = menu->data;
430                if( purple_menu_cmp( mi->label, "initiate chat" ) ||
431                    purple_menu_cmp( mi->label, "initiate conference" ) )
432                        break;
433                menu = menu->next;
434        }
435       
436        if( menu == NULL )
437                return NULL;
438       
439        /* Call the fucker. */
440        callback = (void*) mi->callback;
441        callback( &pb->node, menu->data );
442       
443        return NULL;
444}
445
446void purple_chat_invite( struct groupchat *gc, char *who, char *message )
447{
448        PurpleConversation *pc = gc->data;
449        PurpleConvChat *pcc = PURPLE_CONV_CHAT( pc );
450       
451        purple_conv_chat_invite_user( pcc, who, message && *message ? message : "Please join my chat", FALSE );
452}
453
454void purple_chat_leave( struct groupchat *gc, char *who )
455{
456        PurpleConversation *pc = gc->data;
457       
458        purple_conversation_destroy( pc );
459}
460
461void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle );
462
463static void purple_ui_init();
464
465static PurpleCoreUiOps bee_core_uiops = 
466{
467        NULL,
468        NULL,
469        purple_ui_init,
470        NULL,
471};
472
473static void prplcb_conn_progress( PurpleConnection *gc, const char *text, size_t step, size_t step_count )
474{
475        struct im_connection *ic = purple_ic_by_gc( gc );
476       
477        imcb_log( ic, "%s", text );
478}
479
480static void prplcb_conn_connected( PurpleConnection *gc )
481{
482        struct im_connection *ic = purple_ic_by_gc( gc );
483       
484        imcb_connected( ic );
485       
486        if( gc->flags & PURPLE_CONNECTION_HTML )
487                ic->flags |= OPT_DOES_HTML;
488}
489
490static void prplcb_conn_disconnected( PurpleConnection *gc )
491{
492        struct im_connection *ic = purple_ic_by_gc( gc );
493       
494        if( ic != NULL )
495        {
496                imc_logout( ic, TRUE );
497        }
498}
499
500static void prplcb_conn_notice( PurpleConnection *gc, const char *text )
501{
502        struct im_connection *ic = purple_ic_by_gc( gc );
503       
504        if( ic != NULL )
505                imcb_log( ic, "%s", text );
506}
507
508static void prplcb_conn_report_disconnect_reason( PurpleConnection *gc, PurpleConnectionError reason, const char *text )
509{
510        struct im_connection *ic = purple_ic_by_gc( gc );
511       
512        /* PURPLE_CONNECTION_ERROR_NAME_IN_USE means concurrent login,
513           should probably handle that. */
514        if( ic != NULL )
515                imcb_error( ic, "%s", text );
516}
517
518static PurpleConnectionUiOps bee_conn_uiops =
519{
520        prplcb_conn_progress,
521        prplcb_conn_connected,
522        prplcb_conn_disconnected,
523        prplcb_conn_notice,
524        NULL,
525        NULL,
526        NULL,
527        prplcb_conn_report_disconnect_reason,
528};
529
530static void prplcb_blist_new( PurpleBlistNode *node )
531{
532        PurpleBuddy *bud = (PurpleBuddy*) node;
533       
534        if( node->type == PURPLE_BLIST_BUDDY_NODE )
535        {
536                struct im_connection *ic = purple_ic_by_pa( bud->account );
537               
538                if( ic == NULL )
539                        return;
540               
541                imcb_add_buddy( ic, bud->name, NULL );
542                if( bud->server_alias )
543                {
544                        imcb_rename_buddy( ic, bud->name, bud->server_alias );
545                        imcb_buddy_nick_hint( ic, bud->name, bud->server_alias );
546                }
547        }
548}
549
550static void prplcb_blist_update( PurpleBuddyList *list, PurpleBlistNode *node )
551{
552        PurpleBuddy *bud = (PurpleBuddy*) node;
553       
554        if( node->type == PURPLE_BLIST_BUDDY_NODE )
555        {
556                struct im_connection *ic = purple_ic_by_pa( bud->account );
557                PurpleStatus *as;
558                int flags = 0;
559               
560                if( ic == NULL )
561                        return;
562               
563                if( bud->server_alias )
564                        imcb_rename_buddy( ic, bud->name, bud->server_alias );
565               
566                flags |= purple_presence_is_online( bud->presence ) ? OPT_LOGGED_IN : 0;
567                flags |= purple_presence_is_available( bud->presence ) ? 0 : OPT_AWAY;
568               
569                as = purple_presence_get_active_status( bud->presence );
570               
571                imcb_buddy_status( ic, bud->name, flags, purple_status_get_name( as ),
572                                   purple_status_get_attr_string( as, "message" ) );
573        }
574}
575
576static void prplcb_blist_remove( PurpleBuddyList *list, PurpleBlistNode *node )
577{
578        /*
579        PurpleBuddy *bud = (PurpleBuddy*) node;
580       
581        if( node->type == PURPLE_BLIST_BUDDY_NODE )
582        {
583                struct im_connection *ic = purple_ic_by_pa( bud->account );
584               
585                if( ic == NULL )
586                        return;
587               
588                imcb_remove_buddy( ic, bud->name, NULL );
589        }
590        */
591}
592
593static PurpleBlistUiOps bee_blist_uiops =
594{
595        NULL,
596        prplcb_blist_new,
597        NULL,
598        prplcb_blist_update,
599        prplcb_blist_remove,
600};
601
602void prplcb_conv_new( PurpleConversation *conv )
603{
604        if( conv->type == PURPLE_CONV_TYPE_CHAT )
605        {
606                struct im_connection *ic = purple_ic_by_pa( conv->account );
607                struct groupchat *gc;
608               
609                gc = imcb_chat_new( ic, conv->name );
610                conv->ui_data = gc;
611                gc->data = conv;
612        }
613}
614
615void prplcb_conv_free( PurpleConversation *conv )
616{
617        struct groupchat *gc = conv->ui_data;
618       
619        imcb_chat_free( gc );
620}
621
622void prplcb_conv_add_users( PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals )
623{
624        struct groupchat *gc = conv->ui_data;
625        GList *b;
626       
627        if( !gc->joined && strcmp( conv->account->protocol_id, "prpl-msn" ) == 0 )
628        {
629                /* Work around the broken MSN module which fucks up the user's
630                   handle completely when informing him/her that he just
631                   successfully joined the room s/he just created (v2.6.6). */
632                imcb_chat_add_buddy( gc, gc->ic->acc->user );
633        }
634       
635        for( b = cbuddies; b; b = b->next )
636        {
637                PurpleConvChatBuddy *pcb = b->data;
638               
639                imcb_chat_add_buddy( gc, pcb->name );
640        }
641}
642
643void prplcb_conv_del_users( PurpleConversation *conv, GList *cbuddies )
644{
645        struct groupchat *gc = conv->ui_data;
646        GList *b;
647       
648        for( b = cbuddies; b; b = b->next )
649                imcb_chat_remove_buddy( gc, b->data, "" );
650}
651
652void prplcb_conv_chat_msg( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime )
653{
654        struct groupchat *gc = conv->ui_data;
655        PurpleBuddy *buddy;
656       
657        /* ..._SEND means it's an outgoing message, no need to echo those. */
658        if( flags & PURPLE_MESSAGE_SEND )
659                return;
660       
661        buddy = purple_find_buddy( conv->account, who );
662        if( buddy != NULL )
663                who = purple_buddy_get_name( buddy );
664       
665        imcb_chat_msg( gc, who, (char*) message, 0, mtime );
666}
667
668static void prplcb_conv_im( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime )
669{
670        struct im_connection *ic = purple_ic_by_pa( conv->account );
671        PurpleBuddy *buddy;
672       
673        /* ..._SEND means it's an outgoing message, no need to echo those. */
674        if( flags & PURPLE_MESSAGE_SEND )
675                return;
676       
677        buddy = purple_find_buddy( conv->account, who );
678        if( buddy != NULL )
679                who = purple_buddy_get_name( buddy );
680       
681        imcb_buddy_msg( ic, (char*) who, (char*) message, 0, mtime );
682}
683
684static PurpleConversationUiOps bee_conv_uiops = 
685{
686        prplcb_conv_new,           /* create_conversation  */
687        prplcb_conv_free,          /* destroy_conversation */
688        prplcb_conv_chat_msg,      /* write_chat           */
689        prplcb_conv_im,            /* write_im             */
690        NULL,                      /* write_conv           */
691        prplcb_conv_add_users,     /* chat_add_users       */
692        NULL,                      /* chat_rename_user     */
693        prplcb_conv_del_users,     /* chat_remove_users    */
694        NULL,                      /* chat_update_user     */
695        NULL,                      /* present              */
696        NULL,                      /* has_focus            */
697        NULL,                      /* custom_smiley_add    */
698        NULL,                      /* custom_smiley_write  */
699        NULL,                      /* custom_smiley_close  */
700        NULL,                      /* send_confirm         */
701};
702
703struct prplcb_request_action_data
704{
705        void *user_data, *bee_data;
706        PurpleRequestActionCb yes, no;
707        int yes_i, no_i;
708};
709
710static void prplcb_request_action_yes( void *data )
711{
712        struct prplcb_request_action_data *pqad = data;
713       
714        pqad->yes( pqad->user_data, pqad->yes_i );
715        g_free( pqad );
716}
717
718static void prplcb_request_action_no( void *data )
719{
720        struct prplcb_request_action_data *pqad = data;
721       
722        pqad->no( pqad->user_data, pqad->no_i );
723        g_free( pqad );
724}
725
726static void *prplcb_request_action( const char *title, const char *primary, const char *secondary,
727                                    int default_action, PurpleAccount *account, const char *who,
728                                    PurpleConversation *conv, void *user_data, size_t action_count,
729                                    va_list actions )
730{
731        struct prplcb_request_action_data *pqad; 
732        int i;
733        char *q;
734       
735        pqad = g_new0( struct prplcb_request_action_data, 1 );
736       
737        for( i = 0; i < action_count; i ++ )
738        {
739                char *caption;
740                void *fn;
741               
742                caption = va_arg( actions, char* );
743                fn = va_arg( actions, void* );
744               
745                if( strstr( caption, "Accept" ) )
746                {
747                        pqad->yes = fn;
748                        pqad->yes_i = i;
749                }
750                else if( strstr( caption, "Reject" ) || strstr( caption, "Cancel" ) )
751                {
752                        pqad->no = fn;
753                        pqad->no_i = i;
754                }
755        }
756       
757        pqad->user_data = user_data;
758       
759        q = g_strdup_printf( "Request: %s\n\n%s\n\n%s", title, primary, secondary );
760        pqad->bee_data = query_add( local_irc, purple_ic_by_pa( account ), q,
761                prplcb_request_action_yes, prplcb_request_action_no, pqad );
762       
763        g_free( q );
764       
765        return pqad;
766}
767
768static PurpleRequestUiOps bee_request_uiops =
769{
770        NULL,
771        NULL,
772        prplcb_request_action,
773        NULL,
774        NULL,
775        NULL,
776        NULL,
777};
778
779static void prplcb_debug_print( PurpleDebugLevel level, const char *category, const char *arg_s )
780{
781        fprintf( stderr, "DEBUG %s: %s", category, arg_s );
782}
783
784static PurpleDebugUiOps bee_debug_uiops =
785{
786        prplcb_debug_print,
787};
788
789static guint prplcb_ev_timeout_add( guint interval, GSourceFunc func, gpointer udata )
790{
791        return b_timeout_add( interval, (b_event_handler) func, udata );
792}
793
794static guint prplcb_ev_input_add( int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata )
795{
796        return b_input_add( fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata );
797}
798
799static gboolean prplcb_ev_remove( guint id )
800{
801        b_event_remove( (gint) id );
802        return TRUE;
803}
804
805static PurpleEventLoopUiOps glib_eventloops = 
806{
807        prplcb_ev_timeout_add,
808        prplcb_ev_remove,
809        prplcb_ev_input_add,
810        prplcb_ev_remove,
811};
812
813static void *prplcb_notify_email( PurpleConnection *gc, const char *subject, const char *from,
814                                  const char *to, const char *url )
815{
816        struct im_connection *ic = purple_ic_by_gc( gc );
817       
818        imcb_log( ic, "Received e-mail from %s for %s: %s <%s>", from, to, subject, url );
819       
820        return NULL;
821}
822
823static PurpleNotifyUiOps bee_notify_uiops =
824{
825        NULL,
826        prplcb_notify_email,
827};
828
829
830struct prpl_xfer_data
831{
832        PurpleXfer *xfer;
833        file_transfer_t *ft;
834        gint ready_timer;
835        char *buf;
836        int buf_len;
837};
838
839static file_transfer_t *next_ft;
840
841/* Glorious hack: We seem to have to remind at least some libpurple plugins
842   that we're ready because this info may get lost if we give it too early.
843   So just do it ten times a second. :-/ */
844static gboolean prplcb_xfer_write_request_cb( gpointer data, gint fd, b_input_condition cond )
845{
846        struct prpl_xfer_data *px = data;
847       
848        purple_xfer_ui_ready( px->xfer );
849       
850        return purple_xfer_get_type( px->xfer ) == PURPLE_XFER_RECEIVE;
851}
852
853static gboolean prpl_xfer_write_request( struct file_transfer *ft )
854{
855        struct prpl_xfer_data *px = ft->data;
856        px->ready_timer = b_timeout_add( 100, prplcb_xfer_write_request_cb, px );
857        return TRUE;
858}
859
860static gssize prplcb_xfer_write( PurpleXfer *xfer, const guchar *buffer, gssize size )
861{
862        struct prpl_xfer_data *px = xfer->ui_data;
863        gboolean st;
864       
865        b_event_remove( px->ready_timer );
866        px->ready_timer = 0;
867       
868        st = px->ft->write( px->ft, (char*) buffer, size );
869       
870        if( st && xfer->bytes_remaining == size )
871                imcb_file_finished( px->ft );
872       
873        return st ? size : 0;
874}
875
876static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len )
877{
878        struct prpl_xfer_data *px = ft->data;
879       
880        px->buf = g_memdup( buffer, len );
881        px->buf_len = len;
882       
883        //purple_xfer_ui_ready( px->xfer );
884        px->ready_timer = b_timeout_add( 0, prplcb_xfer_write_request_cb, px );
885       
886        return TRUE;
887}
888
889static void prpl_xfer_accept( struct file_transfer *ft )
890{
891        struct prpl_xfer_data *px = ft->data;
892        purple_xfer_request_accepted( px->xfer, NULL );
893        prpl_xfer_write_request( ft );
894}
895
896static void prpl_xfer_canceled( struct file_transfer *ft, char *reason )
897{
898        struct prpl_xfer_data *px = ft->data;
899        purple_xfer_request_denied( px->xfer );
900}
901
902static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond )
903{
904        PurpleXfer *xfer = data;
905        struct im_connection *ic = purple_ic_by_pa( xfer->account );
906        struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 );
907        PurpleBuddy *buddy;
908        const char *who;
909       
910        buddy = purple_find_buddy( xfer->account, xfer->who );
911        who = buddy ? purple_buddy_get_name( buddy ) : xfer->who;
912       
913        /* TODO(wilmer): After spreading some more const goodness in BitlBee,
914           remove the evil cast below. */
915        px->ft = imcb_file_send_start( ic, (char*) who, xfer->filename, xfer->size );
916        px->ft->data = px;
917        px->xfer = data;
918        px->xfer->ui_data = px;
919       
920        px->ft->accept = prpl_xfer_accept;
921        px->ft->canceled = prpl_xfer_canceled;
922        px->ft->write_request = prpl_xfer_write_request;
923       
924        return FALSE;
925}
926
927static void prplcb_xfer_new( PurpleXfer *xfer )
928{
929        if( purple_xfer_get_type( xfer ) == PURPLE_XFER_RECEIVE )
930        {
931                /* This should suppress the stupid file dialog. */
932                purple_xfer_set_local_filename( xfer, "/tmp/wtf123" );
933               
934                /* Sadly the xfer struct is still empty ATM so come back after
935                   the caller is done. */
936                b_timeout_add( 0, prplcb_xfer_new_send_cb, xfer );
937        }
938        else
939        {
940                struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 );
941               
942                px->ft = next_ft;
943                px->ft->data = px;
944                px->xfer = xfer;
945                px->xfer->ui_data = px;
946               
947                purple_xfer_set_filename( xfer, px->ft->file_name );
948                purple_xfer_set_size( xfer, px->ft->file_size );
949               
950                next_ft = NULL;
951        }
952}
953
954static void prplcb_xfer_dbg( PurpleXfer *xfer )
955{
956        fprintf( stderr, "prplcb_xfer_dbg 0x%p\n", xfer );
957}
958
959gssize prplcb_xfer_read( PurpleXfer *xfer, guchar **buffer, gssize size )
960{
961        struct prpl_xfer_data *px = xfer->ui_data;
962       
963        fprintf( stderr, "xfer_read %d %d\n", size, px->buf_len );
964
965        if( px->buf )
966        {
967                *buffer = px->buf;
968                px->buf = NULL;
969               
970                px->ft->write_request( px->ft );
971               
972                return px->buf_len;
973        }
974       
975        return 0;
976}
977
978static PurpleXferUiOps bee_xfer_uiops =
979{
980        prplcb_xfer_new,
981        prplcb_xfer_dbg,
982        prplcb_xfer_dbg,
983        prplcb_xfer_dbg,
984        prplcb_xfer_dbg,
985        prplcb_xfer_dbg,
986        prplcb_xfer_write,
987        prplcb_xfer_read,
988        prplcb_xfer_dbg,
989};
990
991static gboolean prplcb_xfer_send_cb( gpointer data, gint fd, b_input_condition cond );
992
993void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle )
994{
995        PurpleAccount *pa = ic->proto_data;
996        struct prpl_xfer_data *px;
997       
998        /* xfer_new() will pick up this variable. It's a hack but we're not
999           multi-threaded anyway. */
1000        next_ft = ft;
1001        serv_send_file( purple_account_get_connection( pa ), handle, ft->file_name );
1002       
1003        ft->write = prpl_xfer_write;
1004       
1005        px = ft->data;
1006        imcb_file_recv_start( ft );
1007       
1008        px->ready_timer = b_timeout_add( 100, prplcb_xfer_send_cb, px );
1009}
1010
1011static gboolean prplcb_xfer_send_cb( gpointer data, gint fd, b_input_condition cond )
1012{
1013        struct prpl_xfer_data *px = data;
1014       
1015        if( px->ft->status & FT_STATUS_TRANSFERRING )
1016        {
1017                fprintf( stderr, "The ft, it is ready...\n" );
1018                px->ft->write_request( px->ft );
1019               
1020                return FALSE;
1021        }
1022       
1023        return TRUE;
1024}
1025
1026static void purple_ui_init()
1027{
1028        purple_blist_set_ui_ops( &bee_blist_uiops );
1029        purple_connections_set_ui_ops( &bee_conn_uiops );
1030        purple_conversations_set_ui_ops( &bee_conv_uiops );
1031        purple_request_set_ui_ops( &bee_request_uiops );
1032        purple_notify_set_ui_ops( &bee_notify_uiops );
1033        purple_xfers_set_ui_ops( &bee_xfer_uiops );
1034        //purple_debug_set_ui_ops( &bee_debug_uiops );
1035}
1036
1037void purple_initmodule()
1038{
1039        struct prpl funcs;
1040        GList *prots;
1041        GString *help;
1042       
1043        if( B_EV_IO_READ != PURPLE_INPUT_READ ||
1044            B_EV_IO_WRITE != PURPLE_INPUT_WRITE )
1045        {
1046                /* FIXME FIXME FIXME FIXME FIXME :-) */
1047                exit( 1 );
1048        }
1049       
1050        purple_util_set_user_dir("/tmp");
1051        purple_debug_set_enabled(FALSE);
1052        purple_core_set_ui_ops(&bee_core_uiops);
1053        purple_eventloop_set_ui_ops(&glib_eventloops);
1054        if( !purple_core_init( "BitlBee") )
1055        {
1056                /* Initializing the core failed. Terminate. */
1057                fprintf( stderr, "libpurple initialization failed.\n" );
1058                abort();
1059        }
1060       
1061        /* This seems like stateful shit we don't want... */
1062        purple_set_blist(purple_blist_new());
1063        purple_blist_load();
1064       
1065        /* Meh? */
1066        purple_prefs_load();
1067       
1068        memset( &funcs, 0, sizeof( funcs ) );
1069        funcs.login = purple_login;
1070        funcs.init = purple_init;
1071        funcs.logout = purple_logout;
1072        funcs.buddy_msg = purple_buddy_msg;
1073        funcs.away_states = purple_away_states;
1074        funcs.set_away = purple_set_away;
1075        funcs.add_buddy = purple_add_buddy;
1076        funcs.remove_buddy = purple_remove_buddy;
1077        funcs.keepalive = purple_keepalive;
1078        funcs.send_typing = purple_send_typing;
1079        funcs.handle_cmp = g_strcasecmp;
1080        /* TODO(wilmer): Set these only for protocols that support them? */
1081        funcs.chat_msg = purple_chat_msg;
1082        funcs.chat_with = purple_chat_with;
1083        funcs.chat_invite = purple_chat_invite;
1084        funcs.chat_leave = purple_chat_leave;
1085        funcs.transfer_request = purple_transfer_request;
1086       
1087        help = g_string_new("BitlBee libpurple module supports the following IM protocols:\n");
1088       
1089        /* Add a protocol entry to BitlBee's structures for every protocol
1090           supported by this libpurple instance. */     
1091        for( prots = purple_plugins_get_protocols(); prots; prots = prots->next )
1092        {
1093                PurplePlugin *prot = prots->data;
1094                struct prpl *ret;
1095               
1096                ret = g_memdup( &funcs, sizeof( funcs ) );
1097                ret->name = ret->data = prot->info->id;
1098                if( strncmp( ret->name, "prpl-", 5 ) == 0 )
1099                        ret->name += 5;
1100                register_protocol( ret );
1101               
1102                g_string_append_printf( help, "\n* %s (%s)", ret->name, prot->info->name );
1103               
1104                /* libpurple doesn't define a protocol called OSCAR, but we
1105                   need it to be compatible with normal BitlBee. */
1106                if( g_strcasecmp( prot->info->id, "prpl-aim" ) == 0 )
1107                {
1108                        ret = g_memdup( &funcs, sizeof( funcs ) );
1109                        ret->name = "oscar";
1110                        ret->data = prot->info->id;
1111                        register_protocol( ret );
1112                }
1113        }
1114       
1115        g_string_append( help, "\n\nFor used protocols, more information about available "
1116                         "settings can be found using \x02help purple <protocol name>\x02" );
1117       
1118        /* Add a simple dynamically-generated help item listing all
1119           the supported protocols. */
1120        help_add_mem( &global.help, "purple", help->str );
1121        g_string_free( help, TRUE );
1122}
Note: See TracBrowser for help on using the repository browser.