source: protocols/purple/purple.c @ 0483e1e

Last change on this file since 0483e1e was 87872c7, checked in by dequis <dx@…>, at 2016-11-28T05:53:50Z

purple: Use roomlist_room_serialize, fixes joining jabber chats

The room names in 'chat list' were missing the server part.

Jabber is the only prpl which implements this method as far as I can
see, and it's needed to get the full name.

  • Property mode set to 100644
File size: 51.0 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  libpurple module - Main file                                             *
5*                                                                           *
6*  Copyright 2009-2012 Wilmer van der Gaast <wilmer@gaast.net>              *
7*                                                                           *
8*  This program is free software; you can redistribute it and/or modify     *
9*  it under the terms of the GNU General Public License as published by     *
10*  the Free Software Foundation; either version 2 of the License, or        *
11*  (at your option) any later version.                                      *
12*                                                                           *
13*  This program is distributed in the hope that it will be useful,          *
14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
16*  GNU General Public License for more details.                             *
17*                                                                           *
18*  You should have received a copy of the GNU General Public License along  *
19*  with this program; if not, write to the Free Software Foundation, Inc.,  *
20*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
21*                                                                           *
22\***************************************************************************/
23
24#include "bitlbee.h"
25#include "bpurple.h"
26#include "help.h"
27
28#include <stdarg.h>
29
30#include <glib.h>
31#include <purple.h>
32
33GSList *purple_connections;
34
35/* This makes me VERY sad... :-( But some libpurple callbacks come in without
36   any context so this is the only way to get that. Don't want to support
37   libpurple in daemon mode anyway. */
38static bee_t *local_bee;
39
40static char *set_eval_display_name(set_t *set, char *value);
41
42void purple_request_input_callback(guint id, struct im_connection *ic,
43                                   const char *message, const char *who);
44
45void purple_transfer_cancel_all(struct im_connection *ic);
46
47/* purple_request_input specific stuff */
48typedef void (*ri_callback_t)(gpointer, const gchar *);
49
50struct request_input_data {
51        ri_callback_t data_callback;
52        void *user_data;
53        struct im_connection *ic;
54        char *buddy;
55        guint id;
56};
57
58struct purple_roomlist_data {
59        GSList *chats;
60        gint topic;
61        gboolean initialized;
62};
63
64
65struct im_connection *purple_ic_by_pa(PurpleAccount *pa)
66{
67        GSList *i;
68        struct purple_data *pd;
69
70        for (i = purple_connections; i; i = i->next) {
71                pd = ((struct im_connection *) i->data)->proto_data;
72                if (pd->account == pa) {
73                        return i->data;
74                }
75        }
76
77        return NULL;
78}
79
80static struct im_connection *purple_ic_by_gc(PurpleConnection *gc)
81{
82        return purple_ic_by_pa(purple_connection_get_account(gc));
83}
84
85static gboolean purple_menu_cmp(const char *a, const char *b)
86{
87        while (*a && *b) {
88                while (*a == '_') {
89                        a++;
90                }
91                while (*b == '_') {
92                        b++;
93                }
94                if (g_ascii_tolower(*a) != g_ascii_tolower(*b)) {
95                        return FALSE;
96                }
97
98                a++;
99                b++;
100        }
101
102        return (*a == '\0' && *b == '\0');
103}
104
105static char *purple_get_account_prpl_id(account_t *acc)
106{
107        /* "oscar" is how non-purple bitlbee calls it,
108         * and it might be icq or aim, depending on the username */
109        if (g_strcmp0(acc->prpl->name, "oscar") == 0) {
110                return (g_ascii_isdigit(acc->user[0])) ? "prpl-icq" : "prpl-aim";
111        }
112
113        return acc->prpl->data;
114}
115
116static 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 char *prplcb_roomlist_get_room_name(PurpleRoomlist *list, PurpleRoomlistRoom *room)
1390{
1391        struct im_connection *ic = purple_ic_by_pa(list->account);
1392        struct purple_data *pd = ic->proto_data;
1393        PurplePlugin *prpl = purple_plugins_find_with_id(pd->account->protocol_id);
1394        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
1395
1396        if (pi && pi->roomlist_room_serialize) {
1397                return pi->roomlist_room_serialize(room);
1398        } else {
1399                return g_strdup(purple_roomlist_room_get_name(room));
1400        }
1401}
1402
1403static void prplcb_roomlist_add_room(PurpleRoomlist *list, PurpleRoomlistRoom *room)
1404{
1405        bee_chat_info_t *ci;
1406        char *title;
1407        const char *topic;
1408        GList *fields;
1409        struct purple_roomlist_data *rld = list->ui_data;
1410
1411        fields = purple_roomlist_room_get_fields(room);
1412        title = prplcb_roomlist_get_room_name(list, room);
1413
1414        if (rld->topic >= 0) {
1415                topic = g_list_nth_data(fields, rld->topic);
1416        } else {
1417                topic = NULL;
1418        }
1419
1420        ci = g_new(bee_chat_info_t, 1);
1421        ci->title = title;
1422        ci->topic = g_strdup(topic);
1423        rld->chats = g_slist_prepend(rld->chats, ci);
1424}
1425
1426static void prplcb_roomlist_in_progress(PurpleRoomlist *list, gboolean in_progress)
1427{
1428        struct im_connection *ic;
1429        struct purple_data *pd;
1430        struct purple_roomlist_data *rld = list->ui_data;
1431
1432        if (in_progress || !rld) {
1433                return;
1434        }
1435
1436        ic = purple_ic_by_pa(list->account);
1437        imcb_chat_list_free(ic);
1438
1439        pd = ic->proto_data;
1440        g_free(pd->chat_list_server);
1441        pd->chat_list_server = NULL;
1442
1443        ic->chatlist = g_slist_reverse(rld->chats);
1444        rld->chats = NULL;
1445
1446        imcb_chat_list_finish(ic);
1447
1448        if (rld->initialized) {
1449                purple_roomlist_unref(list);
1450        }
1451}
1452
1453static void prplcb_roomlist_destroy(PurpleRoomlist *list)
1454{
1455        g_free(list->ui_data);
1456        list->ui_data = NULL;
1457}
1458
1459static PurpleRoomlistUiOps bee_roomlist_uiops =
1460{
1461        NULL,                         /* show_with_account */
1462        prplcb_roomlist_create,       /* create */
1463        prplcb_roomlist_set_fields,   /* set_fields */
1464        prplcb_roomlist_add_room,     /* add_room */
1465        prplcb_roomlist_in_progress,  /* in_progress */
1466        prplcb_roomlist_destroy,      /* destroy */
1467};
1468
1469static void prplcb_debug_print(PurpleDebugLevel level, const char *category, const char *arg_s)
1470{
1471        fprintf(stderr, "DEBUG %s: %s", category, arg_s);
1472}
1473
1474static PurpleDebugUiOps bee_debug_uiops =
1475{
1476        prplcb_debug_print,        /* print */
1477};
1478
1479static guint prplcb_ev_timeout_add(guint interval, GSourceFunc func, gpointer udata)
1480{
1481        return b_timeout_add(interval, (b_event_handler) func, udata);
1482}
1483
1484static guint prplcb_ev_input_add(int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata)
1485{
1486        return b_input_add(fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata);
1487}
1488
1489static gboolean prplcb_ev_remove(guint id)
1490{
1491        b_event_remove((gint) id);
1492        return TRUE;
1493}
1494
1495static PurpleEventLoopUiOps glib_eventloops =
1496{
1497        prplcb_ev_timeout_add,     /* timeout_add */
1498        prplcb_ev_remove,          /* timeout_remove */
1499        prplcb_ev_input_add,       /* input_add */
1500        prplcb_ev_remove,          /* input_remove */
1501};
1502
1503/* Absolutely no connection context at all. Thanks purple! brb crying */
1504static void *prplcb_notify_message(PurpleNotifyMsgType type, const char *title,
1505                                   const char *primary, const char *secondary)
1506{
1507        char *text = g_strdup_printf("%s%s - %s%s%s",
1508                (type == PURPLE_NOTIFY_MSG_ERROR) ? "Error: " : "",
1509                title,
1510                primary ?: "",
1511                (primary && secondary) ? " - " : "",
1512                secondary ?: ""
1513        );
1514
1515        if (local_bee->ui->log) {
1516                local_bee->ui->log(local_bee, "purple", text);
1517        }
1518
1519        g_free(text);
1520
1521        return NULL;
1522}
1523
1524static void *prplcb_notify_email(PurpleConnection *gc, const char *subject, const char *from,
1525                                 const char *to, const char *url)
1526{
1527        struct im_connection *ic = purple_ic_by_gc(gc);
1528
1529        imcb_notify_email(ic, "Received e-mail from %s for %s: %s <%s>", from, to, subject, url);
1530
1531        return NULL;
1532}
1533
1534static void *prplcb_notify_userinfo(PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info)
1535{
1536        struct im_connection *ic = purple_ic_by_gc(gc);
1537        GString *info = g_string_new("");
1538        GList *l = purple_notify_user_info_get_entries(user_info);
1539        char *key;
1540        const char *value;
1541        int n;
1542
1543        while (l) {
1544                PurpleNotifyUserInfoEntry *e = l->data;
1545
1546                switch (purple_notify_user_info_entry_get_type(e)) {
1547                case PURPLE_NOTIFY_USER_INFO_ENTRY_PAIR:
1548                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_HEADER:
1549                        key = g_strdup(purple_notify_user_info_entry_get_label(e));
1550                        value = purple_notify_user_info_entry_get_value(e);
1551
1552                        if (key) {
1553                                strip_html(key);
1554                                g_string_append_printf(info, "%s: ", key);
1555
1556                                if (value) {
1557                                        n = strlen(value) - 1;
1558                                        while (g_ascii_isspace(value[n])) {
1559                                                n--;
1560                                        }
1561                                        g_string_append_len(info, value, n + 1);
1562                                }
1563                                g_string_append_c(info, '\n');
1564                                g_free(key);
1565                        }
1566
1567                        break;
1568                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK:
1569                        g_string_append(info, "------------------------\n");
1570                        break;
1571                }
1572
1573                l = l->next;
1574        }
1575
1576        imcb_log(ic, "User %s info:\n%s", who, info->str);
1577        g_string_free(info, TRUE);
1578
1579        return NULL;
1580}
1581
1582static PurpleNotifyUiOps bee_notify_uiops =
1583{
1584        prplcb_notify_message,     /* notify_message */
1585        prplcb_notify_email,       /* notify_email */
1586        NULL,                      /* notify_emails */
1587        NULL,                      /* notify_formatted */
1588        NULL,                      /* notify_searchresults */
1589        NULL,                      /* notify_searchresults_new_rows */
1590        prplcb_notify_userinfo,    /* notify_userinfo */
1591};
1592
1593static void *prplcb_account_request_authorize(PurpleAccount *account, const char *remote_user,
1594                                              const char *id, const char *alias, const char *message, gboolean on_list,
1595                                              PurpleAccountRequestAuthorizationCb authorize_cb,
1596                                              PurpleAccountRequestAuthorizationCb deny_cb, void *user_data)
1597{
1598        struct im_connection *ic = purple_ic_by_pa(account);
1599        char *q;
1600
1601        if (alias) {
1602                q = g_strdup_printf("%s (%s) wants to add you to his/her contact "
1603                                    "list. (%s)", alias, remote_user, message);
1604        } else {
1605                q = g_strdup_printf("%s wants to add you to his/her contact "
1606                                    "list. (%s)", remote_user, message);
1607        }
1608
1609        imcb_ask_with_free(ic, q, user_data, authorize_cb, deny_cb, NULL);
1610        g_free(q);
1611
1612        return NULL;
1613}
1614
1615static PurpleAccountUiOps bee_account_uiops =
1616{
1617        NULL,                              /* notify_added */
1618        NULL,                              /* status_changed */
1619        NULL,                              /* request_add */
1620        prplcb_account_request_authorize,  /* request_authorize */
1621        NULL,                              /* close_account_request */
1622};
1623
1624extern PurpleXferUiOps bee_xfer_uiops;
1625
1626static void purple_ui_init()
1627{
1628        purple_connections_set_ui_ops(&bee_conn_uiops);
1629        purple_blist_set_ui_ops(&bee_blist_uiops);
1630        purple_conversations_set_ui_ops(&bee_conv_uiops);
1631        purple_request_set_ui_ops(&bee_request_uiops);
1632        purple_privacy_set_ui_ops(&bee_privacy_uiops);
1633        purple_roomlist_set_ui_ops(&bee_roomlist_uiops);
1634        purple_notify_set_ui_ops(&bee_notify_uiops);
1635        purple_accounts_set_ui_ops(&bee_account_uiops);
1636        purple_xfers_set_ui_ops(&bee_xfer_uiops);
1637
1638        if (getenv("BITLBEE_DEBUG")) {
1639                purple_debug_set_ui_ops(&bee_debug_uiops);
1640        }
1641}
1642
1643void purple_initmodule()
1644{
1645        struct prpl funcs;
1646        GList *prots;
1647        GString *help;
1648        char *dir;
1649
1650        if (purple_get_core() != NULL) {
1651                log_message(LOGLVL_ERROR, "libpurple already initialized. "
1652                            "Please use inetd or ForkDaemon mode instead.");
1653                return;
1654        }
1655
1656        g_assert((int) B_EV_IO_READ == (int) PURPLE_INPUT_READ);
1657        g_assert((int) B_EV_IO_WRITE == (int) PURPLE_INPUT_WRITE);
1658
1659        dir = g_strdup_printf("%s/purple", global.conf->configdir);
1660        purple_util_set_user_dir(dir);
1661        g_free(dir);
1662
1663        dir = g_strdup_printf("%s/purple", global.conf->plugindir);
1664        purple_plugins_add_search_path(dir);
1665        g_free(dir);
1666
1667        purple_debug_set_enabled(FALSE);
1668        purple_core_set_ui_ops(&bee_core_uiops);
1669        purple_eventloop_set_ui_ops(&glib_eventloops);
1670        if (!purple_core_init("BitlBee")) {
1671                /* Initializing the core failed. Terminate. */
1672                fprintf(stderr, "libpurple initialization failed.\n");
1673                abort();
1674        }
1675
1676        if (proxytype != PROXY_NONE) {
1677                PurpleProxyInfo *pi = purple_global_proxy_get_info();
1678                switch (proxytype) {
1679                case PROXY_SOCKS4A:
1680                case PROXY_SOCKS4:
1681                        purple_proxy_info_set_type(pi, PURPLE_PROXY_SOCKS4);
1682                        break;
1683                case PROXY_SOCKS5:
1684                        purple_proxy_info_set_type(pi, PURPLE_PROXY_SOCKS5);
1685                        break;
1686                case PROXY_HTTP:
1687                        purple_proxy_info_set_type(pi, PURPLE_PROXY_HTTP);
1688                        break;
1689                }
1690                purple_proxy_info_set_host(pi, proxyhost);
1691                purple_proxy_info_set_port(pi, proxyport);
1692                purple_proxy_info_set_username(pi, proxyuser);
1693                purple_proxy_info_set_password(pi, proxypass);
1694        }
1695
1696        purple_set_blist(purple_blist_new());
1697
1698        /* No, really. So far there were ui_ops for everything, but now suddenly
1699           one needs to use signals for typing notification stuff. :-( */
1700        purple_signal_connect(purple_conversations_get_handle(), "buddy-typing",
1701                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1702        purple_signal_connect(purple_conversations_get_handle(), "buddy-typed",
1703                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1704        purple_signal_connect(purple_conversations_get_handle(), "buddy-typing-stopped",
1705                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1706
1707        memset(&funcs, 0, sizeof(funcs));
1708        funcs.login = purple_login;
1709        funcs.init = purple_init;
1710        funcs.logout = purple_logout;
1711        funcs.buddy_msg = purple_buddy_msg;
1712        funcs.away_states = purple_away_states;
1713        funcs.set_away = purple_set_away;
1714        funcs.add_buddy = purple_add_buddy;
1715        funcs.remove_buddy = purple_remove_buddy;
1716        funcs.add_permit = purple_add_permit;
1717        funcs.add_deny = purple_add_deny;
1718        funcs.rem_permit = purple_rem_permit;
1719        funcs.rem_deny = purple_rem_deny;
1720        funcs.get_info = purple_get_info;
1721        funcs.keepalive = purple_keepalive;
1722        funcs.send_typing = purple_send_typing;
1723        funcs.handle_cmp = g_strcasecmp;
1724        /* TODO(wilmer): Set these only for protocols that support them? */
1725        funcs.chat_msg = purple_chat_msg;
1726        funcs.chat_with = purple_chat_with;
1727        funcs.chat_invite = purple_chat_invite;
1728        funcs.chat_topic = purple_chat_set_topic;
1729        funcs.chat_kick = purple_chat_kick;
1730        funcs.chat_leave = purple_chat_leave;
1731        funcs.chat_join = purple_chat_join;
1732        funcs.chat_list = purple_chat_list;
1733        funcs.transfer_request = purple_transfer_request;
1734
1735        help = g_string_new("BitlBee libpurple module supports the following IM protocols:\n");
1736
1737        /* Add a protocol entry to BitlBee's structures for every protocol
1738           supported by this libpurple instance. */
1739        for (prots = purple_plugins_get_protocols(); prots; prots = prots->next) {
1740                PurplePlugin *prot = prots->data;
1741                PurplePluginProtocolInfo *pi = prot->info->extra_info;
1742                struct prpl *ret;
1743
1744                /* If we already have this one (as a native module), don't
1745                   add a libpurple duplicate. */
1746                if (find_protocol(prot->info->id)) {
1747                        continue;
1748                }
1749
1750                ret = g_memdup(&funcs, sizeof(funcs));
1751                ret->name = ret->data = prot->info->id;
1752                if (strncmp(ret->name, "prpl-", 5) == 0) {
1753                        ret->name += 5;
1754                }
1755
1756                if (pi->options & OPT_PROTO_NO_PASSWORD) {
1757                        ret->options |= PRPL_OPT_NO_PASSWORD;
1758                }
1759
1760                if (pi->options & OPT_PROTO_PASSWORD_OPTIONAL) {
1761                        ret->options |= PRPL_OPT_PASSWORD_OPTIONAL;
1762                }
1763
1764                register_protocol(ret);
1765
1766                g_string_append_printf(help, "\n* %s (%s)", ret->name, prot->info->name);
1767
1768                /* libpurple doesn't define a protocol called OSCAR, but we
1769                   need it to be compatible with normal BitlBee. */
1770                if (g_strcasecmp(prot->info->id, "prpl-aim") == 0) {
1771                        ret = g_memdup(&funcs, sizeof(funcs));
1772                        ret->name = "oscar";
1773                        /* purple_get_account_prpl_id() determines the actual protocol ID (icq/aim) */
1774                        ret->data = NULL;
1775                        register_protocol(ret);
1776                }
1777        }
1778
1779        g_string_append(help, "\n\nFor used protocols, more information about available "
1780                        "settings can be found using \x02help purple <protocol name>\x02 "
1781                        "(create an account using that protocol first!)");
1782
1783        /* Add a simple dynamically-generated help item listing all
1784           the supported protocols. */
1785        help_add_mem(&global.help, "purple", help->str);
1786        g_string_free(help, TRUE);
1787}
Note: See TracBrowser for help on using the repository browser.