source: protocols/purple/purple.c @ ea90275

Last change on this file since ea90275 was ea90275, checked in by dequis <dx@…>, at 2016-11-13T20:10:17Z

purple: fix file transfer memory management

This means cancelling transfers on logout to avoid crashes, keeping
track of timeouts, reffing and unreffing the xfers, listening to the
callbacks from UI and purple more carefully and using the correct
functions to free the correct things at the correct moments.

Originally intended to fix a crash triggered when the dcc stall timeout
kicks in after the account is offline, which is apparently very frequent
with telegram (it sends file transfers while fetching history, and
randomly disconnects a while later).

Trying to fix that meant opening a can of worms, but after three days of
work on this bug I'm pretty sure I've finished dealing with the
resulting mess and tested all the typical edge cases.

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