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 "hangouts_conversation.h"
20 
21 #include "hangouts.pb-c.h"
22 #include "hangouts_connection.h"
23 #include "hangouts_events.h"
24 
25 #include <string.h>
26 #include <glib.h>
27 
28 #include "debug.h"
29 #include "status.h"
30 #include "glibcompat.h"
31 #include "image-store.h"
32 
33 // From hangouts_pblite
34 gchar *pblite_dump_json(ProtobufCMessage *message);
35 
36 RequestHeader *
hangouts_get_request_header(HangoutsAccount * ha)37 hangouts_get_request_header(HangoutsAccount *ha)
38 {
39 	RequestHeader *header = g_new0(RequestHeader, 1);
40 	ClientVersion *version = g_new0(ClientVersion, 1);
41 	request_header__init(header);
42 	client_version__init(version);
43 
44 	if (ha->client_id != NULL) {
45 		ClientIdentifier *client_identifier = g_new0(ClientIdentifier, 1);
46 		client_identifier__init(client_identifier);
47 
48 		header->client_identifier = client_identifier;
49 		header->client_identifier->resource = g_strdup(ha->client_id);
50 	}
51 
52 	version->has_client_id = TRUE;
53 	version->client_id = CLIENT_ID__CLIENT_ID_WEB_HANGOUTS;
54 
55 	header->client_version = version;
56 
57 	return header;
58 }
59 
60 void
hangouts_request_header_free(RequestHeader * header)61 hangouts_request_header_free(RequestHeader *header)
62 {
63 	if (header->client_identifier) {
64 		g_free(header->client_identifier->resource);
65 		g_free(header->client_identifier);
66 	}
67 	g_free(header->client_version);
68 	g_free(header);
69 }
70 
71 EventRequestHeader *
hangouts_get_event_request_header(HangoutsAccount * ha,const gchar * conv_id)72 hangouts_get_event_request_header(HangoutsAccount *ha, const gchar *conv_id)
73 {
74 	EventRequestHeader *header = g_new0(EventRequestHeader, 1);
75 	event_request_header__init(header);
76 
77 	if (conv_id != NULL) {
78 		ConversationId *conversation_id = g_new0(ConversationId, 1);
79 		conversation_id__init(conversation_id);
80 
81 		conversation_id->id = g_strdup(conv_id);
82 		header->conversation_id = conversation_id;
83 
84 		if (g_hash_table_contains(ha->google_voice_conversations, conv_id)) {
85 			DeliveryMedium *delivery_medium = g_new0(DeliveryMedium, 1);
86 			PhoneNumber *self_phone = g_new0(PhoneNumber, 1);
87 			delivery_medium__init(delivery_medium);
88 			phone_number__init(self_phone);
89 
90 			delivery_medium->has_medium_type = TRUE;
91 			delivery_medium->medium_type = DELIVERY_MEDIUM_TYPE__DELIVERY_MEDIUM_GOOGLE_VOICE;
92 			self_phone->e164 = g_strdup(ha->self_phone);
93 			delivery_medium->self_phone = self_phone;
94 
95 			header->delivery_medium = delivery_medium;
96 			header->has_event_type = TRUE;
97 			header->event_type = EVENT_TYPE__EVENT_TYPE_SMS;
98 		}
99 	}
100 
101 	header->has_client_generated_id = TRUE;
102 	header->client_generated_id = g_random_int();
103 
104 	//todo off the record status
105 
106 	return header;
107 }
108 
109 void
hangouts_event_request_header_free(EventRequestHeader * header)110 hangouts_event_request_header_free(EventRequestHeader *header)
111 {
112 	if (header->conversation_id) {
113 		g_free(header->conversation_id->id);
114 		g_free(header->conversation_id);
115 	}
116 	if (header->delivery_medium) {
117 		if (header->delivery_medium->self_phone) {
118 			g_free(header->delivery_medium->self_phone->e164);
119 			g_free(header->delivery_medium->self_phone);
120 		}
121 		g_free(header->delivery_medium);
122 	}
123 
124 	g_free(header);
125 }
126 
127 static void
hangouts_got_self_info(HangoutsAccount * ha,GetSelfInfoResponse * response,gpointer user_data)128 hangouts_got_self_info(HangoutsAccount *ha, GetSelfInfoResponse *response, gpointer user_data)
129 {
130 	Entity *self_entity = response->self_entity;
131 	PhoneData *phone_data = response->phone_data;
132 	guint i;
133 	const gchar *alias;
134 
135 	g_return_if_fail(self_entity);
136 
137 	g_free(ha->self_gaia_id);
138 	ha->self_gaia_id = g_strdup(self_entity->id->gaia_id);
139 	purple_connection_set_display_name(ha->pc, ha->self_gaia_id);
140 	purple_account_set_string(ha->account, "self_gaia_id", ha->self_gaia_id);
141 
142 	alias = purple_account_get_private_alias(ha->account);
143 	if (alias == NULL || *alias == '\0') {
144 		purple_account_set_private_alias(ha->account, self_entity->properties->display_name);
145 	}
146 
147 	if (phone_data != NULL) {
148 		for (i = 0; i < phone_data->n_phone; i++) {
149 			Phone *phone = phone_data->phone[i];
150 			if (phone->google_voice) {
151 				g_free(ha->self_phone);
152 				ha->self_phone = g_strdup(phone->phone_number->e164);
153 				break;
154 			}
155 		}
156 	}
157 
158 	hangouts_get_buddy_list(ha);
159 }
160 
161 void
hangouts_get_self_info(HangoutsAccount * ha)162 hangouts_get_self_info(HangoutsAccount *ha)
163 {
164 	GetSelfInfoRequest request;
165 	get_self_info_request__init(&request);
166 
167 	request.request_header = hangouts_get_request_header(ha);
168 
169 	hangouts_pblite_get_self_info(ha, &request, hangouts_got_self_info, NULL);
170 
171 	hangouts_request_header_free(request.request_header);
172 
173 	if (ha->last_event_timestamp != 0) {
174 		hangouts_get_all_events(ha, ha->last_event_timestamp);
175 	}
176 }
177 
178 static void
hangouts_got_users_presence(HangoutsAccount * ha,QueryPresenceResponse * response,gpointer user_data)179 hangouts_got_users_presence(HangoutsAccount *ha, QueryPresenceResponse *response, gpointer user_data)
180 {
181 	guint i;
182 
183 	for (i = 0; i < response->n_presence_result; i++) {
184 		hangouts_process_presence_result(ha, response->presence_result[i]);
185 	}
186 }
187 
188 void
hangouts_get_users_presence(HangoutsAccount * ha,GList * user_ids)189 hangouts_get_users_presence(HangoutsAccount *ha, GList *user_ids)
190 {
191 	QueryPresenceRequest request;
192 	ParticipantId **participant_id;
193 	guint n_participant_id;
194 	GList *cur;
195 	guint i;
196 
197 	query_presence_request__init(&request);
198 	request.request_header = hangouts_get_request_header(ha);
199 
200 	n_participant_id = g_list_length(user_ids);
201 	participant_id = g_new0(ParticipantId *, n_participant_id);
202 
203 	for (i = 0, cur = user_ids; cur && cur->data && i < n_participant_id; (cur = cur->next), i++) {
204 		gchar *who = (gchar *) cur->data;
205 
206 		if (G_UNLIKELY(!hangouts_is_valid_id(who))) {
207 			i--;
208 			n_participant_id--;
209 		} else {
210 			participant_id[i] = g_new0(ParticipantId, 1);
211 			participant_id__init(participant_id[i]);
212 			participant_id[i]->gaia_id = who;
213 		}
214 	}
215 
216 	request.participant_id = participant_id;
217 	request.n_participant_id = n_participant_id;
218 
219 	request.n_field_mask = 7;
220 	request.field_mask = g_new0(FieldMask, request.n_field_mask);
221 	request.field_mask[0] = FIELD_MASK__FIELD_MASK_REACHABLE;
222 	request.field_mask[1] = FIELD_MASK__FIELD_MASK_AVAILABLE;
223 	request.field_mask[2] = FIELD_MASK__FIELD_MASK_MOOD;
224 	request.field_mask[3] = FIELD_MASK__FIELD_MASK_LOCATION;
225 	request.field_mask[4] = FIELD_MASK__FIELD_MASK_IN_CALL;
226 	request.field_mask[5] = FIELD_MASK__FIELD_MASK_DEVICE;
227 	request.field_mask[6] = FIELD_MASK__FIELD_MASK_LAST_SEEN;
228 
229 	hangouts_pblite_query_presence(ha, &request, hangouts_got_users_presence, NULL);
230 
231 	hangouts_request_header_free(request.request_header);
232 	for (i = 0; i < n_participant_id; i++) {
233 		g_free(participant_id[i]);
234 	}
235 	g_free(participant_id);
236 	g_free(request.field_mask);
237 }
238 
239 gboolean
hangouts_poll_buddy_status(gpointer userdata)240 hangouts_poll_buddy_status(gpointer userdata)
241 {
242 	HangoutsAccount *ha = userdata;
243 	GSList *buddies, *i;
244 	GList *user_list = NULL;
245 
246 	if (!PURPLE_CONNECTION_IS_CONNECTED(ha->pc)) {
247 		return FALSE;
248 	}
249 
250 	buddies = purple_blist_find_buddies(ha->account, NULL);
251 	for(i = buddies; i; i = i->next) {
252 		PurpleBuddy *buddy = i->data;
253 		user_list = g_list_prepend(user_list, (gpointer) purple_buddy_get_name(buddy));
254 	}
255 
256 	hangouts_get_users_presence(ha, user_list);
257 
258 	g_slist_free(buddies);
259 	g_list_free(user_list);
260 
261 	return TRUE;
262 }
263 
264 static void hangouts_got_buddy_photo(PurpleHttpConnection *connection, PurpleHttpResponse *response, gpointer user_data);
265 
266 static void
hangouts_got_users_information(HangoutsAccount * ha,GetEntityByIdResponse * response,gpointer user_data)267 hangouts_got_users_information(HangoutsAccount *ha, GetEntityByIdResponse *response, gpointer user_data)
268 {
269 	guint i;
270 
271 	for (i = 0; i < response->n_entity_result; i++) {
272 		Entity *entity = response->entity_result[i]->entity[0];
273 		const gchar *gaia_id;
274 
275 		if (entity == NULL) {
276 			continue;
277 		}
278 		gaia_id = entity->id ? entity->id->gaia_id : NULL;
279 
280 		if (gaia_id != NULL && entity->properties) {
281 			PurpleBuddy *buddy = purple_blist_find_buddy(ha->account, gaia_id);
282 
283 			// Give a best-guess for the buddy's alias
284 			if (entity->properties->display_name)
285 				purple_serv_got_alias(ha->pc, gaia_id, entity->properties->display_name);
286 			else if (entity->properties->canonical_email)
287 				purple_serv_got_alias(ha->pc, gaia_id, entity->properties->canonical_email);
288 			else if (entity->entity_type == PARTICIPANT_TYPE__PARTICIPANT_TYPE_OFF_NETWORK_PHONE
289 			         && entity->properties->n_phone)
290 				purple_serv_got_alias(ha->pc, gaia_id, entity->properties->phone[0]);
291 
292 			// Set the buddy photo, if it's real
293 			if (entity->properties->photo_url != NULL && entity->properties->photo_url_status == PHOTO_URL_STATUS__PHOTO_URL_STATUS_USER_PHOTO) {
294 				gchar *photo = g_strconcat("https:", entity->properties->photo_url, NULL);
295 				if (!purple_strequal(purple_buddy_icons_get_checksum_for_user(buddy), photo)) {
296 					PurpleHttpRequest *photo_request = purple_http_request_new(photo);
297 
298 					if (ha->icons_keepalive_pool == NULL) {
299 						ha->icons_keepalive_pool = purple_http_keepalive_pool_new();
300 						purple_http_keepalive_pool_set_limit_per_host(ha->icons_keepalive_pool, 4);
301 					}
302 					purple_http_request_set_keepalive_pool(photo_request, ha->icons_keepalive_pool);
303 
304 					purple_http_request(ha->pc, photo_request, hangouts_got_buddy_photo, buddy);
305 					purple_http_request_unref(photo_request);
306 				}
307 				g_free(photo);
308 			}
309 		}
310 
311 		if (entity->entity_type == PARTICIPANT_TYPE__PARTICIPANT_TYPE_OFF_NETWORK_PHONE) {
312 			purple_protocol_got_user_status(ha->account, gaia_id, "mobile", NULL);
313 		}
314 	}
315 }
316 
317 void
hangouts_get_users_information(HangoutsAccount * ha,GList * user_ids)318 hangouts_get_users_information(HangoutsAccount *ha, GList *user_ids)
319 {
320 	GetEntityByIdRequest request;
321 	size_t n_batch_lookup_spec;
322 	EntityLookupSpec **batch_lookup_spec;
323 	GList *cur;
324 	guint i;
325 
326 	get_entity_by_id_request__init(&request);
327 	request.request_header = hangouts_get_request_header(ha);
328 
329 	n_batch_lookup_spec = g_list_length(user_ids);
330 	batch_lookup_spec = g_new0(EntityLookupSpec *, n_batch_lookup_spec);
331 
332 	for (i = 0, cur = user_ids; cur && cur->data && i < n_batch_lookup_spec; (cur = cur->next), i++) {
333 		batch_lookup_spec[i] = g_new0(EntityLookupSpec, 1);
334 		entity_lookup_spec__init(batch_lookup_spec[i]);
335 
336 		batch_lookup_spec[i]->gaia_id = (gchar *) cur->data;
337 	}
338 
339 	request.batch_lookup_spec = batch_lookup_spec;
340 	request.n_batch_lookup_spec = n_batch_lookup_spec;
341 
342 	hangouts_pblite_get_entity_by_id(ha, &request, hangouts_got_users_information, NULL);
343 
344 	hangouts_request_header_free(request.request_header);
345 	for (i = 0; i < n_batch_lookup_spec; i++) {
346 		g_free(batch_lookup_spec[i]);
347 	}
348 	g_free(batch_lookup_spec);
349 }
350 
351 static void
hangouts_got_user_info(HangoutsAccount * ha,GetEntityByIdResponse * response,gpointer user_data)352 hangouts_got_user_info(HangoutsAccount *ha, GetEntityByIdResponse *response, gpointer user_data)
353 {
354 	Entity *entity;
355 	EntityProperties *props;
356 	PurpleNotifyUserInfo *user_info;
357 	gchar *who = user_data;
358 	guint i;
359 
360 	if (response->n_entity_result < 1) {
361 		g_free(who);
362 		return;
363 	}
364 
365 	entity = response->entity_result[0]->entity[0];
366 	if (entity == NULL || entity->properties == NULL) {
367 		g_free(who);
368 		return;
369 	}
370 	props = entity->properties;
371 
372 	user_info = purple_notify_user_info_new();
373 
374 	const gchar *type_str;
375 	switch (entity->entity_type) {
376 		case PARTICIPANT_TYPE__PARTICIPANT_TYPE_OFF_NETWORK_PHONE: type_str = _("SMS"); break;
377 		case PARTICIPANT_TYPE__PARTICIPANT_TYPE_GAIA: type_str = _("Hangouts (Gaia)"); break;
378 		default: type_str = _("Unknown"); break;
379 	}
380 	purple_notify_user_info_add_pair_html(user_info, _("Type"), type_str);
381 	if (props->display_name != NULL)
382 		purple_notify_user_info_add_pair_html(user_info, _("Display Name"), props->display_name);
383 	if (props->first_name != NULL)
384 		purple_notify_user_info_add_pair_html(user_info, _("First Name"), props->first_name);
385 
386 	if (props->photo_url) {
387 		gchar *prefix = strncmp(props->photo_url, "//", 2) ? "" : "https:";
388 		gchar *photo_tag = g_strdup_printf("<a href=\"%s%s\"><img width=\"128\" src=\"%s%s\"/></a>",
389 		                                   prefix, props->photo_url, prefix, props->photo_url);
390 		purple_notify_user_info_add_pair_html(user_info, _("Photo"), photo_tag);
391 		g_free(photo_tag);
392 	}
393 
394 	for (i = 0; i < props->n_email; i++) {
395 		purple_notify_user_info_add_pair_html(user_info, _("Email"), props->email[i]);
396 	}
397 	for (i = 0; i < props->n_phone; i++) {
398 		purple_notify_user_info_add_pair_html(user_info, _("Phone"), props->phone[i]);
399 	}
400 	if (props->has_gender) {
401 		const gchar *gender_str;
402 		switch (props->gender) {
403 			case GENDER__GENDER_MALE: gender_str = _("Male"); break;
404 			case GENDER__GENDER_FEMALE: gender_str = _("Female"); break;
405 			default: gender_str = _("Unknown"); break;
406 		}
407 		purple_notify_user_info_add_pair_html(user_info, _("Gender"), gender_str);
408 	}
409 	if (props->canonical_email != NULL)
410 		purple_notify_user_info_add_pair_html(user_info, _("Canonical Email"), props->canonical_email);
411 
412 	purple_notify_userinfo(ha->pc, who, user_info, NULL, NULL);
413 
414 	g_free(who);
415 }
416 
417 
418 void
hangouts_get_info(PurpleConnection * pc,const gchar * who)419 hangouts_get_info(PurpleConnection *pc, const gchar *who)
420 {
421 	HangoutsAccount *ha = purple_connection_get_protocol_data(pc);
422 	GetEntityByIdRequest request;
423 	EntityLookupSpec entity_lookup_spec;
424 	EntityLookupSpec *batch_lookup_spec;
425 	gchar *who_dup = g_strdup(who);
426 
427 	get_entity_by_id_request__init(&request);
428 	request.request_header = hangouts_get_request_header(ha);
429 
430 	entity_lookup_spec__init(&entity_lookup_spec);
431 	entity_lookup_spec.gaia_id = who_dup;
432 
433 	batch_lookup_spec = &entity_lookup_spec;
434 	request.batch_lookup_spec = &batch_lookup_spec;
435 	request.n_batch_lookup_spec = 1;
436 
437 	hangouts_pblite_get_entity_by_id(ha, &request, hangouts_got_user_info, who_dup);
438 
439 	hangouts_request_header_free(request.request_header);
440 }
441 
442 static void
hangouts_got_conversation_events(HangoutsAccount * ha,GetConversationResponse * response,gpointer user_data)443 hangouts_got_conversation_events(HangoutsAccount *ha, GetConversationResponse *response, gpointer user_data)
444 {
445 	Conversation *conversation;
446 	const gchar *conv_id;
447 	PurpleConversation *conv;
448 	PurpleChatConversation *chatconv;
449 	guint i;
450 	PurpleConversationUiOps *convuiops;
451 	PurpleGroup *temp_group = NULL;
452 
453 	if (response->conversation_state == NULL) {
454 		if (response->response_header->status == RESPONSE_STATUS__RESPONSE_STATUS_ERROR_INVALID_CONVERSATION) {
455 			purple_notify_error(ha->pc, _("Invalid conversation"), _("This is not a valid conversation"), _("Please use the Room List to search for a valid conversation"), purple_request_cpar_from_connection(ha->pc));
456 		} else {
457 			purple_notify_error(ha->pc, _("Error"), _("An error occurred while fetching the history of the conversation"), NULL, purple_request_cpar_from_connection(ha->pc));
458 		}
459 		g_warn_if_reached();
460 		return;
461 	}
462 
463 	conversation = response->conversation_state->conversation;
464 	g_return_if_fail(conversation != NULL);
465 	conv_id = conversation->conversation_id->id;
466 
467 	//purple_debug_info("hangouts", "got conversation events %s\n", pblite_dump_json((ProtobufCMessage *)response));
468 
469 	if (conversation->type == CONVERSATION_TYPE__CONVERSATION_TYPE_GROUP) {
470 		chatconv = purple_conversations_find_chat_with_account(conv_id, ha->account);
471 		if (!chatconv) {
472 			chatconv = purple_serv_got_joined_chat(ha->pc, g_str_hash(conv_id), conv_id);
473 			purple_conversation_set_data(PURPLE_CONVERSATION(chatconv), "conv_id", g_strdup(conv_id));
474 		}
475 		conv = PURPLE_CONVERSATION(chatconv);
476 		convuiops = purple_conversation_get_ui_ops(conv);
477 
478 		for (i = 0; i < conversation->n_participant_data; i++) {
479 			ConversationParticipantData *participant_data = conversation->participant_data[i];
480 			PurpleChatUserFlags cbflags = PURPLE_CHAT_USER_NONE;
481 			const gchar *gaia_id = participant_data->id->gaia_id;
482 			PurpleChatUser *cb;
483 			gboolean ui_update_sent = FALSE;
484 
485 			purple_chat_conversation_add_user(chatconv, gaia_id, NULL, cbflags, FALSE);
486 			cb = purple_chat_conversation_find_user(chatconv, gaia_id);
487 			purple_chat_user_set_alias(cb, participant_data->fallback_name);
488 
489 			if (convuiops != NULL) {
490 				// Horrible hack.  Don't try this at home!
491 				if (convuiops->chat_rename_user != NULL) {
492 #if PURPLE_VERSION_CHECK(3, 0, 0)
493 					convuiops->chat_rename_user(chatconv, gaia_id, gaia_id, participant_data->fallback_name);
494 #else
495 					convuiops->chat_rename_user(conv, gaia_id, gaia_id, participant_data->fallback_name);
496 #endif
497 					ui_update_sent = TRUE;
498 				} else if (convuiops->chat_update_user != NULL) {
499 #if PURPLE_VERSION_CHECK(3, 0, 0)
500 					convuiops->chat_update_user(cb);
501 #else
502 					convuiops->chat_update_user(conv, gaia_id);
503 #endif
504 					ui_update_sent = TRUE;
505 				}
506 			}
507 
508 			if (ui_update_sent == FALSE) {
509 				// Bitlbee doesn't have the above two functions, lets do an even worse hack
510 				PurpleBuddy *fakebuddy;
511 				if (temp_group == NULL) {
512 					temp_group = purple_blist_find_group("Hangouts Temporary Chat Buddies");
513 					if (!temp_group)
514 					{
515 						temp_group = purple_group_new("Hangouts Temporary Chat Buddies");
516 						purple_blist_add_group(temp_group, NULL);
517 					}
518 				}
519 
520 				fakebuddy = purple_buddy_new(ha->account, gaia_id, participant_data->fallback_name);
521 				purple_blist_node_set_transient(PURPLE_BLIST_NODE(fakebuddy), TRUE);
522 				purple_blist_add_buddy(fakebuddy, NULL, temp_group, NULL);
523 			}
524 		}
525 	}
526 
527 	for (i = 0; i < response->conversation_state->n_event; i++) {
528 		Event *event = response->conversation_state->event[i];
529 
530 		// Ignore join/parts when loading history
531 		if (!event->membership_change) {
532 			if(event->chat_message != NULL && event->chat_message->message_content->n_attachment && !purple_account_get_bool(ha->account, "fetch_image_history", TRUE)) {
533 				purple_debug_info("hangouts", "skipping attachment due to fetch_image_history disabled\n");
534 				continue;
535 			}
536 			//Send event to the hangouts_events.c slaughterhouse
537 			hangouts_process_conversation_event(ha, conversation, event, response->response_header->current_server_time);
538 		}
539 	}
540 }
541 
542 void
hangouts_get_conversation_events(HangoutsAccount * ha,const gchar * conv_id,gint64 since_timestamp)543 hangouts_get_conversation_events(HangoutsAccount *ha, const gchar *conv_id, gint64 since_timestamp)
544 {
545 	//since_timestamp is in microseconds
546 	GetConversationRequest request;
547 	ConversationId conversation_id;
548 	ConversationSpec conversation_spec;
549 	EventContinuationToken event_continuation_token;
550 
551 	get_conversation_request__init(&request);
552 	request.request_header = hangouts_get_request_header(ha);
553 
554 	conversation_spec__init(&conversation_spec);
555 	request.conversation_spec = &conversation_spec;
556 
557 	conversation_id__init(&conversation_id);
558 	conversation_id.id = (gchar *) conv_id;
559 	conversation_spec.conversation_id = &conversation_id;
560 
561 	if (since_timestamp > 0) {
562 		request.has_include_event = TRUE;
563 		request.include_event = TRUE;
564 		request.has_max_events_per_conversation = TRUE;
565 		request.max_events_per_conversation = 50;
566 
567 		event_continuation_token__init(&event_continuation_token);
568 		event_continuation_token.event_timestamp = since_timestamp;
569 		request.event_continuation_token = &event_continuation_token;
570 	}
571 
572 	hangouts_pblite_get_conversation(ha, &request, hangouts_got_conversation_events, NULL);
573 
574 	hangouts_request_header_free(request.request_header);
575 
576 }
577 
578 static void
hangouts_got_all_events(HangoutsAccount * ha,SyncAllNewEventsResponse * response,gpointer user_data)579 hangouts_got_all_events(HangoutsAccount *ha, SyncAllNewEventsResponse *response, gpointer user_data)
580 {
581 	guint i, j;
582 	guint64 sync_timestamp;
583 
584 	//purple_debug_info("hangouts", "%s\n", pblite_dump_json((ProtobufCMessage *)response));
585 	/*
586 struct  _SyncAllNewEventsResponse
587 {
588   ProtobufCMessage base;
589   ResponseHeader *response_header;
590   protobuf_c_boolean has_sync_timestamp;
591   uint64_t sync_timestamp;
592   size_t n_conversation_state;
593   ConversationState **conversation_state;
594 };
595 
596 struct  _ConversationState
597 {
598   ProtobufCMessage base;
599   ConversationId *conversation_id;
600   Conversation *conversation;
601   size_t n_event;
602   Event **event;
603   EventContinuationToken *event_continuation_token;
604 };
605 
606 */
607 	sync_timestamp = response->sync_timestamp;
608 	for (i = 0; i < response->n_conversation_state; i++) {
609 		ConversationState *conversation_state = response->conversation_state[i];
610 		Conversation *conversation = conversation_state->conversation;
611 
612 		for (j = 0; j < conversation_state->n_event; j++) {
613 			Event *event = conversation_state->event[j];
614 
615 			hangouts_process_conversation_event(ha, conversation, event, sync_timestamp);
616 		}
617 	}
618 }
619 
620 void
hangouts_get_all_events(HangoutsAccount * ha,guint64 since_timestamp)621 hangouts_get_all_events(HangoutsAccount *ha, guint64 since_timestamp)
622 {
623 	SyncAllNewEventsRequest request;
624 
625 	g_return_if_fail(since_timestamp > 0);
626 
627 	sync_all_new_events_request__init(&request);
628 	request.request_header = hangouts_get_request_header(ha);
629 
630 	request.has_last_sync_timestamp = TRUE;
631 	request.last_sync_timestamp = since_timestamp;
632 
633 	request.has_max_response_size_bytes = TRUE;
634 	request.max_response_size_bytes = 1048576; // 1 mibbily bite
635 
636 	hangouts_pblite_sync_all_new_events(ha, &request, hangouts_got_all_events, NULL);
637 
638 	hangouts_request_header_free(request.request_header);
639 }
640 
641 GList *
hangouts_chat_info(PurpleConnection * pc)642 hangouts_chat_info(PurpleConnection *pc)
643 {
644 	GList *m = NULL;
645 	PurpleProtocolChatEntry *pce;
646 
647 	pce = g_new0(PurpleProtocolChatEntry, 1);
648 	pce->label = _("Conversation ID");
649 	pce->identifier = "conv_id";
650 	pce->required = TRUE;
651 	m = g_list_append(m, pce);
652 
653 	return m;
654 }
655 
656 GHashTable *
hangouts_chat_info_defaults(PurpleConnection * pc,const char * chatname)657 hangouts_chat_info_defaults(PurpleConnection *pc, const char *chatname)
658 {
659 	GHashTable *defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
660 
661 	if (chatname != NULL)
662 	{
663 		g_hash_table_insert(defaults, "conv_id", g_strdup(chatname));
664 	}
665 
666 	return defaults;
667 }
668 
669 void
hangouts_join_chat(PurpleConnection * pc,GHashTable * data)670 hangouts_join_chat(PurpleConnection *pc, GHashTable *data)
671 {
672 	HangoutsAccount *ha = purple_connection_get_protocol_data(pc);
673 	gchar *conv_id;
674 	PurpleChatConversation *chatconv;
675 
676 	conv_id = (gchar *)g_hash_table_lookup(data, "conv_id");
677 	if (conv_id == NULL)
678 	{
679 		return;
680 	}
681 
682 	chatconv = purple_conversations_find_chat_with_account(conv_id, ha->account);
683 	if (chatconv != NULL && !purple_chat_conversation_has_left(chatconv)) {
684 		purple_conversation_present(PURPLE_CONVERSATION(chatconv));
685 		return;
686 	}
687 
688 	chatconv = purple_serv_got_joined_chat(pc, g_str_hash(conv_id), conv_id);
689 	purple_conversation_set_data(PURPLE_CONVERSATION(chatconv), "conv_id", g_strdup(conv_id));
690 
691 	purple_conversation_present(PURPLE_CONVERSATION(chatconv));
692 
693 	//TODO store and use timestamp of last event
694 	hangouts_get_conversation_events(ha, conv_id, 0);
695 }
696 
697 static void
hangouts_got_join_chat_from_url(HangoutsAccount * ha,OpenGroupConversationFromUrlResponse * response,gpointer user_data)698 hangouts_got_join_chat_from_url(HangoutsAccount *ha, OpenGroupConversationFromUrlResponse *response, gpointer user_data)
699 {
700 	if (!response || !response->conversation_id || !response->conversation_id->id) {
701 		purple_notify_error(ha->pc, _("Join from URL Error"), _("Could not join group from URL"), response && response->response_header ? response->response_header->error_description : _("Unknown Error"), purple_request_cpar_from_connection(ha->pc));
702 		return;
703 	}
704 
705 	hangouts_get_conversation_events(ha, response->conversation_id->id, 0);
706 }
707 
708 void
hangouts_join_chat_from_url(HangoutsAccount * ha,const gchar * url)709 hangouts_join_chat_from_url(HangoutsAccount *ha, const gchar *url)
710 {
711 	OpenGroupConversationFromUrlRequest request;
712 
713 	g_return_if_fail(url != NULL);
714 
715 	open_group_conversation_from_url_request__init(&request);
716 	request.request_header = hangouts_get_request_header(ha);
717 
718 	request.url = (gchar *) url;
719 
720 	hangouts_pblite_open_group_conversation_from_url(ha, &request, hangouts_got_join_chat_from_url, NULL);
721 
722 	hangouts_request_header_free(request.request_header);
723 }
724 
725 
726 gchar *
hangouts_get_chat_name(GHashTable * data)727 hangouts_get_chat_name(GHashTable *data)
728 {
729 	gchar *temp;
730 
731 	if (data == NULL)
732 		return NULL;
733 
734 	temp = g_hash_table_lookup(data, "conv_id");
735 
736 	if (temp == NULL)
737 		return NULL;
738 
739 	return g_strdup(temp);
740 }
741 
742 void
hangouts_add_person_to_blist(HangoutsAccount * ha,gchar * gaia_id,gchar * alias)743 hangouts_add_person_to_blist(HangoutsAccount *ha, gchar *gaia_id, gchar *alias)
744 {
745 	PurpleGroup *hangouts_group = purple_blist_find_group("Hangouts");
746 
747 	if (purple_account_get_bool(ha->account, "hide_self", FALSE) && purple_strequal(gaia_id, ha->self_gaia_id)) {
748 		return;
749 	}
750 
751 	if (!hangouts_group)
752 	{
753 		hangouts_group = purple_group_new("Hangouts");
754 		purple_blist_add_group(hangouts_group, NULL);
755 	}
756 	purple_blist_add_buddy(purple_buddy_new(ha->account, gaia_id, alias), NULL, hangouts_group, NULL);
757 }
758 
759 
760 void
hangouts_add_conversation_to_blist(HangoutsAccount * ha,Conversation * conversation,GHashTable * unique_user_ids)761 hangouts_add_conversation_to_blist(HangoutsAccount *ha, Conversation *conversation, GHashTable *unique_user_ids)
762 {
763 	PurpleGroup *hangouts_group = NULL;
764 	guint i;
765 	gchar *conv_id = conversation->conversation_id->id;
766 
767 	if ((conversation->self_conversation_state->delivery_medium_option && conversation->self_conversation_state->delivery_medium_option[0]->delivery_medium->medium_type == DELIVERY_MEDIUM_TYPE__DELIVERY_MEDIUM_GOOGLE_VOICE)
768 			|| conversation->network_type[0] == NETWORK_TYPE__NETWORK_TYPE_PHONE) {
769 		g_hash_table_replace(ha->google_voice_conversations, g_strdup(conv_id), NULL);
770 
771 		if (conversation->self_conversation_state->delivery_medium_option && ha->self_phone == NULL) {
772 			ha->self_phone = g_strdup(conversation->self_conversation_state->delivery_medium_option[0]->delivery_medium->self_phone->e164);
773 		}
774 	}
775 
776 	if (conversation->type == CONVERSATION_TYPE__CONVERSATION_TYPE_ONE_TO_ONE) {
777 		gchar *other_person = conversation->participant_data[0]->id->gaia_id;
778 		guint participant_num = 0;
779 		gchar *other_person_alias;
780 
781 		if (!g_strcmp0(other_person, conversation->self_conversation_state->self_read_state->participant_id->gaia_id)) {
782 			other_person = conversation->participant_data[1]->id->gaia_id;
783 			participant_num = 1;
784 		}
785 		other_person_alias = conversation->participant_data[participant_num]->fallback_name;
786 
787 		g_hash_table_replace(ha->one_to_ones, g_strdup(conv_id), g_strdup(other_person));
788 		g_hash_table_replace(ha->one_to_ones_rev, g_strdup(other_person), g_strdup(conv_id));
789 
790 		if (!purple_blist_find_buddy(ha->account, other_person)) {
791 			hangouts_add_person_to_blist(ha, other_person, other_person_alias);
792 		} else {
793 			purple_serv_got_alias(ha->pc, other_person, other_person_alias);
794 		}
795 
796 		if (unique_user_ids == NULL) {
797 			GList *user_list = g_list_prepend(NULL, other_person);
798 			hangouts_get_users_presence(ha, user_list);
799 			g_list_free(user_list);
800 		}
801 	} else {
802 		PurpleChat *chat = purple_blist_find_chat(ha->account, conv_id);
803 		gchar *name = conversation->name;
804 		gboolean has_name = name ? TRUE : FALSE;
805 
806 		g_hash_table_replace(ha->group_chats, g_strdup(conv_id), NULL);
807 
808 		if (chat == NULL) {
809 			hangouts_group = purple_blist_find_group("Hangouts");
810 			if (!hangouts_group)
811 			{
812 				hangouts_group = purple_group_new("Hangouts");
813 				purple_blist_add_group(hangouts_group, NULL);
814 			}
815 
816 			if (!has_name) {
817 				gchar **name_set = g_new0(gchar *, conversation->n_participant_data + 1);
818 				for (i = 0; i < conversation->n_participant_data; i++) {
819 					gchar *p_name = conversation->participant_data[i]->fallback_name;
820 					if (p_name != NULL) {
821 						name_set[i] = p_name;
822 					} else {
823 						name_set[i] = _("Unknown");
824 					}
825 				}
826 				name = g_strjoinv(", ", name_set);
827 				g_free(name_set);
828 			}
829 			purple_blist_add_chat(purple_chat_new(ha->account, name, hangouts_chat_info_defaults(ha->pc, conv_id)), hangouts_group, NULL);
830 			if (!has_name)
831 				g_free(name);
832 		} else {
833 			if(has_name && strstr(purple_chat_get_name(chat), _("Unknown")) != NULL) {
834 				purple_chat_set_alias(chat, name);
835 			}
836 		}
837 	}
838 
839 
840 	for (i = 0; i < conversation->n_participant_data; i++) {
841 		ConversationParticipantData *participant_data = conversation->participant_data[i];
842 
843 		if (participant_data->participant_type != PARTICIPANT_TYPE__PARTICIPANT_TYPE_UNKNOWN) {
844 			if (!purple_blist_find_buddy(ha->account, participant_data->id->gaia_id)) {
845 				hangouts_add_person_to_blist(ha, participant_data->id->gaia_id, participant_data->fallback_name);
846 			}
847 			if (participant_data->fallback_name != NULL) {
848 				purple_serv_got_alias(ha->pc, participant_data->id->gaia_id, participant_data->fallback_name);
849 			}
850 			if (unique_user_ids != NULL) {
851 				g_hash_table_replace(unique_user_ids, participant_data->id->gaia_id, participant_data->id);
852 			}
853 		}
854 	}
855 }
856 
857 static void
hangouts_got_conversation_list(HangoutsAccount * ha,SyncRecentConversationsResponse * response,gpointer user_data)858 hangouts_got_conversation_list(HangoutsAccount *ha, SyncRecentConversationsResponse *response, gpointer user_data)
859 {
860 	guint i;
861 	GHashTable *unique_user_ids = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
862 	GList *unique_user_ids_list;
863 	PurpleBlistNode *node;
864 
865 	for (i = 0; i < response->n_conversation_state; i++) {
866 		ConversationState *conversation_state = response->conversation_state[i];
867 		Conversation *conversation = conversation_state->conversation;
868 
869 		//purple_debug_info("hangouts", "got conversation state %s\n", pblite_dump_json((ProtobufCMessage *)conversation_state));
870 		hangouts_add_conversation_to_blist(ha, conversation, unique_user_ids);
871 	}
872 
873 	//Add missing people from the buddy list
874 	for (node = purple_blist_get_root();
875 	     node != NULL;
876 		 node = purple_blist_node_next(node, TRUE)) {
877 		if (PURPLE_IS_BUDDY(node)) {
878 			PurpleBuddy *buddy = PURPLE_BUDDY(node);
879 			const gchar *name;
880 			if (purple_buddy_get_account(buddy) != ha->account) {
881 				continue;
882 			}
883 
884 			name = purple_buddy_get_name(buddy);
885 			g_hash_table_replace(unique_user_ids, (gchar *) name, NULL);
886 		}
887 	}
888 
889 	unique_user_ids_list = g_hash_table_get_keys(unique_user_ids);
890 	hangouts_get_users_presence(ha, unique_user_ids_list);
891 	hangouts_get_users_information(ha, unique_user_ids_list);
892 	g_list_free(unique_user_ids_list);
893 	g_hash_table_unref(unique_user_ids);
894 }
895 
896 void
hangouts_get_conversation_list(HangoutsAccount * ha)897 hangouts_get_conversation_list(HangoutsAccount *ha)
898 {
899 	SyncRecentConversationsRequest request;
900 	SyncFilter sync_filter[1];
901 	sync_recent_conversations_request__init(&request);
902 
903 	request.request_header = hangouts_get_request_header(ha);
904 	request.has_max_conversations = TRUE;
905 	request.max_conversations = 100;
906 	request.has_max_events_per_conversation = TRUE;
907 	request.max_events_per_conversation = 1;
908 
909 	sync_filter[0] = SYNC_FILTER__SYNC_FILTER_INBOX;
910 	request.sync_filter = sync_filter;
911 	request.n_sync_filter = 1;  // Back streets back, alright!
912 
913 	hangouts_pblite_sync_recent_conversations(ha, &request, hangouts_got_conversation_list, NULL);
914 
915 	hangouts_request_header_free(request.request_header);
916 }
917 
918 
919 static void
hangouts_got_buddy_photo(PurpleHttpConnection * connection,PurpleHttpResponse * response,gpointer user_data)920 hangouts_got_buddy_photo(PurpleHttpConnection *connection, PurpleHttpResponse *response, gpointer user_data)
921 {
922 	PurpleBuddy *buddy = user_data;
923 	PurpleAccount *account = purple_buddy_get_account(buddy);
924 	const gchar *name = purple_buddy_get_name(buddy);
925 	PurpleHttpRequest *request = purple_http_conn_get_request(connection);
926 	const gchar *photo_url = purple_http_request_get_url(request);
927 	const gchar *response_str;
928 	gsize response_len;
929 	gpointer response_dup;
930 
931 	if (purple_http_response_get_error(response) != NULL) {
932 		purple_debug_error("hangouts", "Failed to get buddy photo for %s from %s\n", name, photo_url);
933 		return;
934 	}
935 
936 	response_str = purple_http_response_get_data(response, &response_len);
937 	response_dup = g_memdup(response_str, response_len);
938 	purple_buddy_icons_set_for_user(account, name, response_dup, response_len, photo_url);
939 }
940 
941 static void
hangouts_got_buddy_list(PurpleHttpConnection * http_conn,PurpleHttpResponse * response,gpointer user_data)942 hangouts_got_buddy_list(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
943 {
944 	HangoutsAccount *ha = user_data;
945 	PurpleGroup *hangouts_group = NULL;
946 	const gchar *response_str;
947 	gsize response_len;
948 	JsonObject *obj;
949 	JsonArray *mergedPerson;
950 	gsize i, len;
951 /*
952 {
953 	"id": "ListMergedPeople",
954 	"result": {
955 		"requestMetadata": {
956 			"serverTimeMs": "527",
957 		},
958 		"selection": {
959 			"totalCount": "127",
960 		},
961 		"mergedPerson": [{
962 			"personId": "{USER ID}",
963 			"metadata": {
964 				"contactGroupId": [
965 					"family",
966 					"myContacts",
967 					"starred"
968 				 ],
969 			},
970 			name": [{
971 				"displayName": "{USERS NAME}",
972 			}],
973 			"photo": [{
974 				"url": "https://lh5.googleusercontent.com/-iPLHmUq4g_0/AAAAAAAAAAI/AAAAAAAAAAA/j1C9pusixPY/photo.jpg",
975 				"photoToken": "CAASFTEwOTE4MDY1MTIyOTAyODgxNDcwOBih9d_CAg=="
976 			}],
977 			"inAppReachability": [
978 			 {
979 			  "metadata": {
980 			   "container": "PROFILE",
981 			   "encodedContainerId": "{USER ID}"
982 			  },
983 			  "appType": "BABEL",
984 			  "status": "REACHABLE"
985 			 }]
986 		}
987 */
988 	if (purple_http_response_get_error(response) != NULL) {
989 		purple_debug_error("hangouts", "Failed to download buddy list: %s\n", purple_http_response_get_error(response));
990 		return;
991 	}
992 
993 	response_str = purple_http_response_get_data(response, &response_len);
994 	obj = json_decode_object(response_str, response_len);
995 	mergedPerson = json_object_get_array_member(json_object_get_object_member(obj, "result"), "mergedPerson");
996 	len = json_array_get_length(mergedPerson);
997 	for (i = 0; i < len; i++) {
998 		JsonNode *node = json_array_get_element(mergedPerson, i);
999 		JsonObject *person = json_node_get_object(node);
1000 		const gchar *name;
1001 		gchar *alias;
1002 		gchar *photo;
1003 		PurpleBuddy *buddy;
1004 		gchar *reachableAppType = hangouts_json_path_query_string(node, "$.inAppReachability[*].appType", NULL);
1005 
1006 		if (!purple_strequal(reachableAppType, "BABEL")) {
1007 			//Not a hangouts user
1008 			g_free(reachableAppType);
1009 			continue;
1010 		}
1011 		g_free(reachableAppType);
1012 
1013 		name = json_object_get_string_member(person, "personId");
1014 		alias = hangouts_json_path_query_string(node, "$.name[*].displayName", NULL);
1015 		photo = hangouts_json_path_query_string(node, "$.photo[*].url", NULL);
1016 		buddy = purple_blist_find_buddy(ha->account, name);
1017 
1018 		if (purple_account_get_bool(ha->account, "hide_self", FALSE) && purple_strequal(ha->self_gaia_id, name)) {
1019 			if (buddy != NULL) {
1020 				purple_blist_remove_buddy(buddy);
1021 			}
1022 
1023 			g_free(alias);
1024 			g_free(photo);
1025 			continue;
1026 		}
1027 
1028 		if (buddy == NULL) {
1029 			if (hangouts_group == NULL) {
1030 				hangouts_group = purple_blist_find_group("Hangouts");
1031 				if (!hangouts_group)
1032 				{
1033 					hangouts_group = purple_group_new("Hangouts");
1034 					purple_blist_add_group(hangouts_group, NULL);
1035 				}
1036 			}
1037 			buddy = purple_buddy_new(ha->account, name, alias);
1038 			purple_blist_add_buddy(buddy, NULL, hangouts_group, NULL);
1039 		} else {
1040 			purple_serv_got_alias(ha->pc, name, alias);
1041 		}
1042 
1043 		if (!purple_strequal(purple_buddy_icons_get_checksum_for_user(buddy), photo)) {
1044 			PurpleHttpRequest *photo_request = purple_http_request_new(photo);
1045 
1046 			if (ha->icons_keepalive_pool == NULL) {
1047 				ha->icons_keepalive_pool = purple_http_keepalive_pool_new();
1048 				purple_http_keepalive_pool_set_limit_per_host(ha->icons_keepalive_pool, 4);
1049 			}
1050 			purple_http_request_set_keepalive_pool(photo_request, ha->icons_keepalive_pool);
1051 
1052 			purple_http_request(ha->pc, photo_request, hangouts_got_buddy_photo, buddy);
1053 			purple_http_request_unref(photo_request);
1054 		}
1055 
1056 		g_free(alias);
1057 		g_free(photo);
1058 	}
1059 
1060 	json_object_unref(obj);
1061 }
1062 
1063 void
hangouts_get_buddy_list(HangoutsAccount * ha)1064 hangouts_get_buddy_list(HangoutsAccount *ha)
1065 {
1066 	gchar *request_data;
1067 	/*
1068 	POST https://clients6.google.com/rpc/plusi?key={KEY}&alt=json
1069 Authorization: SAPISIDHASH {AUTH HEADER}
1070 Content-Type: application/json
1071 Accept: application/json
1072 Cookie: {COOKIES}
1073 Pragma: no-cache
1074 Cache-Control: no-cache
1075 
1076 {"method":"plusi.ozinternal.listmergedpeople","id":"ListMergedPeople","apiVersion":"v2","jsonrpc":"2.0","params":{"pageSelection":{"maxResults":1000},"params":{"personId":"{MY_USER_ID}","collection":6,"hasField":[8,11],"requestMask":{"includeField":[1,2,3,8,9,11,32]},"commonParams":{"includeAffinity":[3]},"extensionSet":{"extensionNames":[4]}}}}
1077 */
1078 	request_data = g_strdup_printf("{\"method\":\"plusi.ozinternal.listmergedpeople\",\"id\":\"ListMergedPeople\",\"apiVersion\":\"v2\",\"jsonrpc\":\"2.0\",\"params\":{\"pageSelection\":{\"maxResults\":1000},\"params\":{\"personId\":\"%s\",\"collection\":6,\"hasField\":[8,11],\"requestMask\":{\"includeField\":[1,2,3,8,9,11,32]},\"commonParams\":{\"includeAffinity\":[3]},\"extensionSet\":{\"extensionNames\":[4]}}}}", ha->self_gaia_id);
1079 
1080 	hangouts_client6_request(ha, "/rpc/plusi?key=" GOOGLE_GPLUS_KEY, HANGOUTS_CONTENT_TYPE_JSON, request_data, -1, HANGOUTS_CONTENT_TYPE_JSON, hangouts_got_buddy_list, ha);
1081 
1082 	g_free(request_data);
1083 
1084 	//TODO blocked users
1085 /* {"method":"plusi.ozinternal.loadblockedpeople","id":"getBlockedUsers","apiVersion":"v2","jsonrpc":"2.0","params":{"includeChatBlocked":true}} */
1086 }
1087 
1088 void
hangouts_block_user(PurpleConnection * pc,const char * who)1089 hangouts_block_user(PurpleConnection *pc, const char *who)
1090 {
1091 	HangoutsAccount *ha = purple_connection_get_protocol_data(pc);
1092 	gchar *request_data;
1093 
1094 	request_data = g_strdup_printf("{\"method\":\"plusi.ozinternal.blockuser\",\"id\":\"blockUser\",\"apiVersion\":\"v2\",\"jsonrpc\":\"2.0\",\"params\":{\"membersToBlock\": {\"members\":[{\"memberId\":{\"obfuscatedGaiaId\":\"%s\"}}],\"block\":true}}}", who);
1095 
1096 	hangouts_client6_request(ha, "/rpc/plusi?key=" GOOGLE_GPLUS_KEY, HANGOUTS_CONTENT_TYPE_JSON, request_data, -1, HANGOUTS_CONTENT_TYPE_JSON, NULL, ha);
1097 
1098 	g_free(request_data);
1099 }
1100 
1101 void
hangouts_unblock_user(PurpleConnection * pc,const char * who)1102 hangouts_unblock_user(PurpleConnection *pc, const char *who)
1103 {
1104 	HangoutsAccount *ha = purple_connection_get_protocol_data(pc);
1105 	gchar *request_data;
1106 
1107 	request_data = g_strdup_printf("{\"method\":\"plusi.ozinternal.blockuser\",\"id\":\"unblockUser\",\"apiVersion\":\"v2\",\"jsonrpc\":\"2.0\",\"params\":{\"membersToBlock\": {\"members\":[{\"memberId\":{\"obfuscatedGaiaId\":\"%s\"}}],\"block\":false},\"legacyChatUnblock\":true}}", who);
1108 
1109 	hangouts_client6_request(ha, "/rpc/plusi?key=" GOOGLE_GPLUS_KEY, HANGOUTS_CONTENT_TYPE_JSON, request_data, -1, HANGOUTS_CONTENT_TYPE_JSON, NULL, ha);
1110 
1111 	g_free(request_data);
1112 }
1113 
1114 static Segment **
hangouts_convert_html_to_segments(HangoutsAccount * ha,const gchar * html_message,guint * segments_count)1115 hangouts_convert_html_to_segments(HangoutsAccount *ha, const gchar *html_message, guint *segments_count)
1116 {
1117 	guint n_segments;
1118 	Segment **segments;
1119 	const gchar *c = html_message;
1120 	GString *text_content;
1121 	Segment *segment;
1122 	guint i;
1123 	GList *segment_list = NULL;
1124 	gboolean is_bold = FALSE, is_italic = FALSE, is_strikethrough = FALSE, is_underline = FALSE;
1125 	gboolean is_link = FALSE;
1126 	gchar *last_link = NULL;
1127 
1128 	if (c == NULL || *c == '\0') {
1129 		g_warn_if_reached();
1130 
1131 		if (segments_count != NULL) {
1132 			*segments_count = 0;
1133 		}
1134 		return NULL;
1135 	}
1136 
1137 	text_content = g_string_new("");
1138 	segment = g_new0(Segment, 1);
1139 	segment__init(segment);
1140 
1141 	while (c && *c) {
1142 		if(*c == '<') {
1143 			gboolean opening = TRUE;
1144 			GString *tag = g_string_new("");
1145 			c++;
1146 			if(*c == '/') { // closing tag
1147 				opening = FALSE;
1148 				c++;
1149 			}
1150 			while (*c != ' ' && *c != '>') {
1151 				g_string_append_c(tag, *c);
1152 				c++;
1153 			}
1154 			if (text_content->len) {
1155 				segment->text = g_string_free(text_content, FALSE);
1156 				text_content = g_string_new("");
1157 
1158 				segment->formatting = g_new0(Formatting, 1);
1159 				formatting__init(segment->formatting);
1160 				segment->formatting->has_bold = TRUE;
1161 				segment->formatting->bold = is_bold;
1162 				segment->formatting->has_italics = TRUE;
1163 				segment->formatting->italics = is_italic;
1164 				segment->formatting->has_strikethrough = TRUE;
1165 				segment->formatting->strikethrough = is_strikethrough;
1166 				segment->formatting->has_underline = TRUE;
1167 				segment->formatting->underline = is_underline;
1168 
1169 				if (is_link) {
1170 					segment->type = SEGMENT_TYPE__SEGMENT_TYPE_LINK;
1171 					if (last_link) {
1172 						segment->link_data = g_new0(LinkData, 1);
1173 						link_data__init(segment->link_data);
1174 						segment->link_data->link_target = g_strdup(last_link);
1175 					}
1176 				}
1177 
1178 				segment_list = g_list_append(segment_list, segment);
1179 				segment = g_new0(Segment, 1);
1180 				segment__init(segment);
1181 			}
1182 			if (!g_ascii_strcasecmp(tag->str, "BR") ||
1183 					!g_ascii_strcasecmp(tag->str, "BR/")) {
1184 				//Line break, push directly onto the stack
1185 				segment->type = SEGMENT_TYPE__SEGMENT_TYPE_LINE_BREAK;
1186 				segment_list = g_list_append(segment_list, segment);
1187 				segment = g_new0(Segment, 1);
1188 				segment__init(segment);
1189 			} else if (!g_ascii_strcasecmp(tag->str, "B") ||
1190 					!g_ascii_strcasecmp(tag->str, "BOLD") ||
1191 					!g_ascii_strcasecmp(tag->str, "STRONG")) {
1192 				is_bold = opening;
1193 			} else if (!g_ascii_strcasecmp(tag->str, "I") ||
1194 					!g_ascii_strcasecmp(tag->str, "ITALIC") ||
1195 					!g_ascii_strcasecmp(tag->str, "EM")) {
1196 				is_italic = opening;
1197 			} else if (!g_ascii_strcasecmp(tag->str, "S") ||
1198 					!g_ascii_strcasecmp(tag->str, "STRIKE")) {
1199 				is_strikethrough = opening;
1200 			} else if (!g_ascii_strcasecmp(tag->str, "U") ||
1201 					!g_ascii_strcasecmp(tag->str, "UNDERLINE")) {
1202 				is_underline = opening;
1203 			} else if (!g_ascii_strcasecmp(tag->str, "A")) {
1204 				is_link = opening;
1205 				if (opening) {
1206 					while (*c != '>') {
1207 						//Grab HREF for the A
1208 						if (g_ascii_strcasecmp(tag->str, " HREF=") == 0) {
1209 							gchar *href_end;
1210 							c += 6;
1211 							if (*c == '"' || *c == '\'') {
1212 								href_end = strchr(c + 1, *c);
1213 								c++;
1214 							} else {
1215 								//Wow this should not happen, but what the hell
1216 								href_end = MIN(strchr(c, ' '), strchr(c, '>'));
1217 								if (!href_end)
1218 									href_end = strchr(c, '>');
1219 							}
1220 							g_free(last_link);
1221 
1222 							if (href_end > c) {
1223 								gchar *attrib = g_strndup(c, href_end - c);
1224 								last_link = purple_unescape_text(attrib);
1225 								g_free(attrib);
1226 
1227 								c = href_end;
1228 								break;
1229 							}
1230 
1231 							// Shouldn't be here :s
1232 							g_warn_if_reached();
1233 							last_link = NULL;
1234 						}
1235 						c++;
1236 					}
1237 				} else {
1238 					g_free(last_link);
1239 					last_link = NULL;
1240 				}
1241 			}
1242 			while (*c != '>') {
1243 				c++;
1244 			}
1245 
1246 			c++;
1247 			g_string_free(tag, TRUE);
1248 		} else if(*c == '&') {
1249 			const gchar *plain;
1250 			gint len;
1251 
1252 			if ((plain = purple_markup_unescape_entity(c, &len)) == NULL) {
1253 				g_string_append_c(text_content, *c);
1254 				len = 1;
1255 			} else {
1256 				g_string_append(text_content, plain);
1257 			}
1258 			c += len;
1259 		} else {
1260 			g_string_append_c(text_content, *c);
1261 			c++;
1262 		}
1263 	}
1264 
1265 	if (text_content->len) {
1266 		segment->text = g_string_free(text_content, FALSE);
1267 
1268 		segment->formatting = g_new0(Formatting, 1);
1269 		formatting__init(segment->formatting);
1270 		segment->formatting->has_bold = TRUE;
1271 		segment->formatting->bold = is_bold;
1272 		segment->formatting->has_italics = TRUE;
1273 		segment->formatting->italics = is_italic;
1274 		segment->formatting->has_strikethrough = TRUE;
1275 		segment->formatting->strikethrough = is_strikethrough;
1276 		segment->formatting->has_underline = TRUE;
1277 		segment->formatting->underline = is_underline;
1278 
1279 		if (is_link) {
1280 			segment->type = SEGMENT_TYPE__SEGMENT_TYPE_LINK;
1281 			if (last_link) {
1282 				segment->link_data = g_new0(LinkData, 1);
1283 				link_data__init(segment->link_data);
1284 				segment->link_data->link_target = g_strdup(last_link);
1285 			}
1286 		}
1287 
1288 		segment_list = g_list_append(segment_list, segment);
1289 	}
1290 
1291 	n_segments = g_list_length(segment_list);
1292 	segments = g_new0(Segment *, n_segments + 1);
1293 
1294 	for (i = 0; segment_list && segment_list->data; i++) {
1295 		segments[i] = segment_list->data;
1296 
1297 		segment_list = g_list_delete_link(segment_list, segment_list);
1298 	}
1299 
1300 	if (segments_count != NULL) {
1301 		*segments_count = n_segments;
1302 	}
1303 
1304 	g_free(last_link);
1305 	return segments;
1306 }
1307 
1308 static void
hangouts_free_segments(Segment ** segments)1309 hangouts_free_segments(Segment **segments)
1310 {
1311 	guint i;
1312 
1313 	if (segments == NULL) {
1314 		// Our work here is done
1315 		return;
1316 	}
1317 
1318 	for (i = 0; segments[i]; i++) {
1319 		g_free(segments[i]->text);
1320 		g_free(segments[i]->formatting);
1321 		if (segments[i]->link_data != NULL) {
1322 			g_free(segments[i]->link_data->link_target);
1323 		}
1324 		g_free(segments[i]->link_data);
1325 		g_free(segments[i]);
1326 	}
1327 
1328 	g_free(segments);
1329 }
1330 
1331 //Received the photoid of the sent image to be able to attach to an outgoing message
1332 static void
hangouts_conversation_send_image_part2_cb(PurpleHttpConnection * connection,PurpleHttpResponse * response,gpointer user_data)1333 hangouts_conversation_send_image_part2_cb(PurpleHttpConnection *connection, PurpleHttpResponse *response, gpointer user_data)
1334 {
1335 	HangoutsAccount *ha;
1336 	gchar *conv_id;
1337 	gchar *photoid;
1338 	const gchar *response_raw;
1339 	size_t response_len;
1340 	JsonNode *node;
1341 	PurpleConnection *pc = purple_http_conn_get_purple_connection(connection);
1342 	SendChatMessageRequest request;
1343 	ExistingMedia existing_media;
1344 	Photo photo;
1345 
1346 	if (purple_http_response_get_error(response) != NULL) {
1347 		purple_notify_error(pc, _("Image Send Error"), _("There was an error sending the image"), purple_http_response_get_error(response), purple_request_cpar_from_connection(pc));
1348 		g_dataset_destroy(connection);
1349 		return;
1350 	}
1351 
1352 	ha = user_data;
1353 	response_raw = purple_http_response_get_data(response, &response_len);
1354 	purple_debug_info("hangouts", "image_part2_cb %s\n", response_raw);
1355 	node = json_decode(response_raw, response_len);
1356 
1357 	photoid = hangouts_json_path_query_string(node, "$..photoid", NULL);
1358 	conv_id = g_dataset_get_data(connection, "conv_id");
1359 
1360 	send_chat_message_request__init(&request);
1361 	existing_media__init(&existing_media);
1362 	photo__init(&photo);
1363 
1364 	request.request_header = hangouts_get_request_header(ha);
1365 	request.event_request_header = hangouts_get_event_request_header(ha, conv_id);
1366 
1367 	photo.photo_id = photoid;
1368 	existing_media.photo = &photo;
1369 	request.existing_media = &existing_media;
1370 
1371 	hangouts_pblite_send_chat_message(ha, &request, NULL, NULL);
1372 
1373 	g_hash_table_insert(ha->sent_message_ids, g_strdup_printf("%" G_GUINT64_FORMAT, request.event_request_header->client_generated_id), NULL);
1374 
1375 	g_free(photoid);
1376 	g_dataset_destroy(connection);
1377 	hangouts_request_header_free(request.request_header);
1378 	hangouts_event_request_header_free(request.event_request_header);
1379 	json_node_free(node);
1380 }
1381 
1382 //Received the url to upload the image data to
1383 static void
hangouts_conversation_send_image_part1_cb(PurpleHttpConnection * connection,PurpleHttpResponse * response,gpointer user_data)1384 hangouts_conversation_send_image_part1_cb(PurpleHttpConnection *connection, PurpleHttpResponse *response, gpointer user_data)
1385 {
1386 	HangoutsAccount *ha;
1387 	gchar *conv_id;
1388 	PurpleImage *image;
1389 	gchar *upload_url;
1390 	JsonNode *node;
1391 	PurpleHttpRequest *request;
1392 	PurpleHttpConnection *new_connection;
1393 	PurpleConnection *pc = purple_http_conn_get_purple_connection(connection);
1394 	const gchar *response_raw;
1395 	size_t response_len;
1396 
1397 	if (purple_http_response_get_error(response) != NULL) {
1398 		purple_notify_error(pc, _("Image Send Error"), _("There was an error sending the image"), purple_http_response_get_error(response), purple_request_cpar_from_connection(pc));
1399 		g_dataset_destroy(connection);
1400 		return;
1401 	}
1402 
1403 	ha = user_data;
1404 	conv_id = g_dataset_get_data(connection, "conv_id");
1405 	image = g_dataset_get_data(connection, "image");
1406 
1407 	response_raw = purple_http_response_get_data(response, &response_len);
1408 	purple_debug_info("hangouts", "image_part1_cb %s\n", response_raw);
1409 	node = json_decode(response_raw, response_len);
1410 
1411 	upload_url = hangouts_json_path_query_string(node, "$..putInfo.url", NULL);
1412 
1413 	request = purple_http_request_new(upload_url);
1414 	purple_http_request_set_cookie_jar(request, ha->cookie_jar);
1415 	purple_http_request_header_set(request, "Content-Type", "application/octet-stream");
1416 	purple_http_request_set_method(request, "POST");
1417 	purple_http_request_set_contents(request, purple_image_get_data(image), purple_image_get_data_size(image));
1418 
1419 	new_connection = purple_http_request(ha->pc, request, hangouts_conversation_send_image_part2_cb, ha);
1420 	purple_http_request_unref(request);
1421 
1422 	g_dataset_set_data_full(new_connection, "conv_id", g_strdup(conv_id), g_free);
1423 
1424 	g_free(upload_url);
1425 	g_dataset_destroy(connection);
1426 	json_node_free(node);
1427 }
1428 
1429 static void
hangouts_conversation_send_image(HangoutsAccount * ha,const gchar * conv_id,PurpleImage * image)1430 hangouts_conversation_send_image(HangoutsAccount *ha, const gchar *conv_id, PurpleImage *image)
1431 {
1432 	PurpleHttpRequest *request;
1433 	PurpleHttpConnection *connection;
1434 	gchar *postdata;
1435 	gchar *filename;
1436 
1437 	filename = (gchar *)purple_image_get_path(image);
1438 	if (filename != NULL) {
1439 		filename = g_path_get_basename(filename);
1440 	} else {
1441 		filename = g_strdup_printf("purple%u.%s", g_random_int(), purple_image_get_extension(image));
1442 	}
1443 
1444 	postdata = g_strdup_printf("{\"protocolVersion\":\"0.8\",\"createSessionRequest\":{\"fields\":[{\"external\":{\"name\":\"file\",\"filename\":\"%s\",\"put\":{},\"size\":%" G_GSIZE_FORMAT "}},{\"inlined\":{\"name\":\"client\",\"content\":\"hangouts\",\"contentType\":\"text/plain\"}}]}}", filename, (gsize) purple_image_get_data_size(image));
1445 
1446 	request = purple_http_request_new(HANGOUTS_IMAGE_UPLOAD_URL);
1447 	purple_http_request_set_cookie_jar(request, ha->cookie_jar);
1448 	purple_http_request_header_set(request, "Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
1449 	purple_http_request_set_method(request, "POST");
1450 	purple_http_request_set_contents(request, postdata, -1);
1451 	purple_http_request_set_max_redirects(request, 0);
1452 
1453 	connection = purple_http_request(ha->pc, request, hangouts_conversation_send_image_part1_cb, ha);
1454 	purple_http_request_unref(request);
1455 
1456 	g_dataset_set_data_full(connection, "conv_id", g_strdup(conv_id), g_free);
1457 	g_dataset_set_data_full(connection, "image", image, NULL);
1458 
1459 	g_free(filename);
1460 	g_free(postdata);
1461 }
1462 
1463 static void
hangouts_conversation_check_message_for_images(HangoutsAccount * ha,const gchar * conv_id,const gchar * message)1464 hangouts_conversation_check_message_for_images(HangoutsAccount *ha, const gchar *conv_id, const gchar *message)
1465 {
1466 	const gchar *img;
1467 
1468 	if ((img = strstr(message, "<img ")) || (img = strstr(message, "<IMG "))) {
1469 		const gchar *id, *src;
1470 		const gchar *close = strchr(img, '>');
1471 
1472 		if (((id = strstr(img, "ID=\"")) || (id = strstr(img, "id=\""))) &&
1473 				id < close) {
1474 			int imgid = atoi(id + 4);
1475 			PurpleImage *image = purple_image_store_get(imgid);
1476 
1477 			if (image != NULL) {
1478 				hangouts_conversation_send_image(ha, conv_id, image);
1479 			}
1480 		} else if (((src = strstr(img, "SRC=\"")) || (src = strstr(img, "src=\""))) &&
1481 				src < close) {
1482 			// purple3 embeds images using src="purple-image:1"
1483 			if (strncmp(src + 5, "purple-image:", 13) == 0) {
1484 				int imgid = atoi(src + 5 + 13);
1485 				PurpleImage *image = purple_image_store_get(imgid);
1486 
1487 				if (image != NULL) {
1488 					hangouts_conversation_send_image(ha, conv_id, image);
1489 				}
1490 			}
1491 		}
1492 	}
1493 }
1494 
1495 static gint
hangouts_conversation_send_message(HangoutsAccount * ha,const gchar * conv_id,const gchar * message)1496 hangouts_conversation_send_message(HangoutsAccount *ha, const gchar *conv_id, const gchar *message)
1497 {
1498 	SendChatMessageRequest request;
1499 	MessageContent message_content;
1500 	EventAnnotation event_annotation;
1501 	Segment **segments;
1502 	guint n_segments;
1503 	gchar *message_dup = g_strdup(message);
1504 
1505 	//Check for any images to send first
1506 	hangouts_conversation_check_message_for_images(ha, conv_id, message_dup);
1507 
1508 	send_chat_message_request__init(&request);
1509 	message_content__init(&message_content);
1510 
1511 	if (purple_message_meify(message_dup, -1)) {
1512 		//TODO put purple_account_get_private_alias(sa->account) on the front
1513 
1514 		event_annotation__init(&event_annotation);
1515 		event_annotation.has_type = TRUE;
1516 		event_annotation.type = HANGOUTS_MAGIC_HALF_EIGHT_SLASH_ME_TYPE;
1517 
1518 		request.n_annotation = 1;
1519 		request.annotation = g_new0(EventAnnotation *, 1);
1520 		request.annotation[0] = &event_annotation;
1521 	}
1522 
1523 	segments = hangouts_convert_html_to_segments(ha, message_dup, &n_segments);
1524 	message_content.segment = segments;
1525 	message_content.n_segment = n_segments;
1526 
1527 	request.request_header = hangouts_get_request_header(ha);
1528 	request.event_request_header = hangouts_get_event_request_header(ha, conv_id);
1529 	request.message_content = &message_content;
1530 
1531 	//purple_debug_info("hangouts", "%s\n", pblite_dump_json((ProtobufCMessage *)&request)); //leaky
1532 
1533 	//TODO listen to response
1534 	hangouts_pblite_send_chat_message(ha, &request, NULL, NULL);
1535 
1536 	g_hash_table_insert(ha->sent_message_ids, g_strdup_printf("%" G_GUINT64_FORMAT, request.event_request_header->client_generated_id), NULL);
1537 
1538 	hangouts_free_segments(segments);
1539 	hangouts_request_header_free(request.request_header);
1540 	hangouts_event_request_header_free(request.event_request_header);
1541 
1542 	g_free(message_dup);
1543 
1544 	return 1;
1545 }
1546 
1547 
1548 gint
hangouts_send_im(PurpleConnection * pc,PurpleMessage * msg)1549 hangouts_send_im(PurpleConnection *pc,
1550 #if PURPLE_VERSION_CHECK(3, 0, 0)
1551 PurpleMessage *msg)
1552 {
1553 	const gchar *who = purple_message_get_recipient(msg);
1554 	const gchar *message = purple_message_get_contents(msg);
1555 #else
1556 const gchar *who, const gchar *message, PurpleMessageFlags flags)
1557 {
1558 #endif
1559 
1560 	HangoutsAccount *ha;
1561 	const gchar *conv_id;
1562 
1563 	ha = purple_connection_get_protocol_data(pc);
1564 	conv_id = g_hash_table_lookup(ha->one_to_ones_rev, who);
1565 	if (conv_id == NULL) {
1566 		if (G_UNLIKELY(!hangouts_is_valid_id(who))) {
1567 			hangouts_search_users_text(ha, who);
1568 			return -1;
1569 		}
1570 
1571 		//We don't have any known conversations for this person
1572 		hangouts_create_conversation(ha, TRUE, who, message);
1573 	}
1574 
1575 	return hangouts_conversation_send_message(ha, conv_id, message);
1576 }
1577 
1578 gint
1579 hangouts_chat_send(PurpleConnection *pc, gint id,
1580 #if PURPLE_VERSION_CHECK(3, 0, 0)
1581 PurpleMessage *msg)
1582 {
1583 	const gchar *message = purple_message_get_contents(msg);
1584 #else
1585 const gchar *message, PurpleMessageFlags flags)
1586 {
1587 #endif
1588 
1589 	HangoutsAccount *ha;
1590 	const gchar *conv_id;
1591 	PurpleChatConversation *chatconv;
1592 	gint ret;
1593 
1594 	ha = purple_connection_get_protocol_data(pc);
1595 	chatconv = purple_conversations_find_chat(pc, id);
1596 	conv_id = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "conv_id");
1597 	if (!conv_id) {
1598 		// Fix for a race condition around the chat data and serv_got_joined_chat()
1599 		conv_id = purple_conversation_get_name(PURPLE_CONVERSATION(chatconv));
1600 		g_return_val_if_fail(conv_id, -1);
1601 	}
1602 	g_return_val_if_fail(g_hash_table_contains(ha->group_chats, conv_id), -1);
1603 
1604 	ret = hangouts_conversation_send_message(ha, conv_id, message);
1605 	if (ret > 0) {
1606 		purple_serv_got_chat_in(pc, g_str_hash(conv_id), ha->self_gaia_id, PURPLE_MESSAGE_SEND, message, time(NULL));
1607 	}
1608 	return ret;
1609 }
1610 
1611 guint
1612 hangouts_send_typing(PurpleConnection *pc, const gchar *who, PurpleIMTypingState state)
1613 {
1614 	HangoutsAccount *ha;
1615 	PurpleConversation *conv;
1616 
1617 	ha = purple_connection_get_protocol_data(pc);
1618 	conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(who, purple_connection_get_account(pc)));
1619 	g_return_val_if_fail(conv, -1);
1620 
1621 	return hangouts_conv_send_typing(conv, state, ha);
1622 }
1623 
1624 guint
1625 hangouts_conv_send_typing(PurpleConversation *conv, PurpleIMTypingState state, HangoutsAccount *ha)
1626 {
1627 	PurpleConnection *pc;
1628 	const gchar *conv_id;
1629 	SetTypingRequest request;
1630 	ConversationId conversation_id;
1631 
1632 	pc = purple_conversation_get_connection(conv);
1633 
1634 	if (!PURPLE_CONNECTION_IS_CONNECTED(pc))
1635 		return 0;
1636 
1637 	if (!purple_strequal(purple_protocol_get_id(purple_connection_get_protocol(pc)), HANGOUTS_PLUGIN_ID))
1638 		return 0;
1639 
1640 	if (ha == NULL) {
1641 		ha = purple_connection_get_protocol_data(pc);
1642 	}
1643 
1644 	conv_id = purple_conversation_get_data(conv, "conv_id");
1645 	if (conv_id == NULL) {
1646 		if (PURPLE_IS_IM_CONVERSATION(conv)) {
1647 			conv_id = g_hash_table_lookup(ha->one_to_ones_rev, purple_conversation_get_name(conv));
1648 		} else {
1649 			conv_id = purple_conversation_get_name(conv);
1650 		}
1651 	}
1652 	g_return_val_if_fail(conv_id, -1); //TODO create new conversation for this new person
1653 
1654 	set_typing_request__init(&request);
1655 	request.request_header = hangouts_get_request_header(ha);
1656 
1657 	conversation_id__init(&conversation_id);
1658 	conversation_id.id = (gchar *) conv_id;
1659 	request.conversation_id = &conversation_id;
1660 
1661 	request.has_type = TRUE;
1662 	switch(state) {
1663 		case PURPLE_IM_TYPING:
1664 			request.type = TYPING_TYPE__TYPING_TYPE_STARTED;
1665 			break;
1666 
1667 		case PURPLE_IM_TYPED:
1668 			request.type = TYPING_TYPE__TYPING_TYPE_PAUSED;
1669 			break;
1670 
1671 		case PURPLE_IM_NOT_TYPING:
1672 		default:
1673 			request.type = TYPING_TYPE__TYPING_TYPE_STOPPED;
1674 			break;
1675 	}
1676 
1677 	//TODO listen to response
1678 	//TODO dont send STOPPED if we just sent a message
1679 	hangouts_pblite_set_typing(ha, &request, NULL, NULL);
1680 
1681 	hangouts_request_header_free(request.request_header);
1682 
1683 	return 20;
1684 }
1685 
1686 void
1687 hangouts_chat_leave_by_conv_id(PurpleConnection *pc, const gchar *conv_id, const gchar *who)
1688 {
1689 	HangoutsAccount *ha;
1690 	RemoveUserRequest request;
1691 	ParticipantId participant_id;
1692 
1693 	g_return_if_fail(conv_id);
1694 	ha = purple_connection_get_protocol_data(pc);
1695 	g_return_if_fail(g_hash_table_contains(ha->group_chats, conv_id));
1696 
1697 	remove_user_request__init(&request);
1698 
1699 	if (who != NULL) {
1700 		participant_id__init(&participant_id);
1701 
1702 		participant_id.gaia_id = (gchar *) who;
1703 		participant_id.chat_id = (gchar *) who; //XX do we need this?
1704 		request.participant_id = &participant_id;
1705 	}
1706 
1707 	request.request_header = hangouts_get_request_header(ha);
1708 	request.event_request_header = hangouts_get_event_request_header(ha, conv_id);
1709 
1710 	//XX do we need to see if this was successful, or does it just come through as a new event?
1711 	hangouts_pblite_remove_user(ha, &request, NULL, NULL);
1712 
1713 	hangouts_request_header_free(request.request_header);
1714 	hangouts_event_request_header_free(request.event_request_header);
1715 
1716 	if (who == NULL) {
1717 		g_hash_table_remove(ha->group_chats, conv_id);
1718 	}
1719 }
1720 
1721 void
1722 hangouts_chat_leave(PurpleConnection *pc, int id)
1723 {
1724 	const gchar *conv_id;
1725 	PurpleChatConversation *chatconv;
1726 
1727 	chatconv = purple_conversations_find_chat(pc, id);
1728 	conv_id = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "conv_id");
1729 	if (conv_id == NULL) {
1730 		// Fix for a race condition around the chat data and serv_got_joined_chat()
1731 		conv_id = purple_conversation_get_name(PURPLE_CONVERSATION(chatconv));
1732 	}
1733 
1734 	return hangouts_chat_leave_by_conv_id(pc, conv_id, NULL);
1735 }
1736 
1737 void
1738 hangouts_chat_kick(PurpleConnection *pc, int id, const gchar *who)
1739 {
1740 	const gchar *conv_id;
1741 	PurpleChatConversation *chatconv;
1742 
1743 	chatconv = purple_conversations_find_chat(pc, id);
1744 	conv_id = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "conv_id");
1745 	if (conv_id == NULL) {
1746 		// Fix for a race condition around the chat data and serv_got_joined_chat()
1747 		conv_id = purple_conversation_get_name(PURPLE_CONVERSATION(chatconv));
1748 	}
1749 
1750 	return hangouts_chat_leave_by_conv_id(pc, conv_id, who);
1751 }
1752 
1753 static void
1754 hangouts_created_conversation(HangoutsAccount *ha, CreateConversationResponse *response, gpointer user_data)
1755 {
1756 	Conversation *conversation = response->conversation;
1757 	gchar *message = user_data;
1758 	const gchar *conv_id;
1759 
1760 	gchar *dump = pblite_dump_json((ProtobufCMessage *) response);
1761 	purple_debug_info("hangouts", "%s\n", dump);
1762 	g_free(dump);
1763 
1764 	if (conversation == NULL) {
1765 		purple_debug_error("hangouts", "Could not create conversation\n");
1766 		g_free(message);
1767 		return;
1768 	}
1769 
1770 	hangouts_add_conversation_to_blist(ha, conversation, NULL);
1771 	conv_id = conversation->conversation_id->id;
1772 	hangouts_get_conversation_events(ha, conv_id, 0);
1773 
1774 	if (message != NULL) {
1775 		hangouts_conversation_send_message(ha, conv_id, message);
1776 		g_free(message);
1777 	}
1778 }
1779 
1780 void
1781 hangouts_create_conversation(HangoutsAccount *ha, gboolean is_one_to_one, const char *who, const gchar *optional_message)
1782 {
1783 	CreateConversationRequest request;
1784 	gchar *message_dup = NULL;
1785 
1786 	create_conversation_request__init(&request);
1787 	request.request_header = hangouts_get_request_header(ha);
1788 
1789 	request.has_type = TRUE;
1790 	if (is_one_to_one) {
1791 		request.type = CONVERSATION_TYPE__CONVERSATION_TYPE_ONE_TO_ONE;
1792 	} else {
1793 		request.type = CONVERSATION_TYPE__CONVERSATION_TYPE_GROUP;
1794 	}
1795 
1796 	request.n_invitee_id = 1;
1797 	request.invitee_id = g_new0(InviteeID *, 1);
1798 	request.invitee_id[0] = g_new0(InviteeID, 1);
1799 	invitee_id__init(request.invitee_id[0]);
1800 	request.invitee_id[0]->gaia_id = g_strdup(who);
1801 
1802 	request.has_client_generated_id = TRUE;
1803 	request.client_generated_id = g_random_int();
1804 
1805 	if (optional_message != NULL) {
1806 		message_dup = g_strdup(optional_message);
1807 	}
1808 
1809 	hangouts_pblite_create_conversation(ha, &request, hangouts_created_conversation, message_dup);
1810 
1811 	g_free(request.invitee_id[0]->gaia_id);
1812 	g_free(request.invitee_id[0]);
1813 	g_free(request.invitee_id);
1814 	hangouts_request_header_free(request.request_header);
1815 }
1816 
1817 void
1818 hangouts_archive_conversation(HangoutsAccount *ha, const gchar *conv_id)
1819 {
1820 	ModifyConversationViewRequest request;
1821 	ConversationId conversation_id;
1822 
1823 	if (conv_id == NULL) {
1824 		return;
1825 	}
1826 
1827 	modify_conversation_view_request__init(&request);
1828 	conversation_id__init(&conversation_id);
1829 
1830 	conversation_id.id = (gchar *)conv_id;
1831 
1832 	request.request_header = hangouts_get_request_header(ha);
1833 	request.conversation_id = &conversation_id;
1834 	request.has_new_view = TRUE;
1835 	request.new_view = CONVERSATION_VIEW__CONVERSATION_VIEW_ARCHIVED;
1836 	request.has_last_event_timestamp = TRUE;
1837 	request.last_event_timestamp = ha->last_event_timestamp;
1838 
1839 	hangouts_pblite_modify_conversation_view(ha, &request, NULL, NULL);
1840 
1841 	hangouts_request_header_free(request.request_header);
1842 
1843 	if (g_hash_table_contains(ha->one_to_ones, conv_id)) {
1844 		gchar *buddy_id = g_hash_table_lookup(ha->one_to_ones, conv_id);
1845 
1846 		g_hash_table_remove(ha->one_to_ones_rev, buddy_id);
1847 		g_hash_table_remove(ha->one_to_ones, conv_id);
1848 	} else {
1849 		g_hash_table_remove(ha->group_chats, conv_id);
1850 	}
1851 }
1852 
1853 void
1854 hangouts_initiate_chat_from_node(PurpleBlistNode *node, gpointer userdata)
1855 {
1856 	if(PURPLE_IS_BUDDY(node))
1857 	{
1858 		PurpleBuddy *buddy = (PurpleBuddy *) node;
1859 		HangoutsAccount *ha;
1860 
1861 		if (userdata) {
1862 			ha = userdata;
1863 		} else {
1864 			PurpleConnection *pc = purple_account_get_connection(purple_buddy_get_account(buddy));
1865 			ha = purple_connection_get_protocol_data(pc);
1866 		}
1867 
1868 		hangouts_create_conversation(ha, FALSE, purple_buddy_get_name(buddy), NULL);
1869 	}
1870 }
1871 
1872 void
1873 hangouts_chat_invite(PurpleConnection *pc, int id, const char *message, const char *who)
1874 {
1875 	HangoutsAccount *ha;
1876 	const gchar *conv_id;
1877 	PurpleChatConversation *chatconv;
1878 	AddUserRequest request;
1879 
1880 	ha = purple_connection_get_protocol_data(pc);
1881 	chatconv = purple_conversations_find_chat(pc, id);
1882 	conv_id = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "conv_id");
1883 	if (conv_id == NULL) {
1884 		conv_id = purple_conversation_get_name(PURPLE_CONVERSATION(chatconv));
1885 	}
1886 
1887 	add_user_request__init(&request);
1888 
1889 	request.request_header = hangouts_get_request_header(ha);
1890 	request.event_request_header = hangouts_get_event_request_header(ha, conv_id);
1891 
1892 	request.n_invitee_id = 1;
1893 	request.invitee_id = g_new0(InviteeID *, 1);
1894 	request.invitee_id[0] = g_new0(InviteeID, 1);
1895 	invitee_id__init(request.invitee_id[0]);
1896 	request.invitee_id[0]->gaia_id = g_strdup(who);
1897 
1898 	hangouts_pblite_add_user(ha, &request, NULL, NULL);
1899 
1900 	g_free(request.invitee_id[0]->gaia_id);
1901 	g_free(request.invitee_id[0]);
1902 	g_free(request.invitee_id);
1903 	hangouts_request_header_free(request.request_header);
1904 	hangouts_event_request_header_free(request.event_request_header);
1905 }
1906 
1907 #define PURPLE_CONVERSATION_IS_VALID(conv) (g_list_find(purple_conversations_get_all(), conv) != NULL)
1908 
1909 gboolean
1910 hangouts_mark_conversation_focused_timeout(gpointer convpointer)
1911 {
1912 	PurpleConversation *conv = convpointer;
1913 	PurpleConnection *pc;
1914 	PurpleAccount *account;
1915 	HangoutsAccount *ha;
1916 	SetFocusRequest request;
1917 	ConversationId conversation_id;
1918 	const gchar *conv_id = NULL;
1919 	gboolean is_focused;
1920 
1921 	if (!PURPLE_CONVERSATION_IS_VALID(conv))
1922 		return FALSE;
1923 
1924 	account = purple_conversation_get_account(conv);
1925 	if (account == NULL || !purple_account_is_connected(account))
1926 		return FALSE;
1927 
1928 	pc = purple_account_get_connection(account);
1929 	if (!PURPLE_CONNECTION_IS_CONNECTED(pc))
1930 		return FALSE;
1931 
1932 	ha = purple_connection_get_protocol_data(pc);
1933 
1934 	is_focused = purple_conversation_has_focus(conv);
1935 	if (is_focused && ha->last_conversation_focused == conv)
1936 		return FALSE;
1937 
1938 	set_focus_request__init(&request);
1939 	request.request_header = hangouts_get_request_header(ha);
1940 
1941 	conv_id = purple_conversation_get_data(conv, "conv_id");
1942 	if (conv_id == NULL) {
1943 		if (PURPLE_IS_IM_CONVERSATION(conv)) {
1944 			conv_id = g_hash_table_lookup(ha->one_to_ones_rev, purple_conversation_get_name(conv));
1945 		} else {
1946 			conv_id = purple_conversation_get_name(conv);
1947 		}
1948 	}
1949 	conversation_id__init(&conversation_id);
1950 	conversation_id.id = (gchar *) conv_id;
1951 	request.conversation_id = &conversation_id;
1952 
1953 	if (is_focused) {
1954 		request.type = FOCUS_TYPE__FOCUS_TYPE_FOCUSED;
1955 		ha->last_conversation_focused = conv;
1956 	} else {
1957 		request.type = FOCUS_TYPE__FOCUS_TYPE_UNFOCUSED;
1958 		if (ha->last_conversation_focused == conv) {
1959 			ha->last_conversation_focused = NULL;
1960 		}
1961 	}
1962 	request.has_type = TRUE;
1963 
1964 	hangouts_pblite_set_focus(ha, &request, (HangoutsPbliteSetFocusResponseFunc)hangouts_default_response_dump, NULL);
1965 
1966 	hangouts_request_header_free(request.request_header);
1967 
1968 	return FALSE;
1969 }
1970 
1971 gboolean
1972 hangouts_mark_conversation_seen_timeout(gpointer convpointer)
1973 {
1974 	PurpleConversation *conv = convpointer;
1975 	PurpleAccount *account;
1976 	PurpleConnection *pc;
1977 	HangoutsAccount *ha;
1978 	UpdateWatermarkRequest request;
1979 	ConversationId conversation_id;
1980 	const gchar *conv_id = NULL;
1981 	gint64 *last_read_timestamp_ptr, last_read_timestamp = 0;
1982 	gint64 *last_event_timestamp_ptr, last_event_timestamp = 0;
1983 
1984 	if (!PURPLE_CONVERSATION_IS_VALID(conv))
1985 		return FALSE;
1986 	if (!purple_conversation_has_focus(conv))
1987 		return FALSE;
1988 	account = purple_conversation_get_account(conv);
1989 	if (account == NULL || !purple_account_is_connected(account))
1990 		return FALSE;
1991 	pc = purple_account_get_connection(account);
1992 	if (!PURPLE_CONNECTION_IS_CONNECTED(pc))
1993 		return FALSE;
1994 
1995 	purple_conversation_set_data(conv, "mark_seen_timeout", NULL);
1996 
1997 	ha = purple_connection_get_protocol_data(pc);
1998 
1999 	if (!purple_presence_is_status_primitive_active(purple_account_get_presence(ha->account), PURPLE_STATUS_AVAILABLE)) {
2000 		// We're not here
2001 		return FALSE;
2002 	}
2003 
2004 	last_read_timestamp_ptr = (gint64 *)purple_conversation_get_data(conv, "last_read_timestamp");
2005 	if (last_read_timestamp_ptr != NULL) {
2006 		last_read_timestamp = *last_read_timestamp_ptr;
2007 	}
2008 	last_event_timestamp_ptr = (gint64 *)purple_conversation_get_data(conv, "last_event_timestamp");
2009 	if (last_event_timestamp_ptr != NULL) {
2010 		last_event_timestamp = *last_event_timestamp_ptr;
2011 	}
2012 
2013 	if (last_event_timestamp <= last_read_timestamp) {
2014 		return FALSE;
2015 	}
2016 
2017 	update_watermark_request__init(&request);
2018 	request.request_header = hangouts_get_request_header(ha);
2019 
2020 	conv_id = purple_conversation_get_data(conv, "conv_id");
2021 	if (conv_id == NULL) {
2022 		if (PURPLE_IS_IM_CONVERSATION(conv)) {
2023 			conv_id = g_hash_table_lookup(ha->one_to_ones_rev, purple_conversation_get_name(conv));
2024 		} else {
2025 			conv_id = purple_conversation_get_name(conv);
2026 		}
2027 	}
2028 	conversation_id__init(&conversation_id);
2029 	conversation_id.id = (gchar *) conv_id;
2030 	request.conversation_id = &conversation_id;
2031 
2032 	request.has_last_read_timestamp = TRUE;
2033 	request.last_read_timestamp = last_event_timestamp;
2034 
2035 	hangouts_pblite_update_watermark(ha, &request, (HangoutsPbliteUpdateWatermarkResponseFunc)hangouts_default_response_dump, NULL);
2036 
2037 	hangouts_request_header_free(request.request_header);
2038 
2039 	if (last_read_timestamp_ptr == NULL) {
2040 		last_read_timestamp_ptr = g_new0(gint64, 1);
2041 	}
2042 	*last_read_timestamp_ptr = last_event_timestamp;
2043 	purple_conversation_set_data(conv, "last_read_timestamp", last_read_timestamp_ptr);
2044 
2045 	return FALSE;
2046 }
2047 
2048 void
2049 hangouts_mark_conversation_seen(PurpleConversation *conv, PurpleConversationUpdateType type)
2050 {
2051 	gint mark_seen_timeout;
2052 	PurpleConnection *pc;
2053 
2054 	if (type != PURPLE_CONVERSATION_UPDATE_UNSEEN)
2055 		return;
2056 
2057 	pc = purple_conversation_get_connection(conv);
2058 	if (!PURPLE_CONNECTION_IS_CONNECTED(pc))
2059 		return;
2060 
2061 	if (!purple_strequal(purple_protocol_get_id(purple_connection_get_protocol(pc)), HANGOUTS_PLUGIN_ID))
2062 		return;
2063 
2064 	mark_seen_timeout = GPOINTER_TO_INT(purple_conversation_get_data(conv, "mark_seen_timeout"));
2065 
2066 	if (mark_seen_timeout) {
2067 		g_source_remove(mark_seen_timeout);
2068 	}
2069 
2070 	mark_seen_timeout = g_timeout_add_seconds(1, hangouts_mark_conversation_seen_timeout, conv);
2071 
2072 	purple_conversation_set_data(conv, "mark_seen_timeout", GINT_TO_POINTER(mark_seen_timeout));
2073 
2074 	g_timeout_add_seconds(1, hangouts_mark_conversation_focused_timeout, conv);
2075 
2076 	hangouts_set_active_client(pc);
2077 }
2078 
2079 void
2080 hangouts_set_status(PurpleAccount *account, PurpleStatus *status)
2081 {
2082 	SetPresenceRequest request;
2083 	PurpleConnection *pc = purple_account_get_connection(account);
2084 	HangoutsAccount *ha = purple_connection_get_protocol_data(pc);
2085 	Segment **segments = NULL;
2086 	const gchar *message;
2087 	DndSetting dnd_setting;
2088 	PresenceStateSetting presence_state_setting;
2089 	MoodSetting mood_setting;
2090 	MoodMessage mood_message;
2091 	MoodContent mood_content;
2092 	guint n_segments;
2093 
2094 	set_presence_request__init(&request);
2095 	request.request_header = hangouts_get_request_header(ha);
2096 
2097 	//available:
2098 	if (purple_status_type_get_primitive(purple_status_get_status_type(status)) == PURPLE_STATUS_AVAILABLE) {
2099 		presence_state_setting__init(&presence_state_setting);
2100 		presence_state_setting.has_timeout_secs = TRUE;
2101 		presence_state_setting.timeout_secs = 720;
2102 		presence_state_setting.has_type = TRUE;
2103 		presence_state_setting.type = CLIENT_PRESENCE_STATE_TYPE__CLIENT_PRESENCE_STATE_DESKTOP_ACTIVE;
2104 		request.presence_state_setting = &presence_state_setting;
2105 	}
2106 
2107 	//away
2108 	if (purple_status_type_get_primitive(purple_status_get_status_type(status)) == PURPLE_STATUS_AWAY) {
2109 		presence_state_setting__init(&presence_state_setting);
2110 		presence_state_setting.has_timeout_secs = TRUE;
2111 		presence_state_setting.timeout_secs = 720;
2112 		presence_state_setting.has_type = TRUE;
2113 		presence_state_setting.type = CLIENT_PRESENCE_STATE_TYPE__CLIENT_PRESENCE_STATE_DESKTOP_IDLE;
2114 		request.presence_state_setting = &presence_state_setting;
2115 	}
2116 
2117 	//do-not-disturb
2118 	dnd_setting__init(&dnd_setting);
2119 	if (purple_status_type_get_primitive(purple_status_get_status_type(status)) == PURPLE_STATUS_UNAVAILABLE) {
2120 		dnd_setting.has_do_not_disturb = TRUE;
2121 		dnd_setting.do_not_disturb = TRUE;
2122 		dnd_setting.has_timeout_secs = TRUE;
2123 		dnd_setting.timeout_secs = 172800;
2124 	} else {
2125 		dnd_setting.has_do_not_disturb = TRUE;
2126 		dnd_setting.do_not_disturb = FALSE;
2127 	}
2128 	request.dnd_setting = &dnd_setting;
2129 
2130 	//has message?
2131 	mood_setting__init(&mood_setting);
2132 	mood_message__init(&mood_message);
2133 	mood_content__init(&mood_content);
2134 
2135 	message = purple_status_get_attr_string(status, "message");
2136 	if (message && *message) {
2137 		segments = hangouts_convert_html_to_segments(ha, message, &n_segments);
2138 		mood_content.segment = segments;
2139 		mood_content.n_segment = n_segments;
2140 	}
2141 
2142 	mood_message.mood_content = &mood_content;
2143 	mood_setting.mood_message = &mood_message;
2144 	request.mood_setting = &mood_setting;
2145 
2146 	hangouts_pblite_set_presence(ha, &request, (HangoutsPbliteSetPresenceResponseFunc)hangouts_default_response_dump, NULL);
2147 
2148 	hangouts_request_header_free(request.request_header);
2149 	hangouts_free_segments(segments);
2150 }
2151 
2152 
2153 static void
2154 hangouts_roomlist_got_list(HangoutsAccount *ha, SyncRecentConversationsResponse *response, gpointer user_data)
2155 {
2156 	PurpleRoomlist *roomlist = user_data;
2157 	guint i, j;
2158 
2159 	for (i = 0; i < response->n_conversation_state; i++) {
2160 		ConversationState *conversation_state = response->conversation_state[i];
2161 		Conversation *conversation = conversation_state->conversation;
2162 
2163 		if (conversation->type == CONVERSATION_TYPE__CONVERSATION_TYPE_GROUP) {
2164 			gchar *users = NULL;
2165 			gchar **users_set = g_new0(gchar *, conversation->n_participant_data + 1);
2166 			gchar *name = conversation->name;
2167 			PurpleRoomlistRoom *room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, conversation->conversation_id->id, NULL);
2168 
2169 			purple_roomlist_room_add_field(roomlist, room, conversation->conversation_id->id);
2170 
2171 			for (j = 0; j < conversation->n_participant_data; j++) {
2172 				gchar *p_name = conversation->participant_data[j]->fallback_name;
2173 				if (p_name != NULL) {
2174 					users_set[j] = p_name;
2175 				} else {
2176 					users_set[j] = _("Unknown");
2177 				}
2178 			}
2179 			users = g_strjoinv(", ", users_set);
2180 			g_free(users_set);
2181 			purple_roomlist_room_add_field(roomlist, room, users);
2182 			g_free(users);
2183 
2184 			purple_roomlist_room_add_field(roomlist, room, name);
2185 
2186 			purple_roomlist_room_add(roomlist, room);
2187 		}
2188 	}
2189 
2190 	purple_roomlist_set_in_progress(roomlist, FALSE);
2191 }
2192 
2193 PurpleRoomlist *
2194 hangouts_roomlist_get_list(PurpleConnection *pc)
2195 {
2196 	HangoutsAccount *ha = purple_connection_get_protocol_data(pc);
2197 	PurpleRoomlist *roomlist;
2198 	GList *fields = NULL;
2199 	PurpleRoomlistField *f;
2200 
2201 	roomlist = purple_roomlist_new(ha->account);
2202 
2203 	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("ID"), "chatname", TRUE);
2204 	fields = g_list_append(fields, f);
2205 
2206 	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Users"), "users", FALSE);
2207 	fields = g_list_append(fields, f);
2208 
2209 	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Name"), "name", FALSE);
2210 	fields = g_list_append(fields, f);
2211 
2212 	purple_roomlist_set_fields(roomlist, fields);
2213 	purple_roomlist_set_in_progress(roomlist, TRUE);
2214 
2215 	{
2216 		//Stolen from hangouts_get_conversation_list()
2217 		SyncRecentConversationsRequest request;
2218 		SyncFilter sync_filter[1];
2219 		sync_recent_conversations_request__init(&request);
2220 
2221 		request.request_header = hangouts_get_request_header(ha);
2222 		request.has_max_conversations = TRUE;
2223 		request.max_conversations = 100;
2224 		request.has_max_events_per_conversation = TRUE;
2225 		request.max_events_per_conversation = 1;
2226 
2227 		sync_filter[0] = SYNC_FILTER__SYNC_FILTER_INBOX;
2228 		request.sync_filter = sync_filter;
2229 		request.n_sync_filter = 1;  // Back streets back, alright!
2230 
2231 		hangouts_pblite_sync_recent_conversations(ha, &request, hangouts_roomlist_got_list, roomlist);
2232 
2233 		hangouts_request_header_free(request.request_header);
2234 	}
2235 
2236 
2237 	return roomlist;
2238 }
2239 
2240 void
2241 hangouts_rename_conversation(HangoutsAccount *ha, const gchar *conv_id, const gchar *alias)
2242 {
2243 	RenameConversationRequest request;
2244 
2245 	rename_conversation_request__init(&request);
2246 	request.request_header = hangouts_get_request_header(ha);
2247 	request.event_request_header = hangouts_get_event_request_header(ha, conv_id);
2248 
2249 	request.new_name = (gchar *) alias;
2250 
2251 	hangouts_pblite_rename_conversation(ha, &request, NULL, NULL);
2252 
2253 	hangouts_request_header_free(request.request_header);
2254 	hangouts_event_request_header_free(request.event_request_header);
2255 }
2256 
2257