source: protocols/purple/purple.c @ 985d66d

Last change on this file since 985d66d was 524e931, checked in by dx <dx@…>, at 2016-09-24T14:53:52Z

purple: support setting chat room topics (#84)

  • Property mode set to 100644
File size: 48.9 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_set_topic(struct groupchat *gc, char *topic)
687{
688        PurpleConversation *pc = gc->data;
689        PurpleConvChat *pcc = PURPLE_CONV_CHAT(pc);
690        struct purple_data *pd = gc->ic->proto_data;
691        PurplePlugin *prpl = purple_plugins_find_with_id(pd->account->protocol_id);
692        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
693
694        if (pi->set_chat_topic) {
695                pi->set_chat_topic(purple_account_get_connection(pd->account),
696                                   purple_conv_chat_get_id(pcc),
697                                   topic);
698        }
699}
700
701void purple_chat_kick(struct groupchat *gc, char *who, const char *message)
702{
703        PurpleConversation *pc = gc->data;
704        char *str = g_strdup_printf("kick %s %s", who, message);
705
706        purple_conversation_do_command(pc, str, NULL, NULL);
707        g_free(str);
708}
709
710void purple_chat_leave(struct groupchat *gc)
711{
712        PurpleConversation *pc = gc->data;
713
714        purple_conversation_destroy(pc);
715}
716
717struct groupchat *purple_chat_join(struct im_connection *ic, const char *room, const char *nick, const char *password,
718                                   set_t **sets)
719{
720        struct purple_data *pd = ic->proto_data;
721        PurplePlugin *prpl = purple_plugins_find_with_id(pd->account->protocol_id);
722        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
723        GHashTable *chat_hash;
724        PurpleConversation *conv;
725        GList *info, *l;
726
727        if (!pi->chat_info || !pi->chat_info_defaults ||
728            !(info = pi->chat_info(purple_account_get_connection(pd->account)))) {
729                imcb_error(ic, "Joining chatrooms not supported by this protocol");
730                return NULL;
731        }
732
733        if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
734                                                          room, pd->account))) {
735                purple_conversation_destroy(conv);
736        }
737
738        chat_hash = pi->chat_info_defaults(
739                purple_account_get_connection(pd->account), room
740        );
741
742        for (l = info; l; l = l->next) {
743                struct proto_chat_entry *pce = l->data;
744
745                if (strcmp(pce->identifier, "handle") == 0) {
746                        g_hash_table_replace(chat_hash, "handle", g_strdup(nick));
747                } else if (strcmp(pce->identifier, "password") == 0) {
748                        g_hash_table_replace(chat_hash, "password", g_strdup(password));
749                } else if (strcmp(pce->identifier, "passwd") == 0) {
750                        g_hash_table_replace(chat_hash, "passwd", g_strdup(password));
751                }
752
753                g_free(pce);
754        }
755
756        g_list_free(info);
757
758        serv_join_chat(purple_account_get_connection(pd->account), chat_hash);
759
760        g_hash_table_destroy(chat_hash);
761
762        return imcb_chat_new(ic, room);
763}
764
765void purple_chat_list(struct im_connection *ic, const char *server)
766{
767        PurpleRoomlist *list;
768        struct purple_data *pd = ic->proto_data;
769
770        list = purple_roomlist_get_list(pd->account->gc);
771
772        if (list) {
773                purple_roomlist_ref(list);
774        } else {
775                imcb_log(ic, "Room listing unsupported by this purple plugin");
776        }
777}
778
779void purple_transfer_request(struct im_connection *ic, file_transfer_t *ft, char *handle);
780
781static void purple_ui_init();
782
783GHashTable *prplcb_ui_info()
784{
785        static GHashTable *ret;
786
787        if (ret == NULL) {
788                ret = g_hash_table_new(g_str_hash, g_str_equal);
789                g_hash_table_insert(ret, "name", "BitlBee");
790                g_hash_table_insert(ret, "version", BITLBEE_VERSION);
791        }
792
793        return ret;
794}
795
796static PurpleCoreUiOps bee_core_uiops =
797{
798        NULL,                      /* ui_prefs_init */
799        NULL,                      /* debug_ui_init */
800        purple_ui_init,            /* ui_init */
801        NULL,                      /* quit */
802        prplcb_ui_info,            /* get_ui_info */
803};
804
805static void prplcb_conn_progress(PurpleConnection *gc, const char *text, size_t step, size_t step_count)
806{
807        struct im_connection *ic = purple_ic_by_gc(gc);
808
809        imcb_log(ic, "%s", text);
810}
811
812static void prplcb_conn_connected(PurpleConnection *gc)
813{
814        struct im_connection *ic = purple_ic_by_gc(gc);
815        const char *dn;
816        set_t *s;
817
818        imcb_connected(ic);
819
820        if ((dn = purple_connection_get_display_name(gc)) &&
821            (s = set_find(&ic->acc->set, "display_name"))) {
822                g_free(s->value);
823                s->value = g_strdup(dn);
824        }
825
826        // user list needs to be requested for Gadu-Gadu
827        purple_gg_buddylist_import(gc);
828
829        ic->flags |= OPT_DOES_HTML;
830}
831
832static void prplcb_conn_disconnected(PurpleConnection *gc)
833{
834        struct im_connection *ic = purple_ic_by_gc(gc);
835
836        if (ic != NULL) {
837                imc_logout(ic, !gc->wants_to_die);
838        }
839}
840
841static void prplcb_conn_notice(PurpleConnection *gc, const char *text)
842{
843        struct im_connection *ic = purple_ic_by_gc(gc);
844
845        if (ic != NULL) {
846                imcb_log(ic, "%s", text);
847        }
848}
849
850static void prplcb_conn_report_disconnect_reason(PurpleConnection *gc, PurpleConnectionError reason, const char *text)
851{
852        struct im_connection *ic = purple_ic_by_gc(gc);
853
854        /* PURPLE_CONNECTION_ERROR_NAME_IN_USE means concurrent login,
855           should probably handle that. */
856        if (ic != NULL) {
857                imcb_error(ic, "%s", text);
858        }
859}
860
861static PurpleConnectionUiOps bee_conn_uiops =
862{
863        prplcb_conn_progress,                    /* connect_progress */
864        prplcb_conn_connected,                   /* connected */
865        prplcb_conn_disconnected,                /* disconnected */
866        prplcb_conn_notice,                      /* notice */
867        NULL,                                    /* report_disconnect */
868        NULL,                                    /* network_connected */
869        NULL,                                    /* network_disconnected */
870        prplcb_conn_report_disconnect_reason,    /* report_disconnect_reason */
871};
872
873static void prplcb_blist_update(PurpleBuddyList *list, PurpleBlistNode *node)
874{
875        if (node->type == PURPLE_BLIST_BUDDY_NODE) {
876                PurpleBuddy *bud = (PurpleBuddy *) node;
877                PurpleGroup *group = purple_buddy_get_group(bud);
878                struct im_connection *ic = purple_ic_by_pa(bud->account);
879                PurpleStatus *as;
880                int flags = 0;
881
882                if (ic == NULL) {
883                        return;
884                }
885
886                if (bud->server_alias) {
887                        imcb_rename_buddy(ic, bud->name, bud->server_alias);
888                } else if (bud->alias) {
889                        imcb_rename_buddy(ic, bud->name, bud->alias);
890                }
891
892                if (group) {
893                        imcb_add_buddy(ic, bud->name, purple_group_get_name(group));
894                }
895
896                flags |= purple_presence_is_online(bud->presence) ? OPT_LOGGED_IN : 0;
897                flags |= purple_presence_is_available(bud->presence) ? 0 : OPT_AWAY;
898
899                as = purple_presence_get_active_status(bud->presence);
900
901                imcb_buddy_status(ic, bud->name, flags, purple_status_get_name(as),
902                                  purple_status_get_attr_string(as, "message"));
903
904                imcb_buddy_times(ic, bud->name,
905                                 purple_presence_get_login_time(bud->presence),
906                                 purple_presence_get_idle_time(bud->presence));
907        }
908}
909
910static void prplcb_blist_new(PurpleBlistNode *node)
911{
912        if (node->type == PURPLE_BLIST_BUDDY_NODE) {
913                PurpleBuddy *bud = (PurpleBuddy *) node;
914                struct im_connection *ic = purple_ic_by_pa(bud->account);
915
916                if (ic == NULL) {
917                        return;
918                }
919
920                imcb_add_buddy(ic, bud->name, NULL);
921
922                prplcb_blist_update(NULL, node);
923        }
924}
925
926static void prplcb_blist_remove(PurpleBuddyList *list, PurpleBlistNode *node)
927{
928/*
929        PurpleBuddy *bud = (PurpleBuddy*) node;
930
931        if( node->type == PURPLE_BLIST_BUDDY_NODE )
932        {
933                struct im_connection *ic = purple_ic_by_pa( bud->account );
934
935                if( ic == NULL )
936                        return;
937
938                imcb_remove_buddy( ic, bud->name, NULL );
939        }
940*/
941}
942
943static PurpleBlistUiOps bee_blist_uiops =
944{
945        NULL,                      /* new_list */
946        prplcb_blist_new,          /* new_node */
947        NULL,                      /* show */
948        prplcb_blist_update,       /* update */
949        prplcb_blist_remove,       /* remove */
950};
951
952void prplcb_conv_new(PurpleConversation *conv)
953{
954        if (conv->type == PURPLE_CONV_TYPE_CHAT) {
955                struct im_connection *ic = purple_ic_by_pa(conv->account);
956                struct groupchat *gc;
957
958                gc = bee_chat_by_title(ic->bee, ic, conv->name);
959
960                if (!gc) {
961                        gc = imcb_chat_new(ic, conv->name);
962                        if (conv->title != NULL) {
963                                imcb_chat_name_hint(gc, conv->title);
964                        }
965                }
966
967                /* don't set the topic if it's just the name */
968                if (conv->title != NULL && strcmp(conv->name, conv->title) != 0) {
969                        imcb_chat_topic(gc, NULL, conv->title, 0);
970                }
971
972                conv->ui_data = gc;
973                gc->data = conv;
974
975                /* libpurple brokenness: Whatever. Show that we join right away,
976                   there's no clear "This is you!" signaling in _add_users so
977                   don't even try. */
978                imcb_chat_add_buddy(gc, gc->ic->acc->user);
979        }
980}
981
982void prplcb_conv_free(PurpleConversation *conv)
983{
984        struct groupchat *gc = conv->ui_data;
985
986        imcb_chat_free(gc);
987}
988
989void prplcb_conv_add_users(PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals)
990{
991        struct groupchat *gc = conv->ui_data;
992        GList *b;
993
994        for (b = cbuddies; b; b = b->next) {
995                PurpleConvChatBuddy *pcb = b->data;
996
997                imcb_chat_add_buddy(gc, pcb->name);
998        }
999}
1000
1001void prplcb_conv_del_users(PurpleConversation *conv, GList *cbuddies)
1002{
1003        struct groupchat *gc = conv->ui_data;
1004        GList *b;
1005
1006        for (b = cbuddies; b; b = b->next) {
1007                imcb_chat_remove_buddy(gc, b->data, "");
1008        }
1009}
1010
1011/* Generic handler for IM or chat messages, covers write_chat, write_im and write_conv */
1012static void handle_conv_msg(PurpleConversation *conv, const char *who, const char *message_, guint32 bee_flags, time_t mtime)
1013{
1014        struct im_connection *ic = purple_ic_by_pa(conv->account);
1015        struct groupchat *gc = conv->ui_data;
1016        char *message = g_strdup(message_);
1017        PurpleBuddy *buddy;
1018
1019        buddy = purple_find_buddy(conv->account, who);
1020        if (buddy != NULL) {
1021                who = purple_buddy_get_name(buddy);
1022        }
1023
1024        if (conv->type == PURPLE_CONV_TYPE_IM) {
1025                imcb_buddy_msg(ic, who, message, bee_flags, mtime);
1026        } else if (gc) {
1027                imcb_chat_msg(gc, who, message, bee_flags, mtime);
1028        }
1029
1030        g_free(message);
1031}
1032
1033/* Handles write_im and write_chat. Removes echoes of locally sent messages */
1034static void prplcb_conv_msg(PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime)
1035{
1036        if (!(flags & PURPLE_MESSAGE_SEND)) {
1037                handle_conv_msg(conv, who, message, 0, mtime);
1038        }
1039}
1040
1041/* Handles write_conv. Only passes self messages from other locations through.
1042 * That is, only writes of PURPLE_MESSAGE_SEND.
1043 * There are more events which might be handled in the future, but some are tricky.
1044 * (images look like <img id="123">, what do i do with that?) */
1045static void prplcb_conv_write(PurpleConversation *conv, const char *who, const char *alias, const char *message,
1046                              PurpleMessageFlags flags, time_t mtime)
1047{
1048        if (flags & PURPLE_MESSAGE_SEND) {
1049                handle_conv_msg(conv, who, message, OPT_SELFMESSAGE, mtime);
1050        }
1051}
1052
1053/* No, this is not a ui_op but a signal. */
1054static void prplcb_buddy_typing(PurpleAccount *account, const char *who, gpointer null)
1055{
1056        PurpleConversation *conv;
1057        PurpleConvIm *im;
1058        int state;
1059
1060        if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, account)) == NULL) {
1061                return;
1062        }
1063
1064        im = PURPLE_CONV_IM(conv);
1065        switch (purple_conv_im_get_typing_state(im)) {
1066        case PURPLE_TYPING:
1067                state = OPT_TYPING;
1068                break;
1069        case PURPLE_TYPED:
1070                state = OPT_THINKING;
1071                break;
1072        default:
1073                state = 0;
1074        }
1075
1076        imcb_buddy_typing(purple_ic_by_pa(account), who, state);
1077}
1078
1079static PurpleConversationUiOps bee_conv_uiops =
1080{
1081        prplcb_conv_new,           /* create_conversation  */
1082        prplcb_conv_free,          /* destroy_conversation */
1083        prplcb_conv_msg,           /* write_chat           */
1084        prplcb_conv_msg,           /* write_im             */
1085        prplcb_conv_write,         /* write_conv           */
1086        prplcb_conv_add_users,     /* chat_add_users       */
1087        NULL,                      /* chat_rename_user     */
1088        prplcb_conv_del_users,     /* chat_remove_users    */
1089        NULL,                      /* chat_update_user     */
1090        NULL,                      /* present              */
1091        NULL,                      /* has_focus            */
1092        NULL,                      /* custom_smiley_add    */
1093        NULL,                      /* custom_smiley_write  */
1094        NULL,                      /* custom_smiley_close  */
1095        NULL,                      /* send_confirm         */
1096};
1097
1098struct prplcb_request_action_data {
1099        void *user_data, *bee_data;
1100        PurpleRequestActionCb yes, no;
1101        int yes_i, no_i;
1102};
1103
1104static void prplcb_request_action_yes(void *data)
1105{
1106        struct prplcb_request_action_data *pqad = data;
1107
1108        if (pqad->yes) {
1109                pqad->yes(pqad->user_data, pqad->yes_i);
1110        }
1111}
1112
1113static void prplcb_request_action_no(void *data)
1114{
1115        struct prplcb_request_action_data *pqad = data;
1116
1117        if (pqad->no) {
1118                pqad->no(pqad->user_data, pqad->no_i);
1119        }
1120}
1121
1122/* q->free() callback from query_del()*/
1123static void prplcb_request_action_free(void *data)
1124{
1125        struct prplcb_request_action_data *pqad = data;
1126
1127        pqad->bee_data = NULL;
1128        purple_request_close(PURPLE_REQUEST_ACTION, pqad);
1129}
1130
1131static void *prplcb_request_action(const char *title, const char *primary, const char *secondary,
1132                                   int default_action, PurpleAccount *account, const char *who,
1133                                   PurpleConversation *conv, void *user_data, size_t action_count,
1134                                   va_list actions)
1135{
1136        struct prplcb_request_action_data *pqad;
1137        int i;
1138        char *q;
1139
1140        pqad = g_new0(struct prplcb_request_action_data, 1);
1141
1142        for (i = 0; i < action_count; i++) {
1143                char *caption;
1144                void *fn;
1145
1146                caption = va_arg(actions, char*);
1147                fn = va_arg(actions, void*);
1148
1149                if (strstr(caption, "Accept") || strstr(caption, "OK")) {
1150                        pqad->yes = fn;
1151                        pqad->yes_i = i;
1152                } else if (strstr(caption, "Reject") || strstr(caption, "Cancel")) {
1153                        pqad->no = fn;
1154                        pqad->no_i = i;
1155                }
1156        }
1157
1158        pqad->user_data = user_data;
1159
1160        /* TODO: IRC stuff here :-( */
1161        q = g_strdup_printf("Request: %s\n\n%s\n\n%s", title, primary, secondary);
1162        pqad->bee_data = query_add(local_bee->ui_data, purple_ic_by_pa(account), q,
1163                                   prplcb_request_action_yes, prplcb_request_action_no,
1164                                   prplcb_request_action_free, pqad);
1165
1166        g_free(q);
1167
1168        return pqad;
1169}
1170
1171/* So it turns out some requests have no account context at all, because
1172 * libpurple hates us. This means that query_del_by_conn() won't remove those
1173 * on logout, and will segfault if the user replies. That's why this exists.
1174 */
1175static void prplcb_close_request(PurpleRequestType type, void *data)
1176{
1177        struct prplcb_request_action_data *pqad;
1178        struct request_input_data *ri;
1179        struct purple_data *pd;
1180
1181        if (!data) {
1182                return;
1183        }
1184
1185        switch (type) {
1186        case PURPLE_REQUEST_ACTION:
1187                pqad = data;
1188                /* if this is null, it's because query_del was run already */
1189                if (pqad->bee_data) {
1190                        query_del(local_bee->ui_data, pqad->bee_data);
1191                }
1192                g_free(pqad);
1193                break;
1194        case PURPLE_REQUEST_INPUT:
1195                ri = data;
1196                pd = ri->ic->proto_data;
1197                imcb_remove_buddy(ri->ic, ri->buddy, NULL);
1198                g_free(ri->buddy);
1199                g_hash_table_remove(pd->input_requests, GUINT_TO_POINTER(ri->id));
1200                break;
1201        default:
1202                g_free(data);
1203                break;
1204        }
1205
1206}
1207
1208void* prplcb_request_input(const char *title, const char *primary,
1209        const char *secondary, const char *default_value, gboolean multiline,
1210        gboolean masked, gchar *hint, const char *ok_text, GCallback ok_cb,
1211        const char *cancel_text, GCallback cancel_cb, PurpleAccount *account,
1212        const char *who, PurpleConversation *conv, void *user_data)
1213{
1214        struct im_connection *ic = purple_ic_by_pa(account);
1215        struct purple_data *pd = ic->proto_data;
1216        struct request_input_data *ri = g_new0(struct request_input_data, 1);
1217        guint id = pd->next_request_id++;
1218
1219        ri->id = id;
1220        ri->ic = ic;
1221        ri->buddy = g_strdup_printf("%s_%u", PURPLE_REQUEST_HANDLE, id);
1222        ri->data_callback = (ri_callback_t) ok_cb;
1223        ri->user_data = user_data;
1224        g_hash_table_insert(pd->input_requests, GUINT_TO_POINTER(id), ri);
1225
1226        imcb_add_buddy(ic, ri->buddy, NULL);
1227
1228        if (title && *title) {
1229                imcb_buddy_msg(ic, ri->buddy, title, 0, 0);
1230        }
1231
1232        if (primary && *primary) {
1233                imcb_buddy_msg(ic, ri->buddy, primary, 0, 0);
1234        }
1235
1236        if (secondary && *secondary) {
1237                imcb_buddy_msg(ic, ri->buddy, secondary, 0, 0);
1238        }
1239
1240        return ri;
1241}
1242
1243void purple_request_input_callback(guint id, struct im_connection *ic,
1244                                   const char *message, const char *who)
1245{
1246        struct purple_data *pd = ic->proto_data;
1247        struct request_input_data *ri;
1248
1249        if (!(ri = g_hash_table_lookup(pd->input_requests, GUINT_TO_POINTER(id)))) {
1250                return;
1251        }
1252
1253        ri->data_callback(ri->user_data, message);
1254
1255        purple_request_close(PURPLE_REQUEST_INPUT, ri);
1256}
1257
1258
1259static PurpleRequestUiOps bee_request_uiops =
1260{
1261        prplcb_request_input,      /* request_input */
1262        NULL,                      /* request_choice */
1263        prplcb_request_action,     /* request_action */
1264        NULL,                      /* request_fields */
1265        NULL,                      /* request_file */
1266        prplcb_close_request,      /* close_request */
1267        NULL,                      /* request_folder */
1268};
1269
1270static void prplcb_privacy_permit_added(PurpleAccount *account, const char *name)
1271{
1272        struct im_connection *ic = purple_ic_by_pa(account);
1273
1274        if (!g_slist_find_custom(ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp)) {
1275                ic->permit = g_slist_prepend(ic->permit, g_strdup(name));
1276        }
1277}
1278
1279static void prplcb_privacy_permit_removed(PurpleAccount *account, const char *name)
1280{
1281        struct im_connection *ic = purple_ic_by_pa(account);
1282        void *n;
1283
1284        n = g_slist_find_custom(ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp);
1285        ic->permit = g_slist_remove(ic->permit, n);
1286}
1287
1288static void prplcb_privacy_deny_added(PurpleAccount *account, const char *name)
1289{
1290        struct im_connection *ic = purple_ic_by_pa(account);
1291
1292        if (!g_slist_find_custom(ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp)) {
1293                ic->deny = g_slist_prepend(ic->deny, g_strdup(name));
1294        }
1295}
1296
1297static void prplcb_privacy_deny_removed(PurpleAccount *account, const char *name)
1298{
1299        struct im_connection *ic = purple_ic_by_pa(account);
1300        void *n;
1301
1302        n = g_slist_find_custom(ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp);
1303        ic->deny = g_slist_remove(ic->deny, n);
1304}
1305
1306static PurplePrivacyUiOps bee_privacy_uiops =
1307{
1308        prplcb_privacy_permit_added,       /* permit_added */
1309        prplcb_privacy_permit_removed,     /* permit_removed */
1310        prplcb_privacy_deny_added,         /* deny_added */
1311        prplcb_privacy_deny_removed,       /* deny_removed */
1312};
1313
1314static void prplcb_roomlist_create(PurpleRoomlist *list)
1315{
1316        struct purple_roomlist_data *rld;
1317
1318        list->ui_data = rld = g_new0(struct purple_roomlist_data, 1);
1319        rld->topic = -1;
1320}
1321
1322static void prplcb_roomlist_set_fields(PurpleRoomlist *list, GList *fields)
1323{
1324        gint topic = -1;
1325        GList *l;
1326        guint i;
1327        PurpleRoomlistField *field;
1328        struct purple_roomlist_data *rld = list->ui_data;
1329
1330        for (i = 0, l = fields; l; i++, l = l->next) {
1331                field = l->data;
1332
1333                /* Use the first visible string field as a fallback topic */
1334                if (i != 0 && topic < 0 && !field->hidden &&
1335                    field->type == PURPLE_ROOMLIST_FIELD_STRING) {
1336                        topic = i;
1337                }
1338
1339                if ((g_strcasecmp(field->name, "description") == 0) ||
1340                    (g_strcasecmp(field->name, "topic") == 0)) {
1341                        if (field->type == PURPLE_ROOMLIST_FIELD_STRING) {
1342                                rld->topic = i;
1343                        }
1344                }
1345        }
1346
1347        if (rld->topic < 0) {
1348                rld->topic = topic;
1349        }
1350}
1351
1352static void prplcb_roomlist_add_room(PurpleRoomlist *list, PurpleRoomlistRoom *room)
1353{
1354        bee_chat_info_t *ci;
1355        const char *title;
1356        const char *topic;
1357        GList *fields;
1358        struct purple_roomlist_data *rld = list->ui_data;
1359
1360        fields = purple_roomlist_room_get_fields(room);
1361        title = purple_roomlist_room_get_name(room);
1362
1363        if (rld->topic >= 0) {
1364                topic = g_list_nth_data(fields, rld->topic);
1365        } else {
1366                topic = NULL;
1367        }
1368
1369        ci = g_new(bee_chat_info_t, 1);
1370        ci->title = g_strdup(title);
1371        ci->topic = g_strdup(topic);
1372        rld->chats = g_slist_prepend(rld->chats, ci);
1373}
1374
1375static void prplcb_roomlist_in_progress(PurpleRoomlist *list, gboolean in_progress)
1376{
1377        struct im_connection *ic;
1378        struct purple_roomlist_data *rld = list->ui_data;
1379
1380        if (in_progress) {
1381                return;
1382        }
1383
1384        ic = purple_ic_by_pa(list->account);
1385        purple_chatlist_free(ic);
1386
1387        ic->chatlist = g_slist_reverse(rld->chats);
1388        rld->chats = NULL;
1389
1390        bee_chat_list_finish(ic);
1391        purple_roomlist_unref(list);
1392}
1393
1394static void prplcb_roomlist_destroy(PurpleRoomlist *list)
1395{
1396        g_free(list->ui_data);
1397        list->ui_data = NULL;
1398}
1399
1400static PurpleRoomlistUiOps bee_roomlist_uiops =
1401{
1402        NULL,                         /* show_with_account */
1403        prplcb_roomlist_create,       /* create */
1404        prplcb_roomlist_set_fields,   /* set_fields */
1405        prplcb_roomlist_add_room,     /* add_room */
1406        prplcb_roomlist_in_progress,  /* in_progress */
1407        prplcb_roomlist_destroy,      /* destroy */
1408};
1409
1410static void prplcb_debug_print(PurpleDebugLevel level, const char *category, const char *arg_s)
1411{
1412        fprintf(stderr, "DEBUG %s: %s", category, arg_s);
1413}
1414
1415static PurpleDebugUiOps bee_debug_uiops =
1416{
1417        prplcb_debug_print,        /* print */
1418};
1419
1420static guint prplcb_ev_timeout_add(guint interval, GSourceFunc func, gpointer udata)
1421{
1422        return b_timeout_add(interval, (b_event_handler) func, udata);
1423}
1424
1425static guint prplcb_ev_input_add(int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata)
1426{
1427        return b_input_add(fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata);
1428}
1429
1430static gboolean prplcb_ev_remove(guint id)
1431{
1432        b_event_remove((gint) id);
1433        return TRUE;
1434}
1435
1436static PurpleEventLoopUiOps glib_eventloops =
1437{
1438        prplcb_ev_timeout_add,     /* timeout_add */
1439        prplcb_ev_remove,          /* timeout_remove */
1440        prplcb_ev_input_add,       /* input_add */
1441        prplcb_ev_remove,          /* input_remove */
1442};
1443
1444/* Absolutely no connection context at all. Thanks purple! brb crying */
1445static void *prplcb_notify_message(PurpleNotifyMsgType type, const char *title,
1446                                   const char *primary, const char *secondary)
1447{
1448        char *text = g_strdup_printf("%s%s - %s%s%s",
1449                (type == PURPLE_NOTIFY_MSG_ERROR) ? "Error: " : "",
1450                title,
1451                primary ?: "",
1452                (primary && secondary) ? " - " : "",
1453                secondary ?: ""
1454        );
1455
1456        if (local_bee->ui->log) {
1457                local_bee->ui->log(local_bee, "purple", text);
1458        }
1459
1460        g_free(text);
1461
1462        return NULL;
1463}
1464
1465static void *prplcb_notify_email(PurpleConnection *gc, const char *subject, const char *from,
1466                                 const char *to, const char *url)
1467{
1468        struct im_connection *ic = purple_ic_by_gc(gc);
1469
1470        imcb_notify_email(ic, "Received e-mail from %s for %s: %s <%s>", from, to, subject, url);
1471
1472        return NULL;
1473}
1474
1475static void *prplcb_notify_userinfo(PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info)
1476{
1477        struct im_connection *ic = purple_ic_by_gc(gc);
1478        GString *info = g_string_new("");
1479        GList *l = purple_notify_user_info_get_entries(user_info);
1480        char *key;
1481        const char *value;
1482        int n;
1483
1484        while (l) {
1485                PurpleNotifyUserInfoEntry *e = l->data;
1486
1487                switch (purple_notify_user_info_entry_get_type(e)) {
1488                case PURPLE_NOTIFY_USER_INFO_ENTRY_PAIR:
1489                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_HEADER:
1490                        key = g_strdup(purple_notify_user_info_entry_get_label(e));
1491                        value = purple_notify_user_info_entry_get_value(e);
1492
1493                        if (key) {
1494                                strip_html(key);
1495                                g_string_append_printf(info, "%s: ", key);
1496
1497                                if (value) {
1498                                        n = strlen(value) - 1;
1499                                        while (g_ascii_isspace(value[n])) {
1500                                                n--;
1501                                        }
1502                                        g_string_append_len(info, value, n + 1);
1503                                }
1504                                g_string_append_c(info, '\n');
1505                                g_free(key);
1506                        }
1507
1508                        break;
1509                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK:
1510                        g_string_append(info, "------------------------\n");
1511                        break;
1512                }
1513
1514                l = l->next;
1515        }
1516
1517        imcb_log(ic, "User %s info:\n%s", who, info->str);
1518        g_string_free(info, TRUE);
1519
1520        return NULL;
1521}
1522
1523static PurpleNotifyUiOps bee_notify_uiops =
1524{
1525        prplcb_notify_message,     /* notify_message */
1526        prplcb_notify_email,       /* notify_email */
1527        NULL,                      /* notify_emails */
1528        NULL,                      /* notify_formatted */
1529        NULL,                      /* notify_searchresults */
1530        NULL,                      /* notify_searchresults_new_rows */
1531        prplcb_notify_userinfo,    /* notify_userinfo */
1532};
1533
1534static void *prplcb_account_request_authorize(PurpleAccount *account, const char *remote_user,
1535                                              const char *id, const char *alias, const char *message, gboolean on_list,
1536                                              PurpleAccountRequestAuthorizationCb authorize_cb,
1537                                              PurpleAccountRequestAuthorizationCb deny_cb, void *user_data)
1538{
1539        struct im_connection *ic = purple_ic_by_pa(account);
1540        char *q;
1541
1542        if (alias) {
1543                q = g_strdup_printf("%s (%s) wants to add you to his/her contact "
1544                                    "list. (%s)", alias, remote_user, message);
1545        } else {
1546                q = g_strdup_printf("%s wants to add you to his/her contact "
1547                                    "list. (%s)", remote_user, message);
1548        }
1549
1550        imcb_ask_with_free(ic, q, user_data, authorize_cb, deny_cb, NULL);
1551        g_free(q);
1552
1553        return NULL;
1554}
1555
1556static PurpleAccountUiOps bee_account_uiops =
1557{
1558        NULL,                              /* notify_added */
1559        NULL,                              /* status_changed */
1560        NULL,                              /* request_add */
1561        prplcb_account_request_authorize,  /* request_authorize */
1562        NULL,                              /* close_account_request */
1563};
1564
1565extern PurpleXferUiOps bee_xfer_uiops;
1566
1567static void purple_ui_init()
1568{
1569        purple_connections_set_ui_ops(&bee_conn_uiops);
1570        purple_blist_set_ui_ops(&bee_blist_uiops);
1571        purple_conversations_set_ui_ops(&bee_conv_uiops);
1572        purple_request_set_ui_ops(&bee_request_uiops);
1573        purple_privacy_set_ui_ops(&bee_privacy_uiops);
1574        purple_roomlist_set_ui_ops(&bee_roomlist_uiops);
1575        purple_notify_set_ui_ops(&bee_notify_uiops);
1576        purple_accounts_set_ui_ops(&bee_account_uiops);
1577        purple_xfers_set_ui_ops(&bee_xfer_uiops);
1578
1579        if (getenv("BITLBEE_DEBUG")) {
1580                purple_debug_set_ui_ops(&bee_debug_uiops);
1581        }
1582}
1583
1584void purple_initmodule()
1585{
1586        struct prpl funcs;
1587        GList *prots;
1588        GString *help;
1589        char *dir;
1590
1591        if (purple_get_core() != NULL) {
1592                log_message(LOGLVL_ERROR, "libpurple already initialized. "
1593                            "Please use inetd or ForkDaemon mode instead.");
1594                return;
1595        }
1596
1597        g_assert((int) B_EV_IO_READ == (int) PURPLE_INPUT_READ);
1598        g_assert((int) B_EV_IO_WRITE == (int) PURPLE_INPUT_WRITE);
1599
1600        dir = g_strdup_printf("%s/purple", global.conf->configdir);
1601        purple_util_set_user_dir(dir);
1602        g_free(dir);
1603
1604        dir = g_strdup_printf("%s/purple", global.conf->plugindir);
1605        purple_plugins_add_search_path(dir);
1606        g_free(dir);
1607
1608        purple_debug_set_enabled(FALSE);
1609        purple_core_set_ui_ops(&bee_core_uiops);
1610        purple_eventloop_set_ui_ops(&glib_eventloops);
1611        if (!purple_core_init("BitlBee")) {
1612                /* Initializing the core failed. Terminate. */
1613                fprintf(stderr, "libpurple initialization failed.\n");
1614                abort();
1615        }
1616
1617        if (proxytype != PROXY_NONE) {
1618                PurpleProxyInfo *pi = purple_global_proxy_get_info();
1619                switch (proxytype) {
1620                case PROXY_SOCKS4A:
1621                case PROXY_SOCKS4:
1622                        purple_proxy_info_set_type(pi, PURPLE_PROXY_SOCKS4);
1623                        break;
1624                case PROXY_SOCKS5:
1625                        purple_proxy_info_set_type(pi, PURPLE_PROXY_SOCKS5);
1626                        break;
1627                case PROXY_HTTP:
1628                        purple_proxy_info_set_type(pi, PURPLE_PROXY_HTTP);
1629                        break;
1630                }
1631                purple_proxy_info_set_host(pi, proxyhost);
1632                purple_proxy_info_set_port(pi, proxyport);
1633                purple_proxy_info_set_username(pi, proxyuser);
1634                purple_proxy_info_set_password(pi, proxypass);
1635        }
1636
1637        purple_set_blist(purple_blist_new());
1638
1639        /* No, really. So far there were ui_ops for everything, but now suddenly
1640           one needs to use signals for typing notification stuff. :-( */
1641        purple_signal_connect(purple_conversations_get_handle(), "buddy-typing",
1642                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1643        purple_signal_connect(purple_conversations_get_handle(), "buddy-typed",
1644                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1645        purple_signal_connect(purple_conversations_get_handle(), "buddy-typing-stopped",
1646                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1647
1648        memset(&funcs, 0, sizeof(funcs));
1649        funcs.login = purple_login;
1650        funcs.init = purple_init;
1651        funcs.logout = purple_logout;
1652        funcs.buddy_msg = purple_buddy_msg;
1653        funcs.away_states = purple_away_states;
1654        funcs.set_away = purple_set_away;
1655        funcs.add_buddy = purple_add_buddy;
1656        funcs.remove_buddy = purple_remove_buddy;
1657        funcs.add_permit = purple_add_permit;
1658        funcs.add_deny = purple_add_deny;
1659        funcs.rem_permit = purple_rem_permit;
1660        funcs.rem_deny = purple_rem_deny;
1661        funcs.get_info = purple_get_info;
1662        funcs.keepalive = purple_keepalive;
1663        funcs.send_typing = purple_send_typing;
1664        funcs.handle_cmp = g_strcasecmp;
1665        /* TODO(wilmer): Set these only for protocols that support them? */
1666        funcs.chat_msg = purple_chat_msg;
1667        funcs.chat_with = purple_chat_with;
1668        funcs.chat_invite = purple_chat_invite;
1669        funcs.chat_topic = purple_chat_set_topic;
1670        funcs.chat_kick = purple_chat_kick;
1671        funcs.chat_leave = purple_chat_leave;
1672        funcs.chat_join = purple_chat_join;
1673        funcs.chat_list = purple_chat_list;
1674        funcs.transfer_request = purple_transfer_request;
1675
1676        help = g_string_new("BitlBee libpurple module supports the following IM protocols:\n");
1677
1678        /* Add a protocol entry to BitlBee's structures for every protocol
1679           supported by this libpurple instance. */
1680        for (prots = purple_plugins_get_protocols(); prots; prots = prots->next) {
1681                PurplePlugin *prot = prots->data;
1682                struct prpl *ret;
1683
1684                /* If we already have this one (as a native module), don't
1685                   add a libpurple duplicate. */
1686                if (find_protocol(prot->info->id)) {
1687                        continue;
1688                }
1689
1690                ret = g_memdup(&funcs, sizeof(funcs));
1691                ret->name = ret->data = prot->info->id;
1692                if (strncmp(ret->name, "prpl-", 5) == 0) {
1693                        ret->name += 5;
1694                }
1695                register_protocol(ret);
1696
1697                g_string_append_printf(help, "\n* %s (%s)", ret->name, prot->info->name);
1698
1699                /* libpurple doesn't define a protocol called OSCAR, but we
1700                   need it to be compatible with normal BitlBee. */
1701                if (g_strcasecmp(prot->info->id, "prpl-aim") == 0) {
1702                        ret = g_memdup(&funcs, sizeof(funcs));
1703                        ret->name = "oscar";
1704                        ret->data = prot->info->id;
1705                        register_protocol(ret);
1706                }
1707        }
1708
1709        g_string_append(help, "\n\nFor used protocols, more information about available "
1710                        "settings can be found using \x02help purple <protocol name>\x02 "
1711                        "(create an account using that protocol first!)");
1712
1713        /* Add a simple dynamically-generated help item listing all
1714           the supported protocols. */
1715        help_add_mem(&global.help, "purple", help->str);
1716        g_string_free(help, TRUE);
1717}
Note: See TracBrowser for help on using the repository browser.