1 /*
2  * Hangouts Plugin for libpurple/Pidgin
3  * Copyright (c) 2015-2016 Eion Robb, Mike Ruprecht
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "libhangouts.h"
20 
21 #include <stdlib.h>
22 #include <string.h>
23 #include <stdio.h>
24 #include <glib.h>
25 
26 #include "accountopt.h"
27 #include "cmds.h"
28 #include "debug.h"
29 #include "mediamanager.h"
30 #include "plugins.h"
31 #include "request.h"
32 #include "version.h"
33 
34 #include "hangouts_auth.h"
35 #include "hangouts_pblite.h"
36 #include "hangouts_json.h"
37 #include "hangouts_events.h"
38 #include "hangouts_connection.h"
39 #include "hangouts_conversation.h"
40 #include "hangouts_media.h"
41 
42 
43 /*****************************************************************************/
44 //TODO move to nicer place
45 
46 gboolean
hangouts_is_valid_id(const gchar * id)47 hangouts_is_valid_id(const gchar *id)
48 {
49 	gint i;
50 
51 	for (i = strlen(id) - 1; i >= 0; i--) {
52 		if (!g_ascii_isdigit(id[i])) {
53 			return FALSE;
54 		}
55 	}
56 
57 	return TRUE;
58 }
59 
60 PurpleMediaCaps
hangouts_get_media_caps(PurpleAccount * account,const char * who)61 hangouts_get_media_caps(PurpleAccount *account, const char *who)
62 {
63 	return PURPLE_MEDIA_CAPS_AUDIO | PURPLE_MEDIA_CAPS_VIDEO | PURPLE_MEDIA_CAPS_AUDIO_VIDEO | PURPLE_MEDIA_CAPS_MODIFY_SESSION;
64 }
65 
66 void
hangouts_set_idle(PurpleConnection * pc,int time)67 hangouts_set_idle(PurpleConnection *pc, int time)
68 {
69 	HangoutsAccount *ha;
70 
71 	ha = purple_connection_get_protocol_data(pc);
72 
73 	if (time < HANGOUTS_ACTIVE_CLIENT_TIMEOUT) {
74 		hangouts_set_active_client(pc);
75 	}
76 	ha->idle_time = time;
77 }
78 
79 static GList *
hangouts_add_account_options(GList * account_options)80 hangouts_add_account_options(GList *account_options)
81 {
82 	PurpleAccountOption *option;
83 
84 	option = purple_account_option_bool_new(N_("Show call links in chat"), "show-call-links", !purple_media_manager_get());
85 	account_options = g_list_append(account_options, option);
86 
87 	option = purple_account_option_bool_new(N_("Un-Googlify URLs"), "unravel_google_url", FALSE);
88 	account_options = g_list_append(account_options, option);
89 
90 	option = purple_account_option_bool_new(N_("Treat invisible users as offline"), "treat_invisible_as_offline", FALSE);
91 	account_options = g_list_append(account_options, option);
92 
93 	option = purple_account_option_bool_new(N_("Hide self from buddy list (requires reconnect)"), "hide_self", FALSE);
94 	account_options = g_list_append(account_options, option);
95 
96 	option = purple_account_option_bool_new(N_("Fetch image history when opening group chats"), "fetch_image_history", TRUE);
97 	account_options = g_list_append(account_options, option);
98 
99 	return account_options;
100 }
101 
102 static void
hangouts_blist_node_removed(PurpleBlistNode * node)103 hangouts_blist_node_removed(PurpleBlistNode *node)
104 {
105 	PurpleChat *chat = NULL;
106 	PurpleBuddy *buddy = NULL;
107 	PurpleAccount *account = NULL;
108 	PurpleConnection *pc;
109 	const gchar *conv_id;
110 	GHashTable *components;
111 
112 	if (PURPLE_IS_CHAT(node)) {
113 		chat = PURPLE_CHAT(node);
114 		account = purple_chat_get_account(chat);
115 	} else if (PURPLE_IS_BUDDY(node)) {
116 		buddy = PURPLE_BUDDY(node);
117 		account = purple_buddy_get_account(buddy);
118 	}
119 
120 	if (account == NULL) {
121 		return;
122 	}
123 
124 	if (!purple_strequal(purple_account_get_protocol_id(account), HANGOUTS_PLUGIN_ID)) {
125 		return;
126 	}
127 
128 	pc = purple_account_get_connection(account);
129 	if (pc == NULL) {
130 		return;
131 	}
132 
133 	if (chat != NULL) {
134 		components = purple_chat_get_components(chat);
135 		conv_id = g_hash_table_lookup(components, "conv_id");
136 		if (conv_id == NULL) {
137 			conv_id = purple_chat_get_name_only(chat);
138 		}
139 
140 		hangouts_chat_leave_by_conv_id(pc, conv_id, NULL);
141 	} else {
142 		HangoutsAccount *ha = purple_connection_get_protocol_data(pc);
143 		const gchar *gaia_id = purple_buddy_get_name(buddy);
144 		conv_id = g_hash_table_lookup(ha->one_to_ones_rev, gaia_id);
145 
146 		hangouts_archive_conversation(ha, conv_id);
147 
148 		if (purple_strequal(gaia_id, ha->self_gaia_id)) {
149 			purple_account_set_bool(account, "hide_self", TRUE);
150 		}
151 	}
152 }
153 
154 static void
hangouts_blist_node_aliased(PurpleBlistNode * node,const char * old_alias)155 hangouts_blist_node_aliased(PurpleBlistNode *node, const char *old_alias)
156 {
157 	PurpleChat *chat = NULL;
158 	PurpleAccount *account = NULL;
159 	PurpleConnection *pc;
160 	const gchar *conv_id;
161 	GHashTable *components;
162 	HangoutsAccount *ha;
163 	const gchar *new_alias;
164 
165 	if (PURPLE_IS_CHAT(node)) {
166 		chat = PURPLE_CHAT(node);
167 		account = purple_chat_get_account(chat);
168 	}
169 
170 	if (account == NULL) {
171 		return;
172 	}
173 
174 	if (!purple_strequal(purple_account_get_protocol_id(account), HANGOUTS_PLUGIN_ID)) {
175 		return;
176 	}
177 
178 	pc = purple_account_get_connection(account);
179 	if (pc == NULL) {
180 		return;
181 	}
182 	ha = purple_connection_get_protocol_data(pc);
183 
184 	if (g_dataset_get_data(ha, "ignore_set_alias")) {
185 		return;
186 	}
187 
188 	if (chat != NULL) {
189 		new_alias = purple_chat_get_alias(chat);
190 
191 		// Don't send update to existing update
192 		if (!purple_strequal(old_alias, new_alias)) {
193 			components = purple_chat_get_components(chat);
194 			conv_id = g_hash_table_lookup(components, "conv_id");
195 			if (conv_id == NULL) {
196 				conv_id = purple_chat_get_name_only(chat);
197 			}
198 
199 			hangouts_rename_conversation(ha, conv_id, new_alias);
200 		}
201 	}
202 }
203 
204 static void
hangouts_chat_set_topic(PurpleConnection * pc,int id,const char * topic)205 hangouts_chat_set_topic(PurpleConnection *pc, int id, const char *topic)
206 {
207 	const gchar *conv_id;
208 	PurpleChatConversation *chatconv;
209 	HangoutsAccount *ha;
210 
211 	ha = purple_connection_get_protocol_data(pc);
212 	chatconv = purple_conversations_find_chat(pc, id);
213 	conv_id = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "conv_id");
214 	if (conv_id == NULL) {
215 		// Fix for a race condition around the chat data and serv_got_joined_chat()
216 		conv_id = purple_conversation_get_name(PURPLE_CONVERSATION(chatconv));
217 	}
218 
219 	return hangouts_rename_conversation(ha, conv_id, topic);
220 }
221 
222 static PurpleCmdRet
hangouts_cmd_leave(PurpleConversation * conv,const gchar * cmd,gchar ** args,gchar ** error,void * data)223 hangouts_cmd_leave(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
224 {
225 	PurpleConnection *pc = NULL;
226 	int id = -1;
227 
228 	pc = purple_conversation_get_connection(conv);
229 	id = purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv));
230 
231 	if (pc == NULL || id == -1)
232 		return PURPLE_CMD_RET_FAILED;
233 
234 	hangouts_chat_leave(pc, id);
235 
236 	return PURPLE_CMD_RET_OK;
237 }
238 
239 static PurpleCmdRet
hangouts_cmd_kick(PurpleConversation * conv,const gchar * cmd,gchar ** args,gchar ** error,void * data)240 hangouts_cmd_kick(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
241 {
242 	PurpleConnection *pc = NULL;
243 	int id = -1;
244 
245 	pc = purple_conversation_get_connection(conv);
246 	id = purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv));
247 
248 	if (pc == NULL || id == -1)
249 		return PURPLE_CMD_RET_FAILED;
250 
251 	hangouts_chat_kick(pc, id, args[0]);
252 
253 	return PURPLE_CMD_RET_OK;
254 }
255 
256 static GList *
hangouts_node_menu(PurpleBlistNode * node)257 hangouts_node_menu(PurpleBlistNode *node)
258 {
259 	GList *m = NULL;
260 	PurpleMenuAction *act;
261 
262 	if(PURPLE_IS_BUDDY(node))
263 	{
264 		act = purple_menu_action_new(_("Initiate _Chat"),
265 					PURPLE_CALLBACK(hangouts_initiate_chat_from_node),
266 					NULL, NULL);
267 		m = g_list_append(m, act);
268 	} else if (PURPLE_IS_CHAT(node)) {
269 		act = purple_menu_action_new(_("_Leave Chat"),
270 					PURPLE_CALLBACK(hangouts_blist_node_removed), // A strange coinkidink
271 					NULL, NULL);
272 		m = g_list_append(m, act);
273 	}
274 
275 	return m;
276 }
277 
278 static void
hangouts_join_chat_by_url_action(PurpleProtocolAction * action)279 hangouts_join_chat_by_url_action(PurpleProtocolAction *action)
280 {
281 	PurpleConnection *pc = purple_protocol_action_get_connection(action);
282 	HangoutsAccount *ha = purple_connection_get_protocol_data(pc);
283 
284 	purple_request_input(pc, _("Join chat..."),
285 					   _("Join a Hangouts group chat from the invite URL..."),
286 					   NULL,
287 					   NULL, FALSE, FALSE, "https://hangouts.google.com/group/...",
288 					   _("_Join"), G_CALLBACK(hangouts_join_chat_from_url),
289 					   _("_Cancel"), NULL,
290 					   purple_request_cpar_from_connection(pc),
291 					   ha);
292 
293 }
294 
295 static GList *
hangouts_actions(PurplePlugin * plugin,gpointer context)296 hangouts_actions(
297 #if !PURPLE_VERSION_CHECK(3, 0, 0)
298 PurplePlugin *plugin, gpointer context
299 #else
300 PurpleConnection *pc
301 #endif
302 )
303 {
304 	GList *m = NULL;
305 	PurpleProtocolAction *act;
306 
307 	act = purple_protocol_action_new(_("Search for friends..."), hangouts_search_users);
308 	m = g_list_append(m, act);
309 
310 	act = purple_protocol_action_new(_("Join a group chat by URL..."), hangouts_join_chat_by_url_action);
311 	m = g_list_append(m, act);
312 
313 	return m;
314 }
315 
316 static void
hangouts_authcode_input_cb(gpointer user_data,const gchar * auth_code)317 hangouts_authcode_input_cb(gpointer user_data, const gchar *auth_code)
318 {
319 	HangoutsAccount *ha = user_data;
320 	PurpleConnection *pc = ha->pc;
321 
322 	purple_connection_update_progress(pc, _("Authenticating"), 1, 3);
323 	hangouts_oauth_with_code(ha, auth_code);
324 }
325 
326 static void
hangouts_authcode_input_cancel_cb(gpointer user_data)327 hangouts_authcode_input_cancel_cb(gpointer user_data)
328 {
329 	HangoutsAccount *ha = user_data;
330 	purple_connection_error(ha->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
331 		_("User cancelled authorization"));
332 }
333 
334 static gulong chat_conversation_typing_signal = 0;
335 
336 #if !PURPLE_VERSION_CHECK(3, 0, 0)
337 static gulong deleting_chat_buddy_signal = 0;
338 
339 // See workaround for purple_chat_conversation_find_user() in purplecompat.h
340 static void
hangouts_deleting_chat_buddy(PurpleConvChatBuddy * cb)341 hangouts_deleting_chat_buddy(PurpleConvChatBuddy *cb)
342 {
343 	if (g_dataset_get_data(cb, "chat") != NULL) {
344 		g_dataset_destroy(cb);
345 	}
346 }
347 #endif
348 
349 static void
hangouts_login(PurpleAccount * account)350 hangouts_login(PurpleAccount *account)
351 {
352 	PurpleConnection *pc;
353 	HangoutsAccount *ha; //hahaha
354 	const gchar *password;
355 	const gchar *self_gaia_id;
356 	PurpleConnectionFlags pc_flags;
357 
358 	pc = purple_account_get_connection(account);
359 	password = purple_connection_get_password(pc);
360 
361 	pc_flags = purple_connection_get_flags(pc);
362 	pc_flags |= PURPLE_CONNECTION_FLAG_HTML;
363 	pc_flags |= PURPLE_CONNECTION_FLAG_NO_FONTSIZE;
364 	pc_flags |= PURPLE_CONNECTION_FLAG_NO_BGCOLOR;
365 	pc_flags &= ~PURPLE_CONNECTION_FLAG_NO_IMAGES;
366 	purple_connection_set_flags(pc, pc_flags);
367 
368 	ha = g_new0(HangoutsAccount, 1);
369 	ha->account = account;
370 	ha->pc = pc;
371 	ha->cookie_jar = purple_http_cookie_jar_new();
372 	ha->channel_buffer = g_byte_array_sized_new(HANGOUTS_BUFFER_DEFAULT_SIZE);
373 	ha->channel_keepalive_pool = purple_http_keepalive_pool_new();
374 	ha->client6_keepalive_pool = purple_http_keepalive_pool_new();
375 	ha->sent_message_ids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
376 
377 	ha->one_to_ones = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
378 	ha->one_to_ones_rev = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
379 	ha->group_chats = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
380 	ha->google_voice_conversations = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
381 
382 	self_gaia_id = purple_account_get_string(account, "self_gaia_id", NULL);
383 	if (self_gaia_id != NULL) {
384 		ha->self_gaia_id = g_strdup(self_gaia_id);
385 		purple_connection_set_display_name(pc, ha->self_gaia_id);
386 	}
387 
388 	purple_connection_set_protocol_data(pc, ha);
389 
390 	if (password && *password) {
391 		ha->refresh_token = g_strdup(password);
392 		purple_connection_update_progress(pc, _("Authenticating"), 1, 3);
393 		hangouts_oauth_refresh_token(ha);
394 	} else {
395 		//TODO get this code automatically
396 		purple_notify_uri(pc, "https://www.youtube.com/watch?v=hlDhp-eNLMU");
397 		purple_request_input(pc, _("Authorization Code"), "https://www.youtube.com/watch?v=hlDhp-eNLMU",
398 			_ ("Please follow the YouTube video to get the OAuth code"),
399 			_ ("and then paste the Google OAuth code here"), FALSE, FALSE, NULL,
400 			_("OK"), G_CALLBACK(hangouts_authcode_input_cb),
401 			_("Cancel"), G_CALLBACK(hangouts_authcode_input_cancel_cb),
402 			purple_request_cpar_from_connection(pc), ha);
403 	}
404 
405 	purple_signal_connect(purple_blist_get_handle(), "blist-node-removed", account, PURPLE_CALLBACK(hangouts_blist_node_removed), NULL);
406 	purple_signal_connect(purple_blist_get_handle(), "blist-node-aliased", account, PURPLE_CALLBACK(hangouts_blist_node_aliased), NULL);
407 	purple_signal_connect(purple_conversations_get_handle(), "conversation-updated", account, PURPLE_CALLBACK(hangouts_mark_conversation_seen), NULL);
408 	if (!chat_conversation_typing_signal) {
409 		chat_conversation_typing_signal = purple_signal_connect(purple_conversations_get_handle(), "chat-conversation-typing", purple_connection_get_protocol(pc), PURPLE_CALLBACK(hangouts_conv_send_typing), NULL);
410 	}
411 
412 #if !PURPLE_VERSION_CHECK(3, 0, 0)
413 	if (!deleting_chat_buddy_signal) {
414 		deleting_chat_buddy_signal = purple_signal_connect(purple_conversations_get_handle(), "deleting-chat-buddy", purple_connection_get_protocol(pc), PURPLE_CALLBACK(hangouts_deleting_chat_buddy), NULL);
415 	}
416 #endif
417 
418 	ha->active_client_timeout = g_timeout_add_seconds(HANGOUTS_ACTIVE_CLIENT_TIMEOUT, ((GSourceFunc) hangouts_set_active_client), pc);
419 }
420 
421 static void
hangouts_close(PurpleConnection * pc)422 hangouts_close(PurpleConnection *pc)
423 {
424 	HangoutsAccount *ha; //not so funny anymore
425 
426 	ha = purple_connection_get_protocol_data(pc);
427 	purple_signals_disconnect_by_handle(ha->account);
428 
429 	g_source_remove(ha->active_client_timeout);
430 	g_source_remove(ha->channel_watchdog);
431 	g_source_remove(ha->poll_buddy_status_timeout);
432 
433 	purple_http_conn_cancel_all(pc);
434 
435 	purple_http_keepalive_pool_unref(ha->channel_keepalive_pool);
436 	purple_http_keepalive_pool_unref(ha->client6_keepalive_pool);
437 	g_free(ha->self_gaia_id);
438 	g_free(ha->self_phone);
439 	g_free(ha->refresh_token);
440 	g_free(ha->access_token);
441 	g_free(ha->gsessionid_param);
442 	g_free(ha->sid_param);
443 	g_free(ha->client_id);
444 	purple_http_cookie_jar_unref(ha->cookie_jar);
445 	g_byte_array_free(ha->channel_buffer, TRUE);
446 
447 	g_hash_table_remove_all(ha->sent_message_ids);
448 	g_hash_table_unref(ha->sent_message_ids);
449 	g_hash_table_remove_all(ha->one_to_ones);
450 	g_hash_table_unref(ha->one_to_ones);
451 	g_hash_table_remove_all(ha->one_to_ones_rev);
452 	g_hash_table_unref(ha->one_to_ones_rev);
453 	g_hash_table_remove_all(ha->group_chats);
454 	g_hash_table_unref(ha->group_chats);
455 	g_hash_table_remove_all(ha->google_voice_conversations);
456 	g_hash_table_unref(ha->google_voice_conversations);
457 
458 	g_free(ha);
459 }
460 
461 
462 static const char *
hangouts_list_icon(PurpleAccount * account,PurpleBuddy * buddy)463 hangouts_list_icon(PurpleAccount *account, PurpleBuddy *buddy)
464 {
465 	return "hangouts";
466 }
467 
468 static void
hangouts_tooltip_text(PurpleBuddy * buddy,PurpleNotifyUserInfo * user_info,gboolean full)469 hangouts_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
470 {
471 	PurplePresence *presence;
472 	PurpleStatus *status;
473 	const gchar *message;
474 	HangoutsBuddy *hbuddy;
475 
476 	g_return_if_fail(buddy != NULL);
477 
478 	presence = purple_buddy_get_presence(buddy);
479 	status = purple_presence_get_active_status(presence);
480 	purple_notify_user_info_add_pair_html(user_info, _("Status"), purple_status_get_name(status));
481 
482 	message = purple_status_get_attr_string(status, "message");
483 	if (message != NULL) {
484 		purple_notify_user_info_add_pair_html(user_info, _("Message"), message);
485 	}
486 
487 	hbuddy = purple_buddy_get_protocol_data(buddy);
488 	if (hbuddy != NULL) {
489 		if (hbuddy->last_seen != 0) {
490 			gchar *seen = purple_str_seconds_to_string(time(NULL) - hbuddy->last_seen);
491 			purple_notify_user_info_add_pair_html(user_info, _("Last seen"), seen);
492 			g_free(seen);
493 		}
494 
495 		if (hbuddy->in_call) {
496 			purple_notify_user_info_add_pair_html(user_info, _("In call"), NULL);
497 		}
498 
499 		if (hbuddy->device_type) {
500 			purple_notify_user_info_add_pair_html(user_info, _("Device Type"),
501 				hbuddy->device_type & HANGOUTS_DEVICE_TYPE_DESKTOP ? _("Desktop") :
502 				hbuddy->device_type & HANGOUTS_DEVICE_TYPE_TABLET ? _("Tablet") :
503 				hbuddy->device_type & HANGOUTS_DEVICE_TYPE_MOBILE ? _("Mobile") :
504 				_("Unknown"));
505 		}
506 	}
507 }
508 
509 GList *
hangouts_status_types(PurpleAccount * account)510 hangouts_status_types(PurpleAccount *account)
511 {
512 	GList *types = NULL;
513 	PurpleStatusType *status;
514 
515 	status = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE, TRUE, FALSE, "message", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), NULL);
516 	types = g_list_append(types, status);
517 
518 	status = purple_status_type_new_with_attrs(PURPLE_STATUS_AWAY, NULL, NULL, FALSE, TRUE, FALSE, "message", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), NULL);
519 	types = g_list_append(types, status);
520 
521 	status = purple_status_type_new_with_attrs(PURPLE_STATUS_EXTENDED_AWAY, NULL, NULL, FALSE, FALSE, FALSE, "message", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), NULL);
522 	types = g_list_append(types, status);
523 
524 	status = purple_status_type_new_with_attrs(PURPLE_STATUS_UNAVAILABLE, NULL, _("Do Not Disturb"), FALSE, TRUE, FALSE, "message", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), NULL);
525 	types = g_list_append(types, status);
526 
527 	status = purple_status_type_new_full(PURPLE_STATUS_MOBILE, "mobile", _("Phone"), FALSE, FALSE, FALSE);
528 	types = g_list_append(types, status);
529 
530 	status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
531 	types = g_list_append(types, status);
532 
533 	status = purple_status_type_new_with_attrs(PURPLE_STATUS_OFFLINE, "gone", NULL, FALSE, FALSE, FALSE, "message", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), NULL);
534 	types = g_list_append(types, status);
535 
536 	status = purple_status_type_new_with_attrs(PURPLE_STATUS_INVISIBLE, NULL, NULL, FALSE, FALSE, FALSE, "message", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), NULL);
537 	types = g_list_append(types, status);
538 
539 	return types;
540 }
541 
542 static gchar *
hangouts_status_text(PurpleBuddy * buddy)543 hangouts_status_text(PurpleBuddy *buddy)
544 {
545 	const gchar *message = purple_status_get_attr_string(purple_presence_get_active_status(purple_buddy_get_presence(buddy)), "message");
546 
547 	if (message == NULL) {
548 		return NULL;
549 	}
550 
551 	return g_markup_printf_escaped("%s", message);
552 }
553 
554 static void
hangouts_buddy_free(PurpleBuddy * buddy)555 hangouts_buddy_free(PurpleBuddy *buddy)
556 {
557 	HangoutsBuddy *hbuddy = purple_buddy_get_protocol_data(buddy);
558 
559 	g_return_if_fail(hbuddy != NULL);
560 
561 	g_free(hbuddy);
562 }
563 
564 static gboolean
hangouts_offline_message(const PurpleBuddy * buddy)565 hangouts_offline_message(const PurpleBuddy *buddy)
566 {
567 	return TRUE;
568 }
569 
570 
571 /*****************************************************************************/
572 
573 static gboolean
plugin_load(PurplePlugin * plugin,GError ** error)574 plugin_load(PurplePlugin *plugin, GError **error)
575 {
576 	purple_cmd_register("leave", "", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
577 						PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
578 						HANGOUTS_PLUGIN_ID, hangouts_cmd_leave,
579 						_("leave:  Leave the group chat"), NULL);
580 
581 	purple_cmd_register("kick", "s", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
582 						PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
583 						HANGOUTS_PLUGIN_ID, hangouts_cmd_kick,
584 						_("kick <user>:  Kick a user from the room."), NULL);
585 
586 	return TRUE;
587 }
588 
589 static gboolean
plugin_unload(PurplePlugin * plugin,GError ** error)590 plugin_unload(PurplePlugin *plugin, GError **error)
591 {
592 	purple_signals_disconnect_by_handle(plugin);
593 
594 	return TRUE;
595 }
596 
597 #if PURPLE_VERSION_CHECK(3, 0, 0)
598 
599 G_MODULE_EXPORT GType hangouts_protocol_get_type(void);
600 #define HANGOUTS_TYPE_PROTOCOL			(hangouts_protocol_get_type())
601 #define HANGOUTS_PROTOCOL(obj)			(G_TYPE_CHECK_INSTANCE_CAST((obj), HANGOUTS_TYPE_PROTOCOL, HangoutsProtocol))
602 #define HANGOUTS_PROTOCOL_CLASS(klass)		(G_TYPE_CHECK_CLASS_CAST((klass), HANGOUTS_TYPE_PROTOCOL, HangoutsProtocolClass))
603 #define HANGOUTS_IS_PROTOCOL(obj)		(G_TYPE_CHECK_INSTANCE_TYPE((obj), HANGOUTS_TYPE_PROTOCOL))
604 #define HANGOUTS_IS_PROTOCOL_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE((klass), HANGOUTS_TYPE_PROTOCOL))
605 #define HANGOUTS_PROTOCOL_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS((obj), HANGOUTS_TYPE_PROTOCOL, HangoutsProtocolClass))
606 
607 typedef struct _HangoutsProtocol
608 {
609 	PurpleProtocol parent;
610 } HangoutsProtocol;
611 
612 typedef struct _HangoutsProtocolClass
613 {
614 	PurpleProtocolClass parent_class;
615 } HangoutsProtocolClass;
616 
617 static void
hangouts_protocol_init(PurpleProtocol * prpl_info)618 hangouts_protocol_init(PurpleProtocol *prpl_info)
619 {
620 	PurpleProtocol *plugin = prpl_info, *info = prpl_info;
621 
622 	info->id = HANGOUTS_PLUGIN_ID;
623 	info->name = "Hangouts";
624 
625 	prpl_info->options = OPT_PROTO_NO_PASSWORD | OPT_PROTO_CHAT_TOPIC | OPT_PROTO_MAIL_CHECK;
626 	prpl_info->account_options = hangouts_add_account_options(prpl_info->account_options);
627 
628 	purple_signal_register(plugin, "hangouts-received-stateupdate",
629 			purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2,
630 			PURPLE_TYPE_CONNECTION,
631 			G_TYPE_OBJECT);
632 
633 	purple_signal_register(plugin, "hangouts-gmail-notification",
634 			purple_marshal_VOID__POINTER_POINTER_POINTER, G_TYPE_NONE, 3,
635 			PURPLE_TYPE_CONNECTION,
636 			G_TYPE_STRING,
637 			G_TYPE_OBJECT);
638 
639 	hangouts_register_events(plugin);
640 }
641 
642 static void
hangouts_protocol_class_init(PurpleProtocolClass * prpl_info)643 hangouts_protocol_class_init(PurpleProtocolClass *prpl_info)
644 {
645 	prpl_info->login = hangouts_login;
646 	prpl_info->close = hangouts_close;
647 	prpl_info->status_types = hangouts_status_types;
648 	prpl_info->list_icon = hangouts_list_icon;
649 }
650 
651 static void
hangouts_protocol_client_iface_init(PurpleProtocolClientIface * prpl_info)652 hangouts_protocol_client_iface_init(PurpleProtocolClientIface *prpl_info)
653 {
654 	prpl_info->get_actions = hangouts_actions;
655 	prpl_info->blist_node_menu = hangouts_node_menu;
656 	prpl_info->status_text = hangouts_status_text;
657 	prpl_info->tooltip_text = hangouts_tooltip_text;
658 	prpl_info->buddy_free = hangouts_buddy_free;
659  	prpl_info->offline_message = hangouts_offline_message;
660 }
661 
662 static void
hangouts_protocol_server_iface_init(PurpleProtocolServerIface * prpl_info)663 hangouts_protocol_server_iface_init(PurpleProtocolServerIface *prpl_info)
664 {
665 	prpl_info->get_info = hangouts_get_info;
666 	prpl_info->set_status = hangouts_set_status;
667 	prpl_info->set_idle = hangouts_set_idle;
668 }
669 
670 static void
hangouts_protocol_privacy_iface_init(PurpleProtocolPrivacyIface * prpl_info)671 hangouts_protocol_privacy_iface_init(PurpleProtocolPrivacyIface *prpl_info)
672 {
673 	prpl_info->add_deny = hangouts_block_user;
674 	prpl_info->rem_deny = hangouts_unblock_user;
675 }
676 
677 static void
hangouts_protocol_im_iface_init(PurpleProtocolIMIface * prpl_info)678 hangouts_protocol_im_iface_init(PurpleProtocolIMIface *prpl_info)
679 {
680 	prpl_info->send = hangouts_send_im;
681 	prpl_info->send_typing = hangouts_send_typing;
682 }
683 
684 static void
hangouts_protocol_chat_iface_init(PurpleProtocolChatIface * prpl_info)685 hangouts_protocol_chat_iface_init(PurpleProtocolChatIface *prpl_info)
686 {
687 	prpl_info->send = hangouts_chat_send;
688 	prpl_info->info = hangouts_chat_info;
689 	prpl_info->info_defaults = hangouts_chat_info_defaults;
690 	prpl_info->join = hangouts_join_chat;
691 	prpl_info->get_name = hangouts_get_chat_name;
692 	prpl_info->invite = hangouts_chat_invite;
693 	prpl_info->set_topic = hangouts_chat_set_topic;
694 }
695 
696 static void
hangouts_protocol_media_iface_init(PurpleProtocolMediaIface * prpl_info)697 hangouts_protocol_media_iface_init(PurpleProtocolMediaIface *prpl_info)
698 {
699 	prpl_info->get_caps = hangouts_get_media_caps;
700 	prpl_info->initiate_session = hangouts_initiate_media;
701 }
702 
703 static void
hangouts_protocol_roomlist_iface_init(PurpleProtocolRoomlistIface * prpl_info)704 hangouts_protocol_roomlist_iface_init(PurpleProtocolRoomlistIface *prpl_info)
705 {
706 	prpl_info->get_list = hangouts_roomlist_get_list;
707 }
708 
709 static PurpleProtocol *hangouts_protocol;
710 
711 PURPLE_DEFINE_TYPE_EXTENDED(
712 	HangoutsProtocol, hangouts_protocol, PURPLE_TYPE_PROTOCOL, 0,
713 
714 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_IM_IFACE,
715 	                                  hangouts_protocol_im_iface_init)
716 
717 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CHAT_IFACE,
718 	                                  hangouts_protocol_chat_iface_init)
719 
720 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CLIENT_IFACE,
721 	                                  hangouts_protocol_client_iface_init)
722 
723 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_SERVER_IFACE,
724 	                                  hangouts_protocol_server_iface_init)
725 
726 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_PRIVACY_IFACE,
727 	                                  hangouts_protocol_privacy_iface_init)
728 
729 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_MEDIA_IFACE,
730 	                                  hangouts_protocol_media_iface_init)
731 
732 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_ROOMLIST_IFACE,
733 	                                  hangouts_protocol_roomlist_iface_init)
734 );
735 
736 static gboolean
libpurple3_plugin_load(PurplePlugin * plugin,GError ** error)737 libpurple3_plugin_load(PurplePlugin *plugin, GError **error)
738 {
739 	hangouts_protocol_register_type(plugin);
740 	hangouts_protocol = purple_protocols_add(HANGOUTS_TYPE_PROTOCOL, error);
741 	if (!hangouts_protocol)
742 		return FALSE;
743 
744 	return plugin_load(plugin, error);
745 }
746 
747 static gboolean
libpurple3_plugin_unload(PurplePlugin * plugin,GError ** error)748 libpurple3_plugin_unload(PurplePlugin *plugin, GError **error)
749 {
750 	if (!plugin_unload(plugin, error))
751 		return FALSE;
752 
753 	if (!purple_protocols_remove(hangouts_protocol, error))
754 		return FALSE;
755 
756 	return TRUE;
757 }
758 
759 static PurplePluginInfo *
plugin_query(GError ** error)760 plugin_query(GError **error)
761 {
762 	return purple_plugin_info_new(
763 		"id",          HANGOUTS_PLUGIN_ID,
764 		"name",        "Hangouts",
765 		"version",     HANGOUTS_PLUGIN_VERSION,
766 		"category",    N_("Protocol"),
767 		"summary",     N_("Hangouts Protocol Plugins."),
768 		"description", N_("Adds Hangouts protocol support to libpurple."),
769 		"website",     "https://bitbucket.org/EionRobb/purple-hangouts/",
770 		"abi-version", PURPLE_ABI_VERSION,
771 		"flags",       PURPLE_PLUGIN_INFO_FLAGS_INTERNAL |
772 		               PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD,
773 		NULL
774 	);
775 }
776 
777 PURPLE_PLUGIN_INIT(hangouts, plugin_query,
778 		libpurple3_plugin_load, libpurple3_plugin_unload);
779 
780 #else
781 
782 // Normally set in core.c in purple3
783 void _purple_socket_init(void);
784 void _purple_socket_uninit(void);
785 
786 
787 static gboolean
libpurple2_plugin_load(PurplePlugin * plugin)788 libpurple2_plugin_load(PurplePlugin *plugin)
789 {
790 	_purple_socket_init();
791 	purple_http_init();
792 
793 	return plugin_load(plugin, NULL);
794 }
795 
796 static gboolean
libpurple2_plugin_unload(PurplePlugin * plugin)797 libpurple2_plugin_unload(PurplePlugin *plugin)
798 {
799 	_purple_socket_uninit();
800 	purple_http_uninit();
801 
802 	return plugin_unload(plugin, NULL);
803 }
804 
805 static PurplePluginInfo info =
806 {
807 	PURPLE_PLUGIN_MAGIC,
808 	PURPLE_MAJOR_VERSION,
809 	PURPLE_MINOR_VERSION,
810 	PURPLE_PLUGIN_PROTOCOL,                             /**< type           */
811 	NULL,                                               /**< ui_requirement */
812 	0,                                                  /**< flags          */
813 	NULL,                                               /**< dependencies   */
814 	PURPLE_PRIORITY_DEFAULT,                            /**< priority       */
815 
816 	HANGOUTS_PLUGIN_ID,                                 /**< id             */
817 	N_("Hangouts"),                                     /**< name           */
818 	HANGOUTS_PLUGIN_VERSION,                            /**< version        */
819 
820 	N_("Hangouts Protocol Plugins."),                   /**< summary        */
821 
822 	N_("Adds Hangouts protocol support to libpurple."), /**< description    */
823 	"Eion Robb <eionrobb+hangouts@gmail.com>",          /**< author         */
824 	"https://bitbucket.org/EionRobb/purple-hangouts/",  /**< homepage       */
825 
826 	libpurple2_plugin_load,                             /**< load           */
827 	libpurple2_plugin_unload,                           /**< unload         */
828 	NULL,                                               /**< destroy        */
829 
830 	NULL,                                               /**< ui_info        */
831 	NULL,                                               /**< extra_info     */
832 	NULL,                                               /**< prefs_info     */
833 	NULL,                                               /**< actions        */
834 
835 	/* padding */
836 	NULL,
837 	NULL,
838 	NULL,
839 	NULL
840 };
841 
842 static void
init_plugin(PurplePlugin * plugin)843 init_plugin(PurplePlugin *plugin)
844 {
845 	PurplePluginInfo *info;
846 	PurplePluginProtocolInfo *prpl_info = g_new0(PurplePluginProtocolInfo, 1);
847 
848 	info = plugin->info;
849 	if (info == NULL) {
850 		plugin->info = info = g_new0(PurplePluginInfo, 1);
851 	}
852 
853 	prpl_info->options = OPT_PROTO_NO_PASSWORD | OPT_PROTO_IM_IMAGE | OPT_PROTO_CHAT_TOPIC | OPT_PROTO_MAIL_CHECK;
854 	prpl_info->protocol_options = hangouts_add_account_options(prpl_info->protocol_options);
855 
856 	purple_signal_register(plugin, "hangouts-received-stateupdate",
857 			purple_marshal_VOID__POINTER_POINTER, NULL, 2,
858 			PURPLE_TYPE_CONNECTION,
859 			purple_value_new(PURPLE_TYPE_OBJECT));
860 
861 	purple_signal_register(plugin, "hangouts-gmail-notification",
862 			purple_marshal_VOID__POINTER_POINTER_POINTER, NULL, 3,
863 			PURPLE_TYPE_CONNECTION,
864 			purple_value_new(PURPLE_TYPE_STRING),
865 			purple_value_new(PURPLE_TYPE_OBJECT));
866 
867 	hangouts_register_events(plugin);
868 
869 	prpl_info->login = hangouts_login;
870 	prpl_info->close = hangouts_close;
871 	prpl_info->status_types = hangouts_status_types;
872 	prpl_info->list_icon = hangouts_list_icon;
873 	prpl_info->status_text = hangouts_status_text;
874 	prpl_info->tooltip_text = hangouts_tooltip_text;
875 	prpl_info->buddy_free = hangouts_buddy_free;
876 	prpl_info->offline_message = hangouts_offline_message;
877 
878 	prpl_info->get_info = hangouts_get_info;
879 	prpl_info->set_status = hangouts_set_status;
880 	prpl_info->set_idle = hangouts_set_idle;
881 
882 	prpl_info->blist_node_menu = hangouts_node_menu;
883 
884 	prpl_info->send_im = hangouts_send_im;
885 	prpl_info->send_typing = hangouts_send_typing;
886 	prpl_info->chat_send = hangouts_chat_send;
887 	prpl_info->chat_info = hangouts_chat_info;
888 	prpl_info->chat_info_defaults = hangouts_chat_info_defaults;
889 	prpl_info->join_chat = hangouts_join_chat;
890 	prpl_info->get_chat_name = hangouts_get_chat_name;
891 	prpl_info->chat_invite = hangouts_chat_invite;
892 	prpl_info->set_chat_topic = hangouts_chat_set_topic;
893 
894 	prpl_info->get_media_caps = hangouts_get_media_caps;
895 	prpl_info->initiate_media = hangouts_initiate_media;
896 
897 	prpl_info->add_deny = hangouts_block_user;
898 	prpl_info->rem_deny = hangouts_unblock_user;
899 
900 	prpl_info->roomlist_get_list = hangouts_roomlist_get_list;
901 
902 	info->extra_info = prpl_info;
903 	#if PURPLE_MINOR_VERSION >= 5
904 		prpl_info->struct_size = sizeof(PurplePluginProtocolInfo);
905 	#endif
906 	#if PURPLE_MINOR_VERSION >= 8
907 		//prpl_info->add_buddy_with_invite = skypeweb_add_buddy_with_invite;
908 	#endif
909 
910 	info->actions = hangouts_actions;
911 }
912 
913 PURPLE_INIT_PLUGIN(hangouts, init_plugin, info);
914 
915 #endif
916