source: protocols/purple/purple.c @ 4e4616a

Last change on this file since 4e4616a was 1ec454c, checked in by dequis <dx@…>, at 2015-11-26T02:32:54Z

purple: fix a bunch of small leaks

Most of them related to channel joins, one of them related to my recent
certificate pool path fix.

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