source: protocols/purple/purple.c @ 537d9b9

Last change on this file since 537d9b9 was 537d9b9, checked in by dequis <dx@…>, at 2016-11-20T08:40:36Z

Merge master up to commit '9f03c47' into parson

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