source: protocols/purple/purple.c @ 1882b70

Last change on this file since 1882b70 was 1882b70, checked in by dequis <dx@…>, at 2016-12-26T23:52:24Z

purple: add support for extra groupchat settings (helps with SIPE)

This adds channel settings prefixed by purple_. For example jabber now
has purple_room and purple_server which are decomposed variants of our
own 'room' setting. Okay, that doesn't sound very useful.

It also adds some sync from the values returned by chat_info_defaults()

  • so if the plugin figures something out in there, we save it in our

own settings.

In the case of SIPE this adds a new setting, purple_uri, which can be
set with the ma-chan:// uri for a persistent chat.

This solves the issue with the SIPE plugin only knowing how to do name
lookups after doing 'chat list' - now it just needs to work once, and we
save the real URI in our settings.

  • Property mode set to 100644
File size: 54.8 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        GString *missing_settings = NULL;
766
767        if (!pi->chat_info || !pi->chat_info_defaults ||
768            !(info = pi->chat_info(purple_account_get_connection(pd->account)))) {
769                imcb_error(ic, "Joining chatrooms not supported by this protocol");
770                return NULL;
771        }
772
773        if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
774                                                          room, pd->account))) {
775                purple_conversation_destroy(conv);
776        }
777
778        chat_hash = pi->chat_info_defaults(
779                purple_account_get_connection(pd->account), room
780        );
781
782        for (l = info; l; l = l->next) {
783                struct proto_chat_entry *pce = l->data;
784
785                if (strcmp(pce->identifier, "handle") == 0) {
786                        g_hash_table_replace(chat_hash, "handle", g_strdup(nick));
787                } else if (strcmp(pce->identifier, "password") == 0) {
788                        g_hash_table_replace(chat_hash, "password", g_strdup(password));
789                } else if (strcmp(pce->identifier, "passwd") == 0) {
790                        g_hash_table_replace(chat_hash, "passwd", g_strdup(password));
791                } else {
792                        char *key, *value;
793
794                        key = g_strdup_printf("purple_%s", pce->identifier);
795                        str_reject_chars(key, " -", '_');
796
797                        if ((value = set_getstr(sets, key))) {
798                                /* sync from bitlbee to the prpl */
799                                g_hash_table_replace(chat_hash, (char *) pce->identifier, g_strdup(value));
800                        } else if ((value = g_hash_table_lookup(chat_hash, pce->identifier))) {
801                                /* if the bitlbee one was empty, sync from prpl to bitlbee */
802                                set_setstr(sets, key, value);
803                        }
804
805                        g_free(key);
806                }
807
808                if (pce->required && !g_hash_table_lookup(chat_hash, pce->identifier)) {
809                        if (!missing_settings) {
810                                missing_settings = g_string_new(NULL);
811                                g_string_printf(missing_settings,
812                                        "Can't join %s. The following settings are required: ", room);
813                        }
814                        g_string_append_printf(missing_settings, "%s, ", pce->identifier);
815                }
816
817                g_free(pce);
818        }
819
820        g_list_free(info);
821
822        if (missing_settings) {
823                /* remove the ", " from the end */
824                g_string_truncate(missing_settings, missing_settings->len - 2);
825
826                imcb_error(ic, missing_settings->str);
827
828                g_string_free(missing_settings, TRUE);
829                g_hash_table_destroy(chat_hash);
830                return NULL;
831        }
832
833        /* do this before serv_join_chat to handle cases where prplcb_conv_new is called immediately (not async) */
834        gc = imcb_chat_new(ic, room);
835
836        serv_join_chat(purple_account_get_connection(pd->account), chat_hash);
837
838        g_hash_table_destroy(chat_hash);
839
840        return gc;
841}
842
843void purple_chat_list(struct im_connection *ic, const char *server)
844{
845        PurpleRoomlist *list;
846        struct purple_data *pd = ic->proto_data;
847        PurplePlugin *prpl = purple_plugins_find_with_id(pd->account->protocol_id);
848        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
849
850        if (!pi || !pi->roomlist_get_list) {
851                imcb_log(ic, "Room listing unsupported by this purple plugin");
852                return;
853        }
854
855        g_free(pd->chat_list_server);
856        pd->chat_list_server = (server && *server) ? g_strdup(server) : NULL;
857
858        list = purple_roomlist_get_list(pd->account->gc);
859
860        if (list) {
861                struct purple_roomlist_data *rld = list->ui_data;
862                rld->initialized = TRUE;
863
864                purple_roomlist_ref(list);
865        }
866}
867
868/* handles either prpl->chat_(add|free)_settings depending on the value of 'add' */
869static void purple_chat_update_settings(account_t *acc, set_t **head, gboolean add)
870{
871        PurplePlugin *prpl = purple_plugins_find_with_id((char *) acc->prpl->data);
872        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
873        GList *info, *l;
874
875        if (!pi->chat_info || !pi->chat_info_defaults) {
876                return;
877        }
878
879        /* hack / leap of faith: pass a NULL here because we don't have a connection yet.
880         * i reviewed all the built-in prpls and a bunch of third-party ones and none
881         * of them seem to need this parameter at all, so... i hope it never crashes */
882        info = pi->chat_info(NULL);
883
884        for (l = info; l; l = l->next) {
885                struct proto_chat_entry *pce = l->data;
886                char *key;
887
888                if (strcmp(pce->identifier, "handle") == 0 ||
889                    strcmp(pce->identifier, "password") == 0 ||
890                    strcmp(pce->identifier, "passwd") == 0) {
891                        /* skip these, they are handled above */
892                        g_free(pce);
893                        continue;
894                }
895
896                key = g_strdup_printf("purple_%s", pce->identifier);
897                str_reject_chars(key, " -", '_');
898
899                if (add) {
900                        set_add(head, key, NULL, NULL, NULL);
901                } else {
902                        set_del(head, key);
903                }
904
905                g_free(key);
906                g_free(pce);
907        }
908
909        g_list_free(NULL);
910        g_list_free(info);
911}
912
913static void purple_chat_add_settings(account_t *acc, set_t **head)
914{
915        purple_chat_update_settings(acc, head, TRUE);
916}
917
918static void purple_chat_free_settings(account_t *acc, set_t **head)
919{
920        purple_chat_update_settings(acc, head, FALSE);
921}
922
923void purple_transfer_request(struct im_connection *ic, file_transfer_t *ft, char *handle);
924
925static void purple_ui_init();
926
927GHashTable *prplcb_ui_info()
928{
929        static GHashTable *ret;
930
931        if (ret == NULL) {
932                ret = g_hash_table_new(g_str_hash, g_str_equal);
933                g_hash_table_insert(ret, "name", "BitlBee");
934                g_hash_table_insert(ret, "version", BITLBEE_VERSION);
935        }
936
937        return ret;
938}
939
940static PurpleCoreUiOps bee_core_uiops =
941{
942        NULL,                      /* ui_prefs_init */
943        NULL,                      /* debug_ui_init */
944        purple_ui_init,            /* ui_init */
945        NULL,                      /* quit */
946        prplcb_ui_info,            /* get_ui_info */
947};
948
949static void prplcb_conn_progress(PurpleConnection *gc, const char *text, size_t step, size_t step_count)
950{
951        struct im_connection *ic = purple_ic_by_gc(gc);
952
953        imcb_log(ic, "%s", text);
954}
955
956static void prplcb_conn_connected(PurpleConnection *gc)
957{
958        struct im_connection *ic = purple_ic_by_gc(gc);
959        const char *dn;
960        set_t *s;
961
962        imcb_connected(ic);
963
964        if ((dn = purple_connection_get_display_name(gc)) &&
965            (s = set_find(&ic->acc->set, "display_name"))) {
966                g_free(s->value);
967                s->value = g_strdup(dn);
968        }
969
970        // user list needs to be requested for Gadu-Gadu
971        purple_gg_buddylist_import(gc);
972
973        ic->flags |= OPT_DOES_HTML;
974}
975
976static void prplcb_conn_disconnected(PurpleConnection *gc)
977{
978        struct im_connection *ic = purple_ic_by_gc(gc);
979
980        if (ic != NULL) {
981                imc_logout(ic, !gc->wants_to_die);
982        }
983}
984
985static void prplcb_conn_notice(PurpleConnection *gc, const char *text)
986{
987        struct im_connection *ic = purple_ic_by_gc(gc);
988
989        if (ic != NULL) {
990                imcb_log(ic, "%s", text);
991        }
992}
993
994static void prplcb_conn_report_disconnect_reason(PurpleConnection *gc, PurpleConnectionError reason, const char *text)
995{
996        struct im_connection *ic = purple_ic_by_gc(gc);
997
998        /* PURPLE_CONNECTION_ERROR_NAME_IN_USE means concurrent login,
999           should probably handle that. */
1000        if (ic != NULL) {
1001                imcb_error(ic, "%s", text);
1002        }
1003}
1004
1005static PurpleConnectionUiOps bee_conn_uiops =
1006{
1007        prplcb_conn_progress,                    /* connect_progress */
1008        prplcb_conn_connected,                   /* connected */
1009        prplcb_conn_disconnected,                /* disconnected */
1010        prplcb_conn_notice,                      /* notice */
1011        NULL,                                    /* report_disconnect */
1012        NULL,                                    /* network_connected */
1013        NULL,                                    /* network_disconnected */
1014        prplcb_conn_report_disconnect_reason,    /* report_disconnect_reason */
1015};
1016
1017static void prplcb_blist_update(PurpleBuddyList *list, PurpleBlistNode *node)
1018{
1019        if (node->type == PURPLE_BLIST_BUDDY_NODE) {
1020                PurpleBuddy *bud = (PurpleBuddy *) node;
1021                PurpleGroup *group = purple_buddy_get_group(bud);
1022                struct im_connection *ic = purple_ic_by_pa(bud->account);
1023                struct purple_data *pd = ic->proto_data;
1024                PurpleStatus *as;
1025                int flags = 0;
1026                char *alias = NULL;
1027
1028                if (ic == NULL) {
1029                        return;
1030                }
1031
1032                alias = bud->server_alias ? : bud->alias;
1033
1034                if (alias) {
1035                        imcb_rename_buddy(ic, bud->name, alias);
1036                        if (pd->flags & PURPLE_OPT_SHOULD_SET_NICK) {
1037                                imcb_buddy_nick_change(ic, bud->name, alias);
1038                        }
1039                }
1040
1041                if (group) {
1042                        imcb_add_buddy(ic, bud->name, purple_group_get_name(group));
1043                }
1044
1045                flags |= purple_presence_is_online(bud->presence) ? OPT_LOGGED_IN : 0;
1046                flags |= purple_presence_is_available(bud->presence) ? 0 : OPT_AWAY;
1047
1048                as = purple_presence_get_active_status(bud->presence);
1049
1050                imcb_buddy_status(ic, bud->name, flags, purple_status_get_name(as),
1051                                  purple_status_get_attr_string(as, "message"));
1052
1053                imcb_buddy_times(ic, bud->name,
1054                                 purple_presence_get_login_time(bud->presence),
1055                                 purple_presence_get_idle_time(bud->presence));
1056        }
1057}
1058
1059static void prplcb_blist_new(PurpleBlistNode *node)
1060{
1061        if (node->type == PURPLE_BLIST_BUDDY_NODE) {
1062                PurpleBuddy *bud = (PurpleBuddy *) node;
1063                struct im_connection *ic = purple_ic_by_pa(bud->account);
1064
1065                if (ic == NULL) {
1066                        return;
1067                }
1068
1069                imcb_add_buddy(ic, bud->name, NULL);
1070
1071                prplcb_blist_update(NULL, node);
1072        }
1073}
1074
1075static void prplcb_blist_remove(PurpleBuddyList *list, PurpleBlistNode *node)
1076{
1077/*
1078        PurpleBuddy *bud = (PurpleBuddy*) node;
1079
1080        if( node->type == PURPLE_BLIST_BUDDY_NODE )
1081        {
1082                struct im_connection *ic = purple_ic_by_pa( bud->account );
1083
1084                if( ic == NULL )
1085                        return;
1086
1087                imcb_remove_buddy( ic, bud->name, NULL );
1088        }
1089*/
1090}
1091
1092static PurpleBlistUiOps bee_blist_uiops =
1093{
1094        NULL,                      /* new_list */
1095        prplcb_blist_new,          /* new_node */
1096        NULL,                      /* show */
1097        prplcb_blist_update,       /* update */
1098        prplcb_blist_remove,       /* remove */
1099};
1100
1101void prplcb_conv_new(PurpleConversation *conv)
1102{
1103        if (conv->type == PURPLE_CONV_TYPE_CHAT) {
1104                struct im_connection *ic = purple_ic_by_pa(conv->account);
1105                struct groupchat *gc;
1106
1107                gc = bee_chat_by_title(ic->bee, ic, conv->name);
1108
1109                if (!gc) {
1110                        gc = imcb_chat_new(ic, conv->name);
1111                        if (conv->title != NULL) {
1112                                imcb_chat_name_hint(gc, conv->title);
1113                        }
1114                }
1115
1116                /* don't set the topic if it's just the name */
1117                if (conv->title != NULL && strcmp(conv->name, conv->title) != 0) {
1118                        imcb_chat_topic(gc, NULL, conv->title, 0);
1119                }
1120
1121                conv->ui_data = gc;
1122                gc->data = conv;
1123
1124                /* libpurple brokenness: Whatever. Show that we join right away,
1125                   there's no clear "This is you!" signaling in _add_users so
1126                   don't even try. */
1127                imcb_chat_add_buddy(gc, gc->ic->acc->user);
1128        }
1129}
1130
1131void prplcb_conv_free(PurpleConversation *conv)
1132{
1133        struct groupchat *gc = conv->ui_data;
1134
1135        imcb_chat_free(gc);
1136}
1137
1138void prplcb_conv_add_users(PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals)
1139{
1140        struct groupchat *gc = conv->ui_data;
1141        GList *b;
1142
1143        for (b = cbuddies; b; b = b->next) {
1144                PurpleConvChatBuddy *pcb = b->data;
1145
1146                imcb_chat_add_buddy(gc, pcb->name);
1147        }
1148}
1149
1150void prplcb_conv_del_users(PurpleConversation *conv, GList *cbuddies)
1151{
1152        struct groupchat *gc = conv->ui_data;
1153        GList *b;
1154
1155        for (b = cbuddies; b; b = b->next) {
1156                imcb_chat_remove_buddy(gc, b->data, "");
1157        }
1158}
1159
1160/* Generic handler for IM or chat messages, covers write_chat, write_im and write_conv */
1161static void handle_conv_msg(PurpleConversation *conv, const char *who, const char *message_, guint32 bee_flags, time_t mtime)
1162{
1163        struct im_connection *ic = purple_ic_by_pa(conv->account);
1164        struct groupchat *gc = conv->ui_data;
1165        char *message = g_strdup(message_);
1166        PurpleBuddy *buddy;
1167
1168        buddy = purple_find_buddy(conv->account, who);
1169        if (buddy != NULL) {
1170                who = purple_buddy_get_name(buddy);
1171        }
1172
1173        if (conv->type == PURPLE_CONV_TYPE_IM) {
1174                imcb_buddy_msg(ic, who, message, bee_flags, mtime);
1175        } else if (gc) {
1176                imcb_chat_msg(gc, who, message, bee_flags, mtime);
1177        }
1178
1179        g_free(message);
1180}
1181
1182/* Handles write_im and write_chat. Removes echoes of locally sent messages */
1183static void prplcb_conv_msg(PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime)
1184{
1185        if (!(flags & PURPLE_MESSAGE_SEND)) {
1186                handle_conv_msg(conv, who, message, 0, mtime);
1187        }
1188}
1189
1190/* Handles write_conv. Only passes self messages from other locations through.
1191 * That is, only writes of PURPLE_MESSAGE_SEND.
1192 * There are more events which might be handled in the future, but some are tricky.
1193 * (images look like <img id="123">, what do i do with that?) */
1194static void prplcb_conv_write(PurpleConversation *conv, const char *who, const char *alias, const char *message,
1195                              PurpleMessageFlags flags, time_t mtime)
1196{
1197        if (flags & PURPLE_MESSAGE_SEND) {
1198                handle_conv_msg(conv, who, message, OPT_SELFMESSAGE, mtime);
1199        }
1200}
1201
1202/* No, this is not a ui_op but a signal. */
1203static void prplcb_buddy_typing(PurpleAccount *account, const char *who, gpointer null)
1204{
1205        PurpleConversation *conv;
1206        PurpleConvIm *im;
1207        int state;
1208
1209        if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, account)) == NULL) {
1210                return;
1211        }
1212
1213        im = PURPLE_CONV_IM(conv);
1214        switch (purple_conv_im_get_typing_state(im)) {
1215        case PURPLE_TYPING:
1216                state = OPT_TYPING;
1217                break;
1218        case PURPLE_TYPED:
1219                state = OPT_THINKING;
1220                break;
1221        default:
1222                state = 0;
1223        }
1224
1225        imcb_buddy_typing(purple_ic_by_pa(account), who, state);
1226}
1227
1228static PurpleConversationUiOps bee_conv_uiops =
1229{
1230        prplcb_conv_new,           /* create_conversation  */
1231        prplcb_conv_free,          /* destroy_conversation */
1232        prplcb_conv_msg,           /* write_chat           */
1233        prplcb_conv_msg,           /* write_im             */
1234        prplcb_conv_write,         /* write_conv           */
1235        prplcb_conv_add_users,     /* chat_add_users       */
1236        NULL,                      /* chat_rename_user     */
1237        prplcb_conv_del_users,     /* chat_remove_users    */
1238        NULL,                      /* chat_update_user     */
1239        NULL,                      /* present              */
1240        NULL,                      /* has_focus            */
1241        NULL,                      /* custom_smiley_add    */
1242        NULL,                      /* custom_smiley_write  */
1243        NULL,                      /* custom_smiley_close  */
1244        NULL,                      /* send_confirm         */
1245};
1246
1247struct prplcb_request_action_data {
1248        void *user_data, *bee_data;
1249        PurpleRequestActionCb yes, no;
1250        int yes_i, no_i;
1251};
1252
1253static void prplcb_request_action_yes(void *data)
1254{
1255        struct prplcb_request_action_data *pqad = data;
1256
1257        if (pqad->yes) {
1258                pqad->yes(pqad->user_data, pqad->yes_i);
1259        }
1260}
1261
1262static void prplcb_request_action_no(void *data)
1263{
1264        struct prplcb_request_action_data *pqad = data;
1265
1266        if (pqad->no) {
1267                pqad->no(pqad->user_data, pqad->no_i);
1268        }
1269}
1270
1271/* q->free() callback from query_del()*/
1272static void prplcb_request_action_free(void *data)
1273{
1274        struct prplcb_request_action_data *pqad = data;
1275
1276        pqad->bee_data = NULL;
1277        purple_request_close(PURPLE_REQUEST_ACTION, pqad);
1278}
1279
1280static void *prplcb_request_action(const char *title, const char *primary, const char *secondary,
1281                                   int default_action, PurpleAccount *account, const char *who,
1282                                   PurpleConversation *conv, void *user_data, size_t action_count,
1283                                   va_list actions)
1284{
1285        struct prplcb_request_action_data *pqad;
1286        int i;
1287        char *q;
1288
1289        pqad = g_new0(struct prplcb_request_action_data, 1);
1290
1291        for (i = 0; i < action_count; i++) {
1292                char *caption;
1293                void *fn;
1294
1295                caption = va_arg(actions, char*);
1296                fn = va_arg(actions, void*);
1297
1298                if (strstr(caption, "Accept") || strstr(caption, "OK")) {
1299                        pqad->yes = fn;
1300                        pqad->yes_i = i;
1301                } else if (strstr(caption, "Reject") || strstr(caption, "Cancel")) {
1302                        pqad->no = fn;
1303                        pqad->no_i = i;
1304                }
1305        }
1306
1307        pqad->user_data = user_data;
1308
1309        /* TODO: IRC stuff here :-( */
1310        q = g_strdup_printf("Request: %s\n\n%s\n\n%s", title, primary, secondary);
1311        pqad->bee_data = query_add(local_bee->ui_data, purple_ic_by_pa(account), q,
1312                                   prplcb_request_action_yes, prplcb_request_action_no,
1313                                   prplcb_request_action_free, pqad);
1314
1315        g_free(q);
1316
1317        return pqad;
1318}
1319
1320/* So it turns out some requests have no account context at all, because
1321 * libpurple hates us. This means that query_del_by_conn() won't remove those
1322 * on logout, and will segfault if the user replies. That's why this exists.
1323 */
1324static void prplcb_close_request(PurpleRequestType type, void *data)
1325{
1326        struct prplcb_request_action_data *pqad;
1327        struct request_input_data *ri;
1328        struct purple_data *pd;
1329
1330        if (!data) {
1331                return;
1332        }
1333
1334        switch (type) {
1335        case PURPLE_REQUEST_ACTION:
1336                pqad = data;
1337                /* if this is null, it's because query_del was run already */
1338                if (pqad->bee_data) {
1339                        query_del(local_bee->ui_data, pqad->bee_data);
1340                }
1341                g_free(pqad);
1342                break;
1343        case PURPLE_REQUEST_INPUT:
1344                ri = data;
1345                pd = ri->ic->proto_data;
1346                imcb_remove_buddy(ri->ic, ri->buddy, NULL);
1347                g_free(ri->buddy);
1348                g_hash_table_remove(pd->input_requests, GUINT_TO_POINTER(ri->id));
1349                break;
1350        default:
1351                g_free(data);
1352                break;
1353        }
1354
1355}
1356
1357void* prplcb_request_input(const char *title, const char *primary,
1358        const char *secondary, const char *default_value, gboolean multiline,
1359        gboolean masked, gchar *hint, const char *ok_text, GCallback ok_cb,
1360        const char *cancel_text, GCallback cancel_cb, PurpleAccount *account,
1361        const char *who, PurpleConversation *conv, void *user_data)
1362{
1363        struct im_connection *ic = purple_ic_by_pa(account);
1364        struct purple_data *pd = ic->proto_data;
1365        struct request_input_data *ri;
1366        guint id;
1367
1368        /* hack so that jabber's chat list doesn't ask for conference server twice */
1369        if (pd->chat_list_server && title && g_strcmp0(title, "Enter a Conference Server") == 0) {
1370                ((ri_callback_t) ok_cb)(user_data, pd->chat_list_server);
1371                g_free(pd->chat_list_server);
1372                pd->chat_list_server = NULL;
1373                return NULL;
1374        }
1375
1376        id = pd->next_request_id++;
1377        ri = g_new0(struct request_input_data, 1);
1378
1379        ri->id = id;
1380        ri->ic = ic;
1381        ri->buddy = g_strdup_printf("%s_%u", PURPLE_REQUEST_HANDLE, id);
1382        ri->data_callback = (ri_callback_t) ok_cb;
1383        ri->user_data = user_data;
1384        g_hash_table_insert(pd->input_requests, GUINT_TO_POINTER(id), ri);
1385
1386        imcb_add_buddy(ic, ri->buddy, NULL);
1387
1388        if (title && *title) {
1389                imcb_buddy_msg(ic, ri->buddy, title, 0, 0);
1390        }
1391
1392        if (primary && *primary) {
1393                imcb_buddy_msg(ic, ri->buddy, primary, 0, 0);
1394        }
1395
1396        if (secondary && *secondary) {
1397                imcb_buddy_msg(ic, ri->buddy, secondary, 0, 0);
1398        }
1399
1400        return ri;
1401}
1402
1403void purple_request_input_callback(guint id, struct im_connection *ic,
1404                                   const char *message, const char *who)
1405{
1406        struct purple_data *pd = ic->proto_data;
1407        struct request_input_data *ri;
1408
1409        if (!(ri = g_hash_table_lookup(pd->input_requests, GUINT_TO_POINTER(id)))) {
1410                return;
1411        }
1412
1413        ri->data_callback(ri->user_data, message);
1414
1415        purple_request_close(PURPLE_REQUEST_INPUT, ri);
1416}
1417
1418
1419static PurpleRequestUiOps bee_request_uiops =
1420{
1421        prplcb_request_input,      /* request_input */
1422        NULL,                      /* request_choice */
1423        prplcb_request_action,     /* request_action */
1424        NULL,                      /* request_fields */
1425        NULL,                      /* request_file */
1426        prplcb_close_request,      /* close_request */
1427        NULL,                      /* request_folder */
1428};
1429
1430static void prplcb_privacy_permit_added(PurpleAccount *account, const char *name)
1431{
1432        struct im_connection *ic = purple_ic_by_pa(account);
1433
1434        if (!g_slist_find_custom(ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp)) {
1435                ic->permit = g_slist_prepend(ic->permit, g_strdup(name));
1436        }
1437}
1438
1439static void prplcb_privacy_permit_removed(PurpleAccount *account, const char *name)
1440{
1441        struct im_connection *ic = purple_ic_by_pa(account);
1442        void *n;
1443
1444        n = g_slist_find_custom(ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp);
1445        ic->permit = g_slist_remove(ic->permit, n);
1446}
1447
1448static void prplcb_privacy_deny_added(PurpleAccount *account, const char *name)
1449{
1450        struct im_connection *ic = purple_ic_by_pa(account);
1451
1452        if (!g_slist_find_custom(ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp)) {
1453                ic->deny = g_slist_prepend(ic->deny, g_strdup(name));
1454        }
1455}
1456
1457static void prplcb_privacy_deny_removed(PurpleAccount *account, const char *name)
1458{
1459        struct im_connection *ic = purple_ic_by_pa(account);
1460        void *n;
1461
1462        n = g_slist_find_custom(ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp);
1463        ic->deny = g_slist_remove(ic->deny, n);
1464}
1465
1466static PurplePrivacyUiOps bee_privacy_uiops =
1467{
1468        prplcb_privacy_permit_added,       /* permit_added */
1469        prplcb_privacy_permit_removed,     /* permit_removed */
1470        prplcb_privacy_deny_added,         /* deny_added */
1471        prplcb_privacy_deny_removed,       /* deny_removed */
1472};
1473
1474static void prplcb_roomlist_create(PurpleRoomlist *list)
1475{
1476        struct purple_roomlist_data *rld;
1477
1478        list->ui_data = rld = g_new0(struct purple_roomlist_data, 1);
1479        rld->topic = -1;
1480}
1481
1482static void prplcb_roomlist_set_fields(PurpleRoomlist *list, GList *fields)
1483{
1484        gint topic = -1;
1485        GList *l;
1486        guint i;
1487        PurpleRoomlistField *field;
1488        struct purple_roomlist_data *rld = list->ui_data;
1489
1490        for (i = 0, l = fields; l; i++, l = l->next) {
1491                field = l->data;
1492
1493                /* Use the first visible string field as a fallback topic */
1494                if (i != 0 && topic < 0 && !field->hidden &&
1495                    field->type == PURPLE_ROOMLIST_FIELD_STRING) {
1496                        topic = i;
1497                }
1498
1499                if ((g_strcasecmp(field->name, "description") == 0) ||
1500                    (g_strcasecmp(field->name, "topic") == 0)) {
1501                        if (field->type == PURPLE_ROOMLIST_FIELD_STRING) {
1502                                rld->topic = i;
1503                        }
1504                }
1505        }
1506
1507        if (rld->topic < 0) {
1508                rld->topic = topic;
1509        }
1510}
1511
1512static char *prplcb_roomlist_get_room_name(PurpleRoomlist *list, PurpleRoomlistRoom *room)
1513{
1514        struct im_connection *ic = purple_ic_by_pa(list->account);
1515        struct purple_data *pd = ic->proto_data;
1516        PurplePlugin *prpl = purple_plugins_find_with_id(pd->account->protocol_id);
1517        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
1518
1519        if (pi && pi->roomlist_room_serialize) {
1520                return pi->roomlist_room_serialize(room);
1521        } else {
1522                return g_strdup(purple_roomlist_room_get_name(room));
1523        }
1524}
1525
1526static void prplcb_roomlist_add_room(PurpleRoomlist *list, PurpleRoomlistRoom *room)
1527{
1528        bee_chat_info_t *ci;
1529        char *title;
1530        const char *topic;
1531        GList *fields;
1532        struct purple_roomlist_data *rld = list->ui_data;
1533
1534        fields = purple_roomlist_room_get_fields(room);
1535        title = prplcb_roomlist_get_room_name(list, room);
1536
1537        if (rld->topic >= 0) {
1538                topic = g_list_nth_data(fields, rld->topic);
1539        } else {
1540                topic = NULL;
1541        }
1542
1543        ci = g_new(bee_chat_info_t, 1);
1544        ci->title = title;
1545        ci->topic = g_strdup(topic);
1546        rld->chats = g_slist_prepend(rld->chats, ci);
1547}
1548
1549static void prplcb_roomlist_in_progress(PurpleRoomlist *list, gboolean in_progress)
1550{
1551        struct im_connection *ic;
1552        struct purple_data *pd;
1553        struct purple_roomlist_data *rld = list->ui_data;
1554
1555        if (in_progress || !rld) {
1556                return;
1557        }
1558
1559        ic = purple_ic_by_pa(list->account);
1560        imcb_chat_list_free(ic);
1561
1562        pd = ic->proto_data;
1563        g_free(pd->chat_list_server);
1564        pd->chat_list_server = NULL;
1565
1566        ic->chatlist = g_slist_reverse(rld->chats);
1567        rld->chats = NULL;
1568
1569        imcb_chat_list_finish(ic);
1570
1571        if (rld->initialized) {
1572                purple_roomlist_unref(list);
1573        }
1574}
1575
1576static void prplcb_roomlist_destroy(PurpleRoomlist *list)
1577{
1578        g_free(list->ui_data);
1579        list->ui_data = NULL;
1580}
1581
1582static PurpleRoomlistUiOps bee_roomlist_uiops =
1583{
1584        NULL,                         /* show_with_account */
1585        prplcb_roomlist_create,       /* create */
1586        prplcb_roomlist_set_fields,   /* set_fields */
1587        prplcb_roomlist_add_room,     /* add_room */
1588        prplcb_roomlist_in_progress,  /* in_progress */
1589        prplcb_roomlist_destroy,      /* destroy */
1590};
1591
1592static void prplcb_debug_print(PurpleDebugLevel level, const char *category, const char *arg_s)
1593{
1594        fprintf(stderr, "DEBUG %s: %s", category, arg_s);
1595}
1596
1597static PurpleDebugUiOps bee_debug_uiops =
1598{
1599        prplcb_debug_print,        /* print */
1600};
1601
1602static guint prplcb_ev_timeout_add(guint interval, GSourceFunc func, gpointer udata)
1603{
1604        return b_timeout_add(interval, (b_event_handler) func, udata);
1605}
1606
1607static guint prplcb_ev_input_add(int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata)
1608{
1609        return b_input_add(fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata);
1610}
1611
1612static gboolean prplcb_ev_remove(guint id)
1613{
1614        b_event_remove((gint) id);
1615        return TRUE;
1616}
1617
1618static PurpleEventLoopUiOps glib_eventloops =
1619{
1620        prplcb_ev_timeout_add,     /* timeout_add */
1621        prplcb_ev_remove,          /* timeout_remove */
1622        prplcb_ev_input_add,       /* input_add */
1623        prplcb_ev_remove,          /* input_remove */
1624};
1625
1626/* Absolutely no connection context at all. Thanks purple! brb crying */
1627static void *prplcb_notify_message(PurpleNotifyMsgType type, const char *title,
1628                                   const char *primary, const char *secondary)
1629{
1630        char *text = g_strdup_printf("%s%s - %s%s%s",
1631                (type == PURPLE_NOTIFY_MSG_ERROR) ? "Error: " : "",
1632                title,
1633                primary ?: "",
1634                (primary && secondary) ? " - " : "",
1635                secondary ?: ""
1636        );
1637
1638        if (local_bee->ui->log) {
1639                local_bee->ui->log(local_bee, "purple", text);
1640        }
1641
1642        g_free(text);
1643
1644        return NULL;
1645}
1646
1647static void *prplcb_notify_email(PurpleConnection *gc, const char *subject, const char *from,
1648                                 const char *to, const char *url)
1649{
1650        struct im_connection *ic = purple_ic_by_gc(gc);
1651
1652        imcb_notify_email(ic, "Received e-mail from %s for %s: %s <%s>", from, to, subject, url);
1653
1654        return NULL;
1655}
1656
1657static void *prplcb_notify_userinfo(PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info)
1658{
1659        struct im_connection *ic = purple_ic_by_gc(gc);
1660        GString *info = g_string_new("");
1661        GList *l = purple_notify_user_info_get_entries(user_info);
1662        char *key;
1663        const char *value;
1664        int n;
1665
1666        while (l) {
1667                PurpleNotifyUserInfoEntry *e = l->data;
1668
1669                switch (purple_notify_user_info_entry_get_type(e)) {
1670                case PURPLE_NOTIFY_USER_INFO_ENTRY_PAIR:
1671                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_HEADER:
1672                        key = g_strdup(purple_notify_user_info_entry_get_label(e));
1673                        value = purple_notify_user_info_entry_get_value(e);
1674
1675                        if (key) {
1676                                strip_html(key);
1677                                g_string_append_printf(info, "%s: ", key);
1678
1679                                if (value) {
1680                                        n = strlen(value) - 1;
1681                                        while (g_ascii_isspace(value[n])) {
1682                                                n--;
1683                                        }
1684                                        g_string_append_len(info, value, n + 1);
1685                                }
1686                                g_string_append_c(info, '\n');
1687                                g_free(key);
1688                        }
1689
1690                        break;
1691                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK:
1692                        g_string_append(info, "------------------------\n");
1693                        break;
1694                }
1695
1696                l = l->next;
1697        }
1698
1699        imcb_log(ic, "User %s info:\n%s", who, info->str);
1700        g_string_free(info, TRUE);
1701
1702        return NULL;
1703}
1704
1705static PurpleNotifyUiOps bee_notify_uiops =
1706{
1707        prplcb_notify_message,     /* notify_message */
1708        prplcb_notify_email,       /* notify_email */
1709        NULL,                      /* notify_emails */
1710        NULL,                      /* notify_formatted */
1711        NULL,                      /* notify_searchresults */
1712        NULL,                      /* notify_searchresults_new_rows */
1713        prplcb_notify_userinfo,    /* notify_userinfo */
1714};
1715
1716static void *prplcb_account_request_authorize(PurpleAccount *account, const char *remote_user,
1717                                              const char *id, const char *alias, const char *message, gboolean on_list,
1718                                              PurpleAccountRequestAuthorizationCb authorize_cb,
1719                                              PurpleAccountRequestAuthorizationCb deny_cb, void *user_data)
1720{
1721        struct im_connection *ic = purple_ic_by_pa(account);
1722        char *q;
1723
1724        if (alias) {
1725                q = g_strdup_printf("%s (%s) wants to add you to his/her contact "
1726                                    "list. (%s)", alias, remote_user, message);
1727        } else {
1728                q = g_strdup_printf("%s wants to add you to his/her contact "
1729                                    "list. (%s)", remote_user, message);
1730        }
1731
1732        imcb_ask_with_free(ic, q, user_data, authorize_cb, deny_cb, NULL);
1733        g_free(q);
1734
1735        return NULL;
1736}
1737
1738static PurpleAccountUiOps bee_account_uiops =
1739{
1740        NULL,                              /* notify_added */
1741        NULL,                              /* status_changed */
1742        NULL,                              /* request_add */
1743        prplcb_account_request_authorize,  /* request_authorize */
1744        NULL,                              /* close_account_request */
1745};
1746
1747extern PurpleXferUiOps bee_xfer_uiops;
1748
1749static void purple_ui_init()
1750{
1751        purple_connections_set_ui_ops(&bee_conn_uiops);
1752        purple_blist_set_ui_ops(&bee_blist_uiops);
1753        purple_conversations_set_ui_ops(&bee_conv_uiops);
1754        purple_request_set_ui_ops(&bee_request_uiops);
1755        purple_privacy_set_ui_ops(&bee_privacy_uiops);
1756        purple_roomlist_set_ui_ops(&bee_roomlist_uiops);
1757        purple_notify_set_ui_ops(&bee_notify_uiops);
1758        purple_accounts_set_ui_ops(&bee_account_uiops);
1759        purple_xfers_set_ui_ops(&bee_xfer_uiops);
1760
1761        if (getenv("BITLBEE_DEBUG")) {
1762                purple_debug_set_ui_ops(&bee_debug_uiops);
1763        }
1764}
1765
1766/* borrowing this semi-private function
1767 * TODO: figure out a better interface later (famous last words) */
1768gboolean plugin_info_add(struct plugin_info *info);
1769
1770void purple_initmodule()
1771{
1772        struct prpl funcs;
1773        GList *prots;
1774        GString *help;
1775        char *dir;
1776
1777        if (purple_get_core() != NULL) {
1778                log_message(LOGLVL_ERROR, "libpurple already initialized. "
1779                            "Please use inetd or ForkDaemon mode instead.");
1780                return;
1781        }
1782
1783        g_return_if_fail((int) B_EV_IO_READ == (int) PURPLE_INPUT_READ);
1784        g_return_if_fail((int) B_EV_IO_WRITE == (int) PURPLE_INPUT_WRITE);
1785
1786        dir = g_strdup_printf("%s/purple", global.conf->configdir);
1787        purple_util_set_user_dir(dir);
1788        g_free(dir);
1789
1790        dir = g_strdup_printf("%s/purple", global.conf->plugindir);
1791        purple_plugins_add_search_path(dir);
1792        g_free(dir);
1793
1794        purple_debug_set_enabled(FALSE);
1795        purple_core_set_ui_ops(&bee_core_uiops);
1796        purple_eventloop_set_ui_ops(&glib_eventloops);
1797        if (!purple_core_init("BitlBee")) {
1798                /* Initializing the core failed. Terminate. */
1799                fprintf(stderr, "libpurple initialization failed.\n");
1800                abort();
1801        }
1802
1803        if (proxytype != PROXY_NONE) {
1804                PurpleProxyInfo *pi = purple_global_proxy_get_info();
1805                switch (proxytype) {
1806                case PROXY_SOCKS4A:
1807                case PROXY_SOCKS4:
1808                        purple_proxy_info_set_type(pi, PURPLE_PROXY_SOCKS4);
1809                        break;
1810                case PROXY_SOCKS5:
1811                        purple_proxy_info_set_type(pi, PURPLE_PROXY_SOCKS5);
1812                        break;
1813                case PROXY_HTTP:
1814                        purple_proxy_info_set_type(pi, PURPLE_PROXY_HTTP);
1815                        break;
1816                }
1817                purple_proxy_info_set_host(pi, proxyhost);
1818                purple_proxy_info_set_port(pi, proxyport);
1819                purple_proxy_info_set_username(pi, proxyuser);
1820                purple_proxy_info_set_password(pi, proxypass);
1821        }
1822
1823        purple_set_blist(purple_blist_new());
1824
1825        /* No, really. So far there were ui_ops for everything, but now suddenly
1826           one needs to use signals for typing notification stuff. :-( */
1827        purple_signal_connect(purple_conversations_get_handle(), "buddy-typing",
1828                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1829        purple_signal_connect(purple_conversations_get_handle(), "buddy-typed",
1830                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1831        purple_signal_connect(purple_conversations_get_handle(), "buddy-typing-stopped",
1832                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1833
1834        memset(&funcs, 0, sizeof(funcs));
1835        funcs.login = purple_login;
1836        funcs.init = purple_init;
1837        funcs.logout = purple_logout;
1838        funcs.buddy_msg = purple_buddy_msg;
1839        funcs.away_states = purple_away_states;
1840        funcs.set_away = purple_set_away;
1841        funcs.add_buddy = purple_add_buddy;
1842        funcs.remove_buddy = purple_remove_buddy;
1843        funcs.add_permit = purple_add_permit;
1844        funcs.add_deny = purple_add_deny;
1845        funcs.rem_permit = purple_rem_permit;
1846        funcs.rem_deny = purple_rem_deny;
1847        funcs.get_info = purple_get_info;
1848        funcs.keepalive = purple_keepalive;
1849        funcs.send_typing = purple_send_typing;
1850        funcs.handle_cmp = g_strcasecmp;
1851        /* TODO(wilmer): Set these only for protocols that support them? */
1852        funcs.chat_msg = purple_chat_msg;
1853        funcs.chat_with = purple_chat_with;
1854        funcs.chat_invite = purple_chat_invite;
1855        funcs.chat_topic = purple_chat_set_topic;
1856        funcs.chat_kick = purple_chat_kick;
1857        funcs.chat_leave = purple_chat_leave;
1858        funcs.chat_join = purple_chat_join;
1859        funcs.chat_list = purple_chat_list;
1860        funcs.chat_add_settings = purple_chat_add_settings;
1861        funcs.chat_free_settings = purple_chat_free_settings;
1862        funcs.transfer_request = purple_transfer_request;
1863
1864        help = g_string_new("BitlBee libpurple module supports the following IM protocols:\n");
1865
1866        /* Add a protocol entry to BitlBee's structures for every protocol
1867           supported by this libpurple instance. */
1868        for (prots = purple_plugins_get_protocols(); prots; prots = prots->next) {
1869                PurplePlugin *prot = prots->data;
1870                PurplePluginProtocolInfo *pi = prot->info->extra_info;
1871                struct prpl *ret;
1872                struct plugin_info *info;
1873
1874                /* If we already have this one (as a native module), don't
1875                   add a libpurple duplicate. */
1876                if (find_protocol(prot->info->id)) {
1877                        continue;
1878                }
1879
1880                ret = g_memdup(&funcs, sizeof(funcs));
1881                ret->name = ret->data = prot->info->id;
1882                if (strncmp(ret->name, "prpl-", 5) == 0) {
1883                        ret->name += 5;
1884                }
1885
1886                if (pi->options & OPT_PROTO_NO_PASSWORD) {
1887                        ret->options |= PRPL_OPT_NO_PASSWORD;
1888                }
1889
1890                if (pi->options & OPT_PROTO_PASSWORD_OPTIONAL) {
1891                        ret->options |= PRPL_OPT_PASSWORD_OPTIONAL;
1892                }
1893
1894                register_protocol(ret);
1895
1896                g_string_append_printf(help, "\n* %s (%s)", ret->name, prot->info->name);
1897
1898                /* libpurple doesn't define a protocol called OSCAR, but we
1899                   need it to be compatible with normal BitlBee. */
1900                if (g_strcasecmp(prot->info->id, "prpl-aim") == 0) {
1901                        ret = g_memdup(&funcs, sizeof(funcs));
1902                        ret->name = "oscar";
1903                        /* purple_get_account_prpl_id() determines the actual protocol ID (icq/aim) */
1904                        ret->data = NULL;
1905                        register_protocol(ret);
1906                }
1907
1908                info = g_new0(struct plugin_info, 1);
1909                info->abiver = BITLBEE_ABI_VERSION_CODE;
1910                info->name = ret->name;
1911                info->version = prot->info->version;
1912                info->description = prot->info->description;
1913                info->author = prot->info->author;
1914                info->url = prot->info->homepage;
1915
1916                plugin_info_add(info);
1917        }
1918
1919        g_string_append(help, "\n\nFor used protocols, more information about available "
1920                        "settings can be found using \x02help purple <protocol name>\x02 "
1921                        "(create an account using that protocol first!)");
1922
1923        /* Add a simple dynamically-generated help item listing all
1924           the supported protocols. */
1925        help_add_mem(&global.help, "purple", help->str);
1926        g_string_free(help, TRUE);
1927}
Note: See TracBrowser for help on using the repository browser.