source: protocols/purple/purple.c @ 15794dc

Last change on this file since 15794dc was 15794dc, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-05-02T16:03:41Z

Groupchat support "finished".

Named chatrooms are not supported yet. This only adds support for the
"chat with" command and for getting pulled into other people's chats.

  • Property mode set to 100644
File size: 30.2 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_chat_leave( struct groupchat *gc, char *who )
452{
453        PurpleConversation *pc = gc->data;
454       
455        purple_conversation_destroy( pc );
456}
457
458void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle );
459
460static void purple_ui_init();
461
462static PurpleCoreUiOps bee_core_uiops = 
463{
464        NULL,
465        NULL,
466        purple_ui_init,
467        NULL,
468};
469
470static void prplcb_conn_progress( PurpleConnection *gc, const char *text, size_t step, size_t step_count )
471{
472        struct im_connection *ic = purple_ic_by_gc( gc );
473       
474        imcb_log( ic, "%s", text );
475}
476
477static void prplcb_conn_connected( PurpleConnection *gc )
478{
479        struct im_connection *ic = purple_ic_by_gc( gc );
480       
481        imcb_connected( ic );
482       
483        if( gc->flags & PURPLE_CONNECTION_HTML )
484                ic->flags |= OPT_DOES_HTML;
485}
486
487static void prplcb_conn_disconnected( PurpleConnection *gc )
488{
489        struct im_connection *ic = purple_ic_by_gc( gc );
490       
491        if( ic != NULL )
492        {
493                imc_logout( ic, TRUE );
494        }
495}
496
497static void prplcb_conn_notice( PurpleConnection *gc, const char *text )
498{
499        struct im_connection *ic = purple_ic_by_gc( gc );
500       
501        if( ic != NULL )
502                imcb_log( ic, "%s", text );
503}
504
505static void prplcb_conn_report_disconnect_reason( PurpleConnection *gc, PurpleConnectionError reason, const char *text )
506{
507        struct im_connection *ic = purple_ic_by_gc( gc );
508       
509        /* PURPLE_CONNECTION_ERROR_NAME_IN_USE means concurrent login,
510           should probably handle that. */
511        if( ic != NULL )
512                imcb_error( ic, "%s", text );
513}
514
515static PurpleConnectionUiOps bee_conn_uiops =
516{
517        prplcb_conn_progress,
518        prplcb_conn_connected,
519        prplcb_conn_disconnected,
520        prplcb_conn_notice,
521        NULL,
522        NULL,
523        NULL,
524        prplcb_conn_report_disconnect_reason,
525};
526
527static void prplcb_blist_new( PurpleBlistNode *node )
528{
529        PurpleBuddy *bud = (PurpleBuddy*) node;
530       
531        if( node->type == PURPLE_BLIST_BUDDY_NODE )
532        {
533                struct im_connection *ic = purple_ic_by_pa( bud->account );
534               
535                if( ic == NULL )
536                        return;
537               
538                imcb_add_buddy( ic, bud->name, NULL );
539                if( bud->server_alias )
540                {
541                        imcb_rename_buddy( ic, bud->name, bud->server_alias );
542                        imcb_buddy_nick_hint( ic, bud->name, bud->server_alias );
543                }
544        }
545}
546
547static void prplcb_blist_update( PurpleBuddyList *list, PurpleBlistNode *node )
548{
549        PurpleBuddy *bud = (PurpleBuddy*) node;
550       
551        if( node->type == PURPLE_BLIST_BUDDY_NODE )
552        {
553                struct im_connection *ic = purple_ic_by_pa( bud->account );
554                PurpleStatus *as;
555                int flags = 0;
556               
557                if( ic == NULL )
558                        return;
559               
560                if( bud->server_alias )
561                        imcb_rename_buddy( ic, bud->name, bud->server_alias );
562               
563                flags |= purple_presence_is_online( bud->presence ) ? OPT_LOGGED_IN : 0;
564                flags |= purple_presence_is_available( bud->presence ) ? 0 : OPT_AWAY;
565               
566                as = purple_presence_get_active_status( bud->presence );
567               
568                imcb_buddy_status( ic, bud->name, flags, purple_status_get_name( as ),
569                                   purple_status_get_attr_string( as, "message" ) );
570        }
571}
572
573static void prplcb_blist_remove( PurpleBuddyList *list, PurpleBlistNode *node )
574{
575        /*
576        PurpleBuddy *bud = (PurpleBuddy*) node;
577       
578        if( node->type == PURPLE_BLIST_BUDDY_NODE )
579        {
580                struct im_connection *ic = purple_ic_by_pa( bud->account );
581               
582                if( ic == NULL )
583                        return;
584               
585                imcb_remove_buddy( ic, bud->name, NULL );
586        }
587        */
588}
589
590static PurpleBlistUiOps bee_blist_uiops =
591{
592        NULL,
593        prplcb_blist_new,
594        NULL,
595        prplcb_blist_update,
596        prplcb_blist_remove,
597};
598
599void prplcb_conv_new( PurpleConversation *conv )
600{
601        if( conv->type == PURPLE_CONV_TYPE_CHAT )
602        {
603                struct im_connection *ic = purple_ic_by_pa( conv->account );
604                struct groupchat *gc;
605               
606                gc = imcb_chat_new( ic, conv->name );
607                conv->ui_data = gc;
608                gc->data = conv;
609        }
610}
611
612void prplcb_conv_free( PurpleConversation *conv )
613{
614        struct groupchat *gc = conv->ui_data;
615       
616        imcb_chat_free( gc );
617}
618
619void prplcb_conv_add_users( PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals )
620{
621        struct groupchat *gc = conv->ui_data;
622        GList *b;
623       
624        if( !gc->joined && strcmp( conv->account->protocol_id, "prpl-msn" ) == 0 )
625        {
626                /* Work around the broken MSN module which fucks up the user's
627                   handle completely when informing him/her that he just
628                   successfully joined the room s/he just created (v2.6.6). */
629                imcb_chat_add_buddy( gc, gc->ic->acc->user );
630        }
631       
632        for( b = cbuddies; b; b = b->next )
633        {
634                PurpleConvChatBuddy *pcb = b->data;
635               
636                imcb_chat_add_buddy( gc, pcb->name );
637        }
638}
639
640void prplcb_conv_del_users( PurpleConversation *conv, GList *cbuddies )
641{
642        struct groupchat *gc = conv->ui_data;
643        GList *b;
644       
645        for( b = cbuddies; b; b = b->next )
646                imcb_chat_remove_buddy( gc, b->data, "" );
647}
648
649void prplcb_conv_chat_msg( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime )
650{
651        struct groupchat *gc = conv->ui_data;
652        PurpleBuddy *buddy;
653       
654        /* ..._SEND means it's an outgoing message, no need to echo those. */
655        if( flags & PURPLE_MESSAGE_SEND )
656                return;
657       
658        buddy = purple_find_buddy( conv->account, who );
659        if( buddy != NULL )
660                who = purple_buddy_get_name( buddy );
661       
662        imcb_chat_msg( gc, who, (char*) message, 0, mtime );
663}
664
665static void prplcb_conv_im( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime )
666{
667        struct im_connection *ic = purple_ic_by_pa( conv->account );
668        PurpleBuddy *buddy;
669       
670        /* ..._SEND means it's an outgoing message, no need to echo those. */
671        if( flags & PURPLE_MESSAGE_SEND )
672                return;
673       
674        buddy = purple_find_buddy( conv->account, who );
675        if( buddy != NULL )
676                who = purple_buddy_get_name( buddy );
677       
678        imcb_buddy_msg( ic, (char*) who, (char*) message, 0, mtime );
679}
680
681static PurpleConversationUiOps bee_conv_uiops = 
682{
683        prplcb_conv_new,           /* create_conversation  */
684        prplcb_conv_free,          /* destroy_conversation */
685        prplcb_conv_chat_msg,      /* write_chat           */
686        prplcb_conv_im,            /* write_im             */
687        NULL,                      /* write_conv           */
688        prplcb_conv_add_users,     /* chat_add_users       */
689        NULL,                      /* chat_rename_user     */
690        prplcb_conv_del_users,     /* chat_remove_users    */
691        NULL,                      /* chat_update_user     */
692        NULL,                      /* present              */
693        NULL,                      /* has_focus            */
694        NULL,                      /* custom_smiley_add    */
695        NULL,                      /* custom_smiley_write  */
696        NULL,                      /* custom_smiley_close  */
697        NULL,                      /* send_confirm         */
698};
699
700struct prplcb_request_action_data
701{
702        void *user_data, *bee_data;
703        PurpleRequestActionCb yes, no;
704        int yes_i, no_i;
705};
706
707static void prplcb_request_action_yes( void *data )
708{
709        struct prplcb_request_action_data *pqad = data;
710       
711        pqad->yes( pqad->user_data, pqad->yes_i );
712        g_free( pqad );
713}
714
715static void prplcb_request_action_no( void *data )
716{
717        struct prplcb_request_action_data *pqad = data;
718       
719        pqad->no( pqad->user_data, pqad->no_i );
720        g_free( pqad );
721}
722
723static void *prplcb_request_action( const char *title, const char *primary, const char *secondary,
724                                    int default_action, PurpleAccount *account, const char *who,
725                                    PurpleConversation *conv, void *user_data, size_t action_count,
726                                    va_list actions )
727{
728        struct prplcb_request_action_data *pqad; 
729        int i;
730        char *q;
731       
732        pqad = g_new0( struct prplcb_request_action_data, 1 );
733       
734        for( i = 0; i < action_count; i ++ )
735        {
736                char *caption;
737                void *fn;
738               
739                caption = va_arg( actions, char* );
740                fn = va_arg( actions, void* );
741               
742                if( strstr( caption, "Accept" ) )
743                {
744                        pqad->yes = fn;
745                        pqad->yes_i = i;
746                }
747                else if( strstr( caption, "Reject" ) || strstr( caption, "Cancel" ) )
748                {
749                        pqad->no = fn;
750                        pqad->no_i = i;
751                }
752        }
753       
754        pqad->user_data = user_data;
755       
756        q = g_strdup_printf( "Request: %s\n\n%s\n\n%s", title, primary, secondary );
757        pqad->bee_data = query_add( local_irc, purple_ic_by_pa( account ), q,
758                prplcb_request_action_yes, prplcb_request_action_no, pqad );
759       
760        g_free( q );
761       
762        return pqad;
763}
764
765static PurpleRequestUiOps bee_request_uiops =
766{
767        NULL,
768        NULL,
769        prplcb_request_action,
770        NULL,
771        NULL,
772        NULL,
773        NULL,
774};
775
776static void prplcb_debug_print( PurpleDebugLevel level, const char *category, const char *arg_s )
777{
778        fprintf( stderr, "DEBUG %s: %s", category, arg_s );
779}
780
781static PurpleDebugUiOps bee_debug_uiops =
782{
783        prplcb_debug_print,
784};
785
786static guint prplcb_ev_timeout_add( guint interval, GSourceFunc func, gpointer udata )
787{
788        return b_timeout_add( interval, (b_event_handler) func, udata );
789}
790
791static guint prplcb_ev_input_add( int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata )
792{
793        return b_input_add( fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata );
794}
795
796static gboolean prplcb_ev_remove( guint id )
797{
798        b_event_remove( (gint) id );
799        return TRUE;
800}
801
802static PurpleEventLoopUiOps glib_eventloops = 
803{
804        prplcb_ev_timeout_add,
805        prplcb_ev_remove,
806        prplcb_ev_input_add,
807        prplcb_ev_remove,
808};
809
810static void *prplcb_notify_email( PurpleConnection *gc, const char *subject, const char *from,
811                                  const char *to, const char *url )
812{
813        struct im_connection *ic = purple_ic_by_gc( gc );
814       
815        imcb_log( ic, "Received e-mail from %s for %s: %s <%s>", from, to, subject, url );
816       
817        return NULL;
818}
819
820static PurpleNotifyUiOps bee_notify_uiops =
821{
822        NULL,
823        prplcb_notify_email,
824};
825
826
827struct prpl_xfer_data
828{
829        PurpleXfer *xfer;
830        file_transfer_t *ft;
831        gint ready_timer;
832        char *buf;
833        int buf_len;
834};
835
836static file_transfer_t *next_ft;
837
838/* Glorious hack: We seem to have to remind at least some libpurple plugins
839   that we're ready because this info may get lost if we give it too early.
840   So just do it ten times a second. :-/ */
841static gboolean prplcb_xfer_write_request_cb( gpointer data, gint fd, b_input_condition cond )
842{
843        struct prpl_xfer_data *px = data;
844       
845        purple_xfer_ui_ready( px->xfer );
846       
847        return purple_xfer_get_type( px->xfer ) == PURPLE_XFER_RECEIVE;
848}
849
850static gboolean prpl_xfer_write_request( struct file_transfer *ft )
851{
852        struct prpl_xfer_data *px = ft->data;
853        px->ready_timer = b_timeout_add( 100, prplcb_xfer_write_request_cb, px );
854        return TRUE;
855}
856
857static gssize prplcb_xfer_write( PurpleXfer *xfer, const guchar *buffer, gssize size )
858{
859        struct prpl_xfer_data *px = xfer->ui_data;
860        gboolean st;
861       
862        b_event_remove( px->ready_timer );
863        px->ready_timer = 0;
864       
865        st = px->ft->write( px->ft, (char*) buffer, size );
866       
867        if( st && xfer->bytes_remaining == size )
868                imcb_file_finished( px->ft );
869       
870        return st ? size : 0;
871}
872
873static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len )
874{
875        struct prpl_xfer_data *px = ft->data;
876       
877        px->buf = g_memdup( buffer, len );
878        px->buf_len = len;
879       
880        //purple_xfer_ui_ready( px->xfer );
881        px->ready_timer = b_timeout_add( 0, prplcb_xfer_write_request_cb, px );
882       
883        return TRUE;
884}
885
886static void prpl_xfer_accept( struct file_transfer *ft )
887{
888        struct prpl_xfer_data *px = ft->data;
889        purple_xfer_request_accepted( px->xfer, NULL );
890        prpl_xfer_write_request( ft );
891}
892
893static void prpl_xfer_canceled( struct file_transfer *ft, char *reason )
894{
895        struct prpl_xfer_data *px = ft->data;
896        purple_xfer_request_denied( px->xfer );
897}
898
899static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond )
900{
901        PurpleXfer *xfer = data;
902        struct im_connection *ic = purple_ic_by_pa( xfer->account );
903        struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 );
904        PurpleBuddy *buddy;
905        const char *who;
906       
907        buddy = purple_find_buddy( xfer->account, xfer->who );
908        who = buddy ? purple_buddy_get_name( buddy ) : xfer->who;
909       
910        /* TODO(wilmer): After spreading some more const goodness in BitlBee,
911           remove the evil cast below. */
912        px->ft = imcb_file_send_start( ic, (char*) who, xfer->filename, xfer->size );
913        px->ft->data = px;
914        px->xfer = data;
915        px->xfer->ui_data = px;
916       
917        px->ft->accept = prpl_xfer_accept;
918        px->ft->canceled = prpl_xfer_canceled;
919        px->ft->write_request = prpl_xfer_write_request;
920       
921        return FALSE;
922}
923
924static void prplcb_xfer_new( PurpleXfer *xfer )
925{
926        if( purple_xfer_get_type( xfer ) == PURPLE_XFER_RECEIVE )
927        {
928                /* This should suppress the stupid file dialog. */
929                purple_xfer_set_local_filename( xfer, "/tmp/wtf123" );
930               
931                /* Sadly the xfer struct is still empty ATM so come back after
932                   the caller is done. */
933                b_timeout_add( 0, prplcb_xfer_new_send_cb, xfer );
934        }
935        else
936        {
937                struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 );
938               
939                px->ft = next_ft;
940                px->ft->data = px;
941                px->xfer = xfer;
942                px->xfer->ui_data = px;
943               
944                purple_xfer_set_filename( xfer, px->ft->file_name );
945                purple_xfer_set_size( xfer, px->ft->file_size );
946               
947                next_ft = NULL;
948        }
949}
950
951static void prplcb_xfer_dbg( PurpleXfer *xfer )
952{
953        fprintf( stderr, "prplcb_xfer_dbg 0x%p\n", xfer );
954}
955
956gssize prplcb_xfer_read( PurpleXfer *xfer, guchar **buffer, gssize size )
957{
958        struct prpl_xfer_data *px = xfer->ui_data;
959       
960        fprintf( stderr, "xfer_read %d %d\n", size, px->buf_len );
961
962        if( px->buf )
963        {
964                *buffer = px->buf;
965                px->buf = NULL;
966               
967                px->ft->write_request( px->ft );
968               
969                return px->buf_len;
970        }
971       
972        return 0;
973}
974
975static PurpleXferUiOps bee_xfer_uiops =
976{
977        prplcb_xfer_new,
978        prplcb_xfer_dbg,
979        prplcb_xfer_dbg,
980        prplcb_xfer_dbg,
981        prplcb_xfer_dbg,
982        prplcb_xfer_dbg,
983        prplcb_xfer_write,
984        prplcb_xfer_read,
985        prplcb_xfer_dbg,
986};
987
988static gboolean prplcb_xfer_send_cb( gpointer data, gint fd, b_input_condition cond );
989
990void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle )
991{
992        PurpleAccount *pa = ic->proto_data;
993        struct prpl_xfer_data *px;
994       
995        /* xfer_new() will pick up this variable. It's a hack but we're not
996           multi-threaded anyway. */
997        next_ft = ft;
998        serv_send_file( purple_account_get_connection( pa ), handle, ft->file_name );
999       
1000        ft->write = prpl_xfer_write;
1001       
1002        px = ft->data;
1003        imcb_file_recv_start( ft );
1004       
1005        px->ready_timer = b_timeout_add( 100, prplcb_xfer_send_cb, px );
1006}
1007
1008static gboolean prplcb_xfer_send_cb( gpointer data, gint fd, b_input_condition cond )
1009{
1010        struct prpl_xfer_data *px = data;
1011       
1012        if( px->ft->status & FT_STATUS_TRANSFERRING )
1013        {
1014                fprintf( stderr, "The ft, it is ready...\n" );
1015                px->ft->write_request( px->ft );
1016               
1017                return FALSE;
1018        }
1019       
1020        return TRUE;
1021}
1022
1023static void purple_ui_init()
1024{
1025        purple_blist_set_ui_ops( &bee_blist_uiops );
1026        purple_connections_set_ui_ops( &bee_conn_uiops );
1027        purple_conversations_set_ui_ops( &bee_conv_uiops );
1028        purple_request_set_ui_ops( &bee_request_uiops );
1029        purple_notify_set_ui_ops( &bee_notify_uiops );
1030        purple_xfers_set_ui_ops( &bee_xfer_uiops );
1031        //purple_debug_set_ui_ops( &bee_debug_uiops );
1032}
1033
1034void purple_initmodule()
1035{
1036        struct prpl funcs;
1037        GList *prots;
1038        GString *help;
1039       
1040        if( B_EV_IO_READ != PURPLE_INPUT_READ ||
1041            B_EV_IO_WRITE != PURPLE_INPUT_WRITE )
1042        {
1043                /* FIXME FIXME FIXME FIXME FIXME :-) */
1044                exit( 1 );
1045        }
1046       
1047        purple_util_set_user_dir("/tmp");
1048        purple_debug_set_enabled(FALSE);
1049        purple_core_set_ui_ops(&bee_core_uiops);
1050        purple_eventloop_set_ui_ops(&glib_eventloops);
1051        if( !purple_core_init( "BitlBee") )
1052        {
1053                /* Initializing the core failed. Terminate. */
1054                fprintf( stderr, "libpurple initialization failed.\n" );
1055                abort();
1056        }
1057       
1058        /* This seems like stateful shit we don't want... */
1059        purple_set_blist(purple_blist_new());
1060        purple_blist_load();
1061       
1062        /* Meh? */
1063        purple_prefs_load();
1064       
1065        memset( &funcs, 0, sizeof( funcs ) );
1066        funcs.login = purple_login;
1067        funcs.init = purple_init;
1068        funcs.logout = purple_logout;
1069        funcs.buddy_msg = purple_buddy_msg;
1070        funcs.away_states = purple_away_states;
1071        funcs.set_away = purple_set_away;
1072        funcs.add_buddy = purple_add_buddy;
1073        funcs.remove_buddy = purple_remove_buddy;
1074        funcs.keepalive = purple_keepalive;
1075        funcs.send_typing = purple_send_typing;
1076        funcs.handle_cmp = g_strcasecmp;
1077        /* TODO(wilmer): Set these only for protocols that support them? */
1078        funcs.chat_msg = purple_chat_msg;
1079        funcs.chat_with = purple_chat_with;
1080        funcs.chat_invite = purple_chat_invite;
1081        funcs.chat_leave = purple_chat_leave;
1082        funcs.transfer_request = purple_transfer_request;
1083       
1084        help = g_string_new("BitlBee libpurple module supports the following IM protocols:\n");
1085       
1086        /* Add a protocol entry to BitlBee's structures for every protocol
1087           supported by this libpurple instance. */     
1088        for( prots = purple_plugins_get_protocols(); prots; prots = prots->next )
1089        {
1090                PurplePlugin *prot = prots->data;
1091                struct prpl *ret;
1092               
1093                ret = g_memdup( &funcs, sizeof( funcs ) );
1094                ret->name = ret->data = prot->info->id;
1095                if( strncmp( ret->name, "prpl-", 5 ) == 0 )
1096                        ret->name += 5;
1097                register_protocol( ret );
1098               
1099                g_string_append_printf( help, "\n* %s (%s)", ret->name, prot->info->name );
1100               
1101                /* libpurple doesn't define a protocol called OSCAR, but we
1102                   need it to be compatible with normal BitlBee. */
1103                if( g_strcasecmp( prot->info->id, "prpl-aim" ) == 0 )
1104                {
1105                        ret = g_memdup( &funcs, sizeof( funcs ) );
1106                        ret->name = "oscar";
1107                        ret->data = prot->info->id;
1108                        register_protocol( ret );
1109                }
1110        }
1111       
1112        g_string_append( help, "\n\nFor used protocols, more information about available "
1113                         "settings can be found using \x02help purple <protocol name>\x02" );
1114       
1115        /* Add a simple dynamically-generated help item listing all
1116           the supported protocols. */
1117        help_add_mem( &global.help, "purple", help->str );
1118        g_string_free( help, TRUE );
1119}
Note: See TracBrowser for help on using the repository browser.