source: protocols/purple/purple.c @ 17aa9a2

Last change on this file since 17aa9a2 was 2dd23da, checked in by dequis <dx@…>, at 2015-03-15T08:10:25Z

purple: Fix null pointer dereference when logging out due to daemon mode

Purple just doesn't work with daemon mode anyway, but it's better to
show the intended error message than to crash while showing it.

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