source: protocols/purple/purple.c @ c94e208

Last change on this file since c94e208 was 6e991a9, checked in by dequis <dx@…>, at 2016-10-16T06:51:53Z

Turn purple_chatlist_free() into a imcb_chat_list_free()

I found myself copypasting this to jabber. Might as well make it part of
the API.

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