1 /*
2  * purple
3  *
4  * Purple is the legal property of its developers, whose names are too numerous
5  * to list here.  Please refer to the COPYRIGHT file distributed with this
6  * source distribution.
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
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
21  */
22 #include "internal.h"
23 #include "blist.h"
24 #include "cmds.h"
25 #include "conversation.h"
26 #include "dbus-maybe.h"
27 #include "debug.h"
28 #include "glibcompat.h"
29 #include "imgstore.h"
30 #include "notify.h"
31 #include "prefs.h"
32 #include "prpl.h"
33 #include "request.h"
34 #include "signals.h"
35 #include "util.h"
36 
37 #define SEND_TYPED_TIMEOUT_SECONDS 5
38 
39 static GList *conversations = NULL;
40 static GList *ims = NULL;
41 static GList *chats = NULL;
42 static PurpleConversationUiOps *default_ops = NULL;
43 
44 /**
45  * A hash table used for efficient lookups of conversations by name.
46  * struct _purple_hconv => PurpleConversation*
47  */
48 static GHashTable *conversation_cache = NULL;
49 
50 struct _purple_hconv {
51 	PurpleConversationType type;
52 	char *name;
53 	const PurpleAccount *account;
54 };
55 
_purple_conversations_hconv_hash(struct _purple_hconv * hc)56 static guint _purple_conversations_hconv_hash(struct _purple_hconv *hc)
57 {
58 	return g_str_hash(hc->name) ^ hc->type ^ g_direct_hash(hc->account);
59 }
60 
_purple_conversations_hconv_equal(struct _purple_hconv * hc1,struct _purple_hconv * hc2)61 static guint _purple_conversations_hconv_equal(struct _purple_hconv *hc1, struct _purple_hconv *hc2)
62 {
63 	return (hc1->type == hc2->type &&
64 	        hc1->account == hc2->account &&
65 	        purple_strequal(hc1->name, hc2->name));
66 }
67 
_purple_conversations_hconv_free_key(struct _purple_hconv * hc)68 static void _purple_conversations_hconv_free_key(struct _purple_hconv *hc)
69 {
70 	g_free(hc->name);
71 	g_free(hc);
72 }
73 
_purple_conversation_user_hash(gconstpointer data)74 static guint _purple_conversation_user_hash(gconstpointer data)
75 {
76 	const gchar *name = data;
77 	gchar *collated;
78 	guint hash;
79 
80 	collated = g_utf8_collate_key(name, -1);
81 	hash     = g_str_hash(collated);
82 	g_free(collated);
83 	return hash;
84 }
85 
_purple_conversation_user_equal(gconstpointer a,gconstpointer b)86 static gboolean _purple_conversation_user_equal(gconstpointer a, gconstpointer b)
87 {
88 	return !g_utf8_collate(a, b);
89 }
90 
91 void
purple_conversations_set_ui_ops(PurpleConversationUiOps * ops)92 purple_conversations_set_ui_ops(PurpleConversationUiOps *ops)
93 {
94 	default_ops = ops;
95 }
96 
97 static gboolean
reset_typing_cb(gpointer data)98 reset_typing_cb(gpointer data)
99 {
100 	PurpleConversation *c = (PurpleConversation *)data;
101 	PurpleConvIm *im;
102 
103 	im = PURPLE_CONV_IM(c);
104 
105 	purple_conv_im_set_typing_state(im, PURPLE_NOT_TYPING);
106 	purple_conv_im_stop_typing_timeout(im);
107 
108 	return FALSE;
109 }
110 
111 static gboolean
send_typed_cb(gpointer data)112 send_typed_cb(gpointer data)
113 {
114 	PurpleConversation *conv = (PurpleConversation *)data;
115 	PurpleConnection *gc;
116 	const char *name;
117 
118 	g_return_val_if_fail(conv != NULL, FALSE);
119 
120 	gc   = purple_conversation_get_gc(conv);
121 	name = purple_conversation_get_name(conv);
122 
123 	if (gc != NULL && name != NULL) {
124 		/* We set this to 1 so that PURPLE_TYPING will be sent
125 		 * if the Purple user types anything else.
126 		 */
127 		purple_conv_im_set_type_again(PURPLE_CONV_IM(conv), 1);
128 
129 		serv_send_typing(gc, name, PURPLE_TYPED);
130 
131 		purple_debug(PURPLE_DEBUG_MISC, "conversation", "typed...\n");
132 	}
133 
134 	return FALSE;
135 }
136 
137 static void
common_send(PurpleConversation * conv,const char * message,PurpleMessageFlags msgflags)138 common_send(PurpleConversation *conv, const char *message, PurpleMessageFlags msgflags)
139 {
140 	PurpleConversationType type;
141 	PurpleAccount *account;
142 	PurpleConnection *gc;
143 	char *displayed = NULL, *sent = NULL;
144 	int err = 0;
145 
146 	if (*message == '\0')
147 		return;
148 
149 	account = purple_conversation_get_account(conv);
150 	gc = purple_conversation_get_gc(conv);
151 
152 	g_return_if_fail(account != NULL);
153 	g_return_if_fail(gc != NULL);
154 
155 	type = purple_conversation_get_type(conv);
156 
157 	/* Always linkfy the text for display, unless we're
158 	 * explicitly asked to do otheriwse*/
159 	if (!(msgflags & PURPLE_MESSAGE_INVISIBLE)) {
160 		if(msgflags & PURPLE_MESSAGE_NO_LINKIFY)
161 			displayed = g_strdup(message);
162 		else
163 			displayed = purple_markup_linkify(message);
164 	}
165 
166 	if (displayed && (conv->features & PURPLE_CONNECTION_HTML) &&
167 		!(msgflags & PURPLE_MESSAGE_RAW)) {
168 		sent = g_strdup(displayed);
169 	} else
170 		sent = g_strdup(message);
171 
172 	msgflags |= PURPLE_MESSAGE_SEND;
173 
174 	if (type == PURPLE_CONV_TYPE_IM) {
175 		PurpleConvIm *im = PURPLE_CONV_IM(conv);
176 
177 		purple_signal_emit(purple_conversations_get_handle(), "sending-im-msg",
178 						 account,
179 						 purple_conversation_get_name(conv), &sent);
180 
181 		if (sent != NULL && sent[0] != '\0') {
182 
183 			err = serv_send_im(gc, purple_conversation_get_name(conv),
184 			                   sent, msgflags);
185 
186 			if ((err > 0) && (displayed != NULL))
187 				purple_conv_im_write(im, NULL, displayed, msgflags, time(NULL));
188 
189 			purple_signal_emit(purple_conversations_get_handle(), "sent-im-msg",
190 							 account,
191 							 purple_conversation_get_name(conv), sent);
192 		}
193 	}
194 	else {
195 		purple_signal_emit(purple_conversations_get_handle(), "sending-chat-msg",
196 						 account, &sent,
197 						 purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)));
198 
199 		if (sent != NULL && sent[0] != '\0') {
200 			err = serv_chat_send(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), sent, msgflags);
201 
202 			purple_signal_emit(purple_conversations_get_handle(), "sent-chat-msg",
203 							 account, sent,
204 							 purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)));
205 		}
206 	}
207 
208 	if (err < 0) {
209 		const char *who;
210 		const char *msg;
211 
212 		who = purple_conversation_get_name(conv);
213 
214 		if (err == -E2BIG) {
215 			msg = _("Unable to send message: The message is too large.");
216 
217 			if (!purple_conv_present_error(who, account, msg)) {
218 				char *msg2 = g_strdup_printf(_("Unable to send message to %s."), who);
219 				purple_notify_error(gc, NULL, msg2, _("The message is too large."));
220 				g_free(msg2);
221 			}
222 		}
223 		else if (err == -ENOTCONN) {
224 			purple_debug(PURPLE_DEBUG_ERROR, "conversation",
225 					   "Not yet connected.\n");
226 		}
227 		else {
228 			msg = _("Unable to send message.");
229 
230 			if (!purple_conv_present_error(who, account, msg)) {
231 				char *msg2 = g_strdup_printf(_("Unable to send message to %s."), who);
232 				purple_notify_error(gc, NULL, msg2, NULL);
233 				g_free(msg2);
234 			}
235 		}
236 	}
237 
238 	g_free(displayed);
239 	g_free(sent);
240 }
241 
242 static void
open_log(PurpleConversation * conv)243 open_log(PurpleConversation *conv)
244 {
245 	conv->logs = g_list_append(NULL, purple_log_new(conv->type == PURPLE_CONV_TYPE_CHAT ? PURPLE_LOG_CHAT :
246 							   PURPLE_LOG_IM, conv->name, conv->account,
247 							   conv, time(NULL), NULL));
248 }
249 
250 /* Functions that deal with PurpleConvMessage */
251 
252 static void
add_message_to_history(PurpleConversation * conv,const char * who,const char * alias,const char * message,PurpleMessageFlags flags,time_t when)253 add_message_to_history(PurpleConversation *conv, const char *who, const char *alias,
254 		const char *message, PurpleMessageFlags flags, time_t when)
255 {
256 	PurpleConvMessage *msg;
257 	PurpleConnection *gc;
258 
259 	gc = purple_account_get_connection(conv->account);
260 
261 	if (flags & PURPLE_MESSAGE_SEND) {
262 		const char *me = NULL;
263 		if (gc)
264 			me = purple_connection_get_display_name(gc);
265 		if (!me)
266 			me = conv->account->username;
267 		who = me;
268 	}
269 
270 	msg = g_new0(PurpleConvMessage, 1);
271 	PURPLE_DBUS_REGISTER_POINTER(msg, PurpleConvMessage);
272 	msg->who = g_strdup(who);
273 	msg->alias = g_strdup(alias);
274 	msg->flags = flags;
275 	msg->what = g_strdup(message);
276 	msg->when = when;
277 	msg->conv = conv;
278 
279 	conv->message_history = g_list_prepend(conv->message_history, msg);
280 }
281 
282 static void
free_conv_message(PurpleConvMessage * msg)283 free_conv_message(PurpleConvMessage *msg)
284 {
285 	g_free(msg->who);
286 	g_free(msg->alias);
287 	g_free(msg->what);
288 	PURPLE_DBUS_UNREGISTER_POINTER(msg);
289 	g_free(msg);
290 }
291 
292 /**************************************************************************
293  * Conversation API
294  **************************************************************************/
295 static void
purple_conversation_chat_cleanup_for_rejoin(PurpleConversation * conv)296 purple_conversation_chat_cleanup_for_rejoin(PurpleConversation *conv)
297 {
298 	const char *disp;
299 	PurpleAccount *account;
300 	PurpleConnection *gc;
301 
302 	account = purple_conversation_get_account(conv);
303 
304 	purple_conversation_close_logs(conv);
305 	open_log(conv);
306 
307 	gc = purple_account_get_connection(account);
308 
309 	if ((disp = purple_connection_get_display_name(gc)) != NULL)
310 		purple_conv_chat_set_nick(PURPLE_CONV_CHAT(conv), disp);
311 	else
312 	{
313 		purple_conv_chat_set_nick(PURPLE_CONV_CHAT(conv),
314 								purple_account_get_username(account));
315 	}
316 
317 	purple_conv_chat_clear_users(PURPLE_CONV_CHAT(conv));
318 	purple_conv_chat_set_topic(PURPLE_CONV_CHAT(conv), NULL, NULL);
319 	PURPLE_CONV_CHAT(conv)->left = FALSE;
320 
321 	purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
322 }
323 
324 /* Meaningful name in case a UI doesn't actually honour the INVISIBLE flag */
325 #define TRANSIENT_GROUP_NAME _("(internal) Temporary IM peers")
326 
327 static void
purple_conversation_ensure_buddy(PurpleAccount * account,PurpleConnection * gc,const char * name)328 purple_conversation_ensure_buddy(PurpleAccount *account, PurpleConnection *gc,
329 				 const char *name)
330 {
331 	PurplePluginProtocolInfo *prpl_info;
332 	PurpleBuddy *buddy;
333 	PurpleGroup *group;
334 
335 	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
336 
337 	if ((prpl_info->options & OPT_PROTO_TRANSIENT_BUDDIES) &&
338 	    !purple_find_buddy(account, name)) {
339 		group = purple_find_group(TRANSIENT_GROUP_NAME);
340 		if (!group) {
341 			group = purple_group_new(TRANSIENT_GROUP_NAME);
342 			purple_blist_node_set_flags(PURPLE_BLIST_NODE(group),
343 						    PURPLE_BLIST_NODE_FLAG_NO_SAVE | PURPLE_BLIST_NODE_FLAG_INVISIBLE);
344 			purple_blist_add_group(group, NULL);
345 		}
346 		buddy = purple_buddy_new(account, name, NULL);
347 		purple_blist_node_set_flags(PURPLE_BLIST_NODE(buddy),
348 					    PURPLE_BLIST_NODE_FLAG_NO_SAVE | PURPLE_BLIST_NODE_FLAG_INVISIBLE);
349 
350 		purple_blist_add_buddy(buddy, NULL, group, NULL);
351 		purple_account_add_buddy(account, buddy);
352 	}
353 }
354 
355 static void
purple_conversation_release_buddy(PurpleAccount * account,const char * name)356 purple_conversation_release_buddy(PurpleAccount *account, const char *name)
357 {
358 	PurpleBuddy *buddy;
359 	PurpleGroup *group;
360 
361 	group = purple_find_group(TRANSIENT_GROUP_NAME);
362 	if (!group)
363 		return;
364 
365 	buddy = purple_find_buddy_in_group(account, name, group);
366 	if (!buddy)
367 		return;
368 
369 	purple_account_remove_buddy(account, buddy, group);
370 	purple_blist_remove_buddy(buddy);
371 }
372 
373 PurpleConversation *
purple_conversation_new(PurpleConversationType type,PurpleAccount * account,const char * name)374 purple_conversation_new(PurpleConversationType type, PurpleAccount *account,
375 					  const char *name)
376 {
377 	PurpleConversation *conv;
378 	PurpleConnection *gc;
379 	PurpleConversationUiOps *ops;
380 	struct _purple_hconv *hc;
381 
382 	g_return_val_if_fail(type    != PURPLE_CONV_TYPE_UNKNOWN, NULL);
383 	g_return_val_if_fail(account != NULL, NULL);
384 	g_return_val_if_fail(name    != NULL, NULL);
385 
386 	/* Check if this conversation already exists. */
387 	if ((conv = purple_find_conversation_with_account(type, name, account)) != NULL)
388 	{
389 		if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
390 				!purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv))) {
391 			purple_debug_warning("conversation", "Trying to create multiple "
392 					"chats (%s) with the same name is deprecated and will be "
393 					"removed in libpurple 3.0.0", name);
394 		}
395 
396 		/*
397 		 * This hack is necessary because some prpls have unnamed chats
398 		 * that all use the same name.  A PurpleConversation for one of those
399 		 * is only ever re-used if the user has left, so calls to
400 		 * purple_conversation_new need to fall-through to creating a new
401 		 * chat.
402 		 * TODO 3.0.0: Remove this workaround and mandate unique names.
403 		 */
404 		if (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_CHAT ||
405 				purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv)))
406 		{
407 			if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT)
408 				purple_conversation_chat_cleanup_for_rejoin(conv);
409 
410 			return conv;
411 		}
412 	}
413 
414 	gc = purple_account_get_connection(account);
415 	g_return_val_if_fail(gc != NULL, NULL);
416 
417 	conv = g_new0(PurpleConversation, 1);
418 	PURPLE_DBUS_REGISTER_POINTER(conv, PurpleConversation);
419 
420 	conv->type         = type;
421 	conv->account      = account;
422 	conv->name         = g_strdup(name);
423 	conv->title        = g_strdup(name);
424 	conv->data         = g_hash_table_new_full(g_str_hash, g_str_equal,
425 											   g_free, NULL);
426 	/* copy features from the connection. */
427 	conv->features = gc->flags;
428 
429 	if (type == PURPLE_CONV_TYPE_IM)
430 	{
431 		PurpleBuddyIcon *icon;
432 		conv->u.im = g_new0(PurpleConvIm, 1);
433 		conv->u.im->conv = conv;
434 		PURPLE_DBUS_REGISTER_POINTER(conv->u.im, PurpleConvIm);
435 
436 		ims = g_list_prepend(ims, conv);
437 		if ((icon = purple_buddy_icons_find(account, name)))
438 		{
439 			purple_conv_im_set_icon(conv->u.im, icon);
440 			/* purple_conv_im_set_icon refs the icon. */
441 			purple_buddy_icon_unref(icon);
442 		}
443 
444 		if (purple_prefs_get_bool("/purple/logging/log_ims"))
445 		{
446 			purple_conversation_set_logging(conv, TRUE);
447 			open_log(conv);
448 		}
449 
450 		purple_conversation_ensure_buddy(account, gc, name);
451 	}
452 	else if (type == PURPLE_CONV_TYPE_CHAT)
453 	{
454 		const char *disp;
455 
456 		conv->u.chat = g_new0(PurpleConvChat, 1);
457 		conv->u.chat->conv = conv;
458 		conv->u.chat->users = g_hash_table_new_full(_purple_conversation_user_hash,
459 				_purple_conversation_user_equal, g_free, NULL);
460 		PURPLE_DBUS_REGISTER_POINTER(conv->u.chat, PurpleConvChat);
461 
462 		chats = g_list_prepend(chats, conv);
463 
464 		if ((disp = purple_connection_get_display_name(account->gc)))
465 			purple_conv_chat_set_nick(conv->u.chat, disp);
466 		else
467 			purple_conv_chat_set_nick(conv->u.chat,
468 									purple_account_get_username(account));
469 
470 		if (purple_prefs_get_bool("/purple/logging/log_chats"))
471 		{
472 			purple_conversation_set_logging(conv, TRUE);
473 			open_log(conv);
474 		}
475 	}
476 
477 	conversations = g_list_prepend(conversations, conv);
478 
479 	hc = g_new(struct _purple_hconv, 1);
480 	hc->name = g_strdup(purple_normalize(account, conv->name));
481 	hc->account = account;
482 	hc->type = type;
483 
484 	g_hash_table_insert(conversation_cache, hc, conv);
485 
486 	/* Auto-set the title. */
487 	purple_conversation_autoset_title(conv);
488 
489 	/* Don't move this.. it needs to be one of the last things done otherwise
490 	 * it causes mysterious crashes on my system.
491 	 *  -- Gary
492 	 */
493 	ops  = conv->ui_ops = default_ops;
494 	if (ops != NULL && ops->create_conversation != NULL)
495 		ops->create_conversation(conv);
496 
497 	purple_signal_emit(purple_conversations_get_handle(),
498 					 "conversation-created", conv);
499 
500 	return conv;
501 }
502 
503 void
purple_conversation_destroy(PurpleConversation * conv)504 purple_conversation_destroy(PurpleConversation *conv)
505 {
506 	PurplePluginProtocolInfo *prpl_info = NULL;
507 	PurpleConversationUiOps *ops;
508 	PurpleConnection *gc;
509 	const char *name;
510 	struct _purple_hconv hc;
511 
512 	g_return_if_fail(conv != NULL);
513 
514 	purple_request_close_with_handle(conv);
515 
516 	ops  = purple_conversation_get_ui_ops(conv);
517 	gc   = purple_conversation_get_gc(conv);
518 	name = purple_conversation_get_name(conv);
519 
520 	if (gc != NULL)
521 	{
522 		/* Still connected */
523 		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
524 
525 		if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM)
526 		{
527 			if (purple_prefs_get_bool("/purple/conversations/im/send_typing"))
528 				serv_send_typing(gc, name, PURPLE_NOT_TYPING);
529 
530 			if (gc && prpl_info->convo_closed != NULL)
531 				prpl_info->convo_closed(gc, name);
532 
533 			purple_conversation_release_buddy(conv->account, conv->name);
534 		}
535 		else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT)
536 		{
537 			int chat_id = purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv));
538 #if 0
539 			/*
540 			 * This is unfortunately necessary, because calling
541 			 * serv_chat_leave() calls this purple_conversation_destroy(),
542 			 * which leads to two calls here.. We can't just return after
543 			 * this, because then it'll return on the next pass. So, since
544 			 * serv_got_chat_left(), which is eventually called from the
545 			 * prpl that serv_chat_leave() calls, removes this conversation
546 			 * from the gc's buddy_chats list, we're going to check to see
547 			 * if this exists in the list. If so, we want to return after
548 			 * calling this, because it'll be called again. If not, fall
549 			 * through, because it'll have already been removed, and we'd
550 			 * be on the 2nd pass.
551 			 *
552 			 * Long paragraph. <-- Short sentence.
553 			 *
554 			 *   -- ChipX86
555 			 */
556 
557 			if (gc && g_slist_find(gc->buddy_chats, conv) != NULL) {
558 				serv_chat_leave(gc, chat_id);
559 
560 				return;
561 			}
562 #endif
563 			/*
564 			 * Instead of all of that, lets just close the window when
565 			 * the user tells us to, and let the prpl deal with the
566 			 * internals on it's own time. Don't do this if the prpl already
567 			 * knows it left the chat.
568 			 */
569 			if (!purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv)))
570 				serv_chat_leave(gc, chat_id);
571 
572 			/*
573 			 * If they didn't call serv_got_chat_left by now, it's too late.
574 			 * So we better do it for them before we destroy the thing.
575 			 */
576 			if (!purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv)))
577 				serv_got_chat_left(gc, chat_id);
578 		}
579 	}
580 
581 	/* remove from conversations and im/chats lists prior to emit */
582 	conversations = g_list_remove(conversations, conv);
583 
584 	if(conv->type==PURPLE_CONV_TYPE_IM)
585 		ims = g_list_remove(ims, conv);
586 	else if(conv->type==PURPLE_CONV_TYPE_CHAT)
587 		chats = g_list_remove(chats, conv);
588 
589 	hc.name = (gchar *)purple_normalize(conv->account, conv->name);
590 	hc.account = conv->account;
591 	hc.type = conv->type;
592 
593 	g_hash_table_remove(conversation_cache, &hc);
594 
595 	purple_signal_emit(purple_conversations_get_handle(),
596 					 "deleting-conversation", conv);
597 
598 	g_free(conv->name);
599 	g_free(conv->title);
600 
601 	conv->name = NULL;
602 	conv->title = NULL;
603 
604 	if (conv->type == PURPLE_CONV_TYPE_IM) {
605 		purple_conv_im_stop_typing_timeout(conv->u.im);
606 		purple_conv_im_stop_send_typed_timeout(conv->u.im);
607 
608 		purple_buddy_icon_unref(conv->u.im->icon);
609 		conv->u.im->icon = NULL;
610 
611 		PURPLE_DBUS_UNREGISTER_POINTER(conv->u.im);
612 		g_free(conv->u.im);
613 		conv->u.im = NULL;
614 	}
615 	else if (conv->type == PURPLE_CONV_TYPE_CHAT) {
616 		g_hash_table_destroy(conv->u.chat->users);
617 		conv->u.chat->users = NULL;
618 
619 		g_list_free_full(conv->u.chat->in_room,
620 		                 (GDestroyNotify)purple_conv_chat_cb_destroy);
621 
622 		g_list_free_full(conv->u.chat->ignored, (GDestroyNotify)g_free);
623 
624 		conv->u.chat->in_room = NULL;
625 		conv->u.chat->ignored = NULL;
626 
627 		g_free(conv->u.chat->who);
628 		conv->u.chat->who = NULL;
629 
630 		g_free(conv->u.chat->topic);
631 		conv->u.chat->topic = NULL;
632 
633 		g_free(conv->u.chat->nick);
634 
635 		PURPLE_DBUS_UNREGISTER_POINTER(conv->u.chat);
636 		g_free(conv->u.chat);
637 		conv->u.chat = NULL;
638 	}
639 
640 	g_hash_table_destroy(conv->data);
641 	conv->data = NULL;
642 
643 	if (ops != NULL && ops->destroy_conversation != NULL)
644 		ops->destroy_conversation(conv);
645 	conv->ui_data = NULL;
646 
647 	purple_conversation_close_logs(conv);
648 
649 	purple_conversation_clear_message_history(conv);
650 
651 	PURPLE_DBUS_UNREGISTER_POINTER(conv);
652 	g_free(conv);
653 	conv = NULL;
654 }
655 
656 
657 void
purple_conversation_present(PurpleConversation * conv)658 purple_conversation_present(PurpleConversation *conv) {
659 	PurpleConversationUiOps *ops;
660 
661 	g_return_if_fail(conv != NULL);
662 
663 	ops = purple_conversation_get_ui_ops(conv);
664 	if(ops && ops->present)
665 		ops->present(conv);
666 }
667 
668 
669 void
purple_conversation_set_features(PurpleConversation * conv,PurpleConnectionFlags features)670 purple_conversation_set_features(PurpleConversation *conv, PurpleConnectionFlags features)
671 {
672 	g_return_if_fail(conv != NULL);
673 
674 	conv->features = features;
675 
676 	purple_conversation_update(conv, PURPLE_CONV_UPDATE_FEATURES);
677 }
678 
679 
680 PurpleConnectionFlags
purple_conversation_get_features(PurpleConversation * conv)681 purple_conversation_get_features(PurpleConversation *conv)
682 {
683 	g_return_val_if_fail(conv != NULL, 0);
684 	return conv->features;
685 }
686 
687 
688 PurpleConversationType
purple_conversation_get_type(const PurpleConversation * conv)689 purple_conversation_get_type(const PurpleConversation *conv)
690 {
691 	g_return_val_if_fail(conv != NULL, PURPLE_CONV_TYPE_UNKNOWN);
692 
693 	return conv->type;
694 }
695 
696 void
purple_conversation_set_ui_ops(PurpleConversation * conv,PurpleConversationUiOps * ops)697 purple_conversation_set_ui_ops(PurpleConversation *conv,
698 							 PurpleConversationUiOps *ops)
699 {
700 	g_return_if_fail(conv != NULL);
701 
702 	if (conv->ui_ops == ops)
703 		return;
704 
705 	if (conv->ui_ops != NULL && conv->ui_ops->destroy_conversation != NULL)
706 		conv->ui_ops->destroy_conversation(conv);
707 
708 	conv->ui_data = NULL;
709 
710 	conv->ui_ops = ops;
711 }
712 
713 PurpleConversationUiOps *
purple_conversation_get_ui_ops(const PurpleConversation * conv)714 purple_conversation_get_ui_ops(const PurpleConversation *conv)
715 {
716 	g_return_val_if_fail(conv != NULL, NULL);
717 
718 	return conv->ui_ops;
719 }
720 
721 void
purple_conversation_set_account(PurpleConversation * conv,PurpleAccount * account)722 purple_conversation_set_account(PurpleConversation *conv, PurpleAccount *account)
723 {
724 	g_return_if_fail(conv != NULL);
725 
726 	if (account == purple_conversation_get_account(conv))
727 		return;
728 
729 	conv->account = account;
730 
731 	purple_conversation_update(conv, PURPLE_CONV_UPDATE_ACCOUNT);
732 }
733 
734 PurpleAccount *
purple_conversation_get_account(const PurpleConversation * conv)735 purple_conversation_get_account(const PurpleConversation *conv)
736 {
737 	g_return_val_if_fail(conv != NULL, NULL);
738 
739 	return conv->account;
740 }
741 
742 PurpleConnection *
purple_conversation_get_gc(const PurpleConversation * conv)743 purple_conversation_get_gc(const PurpleConversation *conv)
744 {
745 	PurpleAccount *account;
746 
747 	g_return_val_if_fail(conv != NULL, NULL);
748 
749 	account = purple_conversation_get_account(conv);
750 
751 	if (account == NULL)
752 		return NULL;
753 
754 	return account->gc;
755 }
756 
757 void
purple_conversation_set_title(PurpleConversation * conv,const char * title)758 purple_conversation_set_title(PurpleConversation *conv, const char *title)
759 {
760 	g_return_if_fail(conv  != NULL);
761 	g_return_if_fail(title != NULL);
762 
763 	g_free(conv->title);
764 	conv->title = g_strdup(title);
765 
766 	purple_conversation_update(conv, PURPLE_CONV_UPDATE_TITLE);
767 }
768 
769 const char *
purple_conversation_get_title(const PurpleConversation * conv)770 purple_conversation_get_title(const PurpleConversation *conv)
771 {
772 	g_return_val_if_fail(conv != NULL, NULL);
773 
774 	return conv->title;
775 }
776 
777 void
purple_conversation_autoset_title(PurpleConversation * conv)778 purple_conversation_autoset_title(PurpleConversation *conv)
779 {
780 	PurpleAccount *account;
781 	PurpleBuddy *b;
782 	PurpleChat *chat;
783 	const char *text = NULL, *name;
784 
785 	g_return_if_fail(conv != NULL);
786 
787 	account = purple_conversation_get_account(conv);
788 	name = purple_conversation_get_name(conv);
789 
790 	if(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) {
791 		if(account && ((b = purple_find_buddy(account, name)) != NULL))
792 			text = purple_buddy_get_contact_alias(b);
793 	} else if(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
794 		if(account && ((chat = purple_blist_find_chat(account, name)) != NULL))
795 			text = purple_chat_get_name(chat);
796 	}
797 
798 
799 	if(text == NULL)
800 		text = name;
801 
802 	purple_conversation_set_title(conv, text);
803 }
804 
805 void
purple_conversation_foreach(void (* func)(PurpleConversation * conv))806 purple_conversation_foreach(void (*func)(PurpleConversation *conv))
807 {
808 	PurpleConversation *conv;
809 	GList *l;
810 
811 	g_return_if_fail(func != NULL);
812 
813 	for (l = purple_get_conversations(); l != NULL; l = l->next) {
814 		conv = (PurpleConversation *)l->data;
815 
816 		func(conv);
817 	}
818 }
819 
820 void
purple_conversation_set_name(PurpleConversation * conv,const char * name)821 purple_conversation_set_name(PurpleConversation *conv, const char *name)
822 {
823 	struct _purple_hconv *hc;
824 	g_return_if_fail(conv != NULL);
825 
826 	hc = g_new(struct _purple_hconv, 1);
827 	hc->type = conv->type;
828 	hc->account = conv->account;
829 	hc->name = (gchar *)purple_normalize(conv->account, conv->name);
830 
831 	g_hash_table_remove(conversation_cache, hc);
832 	g_free(conv->name);
833 
834 	conv->name = g_strdup(name);
835 	hc->name = g_strdup(purple_normalize(conv->account, conv->name));
836 	g_hash_table_insert(conversation_cache, hc, conv);
837 
838 	purple_conversation_autoset_title(conv);
839 }
840 
841 const char *
purple_conversation_get_name(const PurpleConversation * conv)842 purple_conversation_get_name(const PurpleConversation *conv)
843 {
844 	g_return_val_if_fail(conv != NULL, NULL);
845 
846 	return conv->name;
847 }
848 
849 void
purple_conversation_set_logging(PurpleConversation * conv,gboolean log)850 purple_conversation_set_logging(PurpleConversation *conv, gboolean log)
851 {
852 	g_return_if_fail(conv != NULL);
853 
854 	if (conv->logging != log)
855 	{
856 		conv->logging = log;
857 		purple_conversation_update(conv, PURPLE_CONV_UPDATE_LOGGING);
858 	}
859 }
860 
861 gboolean
purple_conversation_is_logging(const PurpleConversation * conv)862 purple_conversation_is_logging(const PurpleConversation *conv)
863 {
864 	g_return_val_if_fail(conv != NULL, FALSE);
865 
866 	return conv->logging;
867 }
868 
869 void
purple_conversation_close_logs(PurpleConversation * conv)870 purple_conversation_close_logs(PurpleConversation *conv)
871 {
872 	g_return_if_fail(conv != NULL);
873 
874 	g_list_free_full(conv->logs, (GDestroyNotify)purple_log_free);
875 	conv->logs = NULL;
876 }
877 
878 PurpleConvIm *
purple_conversation_get_im_data(const PurpleConversation * conv)879 purple_conversation_get_im_data(const PurpleConversation *conv)
880 {
881 	g_return_val_if_fail(conv != NULL, NULL);
882 
883 	if (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_IM)
884 		return NULL;
885 
886 	return conv->u.im;
887 }
888 
889 PurpleConvChat *
purple_conversation_get_chat_data(const PurpleConversation * conv)890 purple_conversation_get_chat_data(const PurpleConversation *conv)
891 {
892 	g_return_val_if_fail(conv != NULL, NULL);
893 
894 	if (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_CHAT)
895 		return NULL;
896 
897 	return conv->u.chat;
898 }
899 
900 void
purple_conversation_set_data(PurpleConversation * conv,const char * key,gpointer data)901 purple_conversation_set_data(PurpleConversation *conv, const char *key,
902 						   gpointer data)
903 {
904 	g_return_if_fail(conv != NULL);
905 	g_return_if_fail(key  != NULL);
906 
907 	g_hash_table_replace(conv->data, g_strdup(key), data);
908 }
909 
910 gpointer
purple_conversation_get_data(PurpleConversation * conv,const char * key)911 purple_conversation_get_data(PurpleConversation *conv, const char *key)
912 {
913 	g_return_val_if_fail(conv != NULL, NULL);
914 	g_return_val_if_fail(key  != NULL, NULL);
915 
916 	return g_hash_table_lookup(conv->data, key);
917 }
918 
919 GList *
purple_get_conversations(void)920 purple_get_conversations(void)
921 {
922 	return conversations;
923 }
924 
925 GList *
purple_get_ims(void)926 purple_get_ims(void)
927 {
928 	return ims;
929 }
930 
931 GList *
purple_get_chats(void)932 purple_get_chats(void)
933 {
934 	return chats;
935 }
936 
937 
938 PurpleConversation *
purple_find_conversation_with_account(PurpleConversationType type,const char * name,const PurpleAccount * account)939 purple_find_conversation_with_account(PurpleConversationType type,
940 									const char *name,
941 									const PurpleAccount *account)
942 {
943 	PurpleConversation *c = NULL;
944 	struct _purple_hconv hc;
945 
946 	g_return_val_if_fail(name != NULL, NULL);
947 
948 	hc.name = (gchar *)purple_normalize(account, name);
949 	hc.account = account;
950 	hc.type = type;
951 
952 	switch (type) {
953 		case PURPLE_CONV_TYPE_IM:
954 		case PURPLE_CONV_TYPE_CHAT:
955 			c = g_hash_table_lookup(conversation_cache, &hc);
956 			break;
957 		case PURPLE_CONV_TYPE_ANY:
958 			hc.type = PURPLE_CONV_TYPE_IM;
959 			c = g_hash_table_lookup(conversation_cache, &hc);
960 			if (!c) {
961 				hc.type = PURPLE_CONV_TYPE_CHAT;
962 				c = g_hash_table_lookup(conversation_cache, &hc);
963 			}
964 			break;
965 		default:
966 			g_return_val_if_reached(NULL);
967 	}
968 
969 	return c;
970 }
971 
972 void
purple_conversation_write(PurpleConversation * conv,const char * who,const char * message,PurpleMessageFlags flags,time_t mtime)973 purple_conversation_write(PurpleConversation *conv, const char *who,
974 						const char *message, PurpleMessageFlags flags,
975 						time_t mtime)
976 {
977 	PurplePluginProtocolInfo *prpl_info = NULL;
978 	PurpleConnection *gc = NULL;
979 	PurpleAccount *account;
980 	PurpleConversationUiOps *ops;
981 	char *server_alias = NULL;
982 	const char *alias;
983 	char *displayed = NULL;
984 	PurpleBuddy *b;
985 	int plugin_return;
986 	PurpleConversationType type;
987 	/* int logging_font_options = 0; */
988 
989 	g_return_if_fail(conv    != NULL);
990 	g_return_if_fail(message != NULL);
991 
992 	ops = purple_conversation_get_ui_ops(conv);
993 
994 	account = purple_conversation_get_account(conv);
995 	type = purple_conversation_get_type(conv);
996 
997 	if (account != NULL)
998 		gc = purple_account_get_connection(account);
999 
1000 	if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
1001 		(gc != NULL && !g_slist_find(gc->buddy_chats, conv)))
1002 		return;
1003 
1004 	if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM &&
1005 		!g_list_find(purple_get_conversations(), conv))
1006 		return;
1007 
1008 	displayed = g_strdup(message);
1009 
1010 	if (who == NULL || *who == '\0')
1011 		who = purple_conversation_get_name(conv);
1012 	alias = who;
1013 
1014 	plugin_return =
1015 		GPOINTER_TO_INT(purple_signal_emit_return_1(
1016 			purple_conversations_get_handle(),
1017 			(type == PURPLE_CONV_TYPE_IM ? "writing-im-msg" : "writing-chat-msg"),
1018 			account, who, &displayed, conv, flags));
1019 
1020 	if (displayed == NULL)
1021 		return;
1022 
1023 	if (plugin_return) {
1024 		g_free(displayed);
1025 		return;
1026 	}
1027 
1028 	if (account != NULL) {
1029 		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(account)));
1030 
1031 		if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM ||
1032 			!(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) {
1033 
1034 			if (flags & PURPLE_MESSAGE_SEND) {
1035 				b = purple_find_buddy(account,
1036 							purple_account_get_username(account));
1037 
1038 				if (purple_account_get_alias(account) != NULL)
1039 					alias = account->alias;
1040 				else if (b != NULL && !purple_strequal(purple_buddy_get_name(b), purple_buddy_get_contact_alias(b)))
1041 					alias = purple_buddy_get_contact_alias(b);
1042 				else if (purple_connection_get_display_name(gc) != NULL)
1043 					alias = purple_connection_get_display_name(gc);
1044 				else
1045 					alias = purple_account_get_username(account);
1046 			}
1047 			else
1048 			{
1049 				b = purple_find_buddy(account, who);
1050 
1051 				if (b != NULL)
1052 					alias = purple_buddy_get_contact_alias(b);
1053 			}
1054 		}
1055 		if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
1056 		    alias == who && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_cb_alias)) {
1057 			int id = purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv));
1058 			server_alias = prpl_info->get_cb_alias(gc, id, who);
1059 			if (server_alias)
1060 				alias = server_alias;
1061 		}
1062 	}
1063 
1064 	if (!(flags & PURPLE_MESSAGE_NO_LOG) && purple_conversation_is_logging(conv)) {
1065 		GList *log;
1066 
1067 		if (conv->logs == NULL)
1068 			open_log(conv);
1069 
1070 		log = conv->logs;
1071 		while (log != NULL) {
1072 			purple_log_write((PurpleLog *)log->data, flags, alias, mtime, displayed);
1073 			log = log->next;
1074 		}
1075 	}
1076 
1077 	if (ops && ops->write_conv)
1078 		ops->write_conv(conv, who, alias, displayed, flags, mtime);
1079 
1080 	add_message_to_history(conv, who, alias, message, flags, mtime);
1081 
1082 	purple_signal_emit(purple_conversations_get_handle(),
1083 		(type == PURPLE_CONV_TYPE_IM ? "wrote-im-msg" : "wrote-chat-msg"),
1084 		account, who, displayed, conv, flags);
1085 
1086 	g_free(displayed);
1087 	g_free(server_alias);
1088 }
1089 
1090 gboolean
purple_conversation_has_focus(PurpleConversation * conv)1091 purple_conversation_has_focus(PurpleConversation *conv)
1092 {
1093 	gboolean ret = FALSE;
1094 	PurpleConversationUiOps *ops;
1095 
1096 	g_return_val_if_fail(conv != NULL, FALSE);
1097 
1098 	ops = purple_conversation_get_ui_ops(conv);
1099 
1100 	if (ops != NULL && ops->has_focus != NULL)
1101 		ret = ops->has_focus(conv);
1102 
1103 	return ret;
1104 }
1105 
1106 /*
1107  * TODO: Need to make sure calls to this function happen in the core
1108  * instead of the UI.  That way UIs have less work to do, and the
1109  * core/UI split is cleaner.  Also need to make sure this is called
1110  * when chats are added/removed from the blist.
1111  */
1112 void
purple_conversation_update(PurpleConversation * conv,PurpleConvUpdateType type)1113 purple_conversation_update(PurpleConversation *conv, PurpleConvUpdateType type)
1114 {
1115 	g_return_if_fail(conv != NULL);
1116 
1117 	purple_signal_emit(purple_conversations_get_handle(),
1118 					 "conversation-updated", conv, type);
1119 }
1120 
1121 /**************************************************************************
1122  * IM Conversation API
1123  **************************************************************************/
1124 PurpleConversation *
purple_conv_im_get_conversation(const PurpleConvIm * im)1125 purple_conv_im_get_conversation(const PurpleConvIm *im)
1126 {
1127 	g_return_val_if_fail(im != NULL, NULL);
1128 
1129 	return im->conv;
1130 }
1131 
1132 void
purple_conv_im_set_icon(PurpleConvIm * im,PurpleBuddyIcon * icon)1133 purple_conv_im_set_icon(PurpleConvIm *im, PurpleBuddyIcon *icon)
1134 {
1135 	g_return_if_fail(im != NULL);
1136 
1137 	if (im->icon != icon)
1138 	{
1139 		purple_buddy_icon_unref(im->icon);
1140 
1141 		im->icon = (icon == NULL ? NULL : purple_buddy_icon_ref(icon));
1142 	}
1143 
1144 	purple_conversation_update(purple_conv_im_get_conversation(im),
1145 							 PURPLE_CONV_UPDATE_ICON);
1146 }
1147 
1148 PurpleBuddyIcon *
purple_conv_im_get_icon(const PurpleConvIm * im)1149 purple_conv_im_get_icon(const PurpleConvIm *im)
1150 {
1151 	g_return_val_if_fail(im != NULL, NULL);
1152 
1153 	return im->icon;
1154 }
1155 
1156 void
purple_conv_im_set_typing_state(PurpleConvIm * im,PurpleTypingState state)1157 purple_conv_im_set_typing_state(PurpleConvIm *im, PurpleTypingState state)
1158 {
1159 	g_return_if_fail(im != NULL);
1160 
1161 	if (im->typing_state != state)
1162 	{
1163 		im->typing_state = state;
1164 
1165 		switch (state)
1166 		{
1167 			case PURPLE_TYPING:
1168 				purple_signal_emit(purple_conversations_get_handle(),
1169 								   "buddy-typing", im->conv->account, im->conv->name);
1170 				break;
1171 			case PURPLE_TYPED:
1172 				purple_signal_emit(purple_conversations_get_handle(),
1173 								   "buddy-typed", im->conv->account, im->conv->name);
1174 				break;
1175 			case PURPLE_NOT_TYPING:
1176 				purple_signal_emit(purple_conversations_get_handle(),
1177 								   "buddy-typing-stopped", im->conv->account, im->conv->name);
1178 				break;
1179 		}
1180 
1181 		purple_conv_im_update_typing(im);
1182 	}
1183 }
1184 
1185 PurpleTypingState
purple_conv_im_get_typing_state(const PurpleConvIm * im)1186 purple_conv_im_get_typing_state(const PurpleConvIm *im)
1187 {
1188 	g_return_val_if_fail(im != NULL, 0);
1189 
1190 	return im->typing_state;
1191 }
1192 
1193 void
purple_conv_im_start_typing_timeout(PurpleConvIm * im,int timeout)1194 purple_conv_im_start_typing_timeout(PurpleConvIm *im, int timeout)
1195 {
1196 	PurpleConversation *conv;
1197 
1198 	g_return_if_fail(im != NULL);
1199 
1200 	if (im->typing_timeout > 0)
1201 		purple_conv_im_stop_typing_timeout(im);
1202 
1203 	conv = purple_conv_im_get_conversation(im);
1204 
1205 	im->typing_timeout = purple_timeout_add_seconds(timeout, reset_typing_cb, conv);
1206 }
1207 
1208 void
purple_conv_im_stop_typing_timeout(PurpleConvIm * im)1209 purple_conv_im_stop_typing_timeout(PurpleConvIm *im)
1210 {
1211 	g_return_if_fail(im != NULL);
1212 
1213 	if (im->typing_timeout == 0)
1214 		return;
1215 
1216 	purple_timeout_remove(im->typing_timeout);
1217 	im->typing_timeout = 0;
1218 }
1219 
1220 guint
purple_conv_im_get_typing_timeout(const PurpleConvIm * im)1221 purple_conv_im_get_typing_timeout(const PurpleConvIm *im)
1222 {
1223 	g_return_val_if_fail(im != NULL, 0);
1224 
1225 	return im->typing_timeout;
1226 }
1227 
1228 void
purple_conv_im_set_type_again(PurpleConvIm * im,unsigned int val)1229 purple_conv_im_set_type_again(PurpleConvIm *im, unsigned int val)
1230 {
1231 	g_return_if_fail(im != NULL);
1232 
1233 	if (val == 0)
1234 		im->type_again = 0;
1235 	else
1236 		im->type_again = time(NULL) + val;
1237 }
1238 
1239 time_t
purple_conv_im_get_type_again(const PurpleConvIm * im)1240 purple_conv_im_get_type_again(const PurpleConvIm *im)
1241 {
1242 	g_return_val_if_fail(im != NULL, 0);
1243 
1244 	return im->type_again;
1245 }
1246 
1247 void
purple_conv_im_start_send_typed_timeout(PurpleConvIm * im)1248 purple_conv_im_start_send_typed_timeout(PurpleConvIm *im)
1249 {
1250 	g_return_if_fail(im != NULL);
1251 
1252 	im->send_typed_timeout = purple_timeout_add_seconds(SEND_TYPED_TIMEOUT_SECONDS,
1253 	                                                    send_typed_cb,
1254 	                                                    purple_conv_im_get_conversation(im));
1255 }
1256 
1257 void
purple_conv_im_stop_send_typed_timeout(PurpleConvIm * im)1258 purple_conv_im_stop_send_typed_timeout(PurpleConvIm *im)
1259 {
1260 	g_return_if_fail(im != NULL);
1261 
1262 	if (im->send_typed_timeout == 0)
1263 		return;
1264 
1265 	purple_timeout_remove(im->send_typed_timeout);
1266 	im->send_typed_timeout = 0;
1267 }
1268 
1269 guint
purple_conv_im_get_send_typed_timeout(const PurpleConvIm * im)1270 purple_conv_im_get_send_typed_timeout(const PurpleConvIm *im)
1271 {
1272 	g_return_val_if_fail(im != NULL, 0);
1273 
1274 	return im->send_typed_timeout;
1275 }
1276 
1277 void
purple_conv_im_update_typing(PurpleConvIm * im)1278 purple_conv_im_update_typing(PurpleConvIm *im)
1279 {
1280 	g_return_if_fail(im != NULL);
1281 
1282 	purple_conversation_update(purple_conv_im_get_conversation(im),
1283 							 PURPLE_CONV_UPDATE_TYPING);
1284 }
1285 
1286 void
purple_conv_im_write(PurpleConvIm * im,const char * who,const char * message,PurpleMessageFlags flags,time_t mtime)1287 purple_conv_im_write(PurpleConvIm *im, const char *who, const char *message,
1288 			  PurpleMessageFlags flags, time_t mtime)
1289 {
1290 	PurpleConversation *c;
1291 
1292 	g_return_if_fail(im != NULL);
1293 	g_return_if_fail(message != NULL);
1294 
1295 	c = purple_conv_im_get_conversation(im);
1296 
1297 	if ((flags & PURPLE_MESSAGE_RECV) == PURPLE_MESSAGE_RECV) {
1298 		purple_conv_im_set_typing_state(im, PURPLE_NOT_TYPING);
1299 	}
1300 
1301 	/* Pass this on to either the ops structure or the default write func. */
1302 	if (c->ui_ops != NULL && c->ui_ops->write_im != NULL)
1303 		c->ui_ops->write_im(c, who, message, flags, mtime);
1304 	else
1305 		purple_conversation_write(c, who, message, flags, mtime);
1306 }
1307 
purple_conv_present_error(const char * who,PurpleAccount * account,const char * what)1308 gboolean purple_conv_present_error(const char *who, PurpleAccount *account, const char *what)
1309 {
1310 	PurpleConversation *conv;
1311 
1312 	g_return_val_if_fail(who != NULL, FALSE);
1313 	g_return_val_if_fail(account !=NULL, FALSE);
1314 	g_return_val_if_fail(what != NULL, FALSE);
1315 
1316 	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, who, account);
1317 	if (conv != NULL)
1318 		purple_conversation_write(conv, NULL, what, PURPLE_MESSAGE_ERROR, time(NULL));
1319 	else
1320 		return FALSE;
1321 
1322 	return TRUE;
1323 }
1324 
1325 void
purple_conv_im_send(PurpleConvIm * im,const char * message)1326 purple_conv_im_send(PurpleConvIm *im, const char *message)
1327 {
1328 	purple_conv_im_send_with_flags(im, message, 0);
1329 }
1330 
1331 static void
purple_conv_send_confirm_cb(gpointer * data)1332 purple_conv_send_confirm_cb(gpointer *data)
1333 {
1334 	PurpleConversation *conv = data[0];
1335 	char *message = data[1];
1336 
1337 	g_free(data);
1338 	common_send(conv, message, 0);
1339 }
1340 
1341 void
purple_conv_send_confirm(PurpleConversation * conv,const char * message)1342 purple_conv_send_confirm(PurpleConversation *conv, const char *message)
1343 {
1344 	char *text;
1345 	gpointer *data;
1346 
1347 	g_return_if_fail(conv != NULL);
1348 	g_return_if_fail(message != NULL);
1349 
1350 	if (conv->ui_ops != NULL && conv->ui_ops->send_confirm != NULL)
1351 	{
1352 		conv->ui_ops->send_confirm(conv, message);
1353 		return;
1354 	}
1355 
1356 	text = g_strdup_printf("You are about to send the following message:\n%s", message);
1357 	data = g_new0(gpointer, 2);
1358 	data[0] = conv;
1359 	data[1] = (gpointer)message;
1360 
1361 	purple_request_action(conv, NULL, _("Send Message"), text, 0,
1362 						  purple_conversation_get_account(conv), NULL, conv,
1363 						  data, 2,
1364 						  _("_Send Message"), G_CALLBACK(purple_conv_send_confirm_cb),
1365 						  _("Cancel"), NULL);
1366 }
1367 
1368 void
purple_conv_im_send_with_flags(PurpleConvIm * im,const char * message,PurpleMessageFlags flags)1369 purple_conv_im_send_with_flags(PurpleConvIm *im, const char *message, PurpleMessageFlags flags)
1370 {
1371 	g_return_if_fail(im != NULL);
1372 	g_return_if_fail(message != NULL);
1373 
1374 	common_send(purple_conv_im_get_conversation(im), message, flags);
1375 }
1376 
1377 gboolean
purple_conv_custom_smiley_add(PurpleConversation * conv,const char * smile,const char * cksum_type,const char * chksum,gboolean remote)1378 purple_conv_custom_smiley_add(PurpleConversation *conv, const char *smile,
1379                             const char *cksum_type, const char *chksum,
1380 							gboolean remote)
1381 {
1382 	if (conv == NULL || smile == NULL || !*smile) {
1383 		return FALSE;
1384 	}
1385 
1386 	/* TODO: check if the icon is in the cache and return false if so */
1387 	/* TODO: add an icon cache (that doesn't suck) */
1388 	if (conv->ui_ops != NULL && conv->ui_ops->custom_smiley_add !=NULL) {
1389 		return conv->ui_ops->custom_smiley_add(conv, smile, remote);
1390 	} else {
1391 		purple_debug_info("conversation", "Could not find add custom smiley function");
1392 		return FALSE;
1393 	}
1394 
1395 }
1396 
1397 void
purple_conv_custom_smiley_write(PurpleConversation * conv,const char * smile,const guchar * data,gsize size)1398 purple_conv_custom_smiley_write(PurpleConversation *conv, const char *smile,
1399                                    const guchar *data, gsize size)
1400 {
1401 	g_return_if_fail(conv != NULL);
1402 	g_return_if_fail(smile != NULL && *smile);
1403 
1404 	if (conv->ui_ops != NULL && conv->ui_ops->custom_smiley_write != NULL)
1405 		conv->ui_ops->custom_smiley_write(conv, smile, data, size);
1406 	else
1407 		purple_debug_info("conversation", "Could not find the smiley write function");
1408 }
1409 
1410 void
purple_conv_custom_smiley_close(PurpleConversation * conv,const char * smile)1411 purple_conv_custom_smiley_close(PurpleConversation *conv, const char *smile)
1412 {
1413 	g_return_if_fail(conv != NULL);
1414 	g_return_if_fail(smile != NULL && *smile);
1415 
1416 	if (conv->ui_ops != NULL && conv->ui_ops->custom_smiley_close != NULL)
1417 		conv->ui_ops->custom_smiley_close(conv, smile);
1418 	else
1419 		purple_debug_info("conversation", "Could not find custom smiley close function");
1420 }
1421 
1422 
1423 /**************************************************************************
1424  * Chat Conversation API
1425  **************************************************************************/
1426 
1427 PurpleConversation *
purple_conv_chat_get_conversation(const PurpleConvChat * chat)1428 purple_conv_chat_get_conversation(const PurpleConvChat *chat)
1429 {
1430 	g_return_val_if_fail(chat != NULL, NULL);
1431 
1432 	return chat->conv;
1433 }
1434 
1435 GList *
purple_conv_chat_set_users(PurpleConvChat * chat,GList * users)1436 purple_conv_chat_set_users(PurpleConvChat *chat, GList *users)
1437 {
1438 	g_return_val_if_fail(chat != NULL, NULL);
1439 
1440 	chat->in_room = users;
1441 
1442 	return users;
1443 }
1444 
1445 GList *
purple_conv_chat_get_users(const PurpleConvChat * chat)1446 purple_conv_chat_get_users(const PurpleConvChat *chat)
1447 {
1448 	g_return_val_if_fail(chat != NULL, NULL);
1449 
1450 	return chat->in_room;
1451 }
1452 
1453 void
purple_conv_chat_ignore(PurpleConvChat * chat,const char * name)1454 purple_conv_chat_ignore(PurpleConvChat *chat, const char *name)
1455 {
1456 	g_return_if_fail(chat != NULL);
1457 	g_return_if_fail(name != NULL);
1458 
1459 	/* Make sure the user isn't already ignored. */
1460 	if (purple_conv_chat_is_user_ignored(chat, name))
1461 		return;
1462 
1463 	purple_conv_chat_set_ignored(chat,
1464 		g_list_append(chat->ignored, g_strdup(name)));
1465 }
1466 
1467 void
purple_conv_chat_unignore(PurpleConvChat * chat,const char * name)1468 purple_conv_chat_unignore(PurpleConvChat *chat, const char *name)
1469 {
1470 	GList *item;
1471 
1472 	g_return_if_fail(chat != NULL);
1473 	g_return_if_fail(name != NULL);
1474 
1475 	/* Make sure the user is actually ignored. */
1476 	if (!purple_conv_chat_is_user_ignored(chat, name))
1477 		return;
1478 
1479 	item = g_list_find(purple_conv_chat_get_ignored(chat),
1480 					   purple_conv_chat_get_ignored_user(chat, name));
1481 
1482 	purple_conv_chat_set_ignored(chat,
1483 		g_list_remove_link(chat->ignored, item));
1484 
1485 	g_free(item->data);
1486 	g_list_free_1(item);
1487 }
1488 
1489 GList *
purple_conv_chat_set_ignored(PurpleConvChat * chat,GList * ignored)1490 purple_conv_chat_set_ignored(PurpleConvChat *chat, GList *ignored)
1491 {
1492 	g_return_val_if_fail(chat != NULL, NULL);
1493 
1494 	chat->ignored = ignored;
1495 
1496 	return ignored;
1497 }
1498 
1499 GList *
purple_conv_chat_get_ignored(const PurpleConvChat * chat)1500 purple_conv_chat_get_ignored(const PurpleConvChat *chat)
1501 {
1502 	g_return_val_if_fail(chat != NULL, NULL);
1503 
1504 	return chat->ignored;
1505 }
1506 
1507 const char *
purple_conv_chat_get_ignored_user(const PurpleConvChat * chat,const char * user)1508 purple_conv_chat_get_ignored_user(const PurpleConvChat *chat, const char *user)
1509 {
1510 	GList *ignored;
1511 
1512 	g_return_val_if_fail(chat != NULL, NULL);
1513 	g_return_val_if_fail(user != NULL, NULL);
1514 
1515 	for (ignored = purple_conv_chat_get_ignored(chat);
1516 		 ignored != NULL;
1517 		 ignored = ignored->next) {
1518 
1519 		const char *ign = (const char *)ignored->data;
1520 
1521 		if (!purple_utf8_strcasecmp(user, ign) ||
1522 			((*ign == '+' || *ign == '%') && !purple_utf8_strcasecmp(user, ign + 1)))
1523 			return ign;
1524 
1525 		if (*ign == '@') {
1526 			ign++;
1527 
1528 			if ((*ign == '+' && !purple_utf8_strcasecmp(user, ign + 1)) ||
1529 				(*ign != '+' && !purple_utf8_strcasecmp(user, ign)))
1530 				return ign;
1531 		}
1532 	}
1533 
1534 	return NULL;
1535 }
1536 
1537 gboolean
purple_conv_chat_is_user_ignored(const PurpleConvChat * chat,const char * user)1538 purple_conv_chat_is_user_ignored(const PurpleConvChat *chat, const char *user)
1539 {
1540 	g_return_val_if_fail(chat != NULL, FALSE);
1541 	g_return_val_if_fail(user != NULL, FALSE);
1542 
1543 	return (purple_conv_chat_get_ignored_user(chat, user) != NULL);
1544 }
1545 
1546 void
purple_conv_chat_set_topic(PurpleConvChat * chat,const char * who,const char * topic)1547 purple_conv_chat_set_topic(PurpleConvChat *chat, const char *who, const char *topic)
1548 {
1549 	g_return_if_fail(chat != NULL);
1550 
1551 	g_free(chat->who);
1552 	g_free(chat->topic);
1553 
1554 	chat->who   = g_strdup(who);
1555 	chat->topic = g_strdup(topic);
1556 
1557 	purple_conversation_update(purple_conv_chat_get_conversation(chat),
1558 							 PURPLE_CONV_UPDATE_TOPIC);
1559 
1560 	purple_signal_emit(purple_conversations_get_handle(), "chat-topic-changed",
1561 					 chat->conv, chat->who, chat->topic);
1562 }
1563 
1564 const char *
purple_conv_chat_get_topic(const PurpleConvChat * chat)1565 purple_conv_chat_get_topic(const PurpleConvChat *chat)
1566 {
1567 	g_return_val_if_fail(chat != NULL, NULL);
1568 
1569 	return chat->topic;
1570 }
1571 
1572 void
purple_conv_chat_set_id(PurpleConvChat * chat,int id)1573 purple_conv_chat_set_id(PurpleConvChat *chat, int id)
1574 {
1575 	g_return_if_fail(chat != NULL);
1576 
1577 	chat->id = id;
1578 }
1579 
1580 int
purple_conv_chat_get_id(const PurpleConvChat * chat)1581 purple_conv_chat_get_id(const PurpleConvChat *chat)
1582 {
1583 	g_return_val_if_fail(chat != NULL, -1);
1584 
1585 	return chat->id;
1586 }
1587 
1588 void
purple_conv_chat_write(PurpleConvChat * chat,const char * who,const char * message,PurpleMessageFlags flags,time_t mtime)1589 purple_conv_chat_write(PurpleConvChat *chat, const char *who, const char *message,
1590 				PurpleMessageFlags flags, time_t mtime)
1591 {
1592 	PurpleAccount *account;
1593 	PurpleConversation *conv;
1594 	PurpleConnection *gc;
1595 
1596 	g_return_if_fail(chat != NULL);
1597 	g_return_if_fail(who != NULL);
1598 	g_return_if_fail(message != NULL);
1599 
1600 	conv      = purple_conv_chat_get_conversation(chat);
1601 	gc        = purple_conversation_get_gc(conv);
1602 	account   = purple_connection_get_account(gc);
1603 
1604 	/* Don't display this if the person who wrote it is ignored. */
1605 	if (purple_conv_chat_is_user_ignored(chat, who))
1606 		return;
1607 
1608 	if (mtime < 0) {
1609 		purple_debug_error("conversation",
1610 				"purple_conv_chat_write ignoring negative timestamp\n");
1611 		/* TODO: Would be more appropriate to use a value that indicates
1612 		   that the timestamp is unknown, and surface that in the UI. */
1613 		mtime = time(NULL);
1614 	}
1615 
1616 	if (!(flags & PURPLE_MESSAGE_WHISPER)) {
1617 		const char *str;
1618 
1619 		str = purple_normalize(account, who);
1620 
1621 		if (purple_strequal(str, chat->nick)) {
1622 			flags |= PURPLE_MESSAGE_SEND;
1623 		} else {
1624 			flags |= PURPLE_MESSAGE_RECV;
1625 
1626 			if (purple_utf8_has_word(message, chat->nick))
1627 				flags |= PURPLE_MESSAGE_NICK;
1628 		}
1629 	}
1630 
1631 	/* Pass this on to either the ops structure or the default write func. */
1632 	if (conv->ui_ops != NULL && conv->ui_ops->write_chat != NULL)
1633 		conv->ui_ops->write_chat(conv, who, message, flags, mtime);
1634 	else
1635 		purple_conversation_write(conv, who, message, flags, mtime);
1636 }
1637 
1638 void
purple_conv_chat_send(PurpleConvChat * chat,const char * message)1639 purple_conv_chat_send(PurpleConvChat *chat, const char *message)
1640 {
1641 	purple_conv_chat_send_with_flags(chat, message, 0);
1642 }
1643 
1644 void
purple_conv_chat_send_with_flags(PurpleConvChat * chat,const char * message,PurpleMessageFlags flags)1645 purple_conv_chat_send_with_flags(PurpleConvChat *chat, const char *message, PurpleMessageFlags flags)
1646 {
1647 	g_return_if_fail(chat != NULL);
1648 	g_return_if_fail(message != NULL);
1649 
1650 	common_send(purple_conv_chat_get_conversation(chat), message, flags);
1651 }
1652 
1653 void
purple_conv_chat_add_user(PurpleConvChat * chat,const char * user,const char * extra_msg,PurpleConvChatBuddyFlags flags,gboolean new_arrival)1654 purple_conv_chat_add_user(PurpleConvChat *chat, const char *user,
1655 						const char *extra_msg, PurpleConvChatBuddyFlags flags,
1656 						gboolean new_arrival)
1657 {
1658 	GList *users = g_list_append(NULL, (char *)user);
1659 	GList *extra_msgs = g_list_append(NULL, (char *)extra_msg);
1660 	GList *flags2 = g_list_append(NULL, GINT_TO_POINTER(flags));
1661 
1662 	purple_conv_chat_add_users(chat, users, extra_msgs, flags2, new_arrival);
1663 
1664 	g_list_free(users);
1665 	g_list_free(extra_msgs);
1666 	g_list_free(flags2);
1667 }
1668 
1669 static int
purple_conv_chat_cb_compare(PurpleConvChatBuddy * a,PurpleConvChatBuddy * b)1670 purple_conv_chat_cb_compare(PurpleConvChatBuddy *a, PurpleConvChatBuddy *b)
1671 {
1672 	PurpleConvChatBuddyFlags f1 = 0, f2 = 0;
1673 	char *user1 = NULL, *user2 = NULL;
1674 	gint ret = 0;
1675 
1676 	if (a) {
1677 		f1 = a->flags;
1678 		if (a->alias_key)
1679 			user1 = a->alias_key;
1680 		else if (a->name)
1681 			user1 = a->name;
1682 	}
1683 
1684 	if (b) {
1685 		f2 = b->flags;
1686 		if (b->alias_key)
1687 			user2 = b->alias_key;
1688 		else if (b->name)
1689 			user2 = b->name;
1690 	}
1691 
1692 	if (user1 == NULL || user2 == NULL) {
1693 		if (!(user1 == NULL && user2 == NULL))
1694 			ret = (user1 == NULL) ? -1: 1;
1695 	} else if (f1 != f2) {
1696 		/* sort more important users first */
1697 		ret = (f1 > f2) ? -1 : 1;
1698 	} else if (a->buddy != b->buddy) {
1699 		ret = a->buddy ? -1 : 1;
1700 	} else {
1701 		ret = purple_utf8_strcasecmp(user1, user2);
1702 	}
1703 
1704 	return ret;
1705 }
1706 
1707 void
purple_conv_chat_add_users(PurpleConvChat * chat,GList * users,GList * extra_msgs,GList * flags,gboolean new_arrivals)1708 purple_conv_chat_add_users(PurpleConvChat *chat, GList *users, GList *extra_msgs,
1709 						 GList *flags, gboolean new_arrivals)
1710 {
1711 	PurpleConversation *conv;
1712 	PurpleConversationUiOps *ops;
1713 	PurpleConvChatBuddy *cbuddy;
1714 	PurpleConnection *gc;
1715 	PurplePluginProtocolInfo *prpl_info;
1716 	GList *ul, *fl;
1717 	GList *cbuddies = NULL;
1718 
1719 	g_return_if_fail(chat  != NULL);
1720 	g_return_if_fail(users != NULL);
1721 
1722 	conv = purple_conv_chat_get_conversation(chat);
1723 	ops  = purple_conversation_get_ui_ops(conv);
1724 
1725 	gc = purple_conversation_get_gc(conv);
1726 	g_return_if_fail(gc != NULL);
1727 	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
1728 	g_return_if_fail(prpl_info != NULL);
1729 
1730 	ul = users;
1731 	fl = flags;
1732 	while ((ul != NULL) && (fl != NULL)) {
1733 		const char *user = (const char *)ul->data;
1734 		const char *alias = user;
1735 		char *server_alias = NULL;
1736 		gboolean quiet;
1737 		PurpleConvChatBuddyFlags flag = GPOINTER_TO_INT(fl->data);
1738 		const char *extra_msg = (extra_msgs ? extra_msgs->data : NULL);
1739 
1740 		if(!(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) {
1741 			if (purple_strequal(chat->nick, purple_normalize(conv->account, user))) {
1742 				const char *alias2 = purple_account_get_alias(conv->account);
1743 				if (alias2 != NULL)
1744 					alias = alias2;
1745 				else
1746 				{
1747 					const char *display_name = purple_connection_get_display_name(gc);
1748 					if (display_name != NULL)
1749 						alias = display_name;
1750 				}
1751 			} else {
1752 				PurpleBuddy *buddy;
1753 				if ((buddy = purple_find_buddy(gc->account, user)) != NULL)
1754 					alias = purple_buddy_get_contact_alias(buddy);
1755 			}
1756 		}
1757 		if (alias == user && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_cb_alias)) {
1758 			int id = purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv));
1759 			server_alias = prpl_info->get_cb_alias(gc, id, user);
1760 			if (server_alias)
1761 				alias = server_alias;
1762 		}
1763 
1764 		quiet = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_conversations_get_handle(),
1765 						 "chat-buddy-joining", conv, user, flag)) ||
1766 				purple_conv_chat_is_user_ignored(chat, user);
1767 
1768 		cbuddy = purple_conv_chat_cb_new(user, alias, flag);
1769 		cbuddy->buddy = purple_find_buddy(conv->account, user) != NULL;
1770 
1771 		chat->in_room = g_list_prepend(chat->in_room, cbuddy);
1772 		g_hash_table_replace(chat->users, g_strdup(cbuddy->name), cbuddy);
1773 
1774 		cbuddies = g_list_prepend(cbuddies, cbuddy);
1775 
1776 		if (!quiet && new_arrivals) {
1777 			char *alias_esc = g_markup_escape_text(alias, -1);
1778 			char *tmp;
1779 
1780 			if (extra_msg == NULL)
1781 				tmp = g_strdup_printf(_("%s entered the room."), alias_esc);
1782 			else {
1783 				char *extra_msg_esc = g_markup_escape_text(extra_msg, -1);
1784 				tmp = g_strdup_printf(_("%s [<I>%s</I>] entered the room."),
1785 				                      alias_esc, extra_msg_esc);
1786 				g_free(extra_msg_esc);
1787 			}
1788 			g_free(alias_esc);
1789 
1790 			purple_conversation_write(conv, NULL, tmp,
1791 					PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY,
1792 					time(NULL));
1793 			g_free(tmp);
1794 		}
1795 
1796 		purple_signal_emit(purple_conversations_get_handle(),
1797 						 "chat-buddy-joined", conv, user, flag, new_arrivals);
1798 		ul = ul->next;
1799 		fl = fl->next;
1800 		if (extra_msgs != NULL)
1801 			extra_msgs = extra_msgs->next;
1802 
1803 		g_free(server_alias);
1804 	}
1805 
1806 	cbuddies = g_list_sort(cbuddies, (GCompareFunc)purple_conv_chat_cb_compare);
1807 
1808 	if (ops != NULL && ops->chat_add_users != NULL)
1809 		ops->chat_add_users(conv, cbuddies, new_arrivals);
1810 
1811 	g_list_free(cbuddies);
1812 }
1813 
1814 void
purple_conv_chat_rename_user(PurpleConvChat * chat,const char * old_user,const char * new_user)1815 purple_conv_chat_rename_user(PurpleConvChat *chat, const char *old_user,
1816 						   const char *new_user)
1817 {
1818 	PurpleConversation *conv;
1819 	PurpleConversationUiOps *ops;
1820 	PurpleConnection *gc;
1821 	PurplePluginProtocolInfo *prpl_info;
1822 	PurpleConvChatBuddy *cb;
1823 	PurpleConvChatBuddyFlags flags;
1824 	const char *new_alias = new_user;
1825 	char *server_alias = NULL;
1826 	char tmp[BUF_LONG];
1827 	gboolean is_me = FALSE;
1828 
1829 	g_return_if_fail(chat != NULL);
1830 	g_return_if_fail(old_user != NULL);
1831 	g_return_if_fail(new_user != NULL);
1832 
1833 	conv = purple_conv_chat_get_conversation(chat);
1834 	ops  = purple_conversation_get_ui_ops(conv);
1835 
1836 	gc = purple_conversation_get_gc(conv);
1837 	g_return_if_fail(gc != NULL);
1838 	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
1839 	g_return_if_fail(prpl_info != NULL);
1840 
1841 	if (purple_strequal(chat->nick, purple_normalize(conv->account, old_user))) {
1842 		const char *alias;
1843 
1844 		/* Note this for later. */
1845 		is_me = TRUE;
1846 
1847 		if(!(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) {
1848 			alias = purple_account_get_alias(conv->account);
1849 			if (alias != NULL)
1850 				new_alias = alias;
1851 			else
1852 			{
1853 				const char *display_name = purple_connection_get_display_name(gc);
1854 				if (display_name != NULL)
1855 					new_alias = display_name;
1856 			}
1857 		}
1858 	} else {
1859 		if (!(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) {
1860 			PurpleBuddy *buddy;
1861 			if ((buddy = purple_find_buddy(gc->account, new_user)) != NULL)
1862 				new_alias = purple_buddy_get_contact_alias(buddy);
1863 		}
1864 		if (new_alias == new_user && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_cb_alias)) {
1865 			int id = purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv));
1866 			server_alias = prpl_info->get_cb_alias(gc, id, new_user);
1867 			if (server_alias)
1868 				new_alias = server_alias;
1869 		}
1870 	}
1871 
1872 	flags = purple_conv_chat_user_get_flags(chat, old_user);
1873 	cb = purple_conv_chat_cb_new(new_user, new_alias, flags);
1874 	cb->buddy = purple_find_buddy(conv->account, new_user) != NULL;
1875 
1876 	chat->in_room = g_list_prepend(chat->in_room, cb);
1877 	g_hash_table_replace(chat->users, g_strdup(cb->name), cb);
1878 
1879 	if (ops != NULL && ops->chat_rename_user != NULL)
1880 		ops->chat_rename_user(conv, old_user, new_user, new_alias);
1881 
1882 	cb = purple_conv_chat_cb_find(chat, old_user);
1883 
1884 	if (cb) {
1885 		chat->in_room = g_list_remove(chat->in_room, cb);
1886 		g_hash_table_remove(chat->users, cb->name);
1887 		purple_conv_chat_cb_destroy(cb);
1888 	}
1889 
1890 	if (purple_conv_chat_is_user_ignored(chat, old_user)) {
1891 		purple_conv_chat_unignore(chat, old_user);
1892 		purple_conv_chat_ignore(chat, new_user);
1893 	}
1894 	else if (purple_conv_chat_is_user_ignored(chat, new_user))
1895 		purple_conv_chat_unignore(chat, new_user);
1896 
1897 	if (is_me)
1898 		purple_conv_chat_set_nick(chat, new_user);
1899 
1900 	if (purple_prefs_get_bool("/purple/conversations/chat/show_nick_change") &&
1901 	    !purple_conv_chat_is_user_ignored(chat, new_user)) {
1902 
1903 		if (is_me) {
1904 			char *escaped = g_markup_escape_text(new_user, -1);
1905 			g_snprintf(tmp, sizeof(tmp),
1906 					_("You are now known as %s"), escaped);
1907 			g_free(escaped);
1908 		} else {
1909 			const char *old_alias = old_user;
1910 			const char *new_alias = new_user;
1911 			char *escaped;
1912 			char *escaped2;
1913 
1914 			if (!(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) {
1915 				PurpleBuddy *buddy;
1916 
1917 				if ((buddy = purple_find_buddy(gc->account, old_user)) != NULL)
1918 					old_alias = purple_buddy_get_contact_alias(buddy);
1919 				if ((buddy = purple_find_buddy(gc->account, new_user)) != NULL)
1920 					new_alias = purple_buddy_get_contact_alias(buddy);
1921 			}
1922 
1923 			escaped = g_markup_escape_text(old_alias, -1);
1924 			escaped2 = g_markup_escape_text(new_alias, -1);
1925 			g_snprintf(tmp, sizeof(tmp),
1926 					_("%s is now known as %s"), escaped, escaped2);
1927 			g_free(escaped);
1928 			g_free(escaped2);
1929 		}
1930 
1931 		purple_conversation_write(conv, NULL, tmp,
1932 				PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY,
1933 				time(NULL));
1934 	}
1935 	g_free(server_alias);
1936 }
1937 
1938 void
purple_conv_chat_remove_user(PurpleConvChat * chat,const char * user,const char * reason)1939 purple_conv_chat_remove_user(PurpleConvChat *chat, const char *user, const char *reason)
1940 {
1941 	GList *users = g_list_append(NULL, (char *)user);
1942 
1943 	purple_conv_chat_remove_users(chat, users, reason);
1944 
1945 	g_list_free(users);
1946 }
1947 
1948 void
purple_conv_chat_remove_users(PurpleConvChat * chat,GList * users,const char * reason)1949 purple_conv_chat_remove_users(PurpleConvChat *chat, GList *users, const char *reason)
1950 {
1951 	PurpleConversation *conv;
1952 	PurpleConnection *gc;
1953 	PurplePluginProtocolInfo *prpl_info;
1954 	PurpleConversationUiOps *ops;
1955 	PurpleConvChatBuddy *cb;
1956 	GList *l;
1957 	gboolean quiet;
1958 
1959 	g_return_if_fail(chat  != NULL);
1960 	g_return_if_fail(users != NULL);
1961 
1962 	conv = purple_conv_chat_get_conversation(chat);
1963 
1964 	gc = purple_conversation_get_gc(conv);
1965 	g_return_if_fail(gc != NULL);
1966 	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
1967 	g_return_if_fail(prpl_info != NULL);
1968 
1969 	ops  = purple_conversation_get_ui_ops(conv);
1970 
1971 	for (l = users; l != NULL; l = l->next) {
1972 		const char *user = (const char *)l->data;
1973 		quiet = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_conversations_get_handle(),
1974 					"chat-buddy-leaving", conv, user, reason)) |
1975 				purple_conv_chat_is_user_ignored(chat, user);
1976 
1977 		cb = purple_conv_chat_cb_find(chat, user);
1978 
1979 		if (cb) {
1980 			chat->in_room = g_list_remove(chat->in_room, cb);
1981 			g_hash_table_remove(chat->users, cb->name);
1982 			purple_conv_chat_cb_destroy(cb);
1983 		}
1984 
1985 		/* NOTE: Don't remove them from ignored in case they re-enter. */
1986 
1987 		if (!quiet) {
1988 			const char *alias = user;
1989 			char *server_alias = NULL;
1990 			char *alias_esc;
1991 			char *tmp;
1992 
1993 			if (!(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) {
1994 				PurpleBuddy *buddy;
1995 
1996 				if ((buddy = purple_find_buddy(gc->account, user)) != NULL)
1997 					alias = purple_buddy_get_contact_alias(buddy);
1998 			}
1999 			if (alias == user && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_cb_alias)) {
2000 				int id = purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv));
2001 				server_alias = prpl_info->get_cb_alias(gc, id, user);
2002 				if (server_alias)
2003 					alias = server_alias;
2004 			}
2005 
2006 			alias_esc = g_markup_escape_text(alias, -1);
2007 
2008 			if (reason == NULL || !*reason)
2009 				tmp = g_strdup_printf(_("%s left the room."), alias_esc);
2010 			else {
2011 				char *reason_esc = g_markup_escape_text(reason, -1);
2012 				tmp = g_strdup_printf(_("%s left the room (%s)."),
2013 				                      alias_esc, reason_esc);
2014 				g_free(reason_esc);
2015 			}
2016 			g_free(alias_esc);
2017 			g_free(server_alias);
2018 
2019 			purple_conversation_write(conv, NULL, tmp,
2020 					PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY,
2021 					time(NULL));
2022 			g_free(tmp);
2023 		}
2024 
2025 		purple_signal_emit(purple_conversations_get_handle(), "chat-buddy-left",
2026 						 conv, user, reason);
2027 	}
2028 
2029 	if (ops != NULL && ops->chat_remove_users != NULL)
2030 		ops->chat_remove_users(conv, users);
2031 }
2032 
2033 void
purple_conv_chat_clear_users(PurpleConvChat * chat)2034 purple_conv_chat_clear_users(PurpleConvChat *chat)
2035 {
2036 	PurpleConversation *conv;
2037 	PurpleConversationUiOps *ops;
2038 	GList *users;
2039 	GList *l;
2040 	GList *names = NULL;
2041 
2042 	g_return_if_fail(chat != NULL);
2043 
2044 	conv  = purple_conv_chat_get_conversation(chat);
2045 	ops   = purple_conversation_get_ui_ops(conv);
2046 	users = chat->in_room;
2047 
2048 	if (ops != NULL && ops->chat_remove_users != NULL) {
2049 		for (l = users; l; l = l->next) {
2050 			PurpleConvChatBuddy *cb = l->data;
2051 			names = g_list_prepend(names, cb->name);
2052 		}
2053 		ops->chat_remove_users(conv, names);
2054 		g_list_free(names);
2055 	}
2056 
2057 	for (l = users; l; l = l->next)
2058 	{
2059 		PurpleConvChatBuddy *cb = l->data;
2060 
2061 		purple_signal_emit(purple_conversations_get_handle(),
2062 						 "chat-buddy-leaving", conv, cb->name, NULL);
2063 		purple_signal_emit(purple_conversations_get_handle(),
2064 						 "chat-buddy-left", conv, cb->name, NULL);
2065 
2066 		purple_conv_chat_cb_destroy(cb);
2067 	}
2068 
2069 	g_hash_table_remove_all(chat->users);
2070 
2071 	g_list_free(users);
2072 	chat->in_room = NULL;
2073 }
2074 
2075 
2076 gboolean
purple_conv_chat_find_user(PurpleConvChat * chat,const char * user)2077 purple_conv_chat_find_user(PurpleConvChat *chat, const char *user)
2078 {
2079 	g_return_val_if_fail(chat != NULL, FALSE);
2080 	g_return_val_if_fail(user != NULL, FALSE);
2081 
2082 	return (purple_conv_chat_cb_find(chat, user) != NULL);
2083 }
2084 
2085 void
purple_conv_chat_user_set_flags(PurpleConvChat * chat,const char * user,PurpleConvChatBuddyFlags flags)2086 purple_conv_chat_user_set_flags(PurpleConvChat *chat, const char *user,
2087 							  PurpleConvChatBuddyFlags flags)
2088 {
2089 	PurpleConversation *conv;
2090 	PurpleConversationUiOps *ops;
2091 	PurpleConvChatBuddy *cb;
2092 	PurpleConvChatBuddyFlags oldflags;
2093 
2094 	g_return_if_fail(chat != NULL);
2095 	g_return_if_fail(user != NULL);
2096 
2097 	cb = purple_conv_chat_cb_find(chat, user);
2098 
2099 	if (!cb)
2100 		return;
2101 
2102 	if (flags == cb->flags)
2103 		return;
2104 
2105 	oldflags = cb->flags;
2106 	cb->flags = flags;
2107 
2108 	conv = purple_conv_chat_get_conversation(chat);
2109 	ops = purple_conversation_get_ui_ops(conv);
2110 
2111 	if (ops != NULL && ops->chat_update_user != NULL)
2112 		ops->chat_update_user(conv, user);
2113 
2114 	purple_signal_emit(purple_conversations_get_handle(),
2115 					 "chat-buddy-flags", conv, user, oldflags, flags);
2116 }
2117 
2118 PurpleConvChatBuddyFlags
purple_conv_chat_user_get_flags(PurpleConvChat * chat,const char * user)2119 purple_conv_chat_user_get_flags(PurpleConvChat *chat, const char *user)
2120 {
2121 	PurpleConvChatBuddy *cb;
2122 
2123 	g_return_val_if_fail(chat != NULL, 0);
2124 	g_return_val_if_fail(user != NULL, 0);
2125 
2126 	cb = purple_conv_chat_cb_find(chat, user);
2127 
2128 	if (!cb)
2129 		return PURPLE_CBFLAGS_NONE;
2130 
2131 	return cb->flags;
2132 }
2133 
purple_conv_chat_set_nick(PurpleConvChat * chat,const char * nick)2134 void purple_conv_chat_set_nick(PurpleConvChat *chat, const char *nick) {
2135 	g_return_if_fail(chat != NULL);
2136 
2137 	g_free(chat->nick);
2138 	chat->nick = g_strdup(purple_normalize(chat->conv->account, nick));
2139 }
2140 
purple_conv_chat_get_nick(PurpleConvChat * chat)2141 const char *purple_conv_chat_get_nick(PurpleConvChat *chat) {
2142 	g_return_val_if_fail(chat != NULL, NULL);
2143 
2144 	return chat->nick;
2145 }
2146 
2147 PurpleConversation *
purple_find_chat(const PurpleConnection * gc,int id)2148 purple_find_chat(const PurpleConnection *gc, int id)
2149 {
2150 	GList *l;
2151 	PurpleConversation *conv;
2152 
2153 	for (l = purple_get_chats(); l != NULL; l = l->next) {
2154 		conv = (PurpleConversation *)l->data;
2155 
2156 		if (purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)) == id &&
2157 			purple_conversation_get_gc(conv) == gc)
2158 			return conv;
2159 	}
2160 
2161 	return NULL;
2162 }
2163 
2164 void
purple_conv_chat_left(PurpleConvChat * chat)2165 purple_conv_chat_left(PurpleConvChat *chat)
2166 {
2167 	g_return_if_fail(chat != NULL);
2168 
2169 	chat->left = TRUE;
2170 	purple_conversation_update(chat->conv, PURPLE_CONV_UPDATE_CHATLEFT);
2171 }
2172 
2173 static void
invite_user_to_chat(gpointer data,PurpleRequestFields * fields)2174 invite_user_to_chat(gpointer data, PurpleRequestFields *fields)
2175 {
2176 	PurpleConversation *conv;
2177 	PurpleConvChat *chat;
2178 	const char *user, *message;
2179 
2180 	conv = data;
2181 	chat = PURPLE_CONV_CHAT(conv);
2182 	user = purple_request_fields_get_string(fields, "screenname");
2183 	message = purple_request_fields_get_string(fields, "message");
2184 
2185 	serv_chat_invite(purple_conversation_get_gc(conv), chat->id, message, user);
2186 }
2187 
purple_conv_chat_invite_user(PurpleConvChat * chat,const char * user,const char * message,gboolean confirm)2188 void purple_conv_chat_invite_user(PurpleConvChat *chat, const char *user,
2189 		const char *message, gboolean confirm)
2190 {
2191 	PurpleAccount *account;
2192 	PurpleConversation *conv;
2193 	PurpleRequestFields *fields;
2194 	PurpleRequestFieldGroup *group;
2195 	PurpleRequestField *field;
2196 
2197 	g_return_if_fail(chat);
2198 
2199 	if (!user || !*user || !message || !*message)
2200 		confirm = TRUE;
2201 
2202 	conv = chat->conv;
2203 	account = conv->account;
2204 
2205 	if (!confirm) {
2206 		serv_chat_invite(purple_account_get_connection(account),
2207 				purple_conv_chat_get_id(chat), message, user);
2208 		return;
2209 	}
2210 
2211 	fields = purple_request_fields_new();
2212 	group = purple_request_field_group_new(_("Invite to chat"));
2213 	purple_request_fields_add_group(fields, group);
2214 
2215 	field = purple_request_field_string_new("screenname", _("Buddy"), user, FALSE);
2216 	purple_request_field_group_add_field(group, field);
2217 	purple_request_field_set_required(field, TRUE);
2218 	purple_request_field_set_type_hint(field, "screenname");
2219 
2220 	field = purple_request_field_string_new("message", _("Message"), message, FALSE);
2221 	purple_request_field_group_add_field(group, field);
2222 
2223 	purple_request_fields(conv, _("Invite to chat"), NULL,
2224 			_("Please enter the name of the user you wish to invite, "
2225 				"along with an optional invite message."),
2226 			fields,
2227 			_("Invite"), G_CALLBACK(invite_user_to_chat),
2228 			_("Cancel"), NULL,
2229 			account, user, conv,
2230 			conv);
2231 }
2232 
2233 gboolean
purple_conv_chat_has_left(PurpleConvChat * chat)2234 purple_conv_chat_has_left(PurpleConvChat *chat)
2235 {
2236 	g_return_val_if_fail(chat != NULL, TRUE);
2237 
2238 	return chat->left;
2239 }
2240 
2241 PurpleConvChatBuddy *
purple_conv_chat_cb_new(const char * name,const char * alias,PurpleConvChatBuddyFlags flags)2242 purple_conv_chat_cb_new(const char *name, const char *alias, PurpleConvChatBuddyFlags flags)
2243 {
2244 	PurpleConvChatBuddy *cb;
2245 
2246 	g_return_val_if_fail(name != NULL, NULL);
2247 
2248 	cb = g_new0(PurpleConvChatBuddy, 1);
2249 	cb->name = g_strdup(name);
2250 	cb->flags = flags;
2251 	cb->alias = g_strdup(alias);
2252 	cb->attributes = g_hash_table_new_full(g_str_hash, g_str_equal,
2253 										   g_free, g_free);
2254 
2255 	PURPLE_DBUS_REGISTER_POINTER(cb, PurpleConvChatBuddy);
2256 	return cb;
2257 }
2258 
2259 PurpleConvChatBuddy *
purple_conv_chat_cb_find(PurpleConvChat * chat,const char * name)2260 purple_conv_chat_cb_find(PurpleConvChat *chat, const char *name)
2261 {
2262 	g_return_val_if_fail(chat != NULL, NULL);
2263 	g_return_val_if_fail(name != NULL, NULL);
2264 
2265 	return g_hash_table_lookup(chat->users, name);
2266 }
2267 
2268 void
purple_conv_chat_cb_destroy(PurpleConvChatBuddy * cb)2269 purple_conv_chat_cb_destroy(PurpleConvChatBuddy *cb)
2270 {
2271 	if (cb == NULL)
2272 		return;
2273 
2274 	purple_signal_emit(purple_conversations_get_handle(),
2275 			"deleting-chat-buddy", cb);
2276 
2277 	g_free(cb->alias);
2278 	g_free(cb->alias_key);
2279 	g_free(cb->name);
2280 	g_hash_table_destroy(cb->attributes);
2281 
2282 	PURPLE_DBUS_UNREGISTER_POINTER(cb);
2283 	g_free(cb);
2284 }
2285 
2286 const char *
purple_conv_chat_cb_get_name(PurpleConvChatBuddy * cb)2287 purple_conv_chat_cb_get_name(PurpleConvChatBuddy *cb)
2288 {
2289 	g_return_val_if_fail(cb != NULL, NULL);
2290 
2291 	return cb->name;
2292 }
2293 
2294 const char *
purple_conv_chat_cb_get_attribute(PurpleConvChatBuddy * cb,const char * key)2295 purple_conv_chat_cb_get_attribute(PurpleConvChatBuddy *cb, const char *key)
2296 {
2297 	g_return_val_if_fail(cb != NULL, NULL);
2298 	g_return_val_if_fail(key != NULL, NULL);
2299 
2300 	return g_hash_table_lookup(cb->attributes, key);
2301 }
2302 
2303 static void
append_attribute_key(gpointer key,gpointer value,gpointer user_data)2304 append_attribute_key(gpointer key, gpointer value, gpointer user_data)
2305 {
2306 	GList **list = user_data;
2307 	*list = g_list_prepend(*list, key);
2308 }
2309 
2310 GList *
purple_conv_chat_cb_get_attribute_keys(PurpleConvChatBuddy * cb)2311 purple_conv_chat_cb_get_attribute_keys(PurpleConvChatBuddy *cb)
2312 {
2313 	GList *keys = NULL;
2314 
2315 	g_return_val_if_fail(cb != NULL, NULL);
2316 
2317 	g_hash_table_foreach(cb->attributes, (GHFunc)append_attribute_key, &keys);
2318 
2319 	return keys;
2320 }
2321 
2322 void
purple_conv_chat_cb_set_attribute(PurpleConvChat * chat,PurpleConvChatBuddy * cb,const char * key,const char * value)2323 purple_conv_chat_cb_set_attribute(PurpleConvChat *chat, PurpleConvChatBuddy *cb, const char *key, const char *value)
2324 {
2325 	PurpleConversation *conv;
2326 	PurpleConversationUiOps *ops;
2327 
2328 	g_return_if_fail(cb != NULL);
2329 	g_return_if_fail(key != NULL);
2330 	g_return_if_fail(value != NULL);
2331 
2332 	g_hash_table_replace(cb->attributes, g_strdup(key), g_strdup(value));
2333 
2334 	conv = purple_conv_chat_get_conversation(chat);
2335 	ops = purple_conversation_get_ui_ops(conv);
2336 
2337 	if (ops != NULL && ops->chat_update_user != NULL)
2338 		ops->chat_update_user(conv, cb->name);
2339 }
2340 
2341 void
purple_conv_chat_cb_set_attributes(PurpleConvChat * chat,PurpleConvChatBuddy * cb,GList * keys,GList * values)2342 purple_conv_chat_cb_set_attributes(PurpleConvChat *chat, PurpleConvChatBuddy *cb, GList *keys, GList *values)
2343 {
2344 	PurpleConversation *conv;
2345 	PurpleConversationUiOps *ops;
2346 
2347 	g_return_if_fail(cb != NULL);
2348 	g_return_if_fail(keys != NULL);
2349 	g_return_if_fail(values != NULL);
2350 
2351 	while (keys != NULL && values != NULL) {
2352 		g_hash_table_replace(cb->attributes, g_strdup(keys->data), g_strdup(values->data));
2353 		keys = g_list_next(keys);
2354 		values = g_list_next(values);
2355 	}
2356 
2357 	conv = purple_conv_chat_get_conversation(chat);
2358 	ops = purple_conversation_get_ui_ops(conv);
2359 
2360 	if (ops != NULL && ops->chat_update_user != NULL)
2361 		ops->chat_update_user(conv, cb->name);
2362 }
2363 
2364 GList *
purple_conversation_get_extended_menu(PurpleConversation * conv)2365 purple_conversation_get_extended_menu(PurpleConversation *conv)
2366 {
2367 	GList *menu = NULL;
2368 
2369 	g_return_val_if_fail(conv != NULL, NULL);
2370 
2371 	purple_signal_emit(purple_conversations_get_handle(),
2372 			"conversation-extended-menu", conv, &menu);
2373 	return menu;
2374 }
2375 
purple_conversation_clear_message_history(PurpleConversation * conv)2376 void purple_conversation_clear_message_history(PurpleConversation *conv)
2377 {
2378 	GList *list = conv->message_history;
2379 	g_list_free_full(list, (GDestroyNotify)free_conv_message);
2380 	conv->message_history = NULL;
2381 
2382 	purple_signal_emit(purple_conversations_get_handle(),
2383 			"cleared-message-history", conv);
2384 }
2385 
purple_conversation_get_message_history(PurpleConversation * conv)2386 GList *purple_conversation_get_message_history(PurpleConversation *conv)
2387 {
2388 	return conv->message_history;
2389 }
2390 
purple_conversation_message_get_sender(PurpleConvMessage * msg)2391 const char *purple_conversation_message_get_sender(PurpleConvMessage *msg)
2392 {
2393 	g_return_val_if_fail(msg, NULL);
2394 	return msg->who;
2395 }
2396 
purple_conversation_message_get_message(PurpleConvMessage * msg)2397 const char *purple_conversation_message_get_message(PurpleConvMessage *msg)
2398 {
2399 	g_return_val_if_fail(msg, NULL);
2400 	return msg->what;
2401 }
2402 
purple_conversation_message_get_flags(PurpleConvMessage * msg)2403 PurpleMessageFlags purple_conversation_message_get_flags(PurpleConvMessage *msg)
2404 {
2405 	g_return_val_if_fail(msg, 0);
2406 	return msg->flags;
2407 }
2408 
purple_conversation_message_get_timestamp(PurpleConvMessage * msg)2409 time_t purple_conversation_message_get_timestamp(PurpleConvMessage *msg)
2410 {
2411 	g_return_val_if_fail(msg, 0);
2412 	return msg->when;
2413 }
2414 
2415 gboolean
purple_conversation_do_command(PurpleConversation * conv,const gchar * cmdline,const gchar * markup,gchar ** error)2416 purple_conversation_do_command(PurpleConversation *conv, const gchar *cmdline,
2417 				const gchar *markup, gchar **error)
2418 {
2419 	char *mark = (markup && *markup) ? NULL : g_markup_escape_text(cmdline, -1), *err = NULL;
2420 	PurpleCmdStatus status = purple_cmd_do_command(conv, cmdline, mark ? mark : markup, error ? error : &err);
2421 	g_free(mark);
2422 	g_free(err);
2423 	return (status == PURPLE_CMD_STATUS_OK);
2424 }
2425 
2426 void *
purple_conversations_get_handle(void)2427 purple_conversations_get_handle(void)
2428 {
2429 	static int handle;
2430 
2431 	return &handle;
2432 }
2433 
2434 void
purple_conversations_init(void)2435 purple_conversations_init(void)
2436 {
2437 	void *handle = purple_conversations_get_handle();
2438 
2439 	conversation_cache = g_hash_table_new_full((GHashFunc)_purple_conversations_hconv_hash,
2440 						(GEqualFunc)_purple_conversations_hconv_equal,
2441 						(GDestroyNotify)_purple_conversations_hconv_free_key, NULL);
2442 
2443 	/**********************************************************************
2444 	 * Register preferences
2445 	 **********************************************************************/
2446 
2447 	/* Conversations */
2448 	purple_prefs_add_none("/purple/conversations");
2449 
2450 	/* Conversations -> Chat */
2451 	purple_prefs_add_none("/purple/conversations/chat");
2452 	purple_prefs_add_bool("/purple/conversations/chat/show_nick_change", TRUE);
2453 
2454 	/* Conversations -> IM */
2455 	purple_prefs_add_none("/purple/conversations/im");
2456 	purple_prefs_add_bool("/purple/conversations/im/send_typing", TRUE);
2457 
2458 
2459 	/**********************************************************************
2460 	 * Register signals
2461 	 **********************************************************************/
2462 	purple_signal_register(handle, "writing-im-msg",
2463 						 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_UINT,
2464 						 purple_value_new(PURPLE_TYPE_BOOLEAN), 5,
2465 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2466 										PURPLE_SUBTYPE_ACCOUNT),
2467 						 purple_value_new(PURPLE_TYPE_STRING),
2468 						 purple_value_new_outgoing(PURPLE_TYPE_STRING),
2469 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2470 										PURPLE_SUBTYPE_CONVERSATION),
2471 						 purple_value_new(PURPLE_TYPE_UINT));
2472 
2473 	purple_signal_register(handle, "wrote-im-msg",
2474 						 purple_marshal_VOID__POINTER_POINTER_POINTER_POINTER_UINT,
2475 						 NULL, 5,
2476 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2477 										PURPLE_SUBTYPE_ACCOUNT),
2478 						 purple_value_new(PURPLE_TYPE_STRING),
2479 						 purple_value_new(PURPLE_TYPE_STRING),
2480 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2481 										PURPLE_SUBTYPE_CONVERSATION),
2482 						 purple_value_new(PURPLE_TYPE_UINT));
2483 
2484 	purple_signal_register(handle, "sent-attention",
2485 						 purple_marshal_VOID__POINTER_POINTER_POINTER_UINT,
2486 						 NULL, 4,
2487 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2488 										PURPLE_SUBTYPE_ACCOUNT),
2489 						 purple_value_new(PURPLE_TYPE_STRING),
2490 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2491 										PURPLE_SUBTYPE_CONVERSATION),
2492 						 purple_value_new(PURPLE_TYPE_UINT));
2493 
2494 	purple_signal_register(handle, "got-attention",
2495 						 purple_marshal_VOID__POINTER_POINTER_POINTER_UINT,
2496 						 NULL, 4,
2497 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2498 										PURPLE_SUBTYPE_ACCOUNT),
2499 						 purple_value_new(PURPLE_TYPE_STRING),
2500 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2501 										PURPLE_SUBTYPE_CONVERSATION),
2502 						 purple_value_new(PURPLE_TYPE_UINT));
2503 
2504 	purple_signal_register(handle, "sending-im-msg",
2505 						 purple_marshal_VOID__POINTER_POINTER_POINTER,
2506 						 NULL, 3,
2507 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2508 										PURPLE_SUBTYPE_ACCOUNT),
2509 						 purple_value_new(PURPLE_TYPE_STRING),
2510 						 purple_value_new_outgoing(PURPLE_TYPE_STRING));
2511 
2512 	purple_signal_register(handle, "sent-im-msg",
2513 						 purple_marshal_VOID__POINTER_POINTER_POINTER,
2514 						 NULL, 3,
2515 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2516 										PURPLE_SUBTYPE_ACCOUNT),
2517 						 purple_value_new(PURPLE_TYPE_STRING),
2518 						 purple_value_new(PURPLE_TYPE_STRING));
2519 
2520 	purple_signal_register(handle, "receiving-im-msg",
2521 						 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER,
2522 						 purple_value_new(PURPLE_TYPE_BOOLEAN), 5,
2523 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2524 										PURPLE_SUBTYPE_ACCOUNT),
2525 						 purple_value_new_outgoing(PURPLE_TYPE_STRING),
2526 						 purple_value_new_outgoing(PURPLE_TYPE_STRING),
2527 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2528 										PURPLE_SUBTYPE_CONVERSATION),
2529 						 purple_value_new_outgoing(PURPLE_TYPE_UINT));
2530 
2531 	purple_signal_register(handle, "received-im-msg",
2532 						 purple_marshal_VOID__POINTER_POINTER_POINTER_POINTER_UINT,
2533 						 NULL, 5,
2534 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2535 										PURPLE_SUBTYPE_ACCOUNT),
2536 						 purple_value_new(PURPLE_TYPE_STRING),
2537 						 purple_value_new(PURPLE_TYPE_STRING),
2538 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2539 										PURPLE_SUBTYPE_CONVERSATION),
2540 						 purple_value_new(PURPLE_TYPE_UINT));
2541 
2542 	purple_signal_register(handle, "blocked-im-msg",
2543 						 purple_marshal_VOID__POINTER_POINTER_POINTER_UINT_UINT,
2544 						 NULL, 5,
2545 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2546 							 PURPLE_SUBTYPE_ACCOUNT),
2547 						 purple_value_new(PURPLE_TYPE_STRING),
2548 						 purple_value_new(PURPLE_TYPE_STRING),
2549 						 purple_value_new(PURPLE_TYPE_UINT),
2550 						 purple_value_new(PURPLE_TYPE_UINT));
2551 
2552 	purple_signal_register(handle, "writing-chat-msg",
2553 						 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_UINT,
2554 						 purple_value_new(PURPLE_TYPE_BOOLEAN), 5,
2555 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2556 										PURPLE_SUBTYPE_ACCOUNT),
2557 						 purple_value_new(PURPLE_TYPE_STRING),
2558 						 purple_value_new_outgoing(PURPLE_TYPE_STRING),
2559 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2560 										PURPLE_SUBTYPE_CONVERSATION),
2561 						 purple_value_new(PURPLE_TYPE_UINT));
2562 
2563 	purple_signal_register(handle, "wrote-chat-msg",
2564 						 purple_marshal_VOID__POINTER_POINTER_POINTER_POINTER_UINT,
2565 						 NULL, 5,
2566 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2567 										PURPLE_SUBTYPE_ACCOUNT),
2568 						 purple_value_new(PURPLE_TYPE_STRING),
2569 						 purple_value_new(PURPLE_TYPE_STRING),
2570 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2571 										PURPLE_SUBTYPE_CONVERSATION),
2572 						 purple_value_new(PURPLE_TYPE_UINT));
2573 
2574 	purple_signal_register(handle, "sending-chat-msg",
2575 						 purple_marshal_VOID__POINTER_POINTER_UINT, NULL, 3,
2576 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2577 										PURPLE_SUBTYPE_ACCOUNT),
2578 						 purple_value_new_outgoing(PURPLE_TYPE_STRING),
2579 						 purple_value_new(PURPLE_TYPE_UINT));
2580 
2581 	purple_signal_register(handle, "sent-chat-msg",
2582 						 purple_marshal_VOID__POINTER_POINTER_UINT, NULL, 3,
2583 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2584 										PURPLE_SUBTYPE_ACCOUNT),
2585 						 purple_value_new(PURPLE_TYPE_STRING),
2586 						 purple_value_new(PURPLE_TYPE_UINT));
2587 
2588 	purple_signal_register(handle, "receiving-chat-msg",
2589 						 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER,
2590 						 purple_value_new(PURPLE_TYPE_BOOLEAN), 5,
2591 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2592 										PURPLE_SUBTYPE_ACCOUNT),
2593 						 purple_value_new_outgoing(PURPLE_TYPE_STRING),
2594 						 purple_value_new_outgoing(PURPLE_TYPE_STRING),
2595 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2596 										PURPLE_SUBTYPE_CONVERSATION),
2597 						 purple_value_new_outgoing(PURPLE_TYPE_UINT));
2598 
2599 	purple_signal_register(handle, "received-chat-msg",
2600 						 purple_marshal_VOID__POINTER_POINTER_POINTER_POINTER_UINT,
2601 						 NULL, 5,
2602 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2603 										PURPLE_SUBTYPE_ACCOUNT),
2604 						 purple_value_new(PURPLE_TYPE_STRING),
2605 						 purple_value_new(PURPLE_TYPE_STRING),
2606 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2607 										PURPLE_SUBTYPE_CONVERSATION),
2608 						 purple_value_new(PURPLE_TYPE_UINT));
2609 
2610 	purple_signal_register(handle, "conversation-created",
2611 						 purple_marshal_VOID__POINTER, NULL, 1,
2612 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2613 										PURPLE_SUBTYPE_CONVERSATION));
2614 
2615 	purple_signal_register(handle, "conversation-updated",
2616 						 purple_marshal_VOID__POINTER_UINT, NULL, 2,
2617 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2618 										PURPLE_SUBTYPE_CONVERSATION),
2619 						 purple_value_new(PURPLE_TYPE_UINT));
2620 
2621 	purple_signal_register(handle, "deleting-conversation",
2622 						 purple_marshal_VOID__POINTER, NULL, 1,
2623 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2624 										PURPLE_SUBTYPE_CONVERSATION));
2625 
2626 	purple_signal_register(handle, "buddy-typing",
2627 						 purple_marshal_VOID__POINTER_POINTER, NULL, 2,
2628 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2629 										PURPLE_SUBTYPE_ACCOUNT),
2630 						 purple_value_new(PURPLE_TYPE_STRING));
2631 
2632 	purple_signal_register(handle, "buddy-typed",
2633 						 purple_marshal_VOID__POINTER_POINTER, NULL, 2,
2634 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2635 										PURPLE_SUBTYPE_ACCOUNT),
2636 						 purple_value_new(PURPLE_TYPE_STRING));
2637 
2638 	purple_signal_register(handle, "buddy-typing-stopped",
2639 						 purple_marshal_VOID__POINTER_POINTER, NULL, 2,
2640 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2641 										PURPLE_SUBTYPE_ACCOUNT),
2642 						 purple_value_new(PURPLE_TYPE_STRING));
2643 
2644 	purple_signal_register(handle, "chat-buddy-joining",
2645 						 purple_marshal_BOOLEAN__POINTER_POINTER_UINT,
2646 						 purple_value_new(PURPLE_TYPE_BOOLEAN), 3,
2647 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2648 										PURPLE_SUBTYPE_CONVERSATION),
2649 						 purple_value_new(PURPLE_TYPE_STRING),
2650 						 purple_value_new(PURPLE_TYPE_UINT));
2651 
2652 	purple_signal_register(handle, "chat-buddy-joined",
2653 						 purple_marshal_VOID__POINTER_POINTER_UINT_UINT, NULL, 4,
2654 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2655 										PURPLE_SUBTYPE_CONVERSATION),
2656 						 purple_value_new(PURPLE_TYPE_STRING),
2657 						 purple_value_new(PURPLE_TYPE_UINT),
2658 						 purple_value_new(PURPLE_TYPE_BOOLEAN));
2659 
2660 	purple_signal_register(handle, "chat-buddy-flags",
2661 						 purple_marshal_VOID__POINTER_POINTER_UINT_UINT, NULL, 4,
2662 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2663 										PURPLE_SUBTYPE_CONVERSATION),
2664 						 purple_value_new(PURPLE_TYPE_STRING),
2665 						 purple_value_new(PURPLE_TYPE_UINT),
2666 						 purple_value_new(PURPLE_TYPE_UINT));
2667 
2668 	purple_signal_register(handle, "chat-buddy-leaving",
2669 						 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER,
2670 						 purple_value_new(PURPLE_TYPE_BOOLEAN), 3,
2671 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2672 										PURPLE_SUBTYPE_CONVERSATION),
2673 						 purple_value_new(PURPLE_TYPE_STRING),
2674 						 purple_value_new(PURPLE_TYPE_STRING));
2675 
2676 	purple_signal_register(handle, "chat-buddy-left",
2677 						 purple_marshal_VOID__POINTER_POINTER_POINTER, NULL, 3,
2678 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2679 										PURPLE_SUBTYPE_CONVERSATION),
2680 						 purple_value_new(PURPLE_TYPE_STRING),
2681 						 purple_value_new(PURPLE_TYPE_STRING));
2682 
2683 	purple_signal_register(handle, "deleting-chat-buddy",
2684 						 purple_marshal_VOID__POINTER, NULL, 1,
2685 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2686 										PURPLE_SUBTYPE_CHATBUDDY));
2687 
2688 	purple_signal_register(handle, "chat-inviting-user",
2689 						 purple_marshal_VOID__POINTER_POINTER_POINTER, NULL, 3,
2690 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2691 										PURPLE_SUBTYPE_CONVERSATION),
2692 						 purple_value_new(PURPLE_TYPE_STRING),
2693 						 purple_value_new_outgoing(PURPLE_TYPE_STRING));
2694 
2695 	purple_signal_register(handle, "chat-invited-user",
2696 						 purple_marshal_VOID__POINTER_POINTER_POINTER, NULL, 3,
2697 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2698 										PURPLE_SUBTYPE_CONVERSATION),
2699 						 purple_value_new(PURPLE_TYPE_STRING),
2700 						 purple_value_new(PURPLE_TYPE_STRING));
2701 
2702 	purple_signal_register(handle, "chat-invited",
2703 						 purple_marshal_INT__POINTER_POINTER_POINTER_POINTER_POINTER,
2704 						 purple_value_new(PURPLE_TYPE_INT), 5,
2705 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2706 										PURPLE_SUBTYPE_ACCOUNT),
2707 						 purple_value_new(PURPLE_TYPE_STRING),
2708 						 purple_value_new(PURPLE_TYPE_STRING),
2709 						 purple_value_new(PURPLE_TYPE_STRING),
2710 						 purple_value_new(PURPLE_TYPE_POINTER));
2711 
2712 	purple_signal_register(handle, "chat-invite-blocked",
2713 						 purple_marshal_VOID__POINTER_POINTER_POINTER_POINTER_POINTER,
2714 						 NULL, 5,
2715 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2716 							 PURPLE_SUBTYPE_ACCOUNT),
2717 						 purple_value_new(PURPLE_TYPE_STRING),
2718 						 purple_value_new(PURPLE_TYPE_STRING),
2719 						 purple_value_new(PURPLE_TYPE_STRING),
2720 						 purple_value_new(PURPLE_TYPE_BOXED, "GHashTable *"));
2721 
2722 	purple_signal_register(handle, "chat-joined",
2723 						 purple_marshal_VOID__POINTER, NULL, 1,
2724 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2725 										PURPLE_SUBTYPE_CONVERSATION));
2726 
2727 	purple_signal_register(handle, "chat-join-failed",
2728 						   purple_marshal_VOID__POINTER_POINTER, NULL, 2,
2729 						   purple_value_new(PURPLE_TYPE_SUBTYPE,
2730 										PURPLE_SUBTYPE_CONNECTION),
2731 						   purple_value_new(PURPLE_TYPE_POINTER));
2732 
2733 	purple_signal_register(handle, "chat-left",
2734 						 purple_marshal_VOID__POINTER, NULL, 1,
2735 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2736 										PURPLE_SUBTYPE_CONVERSATION));
2737 
2738 	purple_signal_register(handle, "chat-topic-changed",
2739 						 purple_marshal_VOID__POINTER_POINTER_POINTER, NULL, 3,
2740 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
2741 										PURPLE_SUBTYPE_CONVERSATION),
2742 						 purple_value_new(PURPLE_TYPE_STRING),
2743 						 purple_value_new(PURPLE_TYPE_STRING));
2744 
2745 	purple_signal_register(handle, "cleared-message-history",
2746 	                       purple_marshal_VOID__POINTER, NULL, 1,
2747 	                       purple_value_new(PURPLE_TYPE_SUBTYPE,
2748 	                                        PURPLE_SUBTYPE_CONVERSATION));
2749 
2750 	purple_signal_register(handle, "conversation-extended-menu",
2751 			     purple_marshal_VOID__POINTER_POINTER, NULL, 2,
2752 			     purple_value_new(PURPLE_TYPE_SUBTYPE,
2753 					    PURPLE_SUBTYPE_CONVERSATION),
2754 			     purple_value_new(PURPLE_TYPE_BOXED, "GList **"));
2755 }
2756 
2757 void
purple_conversations_uninit(void)2758 purple_conversations_uninit(void)
2759 {
2760 	while (conversations)
2761 		purple_conversation_destroy((PurpleConversation*)conversations->data);
2762 	g_hash_table_destroy(conversation_cache);
2763 	purple_signals_unregister_by_instance(purple_conversations_get_handle());
2764 }
2765 
2766