source: protocols/purple/purple.c @ ad54149

Last change on this file since ad54149 was 725f942, checked in by jgeboski <jgeboski@…>, at 2016-09-20T03:39:05Z

purple: added room listing support

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