source: protocols/purple/purple.c @ 279607e

Last change on this file since 279607e was 279607e, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-03-07T22:35:00Z

Fixed purple module to work with the new away interface.

  • Property mode set to 100644
File size: 17.2 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  libpurple module - Main file                                             *
5*                                                                           *
6*  Copyright 2009 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        GList *i;
60       
61        for( i = pi->protocol_options; i; i = i->next )
62        {
63                PurpleAccountOption *o = i->data;
64                const char *name;
65                char *def = NULL;
66                set_eval eval = NULL;
67                set_t *s;
68               
69                name = purple_account_option_get_setting( o );
70               
71                switch( purple_account_option_get_type( o ) )
72                {
73                case PURPLE_PREF_STRING:
74                        def = g_strdup( purple_account_option_get_default_string( o ) );
75                        break;
76               
77                case PURPLE_PREF_INT:
78                        def = g_strdup_printf( "%d", purple_account_option_get_default_int( o ) );
79                        eval = set_eval_int;
80                        break;
81               
82                case PURPLE_PREF_BOOLEAN:
83                        if( purple_account_option_get_default_bool( o ) )
84                                def = g_strdup( "true" );
85                        else
86                                def = g_strdup( "false" );
87                        eval = set_eval_bool;
88                        break;
89               
90                default:
91                        fprintf( stderr, "Setting with unknown type: %s (%d)\n", name, purple_account_option_get_type( o ) );
92                        name = NULL;
93                }
94               
95                if( name != NULL )
96                {
97                        s = set_add( &acc->set, name, def, eval, acc );
98                        s->flags |= ACC_SET_OFFLINE_ONLY;
99                        g_free( def );
100                }
101        }
102}
103
104static void purple_sync_settings( account_t *acc, PurpleAccount *pa )
105{
106        PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id );
107        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
108        GList *i;
109       
110        for( i = pi->protocol_options; i; i = i->next )
111        {
112                PurpleAccountOption *o = i->data;
113                const char *name;
114                set_t *s;
115               
116                name = purple_account_option_get_setting( o );
117                s = set_find( &acc->set, name );
118                if( s->value == NULL )
119                        continue;
120               
121                switch( purple_account_option_get_type( o ) )
122                {
123                case PURPLE_PREF_STRING:
124                        purple_account_set_string( pa, name, set_getstr( &acc->set, name ) );
125                        break;
126               
127                case PURPLE_PREF_INT:
128                        purple_account_set_int( pa, name, set_getint( &acc->set, name ) );
129                        break;
130               
131                case PURPLE_PREF_BOOLEAN:
132                        purple_account_set_bool( pa, name, set_getbool( &acc->set, name ) );
133                        break;
134               
135                default:
136                        break;
137                }
138        }
139}
140
141static void purple_login( account_t *acc )
142{
143        struct im_connection *ic = imcb_new( acc );
144        PurpleAccount *pa;
145       
146        if( local_irc != NULL && local_irc != acc->irc )
147        {
148                irc_usermsg( acc->irc, "Daemon mode detected. Do *not* try to use libpurple in daemon mode! "
149                                       "Please use inetd or ForkDaemon mode instead." );
150                return;
151        }
152        local_irc = acc->irc;
153       
154        /* For now this is needed in the _connected() handlers if using
155           GLib event handling, to make sure we're not handling events
156           on dead connections. */
157        purple_connections = g_slist_prepend( purple_connections, ic );
158       
159        ic->proto_data = pa = purple_account_new( acc->user, (char*) acc->prpl->data );
160        purple_account_set_password( pa, acc->pass );
161        purple_sync_settings( acc, pa );
162       
163        purple_account_set_enabled( pa, "BitlBee", TRUE );
164}
165
166static void purple_logout( struct im_connection *ic )
167{
168        PurpleAccount *pa = ic->proto_data;
169       
170        purple_account_set_enabled( pa, "BitlBee", FALSE );
171        purple_connections = g_slist_remove( purple_connections, ic );
172        purple_accounts_remove( pa );
173}
174
175static int purple_buddy_msg( struct im_connection *ic, char *who, char *message, int flags )
176{
177        PurpleConversation *conv;
178       
179        if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM,
180                                                            who, ic->proto_data ) ) == NULL )
181        {
182                conv = purple_conversation_new( PURPLE_CONV_TYPE_IM,
183                                                ic->proto_data, who );
184        }
185       
186        purple_conv_im_send( purple_conversation_get_im_data( conv ), message );
187       
188        return 1;
189}
190
191static GList *purple_away_states( struct im_connection *ic )
192{
193        PurpleAccount *pa = ic->proto_data;
194        GList *st, *ret = NULL;
195       
196        for( st = purple_account_get_status_types( pa ); st; st = st->next )
197        {
198                PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data );
199                if( prim != PURPLE_STATUS_AVAILABLE && prim != PURPLE_STATUS_OFFLINE )
200                        ret = g_list_append( ret, (void*) purple_status_type_get_name( st->data ) );
201        }
202       
203        return ret;
204}
205
206static void purple_set_away( struct im_connection *ic, char *state_txt, char *message )
207{
208        PurpleAccount *pa = ic->proto_data;
209        GList *status_types = purple_account_get_status_types( pa ), *st;
210        PurpleStatusType *pst = NULL;
211        GList *args = NULL;
212       
213        for( st = status_types; st; st = st->next )
214        {
215                pst = st->data;
216               
217                if( state_txt == NULL &&
218                    purple_status_type_get_primitive( st->data ) == PURPLE_STATUS_AVAILABLE )
219                        break;
220
221                if( state_txt != NULL &&
222                    g_strcasecmp( state_txt, purple_status_type_get_name( pst ) ) == 0 )
223                        break;
224        }
225       
226        if( message )
227        {
228                args = g_list_append( args, "message" );
229                args = g_list_append( args, message );
230        }
231       
232        purple_account_set_status_list( pa, st ? purple_status_type_get_id( pst ) : "away",
233                                        TRUE, args );
234
235        g_list_free( args );
236}
237
238static void purple_add_buddy( struct im_connection *ic, char *who, char *group )
239{
240        PurpleBuddy *pb;
241       
242        pb = purple_buddy_new( (PurpleAccount*) ic->proto_data, who, NULL );
243        purple_blist_add_buddy( pb, NULL, NULL, NULL );
244        purple_account_add_buddy( (PurpleAccount*) ic->proto_data, pb );
245}
246
247static void purple_remove_buddy( struct im_connection *ic, char *who, char *group )
248{
249        PurpleBuddy *pb;
250       
251        pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who );
252        if( pb != NULL )
253        {
254                purple_account_remove_buddy( (PurpleAccount*) ic->proto_data, pb, NULL );
255                purple_blist_remove_buddy( pb );
256        }
257}
258
259static void purple_keepalive( struct im_connection *ic )
260{
261}
262
263static int purple_send_typing( struct im_connection *ic, char *who, int flags )
264{
265        PurpleTypingState state = PURPLE_NOT_TYPING;
266        PurpleConversation *conv;
267       
268        if( flags & OPT_TYPING )
269                state = PURPLE_TYPING;
270        else if( flags & OPT_THINKING )
271                state = PURPLE_TYPED;
272       
273        if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM,
274                                                            who, ic->proto_data ) ) == NULL )
275        {
276                purple_conv_im_set_typing_state( purple_conversation_get_im_data( conv ), state );
277                return 1;
278        }
279        else
280        {
281                return 0;
282        }
283}
284
285static void purple_ui_init();
286
287static PurpleCoreUiOps bee_core_uiops = 
288{
289        NULL,
290        NULL,
291        purple_ui_init,
292        NULL,
293};
294
295static void prplcb_conn_progress( PurpleConnection *gc, const char *text, size_t step, size_t step_count )
296{
297        struct im_connection *ic = purple_ic_by_gc( gc );
298       
299        imcb_log( ic, "%s", text );
300}
301
302static void prplcb_conn_connected( PurpleConnection *gc )
303{
304        struct im_connection *ic = purple_ic_by_gc( gc );
305       
306        imcb_connected( ic );
307       
308        if( gc->flags & PURPLE_CONNECTION_HTML )
309                ic->flags |= OPT_DOES_HTML;
310}
311
312static void prplcb_conn_disconnected( PurpleConnection *gc )
313{
314        struct im_connection *ic = purple_ic_by_gc( gc );
315       
316        if( ic != NULL )
317        {
318                imc_logout( ic, TRUE );
319        }
320}
321
322static void prplcb_conn_notice( PurpleConnection *gc, const char *text )
323{
324        struct im_connection *ic = purple_ic_by_gc( gc );
325       
326        if( ic != NULL )
327                imcb_log( ic, "%s", text );
328}
329
330static void prplcb_conn_report_disconnect_reason( PurpleConnection *gc, PurpleConnectionError reason, const char *text )
331{
332        struct im_connection *ic = purple_ic_by_gc( gc );
333       
334        /* PURPLE_CONNECTION_ERROR_NAME_IN_USE means concurrent login,
335           should probably handle that. */
336        if( ic != NULL )
337                imcb_error( ic, "%s", text );
338}
339
340static PurpleConnectionUiOps bee_conn_uiops =
341{
342        prplcb_conn_progress,
343        prplcb_conn_connected,
344        prplcb_conn_disconnected,
345        prplcb_conn_notice,
346        NULL,
347        NULL,
348        NULL,
349        prplcb_conn_report_disconnect_reason,
350};
351
352static void prplcb_blist_new( PurpleBlistNode *node )
353{
354        PurpleBuddy *bud = (PurpleBuddy*) node;
355       
356        if( node->type == PURPLE_BLIST_BUDDY_NODE )
357        {
358                struct im_connection *ic = purple_ic_by_pa( bud->account );
359               
360                if( ic == NULL )
361                        return;
362               
363                imcb_add_buddy( ic, bud->name, NULL );
364                if( bud->server_alias )
365                {
366                        imcb_rename_buddy( ic, bud->name, bud->server_alias );
367                        imcb_buddy_nick_hint( ic, bud->name, bud->server_alias );
368                }
369        }
370}
371
372static void prplcb_blist_update( PurpleBuddyList *list, PurpleBlistNode *node )
373{
374        PurpleBuddy *bud = (PurpleBuddy*) node;
375       
376        if( node->type == PURPLE_BLIST_BUDDY_NODE )
377        {
378                struct im_connection *ic = purple_ic_by_pa( bud->account );
379                PurpleStatus *as;
380                int flags = 0;
381               
382                if( ic == NULL )
383                        return;
384               
385                if( bud->server_alias )
386                        imcb_rename_buddy( ic, bud->name, bud->server_alias );
387               
388                flags |= purple_presence_is_online( bud->presence ) ? OPT_LOGGED_IN : 0;
389                flags |= purple_presence_is_available( bud->presence ) ? 0 : OPT_AWAY;
390               
391                as = purple_presence_get_active_status( bud->presence );
392               
393                imcb_buddy_status( ic, bud->name, flags, purple_status_get_name( as ),
394                                   purple_status_get_attr_string( as, "message" ) );
395        }
396}
397
398static void prplcb_blist_remove( PurpleBuddyList *list, PurpleBlistNode *node )
399{
400        /*
401        PurpleBuddy *bud = (PurpleBuddy*) node;
402       
403        if( node->type == PURPLE_BLIST_BUDDY_NODE )
404        {
405                struct im_connection *ic = purple_ic_by_pa( bud->account );
406               
407                if( ic == NULL )
408                        return;
409               
410                imcb_remove_buddy( ic, bud->name, NULL );
411        }
412        */
413}
414
415static PurpleBlistUiOps bee_blist_uiops =
416{
417        NULL,
418        prplcb_blist_new,
419        NULL,
420        prplcb_blist_update,
421        prplcb_blist_remove,
422};
423
424static void prplcb_conv_im( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime )
425{
426        struct im_connection *ic = purple_ic_by_pa( conv->account );
427        PurpleBuddy *buddy;
428       
429        /* ..._SEND means it's an outgoing message, no need to echo those. */
430        if( flags & PURPLE_MESSAGE_SEND )
431                return;
432       
433        buddy = purple_find_buddy( conv->account, who );
434        if( buddy != NULL )
435                who = purple_buddy_get_name( buddy );
436       
437        imcb_buddy_msg( ic, (char*) who, (char*) message, 0, mtime );
438}
439
440static PurpleConversationUiOps bee_conv_uiops = 
441{
442        NULL,                      /* create_conversation  */
443        NULL,                      /* destroy_conversation */
444        NULL,                      /* write_chat           */
445        prplcb_conv_im,            /* write_im             */
446        NULL,                      /* write_conv           */
447        NULL,                      /* chat_add_users       */
448        NULL,                      /* chat_rename_user     */
449        NULL,                      /* chat_remove_users    */
450        NULL,                      /* chat_update_user     */
451        NULL,                      /* present              */
452        NULL,                      /* has_focus            */
453        NULL,                      /* custom_smiley_add    */
454        NULL,                      /* custom_smiley_write  */
455        NULL,                      /* custom_smiley_close  */
456        NULL,                      /* send_confirm         */
457};
458
459struct prplcb_request_action_data
460{
461        void *user_data, *bee_data;
462        PurpleRequestActionCb yes, no;
463        int yes_i, no_i;
464};
465
466static void prplcb_request_action_yes( void *data )
467{
468        struct prplcb_request_action_data *pqad = data;
469       
470        pqad->yes( pqad->user_data, pqad->yes_i );
471        g_free( pqad );
472}
473
474static void prplcb_request_action_no( void *data )
475{
476        struct prplcb_request_action_data *pqad = data;
477       
478        pqad->no( pqad->user_data, pqad->no_i );
479        g_free( pqad );
480}
481
482static void *prplcb_request_action( const char *title, const char *primary, const char *secondary,
483                                    int default_action, PurpleAccount *account, const char *who,
484                                    PurpleConversation *conv, void *user_data, size_t action_count,
485                                    va_list actions )
486{
487        struct prplcb_request_action_data *pqad; 
488        int i;
489        char *q;
490       
491        pqad = g_new0( struct prplcb_request_action_data, 1 );
492       
493        for( i = 0; i < action_count; i ++ )
494        {
495                char *caption;
496                void *fn;
497               
498                caption = va_arg( actions, char* );
499                fn = va_arg( actions, void* );
500               
501                if( strcmp( caption, "Accept" ) == 0 )
502                {
503                        pqad->yes = fn;
504                        pqad->yes_i = i;
505                }
506                else if( strcmp( caption, "Reject" ) == 0 )
507                {
508                        pqad->no = fn;
509                        pqad->no_i = i;
510                }
511        }
512       
513        pqad->user_data = user_data;
514       
515        q = g_strdup_printf( "Request: %s\n\n%s\n\n%s", title, primary, secondary );
516        pqad->bee_data = query_add( local_irc, purple_ic_by_pa( account ), q,
517                prplcb_request_action_yes, prplcb_request_action_no, pqad );
518       
519        g_free( q );
520       
521        return pqad;
522}
523
524static PurpleRequestUiOps bee_request_uiops =
525{
526        NULL,
527        NULL,
528        prplcb_request_action,
529        NULL,
530        NULL,
531        NULL,
532        NULL,
533};
534
535static void prplcb_debug_print( PurpleDebugLevel level, const char *category, const char *arg_s )
536{
537        fprintf( stderr, "DEBUG %s: %s", category, arg_s );
538}
539
540static PurpleDebugUiOps bee_debug_uiops =
541{
542        prplcb_debug_print,
543};
544
545static guint prplcb_ev_timeout_add( guint interval, GSourceFunc func, gpointer udata )
546{
547        return b_timeout_add( interval, (b_event_handler) func, udata );
548}
549
550static guint prplcb_ev_input_add( int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata )
551{
552        return b_input_add( fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata );
553}
554
555static gboolean prplcb_ev_remove( guint id )
556{
557        b_event_remove( (gint) id );
558        return TRUE;
559}
560
561static PurpleEventLoopUiOps glib_eventloops = 
562{
563        prplcb_ev_timeout_add,
564        prplcb_ev_remove,
565        prplcb_ev_input_add,
566        prplcb_ev_remove,
567};
568
569static void purple_ui_init()
570{
571        purple_blist_set_ui_ops( &bee_blist_uiops );
572        purple_connections_set_ui_ops( &bee_conn_uiops );
573        purple_conversations_set_ui_ops( &bee_conv_uiops );
574        purple_request_set_ui_ops( &bee_request_uiops );
575        //purple_debug_set_ui_ops( &bee_debug_uiops );
576}
577
578void purple_initmodule()
579{
580        struct prpl funcs;
581        GList *prots;
582        GString *help;
583       
584        if( B_EV_IO_READ != PURPLE_INPUT_READ ||
585            B_EV_IO_WRITE != PURPLE_INPUT_WRITE )
586        {
587                /* FIXME FIXME FIXME FIXME FIXME :-) */
588                exit( 1 );
589        }
590       
591        purple_util_set_user_dir("/tmp");
592        purple_debug_set_enabled(FALSE);
593        purple_core_set_ui_ops(&bee_core_uiops);
594        purple_eventloop_set_ui_ops(&glib_eventloops);
595        if( !purple_core_init( "BitlBee") )
596        {
597                /* Initializing the core failed. Terminate. */
598                fprintf( stderr, "libpurple initialization failed.\n" );
599                abort();
600        }
601       
602        /* This seems like stateful shit we don't want... */
603        purple_set_blist(purple_blist_new());
604        purple_blist_load();
605       
606        /* Meh? */
607        purple_prefs_load();
608       
609        memset( &funcs, 0, sizeof( funcs ) );
610        funcs.login = purple_login;
611        funcs.init = purple_init;
612        funcs.logout = purple_logout;
613        funcs.buddy_msg = purple_buddy_msg;
614        funcs.away_states = purple_away_states;
615        funcs.set_away = purple_set_away;
616        funcs.add_buddy = purple_add_buddy;
617        funcs.remove_buddy = purple_remove_buddy;
618        funcs.keepalive = purple_keepalive;
619        funcs.send_typing = purple_send_typing;
620        funcs.handle_cmp = g_strcasecmp;
621       
622        help = g_string_new("BitlBee libpurple module supports the following IM protocols:\n");
623       
624        for( prots = purple_plugins_get_protocols(); prots; prots = prots->next )
625        {
626                PurplePlugin *prot = prots->data;
627                struct prpl *ret;
628               
629                ret = g_memdup( &funcs, sizeof( funcs ) );
630                ret->name = ret->data = prot->info->id;
631                if( strncmp( ret->name, "prpl-", 5 ) == 0 )
632                        ret->name += 5;
633                register_protocol( ret );
634               
635                g_string_append_printf( help, "\n* %s (%s)", ret->name, prot->info->name );
636               
637                if( g_strcasecmp( prot->info->id, "prpl-aim" ) == 0 )
638                {
639                        ret = g_memdup( &funcs, sizeof( funcs ) );
640                        ret->name = "oscar";
641                        ret->data = prot->info->id;
642                        register_protocol( ret );
643                }
644        }
645       
646        help_add_mem( &global.help, "purple", help->str );
647        g_string_free( help, TRUE );
648}
Note: See TracBrowser for help on using the repository browser.