source: protocols/purple/purple.c @ a3019499

Last change on this file since a3019499 was a3019499, checked in by dequis <dx@…>, at 2015-11-26T02:14:34Z

purple: fix /join #channel, which joined a different channel

When joining named channels, purple_chat_join() returned NULL instead of
a struct groupchat, which was actually created in the conversation
created callback (prplcb_conv_new()).

If the name of this channel turned out to be different to the joined
one, this meant having one empty window in the irc client, and another
one for the other channel.

The fix is to return a mostly-empty struct groupchat immediately, and
pick it up later when the PurpleConversation is created using
bee_chat_by_title(). If that fails, fall back to creating a new one.

This bug also meant there was no proper way to report a join failure.
Future commits will address that, this one just makes that easier.

Thanks to Etan Reisner (deryni) for enlightening me while i was trying
to figure out how to fix this.

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