source: protocols/purple/purple.c @ bab1c86

Last change on this file since bab1c86 was bab1c86, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-03-08T01:21:08Z

Mail notifications, partially from http://irc.nfx.cz/patches/notify.patch
written by sd@ircnet.

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