source: protocols/purple/purple.c @ 3fbce97

Last change on this file since 3fbce97 was 3fbce97, checked in by Wilmer van der Gaast <wilmer@…>, at 2016-09-24T20:14:34Z

Merge branch 'master' into parson

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