1 /*
2 * GoogleChat 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 "googlechat_events.h"
20
21 #include <string.h>
22 #include <glib.h>
23
24 #include "core.h"
25 #include "debug.h"
26 #include "glibcompat.h"
27 #include "image.h"
28 #include "image-store.h"
29 #include "mediamanager.h"
30
31 #include "googlechat_conversation.h"
32 #include "googlechat.pb-c.h"
33
34 // From googlechat_pblite
35 gchar *pblite_dump_json(ProtobufCMessage *message);
36
37
38 void googlechat_received_other_notification(PurpleConnection *pc, Event *event);
39 void googlechat_received_presence_notification(PurpleConnection *pc, Event *event);
40 void googlechat_received_typing_notification(PurpleConnection *pc, Event *event);
41 void googlechat_received_message_event(PurpleConnection *pc, Event *event);
42 void googlechat_received_read_receipt(PurpleConnection *pc, Event *event);
43 void googlechat_received_group_viewed(PurpleConnection *pc, Event *event);
44
45 //purple_signal_emit(purple_connection_get_protocol(ha->pc), "googlechat-received-event", ha->pc, events_response.event);
46
47 void
googlechat_register_events(gpointer plugin)48 googlechat_register_events(gpointer plugin)
49 {
50 purple_signal_connect(plugin, "googlechat-received-event", plugin, PURPLE_CALLBACK(googlechat_received_typing_notification), NULL);
51 purple_signal_connect(plugin, "googlechat-received-event", plugin, PURPLE_CALLBACK(googlechat_received_presence_notification), NULL);
52 purple_signal_connect(plugin, "googlechat-received-event", plugin, PURPLE_CALLBACK(googlechat_received_message_event), NULL);
53 purple_signal_connect(plugin, "googlechat-received-event", plugin, PURPLE_CALLBACK(googlechat_received_other_notification), NULL);
54 purple_signal_connect(plugin, "googlechat-received-event", plugin, PURPLE_CALLBACK(googlechat_received_read_receipt), NULL);
55 purple_signal_connect(plugin, "googlechat-received-event", plugin, PURPLE_CALLBACK(googlechat_received_group_viewed), NULL);
56 }
57
58 void
googlechat_process_received_event(GoogleChatAccount * ha,Event * event)59 googlechat_process_received_event(GoogleChatAccount *ha, Event *event)
60 {
61 // Can't just use this ;(
62 //purple_signal_emit(purple_connection_get_protocol(ha->pc), "googlechat-received-event", ha->pc, event);
63
64 size_t n_bodies = 0;
65 Event__EventBody **bodies = NULL;
66
67 //An event can have multiple events within it. Mangle the structs to split multiples out into singles
68 if (event->n_bodies) {
69 n_bodies = event->n_bodies;
70 bodies = event->bodies;
71
72 event->n_bodies = 0;
73 event->bodies = NULL;
74 }
75
76 // Send an initial 'bare' event, if there is one
77 if (event->body) {
78 purple_signal_emit(purple_connection_get_protocol(ha->pc), "googlechat-received-event", ha->pc, event);
79 }
80
81 if (n_bodies > 0) {
82 Event__EventBody *orig_body = event->body;
83 guint i;
84
85 // loop through all the sub-bodies and make them a primary
86 for (i = 0; i < n_bodies; i++) {
87 Event__EventBody *body = bodies[i];
88
89 event->body = body;
90
91 event->has_type = TRUE;
92 event->type = body->event_type;
93
94 purple_signal_emit(purple_connection_get_protocol(ha->pc), "googlechat-received-event", ha->pc, event);
95 }
96
97 // put everything back the way it was to let memory be free'd
98 event->body = orig_body;
99 event->n_bodies = n_bodies;
100 event->bodies = bodies;
101 }
102
103 gint64 event_time = 0;
104 if (event->user_revision) {
105 event_time = event->user_revision->timestamp;
106 }
107 if (event->group_revision) {
108 event_time = event->group_revision->timestamp;
109 }
110
111 if (event_time && event_time > ha->last_event_timestamp) {
112 // libpurple can't store a 64bit int on a 32bit machine, so convert to something more usable instead (puke)
113 // also needs to work cross platform, in case the accounts.xml is being shared (double puke)
114 purple_account_set_int(ha->account, "last_event_timestamp_high", event_time >> 32);
115 purple_account_set_int(ha->account, "last_event_timestamp_low", event_time & 0xFFFFFFFF);
116 }
117 }
118
119
120 /*
121 static void
122 googlechat_remove_conversation(GoogleChatAccount *ha, const gchar *conv_id)
123 {
124 if (g_hash_table_contains(ha->one_to_ones, conv_id)) {
125 const gchar *buddy_id = g_hash_table_lookup(ha->one_to_ones, conv_id);
126 PurpleBuddy *buddy = purple_blist_find_buddy(ha->account, buddy_id);
127
128 purple_blist_remove_buddy(buddy);
129 g_hash_table_remove(ha->one_to_ones, conv_id);
130 g_hash_table_remove(ha->one_to_ones_rev, buddy_id);
131
132 } else if (g_hash_table_contains(ha->group_chats, conv_id)) {
133 PurpleChat *chat = purple_blist_find_chat(ha->account, conv_id);
134 purple_blist_remove_chat(chat);
135
136 g_hash_table_remove(ha->group_chats, conv_id);
137
138 } else {
139 // Unknown conversation!
140 return;
141 }
142 }*/
143
144
145 void
googlechat_received_other_notification(PurpleConnection * pc,Event * event)146 googlechat_received_other_notification(PurpleConnection *pc, Event *event)
147 {
148 gchar *json_dump;
149
150 if (event->type == EVENT__EVENT_TYPE__MESSAGE_POSTED ||
151 event->type == EVENT__EVENT_TYPE__TYPING_STATE_CHANGED ||
152 event->type == EVENT__EVENT_TYPE__GROUP_VIEWED ||
153 event->type == EVENT__EVENT_TYPE__USER_STATUS_UPDATED_EVENT ||
154 event->type == EVENT__EVENT_TYPE__READ_RECEIPT_CHANGED
155 ) {
156 return;
157 }
158
159 purple_debug_info("googlechat", "Received new other event %p\n", event);
160 json_dump = pblite_dump_json((ProtobufCMessage *)event);
161 purple_debug_info("googlechat", "%s\n", json_dump);
162
163 g_free(json_dump);
164 }
165
166 void
googlechat_process_presence_result(GoogleChatAccount * ha,UserPresence * presence)167 googlechat_process_presence_result(GoogleChatAccount *ha, UserPresence *presence)
168 {
169
170 }
171
172 void
googlechat_received_presence_notification(PurpleConnection * pc,Event * event)173 googlechat_received_presence_notification(PurpleConnection *pc, Event *event)
174 {
175 GoogleChatAccount *ha;
176 UserStatusUpdatedEvent *user_status_updated_event;
177
178 if (event->type != EVENT__EVENT_TYPE__USER_STATUS_UPDATED_EVENT) {
179 return;
180 }
181 user_status_updated_event = event->body->user_status_updated_event;
182
183 ha = purple_connection_get_protocol_data(pc);
184
185 const gchar *status_id = NULL;
186 gboolean reachable = FALSE;
187 gboolean available = FALSE;
188 gchar *message = NULL;
189 UserStatus *user_status = user_status_updated_event->user_status;
190 const gchar *user_id = user_status->user_id->id;
191 PurpleBuddy *buddy = purple_blist_find_buddy(ha->account, user_id);
192
193 if (buddy != NULL) {
194 status_id = purple_status_get_id(purple_presence_get_active_status(purple_buddy_get_presence(buddy)));
195 }
196
197 if (user_status->dnd_settings && user_status->dnd_settings->has_dnd_state) {
198 DndSettings__DndStateState dnd_state = user_status->dnd_settings->dnd_state;
199
200 available = TRUE;
201 if (dnd_state == DND_SETTINGS__DND_STATE__STATE__AVAILABLE) {
202 //TODO fetch presence separately from status
203 reachable = TRUE;
204 }
205
206 if (reachable && available) {
207 status_id = purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE);
208 } else if (reachable) {
209 status_id = purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY);
210 } else if (available) {
211 status_id = purple_primitive_get_id_from_type(PURPLE_STATUS_EXTENDED_AWAY);
212 } else if (purple_account_get_bool(ha->account, "treat_invisible_as_offline", FALSE)) {
213 status_id = "gone";
214 } else {
215 // GoogleChat contacts are never really unreachable, just invisible
216 status_id = purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE);
217 }
218 } else if (buddy == NULL) {
219 return;
220 }
221
222 if (user_status != NULL && user_status->custom_status) {
223 const gchar *status_text = user_status->custom_status->status_text;
224
225 if (status_text && *status_text) {
226 message = g_strdup(status_text);
227 }
228 }
229
230 if (message != NULL) {
231 purple_protocol_got_user_status(ha->account, user_id, status_id, "message", message, NULL);
232 } else {
233 purple_protocol_got_user_status(ha->account, user_id, status_id, NULL);
234 }
235
236 g_free(message);
237 }
238
239 static void
googlechat_got_http_image_for_conv(PurpleHttpConnection * connection,PurpleHttpResponse * response,gpointer user_data)240 googlechat_got_http_image_for_conv(PurpleHttpConnection *connection, PurpleHttpResponse *response, gpointer user_data)
241 {
242 GoogleChatAccount *ha = user_data;
243 const gchar *url;
244 const gchar *drive_url;
245 const gchar *sender_id;
246 const gchar *conv_id;
247 PurpleMessageFlags msg_flags;
248 time_t message_timestamp;
249 PurpleImage *image;
250 const gchar *response_data;
251 size_t response_size;
252 guint image_id;
253 gchar *msg;
254 gchar *escaped_image_url;
255
256 if (purple_http_response_get_error(response) != NULL) {
257 g_dataset_destroy(connection);
258 return;
259 }
260
261 url = g_dataset_get_data(connection, "url");
262 drive_url = g_dataset_get_data(connection, "drive_url");
263 sender_id = g_dataset_get_data(connection, "sender_id");
264 conv_id = g_dataset_get_data(connection, "conv_id");
265 msg_flags = GPOINTER_TO_INT(g_dataset_get_data(connection, "msg_flags"));
266 message_timestamp = GPOINTER_TO_INT(g_dataset_get_data(connection, "message_timestamp"));
267
268 response_data = purple_http_response_get_data(response, &response_size);
269 image = purple_image_new_from_data(g_memdup(response_data, response_size), response_size);
270 image_id = purple_image_store_add(image);
271 escaped_image_url = g_markup_escape_text(purple_http_request_get_url(purple_http_conn_get_request(connection)), -1);
272 if (drive_url) {
273 msg = g_strdup_printf("<a href='%s'>View in Drive <img id='%u' src='%s' /></a>", drive_url, image_id, escaped_image_url);
274 } else {
275 msg = g_strdup_printf("<a href='%s'>View full image <img id='%u' src='%s' /></a>", url, image_id, escaped_image_url);
276 }
277 msg_flags |= PURPLE_MESSAGE_IMAGES;
278
279 if (g_hash_table_contains(ha->group_chats, conv_id)) {
280 purple_serv_got_chat_in(ha->pc, g_str_hash(conv_id), sender_id, msg_flags, msg, message_timestamp);
281 } else {
282 if (msg_flags & PURPLE_MESSAGE_RECV) {
283 purple_serv_got_im(ha->pc, sender_id, msg, msg_flags, message_timestamp);
284 } else {
285 sender_id = g_hash_table_lookup(ha->one_to_ones, conv_id);
286 if (sender_id) {
287 PurpleIMConversation *imconv = purple_conversations_find_im_with_account(sender_id, ha->account);
288 PurpleMessage *message = purple_message_new_outgoing(sender_id, msg, msg_flags);
289 if (imconv == NULL) {
290 imconv = purple_im_conversation_new(ha->account, sender_id);
291 }
292 purple_message_set_time(message, message_timestamp);
293 purple_conversation_write_message(PURPLE_CONVERSATION(imconv), message);
294 }
295 }
296 }
297
298 g_free(escaped_image_url);
299 g_free(msg);
300 g_dataset_destroy(connection);
301 }
302
303 static const gchar *
googlechat_format_type_to_string(FormatMetadata__FormatType format_type,gboolean close)304 googlechat_format_type_to_string(FormatMetadata__FormatType format_type, gboolean close)
305 {
306 static gchar output[16];
307 int p = 1;
308
309 output[0] = '<';
310 if (close) {
311 output[1] = '/';
312 p = 2;
313 }
314
315 if (format_type == FORMAT_METADATA__FORMAT_TYPE__BOLD) {
316 output[p] = 'B'; p++;
317 } else if (format_type == FORMAT_METADATA__FORMAT_TYPE__ITALIC) {
318 output[p] = 'I'; p++;
319 } else if (format_type == FORMAT_METADATA__FORMAT_TYPE__UNDERLINE) {
320 output[p] = 'U'; p++;
321 } // else //TODO
322
323 output[p] = '>';
324 output[p + 1] = '\0';
325
326 return output;
327 }
328
329 void
googlechat_received_message_event(PurpleConnection * pc,Event * event)330 googlechat_received_message_event(PurpleConnection *pc, Event *event)
331 {
332 GoogleChatAccount *ha;
333 MessageEvent *message_event;
334 const gchar *conv_id;
335 const gchar *sender_id;
336
337 if (event->type != EVENT__EVENT_TYPE__MESSAGE_POSTED) {
338 return;
339 }
340 message_event = event->body->message_posted;
341
342 ha = purple_connection_get_protocol_data(pc);
343
344 Message *message = message_event->message;
345 guint i;
346
347 if (message->local_id && g_hash_table_remove(ha->sent_message_ids, message->local_id)) {
348 // This probably came from us
349 return;
350 }
351
352 //TODO safety checks
353 sender_id = message->creator->user_id->id;
354 GroupId *group_id = message->id->parent_id->topic_id->group_id;
355 gboolean is_dm = !!group_id->dm_id;
356 if (is_dm) {
357 conv_id = group_id->dm_id->dm_id;
358 } else {
359 conv_id = group_id->space_id->space_id;
360 }
361
362
363 time_t message_timestamp = (message->create_time / 1000000) - ha->server_time_offset;
364 PurpleMessageFlags msg_flags = (g_strcmp0(sender_id, ha->self_gaia_id) ? PURPLE_MESSAGE_RECV : (PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_REMOTE_SEND | PURPLE_MESSAGE_DELAYED));
365 if (((message->create_time / 1000000) - time(NULL) - ha->server_time_offset) > 120) {
366 msg_flags |= PURPLE_MESSAGE_DELAYED;
367 }
368 PurpleConversation *pconv = NULL;
369
370 //TODO process Annotations to add formatting
371 gchar *msg = NULL;
372 GList *format_annotations = NULL;
373 for (i = 0; i < message->n_annotations; i++) {
374 Annotation *annotation = message->annotations[i];
375 if (annotation->type == ANNOTATION_TYPE__FORMAT_DATA || annotation->type == ANNOTATION_TYPE__URL) {
376 format_annotations = g_list_prepend(format_annotations, annotation);
377 }
378 }
379
380 if (format_annotations == NULL) {
381 //shortcut
382 msg = purple_markup_escape_text(message->text_body, -1);
383
384 } else {
385 GString *msg_out = g_string_new(NULL);
386 const gchar *current_char = message->text_body;
387 gunichar c;
388 gint32 pos = 0;
389 GList *format;
390 gint hidden_output = 0;
391
392 do {
393 if (format_annotations != NULL) {
394 format = format_annotations;
395 while(format != NULL) {
396 GList *next_format = format->next;
397 Annotation *annotation = format->data;
398 FormatMetadata__FormatType format_type = annotation->format_metadata ? annotation->format_metadata->format_type : 0;
399
400 // Opening
401 if (annotation->start_index == pos) {
402 if (format_type == FORMAT_METADATA__FORMAT_TYPE__HIDDEN) {
403 hidden_output++;
404
405 } else if (annotation->type == ANNOTATION_TYPE__URL) {
406 UrlMetadata *url_metadata = annotation->url_metadata;
407 if (url_metadata && url_metadata->url && url_metadata->url->url) {
408 gchar *escaped = g_markup_escape_text(url_metadata->url->url, -1);
409 g_string_append_printf(msg_out, "<A HREF=\"%s\">", escaped);
410 g_free(escaped);
411 }
412
413 } else {
414 g_string_append(msg_out, googlechat_format_type_to_string(format_type, FALSE));
415 }
416
417 // Closing
418 } else if (annotation->length + annotation->start_index == pos) {
419 if (format_type == FORMAT_METADATA__FORMAT_TYPE__HIDDEN) {
420 hidden_output--;
421 } else if (annotation->type == ANNOTATION_TYPE__URL) {
422 g_string_append(msg_out, "</A>");
423 } else {
424 g_string_append(msg_out, googlechat_format_type_to_string(format_type, TRUE));
425 }
426
427 format_annotations = g_list_delete_link(format_annotations, format);
428 }
429
430 format = next_format;
431 }
432 }
433
434 if (hidden_output == 0) {
435 // from libpurple/util.c
436 switch (*current_char)
437 {
438 case '&':
439 g_string_append(msg_out, "&");
440 break;
441
442 case '<':
443 g_string_append(msg_out, "<");
444 break;
445
446 case '>':
447 g_string_append(msg_out, ">");
448 break;
449
450 case '"':
451 g_string_append(msg_out, """);
452 break;
453
454 default:
455 c = g_utf8_get_char(current_char);
456 if ((0x1 <= c && c <= 0x8) ||
457 (0xb <= c && c <= 0xc) ||
458 (0xe <= c && c <= 0x1f) ||
459 (0x7f <= c && c <= 0x84) ||
460 (0x86 <= c && c <= 0x9f))
461 g_string_append_printf(msg_out, "&#x%x;", c);
462 else
463 g_string_append_unichar(msg_out, c);
464 break;
465 }
466 }
467 pos++;
468
469 } while ((current_char = g_utf8_next_char(current_char)) && *current_char);
470
471 msg = g_string_free(msg_out, FALSE);
472 }
473
474 if (!is_dm) {
475 PurpleChatConversation *chatconv = purple_conversations_find_chat_with_account(conv_id, ha->account);
476 if (chatconv == NULL) {
477 //TODO /api/get_group
478 chatconv = purple_serv_got_joined_chat(ha->pc, g_str_hash(conv_id), conv_id);
479 purple_conversation_set_data(PURPLE_CONVERSATION(chatconv), "conv_id", g_strdup(conv_id));
480
481 googlechat_lookup_group_info(ha, conv_id);
482 }
483 pconv = PURPLE_CONVERSATION(chatconv);
484 purple_serv_got_chat_in(pc, g_str_hash(conv_id), sender_id, msg_flags, msg, message_timestamp);
485
486 } else {
487 PurpleIMConversation *imconv = NULL;
488 // It's most likely a one-to-one message
489 if (msg_flags & PURPLE_MESSAGE_RECV) {
490 purple_serv_got_im(pc, sender_id, msg, msg_flags, message_timestamp);
491 } else {
492 sender_id = g_hash_table_lookup(ha->one_to_ones, conv_id);
493 if (sender_id) {
494 imconv = purple_conversations_find_im_with_account(sender_id, ha->account);
495 PurpleMessage *pmessage = purple_message_new_outgoing(sender_id, msg, msg_flags);
496 if (imconv == NULL)
497 {
498 imconv = purple_im_conversation_new(ha->account, sender_id);
499 }
500 purple_message_set_time(pmessage, message_timestamp);
501 purple_conversation_write_message(PURPLE_CONVERSATION(imconv), pmessage);
502 }
503 }
504
505 if (imconv == NULL) {
506 imconv = purple_conversations_find_im_with_account(sender_id, ha->account);
507 }
508 pconv = PURPLE_CONVERSATION(imconv);
509 }
510
511 if (purple_conversation_has_focus(pconv)) {
512 googlechat_mark_conversation_seen(pconv, PURPLE_CONVERSATION_UPDATE_UNSEEN);
513 }
514
515 g_free(msg);
516
517 // Process join/part's
518 for (i = 0; i < message->n_annotations; i++) {
519 Annotation *annotation = message->annotations[i];
520
521 if (annotation->membership_changed) { //if (annotation->type == ANNOTATION_TYPE__MEMBERSHIP_CHANGED) {
522 PurpleChatConversation *chatconv = purple_conversations_find_chat_with_account(conv_id, ha->account);
523 MembershipChangedMetadata *membership_change = annotation->membership_changed;
524 guint j;
525
526 if (membership_change->type == MEMBERSHIP_CHANGED_METADATA__TYPE__LEFT ||
527 membership_change->type == MEMBERSHIP_CHANGED_METADATA__TYPE__REMOVED ||
528 membership_change->type == MEMBERSHIP_CHANGED_METADATA__TYPE__BOT_REMOVED) {
529 //TODO
530 const gchar *reason = NULL;
531
532 for (j = 0; j < membership_change->n_affected_members; j++) {
533 MemberId *member_id = membership_change->affected_members[j];
534
535 purple_chat_conversation_remove_user(chatconv, member_id->user_id->id, reason);
536
537 if (g_strcmp0(member_id->user_id->id, ha->self_gaia_id) == 0) {
538 purple_serv_got_chat_left(ha->pc, g_str_hash(conv_id));
539 g_hash_table_remove(ha->group_chats, conv_id);
540 purple_blist_remove_chat(purple_blist_find_chat(ha->account, conv_id));
541 }
542 }
543 } else {
544 PurpleChatUserFlags cbflags = PURPLE_CHAT_USER_NONE; //TODO
545
546 for (j = 0; j < membership_change->n_affected_members; j++) {
547 MemberId *member_id = membership_change->affected_members[j];
548
549 purple_chat_conversation_add_user(chatconv, member_id->user_id->id, NULL, cbflags, TRUE);
550 }
551 }
552 }
553 }
554
555 // Add images
556 for (i = 0; i < message->n_annotations; i++) {
557 Annotation *annotation = message->annotations[i];
558 gchar *image_url = NULL; // Direct image URL
559 const gchar *url = NULL; // Display URL
560 const gchar *drive_url = NULL; // Google Drive URL
561
562 if (annotation->upload_metadata) {
563 UploadMetadata *upload_metadata = annotation->upload_metadata;
564
565 //const gchar *content_type = upload_metadata->content_type;
566 const gchar *attachment_token = upload_metadata->attachment_token;
567
568 GString *image_url_str = g_string_new(NULL);
569
570 g_string_append(image_url_str, "https://chat.google.com/api/get_attachment_url" "?");
571 //could also be THUMBNAIL_URL
572 // or DOWNLOAD_URL for a file
573 g_string_append(image_url_str, "url_type=FIFE_URL&");
574 //g_string_append_printf(image_url_str, "sz=w%d-h%d&", 0, 0); //TODO
575 //g_string_append_printf(image_url_str, "content_type=%s&", purple_url_encode(content_type));
576 g_string_append_printf(image_url_str, "attachment_token=%s&", purple_url_encode(attachment_token));
577
578 // this url redirects to the actual url
579 url = image_url = image_url_str->str;
580 g_string_free(image_url_str, FALSE);
581 }
582
583 if (annotation->drive_metadata) {
584 DriveMetadata *drive_metadata = annotation->drive_metadata;
585
586 if (drive_metadata->thumbnail_url) {
587 image_url = g_strdup(drive_metadata->thumbnail_url);
588 url = drive_metadata->url_fragment;
589
590 } else {
591 const gchar *drive_id = drive_metadata->id;
592
593 GString *image_url_str = g_string_new(NULL);
594
595 g_string_append(image_url_str, "https://lh3.googleusercontent.com/d/");
596 g_string_append(image_url_str, purple_url_encode(drive_id));
597
598 // the preview
599 url = image_url = image_url_str->str;
600
601 GString *drive_url_str = g_string_new(NULL);
602 g_string_append(drive_url_str, "https://drive.google.com/open?id=");
603 g_string_append(drive_url_str, purple_url_encode(drive_id));
604
605 // the link
606 drive_url = drive_url_str->str;
607
608 g_string_free(image_url_str, FALSE);
609 g_string_free(drive_url_str, FALSE);
610 }
611 }
612
613 if (annotation->url_metadata) {
614 UrlMetadata *url_metadata = annotation->url_metadata;
615
616 if (url_metadata->image_url) {
617 image_url = g_strdup(url_metadata->image_url);
618 url = url_metadata->url ? url_metadata->url->url : url_metadata->image_url;
619
620 }
621 }
622
623 if (image_url != NULL) {
624 PurpleHttpConnection *connection;
625
626 if (g_strcmp0(purple_core_get_ui(), "BitlBee") == 0) {
627 // Bitlbee doesn't support images, so just plop a url to the image instead
628 if (g_hash_table_contains(ha->group_chats, conv_id)) {
629 purple_serv_got_chat_in(pc, g_str_hash(conv_id), sender_id, msg_flags, url, message_timestamp);
630 } else {
631 if (msg_flags & PURPLE_MESSAGE_RECV) {
632 purple_serv_got_im(pc, sender_id, url, msg_flags, message_timestamp);
633 } else {
634 PurpleMessage *img_message = purple_message_new_outgoing(sender_id, url, msg_flags);
635 purple_message_set_time(img_message, message_timestamp);
636 purple_conversation_write_message(pconv, img_message);
637 }
638 }
639 } else {
640 PurpleHttpRequest *request = purple_http_request_new(image_url);
641
642 purple_http_request_header_set_printf(request, "Authorization", "Bearer %s", ha->access_token);
643 purple_http_request_set_max_len(request, -1);
644
645 connection = purple_http_request(ha->pc, request, googlechat_got_http_image_for_conv, ha);
646
647 g_dataset_set_data_full(connection, "url", g_strdup(url), g_free);
648 g_dataset_set_data_full(connection, "drive_url", g_strdup(drive_url), g_free);
649 g_dataset_set_data_full(connection, "sender_id", g_strdup(sender_id), g_free);
650 g_dataset_set_data_full(connection, "conv_id", g_strdup(conv_id), g_free);
651 g_dataset_set_data(connection, "msg_flags", GINT_TO_POINTER(msg_flags));
652 g_dataset_set_data(connection, "message_timestamp", GINT_TO_POINTER(message_timestamp));
653
654 purple_http_request_unref(request);
655 }
656 }
657
658 g_free(image_url);
659 }
660
661
662 if (pconv != NULL) {
663 gint64 *last_event_timestamp_ptr = (gint64 *)purple_conversation_get_data(pconv, "last_event_timestamp");
664 if (last_event_timestamp_ptr == NULL) {
665 last_event_timestamp_ptr = g_new0(gint64, 1);
666 }
667 if (message->create_time > *last_event_timestamp_ptr) {
668 *last_event_timestamp_ptr = message->create_time;
669 purple_conversation_set_data(pconv, "last_event_timestamp", last_event_timestamp_ptr);
670 }
671 }
672 }
673
674 void
googlechat_received_read_receipt(PurpleConnection * pc,Event * event)675 googlechat_received_read_receipt(PurpleConnection *pc, Event *event)
676 {
677 const gchar *user_id;
678 GroupId *group_id;
679 const gchar *conv_id;
680 GoogleChatAccount *ha;
681 ReadReceiptSet *receipt_set;
682
683 if (event->type != EVENT__EVENT_TYPE__READ_RECEIPT_CHANGED ||
684 !event->body->read_receipt_changed ||
685 !event->body->read_receipt_changed->read_receipt_set ||
686 !event->body->read_receipt_changed->read_receipt_set->enabled ||
687 !event->body->read_receipt_changed->group_id
688 ) {
689 return;
690 }
691
692 receipt_set = event->body->read_receipt_changed->read_receipt_set;
693 guint i;
694 for (i = 0; i < receipt_set->n_read_receipts; i++) {
695 if (receipt_set->read_receipts[i]->user &&
696 receipt_set->read_receipts[i]->user->user_id &&
697 receipt_set->read_receipts[i]->user->user_id->id
698 ) {
699 user_id = receipt_set->read_receipts[i]->user->user_id->id;
700 ha = purple_connection_get_protocol_data(pc);
701
702 // don't emit our own receipts
703 if (ha->self_gaia_id && g_strcmp0(user_id, ha->self_gaia_id) != 0) {
704 group_id = event->body->read_receipt_changed->group_id;
705 gboolean is_dm = !!group_id->dm_id;
706 if (is_dm) {
707 conv_id = group_id->dm_id->dm_id;
708 } else {
709 conv_id = group_id->space_id->space_id;
710 }
711 if (conv_id) {
712 if (is_dm) {
713 // PurpleChat *chat = purple_blist_find_chat(ha->account, conv_id);
714 PurpleBuddy *buddy = purple_blist_find_buddy(ha->account, user_id);
715 if (buddy) {
716 purple_debug_warning("googlechat", "TODO: username %s read DM\n", purple_buddy_get_alias(buddy)); //purple_chat_get_name(chat));
717 } else {
718 purple_debug_warning("googlechat", "TODO: userid %s read DM\n", user_id);
719 }
720 } else {
721 PurpleChatConversation *chatconv = purple_conversations_find_chat_with_account(conv_id, ha->account);
722
723 if (chatconv) {
724 PurpleChatUser *cb = purple_chat_conversation_find_user(chatconv, user_id);
725 if (cb) {
726 purple_debug_warning("googlechat", "TODO: username %s read chat\n", cb->name);
727 } else {
728 purple_debug_warning("googlechat", "TODO: userid %s read chat\n", user_id);
729 }
730 }
731 }
732 }
733 }
734 }
735 }
736 }
737
738
739 void
googlechat_received_group_viewed(PurpleConnection * pc,Event * event)740 googlechat_received_group_viewed(PurpleConnection *pc, Event *event)
741 {
742 const gchar *user_id;
743 GroupId *group_id;
744 const gchar *conv_id;
745 const gchar *sender_id;
746 GoogleChatAccount *ha;
747 PurpleConversation *pconv = NULL;
748
749 if (event->type != EVENT__EVENT_TYPE__GROUP_VIEWED ||
750 !event->user_id ||
751 !event->user_id->id ||
752 !event->body->group_viewed->group_id
753 ) {
754 return;
755 }
756
757 user_id = event->user_id->id;
758 ha = purple_connection_get_protocol_data(pc);
759
760 purple_debug_warning("googlechat", "Received groupview %p from userid %s\n", event, user_id);
761
762 // we only expect to receive GROUP_VIEWED messages for our own user
763 if (ha->self_gaia_id && g_strcmp0(user_id, ha->self_gaia_id) == 0) {
764 purple_debug_info("googlechat", "...it's us %s\n", user_id);
765 group_id = event->body->group_viewed->group_id;
766 gboolean is_dm = !!group_id->dm_id;
767
768 if (is_dm) {
769 conv_id = group_id->dm_id->dm_id;
770 } else {
771 conv_id = group_id->space_id->space_id;
772 }
773
774 if (!is_dm) {
775 purple_debug_info("googlechat", "...it's not a DM\n");
776 PurpleChatConversation *chatconv = purple_conversations_find_chat_with_account(conv_id, ha->account);
777 if (chatconv == NULL) {
778 //TODO /api/get_group
779 chatconv = purple_serv_got_joined_chat(ha->pc, g_str_hash(conv_id), conv_id);
780 purple_conversation_set_data(PURPLE_CONVERSATION(chatconv), "conv_id", g_strdup(conv_id));
781 googlechat_lookup_group_info(ha, conv_id);
782 }
783 if (chatconv) {
784 pconv = PURPLE_CONVERSATION(chatconv);
785 } else {
786 purple_debug_info("googlechat", "...couldn't find chatconv\n");
787 }
788 } else {
789 purple_debug_info("googlechat", "...it's a DM\n");
790 PurpleIMConversation *imconv = NULL;
791 // It's most likely a one-to-one message
792 sender_id = g_hash_table_lookup(ha->one_to_ones, conv_id);
793 if (sender_id) {
794 imconv = purple_conversations_find_im_with_account(sender_id, ha->account);
795 if (imconv == NULL)
796 {
797 imconv = purple_im_conversation_new(ha->account, sender_id);
798 }
799 }
800 if (imconv == NULL) {
801 imconv = purple_conversations_find_im_with_account(sender_id, ha->account);
802 }
803 if (imconv) {
804 pconv = PURPLE_CONVERSATION(imconv);
805 } else {
806 purple_debug_info("googlechat", "...couldn't find imconv\n");
807 }
808 }
809
810 // the whole point of GROUP_VIEWED seems to be to sync our own seen status
811 if (pconv) {
812 purple_debug_warning("googlechat", "TODO: mark conversation '%s' as seen \n", pconv->title);
813 // googlechat_mark_conversation_seen(pconv, PURPLE_CONVERSATION_UPDATE_UNSEEN);
814 // warning: this makes infinite loops
815 } else {
816 purple_debug_info("googlechat", "...pconv was null\n");
817 }
818 } else {
819 purple_debug_info("googlechat", "...it's not us (%s)\n", user_id);
820 }
821 }
822
823 void
googlechat_received_typing_notification(PurpleConnection * pc,Event * event)824 googlechat_received_typing_notification(PurpleConnection *pc, Event *event)
825 {
826 GoogleChatAccount *ha;
827 TypingStateChangedEvent *typing_notification;
828 const gchar *user_id;
829 const gchar *conv_id;
830 PurpleIMTypingState typing_state;
831
832 if (event->type != EVENT__EVENT_TYPE__TYPING_STATE_CHANGED) {
833 return;
834 }
835 typing_notification = event->body->typing_state_changed_event;
836
837 ha = purple_connection_get_protocol_data(pc);
838
839 user_id = typing_notification->user_id->id;
840 if (ha->self_gaia_id && g_strcmp0(user_id, ha->self_gaia_id) == 0)
841 return;
842
843 if (!typing_notification->context->group_id) {
844 //TODO handle topic_id -> group_id conversion
845 return;
846 }
847
848 GroupId *group_id = typing_notification->context->group_id;
849 gboolean is_dm = !!group_id->dm_id;
850 if (is_dm) {
851 conv_id = group_id->dm_id->dm_id;
852 } else {
853 conv_id = group_id->space_id->space_id;
854 }
855
856 if (!is_dm) {
857 // This is a group conversation
858 PurpleChatConversation *chatconv = purple_conversations_find_chat_with_account(conv_id, ha->account);
859 if (chatconv != NULL) {
860 PurpleChatUser *cb = purple_chat_conversation_find_user(chatconv, user_id);
861 PurpleChatUserFlags cbflags;
862
863 if (cb == NULL) {
864 // Getting notified about a buddy we dont know about yet
865 //TODO add buddy
866 return;
867 }
868 cbflags = purple_chat_user_get_flags(cb);
869
870 if (typing_notification->state == TYPING_STATE__TYPING)
871 cbflags |= PURPLE_CHAT_USER_TYPING;
872 else
873 cbflags &= ~PURPLE_CHAT_USER_TYPING;
874
875 purple_chat_user_set_flags(cb, cbflags);
876 }
877 return;
878 }
879
880 switch(typing_notification->state) {
881 case TYPING_STATE__TYPING:
882 typing_state = PURPLE_IM_TYPING;
883 break;
884
885 default:
886 case TYPING_STATE__STOPPED:
887 typing_state = PURPLE_IM_NOT_TYPING;
888 break;
889 }
890
891 purple_serv_got_typing(pc, user_id, 7, typing_state);
892 }
893