source: protocols/purple/purple.c @ 5447c59

Last change on this file since 5447c59 was 7a9d968, checked in by Wilmer van der Gaast <wilmer@…>, at 2018-03-10T11:30:39Z

Merge branch 'master' into HEAD

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