source: protocols/purple/purple.c @ 30093fa

Last change on this file since 30093fa was fecdd71, checked in by dequis <dx@…>, at 2016-11-04T23:48:52Z

purple: hack to pass server parameter to jabber's input request

Because our purple module is all about hacks, adding more can't hurt.

There's a string comparison for "Enter a Conference Server". It's
gettexted in the source but in practice it isn't affected by locale
(bitlbee disables localization).

Worst case, if this stops working, it will open an input request like it
did before this commit. It also does that in purple's jabber if you
don't provide a server parameter.

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