source: protocols/purple/purple.c @ 12f041d

Last change on this file since 12f041d was 12f041d, checked in by dequis <dx@…>, at 2015-10-21T13:14:17Z

socks4a proxy support (like socks4 with remote DNS)

Fixes trac ticket 995 https://bugs.bitlbee.org/bitlbee/ticket/995

This is slightly pointless for the suggested use case (tor), since with
socks5 we already send a hostname instead of an IP address.

Either way, it was easy to implement, so I hope it helps.

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