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