eion/purple-hangouts
Merge
draft
2019-05-24, Eion Robb
* Hangouts Plugin for libpurple/Pidgin * Copyright (c) 2015-2016 Eion Robb, Mike Ruprecht * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. #include "hangouts_events.h" #include "mediamanager.h" #include "hangouts_conversation.h" #include "hangouts.pb-c.h" gchar *pblite_dump_json(ProtobufCMessage *message); //purple_signal_emit(purple_connection_get_protocol(ha->pc), "hangouts-received-stateupdate", ha->pc, batch_update.state_update[j]); hangouts_register_events(gpointer plugin) purple_signal_connect(plugin, "hangouts-received-stateupdate", plugin, PURPLE_CALLBACK(hangouts_received_typing_notification), NULL); purple_signal_connect(plugin, "hangouts-received-stateupdate", plugin, PURPLE_CALLBACK(hangouts_received_event_notification), NULL); purple_signal_connect(plugin, "hangouts-received-stateupdate", plugin, PURPLE_CALLBACK(hangouts_received_presence_notification), NULL); purple_signal_connect(plugin, "hangouts-received-stateupdate", plugin, PURPLE_CALLBACK(hangouts_received_watermark_notification), NULL); purple_signal_connect(plugin, "hangouts-received-stateupdate", plugin, PURPLE_CALLBACK(hangouts_received_state_update), NULL); purple_signal_connect(plugin, "hangouts-received-stateupdate", plugin, PURPLE_CALLBACK(hangouts_received_view_modification), NULL); purple_signal_connect(plugin, "hangouts-received-stateupdate", plugin, PURPLE_CALLBACK(hangouts_received_delete_notification), NULL); purple_signal_connect(plugin, "hangouts-received-stateupdate", plugin, PURPLE_CALLBACK(hangouts_received_block_notification), NULL); purple_signal_connect(plugin, "hangouts-received-stateupdate", plugin, PURPLE_CALLBACK(hangouts_received_other_notification), NULL); purple_signal_connect(plugin, "hangouts-gmail-notification", plugin, PURPLE_CALLBACK(hangouts_received_gmail_notification), NULL); StateUpdateHeader *state_update_header; Conversation *conversation; EventNotification *event_notification; SetFocusNotification *focus_notification; SetTypingNotification *typing_notification; SetConversationNotificationLevelNotification *notification_level_notification; ReplyToInviteNotification *reply_to_invite_notification; WatermarkNotification *watermark_notification; ConversationViewModification *view_modification; EasterEggNotification *easter_egg_notification; SelfPresenceNotification *self_presence_notification; DeleteActionNotification *delete_notification; PresenceNotification *presence_notification; BlockNotification *block_notification; SetNotificationSettingNotification *notification_setting_notification; RichPresenceEnabledStateNotification *rich_presence_enabled_state_notification; hangouts_received_state_update(PurpleConnection *pc, StateUpdate *state_update) HangoutsAccount *ha = purple_connection_get_protocol_data(pc); if (ha != NULL && state_update->state_update_header != NULL) { gint64 current_server_time = state_update->state_update_header->current_server_time; ha->active_client_state = state_update->state_update_header->active_client_state; // libpurple can't store a 64bit int on a 32bit machine, so convert to something more usable instead (puke) // also needs to work cross platform, in case the accounts.xml is being shared (double puke) purple_account_set_int(ha->account, "last_event_timestamp_high", current_server_time >> 32); purple_account_set_int(ha->account, "last_event_timestamp_low", current_server_time & 0xFFFFFFFF); hangouts_remove_conversation(HangoutsAccount *ha, const gchar *conv_id) if (g_hash_table_contains(ha->one_to_ones, conv_id)) { const gchar *buddy_id = g_hash_table_lookup(ha->one_to_ones, conv_id); PurpleBuddy *buddy = purple_blist_find_buddy(ha->account, buddy_id); purple_blist_remove_buddy(buddy); g_hash_table_remove(ha->one_to_ones, conv_id); g_hash_table_remove(ha->one_to_ones_rev, buddy_id); } else if (g_hash_table_contains(ha->group_chats, conv_id)) { PurpleChat *chat = purple_blist_find_chat(ha->account, conv_id); purple_blist_remove_chat(chat); g_hash_table_remove(ha->group_chats, conv_id); hangouts_received_view_modification(PurpleConnection *pc, StateUpdate *state_update) ConversationViewModification *view_modification = state_update->view_modification; if (view_modification == NULL) { if (view_modification->new_view == CONVERSATION_VIEW__CONVERSATION_VIEW_ARCHIVED) { ha = purple_connection_get_protocol_data(pc); conv_id = view_modification->conversation_id->id; hangouts_remove_conversation(ha, conv_id); hangouts_received_delete_notification(PurpleConnection *pc, StateUpdate *state_update) DeleteActionNotification *delete_notification = state_update->delete_notification; if (delete_notification == NULL) { ha = purple_connection_get_protocol_data(pc); conv_id = delete_notification->conversation_id->id; if (delete_notification->delete_action && delete_notification->delete_action->delete_type == DELETE_TYPE__DELETE_TYPE_UPPER_BOUND) { hangouts_remove_conversation(ha, conv_id); hangouts_received_block_notification(PurpleConnection *pc, StateUpdate *state_update) BlockNotification *block_notification = state_update->block_notification; if (block_notification == NULL) { ha = purple_connection_get_protocol_data(pc); for (i = 0; i < block_notification->n_block_state_change; i++) { BlockStateChange *block_state_change = block_notification->block_state_change[i]; if (block_state_change->has_new_block_state) { gchar *gaia_id = block_state_change->participant_id->gaia_id; if (block_state_change->new_block_state == BLOCK_STATE__BLOCK_STATE_BLOCK) { purple_account_privacy_deny_add(ha->account, gaia_id, TRUE); } else if (block_state_change->new_block_state == BLOCK_STATE__BLOCK_STATE_UNBLOCK) { purple_account_privacy_deny_remove(ha->account, gaia_id, TRUE); hangouts_received_gmail_notification(PurpleConnection *pc, const gchar *username, GmailNotification *msg) gboolean is_unread = FALSE; gboolean is_inbox = FALSE; if (!purple_account_get_check_mail(purple_connection_get_account(pc))) { for (i = 0; i < msg->n_labels; i++) { if (purple_strequal(msg->labels[i], "^u")) { } else if (purple_strequal(msg->labels[i], "^i")) { if (is_unread == FALSE || is_inbox == FALSE) { subject = purple_utf8_strip_unprintables(msg->subject); from = purple_markup_escape_text(msg->sender_name, -1); to = purple_markup_escape_text(username, -1); json_dump = pblite_dump_json((ProtobufCMessage *) msg); purple_debug_info("hangouts", "Received gmail notification %s\n", json_dump); url = g_strconcat("https://mail.google.com/mail/u/", username, "/#inbox/", purple_url_encode(msg->thread_id), NULL); purple_notify_email(pc, subject, from, to, url, NULL, NULL); hangouts_received_other_notification(PurpleConnection *pc, StateUpdate *state_update) if (state_update->typing_notification != NULL || state_update->presence_notification != NULL || state_update->event_notification != NULL || state_update->watermark_notification != NULL) { purple_debug_info("hangouts", "Received new other event %p\n", state_update); json_dump = pblite_dump_json((ProtobufCMessage *)state_update); purple_debug_info("hangouts", "%s\n", json_dump); "id" : "UgxGdpCK_mSrhBX8hrx4AaABAQ" "type" : "CONVERSATION_TYPE_ONE_TO_ONE", "self_conversation_state" : { "client_generated_id" : null, "gaia_id" : "110174066375061118727", "chat_id" : "110174066375061118727" "latest_read_timestamp" : null "status" : "CONVERSATION_STATUS_ACTIVE", "notification_level" : "NOTIFICATION_LEVEL_RING", "CONVERSATION_VIEW_INBOX" "gaia_id" : "111523150620250165866", "chat_id" : "111523150620250165866" "invite_timestamp" : 1367645831562000, "sort_timestamp" : 1453809415517871, "active_timestamp" : 1367645831562000, "delivery_medium_option" : [ "medium_type" : "DELIVERY_MEDIUM_BABEL", "gaia_id" : "111523150620250165866", "chat_id" : "111523150620250165866" "latest_read_timestamp" : null "gaia_id" : "110174066375061118727", "chat_id" : "110174066375061118727" "latest_read_timestamp" : null "has_active_hangout" : null, "otr_status" : "OFF_THE_RECORD_STATUS_ON_THE_RECORD", "otr_toggle" : "OFF_THE_RECORD_TOGGLE_ENABLED", "conversation_history_supported" : null, "current_participant" : [ "gaia_id" : "110174066375061118727", "chat_id" : "110174066375061118727" "gaia_id" : "111523150620250165866", "chat_id" : "111523150620250165866" "gaia_id" : "111523150620250165866", "chat_id" : "111523150620250165866" "fallback_name" : "Mike Ruprecht", "invitation_status" : "INVITATION_STATUS_ACCEPTED", "participant_type" : "PARTICIPANT_TYPE_GAIA", "new_invitation_status" : "INVITATION_STATUS_ACCEPTED" "gaia_id" : "110174066375061118727", "chat_id" : "110174066375061118727" "fallback_name" : "Eion Robb", "invitation_status" : "INVITATION_STATUS_ACCEPTED", "participant_type" : "PARTICIPANT_TYPE_GAIA", "new_invitation_status" : "INVITATION_STATUS_ACCEPTED" "force_history_state" : null hangouts_received_watermark_notification(PurpleConnection *pc, StateUpdate *state_update) WatermarkNotification *watermark_notification = state_update->watermark_notification; if (watermark_notification == NULL) { ha = purple_connection_get_protocol_data(pc); if (FALSE && purple_strequal(watermark_notification->sender_id->gaia_id, ha->self_gaia_id)) { //We marked this message as read ourselves PurpleConversation *conv = NULL; const gchar *conv_id = watermark_notification->conversation_id->id; gint64 *last_read_timestamp_ptr; gint64 latest_read_timestamp; if (g_hash_table_contains(ha->one_to_ones, conv_id)) { conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(g_hash_table_lookup(ha->one_to_ones, conv_id), ha->account)); } else if (g_hash_table_contains(ha->group_chats, conv_id)) { conv = PURPLE_CONVERSATION(purple_conversations_find_chat_with_account(conv_id, ha->account)); latest_read_timestamp = watermark_notification->latest_read_timestamp; last_read_timestamp_ptr = (gint64 *)purple_conversation_get_data(conv, "last_read_timestamp"); if (last_read_timestamp_ptr == NULL) { last_read_timestamp_ptr = g_new0(gint64, 1); if (latest_read_timestamp > *last_read_timestamp_ptr) { *last_read_timestamp_ptr = watermark_notification->latest_read_timestamp; purple_conversation_set_data(conv, "last_read_timestamp", last_read_timestamp_ptr); hangouts_process_presence_result(HangoutsAccount *ha, PresenceResult *presence_result) const gchar *gaia_id = presence_result->user_id->gaia_id; const gchar *status_id = NULL; const gchar *conv_id = g_hash_table_lookup(ha->one_to_ones_rev, gaia_id); gboolean reachable = FALSE; gboolean available = FALSE; PurpleBuddy *buddy = purple_blist_find_buddy(ha->account, gaia_id); Presence *presence = presence_result->presence; status_id = purple_status_get_id(purple_presence_get_active_status(purple_buddy_get_presence(buddy))); if (g_strcmp0(status_id, "mobile") == 0 || (conv_id != NULL && g_hash_table_contains(ha->google_voice_conversations, conv_id))) { // SMS contacts normally appear as 'offline' } else if (presence != NULL && (presence->has_reachable || presence->has_available)) { if (presence->reachable) { if (presence->available) { if (reachable && available) { status_id = purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE); status_id = purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY); status_id = purple_primitive_get_id_from_type(PURPLE_STATUS_EXTENDED_AWAY); } else if (purple_account_get_bool(ha->account, "treat_invisible_as_offline", FALSE)) { // Hangouts contacts are never really unreachable, just invisible status_id = purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE); } else if (buddy == NULL) { if (presence != NULL && presence->mood_setting) { MoodMessage *mood_message = presence->mood_setting->mood_message; MoodContent *mood_content = mood_message ? mood_message->mood_content : NULL; if (mood_content != NULL && mood_content->n_segment) { n_segments = mood_content->n_segment; segments = mood_content->segment; message_str = g_string_new(NULL); for (i = 0; i < n_segments; i++) { Segment *segment = segments[i]; if (segment->type == SEGMENT_TYPE__SEGMENT_TYPE_TEXT) { g_string_append(message_str, segment->text); g_string_append_c(message_str, ' '); message = g_string_free(message_str, FALSE); purple_protocol_got_user_status(ha->account, gaia_id, status_id, "message", message, NULL); purple_protocol_got_user_status(ha->account, gaia_id, status_id, NULL); if (buddy != NULL && presence != NULL) { HangoutsBuddy *hbuddy = purple_buddy_get_protocol_data(buddy); HangoutsDeviceTypeFlags device_type = HANGOUTS_DEVICE_TYPE_UNKNOWN; hbuddy = g_new0(HangoutsBuddy, 1); purple_buddy_set_protocol_data(buddy, hbuddy); hbuddy->in_call = presence->in_call && presence->in_call->has_call_type && presence->in_call->call_type != CALL_TYPE__CALL_TYPE_NONE; hbuddy->last_seen = presence->last_seen ? presence->last_seen->last_seen_timestamp / 1000000 : 0; if (presence->device_status) { if (presence->device_status->mobile) { device_type |= HANGOUTS_DEVICE_TYPE_MOBILE; if (presence->device_status->desktop) { device_type |= HANGOUTS_DEVICE_TYPE_DESKTOP; if (presence->device_status->tablet) { device_type |= HANGOUTS_DEVICE_TYPE_TABLET; hbuddy->device_type = device_type; if (presence->last_seen && !presence->has_reachable && !presence->has_available) { GList *user_list = g_list_prepend(NULL, (gchar *)gaia_id); hangouts_get_users_presence(ha, user_list); hangouts_received_presence_notification(PurpleConnection *pc, StateUpdate *state_update) PresenceNotification *presence_notification = state_update->presence_notification; if (presence_notification == NULL) { ha = purple_connection_get_protocol_data(pc); for (i = 0; i < presence_notification->n_presence; i++) { hangouts_process_presence_result(ha, presence_notification->presence[i]); hangouts_got_http_image_for_conv(PurpleHttpConnection *connection, PurpleHttpResponse *response, gpointer user_data) HangoutsAccount *ha = user_data; PurpleMessageFlags msg_flags; time_t message_timestamp; const gchar *response_data; gchar *escaped_image_url; if (purple_http_response_get_error(response) != NULL) { g_dataset_destroy(connection); url = g_dataset_get_data(connection, "url"); gaia_id = g_dataset_get_data(connection, "gaia_id"); conv_id = g_dataset_get_data(connection, "conv_id"); msg_flags = GPOINTER_TO_INT(g_dataset_get_data(connection, "msg_flags")); message_timestamp = GPOINTER_TO_INT(g_dataset_get_data(connection, "message_timestamp")); response_data = purple_http_response_get_data(response, &response_size); image = purple_image_new_from_data(g_memdup(response_data, response_size), response_size); image_id = purple_image_store_add(image); escaped_image_url = g_markup_escape_text(purple_http_request_get_url(purple_http_conn_get_request(connection)), -1); msg = g_strdup_printf("<a href='%s'>View full image <img id='%u' src='%s' /></a>", url, image_id, escaped_image_url); msg_flags |= PURPLE_MESSAGE_IMAGES; if (g_hash_table_contains(ha->group_chats, conv_id)) { purple_serv_got_chat_in(ha->pc, g_str_hash(conv_id), gaia_id, msg_flags, msg, message_timestamp); if (msg_flags & PURPLE_MESSAGE_RECV) { purple_serv_got_im(ha->pc, gaia_id, msg, msg_flags, message_timestamp); gaia_id = g_hash_table_lookup(ha->one_to_ones, conv_id); PurpleIMConversation *imconv = purple_conversations_find_im_with_account(gaia_id, ha->account); PurpleMessage *message = purple_message_new_outgoing(gaia_id, msg, msg_flags); imconv = purple_im_conversation_new(ha->account, gaia_id); purple_message_set_time(message, message_timestamp); purple_conversation_write_message(PURPLE_CONVERSATION(imconv), message); g_free(escaped_image_url); g_dataset_destroy(connection); hangouts_received_event_notification(PurpleConnection *pc, StateUpdate *state_update) EventNotification *event_notification = state_update->event_notification; Conversation *conversation = state_update->conversation; gint64 current_server_time = state_update->state_update_header->current_server_time; if (event_notification == NULL) { ha = purple_connection_get_protocol_data(pc); event = event_notification->event; if (ha->self_gaia_id == NULL) { ha->self_gaia_id = g_strdup(event->self_event_state->user_id->gaia_id); purple_connection_set_display_name(pc, ha->self_gaia_id); hangouts_process_conversation_event(ha, conversation, event, current_server_time); hangouts_process_conversation_event(HangoutsAccount *ha, Conversation *conversation, Event *event, gint64 current_server_time) PurpleConnection *pc = ha->pc; const gchar *client_generated_id; ChatMessage *chat_message; PurpleMessageFlags msg_flags; PurpleConversation *pconv = NULL; if (conversation && (conv_id = conversation->conversation_id->id) && !g_hash_table_contains(ha->one_to_ones, conv_id) && !g_hash_table_contains(ha->group_chats, conv_id)) { // New conversation we ain't seen before hangouts_add_conversation_to_blist(ha, conversation, NULL); gaia_id = event->sender_id->gaia_id; conv_id = event->conversation_id->id; timestamp = event->timestamp; chat_message = event->chat_message; client_generated_id = event->self_event_state->client_generated_id; if (client_generated_id && g_hash_table_remove(ha->sent_message_ids, client_generated_id)) { // This probably came from us if (event->membership_change != NULL) { //event->event_type == EVENT_TYPE__EVENT_TYPE_REMOVE_USER || EVENT_TYPE__EVENT_TYPE_ADD_USER MembershipChange *membership_change = event->membership_change; PurpleChatConversation *chatconv = purple_conversations_find_chat_with_account(conv_id, ha->account); for (i = 0; i < membership_change->n_participant_ids; i++) { ParticipantId *participant_id = membership_change->participant_ids[i]; if (membership_change->type == MEMBERSHIP_CHANGE_TYPE__MEMBERSHIP_CHANGE_TYPE_LEAVE) { //LeaveReason reason = membership_change->leave_reason; purple_chat_conversation_remove_user(chatconv, participant_id->gaia_id, NULL); if (g_strcmp0(participant_id->gaia_id, ha->self_gaia_id) == 0) { purple_serv_got_chat_left(ha->pc, g_str_hash(conv_id)); g_hash_table_remove(ha->group_chats, conv_id); purple_blist_remove_chat(purple_blist_find_chat(ha->account, conv_id)); PurpleChatUserFlags cbflags = PURPLE_CHAT_USER_NONE; purple_chat_conversation_add_user(chatconv, participant_id->gaia_id, NULL, cbflags, TRUE); if (chat_message != NULL) { size_t n_segments = chat_message->message_content->n_segment; Segment **segments = chat_message->message_content->segment; PurpleXmlNode *html = purple_xmlnode_new("html"); time_t message_timestamp; for (i = 0; i < chat_message->n_annotation; i++) { EventAnnotation *annotation = chat_message->annotation[i]; if (annotation->type == HANGOUTS_MAGIC_HALF_EIGHT_SLASH_ME_TYPE) { //TODO strip name off the front of the first segment purple_xmlnode_insert_data(html, "/me ", -1); for (i = 0; i < n_segments; i++) { Segment *segment = segments[i]; Formatting *formatting = segment->formatting; if (segment->type == SEGMENT_TYPE__SEGMENT_TYPE_TEXT) { node = purple_xmlnode_new_child(html, "span"); } else if (segment->type == SEGMENT_TYPE__SEGMENT_TYPE_LINE_BREAK) { purple_xmlnode_new_child(html, "br"); } else if (segment->type == SEGMENT_TYPE__SEGMENT_TYPE_LINK) { node = purple_xmlnode_new_child(html, "a"); if (segment->link_data) { const gchar *href = segment->link_data->link_target; purple_xmlnode_set_attrib(node, "href", href); // Strip out the www.google.com/url?q= bit if (purple_account_get_bool(ha->account, "unravel_google_url", FALSE)) { PurpleHttpURL *url = purple_http_url_parse(href); if (purple_strequal(purple_http_url_get_host(url), "www.google.com")) { const gchar *path = purple_http_url_get_path(url); //apparently the path includes the query string if (g_str_has_prefix(path, "/url?q=")) { const gchar *end = strchr(path, '&'); gsize len = (end ? (gsize) (end - path) : (gsize) strlen(path)); gchar *new_href = g_strndup(path + 7, len - 7); purple_xmlnode_set_attrib(node, "href", purple_url_decode(new_href)); purple_http_url_free(url); node = purple_xmlnode_new_child(node, "b"); if (formatting->italics) { node = purple_xmlnode_new_child(node, "i"); if (formatting->strikethrough) { node = purple_xmlnode_new_child(node, "s"); if (formatting->underline) { node = purple_xmlnode_new_child(node, "u"); purple_xmlnode_insert_data(node, segment->text, -1); msg = purple_xmlnode_to_str(html, NULL); message_timestamp = time(NULL) - ((current_server_time - timestamp) / 1000000); msg_flags = (g_strcmp0(gaia_id, ha->self_gaia_id) ? PURPLE_MESSAGE_RECV : (PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_REMOTE_SEND | PURPLE_MESSAGE_DELAYED)); if (((current_server_time - timestamp) / 1000000) > 120) { msg_flags |= PURPLE_MESSAGE_DELAYED; if (g_hash_table_contains(ha->group_chats, conv_id)) { PurpleChatConversation *chatconv = purple_conversations_find_chat_with_account(conv_id, ha->account); chatconv = purple_serv_got_joined_chat(ha->pc, g_str_hash(conv_id), conv_id); purple_conversation_set_data(PURPLE_CONVERSATION(chatconv), "conv_id", g_strdup(conv_id)); for (i = 0; i < conversation->n_current_participant; i++) { PurpleChatUserFlags cbflags = PURPLE_CHAT_USER_NONE; purple_chat_conversation_add_user(chatconv, conversation->current_participant[i]->gaia_id, NULL, cbflags, FALSE); pconv = PURPLE_CONVERSATION(chatconv); purple_serv_got_chat_in(pc, g_str_hash(conv_id), gaia_id, msg_flags, msg, message_timestamp); PurpleIMConversation *imconv = NULL; // It's most likely a one-to-one message if (msg_flags & PURPLE_MESSAGE_RECV) { purple_serv_got_im(pc, gaia_id, msg, msg_flags, message_timestamp); gaia_id = g_hash_table_lookup(ha->one_to_ones, conv_id); imconv = purple_conversations_find_im_with_account(gaia_id, ha->account); PurpleMessage *message = purple_message_new_outgoing(gaia_id, msg, msg_flags); imconv = purple_im_conversation_new(ha->account, gaia_id); purple_message_set_time(message, message_timestamp); purple_conversation_write_message(PURPLE_CONVERSATION(imconv), message); imconv = purple_conversations_find_im_with_account(gaia_id, ha->account); pconv = PURPLE_CONVERSATION(imconv); if (purple_conversation_has_focus(pconv)) { hangouts_mark_conversation_seen(pconv, PURPLE_CONVERSATION_UPDATE_UNSEEN); purple_xmlnode_free(html); if (chat_message->message_content->n_attachment) { size_t n_attachment = chat_message->message_content->n_attachment; Attachment **attachments = chat_message->message_content->attachment; for (i = 0; i < n_attachment; i++) { Attachment *attachment = attachments[i]; EmbedItem *embed_item = attachment->embed_item; if (embed_item->plus_photo) { PlusPhoto *plus_photo = embed_item->plus_photo; const gchar *image_url = plus_photo->thumbnail->image_url; const gchar *url = plus_photo->url; PurpleHttpConnection *connection; // Provide a direct link to the video if (plus_photo->media_type == PLUS_PHOTO__MEDIA_TYPE__MEDIA_TYPE_VIDEO && plus_photo->download_url != NULL) { url = plus_photo->download_url; if (g_strcmp0(purple_core_get_ui(), "BitlBee") == 0) { // Bitlbee doesn't support images, so just plop a url to the image instead if (g_hash_table_contains(ha->group_chats, conv_id)) { purple_serv_got_chat_in(pc, g_str_hash(conv_id), gaia_id, msg_flags, url, message_timestamp); if (msg_flags & PURPLE_MESSAGE_RECV) { purple_serv_got_im(pc, gaia_id, url, msg_flags, message_timestamp); PurpleMessage *img_message = purple_message_new_outgoing(gaia_id, url, msg_flags); purple_message_set_time(img_message, message_timestamp); purple_conversation_write_message(pconv, img_message); connection = purple_http_get(ha->pc, hangouts_got_http_image_for_conv, ha, image_url); purple_http_request_set_max_len(purple_http_conn_get_request(connection), -1); g_dataset_set_data_full(connection, "url", g_strdup(url), g_free); g_dataset_set_data_full(connection, "gaia_id", g_strdup(gaia_id), g_free); g_dataset_set_data_full(connection, "conv_id", g_strdup(conv_id), g_free); g_dataset_set_data(connection, "msg_flags", GINT_TO_POINTER(msg_flags)); g_dataset_set_data(connection, "message_timestamp", GINT_TO_POINTER(message_timestamp)); if (event->hangout_event != NULL) { //event->event_type == EVENT_TYPE__EVENT_TYPE_HANGOUT || EVENT_TYPE__EVENT_TYPE_PHONE_CALL //Something to do with calling HangoutEvent *hangout_event = event->hangout_event; time_t message_timestamp = time(NULL) - ((current_server_time - timestamp) / 1000000); switch (hangout_event->event_type) { case HANGOUT_EVENT_TYPE__HANGOUT_EVENT_TYPE_START: case HANGOUT_EVENT_TYPE__HANGOUT_EVENT_TYPE_END: if (g_hash_table_contains(ha->group_chats, conv_id)) { purple_serv_got_chat_in(ha->pc, g_str_hash(conv_id), gaia_id, PURPLE_MESSAGE_SYSTEM, msg, message_timestamp); gaia_id = g_hash_table_lookup(ha->one_to_ones, conv_id); purple_serv_got_im(ha->pc, gaia_id, msg, PURPLE_MESSAGE_SYSTEM, message_timestamp); if (hangout_event->event_type == HANGOUT_EVENT_TYPE__HANGOUT_EVENT_TYPE_START) { if (purple_account_get_bool(ha->account, "show-call-links", !purple_media_manager_get())) { //No voice/video support, display URL gchar *join_message = g_strdup_printf("%s https://plus.google.com/hangouts/_/CONVERSATION/%s", _("To join the call, open "), conv_id); if (g_hash_table_contains(ha->group_chats, conv_id)) { purple_serv_got_chat_in(ha->pc, g_str_hash(conv_id), gaia_id, PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG, join_message, message_timestamp); purple_serv_got_im(ha->pc, gaia_id, join_message, PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG, message_timestamp); if (conv_id && event->conversation_rename != NULL) { ConversationRename *conversation_rename = event->conversation_rename; if (g_hash_table_contains(ha->group_chats, conv_id)) { chat = purple_blist_find_chat(ha->account, conv_id); if (chat && !purple_strequal(purple_chat_get_alias(chat), conversation_rename->new_name)) { g_dataset_set_data(ha, "ignore_set_alias", "true"); purple_chat_set_alias(chat, conversation_rename->new_name); g_dataset_set_data(ha, "ignore_set_alias", NULL); if (timestamp && conv_id) { if (g_hash_table_contains(ha->one_to_ones, conv_id)) { pconv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(g_hash_table_lookup(ha->one_to_ones, conv_id), ha->account)); } else if (g_hash_table_contains(ha->group_chats, conv_id)) { pconv = PURPLE_CONVERSATION(purple_conversations_find_chat_with_account(conv_id, ha->account)); gint64 *last_event_timestamp_ptr = (gint64 *)purple_conversation_get_data(pconv, "last_event_timestamp"); if (last_event_timestamp_ptr == NULL) { last_event_timestamp_ptr = g_new0(gint64, 1); if (timestamp > *last_event_timestamp_ptr) { *last_event_timestamp_ptr = timestamp; purple_conversation_set_data(pconv, "last_event_timestamp", last_event_timestamp_ptr); hangouts_received_typing_notification(PurpleConnection *pc, StateUpdate *state_update) SetTypingNotification *typing_notification = state_update->typing_notification; PurpleIMTypingState typing_state; if (typing_notification == NULL) { ha = purple_connection_get_protocol_data(pc); //purple_debug_info("hangouts", "Received new typing event %p\n", typing_notification); //purple_debug_info("hangouts", "%s\n", pblite_dump_json((ProtobufCMessage *)typing_notification)); //leaky "state_update_header" : { "active_client_state" : ACTIVE_CLIENT_STATE_OTHER_ACTIVE, "request_trace_id" : "-316846338299410553", "notification_settings" : null, "current_server_time" : 1453716154770000 "event_notification" : null, "focus_notification" : null, "typing_notification" : { "id" : "UgxGdpCK_mSrhBX8hrx4AaABAQ" "gaia_id" : "110174066375061118727", "chat_id" : "110174066375061118727" "timestamp" : 1453716154770000, "type" : TYPING_TYPE_STARTED "notification_level_notification" : null, "reply_to_invite_notification" : null, "watermark_notification" : null, "view_modification" : null, "easter_egg_notification" : null, "self_presence_notification" : null, "delete_notification" : null, "presence_notification" : null, "block_notification" : null, "notification_setting_notification" : null, "rich_presence_enabled_state_notification" : null gaia_id = typing_notification->sender_id->gaia_id; if (ha->self_gaia_id && g_strcmp0(gaia_id, ha->self_gaia_id) == 0) conv_id = typing_notification->conversation_id->id; if (g_hash_table_contains(ha->group_chats, conv_id)) { // This is a group conversation PurpleChatConversation *chatconv = purple_conversations_find_chat_with_account(conv_id, ha->account); PurpleChatUser *cb = purple_chat_conversation_find_user(chatconv, gaia_id); PurpleChatUserFlags cbflags; // Getting notified about a buddy we dont know about yet cbflags = purple_chat_user_get_flags(cb); if (typing_notification->type == TYPING_TYPE__TYPING_TYPE_STARTED) cbflags |= PURPLE_CHAT_USER_TYPING; cbflags &= ~PURPLE_CHAT_USER_TYPING; purple_chat_user_set_flags(cb, cbflags); switch(typing_notification->type) { case TYPING_TYPE__TYPING_TYPE_STARTED: typing_state = PURPLE_IM_TYPING; case TYPING_TYPE__TYPING_TYPE_PAUSED: typing_state = PURPLE_IM_TYPED; case TYPING_TYPE__TYPING_TYPE_STOPPED: typing_state = PURPLE_IM_NOT_TYPING; purple_serv_got_typing(pc, gaia_id, 20, typing_state);