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