source: protocols/purple/purple.c @ faa7abb6

Last change on this file since faa7abb6 was faa7abb6, checked in by dequis <dx@…>, at 2016-12-27T00:29:30Z

purple: hack to keep the purple-line auth token in the config

  • Property mode set to 100644
File size: 55.5 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  libpurple module - Main file                                             *
5*                                                                           *
6*  Copyright 2009-2012 Wilmer van der Gaast <wilmer@gaast.net>              *
7*                                                                           *
8*  This program is free software; you can redistribute it and/or modify     *
9*  it under the terms of the GNU General Public License as published by     *
10*  the Free Software Foundation; either version 2 of the License, or        *
11*  (at your option) any later version.                                      *
12*                                                                           *
13*  This program is distributed in the hope that it will be useful,          *
14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
16*  GNU General Public License for more details.                             *
17*                                                                           *
18*  You should have received a copy of the GNU General Public License along  *
19*  with this program; if not, write to the Free Software Foundation, Inc.,  *
20*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
21*                                                                           *
22\***************************************************************************/
23
24#include "bitlbee.h"
25#include "bpurple.h"
26#include "help.h"
27
28#include <stdarg.h>
29
30#include <glib.h>
31#include <purple.h>
32
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_new(NULL);
822                                g_string_printf(missing_settings,
823                                        "Can't join %s. The following settings are required: ", room);
824                        }
825                        g_string_append_printf(missing_settings, "%s, ", pce->identifier);
826                }
827
828                g_free(pce);
829        }
830
831        g_list_free(info);
832
833        if (missing_settings) {
834                /* remove the ", " from the end */
835                g_string_truncate(missing_settings, missing_settings->len - 2);
836
837                imcb_error(ic, missing_settings->str);
838
839                g_string_free(missing_settings, TRUE);
840                g_hash_table_destroy(chat_hash);
841                return NULL;
842        }
843
844        /* do this before serv_join_chat to handle cases where prplcb_conv_new is called immediately (not async) */
845        gc = imcb_chat_new(ic, room);
846
847        serv_join_chat(purple_account_get_connection(pd->account), chat_hash);
848
849        g_hash_table_destroy(chat_hash);
850
851        return gc;
852}
853
854void purple_chat_list(struct im_connection *ic, const char *server)
855{
856        PurpleRoomlist *list;
857        struct purple_data *pd = ic->proto_data;
858        PurplePlugin *prpl = purple_plugins_find_with_id(pd->account->protocol_id);
859        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
860
861        if (!pi || !pi->roomlist_get_list) {
862                imcb_log(ic, "Room listing unsupported by this purple plugin");
863                return;
864        }
865
866        g_free(pd->chat_list_server);
867        pd->chat_list_server = (server && *server) ? g_strdup(server) : NULL;
868
869        list = purple_roomlist_get_list(pd->account->gc);
870
871        if (list) {
872                struct purple_roomlist_data *rld = list->ui_data;
873                rld->initialized = TRUE;
874
875                purple_roomlist_ref(list);
876        }
877}
878
879/* handles either prpl->chat_(add|free)_settings depending on the value of 'add' */
880static void purple_chat_update_settings(account_t *acc, set_t **head, gboolean add)
881{
882        PurplePlugin *prpl = purple_plugins_find_with_id((char *) acc->prpl->data);
883        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
884        GList *info, *l;
885
886        if (!pi->chat_info || !pi->chat_info_defaults) {
887                return;
888        }
889
890        /* hack / leap of faith: pass a NULL here because we don't have a connection yet.
891         * i reviewed all the built-in prpls and a bunch of third-party ones and none
892         * of them seem to need this parameter at all, so... i hope it never crashes */
893        info = pi->chat_info(NULL);
894
895        for (l = info; l; l = l->next) {
896                struct proto_chat_entry *pce = l->data;
897                char *key;
898
899                if (strcmp(pce->identifier, "handle") == 0 ||
900                    strcmp(pce->identifier, "password") == 0 ||
901                    strcmp(pce->identifier, "passwd") == 0) {
902                        /* skip these, they are handled above */
903                        g_free(pce);
904                        continue;
905                }
906
907                key = g_strdup_printf("purple_%s", pce->identifier);
908                str_reject_chars(key, " -", '_');
909
910                if (add) {
911                        set_add(head, key, NULL, NULL, NULL);
912                } else {
913                        set_del(head, key);
914                }
915
916                g_free(key);
917                g_free(pce);
918        }
919
920        g_list_free(NULL);
921        g_list_free(info);
922}
923
924static void purple_chat_add_settings(account_t *acc, set_t **head)
925{
926        purple_chat_update_settings(acc, head, TRUE);
927}
928
929static void purple_chat_free_settings(account_t *acc, set_t **head)
930{
931        purple_chat_update_settings(acc, head, FALSE);
932}
933
934void purple_transfer_request(struct im_connection *ic, file_transfer_t *ft, char *handle);
935
936static void purple_ui_init();
937
938GHashTable *prplcb_ui_info()
939{
940        static GHashTable *ret;
941
942        if (ret == NULL) {
943                ret = g_hash_table_new(g_str_hash, g_str_equal);
944                g_hash_table_insert(ret, "name", "BitlBee");
945                g_hash_table_insert(ret, "version", BITLBEE_VERSION);
946        }
947
948        return ret;
949}
950
951static PurpleCoreUiOps bee_core_uiops =
952{
953        NULL,                      /* ui_prefs_init */
954        NULL,                      /* debug_ui_init */
955        purple_ui_init,            /* ui_init */
956        NULL,                      /* quit */
957        prplcb_ui_info,            /* get_ui_info */
958};
959
960static void prplcb_conn_progress(PurpleConnection *gc, const char *text, size_t step, size_t step_count)
961{
962        struct im_connection *ic = purple_ic_by_gc(gc);
963
964        imcb_log(ic, "%s", text);
965}
966
967static void prplcb_conn_connected(PurpleConnection *gc)
968{
969        struct im_connection *ic = purple_ic_by_gc(gc);
970        struct purple_data *pd = ic->proto_data;
971        const char *dn, *token;
972        set_t *s;
973
974        imcb_connected(ic);
975
976        if ((dn = purple_connection_get_display_name(gc)) &&
977            (s = set_find(&ic->acc->set, "display_name"))) {
978                g_free(s->value);
979                s->value = g_strdup(dn);
980        }
981
982        // user list needs to be requested for Gadu-Gadu
983        purple_gg_buddylist_import(gc);
984
985        /* more awful hacks, because clearly we didn't have enough of those */
986        if ((s = set_find(&ic->acc->set, "line-auth-token")) &&
987            (token = purple_account_get_string(pd->account, "line-auth-token", NULL))) {
988                g_free(s->value);
989                s->value = g_strdup(token);
990        }
991
992        ic->flags |= OPT_DOES_HTML;
993}
994
995static void prplcb_conn_disconnected(PurpleConnection *gc)
996{
997        struct im_connection *ic = purple_ic_by_gc(gc);
998
999        if (ic != NULL) {
1000                imc_logout(ic, !gc->wants_to_die);
1001        }
1002}
1003
1004static void prplcb_conn_notice(PurpleConnection *gc, const char *text)
1005{
1006        struct im_connection *ic = purple_ic_by_gc(gc);
1007
1008        if (ic != NULL) {
1009                imcb_log(ic, "%s", text);
1010        }
1011}
1012
1013static void prplcb_conn_report_disconnect_reason(PurpleConnection *gc, PurpleConnectionError reason, const char *text)
1014{
1015        struct im_connection *ic = purple_ic_by_gc(gc);
1016
1017        /* PURPLE_CONNECTION_ERROR_NAME_IN_USE means concurrent login,
1018           should probably handle that. */
1019        if (ic != NULL) {
1020                imcb_error(ic, "%s", text);
1021        }
1022}
1023
1024static PurpleConnectionUiOps bee_conn_uiops =
1025{
1026        prplcb_conn_progress,                    /* connect_progress */
1027        prplcb_conn_connected,                   /* connected */
1028        prplcb_conn_disconnected,                /* disconnected */
1029        prplcb_conn_notice,                      /* notice */
1030        NULL,                                    /* report_disconnect */
1031        NULL,                                    /* network_connected */
1032        NULL,                                    /* network_disconnected */
1033        prplcb_conn_report_disconnect_reason,    /* report_disconnect_reason */
1034};
1035
1036static void prplcb_blist_update(PurpleBuddyList *list, PurpleBlistNode *node)
1037{
1038        if (node->type == PURPLE_BLIST_BUDDY_NODE) {
1039                PurpleBuddy *bud = (PurpleBuddy *) node;
1040                PurpleGroup *group = purple_buddy_get_group(bud);
1041                struct im_connection *ic = purple_ic_by_pa(bud->account);
1042                struct purple_data *pd = ic->proto_data;
1043                PurpleStatus *as;
1044                int flags = 0;
1045                char *alias = NULL;
1046
1047                if (ic == NULL) {
1048                        return;
1049                }
1050
1051                alias = bud->server_alias ? : bud->alias;
1052
1053                if (alias) {
1054                        imcb_rename_buddy(ic, bud->name, alias);
1055                        if (pd->flags & PURPLE_OPT_SHOULD_SET_NICK) {
1056                                imcb_buddy_nick_change(ic, bud->name, alias);
1057                        }
1058                }
1059
1060                if (group) {
1061                        imcb_add_buddy(ic, bud->name, purple_group_get_name(group));
1062                }
1063
1064                flags |= purple_presence_is_online(bud->presence) ? OPT_LOGGED_IN : 0;
1065                flags |= purple_presence_is_available(bud->presence) ? 0 : OPT_AWAY;
1066
1067                as = purple_presence_get_active_status(bud->presence);
1068
1069                imcb_buddy_status(ic, bud->name, flags, purple_status_get_name(as),
1070                                  purple_status_get_attr_string(as, "message"));
1071
1072                imcb_buddy_times(ic, bud->name,
1073                                 purple_presence_get_login_time(bud->presence),
1074                                 purple_presence_get_idle_time(bud->presence));
1075        }
1076}
1077
1078static void prplcb_blist_new(PurpleBlistNode *node)
1079{
1080        if (node->type == PURPLE_BLIST_BUDDY_NODE) {
1081                PurpleBuddy *bud = (PurpleBuddy *) node;
1082                struct im_connection *ic = purple_ic_by_pa(bud->account);
1083
1084                if (ic == NULL) {
1085                        return;
1086                }
1087
1088                imcb_add_buddy(ic, bud->name, NULL);
1089
1090                prplcb_blist_update(NULL, node);
1091        }
1092}
1093
1094static void prplcb_blist_remove(PurpleBuddyList *list, PurpleBlistNode *node)
1095{
1096/*
1097        PurpleBuddy *bud = (PurpleBuddy*) node;
1098
1099        if( node->type == PURPLE_BLIST_BUDDY_NODE )
1100        {
1101                struct im_connection *ic = purple_ic_by_pa( bud->account );
1102
1103                if( ic == NULL )
1104                        return;
1105
1106                imcb_remove_buddy( ic, bud->name, NULL );
1107        }
1108*/
1109}
1110
1111static PurpleBlistUiOps bee_blist_uiops =
1112{
1113        NULL,                      /* new_list */
1114        prplcb_blist_new,          /* new_node */
1115        NULL,                      /* show */
1116        prplcb_blist_update,       /* update */
1117        prplcb_blist_remove,       /* remove */
1118};
1119
1120void prplcb_conv_new(PurpleConversation *conv)
1121{
1122        if (conv->type == PURPLE_CONV_TYPE_CHAT) {
1123                struct im_connection *ic = purple_ic_by_pa(conv->account);
1124                struct groupchat *gc;
1125
1126                gc = bee_chat_by_title(ic->bee, ic, conv->name);
1127
1128                if (!gc) {
1129                        gc = imcb_chat_new(ic, conv->name);
1130                        if (conv->title != NULL) {
1131                                imcb_chat_name_hint(gc, conv->title);
1132                        }
1133                }
1134
1135                /* don't set the topic if it's just the name */
1136                if (conv->title != NULL && strcmp(conv->name, conv->title) != 0) {
1137                        imcb_chat_topic(gc, NULL, conv->title, 0);
1138                }
1139
1140                conv->ui_data = gc;
1141                gc->data = conv;
1142
1143                /* libpurple brokenness: Whatever. Show that we join right away,
1144                   there's no clear "This is you!" signaling in _add_users so
1145                   don't even try. */
1146                imcb_chat_add_buddy(gc, gc->ic->acc->user);
1147        }
1148}
1149
1150void prplcb_conv_free(PurpleConversation *conv)
1151{
1152        struct groupchat *gc = conv->ui_data;
1153
1154        imcb_chat_free(gc);
1155}
1156
1157void prplcb_conv_add_users(PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals)
1158{
1159        struct groupchat *gc = conv->ui_data;
1160        GList *b;
1161
1162        for (b = cbuddies; b; b = b->next) {
1163                PurpleConvChatBuddy *pcb = b->data;
1164
1165                imcb_chat_add_buddy(gc, pcb->name);
1166        }
1167}
1168
1169void prplcb_conv_del_users(PurpleConversation *conv, GList *cbuddies)
1170{
1171        struct groupchat *gc = conv->ui_data;
1172        GList *b;
1173
1174        for (b = cbuddies; b; b = b->next) {
1175                imcb_chat_remove_buddy(gc, b->data, "");
1176        }
1177}
1178
1179/* Generic handler for IM or chat messages, covers write_chat, write_im and write_conv */
1180static void handle_conv_msg(PurpleConversation *conv, const char *who, const char *message_, guint32 bee_flags, time_t mtime)
1181{
1182        struct im_connection *ic = purple_ic_by_pa(conv->account);
1183        struct groupchat *gc = conv->ui_data;
1184        char *message = g_strdup(message_);
1185        PurpleBuddy *buddy;
1186
1187        buddy = purple_find_buddy(conv->account, who);
1188        if (buddy != NULL) {
1189                who = purple_buddy_get_name(buddy);
1190        }
1191
1192        if (conv->type == PURPLE_CONV_TYPE_IM) {
1193                imcb_buddy_msg(ic, who, message, bee_flags, mtime);
1194        } else if (gc) {
1195                imcb_chat_msg(gc, who, message, bee_flags, mtime);
1196        }
1197
1198        g_free(message);
1199}
1200
1201/* Handles write_im and write_chat. Removes echoes of locally sent messages */
1202static void prplcb_conv_msg(PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime)
1203{
1204        if (!(flags & PURPLE_MESSAGE_SEND)) {
1205                handle_conv_msg(conv, who, message, 0, mtime);
1206        }
1207}
1208
1209/* Handles write_conv. Only passes self messages from other locations through.
1210 * That is, only writes of PURPLE_MESSAGE_SEND.
1211 * There are more events which might be handled in the future, but some are tricky.
1212 * (images look like <img id="123">, what do i do with that?) */
1213static void prplcb_conv_write(PurpleConversation *conv, const char *who, const char *alias, const char *message,
1214                              PurpleMessageFlags flags, time_t mtime)
1215{
1216        if (flags & PURPLE_MESSAGE_SEND) {
1217                handle_conv_msg(conv, who, message, OPT_SELFMESSAGE, mtime);
1218        }
1219}
1220
1221/* No, this is not a ui_op but a signal. */
1222static void prplcb_buddy_typing(PurpleAccount *account, const char *who, gpointer null)
1223{
1224        PurpleConversation *conv;
1225        PurpleConvIm *im;
1226        int state;
1227
1228        if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, account)) == NULL) {
1229                return;
1230        }
1231
1232        im = PURPLE_CONV_IM(conv);
1233        switch (purple_conv_im_get_typing_state(im)) {
1234        case PURPLE_TYPING:
1235                state = OPT_TYPING;
1236                break;
1237        case PURPLE_TYPED:
1238                state = OPT_THINKING;
1239                break;
1240        default:
1241                state = 0;
1242        }
1243
1244        imcb_buddy_typing(purple_ic_by_pa(account), who, state);
1245}
1246
1247static PurpleConversationUiOps bee_conv_uiops =
1248{
1249        prplcb_conv_new,           /* create_conversation  */
1250        prplcb_conv_free,          /* destroy_conversation */
1251        prplcb_conv_msg,           /* write_chat           */
1252        prplcb_conv_msg,           /* write_im             */
1253        prplcb_conv_write,         /* write_conv           */
1254        prplcb_conv_add_users,     /* chat_add_users       */
1255        NULL,                      /* chat_rename_user     */
1256        prplcb_conv_del_users,     /* chat_remove_users    */
1257        NULL,                      /* chat_update_user     */
1258        NULL,                      /* present              */
1259        NULL,                      /* has_focus            */
1260        NULL,                      /* custom_smiley_add    */
1261        NULL,                      /* custom_smiley_write  */
1262        NULL,                      /* custom_smiley_close  */
1263        NULL,                      /* send_confirm         */
1264};
1265
1266struct prplcb_request_action_data {
1267        void *user_data, *bee_data;
1268        PurpleRequestActionCb yes, no;
1269        int yes_i, no_i;
1270};
1271
1272static void prplcb_request_action_yes(void *data)
1273{
1274        struct prplcb_request_action_data *pqad = data;
1275
1276        if (pqad->yes) {
1277                pqad->yes(pqad->user_data, pqad->yes_i);
1278        }
1279}
1280
1281static void prplcb_request_action_no(void *data)
1282{
1283        struct prplcb_request_action_data *pqad = data;
1284
1285        if (pqad->no) {
1286                pqad->no(pqad->user_data, pqad->no_i);
1287        }
1288}
1289
1290/* q->free() callback from query_del()*/
1291static void prplcb_request_action_free(void *data)
1292{
1293        struct prplcb_request_action_data *pqad = data;
1294
1295        pqad->bee_data = NULL;
1296        purple_request_close(PURPLE_REQUEST_ACTION, pqad);
1297}
1298
1299static void *prplcb_request_action(const char *title, const char *primary, const char *secondary,
1300                                   int default_action, PurpleAccount *account, const char *who,
1301                                   PurpleConversation *conv, void *user_data, size_t action_count,
1302                                   va_list actions)
1303{
1304        struct prplcb_request_action_data *pqad;
1305        int i;
1306        char *q;
1307
1308        pqad = g_new0(struct prplcb_request_action_data, 1);
1309
1310        for (i = 0; i < action_count; i++) {
1311                char *caption;
1312                void *fn;
1313
1314                caption = va_arg(actions, char*);
1315                fn = va_arg(actions, void*);
1316
1317                if (strstr(caption, "Accept") || strstr(caption, "OK")) {
1318                        pqad->yes = fn;
1319                        pqad->yes_i = i;
1320                } else if (strstr(caption, "Reject") || strstr(caption, "Cancel")) {
1321                        pqad->no = fn;
1322                        pqad->no_i = i;
1323                }
1324        }
1325
1326        pqad->user_data = user_data;
1327
1328        /* TODO: IRC stuff here :-( */
1329        q = g_strdup_printf("Request: %s\n\n%s\n\n%s", title, primary, secondary);
1330        pqad->bee_data = query_add(local_bee->ui_data, purple_ic_by_pa(account), q,
1331                                   prplcb_request_action_yes, prplcb_request_action_no,
1332                                   prplcb_request_action_free, pqad);
1333
1334        g_free(q);
1335
1336        return pqad;
1337}
1338
1339/* So it turns out some requests have no account context at all, because
1340 * libpurple hates us. This means that query_del_by_conn() won't remove those
1341 * on logout, and will segfault if the user replies. That's why this exists.
1342 */
1343static void prplcb_close_request(PurpleRequestType type, void *data)
1344{
1345        struct prplcb_request_action_data *pqad;
1346        struct request_input_data *ri;
1347        struct purple_data *pd;
1348
1349        if (!data) {
1350                return;
1351        }
1352
1353        switch (type) {
1354        case PURPLE_REQUEST_ACTION:
1355                pqad = data;
1356                /* if this is null, it's because query_del was run already */
1357                if (pqad->bee_data) {
1358                        query_del(local_bee->ui_data, pqad->bee_data);
1359                }
1360                g_free(pqad);
1361                break;
1362        case PURPLE_REQUEST_INPUT:
1363                ri = data;
1364                pd = ri->ic->proto_data;
1365                imcb_remove_buddy(ri->ic, ri->buddy, NULL);
1366                g_free(ri->buddy);
1367                g_hash_table_remove(pd->input_requests, GUINT_TO_POINTER(ri->id));
1368                break;
1369        default:
1370                g_free(data);
1371                break;
1372        }
1373
1374}
1375
1376void* prplcb_request_input(const char *title, const char *primary,
1377        const char *secondary, const char *default_value, gboolean multiline,
1378        gboolean masked, gchar *hint, const char *ok_text, GCallback ok_cb,
1379        const char *cancel_text, GCallback cancel_cb, PurpleAccount *account,
1380        const char *who, PurpleConversation *conv, void *user_data)
1381{
1382        struct im_connection *ic = purple_ic_by_pa(account);
1383        struct purple_data *pd = ic->proto_data;
1384        struct request_input_data *ri;
1385        guint id;
1386
1387        /* hack so that jabber's chat list doesn't ask for conference server twice */
1388        if (pd->chat_list_server && title && g_strcmp0(title, "Enter a Conference Server") == 0) {
1389                ((ri_callback_t) ok_cb)(user_data, pd->chat_list_server);
1390                g_free(pd->chat_list_server);
1391                pd->chat_list_server = NULL;
1392                return NULL;
1393        }
1394
1395        id = pd->next_request_id++;
1396        ri = g_new0(struct request_input_data, 1);
1397
1398        ri->id = id;
1399        ri->ic = ic;
1400        ri->buddy = g_strdup_printf("%s_%u", PURPLE_REQUEST_HANDLE, id);
1401        ri->data_callback = (ri_callback_t) ok_cb;
1402        ri->user_data = user_data;
1403        g_hash_table_insert(pd->input_requests, GUINT_TO_POINTER(id), ri);
1404
1405        imcb_add_buddy(ic, ri->buddy, NULL);
1406
1407        if (title && *title) {
1408                imcb_buddy_msg(ic, ri->buddy, title, 0, 0);
1409        }
1410
1411        if (primary && *primary) {
1412                imcb_buddy_msg(ic, ri->buddy, primary, 0, 0);
1413        }
1414
1415        if (secondary && *secondary) {
1416                imcb_buddy_msg(ic, ri->buddy, secondary, 0, 0);
1417        }
1418
1419        return ri;
1420}
1421
1422void purple_request_input_callback(guint id, struct im_connection *ic,
1423                                   const char *message, const char *who)
1424{
1425        struct purple_data *pd = ic->proto_data;
1426        struct request_input_data *ri;
1427
1428        if (!(ri = g_hash_table_lookup(pd->input_requests, GUINT_TO_POINTER(id)))) {
1429                return;
1430        }
1431
1432        ri->data_callback(ri->user_data, message);
1433
1434        purple_request_close(PURPLE_REQUEST_INPUT, ri);
1435}
1436
1437
1438static PurpleRequestUiOps bee_request_uiops =
1439{
1440        prplcb_request_input,      /* request_input */
1441        NULL,                      /* request_choice */
1442        prplcb_request_action,     /* request_action */
1443        NULL,                      /* request_fields */
1444        NULL,                      /* request_file */
1445        prplcb_close_request,      /* close_request */
1446        NULL,                      /* request_folder */
1447};
1448
1449static void prplcb_privacy_permit_added(PurpleAccount *account, const char *name)
1450{
1451        struct im_connection *ic = purple_ic_by_pa(account);
1452
1453        if (!g_slist_find_custom(ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp)) {
1454                ic->permit = g_slist_prepend(ic->permit, g_strdup(name));
1455        }
1456}
1457
1458static void prplcb_privacy_permit_removed(PurpleAccount *account, const char *name)
1459{
1460        struct im_connection *ic = purple_ic_by_pa(account);
1461        void *n;
1462
1463        n = g_slist_find_custom(ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp);
1464        ic->permit = g_slist_remove(ic->permit, n);
1465}
1466
1467static void prplcb_privacy_deny_added(PurpleAccount *account, const char *name)
1468{
1469        struct im_connection *ic = purple_ic_by_pa(account);
1470
1471        if (!g_slist_find_custom(ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp)) {
1472                ic->deny = g_slist_prepend(ic->deny, g_strdup(name));
1473        }
1474}
1475
1476static void prplcb_privacy_deny_removed(PurpleAccount *account, const char *name)
1477{
1478        struct im_connection *ic = purple_ic_by_pa(account);
1479        void *n;
1480
1481        n = g_slist_find_custom(ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp);
1482        ic->deny = g_slist_remove(ic->deny, n);
1483}
1484
1485static PurplePrivacyUiOps bee_privacy_uiops =
1486{
1487        prplcb_privacy_permit_added,       /* permit_added */
1488        prplcb_privacy_permit_removed,     /* permit_removed */
1489        prplcb_privacy_deny_added,         /* deny_added */
1490        prplcb_privacy_deny_removed,       /* deny_removed */
1491};
1492
1493static void prplcb_roomlist_create(PurpleRoomlist *list)
1494{
1495        struct purple_roomlist_data *rld;
1496
1497        list->ui_data = rld = g_new0(struct purple_roomlist_data, 1);
1498        rld->topic = -1;
1499}
1500
1501static void prplcb_roomlist_set_fields(PurpleRoomlist *list, GList *fields)
1502{
1503        gint topic = -1;
1504        GList *l;
1505        guint i;
1506        PurpleRoomlistField *field;
1507        struct purple_roomlist_data *rld = list->ui_data;
1508
1509        for (i = 0, l = fields; l; i++, l = l->next) {
1510                field = l->data;
1511
1512                /* Use the first visible string field as a fallback topic */
1513                if (i != 0 && topic < 0 && !field->hidden &&
1514                    field->type == PURPLE_ROOMLIST_FIELD_STRING) {
1515                        topic = i;
1516                }
1517
1518                if ((g_strcasecmp(field->name, "description") == 0) ||
1519                    (g_strcasecmp(field->name, "topic") == 0)) {
1520                        if (field->type == PURPLE_ROOMLIST_FIELD_STRING) {
1521                                rld->topic = i;
1522                        }
1523                }
1524        }
1525
1526        if (rld->topic < 0) {
1527                rld->topic = topic;
1528        }
1529}
1530
1531static char *prplcb_roomlist_get_room_name(PurpleRoomlist *list, PurpleRoomlistRoom *room)
1532{
1533        struct im_connection *ic = purple_ic_by_pa(list->account);
1534        struct purple_data *pd = ic->proto_data;
1535        PurplePlugin *prpl = purple_plugins_find_with_id(pd->account->protocol_id);
1536        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
1537
1538        if (pi && pi->roomlist_room_serialize) {
1539                return pi->roomlist_room_serialize(room);
1540        } else {
1541                return g_strdup(purple_roomlist_room_get_name(room));
1542        }
1543}
1544
1545static void prplcb_roomlist_add_room(PurpleRoomlist *list, PurpleRoomlistRoom *room)
1546{
1547        bee_chat_info_t *ci;
1548        char *title;
1549        const char *topic;
1550        GList *fields;
1551        struct purple_roomlist_data *rld = list->ui_data;
1552
1553        fields = purple_roomlist_room_get_fields(room);
1554        title = prplcb_roomlist_get_room_name(list, room);
1555
1556        if (rld->topic >= 0) {
1557                topic = g_list_nth_data(fields, rld->topic);
1558        } else {
1559                topic = NULL;
1560        }
1561
1562        ci = g_new(bee_chat_info_t, 1);
1563        ci->title = title;
1564        ci->topic = g_strdup(topic);
1565        rld->chats = g_slist_prepend(rld->chats, ci);
1566}
1567
1568static void prplcb_roomlist_in_progress(PurpleRoomlist *list, gboolean in_progress)
1569{
1570        struct im_connection *ic;
1571        struct purple_data *pd;
1572        struct purple_roomlist_data *rld = list->ui_data;
1573
1574        if (in_progress || !rld) {
1575                return;
1576        }
1577
1578        ic = purple_ic_by_pa(list->account);
1579        imcb_chat_list_free(ic);
1580
1581        pd = ic->proto_data;
1582        g_free(pd->chat_list_server);
1583        pd->chat_list_server = NULL;
1584
1585        ic->chatlist = g_slist_reverse(rld->chats);
1586        rld->chats = NULL;
1587
1588        imcb_chat_list_finish(ic);
1589
1590        if (rld->initialized) {
1591                purple_roomlist_unref(list);
1592        }
1593}
1594
1595static void prplcb_roomlist_destroy(PurpleRoomlist *list)
1596{
1597        g_free(list->ui_data);
1598        list->ui_data = NULL;
1599}
1600
1601static PurpleRoomlistUiOps bee_roomlist_uiops =
1602{
1603        NULL,                         /* show_with_account */
1604        prplcb_roomlist_create,       /* create */
1605        prplcb_roomlist_set_fields,   /* set_fields */
1606        prplcb_roomlist_add_room,     /* add_room */
1607        prplcb_roomlist_in_progress,  /* in_progress */
1608        prplcb_roomlist_destroy,      /* destroy */
1609};
1610
1611static void prplcb_debug_print(PurpleDebugLevel level, const char *category, const char *arg_s)
1612{
1613        fprintf(stderr, "DEBUG %s: %s", category, arg_s);
1614}
1615
1616static PurpleDebugUiOps bee_debug_uiops =
1617{
1618        prplcb_debug_print,        /* print */
1619};
1620
1621static guint prplcb_ev_timeout_add(guint interval, GSourceFunc func, gpointer udata)
1622{
1623        return b_timeout_add(interval, (b_event_handler) func, udata);
1624}
1625
1626static guint prplcb_ev_input_add(int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata)
1627{
1628        return b_input_add(fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata);
1629}
1630
1631static gboolean prplcb_ev_remove(guint id)
1632{
1633        b_event_remove((gint) id);
1634        return TRUE;
1635}
1636
1637static PurpleEventLoopUiOps glib_eventloops =
1638{
1639        prplcb_ev_timeout_add,     /* timeout_add */
1640        prplcb_ev_remove,          /* timeout_remove */
1641        prplcb_ev_input_add,       /* input_add */
1642        prplcb_ev_remove,          /* input_remove */
1643};
1644
1645/* Absolutely no connection context at all. Thanks purple! brb crying */
1646static void *prplcb_notify_message(PurpleNotifyMsgType type, const char *title,
1647                                   const char *primary, const char *secondary)
1648{
1649        char *text = g_strdup_printf("%s%s - %s%s%s",
1650                (type == PURPLE_NOTIFY_MSG_ERROR) ? "Error: " : "",
1651                title,
1652                primary ?: "",
1653                (primary && secondary) ? " - " : "",
1654                secondary ?: ""
1655        );
1656
1657        if (local_bee->ui->log) {
1658                local_bee->ui->log(local_bee, "purple", text);
1659        }
1660
1661        g_free(text);
1662
1663        return NULL;
1664}
1665
1666static void *prplcb_notify_email(PurpleConnection *gc, const char *subject, const char *from,
1667                                 const char *to, const char *url)
1668{
1669        struct im_connection *ic = purple_ic_by_gc(gc);
1670
1671        imcb_notify_email(ic, "Received e-mail from %s for %s: %s <%s>", from, to, subject, url);
1672
1673        return NULL;
1674}
1675
1676static void *prplcb_notify_userinfo(PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info)
1677{
1678        struct im_connection *ic = purple_ic_by_gc(gc);
1679        GString *info = g_string_new("");
1680        GList *l = purple_notify_user_info_get_entries(user_info);
1681        char *key;
1682        const char *value;
1683        int n;
1684
1685        while (l) {
1686                PurpleNotifyUserInfoEntry *e = l->data;
1687
1688                switch (purple_notify_user_info_entry_get_type(e)) {
1689                case PURPLE_NOTIFY_USER_INFO_ENTRY_PAIR:
1690                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_HEADER:
1691                        key = g_strdup(purple_notify_user_info_entry_get_label(e));
1692                        value = purple_notify_user_info_entry_get_value(e);
1693
1694                        if (key) {
1695                                strip_html(key);
1696                                g_string_append_printf(info, "%s: ", key);
1697
1698                                if (value) {
1699                                        n = strlen(value) - 1;
1700                                        while (g_ascii_isspace(value[n])) {
1701                                                n--;
1702                                        }
1703                                        g_string_append_len(info, value, n + 1);
1704                                }
1705                                g_string_append_c(info, '\n');
1706                                g_free(key);
1707                        }
1708
1709                        break;
1710                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK:
1711                        g_string_append(info, "------------------------\n");
1712                        break;
1713                }
1714
1715                l = l->next;
1716        }
1717
1718        imcb_log(ic, "User %s info:\n%s", who, info->str);
1719        g_string_free(info, TRUE);
1720
1721        return NULL;
1722}
1723
1724static PurpleNotifyUiOps bee_notify_uiops =
1725{
1726        prplcb_notify_message,     /* notify_message */
1727        prplcb_notify_email,       /* notify_email */
1728        NULL,                      /* notify_emails */
1729        NULL,                      /* notify_formatted */
1730        NULL,                      /* notify_searchresults */
1731        NULL,                      /* notify_searchresults_new_rows */
1732        prplcb_notify_userinfo,    /* notify_userinfo */
1733};
1734
1735static void *prplcb_account_request_authorize(PurpleAccount *account, const char *remote_user,
1736                                              const char *id, const char *alias, const char *message, gboolean on_list,
1737                                              PurpleAccountRequestAuthorizationCb authorize_cb,
1738                                              PurpleAccountRequestAuthorizationCb deny_cb, void *user_data)
1739{
1740        struct im_connection *ic = purple_ic_by_pa(account);
1741        char *q;
1742
1743        if (alias) {
1744                q = g_strdup_printf("%s (%s) wants to add you to his/her contact "
1745                                    "list. (%s)", alias, remote_user, message);
1746        } else {
1747                q = g_strdup_printf("%s wants to add you to his/her contact "
1748                                    "list. (%s)", remote_user, message);
1749        }
1750
1751        imcb_ask_with_free(ic, q, user_data, authorize_cb, deny_cb, NULL);
1752        g_free(q);
1753
1754        return NULL;
1755}
1756
1757static PurpleAccountUiOps bee_account_uiops =
1758{
1759        NULL,                              /* notify_added */
1760        NULL,                              /* status_changed */
1761        NULL,                              /* request_add */
1762        prplcb_account_request_authorize,  /* request_authorize */
1763        NULL,                              /* close_account_request */
1764};
1765
1766extern PurpleXferUiOps bee_xfer_uiops;
1767
1768static void purple_ui_init()
1769{
1770        purple_connections_set_ui_ops(&bee_conn_uiops);
1771        purple_blist_set_ui_ops(&bee_blist_uiops);
1772        purple_conversations_set_ui_ops(&bee_conv_uiops);
1773        purple_request_set_ui_ops(&bee_request_uiops);
1774        purple_privacy_set_ui_ops(&bee_privacy_uiops);
1775        purple_roomlist_set_ui_ops(&bee_roomlist_uiops);
1776        purple_notify_set_ui_ops(&bee_notify_uiops);
1777        purple_accounts_set_ui_ops(&bee_account_uiops);
1778        purple_xfers_set_ui_ops(&bee_xfer_uiops);
1779
1780        if (getenv("BITLBEE_DEBUG")) {
1781                purple_debug_set_ui_ops(&bee_debug_uiops);
1782        }
1783}
1784
1785/* borrowing this semi-private function
1786 * TODO: figure out a better interface later (famous last words) */
1787gboolean plugin_info_add(struct plugin_info *info);
1788
1789void purple_initmodule()
1790{
1791        struct prpl funcs;
1792        GList *prots;
1793        GString *help;
1794        char *dir;
1795
1796        if (purple_get_core() != NULL) {
1797                log_message(LOGLVL_ERROR, "libpurple already initialized. "
1798                            "Please use inetd or ForkDaemon mode instead.");
1799                return;
1800        }
1801
1802        g_return_if_fail((int) B_EV_IO_READ == (int) PURPLE_INPUT_READ);
1803        g_return_if_fail((int) B_EV_IO_WRITE == (int) PURPLE_INPUT_WRITE);
1804
1805        dir = g_strdup_printf("%s/purple", global.conf->configdir);
1806        purple_util_set_user_dir(dir);
1807        g_free(dir);
1808
1809        dir = g_strdup_printf("%s/purple", global.conf->plugindir);
1810        purple_plugins_add_search_path(dir);
1811        g_free(dir);
1812
1813        purple_debug_set_enabled(FALSE);
1814        purple_core_set_ui_ops(&bee_core_uiops);
1815        purple_eventloop_set_ui_ops(&glib_eventloops);
1816        if (!purple_core_init("BitlBee")) {
1817                /* Initializing the core failed. Terminate. */
1818                fprintf(stderr, "libpurple initialization failed.\n");
1819                abort();
1820        }
1821
1822        if (proxytype != PROXY_NONE) {
1823                PurpleProxyInfo *pi = purple_global_proxy_get_info();
1824                switch (proxytype) {
1825                case PROXY_SOCKS4A:
1826                case PROXY_SOCKS4:
1827                        purple_proxy_info_set_type(pi, PURPLE_PROXY_SOCKS4);
1828                        break;
1829                case PROXY_SOCKS5:
1830                        purple_proxy_info_set_type(pi, PURPLE_PROXY_SOCKS5);
1831                        break;
1832                case PROXY_HTTP:
1833                        purple_proxy_info_set_type(pi, PURPLE_PROXY_HTTP);
1834                        break;
1835                }
1836                purple_proxy_info_set_host(pi, proxyhost);
1837                purple_proxy_info_set_port(pi, proxyport);
1838                purple_proxy_info_set_username(pi, proxyuser);
1839                purple_proxy_info_set_password(pi, proxypass);
1840        }
1841
1842        purple_set_blist(purple_blist_new());
1843
1844        /* No, really. So far there were ui_ops for everything, but now suddenly
1845           one needs to use signals for typing notification stuff. :-( */
1846        purple_signal_connect(purple_conversations_get_handle(), "buddy-typing",
1847                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1848        purple_signal_connect(purple_conversations_get_handle(), "buddy-typed",
1849                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1850        purple_signal_connect(purple_conversations_get_handle(), "buddy-typing-stopped",
1851                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1852
1853        memset(&funcs, 0, sizeof(funcs));
1854        funcs.login = purple_login;
1855        funcs.init = purple_init;
1856        funcs.logout = purple_logout;
1857        funcs.buddy_msg = purple_buddy_msg;
1858        funcs.away_states = purple_away_states;
1859        funcs.set_away = purple_set_away;
1860        funcs.add_buddy = purple_add_buddy;
1861        funcs.remove_buddy = purple_remove_buddy;
1862        funcs.add_permit = purple_add_permit;
1863        funcs.add_deny = purple_add_deny;
1864        funcs.rem_permit = purple_rem_permit;
1865        funcs.rem_deny = purple_rem_deny;
1866        funcs.get_info = purple_get_info;
1867        funcs.keepalive = purple_keepalive;
1868        funcs.send_typing = purple_send_typing;
1869        funcs.handle_cmp = g_strcasecmp;
1870        /* TODO(wilmer): Set these only for protocols that support them? */
1871        funcs.chat_msg = purple_chat_msg;
1872        funcs.chat_with = purple_chat_with;
1873        funcs.chat_invite = purple_chat_invite;
1874        funcs.chat_topic = purple_chat_set_topic;
1875        funcs.chat_kick = purple_chat_kick;
1876        funcs.chat_leave = purple_chat_leave;
1877        funcs.chat_join = purple_chat_join;
1878        funcs.chat_list = purple_chat_list;
1879        funcs.chat_add_settings = purple_chat_add_settings;
1880        funcs.chat_free_settings = purple_chat_free_settings;
1881        funcs.transfer_request = purple_transfer_request;
1882
1883        help = g_string_new("BitlBee libpurple module supports the following IM protocols:\n");
1884
1885        /* Add a protocol entry to BitlBee's structures for every protocol
1886           supported by this libpurple instance. */
1887        for (prots = purple_plugins_get_protocols(); prots; prots = prots->next) {
1888                PurplePlugin *prot = prots->data;
1889                PurplePluginProtocolInfo *pi = prot->info->extra_info;
1890                struct prpl *ret;
1891                struct plugin_info *info;
1892
1893                /* If we already have this one (as a native module), don't
1894                   add a libpurple duplicate. */
1895                if (find_protocol(prot->info->id)) {
1896                        continue;
1897                }
1898
1899                ret = g_memdup(&funcs, sizeof(funcs));
1900                ret->name = ret->data = prot->info->id;
1901                if (strncmp(ret->name, "prpl-", 5) == 0) {
1902                        ret->name += 5;
1903                }
1904
1905                if (pi->options & OPT_PROTO_NO_PASSWORD) {
1906                        ret->options |= PRPL_OPT_NO_PASSWORD;
1907                }
1908
1909                if (pi->options & OPT_PROTO_PASSWORD_OPTIONAL) {
1910                        ret->options |= PRPL_OPT_PASSWORD_OPTIONAL;
1911                }
1912
1913                register_protocol(ret);
1914
1915                g_string_append_printf(help, "\n* %s (%s)", ret->name, prot->info->name);
1916
1917                /* libpurple doesn't define a protocol called OSCAR, but we
1918                   need it to be compatible with normal BitlBee. */
1919                if (g_strcasecmp(prot->info->id, "prpl-aim") == 0) {
1920                        ret = g_memdup(&funcs, sizeof(funcs));
1921                        ret->name = "oscar";
1922                        /* purple_get_account_prpl_id() determines the actual protocol ID (icq/aim) */
1923                        ret->data = NULL;
1924                        register_protocol(ret);
1925                }
1926
1927                info = g_new0(struct plugin_info, 1);
1928                info->abiver = BITLBEE_ABI_VERSION_CODE;
1929                info->name = ret->name;
1930                info->version = prot->info->version;
1931                info->description = prot->info->description;
1932                info->author = prot->info->author;
1933                info->url = prot->info->homepage;
1934
1935                plugin_info_add(info);
1936        }
1937
1938        g_string_append(help, "\n\nFor used protocols, more information about available "
1939                        "settings can be found using \x02help purple <protocol name>\x02 "
1940                        "(create an account using that protocol first!)");
1941
1942        /* Add a simple dynamically-generated help item listing all
1943           the supported protocols. */
1944        help_add_mem(&global.help, "purple", help->str);
1945        g_string_free(help, TRUE);
1946}
Note: See TracBrowser for help on using the repository browser.