source: protocols/purple/purple.c @ d11822a

Last change on this file since d11822a was a9e0de2, checked in by dequis <dx@…>, at 2017-07-12T22:53:29Z

purple: enable debug during core initialization

Noisy but often important

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