source: protocols/purple/purple.c @ 1a8875f

Last change on this file since 1a8875f was 1a8875f, checked in by dequis <dx@…>, at 2016-09-25T03:07:25Z

purple: Fix another issue where /join results in two channels

Follow up to a3019499665384a3dcbbc11016d256e6d4dcd26c (which actually
made things worse than before for this particular case - found it by
bisecting)

This affected skypeweb and hangouts, maybe others.

Sometimes serv_join_chat() results in a call chain like this
(outermost frame first)

  1. purple_chat_join
  2. serv_join_chat
  3. (the join_chat function of the prpl)
  4. serv_got_joined_chat
  5. purple_conversation_new
  6. prplcb_conv_new

The last one tries to find a struct groupchat by name (that's the code
from the referenced commit). If it doesn't exist, it creates a new one.
This usually isn't an issue because the 4th step is done asynchronously,
and a groupchat is created at the end of purple_chat_join. If it's not,
you end up with two groupchats, which can lead to other nasty issues.

This moves the creation of the groupchat right before serv_join_chat().
That way, it always exists even if the conversation is created
immediately.

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