source: protocols/purple/purple.c @ ad5a907

Last change on this file since ad5a907 was 5dbf66e, checked in by dequis <dx@…>, at 2016-06-13T00:55:26Z

purple: add $plugindir/purple to the plugin search path

This allows adding bitlbee-specific purple plugins in a directory
controlled by the user who starts bitlbee (as it can be defined in
bitlbee.conf, PluginDir).

Pidgin and finch have something similar allowing users to place plugins
in ~/.purple/plugins:

path = g_build_filename(purple_user_dir(), "plugins", NULL);

The direct equivalent would be to use our config dir, but i'd rather not
put executable modules there.

  • Property mode set to 100644
File size: 45.4 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  libpurple module - Main file                                             *
5*                                                                           *
6*  Copyright 2009-2012 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 "bpurple.h"
26#include "help.h"
27
28#include <stdarg.h>
29
30#include <glib.h>
31#include <purple.h>
32
33GSList *purple_connections;
34
35/* This makes me VERY sad... :-( But some libpurple callbacks come in without
36   any context so this is the only way to get that. Don't want to support
37   libpurple in daemon mode anyway. */
38static bee_t *local_bee;
39
40static char *set_eval_display_name(set_t *set, char *value);
41
42void purple_request_input_callback(guint id, struct im_connection *ic,
43                                   const char *message, const char *who);
44
45/* purple_request_input specific stuff */
46typedef void (*ri_callback_t)(gpointer, const gchar *);
47
48struct request_input_data {
49        ri_callback_t data_callback;
50        void *user_data;
51        struct im_connection *ic;
52        char *buddy;
53        guint id;
54};
55
56struct im_connection *purple_ic_by_pa(PurpleAccount *pa)
57{
58        GSList *i;
59        struct purple_data *pd;
60
61        for (i = purple_connections; i; i = i->next) {
62                pd = ((struct im_connection *) i->data)->proto_data;
63                if (pd->account == pa) {
64                        return i->data;
65                }
66        }
67
68        return NULL;
69}
70
71static struct im_connection *purple_ic_by_gc(PurpleConnection *gc)
72{
73        return purple_ic_by_pa(purple_connection_get_account(gc));
74}
75
76static gboolean purple_menu_cmp(const char *a, const char *b)
77{
78        while (*a && *b) {
79                while (*a == '_') {
80                        a++;
81                }
82                while (*b == '_') {
83                        b++;
84                }
85                if (g_ascii_tolower(*a) != g_ascii_tolower(*b)) {
86                        return FALSE;
87                }
88
89                a++;
90                b++;
91        }
92
93        return (*a == '\0' && *b == '\0');
94}
95
96static void purple_init(account_t *acc)
97{
98        PurplePlugin *prpl = purple_plugins_find_with_id((char *) acc->prpl->data);
99        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
100        PurpleAccount *pa;
101        GList *i, *st;
102        set_t *s;
103        char help_title[64];
104        GString *help;
105        static gboolean dir_fixed = FALSE;
106
107        /* Layer violation coming up: Making an exception for libpurple here.
108           Dig in the IRC state a bit to get a username. Ideally we should
109           check if s/he identified but this info doesn't seem *that* important.
110           It's just that fecking libpurple can't *not* store this shit.
111
112           Remember that libpurple is not really meant to be used on public
113           servers anyway! */
114        if (!dir_fixed) {
115                PurpleCertificatePool *pool;
116                irc_t *irc = acc->bee->ui_data;
117                char *dir;
118
119                dir = g_strdup_printf("%s/purple/%s", global.conf->configdir, irc->user->nick);
120                purple_util_set_user_dir(dir);
121                g_free(dir);
122
123                purple_blist_load();
124                purple_prefs_load();
125
126                if (proxytype == PROXY_SOCKS4A) {
127                        /* do this here after loading prefs. yes, i know, it sucks */
128                        purple_prefs_set_bool("/purple/proxy/socks4_remotedns", TRUE);
129                }
130
131                /* re-create the certificate cache directory */
132                pool = purple_certificate_find_pool("x509", "tls_peers");
133                dir = purple_certificate_pool_mkpath(pool, NULL);
134                purple_build_dir(dir, 0700);
135                g_free(dir);
136
137                dir_fixed = TRUE;
138        }
139
140        help = g_string_new("");
141        g_string_printf(help, "BitlBee libpurple module %s (%s).\n\nSupported settings:",
142                        (char *) acc->prpl->name, prpl->info->name);
143
144        if (pi->user_splits) {
145                GList *l;
146                g_string_append_printf(help, "\n* username: Username");
147                for (l = pi->user_splits; l; l = l->next) {
148                        g_string_append_printf(help, "%c%s",
149                                               purple_account_user_split_get_separator(l->data),
150                                               purple_account_user_split_get_text(l->data));
151                }
152        }
153
154        /* Convert all protocol_options into per-account setting variables. */
155        for (i = pi->protocol_options; i; i = i->next) {
156                PurpleAccountOption *o = i->data;
157                const char *name;
158                char *def = NULL;
159                set_eval eval = NULL;
160                void *eval_data = NULL;
161                GList *io = NULL;
162                GSList *opts = NULL;
163
164                name = purple_account_option_get_setting(o);
165
166                switch (purple_account_option_get_type(o)) {
167                case PURPLE_PREF_STRING:
168                        def = g_strdup(purple_account_option_get_default_string(o));
169
170                        g_string_append_printf(help, "\n* %s (%s), %s, default: %s",
171                                               name, purple_account_option_get_text(o),
172                                               "string", def);
173
174                        break;
175
176                case PURPLE_PREF_INT:
177                        def = g_strdup_printf("%d", purple_account_option_get_default_int(o));
178                        eval = set_eval_int;
179
180                        g_string_append_printf(help, "\n* %s (%s), %s, default: %s",
181                                               name, purple_account_option_get_text(o),
182                                               "integer", def);
183
184                        break;
185
186                case PURPLE_PREF_BOOLEAN:
187                        if (purple_account_option_get_default_bool(o)) {
188                                def = g_strdup("true");
189                        } else {
190                                def = g_strdup("false");
191                        }
192                        eval = set_eval_bool;
193
194                        g_string_append_printf(help, "\n* %s (%s), %s, default: %s",
195                                               name, purple_account_option_get_text(o),
196                                               "boolean", def);
197
198                        break;
199
200                case PURPLE_PREF_STRING_LIST:
201                        def = g_strdup(purple_account_option_get_default_list_value(o));
202
203                        g_string_append_printf(help, "\n* %s (%s), %s, default: %s",
204                                               name, purple_account_option_get_text(o),
205                                               "list", def);
206                        g_string_append(help, "\n  Possible values: ");
207
208                        for (io = purple_account_option_get_list(o); io; io = io->next) {
209                                PurpleKeyValuePair *kv = io->data;
210                                opts = g_slist_append(opts, kv->value);
211                                /* TODO: kv->value is not a char*, WTF? */
212                                if (strcmp(kv->value, kv->key) != 0) {
213                                        g_string_append_printf(help, "%s (%s), ", (char *) kv->value, kv->key);
214                                } else {
215                                        g_string_append_printf(help, "%s, ", (char *) kv->value);
216                                }
217                        }
218                        g_string_truncate(help, help->len - 2);
219                        eval = set_eval_list;
220                        eval_data = opts;
221
222                        break;
223
224                default:
225                        /** No way to talk to the user right now, invent one when
226                        this becomes important.
227                        irc_rootmsg( acc->irc, "Setting with unknown type: %s (%d) Expect stuff to break..\n",
228                                     name, purple_account_option_get_type( o ) );
229                        */
230                        g_string_append_printf(help, "\n* [%s] UNSUPPORTED (type %d)",
231                                               name, purple_account_option_get_type(o));
232                        name = NULL;
233                }
234
235                if (name != NULL) {
236                        s = set_add(&acc->set, name, def, eval, acc);
237                        s->flags |= ACC_SET_OFFLINE_ONLY;
238                        s->eval_data = eval_data;
239                        g_free(def);
240                }
241        }
242
243        g_snprintf(help_title, sizeof(help_title), "purple %s", (char *) acc->prpl->name);
244        help_add_mem(&global.help, help_title, help->str);
245        g_string_free(help, TRUE);
246
247        s = set_add(&acc->set, "display_name", NULL, set_eval_display_name, acc);
248        s->flags |= ACC_SET_ONLINE_ONLY;
249
250        if (pi->options & OPT_PROTO_MAIL_CHECK) {
251                s = set_add(&acc->set, "mail_notifications", "false", set_eval_bool, acc);
252                s->flags |= ACC_SET_OFFLINE_ONLY;
253
254                s = set_add(&acc->set, "mail_notifications_handle", NULL, NULL, acc);
255                s->flags |= ACC_SET_OFFLINE_ONLY | SET_NULL_OK;
256        }
257
258        if (strcmp(prpl->info->name, "Gadu-Gadu") == 0) {
259                s = set_add(&acc->set, "gg_sync_contacts", "true", set_eval_bool, acc);
260        }
261
262        /* Go through all away states to figure out if away/status messages
263           are possible. */
264        pa = purple_account_new(acc->user, (char *) acc->prpl->data);
265        for (st = purple_account_get_status_types(pa); st; st = st->next) {
266                PurpleStatusPrimitive prim = purple_status_type_get_primitive(st->data);
267
268                if (prim == PURPLE_STATUS_AVAILABLE) {
269                        if (purple_status_type_get_attr(st->data, "message")) {
270                                acc->flags |= ACC_FLAG_STATUS_MESSAGE;
271                        }
272                } else if (prim != PURPLE_STATUS_OFFLINE) {
273                        if (purple_status_type_get_attr(st->data, "message")) {
274                                acc->flags |= ACC_FLAG_AWAY_MESSAGE;
275                        }
276                }
277        }
278        purple_accounts_remove(pa);
279}
280
281static void purple_sync_settings(account_t *acc, PurpleAccount *pa)
282{
283        PurplePlugin *prpl = purple_plugins_find_with_id(pa->protocol_id);
284        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
285        GList *i;
286
287        for (i = pi->protocol_options; i; i = i->next) {
288                PurpleAccountOption *o = i->data;
289                const char *name;
290                set_t *s;
291
292                name = purple_account_option_get_setting(o);
293                s = set_find(&acc->set, name);
294                if (s->value == NULL) {
295                        continue;
296                }
297
298                switch (purple_account_option_get_type(o)) {
299                case PURPLE_PREF_STRING:
300                case PURPLE_PREF_STRING_LIST:
301                        purple_account_set_string(pa, name, set_getstr(&acc->set, name));
302                        break;
303
304                case PURPLE_PREF_INT:
305                        purple_account_set_int(pa, name, set_getint(&acc->set, name));
306                        break;
307
308                case PURPLE_PREF_BOOLEAN:
309                        purple_account_set_bool(pa, name, set_getbool(&acc->set, name));
310                        break;
311
312                default:
313                        break;
314                }
315        }
316
317        if (pi->options & OPT_PROTO_MAIL_CHECK) {
318                purple_account_set_check_mail(pa, set_getbool(&acc->set, "mail_notifications"));
319        }
320}
321
322static void purple_login(account_t *acc)
323{
324        struct im_connection *ic = imcb_new(acc);
325        struct purple_data *pd;
326
327        if ((local_bee != NULL && local_bee != acc->bee) ||
328            (global.conf->runmode == RUNMODE_DAEMON && !getenv("BITLBEE_DEBUG"))) {
329                imcb_error(ic,  "Daemon mode detected. Do *not* try to use libpurple in daemon mode! "
330                           "Please use inetd or ForkDaemon mode instead.");
331                imc_logout(ic, FALSE);
332                return;
333        }
334        local_bee = acc->bee;
335
336        /* For now this is needed in the _connected() handlers if using
337           GLib event handling, to make sure we're not handling events
338           on dead connections. */
339        purple_connections = g_slist_prepend(purple_connections, ic);
340
341        ic->proto_data = pd = g_new0(struct purple_data, 1);
342        pd->account = purple_account_new(acc->user, (char *) acc->prpl->data);
343        pd->input_requests = g_hash_table_new_full(g_direct_hash, g_direct_equal,
344                                                   NULL, g_free);
345        pd->next_request_id = 0;
346        purple_account_set_password(pd->account, acc->pass);
347        purple_sync_settings(acc, pd->account);
348
349        purple_account_set_enabled(pd->account, "BitlBee", TRUE);
350
351        if (set_getbool(&acc->set, "mail_notifications") && set_getstr(&acc->set, "mail_notifications_handle")) {
352                imcb_add_buddy(ic, set_getstr(&acc->set, "mail_notifications_handle"), NULL);
353        }
354}
355
356static void purple_logout(struct im_connection *ic)
357{
358        struct purple_data *pd = ic->proto_data;
359
360        if (!pd) {
361                return;
362        }
363
364        while (ic->groupchats) {
365                imcb_chat_free(ic->groupchats->data);
366        }
367
368        purple_account_set_enabled(pd->account, "BitlBee", FALSE);
369        purple_connections = g_slist_remove(purple_connections, ic);
370        purple_accounts_remove(pd->account);
371        g_hash_table_destroy(pd->input_requests);
372        g_free(pd);
373}
374
375static int purple_buddy_msg(struct im_connection *ic, char *who, char *message, int flags)
376{
377        PurpleConversation *conv;
378        struct purple_data *pd = ic->proto_data;
379
380        if (!strncmp(who, PURPLE_REQUEST_HANDLE, sizeof(PURPLE_REQUEST_HANDLE) - 1)) {
381                guint request_id = atoi(who + sizeof(PURPLE_REQUEST_HANDLE));
382                purple_request_input_callback(request_id, ic, message, who);
383                return 1;
384        }
385
386        if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
387                                                          who, pd->account)) == NULL) {
388                conv = purple_conversation_new(PURPLE_CONV_TYPE_IM,
389                                               pd->account, who);
390        }
391
392        purple_conv_im_send(purple_conversation_get_im_data(conv), message);
393
394        return 1;
395}
396
397static GList *purple_away_states(struct im_connection *ic)
398{
399        struct purple_data *pd = ic->proto_data;
400        GList *st, *ret = NULL;
401
402        for (st = purple_account_get_status_types(pd->account); st; st = st->next) {
403                PurpleStatusPrimitive prim = purple_status_type_get_primitive(st->data);
404                if (prim != PURPLE_STATUS_AVAILABLE && prim != PURPLE_STATUS_OFFLINE) {
405                        ret = g_list_append(ret, (void *) purple_status_type_get_name(st->data));
406                }
407        }
408
409        return ret;
410}
411
412static void purple_set_away(struct im_connection *ic, char *state_txt, char *message)
413{
414        struct purple_data *pd = ic->proto_data;
415        GList *status_types = purple_account_get_status_types(pd->account), *st;
416        PurpleStatusType *pst = NULL;
417        GList *args = NULL;
418
419        for (st = status_types; st; st = st->next) {
420                pst = st->data;
421
422                if (state_txt == NULL &&
423                    purple_status_type_get_primitive(pst) == PURPLE_STATUS_AVAILABLE) {
424                        break;
425                }
426
427                if (state_txt != NULL &&
428                    g_strcasecmp(state_txt, purple_status_type_get_name(pst)) == 0) {
429                        break;
430                }
431        }
432
433        if (message && purple_status_type_get_attr(pst, "message")) {
434                args = g_list_append(args, "message");
435                args = g_list_append(args, message);
436        }
437
438        purple_account_set_status_list(pd->account,
439                                       st ? purple_status_type_get_id(pst) : "away",
440                                       TRUE, args);
441
442        g_list_free(args);
443}
444
445static char *set_eval_display_name(set_t *set, char *value)
446{
447        account_t *acc = set->data;
448        struct im_connection *ic = acc->ic;
449
450        if (ic) {
451                imcb_log(ic, "Changing display_name not currently supported with libpurple!");
452        }
453
454        return NULL;
455}
456
457/* Bad bad gadu-gadu, not saving buddy list by itself */
458static void purple_gg_buddylist_export(PurpleConnection *gc)
459{
460        struct im_connection *ic = purple_ic_by_gc(gc);
461
462        if (set_getstr(&ic->acc->set, "gg_sync_contacts")) {
463                GList *actions = gc->prpl->info->actions(gc->prpl, gc);
464                GList *p;
465                for (p = g_list_first(actions); p; p = p->next) {
466                        if (((PurplePluginAction *) p->data) &&
467                            purple_menu_cmp(((PurplePluginAction *) p->data)->label,
468                                            "Upload buddylist to Server") == 0) {
469                                PurplePluginAction action;
470                                action.plugin = gc->prpl;
471                                action.context = gc;
472                                action.user_data = NULL;
473                                ((PurplePluginAction *) p->data)->callback(&action);
474                                break;
475                        }
476                }
477                g_list_free(actions);
478        }
479}
480
481static void purple_gg_buddylist_import(PurpleConnection *gc)
482{
483        struct im_connection *ic = purple_ic_by_gc(gc);
484
485        if (set_getstr(&ic->acc->set, "gg_sync_contacts")) {
486                GList *actions = gc->prpl->info->actions(gc->prpl, gc);
487                GList *p;
488                for (p = g_list_first(actions); p; p = p->next) {
489                        if (((PurplePluginAction *) p->data) &&
490                            purple_menu_cmp(((PurplePluginAction *) p->data)->label,
491                                            "Download buddylist from Server") == 0) {
492                                PurplePluginAction action;
493                                action.plugin = gc->prpl;
494                                action.context = gc;
495                                action.user_data = NULL;
496                                ((PurplePluginAction *) p->data)->callback(&action);
497                                break;
498                        }
499                }
500                g_list_free(actions);
501        }
502}
503
504static void purple_add_buddy(struct im_connection *ic, char *who, char *group)
505{
506        PurpleBuddy *pb;
507        PurpleGroup *pg = NULL;
508        struct purple_data *pd = ic->proto_data;
509
510        if (group && !(pg = purple_find_group(group))) {
511                pg = purple_group_new(group);
512                purple_blist_add_group(pg, NULL);
513        }
514
515        pb = purple_buddy_new(pd->account, who, NULL);
516        purple_blist_add_buddy(pb, NULL, pg, NULL);
517        purple_account_add_buddy(pd->account, pb);
518
519        purple_gg_buddylist_export(pd->account->gc);
520}
521
522static void purple_remove_buddy(struct im_connection *ic, char *who, char *group)
523{
524        PurpleBuddy *pb;
525        struct purple_data *pd = ic->proto_data;
526
527        pb = purple_find_buddy(pd->account, who);
528        if (pb != NULL) {
529                PurpleGroup *group;
530
531                group = purple_buddy_get_group(pb);
532                purple_account_remove_buddy(pd->account, pb, group);
533
534                purple_blist_remove_buddy(pb);
535        }
536
537        purple_gg_buddylist_export(pd->account->gc);
538}
539
540static void purple_add_permit(struct im_connection *ic, char *who)
541{
542        struct purple_data *pd = ic->proto_data;
543
544        purple_privacy_permit_add(pd->account, who, FALSE);
545}
546
547static void purple_add_deny(struct im_connection *ic, char *who)
548{
549        struct purple_data *pd = ic->proto_data;
550
551        purple_privacy_deny_add(pd->account, who, FALSE);
552}
553
554static void purple_rem_permit(struct im_connection *ic, char *who)
555{
556        struct purple_data *pd = ic->proto_data;
557
558        purple_privacy_permit_remove(pd->account, who, FALSE);
559}
560
561static void purple_rem_deny(struct im_connection *ic, char *who)
562{
563        struct purple_data *pd = ic->proto_data;
564
565        purple_privacy_deny_remove(pd->account, who, FALSE);
566}
567
568static void purple_get_info(struct im_connection *ic, char *who)
569{
570        struct purple_data *pd = ic->proto_data;
571
572        serv_get_info(purple_account_get_connection(pd->account), who);
573}
574
575static void purple_keepalive(struct im_connection *ic)
576{
577}
578
579static int purple_send_typing(struct im_connection *ic, char *who, int flags)
580{
581        PurpleTypingState state = PURPLE_NOT_TYPING;
582        struct purple_data *pd = ic->proto_data;
583
584        if (flags & OPT_TYPING) {
585                state = PURPLE_TYPING;
586        } else if (flags & OPT_THINKING) {
587                state = PURPLE_TYPED;
588        }
589
590        serv_send_typing(purple_account_get_connection(pd->account), who, state);
591
592        return 1;
593}
594
595static void purple_chat_msg(struct groupchat *gc, char *message, int flags)
596{
597        PurpleConversation *pc = gc->data;
598
599        purple_conv_chat_send(purple_conversation_get_chat_data(pc), message);
600}
601
602struct groupchat *purple_chat_with(struct im_connection *ic, char *who)
603{
604        /* No, "of course" this won't work this way. Or in fact, it almost
605           does, but it only lets you send msgs to it, you won't receive
606           any. Instead, we have to click the virtual menu item.
607        PurpleAccount *pa = ic->proto_data;
608        PurpleConversation *pc;
609        PurpleConvChat *pcc;
610        struct groupchat *gc;
611
612        gc = imcb_chat_new( ic, "BitlBee-libpurple groupchat" );
613        gc->data = pc = purple_conversation_new( PURPLE_CONV_TYPE_CHAT, pa, "BitlBee-libpurple groupchat" );
614        pc->ui_data = gc;
615
616        pcc = PURPLE_CONV_CHAT( pc );
617        purple_conv_chat_add_user( pcc, ic->acc->user, "", 0, TRUE );
618        purple_conv_chat_invite_user( pcc, who, "Please join my chat", FALSE );
619        //purple_conv_chat_add_user( pcc, who, "", 0, TRUE );
620        */
621
622        /* There went my nice afternoon. :-( */
623
624        struct purple_data *pd = ic->proto_data;
625        PurplePlugin *prpl = purple_plugins_find_with_id(pd->account->protocol_id);
626        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
627        PurpleBuddy *pb = purple_find_buddy(pd->account, who);
628        PurpleMenuAction *mi;
629        GList *menu;
630
631        void (*callback)(PurpleBlistNode *, gpointer); /* FFFFFFFFFFFFFUUUUUUUUUUUUUU */
632
633        if (!pb || !pi || !pi->blist_node_menu) {
634                return NULL;
635        }
636
637        menu = pi->blist_node_menu(&pb->node);
638        while (menu) {
639                mi = menu->data;
640                if (purple_menu_cmp(mi->label, "initiate chat") ||
641                    purple_menu_cmp(mi->label, "initiate conference")) {
642                        break;
643                }
644                menu = menu->next;
645        }
646
647        if (menu == NULL) {
648                return NULL;
649        }
650
651        /* Call the fucker. */
652        callback = (void *) mi->callback;
653        callback(&pb->node, mi->data);
654
655        return NULL;
656}
657
658void purple_chat_invite(struct groupchat *gc, char *who, char *message)
659{
660        PurpleConversation *pc = gc->data;
661        PurpleConvChat *pcc = PURPLE_CONV_CHAT(pc);
662        struct purple_data *pd = gc->ic->proto_data;
663
664        serv_chat_invite(purple_account_get_connection(pd->account),
665                         purple_conv_chat_get_id(pcc),
666                         message && *message ? message : "Please join my chat",
667                         who);
668}
669
670void purple_chat_kick(struct groupchat *gc, char *who, const char *message)
671{
672        PurpleConversation *pc = gc->data;
673        char *str = g_strdup_printf("kick %s %s", who, message);
674
675        purple_conversation_do_command(pc, str, NULL, NULL);
676        g_free(str);
677}
678
679void purple_chat_leave(struct groupchat *gc)
680{
681        PurpleConversation *pc = gc->data;
682
683        purple_conversation_destroy(pc);
684}
685
686struct groupchat *purple_chat_join(struct im_connection *ic, const char *room, const char *nick, const char *password,
687                                   set_t **sets)
688{
689        struct purple_data *pd = ic->proto_data;
690        PurplePlugin *prpl = purple_plugins_find_with_id(pd->account->protocol_id);
691        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
692        GHashTable *chat_hash;
693        PurpleConversation *conv;
694        GList *info, *l;
695
696        if (!pi->chat_info || !pi->chat_info_defaults ||
697            !(info = pi->chat_info(purple_account_get_connection(pd->account)))) {
698                imcb_error(ic, "Joining chatrooms not supported by this protocol");
699                return NULL;
700        }
701
702        if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
703                                                          room, pd->account))) {
704                purple_conversation_destroy(conv);
705        }
706
707        chat_hash = pi->chat_info_defaults(
708                purple_account_get_connection(pd->account), room
709        );
710
711        for (l = info; l; l = l->next) {
712                struct proto_chat_entry *pce = l->data;
713
714                if (strcmp(pce->identifier, "handle") == 0) {
715                        g_hash_table_replace(chat_hash, "handle", g_strdup(nick));
716                } else if (strcmp(pce->identifier, "password") == 0) {
717                        g_hash_table_replace(chat_hash, "password", g_strdup(password));
718                } else if (strcmp(pce->identifier, "passwd") == 0) {
719                        g_hash_table_replace(chat_hash, "passwd", g_strdup(password));
720                }
721
722                g_free(pce);
723        }
724
725        g_list_free(info);
726
727        serv_join_chat(purple_account_get_connection(pd->account), chat_hash);
728
729        g_hash_table_destroy(chat_hash);
730
731        return imcb_chat_new(ic, room);
732}
733
734void purple_transfer_request(struct im_connection *ic, file_transfer_t *ft, char *handle);
735
736static void purple_ui_init();
737
738GHashTable *prplcb_ui_info()
739{
740        static GHashTable *ret;
741
742        if (ret == NULL) {
743                ret = g_hash_table_new(g_str_hash, g_str_equal);
744                g_hash_table_insert(ret, "name", "BitlBee");
745                g_hash_table_insert(ret, "version", BITLBEE_VERSION);
746        }
747
748        return ret;
749}
750
751static PurpleCoreUiOps bee_core_uiops =
752{
753        NULL,                      /* ui_prefs_init */
754        NULL,                      /* debug_ui_init */
755        purple_ui_init,            /* ui_init */
756        NULL,                      /* quit */
757        prplcb_ui_info,            /* get_ui_info */
758};
759
760static void prplcb_conn_progress(PurpleConnection *gc, const char *text, size_t step, size_t step_count)
761{
762        struct im_connection *ic = purple_ic_by_gc(gc);
763
764        imcb_log(ic, "%s", text);
765}
766
767static void prplcb_conn_connected(PurpleConnection *gc)
768{
769        struct im_connection *ic = purple_ic_by_gc(gc);
770        const char *dn;
771        set_t *s;
772
773        imcb_connected(ic);
774
775        if ((dn = purple_connection_get_display_name(gc)) &&
776            (s = set_find(&ic->acc->set, "display_name"))) {
777                g_free(s->value);
778                s->value = g_strdup(dn);
779        }
780
781        // user list needs to be requested for Gadu-Gadu
782        purple_gg_buddylist_import(gc);
783
784        ic->flags |= OPT_DOES_HTML;
785}
786
787static void prplcb_conn_disconnected(PurpleConnection *gc)
788{
789        struct im_connection *ic = purple_ic_by_gc(gc);
790
791        if (ic != NULL) {
792                imc_logout(ic, !gc->wants_to_die);
793        }
794}
795
796static void prplcb_conn_notice(PurpleConnection *gc, const char *text)
797{
798        struct im_connection *ic = purple_ic_by_gc(gc);
799
800        if (ic != NULL) {
801                imcb_log(ic, "%s", text);
802        }
803}
804
805static void prplcb_conn_report_disconnect_reason(PurpleConnection *gc, PurpleConnectionError reason, const char *text)
806{
807        struct im_connection *ic = purple_ic_by_gc(gc);
808
809        /* PURPLE_CONNECTION_ERROR_NAME_IN_USE means concurrent login,
810           should probably handle that. */
811        if (ic != NULL) {
812                imcb_error(ic, "%s", text);
813        }
814}
815
816static PurpleConnectionUiOps bee_conn_uiops =
817{
818        prplcb_conn_progress,                    /* connect_progress */
819        prplcb_conn_connected,                   /* connected */
820        prplcb_conn_disconnected,                /* disconnected */
821        prplcb_conn_notice,                      /* notice */
822        NULL,                                    /* report_disconnect */
823        NULL,                                    /* network_connected */
824        NULL,                                    /* network_disconnected */
825        prplcb_conn_report_disconnect_reason,    /* report_disconnect_reason */
826};
827
828static void prplcb_blist_update(PurpleBuddyList *list, PurpleBlistNode *node)
829{
830        if (node->type == PURPLE_BLIST_BUDDY_NODE) {
831                PurpleBuddy *bud = (PurpleBuddy *) node;
832                PurpleGroup *group = purple_buddy_get_group(bud);
833                struct im_connection *ic = purple_ic_by_pa(bud->account);
834                PurpleStatus *as;
835                int flags = 0;
836
837                if (ic == NULL) {
838                        return;
839                }
840
841                if (bud->server_alias) {
842                        imcb_rename_buddy(ic, bud->name, bud->server_alias);
843                } else if (bud->alias) {
844                        imcb_rename_buddy(ic, bud->name, bud->alias);
845                }
846
847                if (group) {
848                        imcb_add_buddy(ic, bud->name, purple_group_get_name(group));
849                }
850
851                flags |= purple_presence_is_online(bud->presence) ? OPT_LOGGED_IN : 0;
852                flags |= purple_presence_is_available(bud->presence) ? 0 : OPT_AWAY;
853
854                as = purple_presence_get_active_status(bud->presence);
855
856                imcb_buddy_status(ic, bud->name, flags, purple_status_get_name(as),
857                                  purple_status_get_attr_string(as, "message"));
858
859                imcb_buddy_times(ic, bud->name,
860                                 purple_presence_get_login_time(bud->presence),
861                                 purple_presence_get_idle_time(bud->presence));
862        }
863}
864
865static void prplcb_blist_new(PurpleBlistNode *node)
866{
867        if (node->type == PURPLE_BLIST_BUDDY_NODE) {
868                PurpleBuddy *bud = (PurpleBuddy *) node;
869                struct im_connection *ic = purple_ic_by_pa(bud->account);
870
871                if (ic == NULL) {
872                        return;
873                }
874
875                imcb_add_buddy(ic, bud->name, NULL);
876
877                prplcb_blist_update(NULL, node);
878        }
879}
880
881static void prplcb_blist_remove(PurpleBuddyList *list, PurpleBlistNode *node)
882{
883/*
884        PurpleBuddy *bud = (PurpleBuddy*) node;
885
886        if( node->type == PURPLE_BLIST_BUDDY_NODE )
887        {
888                struct im_connection *ic = purple_ic_by_pa( bud->account );
889
890                if( ic == NULL )
891                        return;
892
893                imcb_remove_buddy( ic, bud->name, NULL );
894        }
895*/
896}
897
898static PurpleBlistUiOps bee_blist_uiops =
899{
900        NULL,                      /* new_list */
901        prplcb_blist_new,          /* new_node */
902        NULL,                      /* show */
903        prplcb_blist_update,       /* update */
904        prplcb_blist_remove,       /* remove */
905};
906
907void prplcb_conv_new(PurpleConversation *conv)
908{
909        if (conv->type == PURPLE_CONV_TYPE_CHAT) {
910                struct im_connection *ic = purple_ic_by_pa(conv->account);
911                struct groupchat *gc;
912
913                gc = bee_chat_by_title(ic->bee, ic, conv->name);
914
915                if (!gc) {
916                        gc = imcb_chat_new(ic, conv->name);
917                        if (conv->title != NULL) {
918                                imcb_chat_name_hint(gc, conv->title);
919                        }
920                }
921
922                /* don't set the topic if it's just the name */
923                if (conv->title != NULL && strcmp(conv->name, conv->title) != 0) {
924                        imcb_chat_topic(gc, NULL, conv->title, 0);
925                }
926
927                conv->ui_data = gc;
928                gc->data = conv;
929
930                /* libpurple brokenness: Whatever. Show that we join right away,
931                   there's no clear "This is you!" signaling in _add_users so
932                   don't even try. */
933                imcb_chat_add_buddy(gc, gc->ic->acc->user);
934        }
935}
936
937void prplcb_conv_free(PurpleConversation *conv)
938{
939        struct groupchat *gc = conv->ui_data;
940
941        imcb_chat_free(gc);
942}
943
944void prplcb_conv_add_users(PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals)
945{
946        struct groupchat *gc = conv->ui_data;
947        GList *b;
948
949        for (b = cbuddies; b; b = b->next) {
950                PurpleConvChatBuddy *pcb = b->data;
951
952                imcb_chat_add_buddy(gc, pcb->name);
953        }
954}
955
956void prplcb_conv_del_users(PurpleConversation *conv, GList *cbuddies)
957{
958        struct groupchat *gc = conv->ui_data;
959        GList *b;
960
961        for (b = cbuddies; b; b = b->next) {
962                imcb_chat_remove_buddy(gc, b->data, "");
963        }
964}
965
966/* Generic handler for IM or chat messages, covers write_chat, write_im and write_conv */
967static void handle_conv_msg(PurpleConversation *conv, const char *who, const char *message_, guint32 bee_flags, time_t mtime)
968{
969        struct im_connection *ic = purple_ic_by_pa(conv->account);
970        struct groupchat *gc = conv->ui_data;
971        char *message = g_strdup(message_);
972        PurpleBuddy *buddy;
973
974        buddy = purple_find_buddy(conv->account, who);
975        if (buddy != NULL) {
976                who = purple_buddy_get_name(buddy);
977        }
978
979        if (conv->type == PURPLE_CONV_TYPE_IM) {
980                imcb_buddy_msg(ic, who, message, bee_flags, mtime);
981        } else if (gc) {
982                imcb_chat_msg(gc, who, message, bee_flags, mtime);
983        }
984
985        g_free(message);
986}
987
988/* Handles write_im and write_chat. Removes echoes of locally sent messages */
989static void prplcb_conv_msg(PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime)
990{
991        if (!(flags & PURPLE_MESSAGE_SEND)) {
992                handle_conv_msg(conv, who, message, 0, mtime);
993        }
994}
995
996/* Handles write_conv. Only passes self messages from other locations through.
997 * That is, only writes of PURPLE_MESSAGE_SEND.
998 * There are more events which might be handled in the future, but some are tricky.
999 * (images look like <img id="123">, what do i do with that?) */
1000static void prplcb_conv_write(PurpleConversation *conv, const char *who, const char *alias, const char *message,
1001                              PurpleMessageFlags flags, time_t mtime)
1002{
1003        if (flags & PURPLE_MESSAGE_SEND) {
1004                handle_conv_msg(conv, who, message, OPT_SELFMESSAGE, mtime);
1005        }
1006}
1007
1008/* No, this is not a ui_op but a signal. */
1009static void prplcb_buddy_typing(PurpleAccount *account, const char *who, gpointer null)
1010{
1011        PurpleConversation *conv;
1012        PurpleConvIm *im;
1013        int state;
1014
1015        if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, account)) == NULL) {
1016                return;
1017        }
1018
1019        im = PURPLE_CONV_IM(conv);
1020        switch (purple_conv_im_get_typing_state(im)) {
1021        case PURPLE_TYPING:
1022                state = OPT_TYPING;
1023                break;
1024        case PURPLE_TYPED:
1025                state = OPT_THINKING;
1026                break;
1027        default:
1028                state = 0;
1029        }
1030
1031        imcb_buddy_typing(purple_ic_by_pa(account), who, state);
1032}
1033
1034static PurpleConversationUiOps bee_conv_uiops =
1035{
1036        prplcb_conv_new,           /* create_conversation  */
1037        prplcb_conv_free,          /* destroy_conversation */
1038        prplcb_conv_msg,           /* write_chat           */
1039        prplcb_conv_msg,           /* write_im             */
1040        prplcb_conv_write,         /* write_conv           */
1041        prplcb_conv_add_users,     /* chat_add_users       */
1042        NULL,                      /* chat_rename_user     */
1043        prplcb_conv_del_users,     /* chat_remove_users    */
1044        NULL,                      /* chat_update_user     */
1045        NULL,                      /* present              */
1046        NULL,                      /* has_focus            */
1047        NULL,                      /* custom_smiley_add    */
1048        NULL,                      /* custom_smiley_write  */
1049        NULL,                      /* custom_smiley_close  */
1050        NULL,                      /* send_confirm         */
1051};
1052
1053struct prplcb_request_action_data {
1054        void *user_data, *bee_data;
1055        PurpleRequestActionCb yes, no;
1056        int yes_i, no_i;
1057};
1058
1059static void prplcb_request_action_yes(void *data)
1060{
1061        struct prplcb_request_action_data *pqad = data;
1062
1063        if (pqad->yes) {
1064                pqad->yes(pqad->user_data, pqad->yes_i);
1065        }
1066}
1067
1068static void prplcb_request_action_no(void *data)
1069{
1070        struct prplcb_request_action_data *pqad = data;
1071
1072        if (pqad->no) {
1073                pqad->no(pqad->user_data, pqad->no_i);
1074        }
1075}
1076
1077/* q->free() callback from query_del()*/
1078static void prplcb_request_action_free(void *data)
1079{
1080        struct prplcb_request_action_data *pqad = data;
1081
1082        pqad->bee_data = NULL;
1083        purple_request_close(PURPLE_REQUEST_ACTION, pqad);
1084}
1085
1086static void *prplcb_request_action(const char *title, const char *primary, const char *secondary,
1087                                   int default_action, PurpleAccount *account, const char *who,
1088                                   PurpleConversation *conv, void *user_data, size_t action_count,
1089                                   va_list actions)
1090{
1091        struct prplcb_request_action_data *pqad;
1092        int i;
1093        char *q;
1094
1095        pqad = g_new0(struct prplcb_request_action_data, 1);
1096
1097        for (i = 0; i < action_count; i++) {
1098                char *caption;
1099                void *fn;
1100
1101                caption = va_arg(actions, char*);
1102                fn = va_arg(actions, void*);
1103
1104                if (strstr(caption, "Accept") || strstr(caption, "OK")) {
1105                        pqad->yes = fn;
1106                        pqad->yes_i = i;
1107                } else if (strstr(caption, "Reject") || strstr(caption, "Cancel")) {
1108                        pqad->no = fn;
1109                        pqad->no_i = i;
1110                }
1111        }
1112
1113        pqad->user_data = user_data;
1114
1115        /* TODO: IRC stuff here :-( */
1116        q = g_strdup_printf("Request: %s\n\n%s\n\n%s", title, primary, secondary);
1117        pqad->bee_data = query_add(local_bee->ui_data, purple_ic_by_pa(account), q,
1118                                   prplcb_request_action_yes, prplcb_request_action_no,
1119                                   prplcb_request_action_free, pqad);
1120
1121        g_free(q);
1122
1123        return pqad;
1124}
1125
1126/* So it turns out some requests have no account context at all, because
1127 * libpurple hates us. This means that query_del_by_conn() won't remove those
1128 * on logout, and will segfault if the user replies. That's why this exists.
1129 */
1130static void prplcb_close_request(PurpleRequestType type, void *data)
1131{
1132        struct prplcb_request_action_data *pqad;
1133        struct request_input_data *ri;
1134        struct purple_data *pd;
1135
1136        if (!data) {
1137                return;
1138        }
1139
1140        switch (type) {
1141        case PURPLE_REQUEST_ACTION:
1142                pqad = data;
1143                /* if this is null, it's because query_del was run already */
1144                if (pqad->bee_data) {
1145                        query_del(local_bee->ui_data, pqad->bee_data);
1146                }
1147                g_free(pqad);
1148                break;
1149        case PURPLE_REQUEST_INPUT:
1150                ri = data;
1151                pd = ri->ic->proto_data;
1152                imcb_remove_buddy(ri->ic, ri->buddy, NULL);
1153                g_free(ri->buddy);
1154                g_hash_table_remove(pd->input_requests, GUINT_TO_POINTER(ri->id));
1155                break;
1156        default:
1157                g_free(data);
1158                break;
1159        }
1160
1161}
1162
1163void* prplcb_request_input(const char *title, const char *primary,
1164        const char *secondary, const char *default_value, gboolean multiline,
1165        gboolean masked, gchar *hint, const char *ok_text, GCallback ok_cb,
1166        const char *cancel_text, GCallback cancel_cb, PurpleAccount *account,
1167        const char *who, PurpleConversation *conv, void *user_data)
1168{
1169        struct im_connection *ic = purple_ic_by_pa(account);
1170        struct purple_data *pd = ic->proto_data;
1171        struct request_input_data *ri = g_new0(struct request_input_data, 1);
1172        guint id = pd->next_request_id++;
1173
1174        ri->id = id;
1175        ri->ic = ic;
1176        ri->buddy = g_strdup_printf("%s_%u", PURPLE_REQUEST_HANDLE, id);
1177        ri->data_callback = (ri_callback_t) ok_cb;
1178        ri->user_data = user_data;
1179        g_hash_table_insert(pd->input_requests, GUINT_TO_POINTER(id), ri);
1180
1181        imcb_add_buddy(ic, ri->buddy, NULL);
1182
1183        if (title && *title) {
1184                imcb_buddy_msg(ic, ri->buddy, title, 0, 0);
1185        }
1186
1187        if (primary && *primary) {
1188                imcb_buddy_msg(ic, ri->buddy, primary, 0, 0);
1189        }
1190
1191        if (secondary && *secondary) {
1192                imcb_buddy_msg(ic, ri->buddy, secondary, 0, 0);
1193        }
1194
1195        return ri;
1196}
1197
1198void purple_request_input_callback(guint id, struct im_connection *ic,
1199                                   const char *message, const char *who)
1200{
1201        struct purple_data *pd = ic->proto_data;
1202        struct request_input_data *ri;
1203
1204        if (!(ri = g_hash_table_lookup(pd->input_requests, GUINT_TO_POINTER(id)))) {
1205                return;
1206        }
1207
1208        ri->data_callback(ri->user_data, message);
1209
1210        purple_request_close(PURPLE_REQUEST_INPUT, ri);
1211}
1212
1213
1214static PurpleRequestUiOps bee_request_uiops =
1215{
1216        prplcb_request_input,      /* request_input */
1217        NULL,                      /* request_choice */
1218        prplcb_request_action,     /* request_action */
1219        NULL,                      /* request_fields */
1220        NULL,                      /* request_file */
1221        prplcb_close_request,      /* close_request */
1222        NULL,                      /* request_folder */
1223};
1224
1225static void prplcb_privacy_permit_added(PurpleAccount *account, const char *name)
1226{
1227        struct im_connection *ic = purple_ic_by_pa(account);
1228
1229        if (!g_slist_find_custom(ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp)) {
1230                ic->permit = g_slist_prepend(ic->permit, g_strdup(name));
1231        }
1232}
1233
1234static void prplcb_privacy_permit_removed(PurpleAccount *account, const char *name)
1235{
1236        struct im_connection *ic = purple_ic_by_pa(account);
1237        void *n;
1238
1239        n = g_slist_find_custom(ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp);
1240        ic->permit = g_slist_remove(ic->permit, n);
1241}
1242
1243static void prplcb_privacy_deny_added(PurpleAccount *account, const char *name)
1244{
1245        struct im_connection *ic = purple_ic_by_pa(account);
1246
1247        if (!g_slist_find_custom(ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp)) {
1248                ic->deny = g_slist_prepend(ic->deny, g_strdup(name));
1249        }
1250}
1251
1252static void prplcb_privacy_deny_removed(PurpleAccount *account, const char *name)
1253{
1254        struct im_connection *ic = purple_ic_by_pa(account);
1255        void *n;
1256
1257        n = g_slist_find_custom(ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp);
1258        ic->deny = g_slist_remove(ic->deny, n);
1259}
1260
1261static PurplePrivacyUiOps bee_privacy_uiops =
1262{
1263        prplcb_privacy_permit_added,       /* permit_added */
1264        prplcb_privacy_permit_removed,     /* permit_removed */
1265        prplcb_privacy_deny_added,         /* deny_added */
1266        prplcb_privacy_deny_removed,       /* deny_removed */
1267};
1268
1269static void prplcb_debug_print(PurpleDebugLevel level, const char *category, const char *arg_s)
1270{
1271        fprintf(stderr, "DEBUG %s: %s", category, arg_s);
1272}
1273
1274static PurpleDebugUiOps bee_debug_uiops =
1275{
1276        prplcb_debug_print,        /* print */
1277};
1278
1279static guint prplcb_ev_timeout_add(guint interval, GSourceFunc func, gpointer udata)
1280{
1281        return b_timeout_add(interval, (b_event_handler) func, udata);
1282}
1283
1284static guint prplcb_ev_input_add(int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata)
1285{
1286        return b_input_add(fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata);
1287}
1288
1289static gboolean prplcb_ev_remove(guint id)
1290{
1291        b_event_remove((gint) id);
1292        return TRUE;
1293}
1294
1295static PurpleEventLoopUiOps glib_eventloops =
1296{
1297        prplcb_ev_timeout_add,     /* timeout_add */
1298        prplcb_ev_remove,          /* timeout_remove */
1299        prplcb_ev_input_add,       /* input_add */
1300        prplcb_ev_remove,          /* input_remove */
1301};
1302
1303/* Absolutely no connection context at all. Thanks purple! brb crying */
1304static void *prplcb_notify_message(PurpleNotifyMsgType type, const char *title,
1305                                   const char *primary, const char *secondary)
1306{
1307        char *text = g_strdup_printf("%s%s - %s%s%s",
1308                (type == PURPLE_NOTIFY_MSG_ERROR) ? "Error: " : "",
1309                title,
1310                primary ?: "",
1311                (primary && secondary) ? " - " : "",
1312                secondary ?: ""
1313        );
1314
1315        if (local_bee->ui->log) {
1316                local_bee->ui->log(local_bee, "purple", text);
1317        }
1318
1319        g_free(text);
1320
1321        return NULL;
1322}
1323
1324static void *prplcb_notify_email(PurpleConnection *gc, const char *subject, const char *from,
1325                                 const char *to, const char *url)
1326{
1327        struct im_connection *ic = purple_ic_by_gc(gc);
1328
1329        imcb_notify_email(ic, "Received e-mail from %s for %s: %s <%s>", from, to, subject, url);
1330
1331        return NULL;
1332}
1333
1334static void *prplcb_notify_userinfo(PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info)
1335{
1336        struct im_connection *ic = purple_ic_by_gc(gc);
1337        GString *info = g_string_new("");
1338        GList *l = purple_notify_user_info_get_entries(user_info);
1339        char *key;
1340        const char *value;
1341        int n;
1342
1343        while (l) {
1344                PurpleNotifyUserInfoEntry *e = l->data;
1345
1346                switch (purple_notify_user_info_entry_get_type(e)) {
1347                case PURPLE_NOTIFY_USER_INFO_ENTRY_PAIR:
1348                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_HEADER:
1349                        key = g_strdup(purple_notify_user_info_entry_get_label(e));
1350                        value = purple_notify_user_info_entry_get_value(e);
1351
1352                        if (key) {
1353                                strip_html(key);
1354                                g_string_append_printf(info, "%s: ", key);
1355
1356                                if (value) {
1357                                        n = strlen(value) - 1;
1358                                        while (g_ascii_isspace(value[n])) {
1359                                                n--;
1360                                        }
1361                                        g_string_append_len(info, value, n + 1);
1362                                }
1363                                g_string_append_c(info, '\n');
1364                                g_free(key);
1365                        }
1366
1367                        break;
1368                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK:
1369                        g_string_append(info, "------------------------\n");
1370                        break;
1371                }
1372
1373                l = l->next;
1374        }
1375
1376        imcb_log(ic, "User %s info:\n%s", who, info->str);
1377        g_string_free(info, TRUE);
1378
1379        return NULL;
1380}
1381
1382static PurpleNotifyUiOps bee_notify_uiops =
1383{
1384        prplcb_notify_message,     /* notify_message */
1385        prplcb_notify_email,       /* notify_email */
1386        NULL,                      /* notify_emails */
1387        NULL,                      /* notify_formatted */
1388        NULL,                      /* notify_searchresults */
1389        NULL,                      /* notify_searchresults_new_rows */
1390        prplcb_notify_userinfo,    /* notify_userinfo */
1391};
1392
1393static void *prplcb_account_request_authorize(PurpleAccount *account, const char *remote_user,
1394                                              const char *id, const char *alias, const char *message, gboolean on_list,
1395                                              PurpleAccountRequestAuthorizationCb authorize_cb,
1396                                              PurpleAccountRequestAuthorizationCb deny_cb, void *user_data)
1397{
1398        struct im_connection *ic = purple_ic_by_pa(account);
1399        char *q;
1400
1401        if (alias) {
1402                q = g_strdup_printf("%s (%s) wants to add you to his/her contact "
1403                                    "list. (%s)", alias, remote_user, message);
1404        } else {
1405                q = g_strdup_printf("%s wants to add you to his/her contact "
1406                                    "list. (%s)", remote_user, message);
1407        }
1408
1409        imcb_ask_with_free(ic, q, user_data, authorize_cb, deny_cb, NULL);
1410        g_free(q);
1411
1412        return NULL;
1413}
1414
1415static PurpleAccountUiOps bee_account_uiops =
1416{
1417        NULL,                              /* notify_added */
1418        NULL,                              /* status_changed */
1419        NULL,                              /* request_add */
1420        prplcb_account_request_authorize,  /* request_authorize */
1421        NULL,                              /* close_account_request */
1422};
1423
1424extern PurpleXferUiOps bee_xfer_uiops;
1425
1426static void purple_ui_init()
1427{
1428        purple_connections_set_ui_ops(&bee_conn_uiops);
1429        purple_blist_set_ui_ops(&bee_blist_uiops);
1430        purple_conversations_set_ui_ops(&bee_conv_uiops);
1431        purple_request_set_ui_ops(&bee_request_uiops);
1432        purple_privacy_set_ui_ops(&bee_privacy_uiops);
1433        purple_notify_set_ui_ops(&bee_notify_uiops);
1434        purple_accounts_set_ui_ops(&bee_account_uiops);
1435        purple_xfers_set_ui_ops(&bee_xfer_uiops);
1436
1437        if (getenv("BITLBEE_DEBUG")) {
1438                purple_debug_set_ui_ops(&bee_debug_uiops);
1439        }
1440}
1441
1442void purple_initmodule()
1443{
1444        struct prpl funcs;
1445        GList *prots;
1446        GString *help;
1447        char *dir;
1448
1449        g_assert((int) B_EV_IO_READ == (int) PURPLE_INPUT_READ);
1450        g_assert((int) B_EV_IO_WRITE == (int) PURPLE_INPUT_WRITE);
1451
1452        dir = g_strdup_printf("%s/purple", global.conf->configdir);
1453        purple_util_set_user_dir(dir);
1454        g_free(dir);
1455
1456        dir = g_strdup_printf("%s/purple", global.conf->plugindir);
1457        purple_plugins_add_search_path(dir);
1458        g_free(dir);
1459
1460        purple_debug_set_enabled(FALSE);
1461        purple_core_set_ui_ops(&bee_core_uiops);
1462        purple_eventloop_set_ui_ops(&glib_eventloops);
1463        if (!purple_core_init("BitlBee")) {
1464                /* Initializing the core failed. Terminate. */
1465                fprintf(stderr, "libpurple initialization failed.\n");
1466                abort();
1467        }
1468
1469        if (proxytype != PROXY_NONE) {
1470                PurpleProxyInfo *pi = purple_global_proxy_get_info();
1471                switch (proxytype) {
1472                case PROXY_SOCKS4A:
1473                case PROXY_SOCKS4:
1474                        purple_proxy_info_set_type(pi, PURPLE_PROXY_SOCKS4);
1475                        break;
1476                case PROXY_SOCKS5:
1477                        purple_proxy_info_set_type(pi, PURPLE_PROXY_SOCKS5);
1478                        break;
1479                case PROXY_HTTP:
1480                        purple_proxy_info_set_type(pi, PURPLE_PROXY_HTTP);
1481                        break;
1482                }
1483                purple_proxy_info_set_host(pi, proxyhost);
1484                purple_proxy_info_set_port(pi, proxyport);
1485                purple_proxy_info_set_username(pi, proxyuser);
1486                purple_proxy_info_set_password(pi, proxypass);
1487        }
1488
1489        purple_set_blist(purple_blist_new());
1490
1491        /* No, really. So far there were ui_ops for everything, but now suddenly
1492           one needs to use signals for typing notification stuff. :-( */
1493        purple_signal_connect(purple_conversations_get_handle(), "buddy-typing",
1494                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1495        purple_signal_connect(purple_conversations_get_handle(), "buddy-typed",
1496                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1497        purple_signal_connect(purple_conversations_get_handle(), "buddy-typing-stopped",
1498                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1499
1500        memset(&funcs, 0, sizeof(funcs));
1501        funcs.login = purple_login;
1502        funcs.init = purple_init;
1503        funcs.logout = purple_logout;
1504        funcs.buddy_msg = purple_buddy_msg;
1505        funcs.away_states = purple_away_states;
1506        funcs.set_away = purple_set_away;
1507        funcs.add_buddy = purple_add_buddy;
1508        funcs.remove_buddy = purple_remove_buddy;
1509        funcs.add_permit = purple_add_permit;
1510        funcs.add_deny = purple_add_deny;
1511        funcs.rem_permit = purple_rem_permit;
1512        funcs.rem_deny = purple_rem_deny;
1513        funcs.get_info = purple_get_info;
1514        funcs.keepalive = purple_keepalive;
1515        funcs.send_typing = purple_send_typing;
1516        funcs.handle_cmp = g_strcasecmp;
1517        /* TODO(wilmer): Set these only for protocols that support them? */
1518        funcs.chat_msg = purple_chat_msg;
1519        funcs.chat_with = purple_chat_with;
1520        funcs.chat_invite = purple_chat_invite;
1521        funcs.chat_kick = purple_chat_kick;
1522        funcs.chat_leave = purple_chat_leave;
1523        funcs.chat_join = purple_chat_join;
1524        funcs.transfer_request = purple_transfer_request;
1525
1526        help = g_string_new("BitlBee libpurple module supports the following IM protocols:\n");
1527
1528        /* Add a protocol entry to BitlBee's structures for every protocol
1529           supported by this libpurple instance. */
1530        for (prots = purple_plugins_get_protocols(); prots; prots = prots->next) {
1531                PurplePlugin *prot = prots->data;
1532                struct prpl *ret;
1533
1534                /* If we already have this one (as a native module), don't
1535                   add a libpurple duplicate. */
1536                if (find_protocol(prot->info->id)) {
1537                        continue;
1538                }
1539
1540                ret = g_memdup(&funcs, sizeof(funcs));
1541                ret->name = ret->data = prot->info->id;
1542                if (strncmp(ret->name, "prpl-", 5) == 0) {
1543                        ret->name += 5;
1544                }
1545                register_protocol(ret);
1546
1547                g_string_append_printf(help, "\n* %s (%s)", ret->name, prot->info->name);
1548
1549                /* libpurple doesn't define a protocol called OSCAR, but we
1550                   need it to be compatible with normal BitlBee. */
1551                if (g_strcasecmp(prot->info->id, "prpl-aim") == 0) {
1552                        ret = g_memdup(&funcs, sizeof(funcs));
1553                        ret->name = "oscar";
1554                        ret->data = prot->info->id;
1555                        register_protocol(ret);
1556                }
1557        }
1558
1559        g_string_append(help, "\n\nFor used protocols, more information about available "
1560                        "settings can be found using \x02help purple <protocol name>\x02 "
1561                        "(create an account using that protocol first!)");
1562
1563        /* Add a simple dynamically-generated help item listing all
1564           the supported protocols. */
1565        help_add_mem(&global.help, "purple", help->str);
1566        g_string_free(help, TRUE);
1567}
Note: See TracBrowser for help on using the repository browser.