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