source: protocols/purple/purple.c @ 1a8875f

Last change on this file since 1a8875f was 1a8875f, checked in by dequis <dx@…>, at 2016-09-25T03:07:25Z

purple: Fix another issue where /join results in two channels

Follow up to a3019499665384a3dcbbc11016d256e6d4dcd26c (which actually
made things worse than before for this particular case - found it by
bisecting)

This affected skypeweb and hangouts, maybe others.

Sometimes serv_join_chat() results in a call chain like this
(outermost frame first)

  1. purple_chat_join
  2. serv_join_chat
  3. (the join_chat function of the prpl)
  4. serv_got_joined_chat
  5. purple_conversation_new
  6. prplcb_conv_new

The last one tries to find a struct groupchat by name (that's the code
from the referenced commit). If it doesn't exist, it creates a new one.
This usually isn't an issue because the 4th step is done asynchronously,
and a groupchat is created at the end of purple_chat_join. If it's not,
you end up with two groupchats, which can lead to other nasty issues.

This moves the creation of the groupchat right before serv_join_chat().
That way, it always exists even if the conversation is created
immediately.

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