source: protocols/purple/purple.c @ 64768d4

Last change on this file since 64768d4 was bad41f56, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-08-19T23:42:11Z

libpurple: Fix typing notifications (in and out). Closes #671.

  • Property mode set to 100644
File size: 34.0 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  libpurple module - Main file                                             *
5*                                                                           *
6*  Copyright 2009-2010 Wilmer van der Gaast <wilmer@gaast.net>              *
7*                                                                           *
8*  This program is free software; you can redistribute it and/or modify     *
9*  it under the terms of the GNU General Public License as published by     *
10*  the Free Software Foundation; either version 2 of the License, or        *
11*  (at your option) any later version.                                      *
12*                                                                           *
13*  This program is distributed in the hope that it will be useful,          *
14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
16*  GNU General Public License for more details.                             *
17*                                                                           *
18*  You should have received a copy of the GNU General Public License along  *
19*  with this program; if not, write to the Free Software Foundation, Inc.,  *
20*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
21*                                                                           *
22\***************************************************************************/
23
24#include "bitlbee.h"
25#include "help.h"
26
27#include <stdarg.h>
28
29#include <glib.h>
30#include <purple.h>
31
32GSList *purple_connections;
33
34/* This makes me VERY sad... :-( But some libpurple callbacks come in without
35   any context so this is the only way to get that. Don't want to support
36   libpurple in daemon mode anyway. */
37static bee_t *local_bee;
38
39static char *set_eval_display_name( set_t *set, char *value );
40
41struct im_connection *purple_ic_by_pa( PurpleAccount *pa )
42{
43        GSList *i;
44       
45        for( i = purple_connections; i; i = i->next )
46                if( ((struct im_connection *)i->data)->proto_data == pa )
47                        return i->data;
48       
49        return NULL;
50}
51
52static struct im_connection *purple_ic_by_gc( PurpleConnection *gc )
53{
54        return purple_ic_by_pa( purple_connection_get_account( gc ) );
55}
56
57static gboolean purple_menu_cmp( const char *a, const char *b )
58{
59        while( *a && *b )
60        {
61                while( *a == '_' ) a ++;
62                while( *b == '_' ) b ++;
63                if( tolower( *a ) != tolower( *b ) )
64                        return FALSE;
65               
66                a ++;
67                b ++;
68        }
69       
70        return ( *a == '\0' && *b == '\0' );
71}
72
73static void purple_init( account_t *acc )
74{
75        PurplePlugin *prpl = purple_plugins_find_with_id( (char*) acc->prpl->data );
76        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
77        PurpleAccount *pa;
78        GList *i, *st;
79        set_t *s;
80        char help_title[64];
81        GString *help;
82       
83        help = g_string_new( "" );
84        g_string_printf( help, "BitlBee libpurple module %s (%s).\n\nSupported settings:",
85                                (char*) acc->prpl->name, prpl->info->name );
86       
87        /* Convert all protocol_options into per-account setting variables. */
88        for( i = pi->protocol_options; i; i = i->next )
89        {
90                PurpleAccountOption *o = i->data;
91                const char *name;
92                char *def = NULL;
93                set_eval eval = NULL;
94                void *eval_data = NULL;
95                GList *io = NULL;
96                GSList *opts = NULL;
97               
98                name = purple_account_option_get_setting( o );
99               
100                switch( purple_account_option_get_type( o ) )
101                {
102                case PURPLE_PREF_STRING:
103                        def = g_strdup( purple_account_option_get_default_string( o ) );
104                       
105                        g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
106                                                name, purple_account_option_get_text( o ),
107                                                "string", def );
108                       
109                        break;
110               
111                case PURPLE_PREF_INT:
112                        def = g_strdup_printf( "%d", purple_account_option_get_default_int( o ) );
113                        eval = set_eval_int;
114                       
115                        g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
116                                                name, purple_account_option_get_text( o ),
117                                                "integer", def );
118                       
119                        break;
120               
121                case PURPLE_PREF_BOOLEAN:
122                        if( purple_account_option_get_default_bool( o ) )
123                                def = g_strdup( "true" );
124                        else
125                                def = g_strdup( "false" );
126                        eval = set_eval_bool;
127                       
128                        g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
129                                                name, purple_account_option_get_text( o ),
130                                                "boolean", def );
131                       
132                        break;
133               
134                case PURPLE_PREF_STRING_LIST:
135                        def = g_strdup( purple_account_option_get_default_list_value( o ) );
136                       
137                        g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
138                                                name, purple_account_option_get_text( o ),
139                                                "list", def );
140                        g_string_append( help, "\n  Possible values: " );
141                       
142                        for( io = purple_account_option_get_list( o ); io; io = io->next )
143                        {
144                                PurpleKeyValuePair *kv = io->data;
145                                opts = g_slist_append( opts, kv->value );
146                                /* TODO: kv->value is not a char*, WTF? */
147                                if( strcmp( kv->value, kv->key ) != 0 )
148                                        g_string_append_printf( help, "%s (%s), ", (char*) kv->value, kv->key );
149                                else
150                                        g_string_append_printf( help, "%s, ", (char*) kv->value );
151                        }
152                        g_string_truncate( help, help->len - 2 );
153                        eval = set_eval_list;
154                        eval_data = opts;
155                       
156                        break;
157                       
158                default:
159                        /** No way to talk to the user right now, invent one when
160                        this becomes important.
161                        irc_usermsg( acc->irc, "Setting with unknown type: %s (%d) Expect stuff to break..\n",
162                                     name, purple_account_option_get_type( o ) );
163                        */
164                        name = NULL;
165                }
166               
167                if( name != NULL )
168                {
169                        s = set_add( &acc->set, name, def, eval, acc );
170                        s->flags |= ACC_SET_OFFLINE_ONLY;
171                        s->eval_data = eval_data;
172                        g_free( def );
173                }
174        }
175       
176        g_snprintf( help_title, sizeof( help_title ), "purple %s", (char*) acc->prpl->name );
177        help_add_mem( &global.help, help_title, help->str );
178        g_string_free( help, TRUE );
179       
180        s = set_add( &acc->set, "display_name", NULL, set_eval_display_name, acc );
181        s->flags |= ACC_SET_ONLINE_ONLY;
182       
183        if( pi->options & OPT_PROTO_MAIL_CHECK )
184        {
185                s = set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc );
186                s->flags |= ACC_SET_OFFLINE_ONLY;
187        }
188       
189        /* Go through all away states to figure out if away/status messages
190           are possible. */
191        pa = purple_account_new( acc->user, (char*) acc->prpl->data );
192        for( st = purple_account_get_status_types( pa ); st; st = st->next )
193        {
194                PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data );
195               
196                if( prim == PURPLE_STATUS_AVAILABLE )
197                {
198                        if( purple_status_type_get_attr( st->data, "message" ) )
199                                acc->flags |= ACC_FLAG_STATUS_MESSAGE;
200                }
201                else if( prim != PURPLE_STATUS_OFFLINE )
202                {
203                        if( purple_status_type_get_attr( st->data, "message" ) )
204                                acc->flags |= ACC_FLAG_AWAY_MESSAGE;
205                }
206        }
207        purple_accounts_remove( pa );
208}
209
210static void purple_sync_settings( account_t *acc, PurpleAccount *pa )
211{
212        PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id );
213        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
214        GList *i;
215       
216        for( i = pi->protocol_options; i; i = i->next )
217        {
218                PurpleAccountOption *o = i->data;
219                const char *name;
220                set_t *s;
221               
222                name = purple_account_option_get_setting( o );
223                s = set_find( &acc->set, name );
224                if( s->value == NULL )
225                        continue;
226               
227                switch( purple_account_option_get_type( o ) )
228                {
229                case PURPLE_PREF_STRING:
230                case PURPLE_PREF_STRING_LIST:
231                        purple_account_set_string( pa, name, set_getstr( &acc->set, name ) );
232                        break;
233               
234                case PURPLE_PREF_INT:
235                        purple_account_set_int( pa, name, set_getint( &acc->set, name ) );
236                        break;
237               
238                case PURPLE_PREF_BOOLEAN:
239                        purple_account_set_bool( pa, name, set_getbool( &acc->set, name ) );
240                        break;
241               
242                default:
243                        break;
244                }
245        }
246       
247        if( pi->options & OPT_PROTO_MAIL_CHECK )
248                purple_account_set_check_mail( pa, set_getbool( &acc->set, "mail_notifications" ) );
249}
250
251static void purple_login( account_t *acc )
252{
253        struct im_connection *ic = imcb_new( acc );
254        PurpleAccount *pa;
255       
256        if( local_bee != NULL && local_bee != acc->bee )
257        {
258                imcb_error( ic,  "Daemon mode detected. Do *not* try to use libpurple in daemon mode! "
259                                 "Please use inetd or ForkDaemon mode instead." );
260                imc_logout( ic, FALSE );
261                return;
262        }
263        local_bee = acc->bee;
264       
265        /* For now this is needed in the _connected() handlers if using
266           GLib event handling, to make sure we're not handling events
267           on dead connections. */
268        purple_connections = g_slist_prepend( purple_connections, ic );
269       
270        ic->proto_data = pa = purple_account_new( acc->user, (char*) acc->prpl->data );
271        purple_account_set_password( pa, acc->pass );
272        purple_sync_settings( acc, pa );
273       
274        purple_account_set_enabled( pa, "BitlBee", TRUE );
275}
276
277static void purple_logout( struct im_connection *ic )
278{
279        PurpleAccount *pa = ic->proto_data;
280       
281        purple_account_set_enabled( pa, "BitlBee", FALSE );
282        purple_connections = g_slist_remove( purple_connections, ic );
283        purple_accounts_remove( pa );
284}
285
286static int purple_buddy_msg( struct im_connection *ic, char *who, char *message, int flags )
287{
288        PurpleConversation *conv;
289       
290        if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM,
291                                                            who, ic->proto_data ) ) == NULL )
292        {
293                conv = purple_conversation_new( PURPLE_CONV_TYPE_IM,
294                                                ic->proto_data, who );
295        }
296       
297        purple_conv_im_send( purple_conversation_get_im_data( conv ), message );
298       
299        return 1;
300}
301
302static GList *purple_away_states( struct im_connection *ic )
303{
304        PurpleAccount *pa = ic->proto_data;
305        GList *st, *ret = NULL;
306       
307        for( st = purple_account_get_status_types( pa ); st; st = st->next )
308        {
309                PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data );
310                if( prim != PURPLE_STATUS_AVAILABLE && prim != PURPLE_STATUS_OFFLINE )
311                        ret = g_list_append( ret, (void*) purple_status_type_get_name( st->data ) );
312        }
313       
314        return ret;
315}
316
317static void purple_set_away( struct im_connection *ic, char *state_txt, char *message )
318{
319        PurpleAccount *pa = ic->proto_data;
320        GList *status_types = purple_account_get_status_types( pa ), *st;
321        PurpleStatusType *pst = NULL;
322        GList *args = NULL;
323       
324        for( st = status_types; st; st = st->next )
325        {
326                pst = st->data;
327               
328                if( state_txt == NULL &&
329                    purple_status_type_get_primitive( pst ) == PURPLE_STATUS_AVAILABLE )
330                        break;
331
332                if( state_txt != NULL &&
333                    g_strcasecmp( state_txt, purple_status_type_get_name( pst ) ) == 0 )
334                        break;
335        }
336       
337        if( message && purple_status_type_get_attr( pst, "message" ) )
338        {
339                args = g_list_append( args, "message" );
340                args = g_list_append( args, message );
341        }
342       
343        purple_account_set_status_list( pa, st ? purple_status_type_get_id( pst ) : "away",
344                                        TRUE, args );
345
346        g_list_free( args );
347}
348
349static char *set_eval_display_name( set_t *set, char *value )
350{
351        account_t *acc = set->data;
352        struct im_connection *ic = acc->ic;
353       
354        return NULL;
355}
356
357static void purple_add_buddy( struct im_connection *ic, char *who, char *group )
358{
359        PurpleBuddy *pb;
360        PurpleGroup *pg = NULL;
361       
362        if( group && !( pg = purple_find_group( group ) ) )
363        {
364                pg = purple_group_new( group );
365                purple_blist_add_group( pg, NULL );
366        }
367       
368        pb = purple_buddy_new( (PurpleAccount*) ic->proto_data, who, NULL );
369        purple_blist_add_buddy( pb, NULL, pg, NULL );
370        purple_account_add_buddy( (PurpleAccount*) ic->proto_data, pb );
371}
372
373static void purple_remove_buddy( struct im_connection *ic, char *who, char *group )
374{
375        PurpleBuddy *pb;
376       
377        pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who );
378        if( pb != NULL )
379        {
380                PurpleGroup *group;
381               
382                group = purple_buddy_get_group( pb );
383                purple_account_remove_buddy( (PurpleAccount*) ic->proto_data, pb, group );
384               
385                purple_blist_remove_buddy( pb );
386        }
387}
388
389static void purple_add_permit( struct im_connection *ic, char *who )
390{
391        PurpleAccount *pa = ic->proto_data;
392       
393        purple_privacy_permit_add( pa, who, FALSE );
394}
395
396static void purple_add_deny( struct im_connection *ic, char *who )
397{
398        PurpleAccount *pa = ic->proto_data;
399       
400        purple_privacy_deny_add( pa, who, FALSE );
401}
402
403static void purple_rem_permit( struct im_connection *ic, char *who )
404{
405        PurpleAccount *pa = ic->proto_data;
406       
407        purple_privacy_permit_remove( pa, who, FALSE );
408}
409
410static void purple_rem_deny( struct im_connection *ic, char *who )
411{
412        PurpleAccount *pa = ic->proto_data;
413       
414        purple_privacy_deny_remove( pa, who, FALSE );
415}
416
417static void purple_get_info( struct im_connection *ic, char *who )
418{
419        serv_get_info( purple_account_get_connection( ic->proto_data ), who );
420}
421
422static void purple_keepalive( struct im_connection *ic )
423{
424}
425
426static int purple_send_typing( struct im_connection *ic, char *who, int flags )
427{
428        PurpleTypingState state = PURPLE_NOT_TYPING;
429        PurpleAccount *pa = ic->proto_data;
430       
431        if( flags & OPT_TYPING )
432                state = PURPLE_TYPING;
433        else if( flags & OPT_THINKING )
434                state = PURPLE_TYPED;
435       
436        serv_send_typing( purple_account_get_connection( pa ), who, state );
437       
438        return 1;
439}
440
441static void purple_chat_msg( struct groupchat *gc, char *message, int flags )
442{
443        PurpleConversation *pc = gc->data;
444       
445        purple_conv_chat_send( purple_conversation_get_chat_data( pc ), message );
446}
447
448struct groupchat *purple_chat_with( struct im_connection *ic, char *who )
449{
450        /* No, "of course" this won't work this way. Or in fact, it almost
451           does, but it only lets you send msgs to it, you won't receive
452           any. Instead, we have to click the virtual menu item.
453        PurpleAccount *pa = ic->proto_data;
454        PurpleConversation *pc;
455        PurpleConvChat *pcc;
456        struct groupchat *gc;
457       
458        gc = imcb_chat_new( ic, "BitlBee-libpurple groupchat" );
459        gc->data = pc = purple_conversation_new( PURPLE_CONV_TYPE_CHAT, pa, "BitlBee-libpurple groupchat" );
460        pc->ui_data = gc;
461       
462        pcc = PURPLE_CONV_CHAT( pc );
463        purple_conv_chat_add_user( pcc, ic->acc->user, "", 0, TRUE );
464        purple_conv_chat_invite_user( pcc, who, "Please join my chat", FALSE );
465        //purple_conv_chat_add_user( pcc, who, "", 0, TRUE );
466        */
467       
468        /* There went my nice afternoon. :-( */
469       
470        PurpleAccount *pa = ic->proto_data;
471        PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id );
472        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
473        PurpleBuddy *pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who );
474        PurpleMenuAction *mi;
475        GList *menu;
476        void (*callback)(PurpleBlistNode *, gpointer); /* FFFFFFFFFFFFFUUUUUUUUUUUUUU */
477       
478        if( !pb || !pi || !pi->blist_node_menu )
479                return NULL;
480       
481        menu = pi->blist_node_menu( &pb->node );
482        while( menu )
483        {
484                mi = menu->data;
485                if( purple_menu_cmp( mi->label, "initiate chat" ) ||
486                    purple_menu_cmp( mi->label, "initiate conference" ) )
487                        break;
488                menu = menu->next;
489        }
490       
491        if( menu == NULL )
492                return NULL;
493       
494        /* Call the fucker. */
495        callback = (void*) mi->callback;
496        callback( &pb->node, menu->data );
497       
498        return NULL;
499}
500
501void purple_chat_invite( struct groupchat *gc, char *who, char *message )
502{
503        PurpleConversation *pc = gc->data;
504        PurpleConvChat *pcc = PURPLE_CONV_CHAT( pc );
505       
506        serv_chat_invite( purple_account_get_connection( gc->ic->proto_data ),
507                          purple_conv_chat_get_id( pcc ), 
508                          message && *message ? message : "Please join my chat",
509                          who );
510}
511
512void purple_chat_leave( struct groupchat *gc )
513{
514        PurpleConversation *pc = gc->data;
515       
516        purple_conversation_destroy( pc );
517}
518
519struct groupchat *purple_chat_join( struct im_connection *ic, const char *room, const char *nick, const char *password )
520{
521        PurpleAccount *pa = ic->proto_data;
522        PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id );
523        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
524        GHashTable *chat_hash;
525        PurpleConversation *conv;
526        GList *info, *l;
527       
528        if( !pi->chat_info || !pi->chat_info_defaults ||
529            !( info = pi->chat_info( purple_account_get_connection( pa ) ) ) )
530        {
531                imcb_error( ic, "Joining chatrooms not supported by this protocol" );
532                return NULL;
533        }
534       
535        if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_CHAT, room, pa ) ) )
536                purple_conversation_destroy( conv );
537       
538        chat_hash = pi->chat_info_defaults( purple_account_get_connection( pa ), room );
539       
540        for( l = info; l; l = l->next )
541        {
542                struct proto_chat_entry *pce = l->data;
543               
544                if( strcmp( pce->identifier, "handle" ) == 0 )
545                        g_hash_table_replace( chat_hash, "handle", g_strdup( nick ) );
546                else if( strcmp( pce->identifier, "password" ) == 0 )
547                        g_hash_table_replace( chat_hash, "password", g_strdup( password ) );
548                else if( strcmp( pce->identifier, "passwd" ) == 0 )
549                        g_hash_table_replace( chat_hash, "passwd", g_strdup( password ) );
550        }
551       
552        serv_join_chat( purple_account_get_connection( pa ), chat_hash );
553       
554        return NULL;
555}
556
557void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle );
558
559static void purple_ui_init();
560
561GHashTable *prplcb_ui_info()
562{
563        static GHashTable *ret;
564       
565        if( ret == NULL )
566        {
567                ret = g_hash_table_new(g_str_hash, g_str_equal);
568                g_hash_table_insert( ret, "name", "BitlBee" );
569                g_hash_table_insert( ret, "version", BITLBEE_VERSION );
570        }
571       
572        return ret;
573}
574
575static PurpleCoreUiOps bee_core_uiops = 
576{
577        NULL,
578        NULL,
579        purple_ui_init,
580        NULL,
581        prplcb_ui_info,
582};
583
584static void prplcb_conn_progress( PurpleConnection *gc, const char *text, size_t step, size_t step_count )
585{
586        struct im_connection *ic = purple_ic_by_gc( gc );
587       
588        imcb_log( ic, "%s", text );
589}
590
591static void prplcb_conn_connected( PurpleConnection *gc )
592{
593        struct im_connection *ic = purple_ic_by_gc( gc );
594        const char *dn;
595        set_t *s;
596       
597        imcb_connected( ic );
598       
599        if( ( dn = purple_connection_get_display_name( gc ) ) &&
600            ( s = set_find( &ic->acc->set, "display_name" ) ) )
601        {
602                g_free( s->value );
603                s->value = g_strdup( dn );
604        }
605       
606        if( gc->flags & PURPLE_CONNECTION_HTML )
607                ic->flags |= OPT_DOES_HTML;
608}
609
610static void prplcb_conn_disconnected( PurpleConnection *gc )
611{
612        struct im_connection *ic = purple_ic_by_gc( gc );
613       
614        if( ic != NULL )
615        {
616                imc_logout( ic, !gc->wants_to_die );
617        }
618}
619
620static void prplcb_conn_notice( PurpleConnection *gc, const char *text )
621{
622        struct im_connection *ic = purple_ic_by_gc( gc );
623       
624        if( ic != NULL )
625                imcb_log( ic, "%s", text );
626}
627
628static void prplcb_conn_report_disconnect_reason( PurpleConnection *gc, PurpleConnectionError reason, const char *text )
629{
630        struct im_connection *ic = purple_ic_by_gc( gc );
631       
632        /* PURPLE_CONNECTION_ERROR_NAME_IN_USE means concurrent login,
633           should probably handle that. */
634        if( ic != NULL )
635                imcb_error( ic, "%s", text );
636}
637
638static PurpleConnectionUiOps bee_conn_uiops =
639{
640        prplcb_conn_progress,
641        prplcb_conn_connected,
642        prplcb_conn_disconnected,
643        prplcb_conn_notice,
644        NULL,
645        NULL,
646        NULL,
647        prplcb_conn_report_disconnect_reason,
648};
649
650static void prplcb_blist_update( PurpleBuddyList *list, PurpleBlistNode *node )
651{
652        if( node->type == PURPLE_BLIST_BUDDY_NODE )
653        {
654                PurpleBuddy *bud = (PurpleBuddy*) node;
655                PurpleGroup *group = purple_buddy_get_group( bud );
656                struct im_connection *ic = purple_ic_by_pa( bud->account );
657                PurpleStatus *as;
658                int flags = 0;
659               
660                if( ic == NULL )
661                        return;
662               
663                if( bud->server_alias )
664                        imcb_rename_buddy( ic, bud->name, bud->server_alias );
665               
666                if( group )
667                        imcb_add_buddy( ic, bud->name, purple_group_get_name( group ) );
668               
669                flags |= purple_presence_is_online( bud->presence ) ? OPT_LOGGED_IN : 0;
670                flags |= purple_presence_is_available( bud->presence ) ? 0 : OPT_AWAY;
671               
672                as = purple_presence_get_active_status( bud->presence );
673               
674                imcb_buddy_status( ic, bud->name, flags, purple_status_get_name( as ),
675                                   purple_status_get_attr_string( as, "message" ) );
676               
677                imcb_buddy_times( ic, bud->name,
678                                  purple_presence_get_login_time( bud->presence ),
679                                  purple_presence_get_idle_time( bud->presence ) );
680        }
681}
682
683static void prplcb_blist_new( PurpleBlistNode *node )
684{
685        if( node->type == PURPLE_BLIST_BUDDY_NODE )
686        {
687                PurpleBuddy *bud = (PurpleBuddy*) node;
688                struct im_connection *ic = purple_ic_by_pa( bud->account );
689               
690                if( ic == NULL )
691                        return;
692               
693                imcb_add_buddy( ic, bud->name, NULL );
694               
695                prplcb_blist_update( NULL, node );
696        }
697}
698
699static void prplcb_blist_remove( PurpleBuddyList *list, PurpleBlistNode *node )
700{
701/*
702        PurpleBuddy *bud = (PurpleBuddy*) node;
703       
704        if( node->type == PURPLE_BLIST_BUDDY_NODE )
705        {
706                struct im_connection *ic = purple_ic_by_pa( bud->account );
707               
708                if( ic == NULL )
709                        return;
710               
711                imcb_remove_buddy( ic, bud->name, NULL );
712        }
713*/
714}
715
716static PurpleBlistUiOps bee_blist_uiops =
717{
718        NULL,
719        prplcb_blist_new,
720        NULL,
721        prplcb_blist_update,
722        prplcb_blist_remove,
723};
724
725void prplcb_conv_new( PurpleConversation *conv )
726{
727        if( conv->type == PURPLE_CONV_TYPE_CHAT )
728        {
729                struct im_connection *ic = purple_ic_by_pa( conv->account );
730                struct groupchat *gc;
731               
732                gc = imcb_chat_new( ic, conv->name );
733                conv->ui_data = gc;
734                gc->data = conv;
735               
736                /* libpurple brokenness: Whatever. Show that we join right away,
737                   there's no clear "This is you!" signaling in _add_users so
738                   don't even try. */
739                imcb_chat_add_buddy( gc, gc->ic->acc->user );
740        }
741}
742
743void prplcb_conv_free( PurpleConversation *conv )
744{
745        struct groupchat *gc = conv->ui_data;
746       
747        imcb_chat_free( gc );
748}
749
750void prplcb_conv_add_users( PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals )
751{
752        struct groupchat *gc = conv->ui_data;
753        GList *b;
754       
755        for( b = cbuddies; b; b = b->next )
756        {
757                PurpleConvChatBuddy *pcb = b->data;
758               
759                imcb_chat_add_buddy( gc, pcb->name );
760        }
761}
762
763void prplcb_conv_del_users( PurpleConversation *conv, GList *cbuddies )
764{
765        struct groupchat *gc = conv->ui_data;
766        GList *b;
767       
768        for( b = cbuddies; b; b = b->next )
769                imcb_chat_remove_buddy( gc, b->data, "" );
770}
771
772void prplcb_conv_chat_msg( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime )
773{
774        struct groupchat *gc = conv->ui_data;
775        PurpleBuddy *buddy;
776       
777        /* ..._SEND means it's an outgoing message, no need to echo those. */
778        if( flags & PURPLE_MESSAGE_SEND )
779                return;
780       
781        buddy = purple_find_buddy( conv->account, who );
782        if( buddy != NULL )
783                who = purple_buddy_get_name( buddy );
784       
785        imcb_chat_msg( gc, who, (char*) message, 0, mtime );
786}
787
788static void prplcb_conv_im( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime )
789{
790        struct im_connection *ic = purple_ic_by_pa( conv->account );
791        PurpleBuddy *buddy;
792       
793        /* ..._SEND means it's an outgoing message, no need to echo those. */
794        if( flags & PURPLE_MESSAGE_SEND )
795                return;
796       
797        buddy = purple_find_buddy( conv->account, who );
798        if( buddy != NULL )
799                who = purple_buddy_get_name( buddy );
800       
801        imcb_buddy_msg( ic, (char*) who, (char*) message, 0, mtime );
802}
803
804/* No, this is not a ui_op but a signal. */
805static void prplcb_buddy_typing( PurpleAccount *account, const char *who, gpointer null )
806{
807        PurpleConversation *conv;
808        PurpleConvIm *im;
809        int state;
810       
811        if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM, who, account ) ) == NULL )
812                return;
813       
814        im = PURPLE_CONV_IM(conv);
815        switch( purple_conv_im_get_typing_state( im ) )
816        {
817        case PURPLE_TYPING:
818                state = OPT_TYPING;
819                break;
820        case PURPLE_TYPED:
821                state = OPT_THINKING;
822                break;
823        default:
824                state = 0;
825        }
826       
827        imcb_buddy_typing( purple_ic_by_pa( account ), who, state );
828}
829
830static PurpleConversationUiOps bee_conv_uiops = 
831{
832        prplcb_conv_new,           /* create_conversation  */
833        prplcb_conv_free,          /* destroy_conversation */
834        prplcb_conv_chat_msg,      /* write_chat           */
835        prplcb_conv_im,            /* write_im             */
836        NULL,                      /* write_conv           */
837        prplcb_conv_add_users,     /* chat_add_users       */
838        NULL,                      /* chat_rename_user     */
839        prplcb_conv_del_users,     /* chat_remove_users    */
840        NULL,                      /* chat_update_user     */
841        NULL,                      /* present              */
842        NULL,                      /* has_focus            */
843        NULL,                      /* custom_smiley_add    */
844        NULL,                      /* custom_smiley_write  */
845        NULL,                      /* custom_smiley_close  */
846        NULL,                      /* send_confirm         */
847};
848
849struct prplcb_request_action_data
850{
851        void *user_data, *bee_data;
852        PurpleRequestActionCb yes, no;
853        int yes_i, no_i;
854};
855
856static void prplcb_request_action_yes( void *data )
857{
858        struct prplcb_request_action_data *pqad = data;
859       
860        if( pqad->yes )
861                pqad->yes( pqad->user_data, pqad->yes_i );
862        g_free( pqad );
863}
864
865static void prplcb_request_action_no( void *data )
866{
867        struct prplcb_request_action_data *pqad = data;
868       
869        if( pqad->no )
870                pqad->no( pqad->user_data, pqad->no_i );
871        g_free( pqad );
872}
873
874static void *prplcb_request_action( const char *title, const char *primary, const char *secondary,
875                                    int default_action, PurpleAccount *account, const char *who,
876                                    PurpleConversation *conv, void *user_data, size_t action_count,
877                                    va_list actions )
878{
879        struct prplcb_request_action_data *pqad; 
880        int i;
881        char *q;
882       
883        pqad = g_new0( struct prplcb_request_action_data, 1 );
884       
885        for( i = 0; i < action_count; i ++ )
886        {
887                char *caption;
888                void *fn;
889               
890                caption = va_arg( actions, char* );
891                fn = va_arg( actions, void* );
892               
893                if( strstr( caption, "Accept" ) || strstr( caption, "OK" ) )
894                {
895                        pqad->yes = fn;
896                        pqad->yes_i = i;
897                }
898                else if( strstr( caption, "Reject" ) || strstr( caption, "Cancel" ) )
899                {
900                        pqad->no = fn;
901                        pqad->no_i = i;
902                }
903        }
904       
905        pqad->user_data = user_data;
906       
907        /* TODO: IRC stuff here :-( */
908        q = g_strdup_printf( "Request: %s\n\n%s\n\n%s", title, primary, secondary );
909        pqad->bee_data = query_add( local_bee->ui_data, purple_ic_by_pa( account ), q,
910                prplcb_request_action_yes, prplcb_request_action_no, g_free, pqad );
911       
912        g_free( q );
913       
914        return pqad;
915}
916
917/*
918static void prplcb_request_test()
919{
920        fprintf( stderr, "bla\n" );
921}
922*/
923
924static PurpleRequestUiOps bee_request_uiops =
925{
926        NULL,
927        NULL,
928        prplcb_request_action,
929        NULL,
930        NULL,
931        NULL,
932        NULL,
933};
934
935static void prplcb_privacy_permit_added( PurpleAccount *account, const char *name )
936{
937        struct im_connection *ic = purple_ic_by_pa( account );
938       
939        if( !g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) )
940                ic->permit = g_slist_prepend( ic->permit, g_strdup( name ) );
941}
942
943static void prplcb_privacy_permit_removed( PurpleAccount *account, const char *name )
944{
945        struct im_connection *ic = purple_ic_by_pa( account );
946        void *n;
947       
948        n = g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp );
949        ic->permit = g_slist_remove( ic->permit, n );
950}
951
952static void prplcb_privacy_deny_added( PurpleAccount *account, const char *name )
953{
954        struct im_connection *ic = purple_ic_by_pa( account );
955       
956        if( !g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) )
957                ic->deny = g_slist_prepend( ic->deny, g_strdup( name ) );
958}
959
960static void prplcb_privacy_deny_removed( PurpleAccount *account, const char *name )
961{
962        struct im_connection *ic = purple_ic_by_pa( account );
963        void *n;
964       
965        n = g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp );
966        ic->deny = g_slist_remove( ic->deny, n );
967}
968
969static PurplePrivacyUiOps bee_privacy_uiops =
970{
971        prplcb_privacy_permit_added,
972        prplcb_privacy_permit_removed,
973        prplcb_privacy_deny_added,
974        prplcb_privacy_deny_removed,
975};
976
977static void prplcb_debug_print( PurpleDebugLevel level, const char *category, const char *arg_s )
978{
979        fprintf( stderr, "DEBUG %s: %s", category, arg_s );
980}
981
982static PurpleDebugUiOps bee_debug_uiops =
983{
984        prplcb_debug_print,
985};
986
987static guint prplcb_ev_timeout_add( guint interval, GSourceFunc func, gpointer udata )
988{
989        return b_timeout_add( interval, (b_event_handler) func, udata );
990}
991
992static guint prplcb_ev_input_add( int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata )
993{
994        return b_input_add( fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata );
995}
996
997static gboolean prplcb_ev_remove( guint id )
998{
999        b_event_remove( (gint) id );
1000        return TRUE;
1001}
1002
1003static PurpleEventLoopUiOps glib_eventloops = 
1004{
1005        prplcb_ev_timeout_add,
1006        prplcb_ev_remove,
1007        prplcb_ev_input_add,
1008        prplcb_ev_remove,
1009};
1010
1011static void *prplcb_notify_email( PurpleConnection *gc, const char *subject, const char *from,
1012                                  const char *to, const char *url )
1013{
1014        struct im_connection *ic = purple_ic_by_gc( gc );
1015       
1016        imcb_log( ic, "Received e-mail from %s for %s: %s <%s>", from, to, subject, url );
1017       
1018        return NULL;
1019}
1020
1021static void *prplcb_notify_userinfo( PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info )
1022{
1023        struct im_connection *ic = purple_ic_by_gc( gc );
1024        GString *info = g_string_new( "" );
1025        GList *l = purple_notify_user_info_get_entries( user_info );
1026        char *key;
1027        const char *value;
1028        int n;
1029       
1030        while( l )
1031        {
1032                PurpleNotifyUserInfoEntry *e = l->data;
1033               
1034                switch( purple_notify_user_info_entry_get_type( e ) )
1035                {
1036                case PURPLE_NOTIFY_USER_INFO_ENTRY_PAIR:
1037                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_HEADER:
1038                        key = g_strdup( purple_notify_user_info_entry_get_label( e ) );
1039                        value = purple_notify_user_info_entry_get_value( e );
1040                       
1041                        if( key )
1042                        {
1043                                strip_html( key );
1044                                g_string_append_printf( info, "%s: ", key );
1045                               
1046                                if( value )
1047                                {
1048                                        n = strlen( value ) - 1;
1049                                        while( isspace( value[n] ) )
1050                                                n --;
1051                                        g_string_append_len( info, value, n + 1 );
1052                                }
1053                                g_string_append_c( info, '\n' );
1054                                g_free( key );
1055                        }
1056                       
1057                        break;
1058                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK:
1059                        g_string_append( info, "------------------------\n" );
1060                        break;
1061                }
1062               
1063                l = l->next;
1064        }
1065       
1066        imcb_log( ic, "User %s info:\n%s", who, info->str );
1067        g_string_free( info, TRUE );
1068       
1069        return NULL;
1070}
1071
1072static PurpleNotifyUiOps bee_notify_uiops =
1073{
1074        NULL,
1075        prplcb_notify_email,
1076        NULL,
1077        NULL,
1078        NULL,
1079        NULL,
1080        prplcb_notify_userinfo,
1081};
1082
1083static void *prplcb_account_request_authorize( PurpleAccount *account, const char *remote_user,
1084        const char *id, const char *alias, const char *message, gboolean on_list,
1085        PurpleAccountRequestAuthorizationCb authorize_cb, PurpleAccountRequestAuthorizationCb deny_cb, void *user_data )
1086{
1087        struct im_connection *ic = purple_ic_by_pa( account );
1088        char *q;
1089       
1090        if( alias )
1091                q = g_strdup_printf( "%s (%s) wants to add you to his/her contact "
1092                                     "list. (%s)", alias, remote_user, message );
1093        else
1094                q = g_strdup_printf( "%s wants to add you to his/her contact "
1095                                     "list. (%s)", remote_user, message );
1096       
1097        imcb_ask_with_free( ic, q, user_data, authorize_cb, deny_cb, NULL );
1098        g_free( q );
1099       
1100        return NULL;
1101}
1102
1103static PurpleAccountUiOps bee_account_uiops =
1104{
1105        NULL,
1106        NULL,
1107        NULL,
1108        prplcb_account_request_authorize,
1109        NULL,
1110};
1111
1112extern PurpleXferUiOps bee_xfer_uiops;
1113
1114static void purple_ui_init()
1115{
1116        purple_connections_set_ui_ops( &bee_conn_uiops );
1117        purple_blist_set_ui_ops( &bee_blist_uiops );
1118        purple_conversations_set_ui_ops( &bee_conv_uiops );
1119        purple_request_set_ui_ops( &bee_request_uiops );
1120        purple_privacy_set_ui_ops( &bee_privacy_uiops );
1121        purple_notify_set_ui_ops( &bee_notify_uiops );
1122        purple_accounts_set_ui_ops( &bee_account_uiops );
1123        purple_xfers_set_ui_ops( &bee_xfer_uiops );
1124       
1125        if( getenv( "BITLBEE_DEBUG" ) )
1126                purple_debug_set_ui_ops( &bee_debug_uiops );
1127}
1128
1129void purple_initmodule()
1130{
1131        struct prpl funcs;
1132        GList *prots;
1133        GString *help;
1134       
1135        if( B_EV_IO_READ != PURPLE_INPUT_READ ||
1136            B_EV_IO_WRITE != PURPLE_INPUT_WRITE )
1137        {
1138                /* FIXME FIXME FIXME FIXME FIXME :-) */
1139                exit( 1 );
1140        }
1141       
1142        purple_util_set_user_dir( "/tmp" );
1143        purple_debug_set_enabled( FALSE );
1144        purple_core_set_ui_ops( &bee_core_uiops );
1145        purple_eventloop_set_ui_ops( &glib_eventloops );
1146        if( !purple_core_init( "BitlBee") )
1147        {
1148                /* Initializing the core failed. Terminate. */
1149                fprintf( stderr, "libpurple initialization failed.\n" );
1150                abort();
1151        }
1152       
1153        /* This seems like stateful shit we don't want... */
1154        purple_set_blist( purple_blist_new() );
1155        purple_blist_load();
1156       
1157        /* Meh? */
1158        purple_prefs_load();
1159       
1160        /* No, really. So far there were ui_ops for everything, but now suddenly
1161           one needs to use signals for typing notification stuff. :-( */
1162        purple_signal_connect( purple_conversations_get_handle(), "buddy-typing",
1163                               &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL );
1164        purple_signal_connect( purple_conversations_get_handle(), "buddy-typed",
1165                               &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL );
1166        purple_signal_connect( purple_conversations_get_handle(), "buddy-typing-stopped",
1167                               &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL );
1168       
1169        memset( &funcs, 0, sizeof( funcs ) );
1170        funcs.login = purple_login;
1171        funcs.init = purple_init;
1172        funcs.logout = purple_logout;
1173        funcs.buddy_msg = purple_buddy_msg;
1174        funcs.away_states = purple_away_states;
1175        funcs.set_away = purple_set_away;
1176        funcs.add_buddy = purple_add_buddy;
1177        funcs.remove_buddy = purple_remove_buddy;
1178        funcs.add_permit = purple_add_permit;
1179        funcs.add_deny = purple_add_deny;
1180        funcs.rem_permit = purple_rem_permit;
1181        funcs.rem_deny = purple_rem_deny;
1182        funcs.get_info = purple_get_info;
1183        funcs.keepalive = purple_keepalive;
1184        funcs.send_typing = purple_send_typing;
1185        funcs.handle_cmp = g_strcasecmp;
1186        /* TODO(wilmer): Set these only for protocols that support them? */
1187        funcs.chat_msg = purple_chat_msg;
1188        funcs.chat_with = purple_chat_with;
1189        funcs.chat_invite = purple_chat_invite;
1190        funcs.chat_leave = purple_chat_leave;
1191        funcs.chat_join = purple_chat_join;
1192        funcs.transfer_request = purple_transfer_request;
1193       
1194        help = g_string_new( "BitlBee libpurple module supports the following IM protocols:\n" );
1195       
1196        /* Add a protocol entry to BitlBee's structures for every protocol
1197           supported by this libpurple instance. */     
1198        for( prots = purple_plugins_get_protocols(); prots; prots = prots->next )
1199        {
1200                PurplePlugin *prot = prots->data;
1201                struct prpl *ret;
1202               
1203                ret = g_memdup( &funcs, sizeof( funcs ) );
1204                ret->name = ret->data = prot->info->id;
1205                if( strncmp( ret->name, "prpl-", 5 ) == 0 )
1206                        ret->name += 5;
1207                register_protocol( ret );
1208               
1209                g_string_append_printf( help, "\n* %s (%s)", ret->name, prot->info->name );
1210               
1211                /* libpurple doesn't define a protocol called OSCAR, but we
1212                   need it to be compatible with normal BitlBee. */
1213                if( g_strcasecmp( prot->info->id, "prpl-aim" ) == 0 )
1214                {
1215                        ret = g_memdup( &funcs, sizeof( funcs ) );
1216                        ret->name = "oscar";
1217                        ret->data = prot->info->id;
1218                        register_protocol( ret );
1219                }
1220        }
1221       
1222        g_string_append( help, "\n\nFor used protocols, more information about available "
1223                         "settings can be found using \x02help purple <protocol name>\x02" );
1224       
1225        /* Add a simple dynamically-generated help item listing all
1226           the supported protocols. */
1227        help_add_mem( &global.help, "purple", help->str );
1228        g_string_free( help, TRUE );
1229}
Note: See TracBrowser for help on using the repository browser.