source: protocols/purple/purple.c @ c11b68a

Last change on this file since c11b68a was c11b68a, checked in by dequis <dx@…>, at 2016-12-27T00:37:50Z

purple: fix -Werror=format-string in chat settings code

This one was caught by the debian build scripts in travis. I had
format-security in my local cflags, not format-string. Welp.

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