source: protocols/purple/purple.c @ 5ff4618

Last change on this file since 5ff4618 was 5ff4618, checked in by dequis <dx@…>, at 2015-02-22T21:17:21Z

purple: cleanup, remove one usage of static local_bee

Shouldn't affect anything, and still doesn't allow initializing
libpurple more than once, but whatever.

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