source: protocols/purple/purple.c @ 2700925

Last change on this file since 2700925 was 93e0901, checked in by Wilmer van der Gaast <wilmer@…>, at 2015-05-24T18:51:57Z

ACC_FLAG_LOCAL -> ACC_FLAG_LOCAL_CONTACTS.

Also, handle the flag more properly. The config loader was not the right
place to do this in.

Next up: Add a way for a protocol module to fetch the contact list
pre-login.

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