source: protocols/purple/purple.c @ e17d0a4

Last change on this file since e17d0a4 was e5d2c56, checked in by dequis <dx@…>, at 2019-02-03T15:18:54Z

Remove OSCAR since both ICQ and AIM are dead

RIP.

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