source: protocols/purple/purple.c @ 01d56c0

Last change on this file since 01d56c0 was 01d56c0, checked in by dequis <dx@…>, at 2016-10-08T06:39:05Z

purple: Fix handling of empty, immediate roomlist results

Two issues here:

  1. SIPE called in_progress(FALSE) immediately (which decreases refcount),

before purple_roomlist_get_list() could return (which would normally
increase refcount). The first refcount decrease steals it from the prpl,
and bad things happen.

Added an initialized flag to only do that decrease after it was
increased first. This is similar to how pidgin sets a 'dialog' attribute
after the purple_roomlist_get_list() call, and skips the unref if it's
not set.

  1. The code assumed that NULL return value means room listing not

supported. That's not quite true, so now it checks in the prpl info to
see if roomlist_get_list is defined.

Also, made purple_roomlist_data more private.

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