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

Last change on this file since 5ebff60 was 5ebff60, checked in by dequis <dx@…>, at 2015-02-20T22:50:54Z

Reindent everything to K&R style with tabs

Used uncrustify, with the configuration file in ./doc/uncrustify.cfg

Commit author set to "Indent <please@…>" so that it's easier to
skip while doing git blame.

  • 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        int i;
1003        char *q;
1004
1005        pqad = g_new0(struct prplcb_request_action_data, 1);
1006
1007        for (i = 0; i < action_count; i++) {
1008                char *caption;
1009                void *fn;
1010
1011                caption = va_arg(actions, char*);
1012                fn = va_arg(actions, void*);
1013
1014                if (strstr(caption, "Accept") || strstr(caption, "OK")) {
1015                        pqad->yes = fn;
1016                        pqad->yes_i = i;
1017                } else if (strstr(caption, "Reject") || strstr(caption, "Cancel")) {
1018                        pqad->no = fn;
1019                        pqad->no_i = i;
1020                }
1021        }
1022
1023        pqad->user_data = user_data;
1024
1025        /* TODO: IRC stuff here :-( */
1026        q = g_strdup_printf("Request: %s\n\n%s\n\n%s", title, primary, secondary);
1027        pqad->bee_data = query_add(local_bee->ui_data, purple_ic_by_pa(account), q,
1028                                   prplcb_request_action_yes, prplcb_request_action_no, g_free, pqad);
1029
1030        g_free(q);
1031
1032        return pqad;
1033}
1034
1035/*
1036static void prplcb_request_test()
1037{
1038        fprintf( stderr, "bla\n" );
1039}
1040*/
1041
1042static PurpleRequestUiOps bee_request_uiops =
1043{
1044        NULL,
1045        NULL,
1046        prplcb_request_action,
1047        NULL,
1048        NULL,
1049        NULL,
1050        NULL,
1051};
1052
1053static void prplcb_privacy_permit_added(PurpleAccount *account, const char *name)
1054{
1055        struct im_connection *ic = purple_ic_by_pa(account);
1056
1057        if (!g_slist_find_custom(ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp)) {
1058                ic->permit = g_slist_prepend(ic->permit, g_strdup(name));
1059        }
1060}
1061
1062static void prplcb_privacy_permit_removed(PurpleAccount *account, const char *name)
1063{
1064        struct im_connection *ic = purple_ic_by_pa(account);
1065        void *n;
1066
1067        n = g_slist_find_custom(ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp);
1068        ic->permit = g_slist_remove(ic->permit, n);
1069}
1070
1071static void prplcb_privacy_deny_added(PurpleAccount *account, const char *name)
1072{
1073        struct im_connection *ic = purple_ic_by_pa(account);
1074
1075        if (!g_slist_find_custom(ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp)) {
1076                ic->deny = g_slist_prepend(ic->deny, g_strdup(name));
1077        }
1078}
1079
1080static void prplcb_privacy_deny_removed(PurpleAccount *account, const char *name)
1081{
1082        struct im_connection *ic = purple_ic_by_pa(account);
1083        void *n;
1084
1085        n = g_slist_find_custom(ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp);
1086        ic->deny = g_slist_remove(ic->deny, n);
1087}
1088
1089static PurplePrivacyUiOps bee_privacy_uiops =
1090{
1091        prplcb_privacy_permit_added,
1092        prplcb_privacy_permit_removed,
1093        prplcb_privacy_deny_added,
1094        prplcb_privacy_deny_removed,
1095};
1096
1097static void prplcb_debug_print(PurpleDebugLevel level, const char *category, const char *arg_s)
1098{
1099        fprintf(stderr, "DEBUG %s: %s", category, arg_s);
1100}
1101
1102static PurpleDebugUiOps bee_debug_uiops =
1103{
1104        prplcb_debug_print,
1105};
1106
1107static guint prplcb_ev_timeout_add(guint interval, GSourceFunc func, gpointer udata)
1108{
1109        return b_timeout_add(interval, (b_event_handler) func, udata);
1110}
1111
1112static guint prplcb_ev_input_add(int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata)
1113{
1114        return b_input_add(fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata);
1115}
1116
1117static gboolean prplcb_ev_remove(guint id)
1118{
1119        b_event_remove((gint) id);
1120        return TRUE;
1121}
1122
1123static PurpleEventLoopUiOps glib_eventloops =
1124{
1125        prplcb_ev_timeout_add,
1126        prplcb_ev_remove,
1127        prplcb_ev_input_add,
1128        prplcb_ev_remove,
1129};
1130
1131static void *prplcb_notify_email(PurpleConnection *gc, const char *subject, const char *from,
1132                                 const char *to, const char *url)
1133{
1134        struct im_connection *ic = purple_ic_by_gc(gc);
1135
1136        imcb_log(ic, "Received e-mail from %s for %s: %s <%s>", from, to, subject, url);
1137
1138        return NULL;
1139}
1140
1141static void *prplcb_notify_userinfo(PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info)
1142{
1143        struct im_connection *ic = purple_ic_by_gc(gc);
1144        GString *info = g_string_new("");
1145        GList *l = purple_notify_user_info_get_entries(user_info);
1146        char *key;
1147        const char *value;
1148        int n;
1149
1150        while (l) {
1151                PurpleNotifyUserInfoEntry *e = l->data;
1152
1153                switch (purple_notify_user_info_entry_get_type(e)) {
1154                case PURPLE_NOTIFY_USER_INFO_ENTRY_PAIR:
1155                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_HEADER:
1156                        key = g_strdup(purple_notify_user_info_entry_get_label(e));
1157                        value = purple_notify_user_info_entry_get_value(e);
1158
1159                        if (key) {
1160                                strip_html(key);
1161                                g_string_append_printf(info, "%s: ", key);
1162
1163                                if (value) {
1164                                        n = strlen(value) - 1;
1165                                        while (g_ascii_isspace(value[n])) {
1166                                                n--;
1167                                        }
1168                                        g_string_append_len(info, value, n + 1);
1169                                }
1170                                g_string_append_c(info, '\n');
1171                                g_free(key);
1172                        }
1173
1174                        break;
1175                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK:
1176                        g_string_append(info, "------------------------\n");
1177                        break;
1178                }
1179
1180                l = l->next;
1181        }
1182
1183        imcb_log(ic, "User %s info:\n%s", who, info->str);
1184        g_string_free(info, TRUE);
1185
1186        return NULL;
1187}
1188
1189static PurpleNotifyUiOps bee_notify_uiops =
1190{
1191        NULL,
1192        prplcb_notify_email,
1193        NULL,
1194        NULL,
1195        NULL,
1196        NULL,
1197        prplcb_notify_userinfo,
1198};
1199
1200static void *prplcb_account_request_authorize(PurpleAccount *account, const char *remote_user,
1201                                              const char *id, const char *alias, const char *message, gboolean on_list,
1202                                              PurpleAccountRequestAuthorizationCb authorize_cb,
1203                                              PurpleAccountRequestAuthorizationCb deny_cb, void *user_data)
1204{
1205        struct im_connection *ic = purple_ic_by_pa(account);
1206        char *q;
1207
1208        if (alias) {
1209                q = g_strdup_printf("%s (%s) wants to add you to his/her contact "
1210                                    "list. (%s)", alias, remote_user, message);
1211        } else {
1212                q = g_strdup_printf("%s wants to add you to his/her contact "
1213                                    "list. (%s)", remote_user, message);
1214        }
1215
1216        imcb_ask_with_free(ic, q, user_data, authorize_cb, deny_cb, NULL);
1217        g_free(q);
1218
1219        return NULL;
1220}
1221
1222static PurpleAccountUiOps bee_account_uiops =
1223{
1224        NULL,
1225        NULL,
1226        NULL,
1227        prplcb_account_request_authorize,
1228        NULL,
1229};
1230
1231extern PurpleXferUiOps bee_xfer_uiops;
1232
1233static void purple_ui_init()
1234{
1235        purple_connections_set_ui_ops(&bee_conn_uiops);
1236        purple_blist_set_ui_ops(&bee_blist_uiops);
1237        purple_conversations_set_ui_ops(&bee_conv_uiops);
1238        purple_request_set_ui_ops(&bee_request_uiops);
1239        purple_privacy_set_ui_ops(&bee_privacy_uiops);
1240        purple_notify_set_ui_ops(&bee_notify_uiops);
1241        purple_accounts_set_ui_ops(&bee_account_uiops);
1242        purple_xfers_set_ui_ops(&bee_xfer_uiops);
1243
1244        if (getenv("BITLBEE_DEBUG")) {
1245                purple_debug_set_ui_ops(&bee_debug_uiops);
1246        }
1247}
1248
1249void purple_initmodule()
1250{
1251        struct prpl funcs;
1252        GList *prots;
1253        GString *help;
1254        char *dir;
1255
1256        if (B_EV_IO_READ != PURPLE_INPUT_READ ||
1257            B_EV_IO_WRITE != PURPLE_INPUT_WRITE) {
1258                /* FIXME FIXME FIXME FIXME FIXME :-) */
1259                exit(1);
1260        }
1261
1262        dir = g_strdup_printf("%s/purple", global.conf->configdir);
1263        purple_util_set_user_dir(dir);
1264        g_free(dir);
1265
1266        purple_debug_set_enabled(FALSE);
1267        purple_core_set_ui_ops(&bee_core_uiops);
1268        purple_eventloop_set_ui_ops(&glib_eventloops);
1269        if (!purple_core_init("BitlBee")) {
1270                /* Initializing the core failed. Terminate. */
1271                fprintf(stderr, "libpurple initialization failed.\n");
1272                abort();
1273        }
1274
1275        if (proxytype != PROXY_NONE) {
1276                PurpleProxyInfo *pi = purple_global_proxy_get_info();
1277                switch (proxytype) {
1278                case PROXY_SOCKS4:
1279                        purple_proxy_info_set_type(pi, PURPLE_PROXY_SOCKS4);
1280                        break;
1281                case PROXY_SOCKS5:
1282                        purple_proxy_info_set_type(pi, PURPLE_PROXY_SOCKS5);
1283                        break;
1284                case PROXY_HTTP:
1285                        purple_proxy_info_set_type(pi, PURPLE_PROXY_HTTP);
1286                        break;
1287                }
1288                purple_proxy_info_set_host(pi, proxyhost);
1289                purple_proxy_info_set_port(pi, proxyport);
1290                purple_proxy_info_set_username(pi, proxyuser);
1291                purple_proxy_info_set_password(pi, proxypass);
1292        }
1293
1294        purple_set_blist(purple_blist_new());
1295
1296        /* No, really. So far there were ui_ops for everything, but now suddenly
1297           one needs to use signals for typing notification stuff. :-( */
1298        purple_signal_connect(purple_conversations_get_handle(), "buddy-typing",
1299                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1300        purple_signal_connect(purple_conversations_get_handle(), "buddy-typed",
1301                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1302        purple_signal_connect(purple_conversations_get_handle(), "buddy-typing-stopped",
1303                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1304
1305        memset(&funcs, 0, sizeof(funcs));
1306        funcs.login = purple_login;
1307        funcs.init = purple_init;
1308        funcs.logout = purple_logout;
1309        funcs.buddy_msg = purple_buddy_msg;
1310        funcs.away_states = purple_away_states;
1311        funcs.set_away = purple_set_away;
1312        funcs.add_buddy = purple_add_buddy;
1313        funcs.remove_buddy = purple_remove_buddy;
1314        funcs.add_permit = purple_add_permit;
1315        funcs.add_deny = purple_add_deny;
1316        funcs.rem_permit = purple_rem_permit;
1317        funcs.rem_deny = purple_rem_deny;
1318        funcs.get_info = purple_get_info;
1319        funcs.keepalive = purple_keepalive;
1320        funcs.send_typing = purple_send_typing;
1321        funcs.handle_cmp = g_strcasecmp;
1322        /* TODO(wilmer): Set these only for protocols that support them? */
1323        funcs.chat_msg = purple_chat_msg;
1324        funcs.chat_with = purple_chat_with;
1325        funcs.chat_invite = purple_chat_invite;
1326        funcs.chat_kick = purple_chat_kick;
1327        funcs.chat_leave = purple_chat_leave;
1328        funcs.chat_join = purple_chat_join;
1329        funcs.transfer_request = purple_transfer_request;
1330
1331        help = g_string_new("BitlBee libpurple module supports the following IM protocols:\n");
1332
1333        /* Add a protocol entry to BitlBee's structures for every protocol
1334           supported by this libpurple instance. */
1335        for (prots = purple_plugins_get_protocols(); prots; prots = prots->next) {
1336                PurplePlugin *prot = prots->data;
1337                struct prpl *ret;
1338
1339                /* If we already have this one (as a native module), don't
1340                   add a libpurple duplicate. */
1341                if (find_protocol(prot->info->id)) {
1342                        continue;
1343                }
1344
1345                ret = g_memdup(&funcs, sizeof(funcs));
1346                ret->name = ret->data = prot->info->id;
1347                if (strncmp(ret->name, "prpl-", 5) == 0) {
1348                        ret->name += 5;
1349                }
1350                register_protocol(ret);
1351
1352                g_string_append_printf(help, "\n* %s (%s)", ret->name, prot->info->name);
1353
1354                /* libpurple doesn't define a protocol called OSCAR, but we
1355                   need it to be compatible with normal BitlBee. */
1356                if (g_strcasecmp(prot->info->id, "prpl-aim") == 0) {
1357                        ret = g_memdup(&funcs, sizeof(funcs));
1358                        ret->name = "oscar";
1359                        ret->data = prot->info->id;
1360                        register_protocol(ret);
1361                }
1362        }
1363
1364        g_string_append(help, "\n\nFor used protocols, more information about available "
1365                        "settings can be found using \x02help purple <protocol name>\x02 "
1366                        "(create an account using that protocol first!)");
1367
1368        /* Add a simple dynamically-generated help item listing all
1369           the supported protocols. */
1370        help_add_mem(&global.help, "purple", help->str);
1371        g_string_free(help, TRUE);
1372}
Note: See TracBrowser for help on using the repository browser.