source: protocols/purple/purple.c @ 9c7ef22

Last change on this file since 9c7ef22 was 59ccef5, checked in by dequis <dx@…>, at 2016-12-26T00:20:09Z

purple: Call imcb_buddy_nick_change() on a few whitelisted plugins

The whitelist includes hangouts, funyahoo and icq.

These plugins tend to have numeric or meaningless usernames. With this
change, users don't have to do 'ac whatever set nick_format %full_name'
anymore. Just sugar.

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