source: protocols/purple/purple.c @ 50988d1

Last change on this file since 50988d1 was 50988d1, checked in by dequis <dx@…>, at 2016-10-31T00:38:32Z

purple: fix icq login when the protocol name is "oscar"

Which happens when moving from non-purple to purple.

Fixes trac ticket 1269

Since "oscar" doesn't exist in purple, the old code called
register_protocol() to associate oscar with prpl-aim, which meant that
aim accounts migrated seamlessly to purple but icq accounts broke
silently, throwing incorrect password errors.

Now the oscar protocol is special-cased to return prpl-aim or prpl-icq
depending on the first character of the username, which is the same
thing the built-in oscar protocol does.

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