source: protocols/purple/purple.c @ d57484d

Last change on this file since d57484d was d57484d, checked in by dequis <dx@…>, at 2016-12-26T22:39:18Z

Change some asserts into g_return_if_fail()

Because crashing asserts are bad, and maybe this helps fix the
captures_build_path issue with debian's reproducible builds
(those asserts probably include FILE)

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