source: protocols/purple/purple.c @ f485008

Last change on this file since f485008 was f485008, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-05-01T23:55:48Z

Support at least incoming groupchats. Not sure yet how starting them is
going to work.

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