eion/purple-hangouts
Clone
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_conversation.h" #include "hangouts.pb-c.h" #include "hangouts_connection.h" #include "hangouts_events.h" gchar * pblite_dump_json ( ProtobufCMessage * message ); hangouts_get_request_header ( HangoutsAccount * ha ) RequestHeader * header = g_new0 ( RequestHeader , 1 ); ClientVersion * version = g_new0 ( ClientVersion , 1 ); request_header__init ( header ); client_version__init ( version ); if ( ha -> client_id != NULL ) { ClientIdentifier * client_identifier = g_new0 ( ClientIdentifier , 1 ); client_identifier__init ( client_identifier ); header -> client_identifier = client_identifier ; header -> client_identifier -> resource = g_strdup ( ha -> client_id ); version -> has_client_id = TRUE ; version -> client_id = CLIENT_ID__CLIENT_ID_WEB_HANGOUTS ; header -> client_version = version ; hangouts_request_header_free ( RequestHeader * header ) if ( header -> client_identifier ) { g_free ( header -> client_identifier -> resource ); g_free ( header -> client_identifier ); g_free ( header -> client_version ); hangouts_get_event_request_header ( HangoutsAccount * ha , const gchar * conv_id ) EventRequestHeader * header = g_new0 ( EventRequestHeader , 1 ); event_request_header__init ( header ); ConversationId * conversation_id = g_new0 ( ConversationId , 1 ); conversation_id__init ( conversation_id ); conversation_id -> id = g_strdup ( conv_id ); header -> conversation_id = conversation_id ; if ( g_hash_table_contains ( ha -> google_voice_conversations , conv_id )) { DeliveryMedium * delivery_medium = g_new0 ( DeliveryMedium , 1 ); PhoneNumber * self_phone = g_new0 ( PhoneNumber , 1 ); delivery_medium__init ( delivery_medium ); phone_number__init ( self_phone ); delivery_medium -> has_medium_type = TRUE ; delivery_medium -> medium_type = DELIVERY_MEDIUM_TYPE__DELIVERY_MEDIUM_GOOGLE_VOICE ; self_phone -> e164 = g_strdup ( ha -> self_phone ); delivery_medium -> self_phone = self_phone ; header -> delivery_medium = delivery_medium ; header -> has_event_type = TRUE ; header -> event_type = EVENT_TYPE__EVENT_TYPE_SMS ; header -> has_client_generated_id = TRUE ; header -> client_generated_id = g_random_int (); //todo off the record status hangouts_event_request_header_free ( EventRequestHeader * header ) if ( header -> conversation_id ) { g_free ( header -> conversation_id -> id ); g_free ( header -> conversation_id ); if ( header -> delivery_medium ) { if ( header -> delivery_medium -> self_phone ) { g_free ( header -> delivery_medium -> self_phone -> e164 ); g_free ( header -> delivery_medium -> self_phone ); g_free ( header -> delivery_medium ); hangouts_got_self_info ( HangoutsAccount * ha , GetSelfInfoResponse * response , gpointer user_data ) Entity * self_entity = response -> self_entity ; PhoneData * phone_data = response -> phone_data ; g_return_if_fail ( self_entity ); g_free ( ha -> self_gaia_id ); ha -> self_gaia_id = g_strdup ( self_entity -> id -> gaia_id ); purple_connection_set_display_name ( ha -> pc , ha -> self_gaia_id ); purple_account_set_string ( ha -> account , "self_gaia_id" , ha -> self_gaia_id ); alias = purple_account_get_private_alias ( ha -> account ); if ( alias == NULL || * alias == '\0' ) { purple_account_set_private_alias ( ha -> account , self_entity -> properties -> display_name ); if ( phone_data != NULL ) { for ( i = 0 ; i < phone_data -> n_phone ; i ++ ) { Phone * phone = phone_data -> phone [ i ]; if ( phone -> google_voice ) { ha -> self_phone = g_strdup ( phone -> phone_number -> e164 ); hangouts_get_buddy_list ( ha ); hangouts_get_self_info ( HangoutsAccount * ha ) GetSelfInfoRequest request ; get_self_info_request__init ( & request ); request . request_header = hangouts_get_request_header ( ha ); hangouts_pblite_get_self_info ( ha , & request , hangouts_got_self_info , NULL ); hangouts_request_header_free ( request . request_header ); if ( ha -> last_event_timestamp != 0 ) { hangouts_get_all_events ( ha , ha -> last_event_timestamp ); hangouts_got_users_presence ( HangoutsAccount * ha , QueryPresenceResponse * response , gpointer user_data ) for ( i = 0 ; i < response -> n_presence_result ; i ++ ) { hangouts_process_presence_result ( ha , response -> presence_result [ i ]); hangouts_get_users_presence ( HangoutsAccount * ha , GList * user_ids ) QueryPresenceRequest request ; ParticipantId ** participant_id ; query_presence_request__init ( & request ); request . request_header = hangouts_get_request_header ( ha ); n_participant_id = g_list_length ( user_ids ); participant_id = g_new0 ( ParticipantId * , n_participant_id ); for ( i = 0 , cur = user_ids ; cur && cur -> data && i < n_participant_id ; ( cur = cur -> next ), i ++ ) { gchar * who = ( gchar * ) cur -> data ; if ( G_UNLIKELY ( ! hangouts_is_valid_id ( who ))) { participant_id [ i ] = g_new0 ( ParticipantId , 1 ); participant_id__init ( participant_id [ i ]); participant_id [ i ] -> gaia_id = who ; request . participant_id = participant_id ; request . n_participant_id = n_participant_id ; request . n_field_mask = 7 ; request . field_mask = g_new0 ( FieldMask , request . n_field_mask ); request . field_mask [ 0 ] = FIELD_MASK__FIELD_MASK_REACHABLE ; request . field_mask [ 1 ] = FIELD_MASK__FIELD_MASK_AVAILABLE ; request . field_mask [ 2 ] = FIELD_MASK__FIELD_MASK_MOOD ; request . field_mask [ 3 ] = FIELD_MASK__FIELD_MASK_LOCATION ; request . field_mask [ 4 ] = FIELD_MASK__FIELD_MASK_IN_CALL ; request . field_mask [ 5 ] = FIELD_MASK__FIELD_MASK_DEVICE ; request . field_mask [ 6 ] = FIELD_MASK__FIELD_MASK_LAST_SEEN ; hangouts_pblite_query_presence ( ha , & request , hangouts_got_users_presence , NULL ); hangouts_request_header_free ( request . request_header ); for ( i = 0 ; i < n_participant_id ; i ++ ) { g_free ( participant_id [ i ]); g_free ( request . field_mask ); hangouts_poll_buddy_status ( gpointer userdata ) HangoutsAccount * ha = userdata ; if ( ! PURPLE_CONNECTION_IS_CONNECTED ( ha -> pc )) { buddies = purple_blist_find_buddies ( ha -> account , NULL ); for ( i = buddies ; i ; i = i -> next ) { PurpleBuddy * buddy = i -> data ; user_list = g_list_prepend ( user_list , ( gpointer ) purple_buddy_get_name ( buddy )); hangouts_get_users_presence ( ha , user_list ); static void hangouts_got_buddy_photo ( PurpleHttpConnection * connection , PurpleHttpResponse * response , gpointer user_data ); hangouts_got_users_information ( HangoutsAccount * ha , GetEntityByIdResponse * response , gpointer user_data ) for ( i = 0 ; i < response -> n_entity_result ; i ++ ) { Entity * entity = response -> entity_result [ i ] -> entity [ 0 ]; gaia_id = entity -> id ? entity -> id -> gaia_id : NULL ; if ( gaia_id != NULL && entity -> properties ) { PurpleBuddy * buddy = purple_blist_find_buddy ( ha -> account , gaia_id ); // Give a best-guess for the buddy's alias if ( entity -> properties -> display_name ) purple_serv_got_alias ( ha -> pc , gaia_id , entity -> properties -> display_name ); else if ( entity -> properties -> canonical_email ) purple_serv_got_alias ( ha -> pc , gaia_id , entity -> properties -> canonical_email ); else if ( entity -> entity_type == PARTICIPANT_TYPE__PARTICIPANT_TYPE_OFF_NETWORK_PHONE && entity -> properties -> n_phone ) purple_serv_got_alias ( ha -> pc , gaia_id , entity -> properties -> phone [ 0 ]); // Set the buddy photo, if it's real if ( entity -> properties -> photo_url != NULL && entity -> properties -> photo_url_status == PHOTO_URL_STATUS__PHOTO_URL_STATUS_USER_PHOTO ) { gchar * photo = g_strconcat ( "https:" , entity -> properties -> photo_url , NULL ); if ( ! purple_strequal ( purple_buddy_icons_get_checksum_for_user ( buddy ), photo )) { PurpleHttpRequest * photo_request = purple_http_request_new ( photo ); if ( ha -> icons_keepalive_pool == NULL ) { ha -> icons_keepalive_pool = purple_http_keepalive_pool_new (); purple_http_keepalive_pool_set_limit_per_host ( ha -> icons_keepalive_pool , 4 ); purple_http_request_set_keepalive_pool ( photo_request , ha -> icons_keepalive_pool ); purple_http_request ( ha -> pc , photo_request , hangouts_got_buddy_photo , buddy ); purple_http_request_unref ( photo_request ); if ( entity -> entity_type == PARTICIPANT_TYPE__PARTICIPANT_TYPE_OFF_NETWORK_PHONE ) { purple_protocol_got_user_status ( ha -> account , gaia_id , "mobile" , NULL ); hangouts_get_users_information ( HangoutsAccount * ha , GList * user_ids ) GetEntityByIdRequest request ; size_t n_batch_lookup_spec ; EntityLookupSpec ** batch_lookup_spec ; get_entity_by_id_request__init ( & request ); request . request_header = hangouts_get_request_header ( ha ); n_batch_lookup_spec = g_list_length ( user_ids ); batch_lookup_spec = g_new0 ( EntityLookupSpec * , n_batch_lookup_spec ); for ( i = 0 , cur = user_ids ; cur && cur -> data && i < n_batch_lookup_spec ; ( cur = cur -> next ), i ++ ) { batch_lookup_spec [ i ] = g_new0 ( EntityLookupSpec , 1 ); entity_lookup_spec__init ( batch_lookup_spec [ i ]); batch_lookup_spec [ i ] -> gaia_id = ( gchar * ) cur -> data ; request . batch_lookup_spec = batch_lookup_spec ; request . n_batch_lookup_spec = n_batch_lookup_spec ; hangouts_pblite_get_entity_by_id ( ha , & request , hangouts_got_users_information , NULL ); hangouts_request_header_free ( request . request_header ); for ( i = 0 ; i < n_batch_lookup_spec ; i ++ ) { g_free ( batch_lookup_spec [ i ]); g_free ( batch_lookup_spec ); hangouts_got_user_info ( HangoutsAccount * ha , GetEntityByIdResponse * response , gpointer user_data ) PurpleNotifyUserInfo * user_info ; if ( response -> n_entity_result < 1 ) { entity = response -> entity_result [ 0 ] -> entity [ 0 ]; if ( entity == NULL || entity -> properties == NULL ) { props = entity -> properties ; user_info = purple_notify_user_info_new (); switch ( entity -> entity_type ) { case PARTICIPANT_TYPE__PARTICIPANT_TYPE_OFF_NETWORK_PHONE : type_str = _ ( "SMS" ); break ; case PARTICIPANT_TYPE__PARTICIPANT_TYPE_GAIA : type_str = _ ( "Hangouts (Gaia)" ); break ; default : type_str = _ ( "Unknown" ); break ; purple_notify_user_info_add_pair_html ( user_info , _ ( "Type" ), type_str ); if ( props -> display_name != NULL ) purple_notify_user_info_add_pair_html ( user_info , _ ( "Display Name" ), props -> display_name ); if ( props -> first_name != NULL ) purple_notify_user_info_add_pair_html ( user_info , _ ( "First Name" ), props -> first_name ); gchar * prefix = strncmp ( props -> photo_url , "//" , 2 ) ? "" : "https:" ; gchar * photo_tag = g_strdup_printf ( "<a href= \" %s%s \" ><img width= \" 128 \" src= \" %s%s \" /></a>" , prefix , props -> photo_url , prefix , props -> photo_url ); purple_notify_user_info_add_pair_html ( user_info , _ ( "Photo" ), photo_tag ); for ( i = 0 ; i < props -> n_email ; i ++ ) { purple_notify_user_info_add_pair_html ( user_info , _ ( "Email" ), props -> email [ i ]); for ( i = 0 ; i < props -> n_phone ; i ++ ) { purple_notify_user_info_add_pair_html ( user_info , _ ( "Phone" ), props -> phone [ i ]); case GENDER__GENDER_MALE : gender_str = _ ( "Male" ); break ; case GENDER__GENDER_FEMALE : gender_str = _ ( "Female" ); break ; default : gender_str = _ ( "Unknown" ); break ; purple_notify_user_info_add_pair_html ( user_info , _ ( "Gender" ), gender_str ); if ( props -> canonical_email != NULL ) purple_notify_user_info_add_pair_html ( user_info , _ ( "Canonical Email" ), props -> canonical_email ); purple_notify_userinfo ( ha -> pc , who , user_info , NULL , NULL ); hangouts_get_info ( PurpleConnection * pc , const gchar * who ) HangoutsAccount * ha = purple_connection_get_protocol_data ( pc ); GetEntityByIdRequest request ; EntityLookupSpec entity_lookup_spec ; EntityLookupSpec * batch_lookup_spec ; gchar * who_dup = g_strdup ( who ); get_entity_by_id_request__init ( & request ); request . request_header = hangouts_get_request_header ( ha ); entity_lookup_spec__init ( & entity_lookup_spec ); entity_lookup_spec . gaia_id = who_dup ; batch_lookup_spec = & entity_lookup_spec ; request . batch_lookup_spec = & batch_lookup_spec ; request . n_batch_lookup_spec = 1 ; hangouts_pblite_get_entity_by_id ( ha , & request , hangouts_got_user_info , who_dup ); hangouts_request_header_free ( request . request_header ); hangouts_got_conversation_events ( HangoutsAccount * ha , GetConversationResponse * response , gpointer user_data ) Conversation * conversation ; PurpleConversation * conv ; PurpleChatConversation * chatconv ; PurpleConversationUiOps * convuiops ; PurpleGroup * temp_group = NULL ; if ( response -> conversation_state == NULL ) { if ( response -> response_header -> status == RESPONSE_STATUS__RESPONSE_STATUS_ERROR_INVALID_CONVERSATION ) { 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 )); purple_notify_error ( ha -> pc , _ ( "Error" ), _ ( "An error occurred while fetching the history of the conversation" ), NULL , purple_request_cpar_from_connection ( ha -> pc )); conversation = response -> conversation_state -> conversation ; g_return_if_fail ( conversation != NULL ); conv_id = conversation -> conversation_id -> id ; //purple_debug_info("hangouts", "got conversation events %s\n", pblite_dump_json((ProtobufCMessage *)response)); if ( conversation -> type == CONVERSATION_TYPE__CONVERSATION_TYPE_GROUP ) { 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 )); conv = PURPLE_CONVERSATION ( chatconv ); convuiops = purple_conversation_get_ui_ops ( conv ); for ( i = 0 ; i < conversation -> n_participant_data ; i ++ ) { ConversationParticipantData * participant_data = conversation -> participant_data [ i ]; PurpleChatUserFlags cbflags = PURPLE_CHAT_USER_NONE ; const gchar * gaia_id = participant_data -> id -> gaia_id ; gboolean ui_update_sent = FALSE ; purple_chat_conversation_add_user ( chatconv , gaia_id , NULL , cbflags , FALSE ); cb = purple_chat_conversation_find_user ( chatconv , gaia_id ); purple_chat_user_set_alias ( cb , participant_data -> fallback_name ); // Horrible hack. Don't try this at home! if ( convuiops -> chat_rename_user != NULL ) { #if PURPLE_VERSION_CHECK(3, 0, 0) convuiops -> chat_rename_user ( chatconv , gaia_id , gaia_id , participant_data -> fallback_name ); convuiops -> chat_rename_user ( conv , gaia_id , gaia_id , participant_data -> fallback_name ); } else if ( convuiops -> chat_update_user != NULL ) { #if PURPLE_VERSION_CHECK(3, 0, 0) convuiops -> chat_update_user ( cb ); convuiops -> chat_update_user ( conv , gaia_id ); if ( ui_update_sent == FALSE ) { // Bitlbee doesn't have the above two functions, lets do an even worse hack if ( temp_group == NULL ) { temp_group = purple_blist_find_group ( "Hangouts Temporary Chat Buddies" ); temp_group = purple_group_new ( "Hangouts Temporary Chat Buddies" ); purple_blist_add_group ( temp_group , NULL ); fakebuddy = purple_buddy_new ( ha -> account , gaia_id , participant_data -> fallback_name ); purple_blist_node_set_transient ( PURPLE_BLIST_NODE ( fakebuddy ), TRUE ); purple_blist_add_buddy ( fakebuddy , NULL , temp_group , NULL ); for ( i = 0 ; i < response -> conversation_state -> n_event ; i ++ ) { Event * event = response -> conversation_state -> event [ i ]; // Ignore join/parts when loading history if ( ! event -> membership_change ) { if ( event -> chat_message != NULL && event -> chat_message -> message_content -> n_attachment && ! purple_account_get_bool ( ha -> account , "fetch_image_history" , TRUE )) { purple_debug_info ( "hangouts" , "skipping attachment due to fetch_image_history disabled \n " ); //Send event to the hangouts_events.c slaughterhouse hangouts_process_conversation_event ( ha , conversation , event , response -> response_header -> current_server_time ); hangouts_get_conversation_events ( HangoutsAccount * ha , const gchar * conv_id , gint64 since_timestamp ) //since_timestamp is in microseconds GetConversationRequest request ; ConversationId conversation_id ; ConversationSpec conversation_spec ; EventContinuationToken event_continuation_token ; get_conversation_request__init ( & request ); request . request_header = hangouts_get_request_header ( ha ); conversation_spec__init ( & conversation_spec ); request . conversation_spec = & conversation_spec ; conversation_id__init ( & conversation_id ); conversation_id . id = ( gchar * ) conv_id ; conversation_spec . conversation_id = & conversation_id ; if ( since_timestamp > 0 ) { request . has_include_event = TRUE ; request . include_event = TRUE ; request . has_max_events_per_conversation = TRUE ; request . max_events_per_conversation = 50 ; event_continuation_token__init ( & event_continuation_token ); event_continuation_token . event_timestamp = since_timestamp ; request . event_continuation_token = & event_continuation_token ; hangouts_pblite_get_conversation ( ha , & request , hangouts_got_conversation_events , NULL ); hangouts_request_header_free ( request . request_header ); hangouts_got_all_events ( HangoutsAccount * ha , SyncAllNewEventsResponse * response , gpointer user_data ) //purple_debug_info("hangouts", "%s\n", pblite_dump_json((ProtobufCMessage *)response)); struct _SyncAllNewEventsResponse ResponseHeader *response_header; protobuf_c_boolean has_sync_timestamp; size_t n_conversation_state; ConversationState **conversation_state; struct _ConversationState ConversationId *conversation_id; Conversation *conversation; EventContinuationToken *event_continuation_token; sync_timestamp = response -> sync_timestamp ; for ( i = 0 ; i < response -> n_conversation_state ; i ++ ) { ConversationState * conversation_state = response -> conversation_state [ i ]; Conversation * conversation = conversation_state -> conversation ; for ( j = 0 ; j < conversation_state -> n_event ; j ++ ) { Event * event = conversation_state -> event [ j ]; hangouts_process_conversation_event ( ha , conversation , event , sync_timestamp ); hangouts_get_all_events ( HangoutsAccount * ha , guint64 since_timestamp ) SyncAllNewEventsRequest request ; g_return_if_fail ( since_timestamp > 0 ); sync_all_new_events_request__init ( & request ); request . request_header = hangouts_get_request_header ( ha ); request . has_last_sync_timestamp = TRUE ; request . last_sync_timestamp = since_timestamp ; request . has_max_response_size_bytes = TRUE ; request . max_response_size_bytes = 1048576 ; // 1 mibbily bite hangouts_pblite_sync_all_new_events ( ha , & request , hangouts_got_all_events , NULL ); hangouts_request_header_free ( request . request_header ); hangouts_chat_info ( PurpleConnection * pc ) PurpleProtocolChatEntry * pce ; pce = g_new0 ( PurpleProtocolChatEntry , 1 ); pce -> label = _ ( "Conversation ID" ); pce -> identifier = "conv_id" ; m = g_list_append ( m , pce ); hangouts_chat_info_defaults ( PurpleConnection * pc , const char * chatname ) GHashTable * defaults = g_hash_table_new_full ( g_str_hash , g_str_equal , NULL , g_free ); g_hash_table_insert ( defaults , "conv_id" , g_strdup ( chatname )); hangouts_join_chat ( PurpleConnection * pc , GHashTable * data ) HangoutsAccount * ha = purple_connection_get_protocol_data ( pc ); PurpleChatConversation * chatconv ; conv_id = ( gchar * ) g_hash_table_lookup ( data , "conv_id" ); chatconv = purple_conversations_find_chat_with_account ( conv_id , ha -> account ); if ( chatconv != NULL && ! purple_chat_conversation_has_left ( chatconv )) { purple_conversation_present ( PURPLE_CONVERSATION ( chatconv )); chatconv = purple_serv_got_joined_chat ( pc , g_str_hash ( conv_id ), conv_id ); purple_conversation_set_data ( PURPLE_CONVERSATION ( chatconv ), "conv_id" , g_strdup ( conv_id )); purple_conversation_present ( PURPLE_CONVERSATION ( chatconv )); //TODO store and use timestamp of last event hangouts_get_conversation_events ( ha , conv_id , 0 ); hangouts_got_join_chat_from_url ( HangoutsAccount * ha , OpenGroupConversationFromUrlResponse * response , gpointer user_data ) if ( ! response || ! response -> conversation_id || ! response -> conversation_id -> id ) { 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 )); hangouts_get_conversation_events ( ha , response -> conversation_id -> id , 0 ); hangouts_join_chat_from_url ( HangoutsAccount * ha , const gchar * url ) OpenGroupConversationFromUrlRequest request ; g_return_if_fail ( url != NULL ); open_group_conversation_from_url_request__init ( & request ); request . request_header = hangouts_get_request_header ( ha ); request . url = ( gchar * ) url ; hangouts_pblite_open_group_conversation_from_url ( ha , & request , hangouts_got_join_chat_from_url , NULL ); hangouts_request_header_free ( request . request_header ); hangouts_get_chat_name ( GHashTable * data ) temp = g_hash_table_lookup ( data , "conv_id" ); hangouts_add_person_to_blist ( HangoutsAccount * ha , gchar * gaia_id , gchar * alias ) PurpleGroup * hangouts_group = purple_blist_find_group ( "Hangouts" ); if ( purple_account_get_bool ( ha -> account , "hide_self" , FALSE ) && purple_strequal ( gaia_id , ha -> self_gaia_id )) { hangouts_group = purple_group_new ( "Hangouts" ); purple_blist_add_group ( hangouts_group , NULL ); purple_blist_add_buddy ( purple_buddy_new ( ha -> account , gaia_id , alias ), NULL , hangouts_group , NULL ); hangouts_add_conversation_to_blist ( HangoutsAccount * ha , Conversation * conversation , GHashTable * unique_user_ids ) PurpleGroup * hangouts_group = NULL ; gchar * conv_id = conversation -> conversation_id -> id ; 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 ) || conversation -> network_type [ 0 ] == NETWORK_TYPE__NETWORK_TYPE_PHONE ) { g_hash_table_replace ( ha -> google_voice_conversations , g_strdup ( conv_id ), NULL ); if ( conversation -> self_conversation_state -> delivery_medium_option && ha -> self_phone == NULL ) { ha -> self_phone = g_strdup ( conversation -> self_conversation_state -> delivery_medium_option [ 0 ] -> delivery_medium -> self_phone -> e164 ); if ( conversation -> type == CONVERSATION_TYPE__CONVERSATION_TYPE_ONE_TO_ONE ) { gchar * other_person = conversation -> participant_data [ 0 ] -> id -> gaia_id ; guint participant_num = 0 ; gchar * other_person_alias ; if ( ! g_strcmp0 ( other_person , conversation -> self_conversation_state -> self_read_state -> participant_id -> gaia_id )) { other_person = conversation -> participant_data [ 1 ] -> id -> gaia_id ; other_person_alias = conversation -> participant_data [ participant_num ] -> fallback_name ; g_hash_table_replace ( ha -> one_to_ones , g_strdup ( conv_id ), g_strdup ( other_person )); g_hash_table_replace ( ha -> one_to_ones_rev , g_strdup ( other_person ), g_strdup ( conv_id )); if ( ! purple_blist_find_buddy ( ha -> account , other_person )) { hangouts_add_person_to_blist ( ha , other_person , other_person_alias ); purple_serv_got_alias ( ha -> pc , other_person , other_person_alias ); if ( unique_user_ids == NULL ) { GList * user_list = g_list_prepend ( NULL , other_person ); hangouts_get_users_presence ( ha , user_list ); PurpleChat * chat = purple_blist_find_chat ( ha -> account , conv_id ); gchar * name = conversation -> name ; gboolean has_name = name ? TRUE : FALSE ; g_hash_table_replace ( ha -> group_chats , g_strdup ( conv_id ), NULL ); hangouts_group = purple_blist_find_group ( "Hangouts" ); hangouts_group = purple_group_new ( "Hangouts" ); purple_blist_add_group ( hangouts_group , NULL ); gchar ** name_set = g_new0 ( gchar * , conversation -> n_participant_data + 1 ); for ( i = 0 ; i < conversation -> n_participant_data ; i ++ ) { gchar * p_name = conversation -> participant_data [ i ] -> fallback_name ; name_set [ i ] = _ ( "Unknown" ); name = g_strjoinv ( ", " , name_set ); purple_blist_add_chat ( purple_chat_new ( ha -> account , name , hangouts_chat_info_defaults ( ha -> pc , conv_id )), hangouts_group , NULL ); if ( has_name && strstr ( purple_chat_get_name ( chat ), _ ( "Unknown" )) != NULL ) { purple_chat_set_alias ( chat , name ); for ( i = 0 ; i < conversation -> n_participant_data ; i ++ ) { ConversationParticipantData * participant_data = conversation -> participant_data [ i ]; if ( participant_data -> participant_type != PARTICIPANT_TYPE__PARTICIPANT_TYPE_UNKNOWN ) { if ( ! purple_blist_find_buddy ( ha -> account , participant_data -> id -> gaia_id )) { hangouts_add_person_to_blist ( ha , participant_data -> id -> gaia_id , participant_data -> fallback_name ); if ( participant_data -> fallback_name != NULL ) { purple_serv_got_alias ( ha -> pc , participant_data -> id -> gaia_id , participant_data -> fallback_name ); if ( unique_user_ids != NULL ) { g_hash_table_replace ( unique_user_ids , participant_data -> id -> gaia_id , participant_data -> id ); hangouts_got_conversation_list ( HangoutsAccount * ha , SyncRecentConversationsResponse * response , gpointer user_data ) GHashTable * unique_user_ids = g_hash_table_new_full ( g_str_hash , g_str_equal , NULL , NULL ); GList * unique_user_ids_list ; for ( i = 0 ; i < response -> n_conversation_state ; i ++ ) { ConversationState * conversation_state = response -> conversation_state [ i ]; Conversation * conversation = conversation_state -> conversation ; //purple_debug_info("hangouts", "got conversation state %s\n", pblite_dump_json((ProtobufCMessage *)conversation_state)); hangouts_add_conversation_to_blist ( ha , conversation , unique_user_ids ); //Add missing people from the buddy list for ( node = purple_blist_get_root (); node = purple_blist_node_next ( node , TRUE )) { if ( PURPLE_IS_BUDDY ( node )) { PurpleBuddy * buddy = PURPLE_BUDDY ( node ); if ( purple_buddy_get_account ( buddy ) != ha -> account ) { name = purple_buddy_get_name ( buddy ); g_hash_table_replace ( unique_user_ids , ( gchar * ) name , NULL ); unique_user_ids_list = g_hash_table_get_keys ( unique_user_ids ); hangouts_get_users_presence ( ha , unique_user_ids_list ); hangouts_get_users_information ( ha , unique_user_ids_list ); g_list_free ( unique_user_ids_list ); g_hash_table_unref ( unique_user_ids ); hangouts_get_conversation_list ( HangoutsAccount * ha ) SyncRecentConversationsRequest request ; SyncFilter sync_filter [ 1 ]; sync_recent_conversations_request__init ( & request ); request . request_header = hangouts_get_request_header ( ha ); request . has_max_conversations = TRUE ; request . max_conversations = 100 ; request . has_max_events_per_conversation = TRUE ; request . max_events_per_conversation = 1 ; sync_filter [ 0 ] = SYNC_FILTER__SYNC_FILTER_INBOX ; request . sync_filter = sync_filter ; request . n_sync_filter = 1 ; // Back streets back, alright! hangouts_pblite_sync_recent_conversations ( ha , & request , hangouts_got_conversation_list , NULL ); hangouts_request_header_free ( request . request_header ); hangouts_got_buddy_photo ( PurpleHttpConnection * connection , PurpleHttpResponse * response , gpointer user_data ) PurpleBuddy * buddy = user_data ; PurpleAccount * account = purple_buddy_get_account ( buddy ); const gchar * name = purple_buddy_get_name ( buddy ); PurpleHttpRequest * request = purple_http_conn_get_request ( connection ); const gchar * photo_url = purple_http_request_get_url ( request ); const gchar * response_str ; if ( purple_http_response_get_error ( response ) != NULL ) { purple_debug_error ( "hangouts" , "Failed to get buddy photo for %s from %s \n " , name , photo_url ); response_str = purple_http_response_get_data ( response , & response_len ); response_dup = g_memdup ( response_str , response_len ); purple_buddy_icons_set_for_user ( account , name , response_dup , response_len , photo_url ); hangouts_got_buddy_list ( PurpleHttpConnection * http_conn , PurpleHttpResponse * response , gpointer user_data ) HangoutsAccount * ha = user_data ; PurpleGroup * hangouts_group = NULL ; const gchar * response_str ; "id": "ListMergedPeople", "displayName": "{USERS NAME}", "url": "https://lh5.googleusercontent.com/-iPLHmUq4g_0/AAAAAAAAAAI/AAAAAAAAAAA/j1C9pusixPY/photo.jpg", "photoToken": "CAASFTEwOTE4MDY1MTIyOTAyODgxNDcwOBih9d_CAg==" "encodedContainerId": "{USER ID}" if ( purple_http_response_get_error ( response ) != NULL ) { purple_debug_error ( "hangouts" , "Failed to download buddy list: %s \n " , purple_http_response_get_error ( response )); response_str = purple_http_response_get_data ( response , & response_len ); obj = json_decode_object ( response_str , response_len ); mergedPerson = json_object_get_array_member ( json_object_get_object_member ( obj , "result" ), "mergedPerson" ); len = json_array_get_length ( mergedPerson ); for ( i = 0 ; i < len ; i ++ ) { JsonNode * node = json_array_get_element ( mergedPerson , i ); JsonObject * person = json_node_get_object ( node ); gchar * reachableAppType = hangouts_json_path_query_string ( node , "$.inAppReachability[*].appType" , NULL ); if ( ! purple_strequal ( reachableAppType , "BABEL" )) { g_free ( reachableAppType ); g_free ( reachableAppType ); name = json_object_get_string_member ( person , "personId" ); alias = hangouts_json_path_query_string ( node , "$.name[*].displayName" , NULL ); photo = hangouts_json_path_query_string ( node , "$.photo[*].url" , NULL ); buddy = purple_blist_find_buddy ( ha -> account , name ); if ( purple_account_get_bool ( ha -> account , "hide_self" , FALSE ) && purple_strequal ( ha -> self_gaia_id , name )) { purple_blist_remove_buddy ( buddy ); if ( hangouts_group == NULL ) { hangouts_group = purple_blist_find_group ( "Hangouts" ); hangouts_group = purple_group_new ( "Hangouts" ); purple_blist_add_group ( hangouts_group , NULL ); buddy = purple_buddy_new ( ha -> account , name , alias ); purple_blist_add_buddy ( buddy , NULL , hangouts_group , NULL ); purple_serv_got_alias ( ha -> pc , name , alias ); if ( ! purple_strequal ( purple_buddy_icons_get_checksum_for_user ( buddy ), photo )) { PurpleHttpRequest * photo_request = purple_http_request_new ( photo ); if ( ha -> icons_keepalive_pool == NULL ) { ha -> icons_keepalive_pool = purple_http_keepalive_pool_new (); purple_http_keepalive_pool_set_limit_per_host ( ha -> icons_keepalive_pool , 4 ); purple_http_request_set_keepalive_pool ( photo_request , ha -> icons_keepalive_pool ); purple_http_request ( ha -> pc , photo_request , hangouts_got_buddy_photo , buddy ); purple_http_request_unref ( photo_request ); hangouts_get_buddy_list ( HangoutsAccount * ha ) POST https://clients6.google.com/rpc/plusi?key={KEY}&alt=json Authorization: SAPISIDHASH {AUTH HEADER} Content-Type: application/json {"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]}}}} 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 ); 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 ); /* {"method":"plusi.ozinternal.loadblockedpeople","id":"getBlockedUsers","apiVersion":"v2","jsonrpc":"2.0","params":{"includeChatBlocked":true}} */ hangouts_block_user ( PurpleConnection * pc , const char * who ) HangoutsAccount * ha = purple_connection_get_protocol_data ( pc ); 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 ); hangouts_client6_request ( ha , "/rpc/plusi?key=" GOOGLE_GPLUS_KEY , HANGOUTS_CONTENT_TYPE_JSON , request_data , -1 , HANGOUTS_CONTENT_TYPE_JSON , NULL , ha ); hangouts_unblock_user ( PurpleConnection * pc , const char * who ) HangoutsAccount * ha = purple_connection_get_protocol_data ( pc ); 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 ); hangouts_client6_request ( ha , "/rpc/plusi?key=" GOOGLE_GPLUS_KEY , HANGOUTS_CONTENT_TYPE_JSON , request_data , -1 , HANGOUTS_CONTENT_TYPE_JSON , NULL , ha ); hangouts_convert_html_to_segments ( HangoutsAccount * ha , const gchar * html_message , guint * segments_count ) const gchar * c = html_message ; GList * segment_list = NULL ; gboolean is_bold = FALSE , is_italic = FALSE , is_strikethrough = FALSE , is_underline = FALSE ; gboolean is_link = FALSE ; if ( c == NULL || * c == '\0' ) { if ( segments_count != NULL ) { text_content = g_string_new ( "" ); segment = g_new0 ( Segment , 1 ); GString * tag = g_string_new ( "" ); if ( * c == '/' ) { // closing tag while ( * c != ' ' && * c != '>' ) { g_string_append_c ( tag , * c ); segment -> text = g_string_free ( text_content , FALSE ); text_content = g_string_new ( "" ); segment -> formatting = g_new0 ( Formatting , 1 ); formatting__init ( segment -> formatting ); segment -> formatting -> has_bold = TRUE ; segment -> formatting -> bold = is_bold ; segment -> formatting -> has_italics = TRUE ; segment -> formatting -> italics = is_italic ; segment -> formatting -> has_strikethrough = TRUE ; segment -> formatting -> strikethrough = is_strikethrough ; segment -> formatting -> has_underline = TRUE ; segment -> formatting -> underline = is_underline ; segment -> type = SEGMENT_TYPE__SEGMENT_TYPE_LINK ; segment -> link_data = g_new0 ( LinkData , 1 ); link_data__init ( segment -> link_data ); segment -> link_data -> link_target = g_strdup ( last_link ); segment_list = g_list_append ( segment_list , segment ); segment = g_new0 ( Segment , 1 ); if ( ! g_ascii_strcasecmp ( tag -> str , "BR" ) || ! g_ascii_strcasecmp ( tag -> str , "BR/" )) { //Line break, push directly onto the stack segment -> type = SEGMENT_TYPE__SEGMENT_TYPE_LINE_BREAK ; segment_list = g_list_append ( segment_list , segment ); segment = g_new0 ( Segment , 1 ); } else if ( ! g_ascii_strcasecmp ( tag -> str , "B" ) || ! g_ascii_strcasecmp ( tag -> str , "BOLD" ) || ! g_ascii_strcasecmp ( tag -> str , "STRONG" )) { } else if ( ! g_ascii_strcasecmp ( tag -> str , "I" ) || ! g_ascii_strcasecmp ( tag -> str , "ITALIC" ) || ! g_ascii_strcasecmp ( tag -> str , "EM" )) { } else if ( ! g_ascii_strcasecmp ( tag -> str , "S" ) || ! g_ascii_strcasecmp ( tag -> str , "STRIKE" )) { is_strikethrough = opening ; } else if ( ! g_ascii_strcasecmp ( tag -> str , "U" ) || ! g_ascii_strcasecmp ( tag -> str , "UNDERLINE" )) { } else if ( ! g_ascii_strcasecmp ( tag -> str , "A" )) { if ( g_ascii_strcasecmp ( tag -> str , " HREF=" ) == 0 ) { if ( * c == '"' || * c == '\'' ) { href_end = strchr ( c + 1 , * c ); //Wow this should not happen, but what the hell href_end = MIN ( strchr ( c , ' ' ), strchr ( c , '>' )); href_end = strchr ( c , '>' ); gchar * attrib = g_strndup ( c , href_end - c ); last_link = purple_unescape_text ( attrib ); g_string_free ( tag , TRUE ); if (( plain = purple_markup_unescape_entity ( c , & len )) == NULL ) { g_string_append_c ( text_content , * c ); g_string_append ( text_content , plain ); g_string_append_c ( text_content , * c ); segment -> text = g_string_free ( text_content , FALSE ); segment -> formatting = g_new0 ( Formatting , 1 ); formatting__init ( segment -> formatting ); segment -> formatting -> has_bold = TRUE ; segment -> formatting -> bold = is_bold ; segment -> formatting -> has_italics = TRUE ; segment -> formatting -> italics = is_italic ; segment -> formatting -> has_strikethrough = TRUE ; segment -> formatting -> strikethrough = is_strikethrough ; segment -> formatting -> has_underline = TRUE ; segment -> formatting -> underline = is_underline ; segment -> type = SEGMENT_TYPE__SEGMENT_TYPE_LINK ; segment -> link_data = g_new0 ( LinkData , 1 ); link_data__init ( segment -> link_data ); segment -> link_data -> link_target = g_strdup ( last_link ); segment_list = g_list_append ( segment_list , segment ); n_segments = g_list_length ( segment_list ); segments = g_new0 ( Segment * , n_segments + 1 ); for ( i = 0 ; segment_list && segment_list -> data ; i ++ ) { segments [ i ] = segment_list -> data ; segment_list = g_list_delete_link ( segment_list , segment_list ); if ( segments_count != NULL ) { * segments_count = n_segments ; hangouts_free_segments ( Segment ** segments ) for ( i = 0 ; segments [ i ]; i ++ ) { g_free ( segments [ i ] -> text ); g_free ( segments [ i ] -> formatting ); if ( segments [ i ] -> link_data != NULL ) { g_free ( segments [ i ] -> link_data -> link_target ); g_free ( segments [ i ] -> link_data ); //Received the photoid of the sent image to be able to attach to an outgoing message hangouts_conversation_send_image_part2_cb ( PurpleHttpConnection * connection , PurpleHttpResponse * response , gpointer user_data ) const gchar * response_raw ; PurpleConnection * pc = purple_http_conn_get_purple_connection ( connection ); SendChatMessageRequest request ; ExistingMedia existing_media ; if ( purple_http_response_get_error ( response ) != NULL ) { 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 )); g_dataset_destroy ( connection ); response_raw = purple_http_response_get_data ( response , & response_len ); purple_debug_info ( "hangouts" , "image_part2_cb %s \n " , response_raw ); node = json_decode ( response_raw , response_len ); photoid = hangouts_json_path_query_string ( node , "$..photoid" , NULL ); conv_id = g_dataset_get_data ( connection , "conv_id" ); send_chat_message_request__init ( & request ); existing_media__init ( & existing_media ); request . request_header = hangouts_get_request_header ( ha ); request . event_request_header = hangouts_get_event_request_header ( ha , conv_id ); photo . photo_id = photoid ; existing_media . photo = & photo ; request . existing_media = & existing_media ; hangouts_pblite_send_chat_message ( ha , & request , NULL , NULL ); g_hash_table_insert ( ha -> sent_message_ids , g_strdup_printf ( "%" G_GUINT64_FORMAT , request . event_request_header -> client_generated_id ), NULL ); g_dataset_destroy ( connection ); hangouts_request_header_free ( request . request_header ); hangouts_event_request_header_free ( request . event_request_header ); //Received the url to upload the image data to hangouts_conversation_send_image_part1_cb ( PurpleHttpConnection * connection , PurpleHttpResponse * response , gpointer user_data ) PurpleHttpRequest * request ; PurpleHttpConnection * new_connection ; PurpleConnection * pc = purple_http_conn_get_purple_connection ( connection ); const gchar * response_raw ; if ( purple_http_response_get_error ( response ) != NULL ) { 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 )); g_dataset_destroy ( connection ); conv_id = g_dataset_get_data ( connection , "conv_id" ); image = g_dataset_get_data ( connection , "image" ); response_raw = purple_http_response_get_data ( response , & response_len ); purple_debug_info ( "hangouts" , "image_part1_cb %s \n " , response_raw ); node = json_decode ( response_raw , response_len ); upload_url = hangouts_json_path_query_string ( node , "$..putInfo.url" , NULL ); request = purple_http_request_new ( upload_url ); purple_http_request_set_cookie_jar ( request , ha -> cookie_jar ); purple_http_request_header_set ( request , "Content-Type" , "application/octet-stream" ); purple_http_request_set_method ( request , "POST" ); purple_http_request_set_contents ( request , purple_image_get_data ( image ), purple_image_get_data_size ( image )); new_connection = purple_http_request ( ha -> pc , request , hangouts_conversation_send_image_part2_cb , ha ); purple_http_request_unref ( request ); g_dataset_set_data_full ( new_connection , "conv_id" , g_strdup ( conv_id ), g_free ); g_dataset_destroy ( connection ); hangouts_conversation_send_image ( HangoutsAccount * ha , const gchar * conv_id , PurpleImage * image ) PurpleHttpRequest * request ; PurpleHttpConnection * connection ; filename = ( gchar * ) purple_image_get_path ( image ); filename = g_path_get_basename ( filename ); filename = g_strdup_printf ( "purple%u.%s" , g_random_int (), purple_image_get_extension ( image )); 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 )); request = purple_http_request_new ( HANGOUTS_IMAGE_UPLOAD_URL ); purple_http_request_set_cookie_jar ( request , ha -> cookie_jar ); purple_http_request_header_set ( request , "Content-Type" , "application/x-www-form-urlencoded;charset=UTF-8" ); purple_http_request_set_method ( request , "POST" ); purple_http_request_set_contents ( request , postdata , -1 ); purple_http_request_set_max_redirects ( request , 0 ); connection = purple_http_request ( ha -> pc , request , hangouts_conversation_send_image_part1_cb , ha ); purple_http_request_unref ( request ); g_dataset_set_data_full ( connection , "conv_id" , g_strdup ( conv_id ), g_free ); g_dataset_set_data_full ( connection , "image" , image , NULL ); hangouts_conversation_check_message_for_images ( HangoutsAccount * ha , const gchar * conv_id , const gchar * message ) if (( img = strstr ( message , "<img " )) || ( img = strstr ( message , "<IMG " ))) { const gchar * close = strchr ( img , '>' ); if ((( id = strstr ( img , "ID= \" " )) || ( id = strstr ( img , "id= \" " ))) && int imgid = atoi ( id + 4 ); PurpleImage * image = purple_image_store_get ( imgid ); hangouts_conversation_send_image ( ha , conv_id , image ); } else if ((( src = strstr ( img , "SRC= \" " )) || ( src = strstr ( img , "src= \" " ))) && // purple3 embeds images using src="purple-image:1" if ( strncmp ( src + 5 , "purple-image:" , 13 ) == 0 ) { int imgid = atoi ( src + 5 + 13 ); PurpleImage * image = purple_image_store_get ( imgid ); hangouts_conversation_send_image ( ha , conv_id , image ); hangouts_conversation_send_message ( HangoutsAccount * ha , const gchar * conv_id , const gchar * message ) SendChatMessageRequest request ; MessageContent message_content ; EventAnnotation event_annotation ; gchar * message_dup = g_strdup ( message ); //Check for any images to send first hangouts_conversation_check_message_for_images ( ha , conv_id , message_dup ); send_chat_message_request__init ( & request ); message_content__init ( & message_content ); if ( purple_message_meify ( message_dup , -1 )) { //TODO put purple_account_get_private_alias(sa->account) on the front event_annotation__init ( & event_annotation ); event_annotation . has_type = TRUE ; event_annotation . type = HANGOUTS_MAGIC_HALF_EIGHT_SLASH_ME_TYPE ; request . n_annotation = 1 ; request . annotation = g_new0 ( EventAnnotation * , 1 ); request . annotation [ 0 ] = & event_annotation ; segments = hangouts_convert_html_to_segments ( ha , message_dup , & n_segments ); message_content . segment = segments ; message_content . n_segment = n_segments ; request . request_header = hangouts_get_request_header ( ha ); request . event_request_header = hangouts_get_event_request_header ( ha , conv_id ); request . message_content = & message_content ; //purple_debug_info("hangouts", "%s\n", pblite_dump_json((ProtobufCMessage *)&request)); //leaky //TODO listen to response hangouts_pblite_send_chat_message ( ha , & request , NULL , NULL ); g_hash_table_insert ( ha -> sent_message_ids , g_strdup_printf ( "%" G_GUINT64_FORMAT , request . event_request_header -> client_generated_id ), NULL ); hangouts_free_segments ( segments ); hangouts_request_header_free ( request . request_header ); hangouts_event_request_header_free ( request . event_request_header ); hangouts_send_im ( PurpleConnection * pc , #if PURPLE_VERSION_CHECK(3, 0, 0) const gchar * who = purple_message_get_recipient ( msg ); const gchar * message = purple_message_get_contents ( msg ); const gchar * who , const gchar * message , PurpleMessageFlags flags ) ha = purple_connection_get_protocol_data ( pc ); conv_id = g_hash_table_lookup ( ha -> one_to_ones_rev , who ); if ( G_UNLIKELY ( ! hangouts_is_valid_id ( who ))) { hangouts_search_users_text ( ha , who ); //We don't have any known conversations for this person hangouts_create_conversation ( ha , TRUE , who , message ); return hangouts_conversation_send_message ( ha , conv_id , message ); hangouts_chat_send ( PurpleConnection * pc , gint id , #if PURPLE_VERSION_CHECK(3, 0, 0) const gchar * message = purple_message_get_contents ( msg ); const gchar * message , PurpleMessageFlags flags ) PurpleChatConversation * chatconv ; ha = purple_connection_get_protocol_data ( pc ); chatconv = purple_conversations_find_chat ( pc , id ); conv_id = purple_conversation_get_data ( PURPLE_CONVERSATION ( chatconv ), "conv_id" ); // Fix for a race condition around the chat data and serv_got_joined_chat() conv_id = purple_conversation_get_name ( PURPLE_CONVERSATION ( chatconv )); g_return_val_if_fail ( conv_id , -1 ); g_return_val_if_fail ( g_hash_table_contains ( ha -> group_chats , conv_id ), -1 ); ret = hangouts_conversation_send_message ( ha , conv_id , message ); purple_serv_got_chat_in ( pc , g_str_hash ( conv_id ), ha -> self_gaia_id , PURPLE_MESSAGE_SEND , message , time ( NULL )); hangouts_send_typing ( PurpleConnection * pc , const gchar * who , PurpleIMTypingState state ) PurpleConversation * conv ; ha = purple_connection_get_protocol_data ( pc ); conv = PURPLE_CONVERSATION ( purple_conversations_find_im_with_account ( who , purple_connection_get_account ( pc ))); g_return_val_if_fail ( conv , -1 ); return hangouts_conv_send_typing ( conv , state , ha ); hangouts_conv_send_typing ( PurpleConversation * conv , PurpleIMTypingState state , HangoutsAccount * ha ) SetTypingRequest request ; ConversationId conversation_id ; pc = purple_conversation_get_connection ( conv ); if ( ! PURPLE_CONNECTION_IS_CONNECTED ( pc )) if ( ! purple_strequal ( purple_protocol_get_id ( purple_connection_get_protocol ( pc )), HANGOUTS_PLUGIN_ID )) ha = purple_connection_get_protocol_data ( pc ); conv_id = purple_conversation_get_data ( conv , "conv_id" ); if ( PURPLE_IS_IM_CONVERSATION ( conv )) { conv_id = g_hash_table_lookup ( ha -> one_to_ones_rev , purple_conversation_get_name ( conv )); conv_id = purple_conversation_get_name ( conv ); g_return_val_if_fail ( conv_id , -1 ); //TODO create new conversation for this new person set_typing_request__init ( & request ); request . request_header = hangouts_get_request_header ( ha ); conversation_id__init ( & conversation_id ); conversation_id . id = ( gchar * ) conv_id ; request . conversation_id = & conversation_id ; request . type = TYPING_TYPE__TYPING_TYPE_STARTED ; request . type = TYPING_TYPE__TYPING_TYPE_PAUSED ; case PURPLE_IM_NOT_TYPING : request . type = TYPING_TYPE__TYPING_TYPE_STOPPED ; //TODO listen to response //TODO dont send STOPPED if we just sent a message hangouts_pblite_set_typing ( ha , & request , NULL , NULL ); hangouts_request_header_free ( request . request_header ); hangouts_chat_leave_by_conv_id ( PurpleConnection * pc , const gchar * conv_id , const gchar * who ) RemoveUserRequest request ; ParticipantId participant_id ; g_return_if_fail ( conv_id ); ha = purple_connection_get_protocol_data ( pc ); g_return_if_fail ( g_hash_table_contains ( ha -> group_chats , conv_id )); remove_user_request__init ( & request ); participant_id__init ( & participant_id ); participant_id . gaia_id = ( gchar * ) who ; participant_id . chat_id = ( gchar * ) who ; //XX do we need this? request . participant_id = & participant_id ; request . request_header = hangouts_get_request_header ( ha ); request . event_request_header = hangouts_get_event_request_header ( ha , conv_id ); //XX do we need to see if this was successful, or does it just come through as a new event? hangouts_pblite_remove_user ( ha , & request , NULL , NULL ); hangouts_request_header_free ( request . request_header ); hangouts_event_request_header_free ( request . event_request_header ); g_hash_table_remove ( ha -> group_chats , conv_id ); hangouts_chat_leave ( PurpleConnection * pc , int id ) PurpleChatConversation * chatconv ; chatconv = purple_conversations_find_chat ( pc , id ); conv_id = purple_conversation_get_data ( PURPLE_CONVERSATION ( chatconv ), "conv_id" ); // Fix for a race condition around the chat data and serv_got_joined_chat() conv_id = purple_conversation_get_name ( PURPLE_CONVERSATION ( chatconv )); return hangouts_chat_leave_by_conv_id ( pc , conv_id , NULL ); hangouts_chat_kick ( PurpleConnection * pc , int id , const gchar * who ) PurpleChatConversation * chatconv ; chatconv = purple_conversations_find_chat ( pc , id ); conv_id = purple_conversation_get_data ( PURPLE_CONVERSATION ( chatconv ), "conv_id" ); // Fix for a race condition around the chat data and serv_got_joined_chat() conv_id = purple_conversation_get_name ( PURPLE_CONVERSATION ( chatconv )); return hangouts_chat_leave_by_conv_id ( pc , conv_id , who ); hangouts_created_conversation ( HangoutsAccount * ha , CreateConversationResponse * response , gpointer user_data ) Conversation * conversation = response -> conversation ; gchar * message = user_data ; gchar * dump = pblite_dump_json (( ProtobufCMessage * ) response ); purple_debug_info ( "hangouts" , "%s \n " , dump ); if ( conversation == NULL ) { purple_debug_error ( "hangouts" , "Could not create conversation \n " ); hangouts_add_conversation_to_blist ( ha , conversation , NULL ); conv_id = conversation -> conversation_id -> id ; hangouts_get_conversation_events ( ha , conv_id , 0 ); hangouts_conversation_send_message ( ha , conv_id , message ); hangouts_create_conversation ( HangoutsAccount * ha , gboolean is_one_to_one , const char * who , const gchar * optional_message ) CreateConversationRequest request ; gchar * message_dup = NULL ; create_conversation_request__init ( & request ); request . request_header = hangouts_get_request_header ( ha ); request . type = CONVERSATION_TYPE__CONVERSATION_TYPE_ONE_TO_ONE ; request . type = CONVERSATION_TYPE__CONVERSATION_TYPE_GROUP ; request . n_invitee_id = 1 ; request . invitee_id = g_new0 ( InviteeID * , 1 ); request . invitee_id [ 0 ] = g_new0 ( InviteeID , 1 ); invitee_id__init ( request . invitee_id [ 0 ]); request . invitee_id [ 0 ] -> gaia_id = g_strdup ( who ); request . has_client_generated_id = TRUE ; request . client_generated_id = g_random_int (); if ( optional_message != NULL ) { message_dup = g_strdup ( optional_message ); hangouts_pblite_create_conversation ( ha , & request , hangouts_created_conversation , message_dup ); g_free ( request . invitee_id [ 0 ] -> gaia_id ); g_free ( request . invitee_id [ 0 ]); g_free ( request . invitee_id ); hangouts_request_header_free ( request . request_header ); hangouts_archive_conversation ( HangoutsAccount * ha , const gchar * conv_id ) ModifyConversationViewRequest request ; ConversationId conversation_id ; modify_conversation_view_request__init ( & request ); conversation_id__init ( & conversation_id ); conversation_id . id = ( gchar * ) conv_id ; request . request_header = hangouts_get_request_header ( ha ); request . conversation_id = & conversation_id ; request . has_new_view = TRUE ; request . new_view = CONVERSATION_VIEW__CONVERSATION_VIEW_ARCHIVED ; request . has_last_event_timestamp = TRUE ; request . last_event_timestamp = ha -> last_event_timestamp ; hangouts_pblite_modify_conversation_view ( ha , & request , NULL , NULL ); hangouts_request_header_free ( request . request_header ); if ( g_hash_table_contains ( ha -> one_to_ones , conv_id )) { gchar * buddy_id = g_hash_table_lookup ( ha -> one_to_ones , conv_id ); g_hash_table_remove ( ha -> one_to_ones_rev , buddy_id ); g_hash_table_remove ( ha -> one_to_ones , conv_id ); g_hash_table_remove ( ha -> group_chats , conv_id ); hangouts_initiate_chat_from_node ( PurpleBlistNode * node , gpointer userdata ) if ( PURPLE_IS_BUDDY ( node )) PurpleBuddy * buddy = ( PurpleBuddy * ) node ; PurpleConnection * pc = purple_account_get_connection ( purple_buddy_get_account ( buddy )); ha = purple_connection_get_protocol_data ( pc ); hangouts_create_conversation ( ha , FALSE , purple_buddy_get_name ( buddy ), NULL ); hangouts_chat_invite ( PurpleConnection * pc , int id , const char * message , const char * who ) PurpleChatConversation * chatconv ; ha = purple_connection_get_protocol_data ( pc ); chatconv = purple_conversations_find_chat ( pc , id ); conv_id = purple_conversation_get_data ( PURPLE_CONVERSATION ( chatconv ), "conv_id" ); conv_id = purple_conversation_get_name ( PURPLE_CONVERSATION ( chatconv )); add_user_request__init ( & request ); request . request_header = hangouts_get_request_header ( ha ); request . event_request_header = hangouts_get_event_request_header ( ha , conv_id ); request . n_invitee_id = 1 ; request . invitee_id = g_new0 ( InviteeID * , 1 ); request . invitee_id [ 0 ] = g_new0 ( InviteeID , 1 ); invitee_id__init ( request . invitee_id [ 0 ]); request . invitee_id [ 0 ] -> gaia_id = g_strdup ( who ); hangouts_pblite_add_user ( ha , & request , NULL , NULL ); g_free ( request . invitee_id [ 0 ] -> gaia_id ); g_free ( request . invitee_id [ 0 ]); g_free ( request . invitee_id ); hangouts_request_header_free ( request . request_header ); hangouts_event_request_header_free ( request . event_request_header ); #define PURPLE_CONVERSATION_IS_VALID(conv) (g_list_find(purple_conversations_get_all(), conv) != NULL) hangouts_mark_conversation_focused_timeout ( gpointer convpointer ) PurpleConversation * conv = convpointer ; ConversationId conversation_id ; const gchar * conv_id = NULL ; if ( ! PURPLE_CONVERSATION_IS_VALID ( conv )) account = purple_conversation_get_account ( conv ); if ( account == NULL || ! purple_account_is_connected ( account )) pc = purple_account_get_connection ( account ); if ( ! PURPLE_CONNECTION_IS_CONNECTED ( pc )) ha = purple_connection_get_protocol_data ( pc ); is_focused = purple_conversation_has_focus ( conv ); if ( is_focused && ha -> last_conversation_focused == conv ) set_focus_request__init ( & request ); request . request_header = hangouts_get_request_header ( ha ); conv_id = purple_conversation_get_data ( conv , "conv_id" ); if ( PURPLE_IS_IM_CONVERSATION ( conv )) { conv_id = g_hash_table_lookup ( ha -> one_to_ones_rev , purple_conversation_get_name ( conv )); conv_id = purple_conversation_get_name ( conv ); conversation_id__init ( & conversation_id ); conversation_id . id = ( gchar * ) conv_id ; request . conversation_id = & conversation_id ; request . type = FOCUS_TYPE__FOCUS_TYPE_FOCUSED ; ha -> last_conversation_focused = conv ; request . type = FOCUS_TYPE__FOCUS_TYPE_UNFOCUSED ; if ( ha -> last_conversation_focused == conv ) { ha -> last_conversation_focused = NULL ; hangouts_pblite_set_focus ( ha , & request , ( HangoutsPbliteSetFocusResponseFunc ) hangouts_default_response_dump , NULL ); hangouts_request_header_free ( request . request_header ); hangouts_mark_conversation_seen_timeout ( gpointer convpointer ) PurpleConversation * conv = convpointer ; UpdateWatermarkRequest request ; ConversationId conversation_id ; const gchar * conv_id = NULL ; gint64 * last_read_timestamp_ptr , last_read_timestamp = 0 ; gint64 * last_event_timestamp_ptr , last_event_timestamp = 0 ; if ( ! PURPLE_CONVERSATION_IS_VALID ( conv )) if ( ! purple_conversation_has_focus ( conv )) account = purple_conversation_get_account ( conv ); if ( account == NULL || ! purple_account_is_connected ( account )) pc = purple_account_get_connection ( account ); if ( ! PURPLE_CONNECTION_IS_CONNECTED ( pc )) purple_conversation_set_data ( conv , "mark_seen_timeout" , NULL ); ha = purple_connection_get_protocol_data ( pc ); if ( ! purple_presence_is_status_primitive_active ( purple_account_get_presence ( ha -> account ), PURPLE_STATUS_AVAILABLE )) { last_read_timestamp_ptr = ( gint64 * ) purple_conversation_get_data ( conv , "last_read_timestamp" ); if ( last_read_timestamp_ptr != NULL ) { last_read_timestamp = * last_read_timestamp_ptr ; last_event_timestamp_ptr = ( gint64 * ) purple_conversation_get_data ( conv , "last_event_timestamp" ); if ( last_event_timestamp_ptr != NULL ) { last_event_timestamp = * last_event_timestamp_ptr ; if ( last_event_timestamp <= last_read_timestamp ) { update_watermark_request__init ( & request ); request . request_header = hangouts_get_request_header ( ha ); conv_id = purple_conversation_get_data ( conv , "conv_id" ); if ( PURPLE_IS_IM_CONVERSATION ( conv )) { conv_id = g_hash_table_lookup ( ha -> one_to_ones_rev , purple_conversation_get_name ( conv )); conv_id = purple_conversation_get_name ( conv ); conversation_id__init ( & conversation_id ); conversation_id . id = ( gchar * ) conv_id ; request . conversation_id = & conversation_id ; request . has_last_read_timestamp = TRUE ; request . last_read_timestamp = last_event_timestamp ; hangouts_pblite_update_watermark ( ha , & request , ( HangoutsPbliteUpdateWatermarkResponseFunc ) hangouts_default_response_dump , NULL ); hangouts_request_header_free ( request . request_header ); if ( last_read_timestamp_ptr == NULL ) { last_read_timestamp_ptr = g_new0 ( gint64 , 1 ); * last_read_timestamp_ptr = last_event_timestamp ; purple_conversation_set_data ( conv , "last_read_timestamp" , last_read_timestamp_ptr ); hangouts_mark_conversation_seen ( PurpleConversation * conv , PurpleConversationUpdateType type ) if ( type != PURPLE_CONVERSATION_UPDATE_UNSEEN ) pc = purple_conversation_get_connection ( conv ); if ( ! PURPLE_CONNECTION_IS_CONNECTED ( pc )) if ( ! purple_strequal ( purple_protocol_get_id ( purple_connection_get_protocol ( pc )), HANGOUTS_PLUGIN_ID )) mark_seen_timeout = GPOINTER_TO_INT ( purple_conversation_get_data ( conv , "mark_seen_timeout" )); g_source_remove ( mark_seen_timeout ); mark_seen_timeout = g_timeout_add_seconds ( 1 , hangouts_mark_conversation_seen_timeout , conv ); purple_conversation_set_data ( conv , "mark_seen_timeout" , GINT_TO_POINTER ( mark_seen_timeout )); g_timeout_add_seconds ( 1 , hangouts_mark_conversation_focused_timeout , conv ); hangouts_set_active_client ( pc ); hangouts_set_status ( PurpleAccount * account , PurpleStatus * status ) SetPresenceRequest request ; PurpleConnection * pc = purple_account_get_connection ( account ); HangoutsAccount * ha = purple_connection_get_protocol_data ( pc ); Segment ** segments = NULL ; PresenceStateSetting presence_state_setting ; MoodSetting mood_setting ; MoodMessage mood_message ; MoodContent mood_content ; set_presence_request__init ( & request ); request . request_header = hangouts_get_request_header ( ha ); if ( purple_status_type_get_primitive ( purple_status_get_status_type ( status )) == PURPLE_STATUS_AVAILABLE ) { presence_state_setting__init ( & presence_state_setting ); presence_state_setting . has_timeout_secs = TRUE ; presence_state_setting . timeout_secs = 720 ; presence_state_setting . has_type = TRUE ; presence_state_setting . type = CLIENT_PRESENCE_STATE_TYPE__CLIENT_PRESENCE_STATE_DESKTOP_ACTIVE ; request . presence_state_setting = & presence_state_setting ; if ( purple_status_type_get_primitive ( purple_status_get_status_type ( status )) == PURPLE_STATUS_AWAY ) { presence_state_setting__init ( & presence_state_setting ); presence_state_setting . has_timeout_secs = TRUE ; presence_state_setting . timeout_secs = 720 ; presence_state_setting . has_type = TRUE ; presence_state_setting . type = CLIENT_PRESENCE_STATE_TYPE__CLIENT_PRESENCE_STATE_DESKTOP_IDLE ; request . presence_state_setting = & presence_state_setting ; dnd_setting__init ( & dnd_setting ); if ( purple_status_type_get_primitive ( purple_status_get_status_type ( status )) == PURPLE_STATUS_UNAVAILABLE ) { dnd_setting . has_do_not_disturb = TRUE ; dnd_setting . do_not_disturb = TRUE ; dnd_setting . has_timeout_secs = TRUE ; dnd_setting . timeout_secs = 172800 ; dnd_setting . has_do_not_disturb = TRUE ; dnd_setting . do_not_disturb = FALSE ; request . dnd_setting = & dnd_setting ; mood_setting__init ( & mood_setting ); mood_message__init ( & mood_message ); mood_content__init ( & mood_content ); message = purple_status_get_attr_string ( status , "message" ); if ( message && * message ) { segments = hangouts_convert_html_to_segments ( ha , message , & n_segments ); mood_content . segment = segments ; mood_content . n_segment = n_segments ; mood_message . mood_content = & mood_content ; mood_setting . mood_message = & mood_message ; request . mood_setting = & mood_setting ; hangouts_pblite_set_presence ( ha , & request , ( HangoutsPbliteSetPresenceResponseFunc ) hangouts_default_response_dump , NULL ); hangouts_request_header_free ( request . request_header ); hangouts_free_segments ( segments ); hangouts_roomlist_got_list ( HangoutsAccount * ha , SyncRecentConversationsResponse * response , gpointer user_data ) PurpleRoomlist * roomlist = user_data ; for ( i = 0 ; i < response -> n_conversation_state ; i ++ ) { ConversationState * conversation_state = response -> conversation_state [ i ]; Conversation * conversation = conversation_state -> conversation ; if ( conversation -> type == CONVERSATION_TYPE__CONVERSATION_TYPE_GROUP ) { gchar ** users_set = g_new0 ( gchar * , conversation -> n_participant_data + 1 ); gchar * name = conversation -> name ; PurpleRoomlistRoom * room = purple_roomlist_room_new ( PURPLE_ROOMLIST_ROOMTYPE_ROOM , conversation -> conversation_id -> id , NULL ); purple_roomlist_room_add_field ( roomlist , room , conversation -> conversation_id -> id ); for ( j = 0 ; j < conversation -> n_participant_data ; j ++ ) { gchar * p_name = conversation -> participant_data [ j ] -> fallback_name ; users_set [ j ] = _ ( "Unknown" ); users = g_strjoinv ( ", " , users_set ); purple_roomlist_room_add_field ( roomlist , room , users ); purple_roomlist_room_add_field ( roomlist , room , name ); purple_roomlist_room_add ( roomlist , room ); purple_roomlist_set_in_progress ( roomlist , FALSE ); hangouts_roomlist_get_list ( PurpleConnection * pc ) HangoutsAccount * ha = purple_connection_get_protocol_data ( pc ); PurpleRoomlist * roomlist ; roomlist = purple_roomlist_new ( ha -> account ); f = purple_roomlist_field_new ( PURPLE_ROOMLIST_FIELD_STRING , _ ( "ID" ), "chatname" , TRUE ); fields = g_list_append ( fields , f ); f = purple_roomlist_field_new ( PURPLE_ROOMLIST_FIELD_STRING , _ ( "Users" ), "users" , FALSE ); fields = g_list_append ( fields , f ); f = purple_roomlist_field_new ( PURPLE_ROOMLIST_FIELD_STRING , _ ( "Name" ), "name" , FALSE ); fields = g_list_append ( fields , f ); purple_roomlist_set_fields ( roomlist , fields ); purple_roomlist_set_in_progress ( roomlist , TRUE ); //Stolen from hangouts_get_conversation_list() SyncRecentConversationsRequest request ; SyncFilter sync_filter [ 1 ]; sync_recent_conversations_request__init ( & request ); request . request_header = hangouts_get_request_header ( ha ); request . has_max_conversations = TRUE ; request . max_conversations = 100 ; request . has_max_events_per_conversation = TRUE ; request . max_events_per_conversation = 1 ; sync_filter [ 0 ] = SYNC_FILTER__SYNC_FILTER_INBOX ; request . sync_filter = sync_filter ; request . n_sync_filter = 1 ; // Back streets back, alright! hangouts_pblite_sync_recent_conversations ( ha , & request , hangouts_roomlist_got_list , roomlist ); hangouts_request_header_free ( request . request_header ); hangouts_rename_conversation ( HangoutsAccount * ha , const gchar * conv_id , const gchar * alias ) RenameConversationRequest request ; rename_conversation_request__init ( & request ); request . request_header = hangouts_get_request_header ( ha ); request . event_request_header = hangouts_get_event_request_header ( ha , conv_id ); request . new_name = ( gchar * ) alias ; hangouts_pblite_rename_conversation ( ha , & request , NULL , NULL ); hangouts_request_header_free ( request . request_header ); hangouts_event_request_header_free ( request . event_request_header );