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

Last change on this file since 8ad5c34 was 8ad5c34, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-05-02T15:53:18Z

Added support for creating groupchats. This can only be done in a horribly
broken way which is surely going to break somehow someday.

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