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_connection.h" #include "hangouts_pblite.h" #include "hangouts_json.h" #include "hangouts.pb-c.h" #include "hangouts_conversation.h" hangouts_process_data_chunks ( HangoutsAccount * ha , const gchar * data , gsize len ) chunks = json_decode_array ( data , len ); for ( i = 0 , num_chunks = json_array_get_length ( chunks ); i < num_chunks ; i ++ ) { chunk = json_array_get_array_element ( chunks , i ); array = json_array_get_array_element ( chunk , 1 ); array0 = json_array_get_element ( array , 0 ); if ( JSON_NODE_HOLDS_VALUE ( array0 )) { if ( g_strcmp0 ( json_node_get_string ( array0 ), "noop" ) == 0 ) { //A nope ninja delivers a wicked dragon kick const gchar * p = json_object_get_string_member ( json_node_get_object ( array0 ), "p" ); JsonObject * wrapper = json_decode_object ( p , -1 ); if ( json_object_has_member ( wrapper , "3" )) { const gchar * new_client_id = json_object_get_string_member ( json_object_get_object_member ( wrapper , "3" ), "2" ); purple_debug_info ( "hangouts" , "Received new client_id: %s \n " , new_client_id ); ha -> client_id = g_strdup ( new_client_id ); hangouts_add_channel_services ( ha ); hangouts_set_active_client ( ha -> pc ); hangouts_set_status ( ha -> account , purple_account_get_active_status ( ha -> account )); if ( json_object_has_member ( wrapper , "2" )) { const gchar * wrapper22 = json_object_get_string_member ( json_object_get_object_member ( wrapper , "2" ), "2" ); JsonArray * pblite_message = json_decode_array ( wrapper22 , -1 ); const gchar * message_type ; if ( pblite_message == NULL ) { printf ( "bad wrapper22 %s \n " , wrapper22 ); json_object_unref ( wrapper ); message_type = json_array_get_string_element ( pblite_message , 0 ); //cbu == ClientBatchUpdate if ( purple_strequal ( message_type , "cbu" )) { BatchUpdate batch_update = BATCH_UPDATE__INIT ; printf ( "---------------------- \n " ); pblite_decode (( ProtobufCMessage * ) & batch_update , pblite_message , TRUE ); printf ( "====================== \n " ); printf ( "Is valid? %s \n " , protobuf_c_message_check (( ProtobufCMessage * ) & batch_update ) ? "Yes" : "No" ); printf ( "====================== \n " ); printf ( "CBU %s" , pblite_dump_json (( ProtobufCMessage * ) & batch_update )); JsonArray * debug = pblite_encode (( ProtobufCMessage * ) & batch_update ); JsonNode * node = json_node_new ( JSON_NODE_ARRAY ); json_node_take_array ( node , debug ); gchar * json = json_encode ( node , NULL ); printf ( "Old: %s \n New: %s \n " , wrapper22 , json ); pblite_decode (( ProtobufCMessage * ) & batch_update , debug , TRUE ); debug = pblite_encode (( ProtobufCMessage * ) & batch_update ); json_node_take_array ( node , debug ); gchar * json2 = json_encode ( node , NULL ); printf ( "Mine1: %s \n Mine2: %s \n " , json , json2 ); printf ( "---------------------- \n " ); for ( j = 0 ; j < batch_update . n_state_update ; j ++ ) { purple_signal_emit ( purple_connection_get_protocol ( ha -> pc ), "hangouts-received-stateupdate" , ha -> pc , batch_update . state_update [ j ]); } else if ( purple_strequal ( message_type , "n_nm" )) { GmailNotification gmail_notification = GMAIL_NOTIFICATION__INIT ; const gchar * username = json_object_get_string_member ( json_object_get_object_member ( json_object_get_object_member ( wrapper , "2" ), "1" ), "2" ); pblite_decode (( ProtobufCMessage * ) & gmail_notification , pblite_message , TRUE ); purple_signal_emit ( purple_connection_get_protocol ( ha -> pc ), "hangouts-gmail-notification" , ha -> pc , username , & gmail_notification ); json_array_unref ( pblite_message ); json_object_unref ( wrapper ); json_array_unref ( chunks ); read_all ( int fd , void * buf , size_t len ) int rval = read ( fd , buf + rs , len - rs ); hangouts_process_channel ( int fd ) while ( read ( fd , len_str + lenpos , 1 ) > 0 ) { if ( len_str [ lenpos ] == '\n' ) { //convert to int, use as length of string to read printf ( "len_str is %s \n " , len_str ); chunk = g_new ( gchar , len * 2 ); //XX - could be a utf-16 length*2 though, so read up until \n???? if ( read_all ( fd , chunk , len ) > 0 ) { //throw chunk to hangouts_process_data_chunks hangouts_process_data_chunks ( NULL , chunk , len ); hangouts_process_channel_buffer ( HangoutsAccount * ha ) guint len_len ; //len len len len len len len len len g_return_if_fail ( ha -> channel_buffer ); while ( ha -> channel_buffer -> len ) { bufdata = ( gchar * ) ha -> channel_buffer -> data ; bufsize = ha -> channel_buffer -> len ; len_end = g_strstr_len ( bufdata , bufsize , " \n " ); // Not enough data to read if ( purple_debug_is_verbose ()) { purple_debug_info ( "hangouts" , "Couldn't find length of chunk \n " ); len_len = len_end - bufdata ; len_str = g_strndup ( bufdata , len_len ); len = ( gsize ) atoi ( len_str ); // Len was 0 ? Must have been a bad read :( bufsize = bufsize - len_len - 1 ; // Not enough data to read if ( purple_debug_is_verbose ()) { purple_debug_info ( "hangouts" , "Couldn't read %" G_GSIZE_FORMAT " bytes when we only have %" G_GSIZE_FORMAT " \n " , len , bufsize ); hangouts_process_data_chunks ( ha , bufdata + len_len + 1 , len ); g_byte_array_remove_range ( ha -> channel_buffer , 0 , len + len_len + 1 ); hangouts_set_auth_headers ( HangoutsAccount * ha , PurpleHttpRequest * request ) g_get_current_time ( & time ); mstime = ((( gint64 ) time . tv_sec ) * 1000 ) + ( time . tv_usec / 1000 ); mstime_str = g_strdup_printf ( "%" G_GINT64_FORMAT , mstime ); sapisid_cookie = purple_http_cookie_jar_get ( ha -> cookie_jar , "SAPISID" ); hash = g_checksum_new ( G_CHECKSUM_SHA1 ); g_checksum_update ( hash , ( guchar * ) mstime_str , strlen ( mstime_str )); g_checksum_update ( hash , ( guchar * ) " " , 1 ); if ( sapisid_cookie && * sapisid_cookie ) { // Should we just bail out if we dont have the cookie? g_checksum_update ( hash , ( guchar * ) sapisid_cookie , strlen ( sapisid_cookie )); g_checksum_update ( hash , ( guchar * ) " " , 1 ); g_checksum_update ( hash , ( guchar * ) HANGOUTS_PBLITE_XORIGIN_URL , strlen ( HANGOUTS_PBLITE_XORIGIN_URL )); sha1 = g_checksum_get_string ( hash ); purple_http_request_header_set_printf ( request , "Authorization" , "SAPISIDHASH %s_%s" , mstime_str , sha1 ); purple_http_request_header_set ( request , "X-Origin" , HANGOUTS_PBLITE_XORIGIN_URL ); purple_http_request_header_set ( request , "X-Goog-AuthUser" , "0" ); hangouts_longpoll_request_content ( PurpleHttpConnection * http_conn , PurpleHttpResponse * response , const gchar * buffer , size_t offset , size_t length , gpointer user_data ) HangoutsAccount * ha = user_data ; ha -> last_data_received = time ( NULL ); if ( ! purple_http_response_is_successful ( response )) { purple_debug_error ( "hangouts" , "longpoll_request_content had error: '%s' \n " , purple_http_response_get_error ( response )); g_byte_array_append ( ha -> channel_buffer , ( guint8 * ) buffer , length ); hangouts_process_channel_buffer ( ha ); hangouts_longpoll_request_closed ( PurpleHttpConnection * http_conn , PurpleHttpResponse * response , gpointer user_data ) HangoutsAccount * ha = user_data ; if ( ! PURPLE_IS_CONNECTION ( purple_http_conn_get_purple_connection ( http_conn ))) { if ( ha -> channel_watchdog ) { g_source_remove ( ha -> channel_watchdog ); ha -> channel_watchdog = 0 ; // remaining data 'should' have been dealt with in hangouts_longpoll_request_content g_byte_array_free ( ha -> channel_buffer , TRUE ); ha -> channel_buffer = g_byte_array_sized_new ( HANGOUTS_BUFFER_DEFAULT_SIZE ); if ( purple_http_response_get_error ( response ) != NULL ) { purple_debug_error ( "hangouts" , "longpoll_request_closed %d %s \n " , purple_http_response_get_code ( response ), purple_http_response_get_error ( response )); hangouts_fetch_channel_sid ( ha ); hangouts_longpoll_request ( ha ); channel_watchdog_check ( gpointer data ) PurpleConnection * pc = data ; PurpleHttpConnection * conn ; if ( PURPLE_IS_CONNECTION ( pc )) { ha = purple_connection_get_protocol_data ( pc ); conn = ha -> channel_connection ; if ( ha -> last_data_received && ha -> last_data_received < ( time ( NULL ) - 60 )) { // should have been something within the last 60 seconds purple_http_conn_cancel ( conn ); ha -> last_data_received = 0 ; if ( ! purple_http_conn_is_running ( conn )) { hangouts_longpoll_request ( ha ); hangouts_longpoll_request ( HangoutsAccount * ha ) PurpleHttpRequest * request ; url = g_string_new ( HANGOUTS_CHANNEL_URL_PREFIX "channel/bind" "?" ); g_string_append ( url , "VER=8&" ); // channel protocol version g_string_append_printf ( url , "gsessionid=%s&" , purple_url_encode ( ha -> gsessionid_param )); g_string_append ( url , "RID=rpc&" ); // request identifier g_string_append ( url , "t=1&" ); // trial g_string_append_printf ( url , "SID=%s&" , purple_url_encode ( ha -> sid_param )); // session ID g_string_append ( url , "CI=0&" ); // 0 if streaming/chunked requests should be used g_string_append ( url , "ctype=hangouts&" ); // client type g_string_append ( url , "TYPE=xmlhttp&" ); // type of request request = purple_http_request_new ( NULL ); purple_http_request_set_cookie_jar ( request , ha -> cookie_jar ); purple_http_request_set_url ( request , url -> str ); purple_http_request_set_timeout ( request , -1 ); // to infinity and beyond! purple_http_request_set_response_writer ( request , hangouts_longpoll_request_content , ha ); purple_http_request_set_keepalive_pool ( request , ha -> channel_keepalive_pool ); hangouts_set_auth_headers ( ha , request ); ha -> channel_connection = purple_http_request ( ha -> pc , request , hangouts_longpoll_request_closed , ha ); g_string_free ( url , TRUE ); if ( ha -> channel_watchdog ) { g_source_remove ( ha -> channel_watchdog ); ha -> channel_watchdog = g_timeout_add_seconds ( 1 , channel_watchdog_check , ha -> pc ); hangouts_send_maps_cb ( PurpleHttpConnection * http_conn , PurpleHttpResponse * response , gpointer user_data ) * [0,["c","<sid>","",8]], * [1,[{"gsid":"<gsid>"}]] HangoutsAccount * ha = user_data ; if ( purple_http_response_get_error ( response ) != NULL ) { purple_connection_error ( ha -> pc , PURPLE_CONNECTION_ERROR_NETWORK_ERROR , purple_http_response_get_error ( response )); res_raw = purple_http_response_get_data ( response , & res_len ); json_start = g_strstr_len ( res_raw , res_len , " \n " ); if ( json_start == NULL ) { purple_connection_error ( ha -> pc , PURPLE_CONNECTION_ERROR_NETWORK_ERROR , "Blank maps response" ); node = json_decode ( json_start , atoi ( res_raw )); sid = hangouts_json_path_query_string ( node , "$[0][1][1]" , NULL ); gsid = hangouts_json_path_query_string ( node , "$[1][1][0].gsid" , NULL ); g_free ( ha -> gsessionid_param ); ha -> gsessionid_param = gsid ; hangouts_longpoll_request ( ha ); hangouts_fetch_channel_sid ( HangoutsAccount * ha ) g_free ( ha -> gsessionid_param ); ha -> gsessionid_param = NULL ; hangouts_send_maps ( ha , NULL , hangouts_send_maps_cb ); hangouts_send_maps ( HangoutsAccount * ha , JsonArray * map_list , PurpleHttpCallback send_maps_callback ) PurpleHttpRequest * request ; url = g_string_new ( HANGOUTS_CHANNEL_URL_PREFIX "channel/bind" "?" ); g_string_append ( url , "VER=8&" ); // channel protocol version g_string_append ( url , "RID=81188&" ); // request identifier g_string_append ( url , "ctype=hangouts&" ); // client type if ( ha -> gsessionid_param ) g_string_append_printf ( url , "gsessionid=%s&" , purple_url_encode ( ha -> gsessionid_param )); g_string_append_printf ( url , "SID=%s&" , purple_url_encode ( ha -> sid_param )); // session ID request = purple_http_request_new ( NULL ); purple_http_request_set_cookie_jar ( request , ha -> cookie_jar ); purple_http_request_set_url ( request , url -> str ); purple_http_request_set_method ( request , "POST" ); purple_http_request_header_set ( request , "Content-Type" , "application/x-www-form-urlencoded" ); hangouts_set_auth_headers ( ha , request ); postdata = g_string_new ( NULL ); map_list_len = json_array_get_length ( map_list ); g_string_append_printf ( postdata , "count=%u&" , map_list_len ); g_string_append ( postdata , "ofs=0&" ); for ( i = 0 ; i < map_list_len ; i ++ ) { JsonObject * obj = json_array_get_object_element ( map_list , i ); GList * members = json_object_get_members ( obj ); for ( l = members ; l != NULL ; l = l -> next ) { const gchar * member_name = l -> data ; JsonNode * value = json_object_get_member ( obj , member_name ); g_string_append_printf ( postdata , "req%u_%s=" , i , purple_url_encode ( member_name )); g_string_append_printf ( postdata , "%s&" , purple_url_encode ( json_node_get_string ( value ))); purple_http_request_set_contents ( request , postdata -> str , postdata -> len ); purple_http_request ( ha -> pc , request , send_maps_callback , ha ); purple_http_request_unref ( request ); g_string_free ( postdata , TRUE ); g_string_free ( url , TRUE ); hangouts_add_channel_services ( HangoutsAccount * ha ) JsonArray * map_list = json_array_new (); // TODO Work out what this is for json_object_set_string_member ( obj , "p" , "{ \" 3 \" :{ \" 1 \" :{ \" 1 \" : \" tango_service \" }}}" ); json_array_add_object_element ( map_list , obj ); // This is for the chat messages json_object_set_string_member ( obj , "p" , "{ \" 3 \" :{ \" 1 \" :{ \" 1 \" : \" babel \" }}}" ); json_array_add_object_element ( map_list , obj ); // This is for the presence updates json_object_set_string_member ( obj , "p" , "{ \" 3 \" :{ \" 1 \" :{ \" 1 \" : \" babel_presence_last_seen \" }}}" ); json_array_add_object_element ( map_list , obj ); // TODO Work out what this is for json_object_set_string_member ( obj , "p" , "{ \" 3 \" :{ \" 1 \" :{ \" 1 \" : \" hangout_invite \" }}}" ); json_array_add_object_element ( map_list , obj ); json_object_set_string_member ( obj , "p" , "{ \" 3 \" :{ \" 1 \" :{ \" 1 \" : \" gmail \" }}}" ); json_array_add_object_element ( map_list , obj ); hangouts_send_maps ( ha , map_list , NULL ); json_array_unref ( map_list ); HangoutsPbliteResponseFunc callback ; ProtobufCMessage * response_message ; } LazyPblistRequestStore ; hangouts_pblite_request_cb ( PurpleHttpConnection * http_conn , PurpleHttpResponse * response , gpointer user_data ) LazyPblistRequestStore * request_info = user_data ; HangoutsAccount * ha = request_info -> ha ; HangoutsPbliteResponseFunc callback = request_info -> callback ; gpointer real_user_data = request_info -> user_data ; ProtobufCMessage * response_message = request_info -> response_message ; ProtobufCMessage * unpacked_message ; const gchar * raw_response ; guchar * decoded_response ; const gchar * content_type ; if ( purple_http_response_get_error ( response ) != NULL ) { g_free ( response_message ); purple_debug_error ( "hangouts" , "Error from server: (%s) %s \n " , purple_http_response_get_error ( response ), purple_http_response_get_data ( response , NULL )); return ; //TODO should we send NULL to the callee? raw_response = purple_http_response_get_data ( response , NULL ); content_type = purple_http_response_get_header ( response , "X-Goog-Safety-Content-Type" ); if ( g_strcmp0 ( content_type , "application/x-protobuf" ) == 0 ) { decoded_response = g_base64_decode ( raw_response , & response_len ); unpacked_message = protobuf_c_message_unpack ( response_message -> descriptor , NULL , response_len , decoded_response ); if ( unpacked_message != NULL ) { if ( purple_debug_is_verbose ()) { gchar * pretty_json = pblite_dump_json ( unpacked_message ); purple_debug_misc ( "hangouts" , "Response: %s" , pretty_json ); callback ( ha , unpacked_message , real_user_data ); protobuf_c_message_free_unpacked ( unpacked_message , NULL ); purple_debug_error ( "hangouts" , "Error decoding protobuf! \n " ); gchar * tidied_json = hangouts_json_tidy_blank_arrays ( raw_response ); JsonArray * response_array = json_decode_array ( tidied_json , -1 ); const gchar * first_element = json_array_get_string_element ( response_array , 0 ); gboolean ignore_first_element = ( first_element != NULL ); pblite_decode ( response_message , response_array , ignore_first_element ); if ( ignore_first_element ) { purple_debug_info ( "hangouts" , "A '%s' says '%s' \n " , response_message -> descriptor -> name , first_element ); if ( purple_debug_is_verbose ()) { gchar * pretty_json = pblite_dump_json ( response_message ); purple_debug_misc ( "hangouts" , "Response: %s" , pretty_json ); callback ( ha , response_message , real_user_data ); json_array_unref ( response_array ); g_free ( response_message ); hangouts_client6_request ( HangoutsAccount * ha , const gchar * path , HangoutsContentType request_type , const gchar * request_data , gssize request_len , HangoutsContentType response_type , PurpleHttpCallback callback , gpointer user_data ) PurpleHttpRequest * request ; PurpleHttpConnection * connection ; const gchar * response_type_str ; case HANGOUTS_CONTENT_TYPE_NONE : case HANGOUTS_CONTENT_TYPE_JSON : response_type_str = "json" ; case HANGOUTS_CONTENT_TYPE_PBLITE : response_type_str = "protojson" ; case HANGOUTS_CONTENT_TYPE_PROTOBUF : response_type_str = "proto" ; request = purple_http_request_new ( NULL ); purple_http_request_set_url_printf ( request , HANGOUTS_PBLITE_API_URL "%s%ckey=" GOOGLE_GPLUS_KEY "&alt=%s" , path , ( strchr ( path , '?' ) ? '&' : '?' ), response_type_str ); purple_http_request_set_cookie_jar ( request , ha -> cookie_jar ); purple_http_request_set_keepalive_pool ( request , ha -> client6_keepalive_pool ); purple_http_request_set_max_len ( request , G_MAXINT32 - 1 ); purple_http_request_header_set ( request , "X-Goog-Encode-Response-If-Executable" , "base64" ); if ( request_type != HANGOUTS_CONTENT_TYPE_NONE ) { purple_http_request_set_method ( request , "POST" ); purple_http_request_set_contents ( request , request_data , request_len ); if ( request_type == HANGOUTS_CONTENT_TYPE_PROTOBUF ) { purple_http_request_header_set ( request , "Content-Type" , "application/x-protobuf" ); } else if ( request_type == HANGOUTS_CONTENT_TYPE_PBLITE ) { purple_http_request_header_set ( request , "Content-Type" , "application/json+protobuf" ); } else if ( request_type == HANGOUTS_CONTENT_TYPE_JSON ) { purple_http_request_header_set ( request , "Content-Type" , "application/json" ); hangouts_set_auth_headers ( ha , request ); connection = purple_http_request ( ha -> pc , request , callback , user_data ); purple_http_request_unref ( request ); hangouts_pblite_request ( HangoutsAccount * ha , const gchar * endpoint , ProtobufCMessage * request_message , HangoutsPbliteResponseFunc callback , ProtobufCMessage * response_message , gpointer user_data ) LazyPblistRequestStore * request_info = g_new0 ( LazyPblistRequestStore , 1 ); JsonArray * request_encoded = pblite_encode ( request_message ); JsonNode * node = json_node_new ( JSON_NODE_ARRAY ); json_node_take_array ( node , request_encoded ); request_data = json_encode ( node , & request_len ); request_info -> callback = callback ; request_info -> response_message = response_message ; request_info -> user_data = user_data ; if ( purple_debug_is_verbose ()) { gchar * pretty_json = pblite_dump_json ( request_message ); purple_debug_misc ( "hangouts" , "Request: %s" , pretty_json ); hangouts_client6_request ( ha , endpoint , HANGOUTS_CONTENT_TYPE_PBLITE , request_data , request_len , HANGOUTS_CONTENT_TYPE_PBLITE , hangouts_pblite_request_cb , request_info ); hangouts_default_response_dump ( HangoutsAccount * ha , ProtobufCMessage * response , gpointer user_data ) gchar * dump = pblite_dump_json ( response ); purple_debug_info ( "hangouts" , "%s \n " , dump ); hangouts_set_active_client ( PurpleConnection * pc ) SetActiveClientRequest request ; switch ( purple_connection_get_state ( pc )) { case PURPLE_CONNECTION_DISCONNECTED : // I couldn't eat another bite case PURPLE_CONNECTION_CONNECTING : // Come back for more later ha = purple_connection_get_protocol_data ( pc ); if ( ha -> active_client_state == ACTIVE_CLIENT_STATE__ACTIVE_CLIENT_STATE_IS_ACTIVE ) { //We're already the active client if ( ha -> idle_time > HANGOUTS_ACTIVE_CLIENT_TIMEOUT ) { if ( ! purple_presence_is_status_primitive_active ( purple_account_get_presence ( ha -> account ), PURPLE_STATUS_AVAILABLE )) { //We're marked as not available somehow ha -> active_client_state = ACTIVE_CLIENT_STATE__ACTIVE_CLIENT_STATE_IS_ACTIVE ; set_active_client_request__init ( & request ); request . request_header = hangouts_get_request_header ( ha ); request . has_is_active = TRUE ; request . is_active = TRUE ; request . full_jid = g_strdup_printf ( "%s/%s" , purple_account_get_username ( ha -> account ), ha -> client_id ); request . has_timeout_secs = TRUE ; request . timeout_secs = HANGOUTS_ACTIVE_CLIENT_TIMEOUT ; hangouts_pblite_set_active_client ( ha , & request , ( HangoutsPbliteSetActiveClientResponseFunc ) hangouts_default_response_dump , NULL ); hangouts_request_header_free ( request . request_header ); g_free ( request . full_jid ); hangouts_search_results_send_im ( PurpleConnection * pc , GList * row , void * user_data ) PurpleAccount * account = purple_connection_get_account ( pc ); const gchar * who = g_list_nth_data ( row , 0 ); PurpleIMConversation * imconv ; imconv = purple_conversations_find_im_with_account ( who , account ); imconv = purple_im_conversation_new ( account , who ); purple_conversation_present ( PURPLE_CONVERSATION ( imconv )); hangouts_search_results_get_info ( PurpleConnection * pc , GList * row , void * user_data ) hangouts_get_info ( pc , g_list_nth_data ( row , 0 )); hangouts_search_results_add_buddy ( PurpleConnection * pc , GList * row , void * user_data ) PurpleAccount * account = purple_connection_get_account ( pc ); if ( ! purple_blist_find_buddy ( account , g_list_nth_data ( row , 0 ))) purple_blist_request_add_buddy ( account , g_list_nth_data ( row , 0 ), "Hangouts" , g_list_nth_data ( row , 1 )); hangouts_search_users_text_cb ( PurpleHttpConnection * connection , PurpleHttpResponse * response , gpointer user_data ) HangoutsAccount * ha = user_data ; const gchar * response_data ; PurpleNotifySearchResults * results ; PurpleNotifySearchColumn * column ; if ( purple_http_response_get_error ( response ) != NULL ) { purple_notify_error ( ha -> pc , _ ( "Search Error" ), _ ( "There was an error searching for the user" ), purple_http_response_get_error ( response ), purple_request_cpar_from_connection ( ha -> pc )); g_dataset_destroy ( connection ); response_data = purple_http_response_get_data ( response , & response_size ); node = json_decode_object ( response_data , response_size ); search_term = g_dataset_get_data ( connection , "search_term" ); resultsarray = json_object_get_array_member ( node , "results" ); length = json_array_get_length ( resultsarray ); status = json_object_get_object_member ( node , "status" ); if ( ! json_object_has_member ( status , "personalResultsNotReady" ) || json_object_get_boolean_member ( status , "personalResultsNotReady" ) == TRUE ) { hangouts_search_users_text ( ha , search_term ); gchar * primary_text = g_strdup_printf ( _ ( "Your search for the user \" %s \" returned no results" ), search_term ); purple_notify_warning ( ha -> pc , _ ( "No users found" ), primary_text , "" , purple_request_cpar_from_connection ( ha -> pc )); g_dataset_destroy ( connection ); results = purple_notify_searchresults_new (); g_dataset_destroy ( connection ); /* columns: Friend ID, Name, Network */ column = purple_notify_searchresults_column_new ( _ ( "ID" )); purple_notify_searchresults_column_add ( results , column ); column = purple_notify_searchresults_column_new ( _ ( "Display Name" )); purple_notify_searchresults_column_add ( results , column ); purple_notify_searchresults_button_add ( results , PURPLE_NOTIFY_BUTTON_ADD , hangouts_search_results_add_buddy ); purple_notify_searchresults_button_add ( results , PURPLE_NOTIFY_BUTTON_INFO , hangouts_search_results_get_info ); purple_notify_searchresults_button_add ( results , PURPLE_NOTIFY_BUTTON_IM , hangouts_search_results_send_im ); for ( index = 0 ; index < length ; index ++ ) JsonNode * result = json_array_get_element ( resultsarray , index ); gchar * id = hangouts_json_path_query_string ( result , "$.person.personId" , NULL ); gchar * displayname = hangouts_json_path_query_string ( result , "$.person.name[*].displayName" , NULL ); row = g_list_append ( row , id ); row = g_list_append ( row , displayname ); purple_notify_searchresults_row_add ( results , row ); purple_notify_searchresults ( ha -> pc , NULL , search_term , NULL , results , NULL , NULL ); g_dataset_destroy ( connection ); POST https://people-pa.clients6.google.com/v2/people/lookup id=actual_email_address%40gmail.com&type=EMAIL&matchType=LENIENT&requestMask.includeField.paths=person.email&requestMask.includeField.paths=person.gender&requestMask.includeField.paths=person.in_app_reachability&requestMask.includeField.paths=person.metadata&requestMask.includeField.paths=person.name&requestMask.includeField.paths=person.phone&requestMask.includeField.paths=person.photo&requestMask.includeField.paths=person.read_only_profile_info&extensionSet.extensionNames=HANGOUTS_ADDITIONAL_DATA&extensionSet.extensionNames=HANGOUTS_OFF_NETWORK_GAIA_LOOKUP&extensionSet.extensionNames=HANGOUTS_PHONE_DATA&coreIdParams.useRealtimeNotificationExpandedAcls=true&key=AIzaSyAfFJCeph-euFSwtmqFZi0kaKk-cZ5wufM id=%2B123456789&type=PHONE&matchType=LENIENT&requestMask.includeField.paths=person.email&requestMask.includeField.paths=person.gender&requestMask.includeField.paths=person.in_app_reachability&requestMask.includeField.paths=person.metadata&requestMask.includeField.paths=person.name&requestMask.includeField.paths=person.phone&requestMask.includeField.paths=person.photo&requestMask.includeField.paths=person.read_only_profile_info&extensionSet.extensionNames=HANGOUTS_ADDITIONAL_DATA&extensionSet.extensionNames=HANGOUTS_OFF_NETWORK_GAIA_LOOKUP&extensionSet.extensionNames=HANGOUTS_PHONE_DATA&coreIdParams.useRealtimeNotificationExpandedAcls=true"aFilterType=PHONE&key=AIzaSyAfFJCeph-euFSwtmqFZi0kaKk-cZ5wufM hangouts_search_users_text ( HangoutsAccount * ha , const gchar * text ) PurpleHttpRequest * request ; GString * url = g_string_new ( "https://people-pa.clients6.google.com/v2/people/autocomplete?" ); PurpleHttpConnection * connection ; g_string_append_printf ( url , "query=%s&" , purple_url_encode ( text )); g_string_append ( url , "client=HANGOUTS_WITH_DATA&" ); g_string_append ( url , "pageSize=20&" ); g_string_append_printf ( url , "key=%s&" , purple_url_encode ( GOOGLE_GPLUS_KEY )); request = purple_http_request_new ( NULL ); purple_http_request_set_cookie_jar ( request , ha -> cookie_jar ); purple_http_request_set_url ( request , url -> str ); hangouts_set_auth_headers ( ha , request ); connection = purple_http_request ( ha -> pc , request , hangouts_search_users_text_cb , ha ); purple_http_request_unref ( request ); g_dataset_set_data_full ( connection , "search_term" , g_strdup ( text ), g_free ); g_string_free ( url , TRUE ); hangouts_search_users ( PurpleProtocolAction * action ) PurpleConnection * pc = purple_protocol_action_get_connection ( action ); HangoutsAccount * ha = purple_connection_get_protocol_data ( pc ); purple_request_input ( pc , _ ( "Search for friends..." ), _ ( "Search for friends..." ), NULL , FALSE , FALSE , NULL , _ ( "_Search" ), G_CALLBACK ( hangouts_search_users_text ), purple_request_cpar_from_connection ( pc ),