source: protocols/purple/purple.c @ b75671d

Last change on this file since b75671d was b75671d, checked in by Wilmer van der Gaast <wilmer@…>, at 2015-06-17T22:47:26Z

Merge remote-tracking branch 'origin/master' into parson

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