source: protocols/purple/purple.c @ 6a48992

Last change on this file since 6a48992 was 6a48992, checked in by dequis <dx@…>, at 2015-03-14T01:02:56Z

purple: handle purple_request_input

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