From e3c0c006f5e87f16060b09b59f24e02a3faccf44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20P=C3=B6sel?= Date: Sat, 10 May 2014 17:38:54 +0000 Subject: Facebook: Fix not working login for some people git-svn-id: http://svn.miranda-ng.org/main/trunk@9156 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- protocols/FacebookRM/src/communication.cpp | 2916 ++++++++++++++-------------- protocols/FacebookRM/src/contacts.cpp | 836 ++++---- 2 files changed, 1878 insertions(+), 1874 deletions(-) (limited to 'protocols/FacebookRM/src') diff --git a/protocols/FacebookRM/src/communication.cpp b/protocols/FacebookRM/src/communication.cpp index 5b44a7e9f1..6eb267b1e0 100644 --- a/protocols/FacebookRM/src/communication.cpp +++ b/protocols/FacebookRM/src/communication.cpp @@ -1,1456 +1,1460 @@ -/* - -Facebook plugin for Miranda Instant Messenger -_____________________________________________ - -Copyright © 2009-11 Michal Zelinka, 2011-13 Robert Pösel - -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 2 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 . - -*/ - -#include "common.h" - -void facebook_client::client_notify(TCHAR* message) -{ - parent->NotifyEvent(parent->m_tszUserName, message, NULL, FACEBOOK_EVENT_CLIENT); -} - -http::response facebook_client::flap(RequestType request_type, std::string* request_data, std::string* request_get_data, int method) -{ - NETLIBHTTPREQUEST nlhr = {sizeof(NETLIBHTTPREQUEST)}; - nlhr.requestType = !method ? choose_method(request_type) : method; - - std::string url = choose_proto(request_type); - url.append(choose_server(request_type, request_data, request_get_data)); - url.append(choose_action(request_type, request_data, request_get_data)); - - nlhr.szUrl = (char*)url.c_str(); - nlhr.flags = NLHRF_HTTP11 | choose_security_level(request_type); - nlhr.headers = get_request_headers(nlhr.requestType, &nlhr.headersCount); - - #ifdef _DEBUG - nlhr.flags |= NLHRF_DUMPASTEXT; - #else - nlhr.flags |= NLHRF_NODUMP; - #endif - - switch (request_type) - { - case REQUEST_MESSAGES_RECEIVE: - nlhr.timeout = 1000 * 65; break; - default: - nlhr.timeout = 1000 * 20; break; - } - - if (request_data != NULL) - { - nlhr.pData = (char*)(*request_data).c_str(); - nlhr.dataLength = (int)request_data->length(); - } - - parent->debugLogA("@@@@@ Sending request to '%s'", nlhr.szUrl); - - switch (request_type) - { - case REQUEST_LOGIN: - nlhr.nlc = NULL; - break; - - case REQUEST_MESSAGES_RECEIVE: - nlhr.nlc = hMsgCon; - nlhr.flags |= NLHRF_PERSISTENT; - break; - - default: - WaitForSingleObject(fcb_conn_lock_, INFINITE); - nlhr.nlc = hFcbCon; - nlhr.flags |= NLHRF_PERSISTENT; - break; - } - - NETLIBHTTPREQUEST* pnlhr = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)handle_, (LPARAM)&nlhr); - - mir_free(nlhr.headers[3].szValue); - mir_free(nlhr.headers); - - http::response resp; - - switch (request_type) - { - case REQUEST_LOGIN: - case REQUEST_SETUP_MACHINE: - break; - - case REQUEST_MESSAGES_RECEIVE: - hMsgCon = pnlhr ? pnlhr->nlc : NULL; - break; - - default: - ReleaseMutex(fcb_conn_lock_); - hFcbCon = pnlhr ? pnlhr->nlc : NULL; - break; - } - - if (pnlhr != NULL) - { - parent->debugLogA("@@@@@ Got response with code %d", pnlhr->resultCode); - store_headers(&resp, pnlhr->headers, pnlhr->headersCount); - resp.code = pnlhr->resultCode; - resp.data = pnlhr->pData ? pnlhr->pData : ""; - - CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)pnlhr); - } else { - parent->debugLogA("!!!!! No response from server (time-out)"); - resp.code = HTTP_CODE_FAKE_DISCONNECTED; - // Better to have something set explicitely as this value is compaired in all communication requests - } - - // Get Facebook's error message - if (resp.code == HTTP_CODE_OK) { - std::string::size_type pos = resp.data.find("\"error\":"); - if (pos != std::string::npos) { - pos += 8; - int error_num = atoi(resp.data.substr(pos, resp.data.find(",", pos) - pos).c_str()); - if (error_num != 0) { - std::string error = ""; - - pos = resp.data.find("\"errorDescription\":\"", pos); - if (pos != std::string::npos) { - pos += 20; - error = resp.data.substr(pos, resp.data.find("\"", pos) - pos); - error = utils::text::trim(utils::text::special_expressions_decode(utils::text::slashu_to_utf8(error))); - error = ptrA( mir_utf8decodeA(error.c_str())); - } - - std::string title = ""; - pos = resp.data.find("\"errorSummary\":\"", pos); - if (pos != std::string::npos) { - pos += 16; - title = resp.data.substr(pos, resp.data.find("\"", pos) - pos); - title = utils::text::trim(utils::text::special_expressions_decode(utils::text::slashu_to_utf8(title))); - title = ptrA( mir_utf8decodeA(title.c_str())); - } - - bool silent = resp.data.find("\"silentError\":1") != std::string::npos; - - resp.error_number = error_num; - resp.error_text = error; - resp.error_title = title; - resp.code = HTTP_CODE_FAKE_ERROR; - - parent->debugLogA(" ! ! Received Facebook error: %d -- %s", error_num, error.c_str()); - if (notify_errors(request_type) && !silent) - client_notify(_A2T(error.c_str())); - } - } - } - - return resp; -} - -bool facebook_client::handle_entry(std::string method) -{ - parent->debugLogA(" >> Entering %s()", method.c_str()); - return true; -} - -bool facebook_client::handle_success(std::string method) -{ - parent->debugLogA(" << Quitting %s()", method.c_str()); - reset_error(); - return true; -} - -bool facebook_client::handle_error(std::string method, int action) -{ - increment_error(); - parent->debugLogA("!!!!! %s(): Something with Facebook went wrong", method.c_str()); - - bool result = (error_count_ <= (UINT)parent->getByte(FACEBOOK_KEY_TIMEOUTS_LIMIT, FACEBOOK_TIMEOUTS_LIMIT)); - if (action == FORCE_DISCONNECT || action == FORCE_QUIT) - result = false; - - if (!result) { - reset_error(); - - if (action != FORCE_QUIT) - parent->SetStatus(ID_STATUS_OFFLINE); - } - - return result; -} - -////////////////////////////////////////////////////////////////////////////// - -DWORD facebook_client::choose_security_level(RequestType request_type) -{ - if (this->https_) - if (request_type != REQUEST_MESSAGES_RECEIVE || parent->getByte(FACEBOOK_KEY_FORCE_HTTPS_CHANNEL, DEFAULT_FORCE_HTTPS_CHANNEL)) - return NLHRF_SSL; - - switch (request_type) { - case REQUEST_LOGIN: - case REQUEST_SETUP_MACHINE: - return NLHRF_SSL; - -// case REQUEST_LOGOUT: -// case REQUEST_HOME: -// case REQUEST_DTSG: -// case REQUEST_BUDDY_LIST: -// case REQUEST_LOAD_FRIEND: -// case REQUEST_LOAD_FRIENDS: -// case REQUEST_USER_INFO: -// case REQUEST_LOAD_REQUESTS: -// case REQUEST_SEARCH: -// case REQUEST_DELETE_FRIEND: -// case REQUEST_REQUEST_FRIEND: -// case REQUEST_APPROVE_FRIEND: -// case REQUEST_CANCEL_REQUEST: -// case REQUEST_FEEDS: -// case REQUEST_PAGES: -// case REQUEST_NOTIFICATIONS: -// case REQUEST_RECONNECT: -// case REQUEST_POST_STATUS: -// case REQUEST_IDENTITY_SWITCH: -// case REQUEST_STATUS_COMPOSER: -// case REQUEST_LINK_SCRAPER: -// case REQUEST_MESSAGE_SEND: -// case REQUEST_MESSAGE_SEND2: -// case REQUEST_THREAD_INFO: -// case REQUEST_MESSAGES_RECEIVE: -// case REQUEST_VISIBILITY: -// case REQUEST_POKE: -// case REQUEST_ASYNC: -// case REQUEST_MARK_READ: -// case REQUEST_NOTIFICATIONS_READ: -// case REQUEST_UNREAD_THREADS: -// case REQUEST_TYPING_SEND: - default: - return (DWORD)0; - } -} - -int facebook_client::choose_method(RequestType request_type) -{ - switch (request_type) - { - case REQUEST_LOGIN: - case REQUEST_SETUP_MACHINE: - case REQUEST_BUDDY_LIST: - case REQUEST_POST_STATUS: - case REQUEST_IDENTITY_SWITCH: - case REQUEST_STATUS_COMPOSER: - case REQUEST_LINK_SCRAPER: - case REQUEST_MESSAGE_SEND: - case REQUEST_MESSAGE_SEND2: - case REQUEST_THREAD_INFO: - case REQUEST_VISIBILITY: - case REQUEST_POKE: - case REQUEST_ASYNC: - case REQUEST_MARK_READ: - case REQUEST_NOTIFICATIONS_READ: - case REQUEST_TYPING_SEND: - case REQUEST_LOGOUT: - case REQUEST_DELETE_FRIEND: - case REQUEST_REQUEST_FRIEND: - case REQUEST_APPROVE_FRIEND: - case REQUEST_CANCEL_REQUEST: - case REQUEST_UNREAD_THREADS: - return REQUEST_POST; - -// case REQUEST_HOME: -// case REQUEST_DTSG: -// case REQUEST_MESSAGES_RECEIVE: -// case REQUEST_FEEDS: -// case REQUEST_PAGES: -// case REQUEST_NOTIFICATIONS: -// case REQUEST_RECONNECT: -// case REQUEST_LOAD_FRIEND: -// case REQUEST_LOAD_FRIENDS: -// case REQUEST_USER_INFO: -// case REQUEST_LOAD_REQUESTS: -// case REQUEST_SEARCH: - default: - return REQUEST_GET; - } -} - -std::string facebook_client::choose_proto(RequestType request_type) -{ - if (choose_security_level(request_type) == NLHRF_SSL) - return HTTP_PROTO_SECURE; - else - return HTTP_PROTO_REGULAR; -} - -std::string facebook_client::choose_server(RequestType request_type, std::string* data, std::string* get_data) -{ - switch (request_type) - { - case REQUEST_LOGIN: - return FACEBOOK_SERVER_LOGIN; - - case REQUEST_MESSAGES_RECEIVE: - { - std::string server = FACEBOOK_SERVER_CHAT; - utils::text::replace_first(&server, "%s", this->chat_conn_num_.empty() ? "0" : this->chat_conn_num_); - utils::text::replace_first(&server, "%s", this->chat_channel_host_); - return server; - } - - case REQUEST_HOME: - case REQUEST_DTSG: - case REQUEST_APPROVE_FRIEND: - case REQUEST_LOAD_REQUESTS: - case REQUEST_SEARCH: - case REQUEST_USER_INFO: - return FACEBOOK_SERVER_MOBILE; - -// case REQUEST_LOGOUT: -// case REQUEST_BUDDY_LIST: -// case REQUEST_LOAD_FRIEND: -// case REQUEST_LOAD_FRIENDS: -// case REQUEST_FEEDS: -// case REQUEST_PAGES: -// case REQUEST_NOTIFICATIONS: -// case REQUEST_RECONNECT: -// case REQUEST_POST_STATUS: -// case REQUEST_IDENTITY_SWITCH: -// case REQUEST_STATUS_COMPOSER: -// case REQUEST_LINK_SCRAPER: -// case REQUEST_MESSAGE_SEND: -// case REQUEST_MESSAGE_SEND2: -// case REQUEST_THREAD_INFO: -// case REQUEST_VISIBILITY: -// case REQUEST_POKE: -// case REQUEST_ASYNC: -// case REQUEST_MARK_READ: -// case REQUEST_NOTIFICATIONS_READ: -// case REQUEST_TYPING_SEND: -// case REQUEST_SETUP_MACHINE: -// case REQUEST_DELETE_FRIEND: -// case REQUEST_REQUEST_FRIEND: -// case REQUEST_CANCEL_REQUEST: -// case REQUEST_UNREAD_THREADS: - default: - return FACEBOOK_SERVER_REGULAR; - } -} - -std::string facebook_client::choose_action(RequestType request_type, std::string* data, std::string* get_data) -{ - switch (request_type) - { - case REQUEST_LOGIN: - return "/login.php?login_attempt=1"; - - case REQUEST_SETUP_MACHINE: - return "/checkpoint/?next"; - - case REQUEST_LOGOUT: - return "/logout.php"; - - case REQUEST_HOME: - return "/profile.php?v=info"; - - case REQUEST_DTSG: - return "/editprofile.php?edit=current_city&type=basic"; - - case REQUEST_BUDDY_LIST: - return "/ajax/chat/buddy_list.php?__a=1"; - - case REQUEST_LOAD_FRIEND: - { - std::string action = "/ajax/chat/user_info.php?__a=1&viewer=%s&__user=%s"; - utils::text::replace_all(&action, "%s", self_.user_id); - if (get_data != NULL) { - action += "&" + (*get_data); - } - return action; - } - - case REQUEST_LOAD_FRIENDS: - { - std::string action = "/ajax/chat/user_info_all.php?__a=1&viewer=%s&__user=%s"; - utils::text::replace_all(&action, "%s", self_.user_id); - return action; - } - - case REQUEST_USER_INFO: - { - std::string action = "/%sv=info"; - if (get_data != NULL) { - utils::text::replace_all(&action, "%s", *get_data); - } - return action; - } - - case REQUEST_LOAD_REQUESTS: - { - return "/friends/"; - } - - case REQUEST_SEARCH: - { - std::string action = "/search/?search=people&query="; - if (get_data != NULL) { - action += *get_data; - } - return action; - } - - case REQUEST_UNREAD_THREADS: - { - return "/ajax/mercury/unread_threads.php?__a=1"; - } - - case REQUEST_DELETE_FRIEND: - { - std::string action = "/ajax/profile/removefriendconfirm.php?__a=1"; - if (get_data != NULL) { - action += *get_data; - } - return action; - } - - case REQUEST_REQUEST_FRIEND: - { - return "/ajax/add_friend/action.php?__a=1"; - } - - case REQUEST_APPROVE_FRIEND: - { - std::string action = "/a/notifications.php?__a=1"; - if (get_data != NULL) { - action += "&" + (*get_data); - } - return action; - } - - case REQUEST_CANCEL_REQUEST: - { - return "/ajax/friends/requests/cancel.php?__a=1"; - } - - case REQUEST_FEEDS: - { - std::string action = "/ajax/intent.php?filter="; - action += get_newsfeed_type(); - action += "&request_type=4&__a=1&newest=%s&ignore_self=true&load_newer=true&__user=%s"; - std::string newest = utils::conversion::to_string((void*)&this->last_feeds_update_, UTILS_CONV_TIME_T); - utils::text::replace_first(&action, "%s", newest); - utils::text::replace_first(&action, "%s", self_.user_id); - return action; - } - - case REQUEST_PAGES: - { - return "/bookmarks/pages"; - } - - case REQUEST_NOTIFICATIONS: - { - std::string action = "/ajax/notifications/get.php?__a=1&user=%s&time=0&version=2&__user=%s"; - // TODO: use better format notifications request - // std::string action = "/ajax/notifications/client/get.php?__a=1&user=%s&time=0&version=2&__user=%s"; - utils::text::replace_all(&action, "%s", self_.user_id); - return action; - } - - case REQUEST_RECONNECT: - { - std::string action = "/ajax/presence/reconnect.php?__a=1&reason=%s&fb_dtsg=%s&__user=%s"; - - if (this->chat_reconnect_reason_.empty()) - this->chat_reconnect_reason_ = "6"; - - utils::text::replace_first(&action, "%s", this->chat_reconnect_reason_); - utils::text::replace_first(&action, "%s", this->dtsg_); - utils::text::replace_first(&action, "%s", this->self_.user_id); - return action; - } - - case REQUEST_POST_STATUS: - return "/ajax/updatestatus.php?__a=1"; - - case REQUEST_IDENTITY_SWITCH: - return "/identity_switch.php?__a=1"; - - case REQUEST_STATUS_COMPOSER: - return "/ajax/profile/composer.php?__a=1"; - - case REQUEST_LINK_SCRAPER: - { - std::string action = "/ajax/composerx/attachment/link/scraper/?__a=1&composerurihash=2&scrape_url="; - if (get_data != NULL) { - action += utils::url::encode(*get_data); - } - return action; - } - - case REQUEST_MESSAGE_SEND: - return "/ajax/mercury/send_messages.php?__a=1"; - - case REQUEST_MESSAGE_SEND2: - return "/ajax/messaging/send.php"; - - case REQUEST_THREAD_INFO: - return "/ajax/mercury/thread_info.php?__a=1"; - - case REQUEST_MESSAGES_RECEIVE: - { - std::string action = "/pull?channel=" + (this->chat_channel_.empty() ? "p_" + self_.user_id : this->chat_channel_); - action += "&seq=" + (this->chat_sequence_num_.empty() ? "0" : this->chat_sequence_num_); - action += "&partition=" + (this->chat_channel_partition_.empty() ? "0" : this->chat_channel_partition_); - action += "&clientid=" + this->chat_clientid_; - action += "&cb=" + utils::text::rand_string(4, "0123456789abcdefghijklmnopqrstuvwxyz"); - action += "&idle=0&state=active"; - if (!this->chat_sticky_num_.empty()) - action += "&sticky_token=" + this->chat_sticky_num_; - - return action; - } - - case REQUEST_VISIBILITY: - return "/ajax/chat/privacy/visibility.php?__a=1"; - - case REQUEST_POKE: - return "/pokes/dialog/?__a=1"; - - case REQUEST_ASYNC: - { - std::string action = "/ajax/messaging/async.php?__a=1"; - if (get_data != NULL) { - action += "&" + (*get_data); - } - return action; - } - - case REQUEST_MARK_READ: - return "/ajax/mercury/change_read_status.php?__a=1"; - - case REQUEST_NOTIFICATIONS_READ: - { - std::string action = "/ajax/notifications/mark_read.php?__a=1"; - if (get_data != NULL) { - action += "&" + (*get_data); - } - return action; - } - - case REQUEST_TYPING_SEND: - return "/ajax/messaging/typ.php?__a=1"; - - default: - return "/?_fb_noscript=1"; - } -} - -bool facebook_client::notify_errors(RequestType request_type) -{ - switch (request_type) - { - case REQUEST_BUDDY_LIST: - case REQUEST_MESSAGE_SEND2: - case REQUEST_MESSAGE_SEND: - case REQUEST_ASYNC: - return false; - - default: - return true; - } -} - -NETLIBHTTPHEADER* facebook_client::get_request_headers(int request_type, int* headers_count) -{ - if (request_type == REQUEST_POST) - *headers_count = 5; - else - *headers_count = 4; - - NETLIBHTTPHEADER* headers = (NETLIBHTTPHEADER*)mir_calloc(sizeof(NETLIBHTTPHEADER)*(*headers_count)); - - if (request_type == REQUEST_POST) - { - headers[4].szName = "Content-Type"; - headers[4].szValue = "application/x-www-form-urlencoded; charset=utf-8"; - } - - headers[3].szName = "Cookie"; - headers[3].szValue = load_cookies(); - headers[2].szName = "User-Agent"; - headers[2].szValue = (char *)g_strUserAgent.c_str(); - headers[1].szName = "Accept"; - headers[1].szValue = "*/*"; - headers[0].szName = "Accept-Language"; - headers[0].szValue = "en,en-US;q=0.9"; - - return headers; -} - -std::string facebook_client::get_newsfeed_type() -{ - BYTE feed_type = parent->getByte(FACEBOOK_KEY_FEED_TYPE, 0); - if (feed_type >= SIZEOF(feed_types)) - feed_type = 0; - return feed_types[feed_type].id; -} - -std::string facebook_client::get_server_type() -{ - BYTE server_type = parent->getByte(FACEBOOK_KEY_SERVER_TYPE, 0); - if (server_type >= SIZEOF(server_types)) - server_type = 0; - return server_types[server_type].id; -} - -std::string facebook_client::get_privacy_type() -{ - BYTE privacy_type = parent->getByte(FACEBOOK_KEY_PRIVACY_TYPE, 0); - if (privacy_type >= SIZEOF(privacy_types)) - privacy_type = 0; - return privacy_types[privacy_type].id; -} - - -char* facebook_client::load_cookies() -{ - ScopedLock s(cookies_lock_); - - std::string cookieString = "isfbe=false;"; - - if (!cookies.empty()) - for (std::map< std::string, std::string >::iterator iter = cookies.begin(); iter != cookies.end(); ++iter) - { - cookieString.append(iter->first); - cookieString.append(1, '='); - cookieString.append(iter->second); - cookieString.append(1, ';'); - } - - return mir_strdup(cookieString.c_str()); -} - -void facebook_client::store_headers(http::response* resp, NETLIBHTTPHEADER* headers, int headersCount) -{ - ScopedLock c(cookies_lock_); - - for (int i = 0; i < headersCount; i++) - { - std::string header_name = headers[i].szName; // TODO: Casting? - std::string header_value = headers[i].szValue; // TODO: Casting? - - if (header_name == "Set-Cookie") - { - std::string cookie_name = header_value.substr(0, header_value.find("=")); - std::string cookie_value = header_value.substr(header_value.find("=") + 1, header_value.find(";") - header_value.find("=") - 1); - if (cookie_value == "deleted") - { - parent->debugLogA(" Deleted cookie '%s'", cookie_name.c_str()); - cookies.erase(cookie_name); - } else { - parent->debugLogA(" New cookie '%s'", cookie_name.c_str()); - cookies[cookie_name] = cookie_value; - } - } - else - { // TODO RM: (un)comment - //parent->debugLogA("----- Got header '%s': %s", header_name.c_str(), header_value.c_str()); - resp->headers[header_name] = header_value; - } - } -} - -void facebook_client::clear_cookies() -{ - ScopedLock s(cookies_lock_); - - if (!cookies.empty()) - cookies.clear(); -} - -void facebook_client::clear_notifications() -{ - for (std::map::iterator it = notifications.begin(); it != notifications.end(); ) { - if (it->second->hWndPopup != NULL) - PUDeletePopup(it->second->hWndPopup); // close popup - - delete it->second; - it = notifications.erase(it); - } - - notifications.clear(); -} - -void loginError(FacebookProto *proto, std::string error_str) { - error_str = utils::text::trim( - utils::text::special_expressions_decode( - utils::text::remove_html( - utils::text::edit_html(error_str)))); - - proto->debugLogA(" ! ! Login error: %s", error_str.length() ? error_str.c_str() : "Unknown error"); - - TCHAR buf[200]; - mir_sntprintf(buf, SIZEOF(buf), TranslateT("Login error: %s"), - (!error_str.length()) ? TranslateT("Unknown error") : ptrT(mir_utf8decodeT(error_str.c_str()))); - proto->facy.client_notify(buf); -} - -bool facebook_client::login(const char *username, const char *password) -{ - handle_entry("login"); - - username_ = username; - password_ = password; - - if (cookies.empty()) { - // Set device ID - ptrA device( parent->getStringA(FACEBOOK_KEY_DEVICE_ID)); - if (device != NULL) - cookies["datr"] = device; - - // Get initial cookies - flap(REQUEST_HOME); - } - - // Prepare login data - std::string data = "persistent=1"; - data += "&email=" + utils::url::encode(username); - data += "&pass=" + utils::url::encode(password); - - ptrA locale(parent->getStringA(FACEBOOK_KEY_LOCALE)); - if (locale != NULL) - data += "&locale=" + std::string(locale); - - // Send validation - http::response resp = flap(REQUEST_LOGIN, &data); - - // Save Device ID - if (cookies["datr"].length()) - parent->setString(FACEBOOK_KEY_DEVICE_ID, cookies["datr"].c_str()); - - if (resp.code == HTTP_CODE_FOUND && resp.headers.find("Location") != resp.headers.end()) - { - // Check for invalid requests - if (resp.headers["Location"].find("invalid_request.php") != std::string::npos) { - client_notify(TranslateT("Login error: Invalid request.")); - parent->debugLogA(" ! ! Login error: Invalid request."); - return handle_error("login", FORCE_QUIT); - } - - // Check whether some Facebook things are required - if (resp.headers["Location"].find("help.php") != std::string::npos) - { - client_notify(TranslateT("Login error: Some Facebook things are required.")); - parent->debugLogA(" ! ! Login error: Some Facebook things are required."); - // return handle_error("login", FORCE_QUIT); - } - - // Check whether setting Machine name is required - if (resp.headers["Location"].find("/checkpoint/") != std::string::npos) - { - resp = flap(REQUEST_SETUP_MACHINE, NULL, NULL, REQUEST_GET); - - if (resp.data.find("login_approvals_no_phones") != std::string::npos) { - // Code approval - but no phones in account - loginError(parent, utils::text::source_get_value(&resp.data, 4, "login_approvals_no_phones", "", "")); - return handle_error("login", FORCE_QUIT); - } - - std::string inner_data; - if (resp.data.find("name=\"submit[Continue]\"") != std::string::npos) { - - // Check if we need to approve also last unapproved device - if (resp.data.find("name=\"name_action_selected\"") == std::string::npos) { - // 1) Continue - inner_data = "submit[Continue]=Continue"; - inner_data += "&nh=" + utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\""); - inner_data += "&fb_dtsg=" + utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""); - resp = flap(REQUEST_SETUP_MACHINE, &inner_data); - - // 2) Approve last unknown login - // inner_data = "submit[I%20don't%20recognize]=I%20don't%20recognize"; // Don't recognize - this will force to change account password - inner_data = "submit[This%20is%20Okay]=This%20is%20Okay"; // Recognize - inner_data += "&nh=" + utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\""); - inner_data += "&fb_dtsg=" + utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""); - resp = flap(REQUEST_SETUP_MACHINE, &inner_data); - - // 3) Save last device - inner_data = "submit[Continue]=Continue"; - inner_data += "&nh=" + utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\""); - inner_data += "&fb_dtsg=" + utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""); - inner_data += "&name_action_selected=save_device"; // Save device - or "dont_save" - resp = flap(REQUEST_SETUP_MACHINE, &inner_data); - } - - // Save this actual device - inner_data = "submit[Continue]=Continue"; - inner_data += "&nh=" + utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\""); - inner_data += "&fb_dtsg=" + utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""); - inner_data += "&name_action_selected=save_device"; // Save device - or "dont_save" - resp = flap(REQUEST_SETUP_MACHINE, &inner_data); - } - - // Save actual machine name - // inner_data = "machine_name=Miranda%20NG&submit[Don't%20Save]=Don't%20Save"; // Don't save - inner_data = "machine_name=Miranda%20NG&submit[Save%20Device]=Save%20Device"; // Save - inner_data += "&lsd=" + utils::text::source_get_value(&resp.data, 3, "name=\"lsd\"", "value=\"", "\""); - inner_data += "&nh=" + utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\""); - inner_data += "&fb_dtsg=" + utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""); - - resp = flap(REQUEST_SETUP_MACHINE, &inner_data); - } - } - - switch (resp.code) - { - case HTTP_CODE_FAKE_DISCONNECTED: - { - // When is error only because timeout, try login once more - if (handle_error("login")) - return login(username, password); - else - return handle_error("login", FORCE_QUIT); - } - - case HTTP_CODE_OK: // OK page returned, but that is regular login page we don't want in fact - { - // Check whether captcha code is required - if (resp.data.find("id=\"captcha\"") != std::string::npos) - { - client_notify(TranslateT("Login error: Captcha code is required. Bad login credentials?")); - parent->debugLogA(" ! ! Login error: Captcha code is required."); - return handle_error("login", FORCE_QUIT); - } - - // Get and notify error message - loginError(parent, utils::text::source_get_value(&resp.data, 4, "login_error_box", "", "")); - } - case HTTP_CODE_FORBIDDEN: // Forbidden - case HTTP_CODE_NOT_FOUND: // Not Found - default: - return handle_error("login", FORCE_QUIT); - - case HTTP_CODE_FOUND: // Found and redirected to Home, Logged in, everything is OK - { - if (cookies.find("c_user") != cookies.end()) { - this->self_.user_id = cookies.find("c_user")->second; - parent->setString(FACEBOOK_KEY_ID, this->self_.user_id.c_str()); - parent->debugLogA(" Got self user id: %s", this->self_.user_id.c_str()); - return handle_success("login"); - } else { - client_notify(TranslateT("Login error, probably bad login credentials.")); - parent->debugLogA(" ! ! Login error, probably bad login credentials."); - return handle_error("login", FORCE_QUIT); - } - } - } -} - -bool facebook_client::logout() -{ - if (parent->getByte(FACEBOOK_KEY_DISABLE_LOGOUT, 0)) - return true; - - handle_entry("logout"); - - std::string data = "fb_dtsg=" + (this->dtsg_.length() ? this->dtsg_ : "0"); - data += "&ref=mb&h=" + this->logout_hash_; - - http::response resp = flap(REQUEST_LOGOUT, &data); - - if (hFcbCon) - Netlib_CloseHandle(hFcbCon); - hFcbCon = NULL; - - // Process result - username_ = password_ = self_.user_id = ""; - - switch (resp.code) - { - case HTTP_CODE_OK: - case HTTP_CODE_FOUND: - return handle_success("logout"); - - default: - return false; // Logout not finished properly, but..okay, who cares :P - } -} - -bool facebook_client::home() -{ - handle_entry("home"); - - // get fb_dtsg - http::response resp = flap(REQUEST_DTSG); - - // Check whether HTTPS connection is required and we don't have it enabled - if (!this->https_ && resp.headers["Location"].find("https://") != std::string::npos) { - client_notify(TranslateT("Your account requires HTTPS connection. Activating.")); - parent->setByte(FACEBOOK_KEY_FORCE_HTTPS, 1); - this->https_ = true; - return home(); - } - - this->dtsg_ = utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""); - parent->debugLogA(" Got self dtsg: %s", this->dtsg_.c_str()); - - if (this->dtsg_.empty()) - return false; - - resp = flap(REQUEST_HOME); - - switch (resp.code) - { - case HTTP_CODE_OK: - { - // Get real name - this->self_.real_name = utils::text::source_get_value(&resp.data, 2, "", ""); - - // Get and strip optional nickname - std::string::size_type pos = this->self_.real_name.find(""); - if (pos != std::string::npos) { - this->self_.nick = utils::text::source_get_value(&this->self_.real_name, 2, "(", ")"); - parent->debugLogA(" Got self nick name: %s", this->self_.nick.c_str()); - - this->self_.real_name = this->self_.real_name.substr(0, pos - 1); - } - parent->debugLogA(" Got self real name: %s", this->self_.real_name.c_str()); - - // Save name and nickname - parent->SaveName(NULL, &this->self_); - - if (this->self_.real_name.empty()) { - client_notify(TranslateT("Something happened to Facebook. Maybe there was some major update so you should wait for an update.")); - return handle_error("home", FORCE_QUIT); - } - - // Get avatar - this->self_.image_url = utils::text::source_get_value(&resp.data, 3, "class=\"l\"", "debugLogA(" Got self avatar: %s", this->self_.image_url.c_str()); - parent->CheckAvatarChange(NULL, this->self_.image_url); - - // Get logout hash - this->logout_hash_ = utils::text::source_get_value2(&resp.data, "/logout.php?h=", "&\""); - parent->debugLogA(" Got self logout hash: %s", this->logout_hash_.c_str()); - - return handle_success("home"); - } - case HTTP_CODE_FOUND: - // Work-around for replica_down, f**king hell what's that? - parent->debugLogA(" REPLICA_DOWN is back in force!"); - return this->home(); - - default: - return handle_error("home", FORCE_QUIT); - } -} - -bool facebook_client::chat_state(bool online) -{ - handle_entry("chat_state"); - - std::string data = (online ? "visibility=1" : "visibility=0"); - data += "&window_id=0"; - data += "&fb_dtsg=" + (dtsg_.length() ? dtsg_ : "0"); - data += "&phstamp=0&__user=" + self_.user_id; - http::response resp = flap(REQUEST_VISIBILITY, &data); - - return handle_success("chat_state"); -} - -bool facebook_client::reconnect() -{ - handle_entry("reconnect"); - - // Request reconnect - http::response resp = flap(REQUEST_RECONNECT); - - switch (resp.code) - { - case HTTP_CODE_OK: - { - this->chat_channel_ = utils::text::source_get_value(&resp.data, 2, "\"user_channel\":\"", "\""); - parent->debugLogA(" Got self channel: %s", this->chat_channel_.c_str()); - - this->chat_channel_partition_ = utils::text::source_get_value2(&resp.data, "\"partition\":", ",}"); - parent->debugLogA(" Got self channel partition: %s", this->chat_channel_partition_.c_str()); - - this->chat_channel_host_ = utils::text::source_get_value(&resp.data, 2, "\"host\":\"", "\""); - parent->debugLogA(" Got self channel host: %s", this->chat_channel_host_.c_str()); - - this->chat_sequence_num_ = utils::text::source_get_value2(&resp.data, "\"seq\":", ",}"); - parent->debugLogA(" Got self sequence number: %s", this->chat_sequence_num_.c_str()); - - this->chat_conn_num_ = utils::text::source_get_value2(&resp.data, "\"max_conn\":", ",}"); - parent->debugLogA(" Got self max_conn: %s", this->chat_conn_num_.c_str()); - - return handle_success("reconnect"); - } - - default: - return handle_error("reconnect", FORCE_DISCONNECT); - } -} - -bool facebook_client::buddy_list() -{ - handle_entry("buddy_list"); - - // Prepare update data - std::string data = "user=" + this->self_.user_id + "&fetch_mobile=true&phstamp=0&fb_dtsg=" + this->dtsg_ + "&__user=" + this->self_.user_id; - - { - ScopedLock s(buddies_lock_); - - data += "&cached_user_info_ids="; - int counter = 0; - for (List::Item< facebook_user >* i = buddies.begin(); i != NULL; i = i->next, counter++) - { - data += i->data->user_id + "%2C"; - } - } - - // Get buddy list - http::response resp = flap(REQUEST_BUDDY_LIST, &data); - - switch (resp.code) { - case HTTP_CODE_OK: - parent->ForkThread(&FacebookProto::ProcessBuddyList, new std::string(resp.data)); - return handle_success("buddy_list"); - - case HTTP_CODE_FAKE_ERROR: - case HTTP_CODE_FAKE_DISCONNECTED: - default: - return handle_error("buddy_list"); - } -} - -bool facebook_client::load_friends() -{ - handle_entry("load_friends"); - - // Get buddy list - http::response resp = flap(REQUEST_LOAD_FRIENDS); - - switch (resp.code) { - case HTTP_CODE_OK: - parent->ForkThread(&FacebookProto::ProcessFriendList, new std::string(resp.data)); - return handle_success("load_friends"); - - case HTTP_CODE_FAKE_ERROR: - case HTTP_CODE_FAKE_DISCONNECTED: - default: - return handle_error("load_friends"); - } -} - -bool facebook_client::feeds() -{ - handle_entry("feeds"); - - // Get feeds - http::response resp = flap(REQUEST_FEEDS); - - switch (resp.code) { - case HTTP_CODE_OK: - if (resp.data.find("\"num_stories\":0") == std::string::npos) - parent->ForkThread(&FacebookProto::ProcessFeeds, new std::string(resp.data)); - - return handle_success("feeds"); - - case HTTP_CODE_FAKE_ERROR: - case HTTP_CODE_FAKE_DISCONNECTED: - default: - return handle_error("feeds"); - } -} - -bool facebook_client::load_pages() -{ - if (!parent->getByte(FACEBOOK_KEY_LOAD_PAGES, DEFAULT_LOAD_PAGES)) - return true; - - handle_entry("load_pages"); - - // Get feeds - http::response resp = flap(REQUEST_PAGES); - - switch (resp.code) { - case HTTP_CODE_OK: - { - std::string content = utils::text::source_get_value(&resp.data, 2, "id=\"bookmarksSeeAllSection\"", ""); - - std::string::size_type start, end; - start = content.find(""); - - start = content.find("debugLogA(" Got page: %s (%s)", title.c_str(), id.c_str()); - pages[id] = title; - } - - return handle_success("load_pages"); - } - case HTTP_CODE_FAKE_ERROR: - case HTTP_CODE_FAKE_DISCONNECTED: - default: - return handle_error("load_pages"); - } -} - -bool facebook_client::channel() -{ - handle_entry("channel"); - - // Get update - http::response resp = flap(REQUEST_MESSAGES_RECEIVE); - - if (resp.data.empty()) { - // Something went wrong - return handle_error("channel"); - } - - std::string type = utils::text::source_get_value(&resp.data, 2, "\"t\":\"", "\""); - if (type == "continue" || type == "heartbeat") { - // Everything is OK, no new message received - } - else if (type == "lb") { - // Some new stuff (idk how does it work yet) - this->chat_channel_host_ = utils::text::source_get_value(&resp.data, 2, "\"vip\":\"", "\""); - parent->debugLogA(" Got self channel host: %s", this->chat_channel_host_.c_str()); - - this->chat_sticky_num_ = utils::text::source_get_value2(&resp.data, "\"sticky\":\"", "\""); - parent->debugLogA(" Got self sticky number: %s", this->chat_sticky_num_.c_str()); - } - else if (type == "fullReload" || type == "refresh") { - // Something went wrong (server flooding?) - parent->debugLogA("! ! ! Requested %s", type.c_str()); - - this->chat_sequence_num_ = utils::text::source_get_value2(&resp.data, "\"seq\":", ",}"); - parent->debugLogA(" Got self sequence number: %s", this->chat_sequence_num_.c_str()); - - this->chat_reconnect_reason_ = utils::text::source_get_value2(&resp.data, "\"reason\":", ",}"); - parent->debugLogA(" Reconnect reason: %s", this->chat_reconnect_reason_.c_str()); - - if (type == "refresh") - return this->reconnect(); - } - else { - // Something has been received, throw to new thread to process - std::string* response_data = new std::string(resp.data); - parent->ForkThread(&FacebookProto::ProcessMessages, response_data); - - // Increment sequence number - this->chat_sequence_num_ = utils::text::source_get_value2(&resp.data, "\"seq\":", ",}"); - parent->debugLogA(" Got self sequence number: %s", this->chat_sequence_num_.c_str()); - } - - // Return - switch (resp.code) - { - case HTTP_CODE_OK: - return handle_success("channel"); - - case HTTP_CODE_GATEWAY_TIMEOUT: - // Maybe we have same clientid as other connected client, try to generate different one - this->chat_clientid_ = utils::text::rand_string(8, "0123456789abcdef"); - - // Intentionally fall to handle_error() below - case HTTP_CODE_FAKE_DISCONNECTED: - case HTTP_CODE_FAKE_ERROR: - default: - return handle_error("channel"); - } -} - -bool facebook_client::send_message(std::string message_recipient, std::string message_text, std::string *error_text, MessageMethod method) -{ - ScopedLock s(send_message_lock_); - - handle_entry("send_message"); - - http::response resp; - - switch (method) { - case MESSAGE_INBOX: - { - parent->debugLogA(" > Sending message through INBOX"); - std::string data = "action=send"; - data += "&body=" + utils::url::encode(message_text); - data += "&recipients[0]=" + message_recipient; - data += "&__user=" + this->self_.user_id; - data += "&__a=1"; - data += "&fb_dtsg=" + (dtsg_.length() ? dtsg_ : "0"); - data += "&phstamp=0"; - - resp = flap(REQUEST_MESSAGE_SEND2, &data); - break; - } - case MESSAGE_MERCURY: - { - parent->debugLogA(" > Sending message through CHAT"); - std::string data = "message_batch[0][action_type]=ma-type:user-generated-message"; - data += "&message_batch[0][thread_id]"; - data += "&message_batch[0][author]=fbid:" + this->self_.user_id; - data += "&message_batch[0][author_email]"; - data += "&message_batch[0][coordinates]"; - data += "&message_batch[0][timestamp]=" + utils::time::mili_timestamp(); - data += "&message_batch[0][timestamp_absolute]"; - data += "&message_batch[0][timestamp_relative]"; - data += "&message_batch[0][is_unread]=false"; - data += "&message_batch[0][is_cleared]=false"; - data += "&message_batch[0][is_forward]=false"; - data += "&message_batch[0][spoof_warning]=false"; - data += "&message_batch[0][source]=source:chat:web"; - data += "&message_batch[0][source_tags][0]=source:chat"; - data += "&message_batch[0][body]=" + utils::url::encode(message_text); - data += "&message_batch[0][has_attachment]=false"; - data += "&message_batch[0][html_body]=false"; - data += "&message_batch[0][specific_to_list][0]=fbid:" + message_recipient; - data += "&message_batch[0][specific_to_list][1]=fbid:" + this->self_.user_id; - data += "&message_batch[0][status]=0"; - data += "&message_batch[0][message_id]"; - data += "&message_batch[0][client_thread_id]=user:" + message_recipient; - data += "&client=mercury"; - data += "&fb_dtsg=" + (dtsg_.length() ? dtsg_ : "0"); - data += "&__user=" + this->self_.user_id; - data += "&__a=1"; - data += "&phstamp=0"; - - resp = flap(REQUEST_MESSAGE_SEND, &data); - break; - } - case MESSAGE_TID: - { - parent->debugLogA(" > Sending message through MERCURY (TID)"); - std::string data = "message_batch[0][action_type]=ma-type:user-generated-message"; - data += "&message_batch[0][thread_id]=" + message_recipient; - data += "&message_batch[0][author]=fbid:" + this->self_.user_id; - data += "&message_batch[0][timestamp]=" + utils::time::mili_timestamp(); - data += "&message_batch[0][timestamp_absolute]="; - data += "&message_batch[0][timestamp_relative]="; - data += "&message_batch[0][is_unread]=false"; - data += "&message_batch[0][is_cleared]=false"; - data += "&message_batch[0][is_forward]=false"; - data += "&message_batch[0][source]=source:chat:web"; - data += "&message_batch[0][body]=" + utils::url::encode(message_text); - data += "&message_batch[0][has_attachment]=false"; - data += "&message_batch[0][is_html]=false"; - data += "&message_batch[0][message_id]="; - data += "&fb_dtsg=" + (dtsg_.length() ? dtsg_ : "0"); - data += "&__user=" + this->self_.user_id; - data += "&phstamp=0"; - - resp = flap(REQUEST_MESSAGE_SEND, &data); - break; - } - case MESSAGE_ASYNC: - { - parent->debugLogA(" > Sending message through ASYNC"); - std::string data = "action=send"; - data += "&body=" + utils::url::encode(message_text); - data += "&recipients[0]=" + message_recipient; - data += "&lsd="; - data += "&fb_dtsg=" + (dtsg_.length() ? dtsg_ : "0"); - - resp = flap(REQUEST_ASYNC, &data); - break; - } - } - - *error_text = resp.error_text; - - switch (resp.error_number) - { - case 0: // Everything is OK - { - // Remember this message id - std::string mid = utils::text::source_get_value(&resp.data, 2, "\"message_id\":\"", "\""); - messages_ignore.insert(std::make_pair(mid, false)); - } break; - - //case 1356002: // You are offline - wtf?? - - case 1356003: // Contact is offline - { - MCONTACT hContact = parent->ContactIDToHContact(message_recipient); - if (hContact != NULL) - parent->setWord(hContact, "Status", ID_STATUS_OFFLINE); - return false; - } break; - - case 1356026: // Contact has alternative client - { - client_notify(TranslateT("Need confirmation for sending messages to other clients.\nOpen Facebook website and try to send message to this contact again!")); - return false; - } break; - - default: // Other error - parent->debugLogA(" !!! Send message error #%d: %s", resp.error_number, resp.error_text); - return false; - } - - switch (resp.code) - { - case HTTP_CODE_OK: - return handle_success("send_message"); - - case HTTP_CODE_FAKE_ERROR: - case HTTP_CODE_FAKE_DISCONNECTED: - default: - *error_text = Translate("Timeout when sending message."); - - handle_error("send_message"); - return false; - } -} - -bool facebook_client::post_status(status_data *status) -{ - if (status == NULL || (status->text.empty() && status->url.empty())) - return false; - - handle_entry("post_status"); - - if (status->isPage) { - std::string data = "fb_dtsg=" + (this->dtsg_.length() ? this->dtsg_ : "0"); - data += "&user_id=" + status->user_id; - data += "&url=" + std::string(FACEBOOK_URL_HOMEPAGE); - flap(REQUEST_IDENTITY_SWITCH, &data); - } - - std::string data; - RequestType request = REQUEST_POST_STATUS; - - if (!status->url.empty()) { - data = "fb_dtsg=" + (this->dtsg_.length() ? this->dtsg_ : "0"); - data += "&composerid=u_jsonp_2_b"; - data += "&targetid=" + (status->user_id.empty() ? this->self_.user_id : status->user_id); - data += "&istimeline=1&composercontext=composer&onecolumn=1&nctr[_mod]=pagelet_timeline_recent&__a=1&ttstamp=0"; - data += "&__user=" + (status->isPage && !status->user_id.empty() ? status->user_id : this->self_.user_id); - data += "&loaded_components[0]=maininput&loaded_components[1]=backdateicon&loaded_components[2]=withtaggericon&loaded_components[3]=cameraicon&loaded_components[4]=placetaggericon&loaded_components[5]=mainprivacywidget&loaded_components[6]=withtaggericon&loaded_components[7]=backdateicon&loaded_components[8]=placetaggericon&loaded_components[9]=cameraicon&loaded_components[10]=mainprivacywidget&loaded_components[11]=maininput&loaded_components[12]=explicitplaceinput&loaded_components[13]=hiddenplaceinput&loaded_components[14]=placenameinput&loaded_components[15]=hiddensessionid&loaded_components[16]=withtagger&loaded_components[17]=backdatepicker&loaded_components[18]=placetagger&loaded_components[19]=citysharericon"; - http::response resp = flap(REQUEST_LINK_SCRAPER, &data, &status->url); - resp.data = utils::text::special_expressions_decode(utils::text::slashu_to_utf8(resp.data)); - - data = "&xhpc_context=profile&xhpc_ismeta=1&xhpc_timeline=1&xhpc_composerid=u_jsonp_2_0&is_explicit_place=&composertags_place=&composer_session_id=&composertags_city=&disable_location_sharing=false&composer_predicted_city=&nctr[_mod]=pagelet_composer&__a=1&__dyn=&__req=1f&ttstamp=0"; - std::string form = utils::text::source_get_value(&resp.data, 2, ""); - utils::text::replace_all(&form, "\\\"", "\""); - data += "&" + utils::text::source_get_form_data(&form) + "&"; - //data += "&no_picture=0"; - - request = REQUEST_STATUS_COMPOSER; - } - - std::string text = utils::url::encode(status->text); - - data += "fb_dtsg=" + (this->dtsg_.length() ? this->dtsg_ : "0"); - data += "&xhpc_targetid=" + (status->user_id.empty() ? this->self_.user_id : status->user_id); - data += "&__user=" + (status->isPage && !status->user_id.empty() ? status->user_id : this->self_.user_id); - data += "&xhpc_message=" + text; - data += "&xhpc_message_text=" + text; - if (!status->isPage) - data += "&audience[0][value]=" + get_privacy_type(); - if (!status->place.empty()) { - data += "&composertags_place_name="; - data += utils::url::encode(status->place); - } - for(std::vector::size_type i = 0; i < status->users.size(); i++) { - data += "&composertags_with[" + utils::conversion::to_string(&i, UTILS_CONV_UNSIGNED_NUMBER); - data += "]=" + status->users[i]->user_id; - data += "&text_composertags_with[" + utils::conversion::to_string(&i, UTILS_CONV_UNSIGNED_NUMBER); - data += "]=" + status->users[i]->real_name; - delete status->users[i]; - } - status->users.clear(); - - data += "&xhpc_context=profile&xhpc_ismeta=1&xhpc_timeline=1&xhpc_composerid=u_0_2y&is_explicit_place=&composertags_place=&composertags_city="; - - http::response resp = flap(request, &data); - - if (status->isPage) { - std::string data = "fb_dtsg=" + (this->dtsg_.length() ? this->dtsg_ : "0"); - data += "&user_id=" + this->self_.user_id; - data += "&url=" + std::string(FACEBOOK_URL_HOMEPAGE); - flap(REQUEST_IDENTITY_SWITCH, &data); - } - - if (resp.isValid()) { - parent->NotifyEvent(parent->m_tszUserName, TranslateT("Status update was successful."), NULL, FACEBOOK_EVENT_OTHER); - return handle_success("post_status"); - } - - return handle_error("post_status"); -} - -////////////////////////////////////////////////////////////////////////////// - -bool facebook_client::save_url(const std::string &url,const std::tstring &filename, HANDLE &nlc) -{ - NETLIBHTTPREQUEST req = {sizeof(req)}; - NETLIBHTTPREQUEST *resp; - req.requestType = REQUEST_GET; - req.szUrl = const_cast(url.c_str()); - req.flags = NLHRF_HTTP11 | NLHRF_REDIRECT | NLHRF_PERSISTENT | NLHRF_NODUMP; - req.nlc = nlc; - - resp = reinterpret_cast(CallService(MS_NETLIB_HTTPTRANSACTION, - reinterpret_cast(this->parent->m_hNetlibUser), reinterpret_cast(&req))); - - bool ret = false; - - if (resp) { - nlc = resp->nlc; - parent->debugLogA("@@@@@ Saving avatar URL %s to path %s", url.c_str(), _T2A(filename.c_str())); - - // Create folder if necessary - std::tstring dir = filename.substr(0,filename.rfind('\\')); - if (_taccess(dir.c_str(), 0)) - CreateDirectoryTreeT(dir.c_str()); - - // Write to file - FILE *f = _tfopen(filename.c_str(), _T("wb")); - if (f != NULL) { - fwrite(resp->pData,1,resp->dataLength,f); - fclose(f); - - ret = _taccess(filename.c_str(), 0) == 0; - } - - CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp); - } else { - nlc = NULL; - } - - return ret; -} +/* + +Facebook plugin for Miranda Instant Messenger +_____________________________________________ + +Copyright � 2009-11 Michal Zelinka, 2011-13 Robert P�sel + +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 2 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 . + +*/ + +#include "common.h" + +void facebook_client::client_notify(TCHAR* message) +{ + parent->NotifyEvent(parent->m_tszUserName, message, NULL, FACEBOOK_EVENT_CLIENT); +} + +http::response facebook_client::flap(RequestType request_type, std::string* request_data, std::string* request_get_data, int method) +{ + NETLIBHTTPREQUEST nlhr = {sizeof(NETLIBHTTPREQUEST)}; + nlhr.requestType = !method ? choose_method(request_type) : method; + + std::string url = choose_proto(request_type); + url.append(choose_server(request_type, request_data, request_get_data)); + url.append(choose_action(request_type, request_data, request_get_data)); + + nlhr.szUrl = (char*)url.c_str(); + nlhr.flags = NLHRF_HTTP11 | choose_security_level(request_type); + nlhr.headers = get_request_headers(nlhr.requestType, &nlhr.headersCount); + + #ifdef _DEBUG + nlhr.flags |= NLHRF_DUMPASTEXT; + #else + nlhr.flags |= NLHRF_NODUMP; + #endif + + switch (request_type) + { + case REQUEST_MESSAGES_RECEIVE: + nlhr.timeout = 1000 * 65; break; + default: + nlhr.timeout = 1000 * 20; break; + } + + if (request_data != NULL) + { + nlhr.pData = (char*)(*request_data).c_str(); + nlhr.dataLength = (int)request_data->length(); + } + + parent->debugLogA("@@@@@ Sending request to '%s'", nlhr.szUrl); + + switch (request_type) + { + case REQUEST_LOGIN: + nlhr.nlc = NULL; + break; + + case REQUEST_MESSAGES_RECEIVE: + nlhr.nlc = hMsgCon; + nlhr.flags |= NLHRF_PERSISTENT; + break; + + default: + WaitForSingleObject(fcb_conn_lock_, INFINITE); + nlhr.nlc = hFcbCon; + nlhr.flags |= NLHRF_PERSISTENT; + break; + } + + NETLIBHTTPREQUEST* pnlhr = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)handle_, (LPARAM)&nlhr); + + mir_free(nlhr.headers[3].szValue); + mir_free(nlhr.headers); + + http::response resp; + + switch (request_type) + { + case REQUEST_LOGIN: + case REQUEST_SETUP_MACHINE: + break; + + case REQUEST_MESSAGES_RECEIVE: + hMsgCon = pnlhr ? pnlhr->nlc : NULL; + break; + + default: + ReleaseMutex(fcb_conn_lock_); + hFcbCon = pnlhr ? pnlhr->nlc : NULL; + break; + } + + if (pnlhr != NULL) + { + parent->debugLogA("@@@@@ Got response with code %d", pnlhr->resultCode); + store_headers(&resp, pnlhr->headers, pnlhr->headersCount); + resp.code = pnlhr->resultCode; + resp.data = pnlhr->pData ? pnlhr->pData : ""; + + CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)pnlhr); + } else { + parent->debugLogA("!!!!! No response from server (time-out)"); + resp.code = HTTP_CODE_FAKE_DISCONNECTED; + // Better to have something set explicitely as this value is compaired in all communication requests + } + + // Get Facebook's error message + if (resp.code == HTTP_CODE_OK) { + std::string::size_type pos = resp.data.find("\"error\":"); + if (pos != std::string::npos) { + pos += 8; + int error_num = atoi(resp.data.substr(pos, resp.data.find(",", pos) - pos).c_str()); + if (error_num != 0) { + std::string error = ""; + + pos = resp.data.find("\"errorDescription\":\"", pos); + if (pos != std::string::npos) { + pos += 20; + error = resp.data.substr(pos, resp.data.find("\"", pos) - pos); + error = utils::text::trim(utils::text::special_expressions_decode(utils::text::slashu_to_utf8(error))); + error = ptrA( mir_utf8decodeA(error.c_str())); + } + + std::string title = ""; + pos = resp.data.find("\"errorSummary\":\"", pos); + if (pos != std::string::npos) { + pos += 16; + title = resp.data.substr(pos, resp.data.find("\"", pos) - pos); + title = utils::text::trim(utils::text::special_expressions_decode(utils::text::slashu_to_utf8(title))); + title = ptrA( mir_utf8decodeA(title.c_str())); + } + + bool silent = resp.data.find("\"silentError\":1") != std::string::npos; + + resp.error_number = error_num; + resp.error_text = error; + resp.error_title = title; + resp.code = HTTP_CODE_FAKE_ERROR; + + parent->debugLogA(" ! ! Received Facebook error: %d -- %s", error_num, error.c_str()); + if (notify_errors(request_type) && !silent) + client_notify(_A2T(error.c_str())); + } + } + } + + return resp; +} + +bool facebook_client::handle_entry(std::string method) +{ + parent->debugLogA(" >> Entering %s()", method.c_str()); + return true; +} + +bool facebook_client::handle_success(std::string method) +{ + parent->debugLogA(" << Quitting %s()", method.c_str()); + reset_error(); + return true; +} + +bool facebook_client::handle_error(std::string method, int action) +{ + increment_error(); + parent->debugLogA("!!!!! %s(): Something with Facebook went wrong", method.c_str()); + + bool result = (error_count_ <= (UINT)parent->getByte(FACEBOOK_KEY_TIMEOUTS_LIMIT, FACEBOOK_TIMEOUTS_LIMIT)); + if (action == FORCE_DISCONNECT || action == FORCE_QUIT) + result = false; + + if (!result) { + reset_error(); + + if (action != FORCE_QUIT) + parent->SetStatus(ID_STATUS_OFFLINE); + } + + return result; +} + +////////////////////////////////////////////////////////////////////////////// + +DWORD facebook_client::choose_security_level(RequestType request_type) +{ + if (this->https_) + if (request_type != REQUEST_MESSAGES_RECEIVE || parent->getByte(FACEBOOK_KEY_FORCE_HTTPS_CHANNEL, DEFAULT_FORCE_HTTPS_CHANNEL)) + return NLHRF_SSL; + + switch (request_type) { + case REQUEST_LOGIN: + case REQUEST_SETUP_MACHINE: + return NLHRF_SSL; + +// case REQUEST_LOGOUT: +// case REQUEST_HOME: +// case REQUEST_DTSG: +// case REQUEST_BUDDY_LIST: +// case REQUEST_LOAD_FRIEND: +// case REQUEST_LOAD_FRIENDS: +// case REQUEST_USER_INFO: +// case REQUEST_LOAD_REQUESTS: +// case REQUEST_SEARCH: +// case REQUEST_DELETE_FRIEND: +// case REQUEST_REQUEST_FRIEND: +// case REQUEST_APPROVE_FRIEND: +// case REQUEST_CANCEL_REQUEST: +// case REQUEST_FEEDS: +// case REQUEST_PAGES: +// case REQUEST_NOTIFICATIONS: +// case REQUEST_RECONNECT: +// case REQUEST_POST_STATUS: +// case REQUEST_IDENTITY_SWITCH: +// case REQUEST_STATUS_COMPOSER: +// case REQUEST_LINK_SCRAPER: +// case REQUEST_MESSAGE_SEND: +// case REQUEST_MESSAGE_SEND2: +// case REQUEST_THREAD_INFO: +// case REQUEST_MESSAGES_RECEIVE: +// case REQUEST_VISIBILITY: +// case REQUEST_POKE: +// case REQUEST_ASYNC: +// case REQUEST_MARK_READ: +// case REQUEST_NOTIFICATIONS_READ: +// case REQUEST_UNREAD_THREADS: +// case REQUEST_TYPING_SEND: + default: + return (DWORD)0; + } +} + +int facebook_client::choose_method(RequestType request_type) +{ + switch (request_type) + { + case REQUEST_LOGIN: + case REQUEST_SETUP_MACHINE: + case REQUEST_BUDDY_LIST: + case REQUEST_POST_STATUS: + case REQUEST_IDENTITY_SWITCH: + case REQUEST_STATUS_COMPOSER: + case REQUEST_LINK_SCRAPER: + case REQUEST_MESSAGE_SEND: + case REQUEST_MESSAGE_SEND2: + case REQUEST_THREAD_INFO: + case REQUEST_VISIBILITY: + case REQUEST_POKE: + case REQUEST_ASYNC: + case REQUEST_MARK_READ: + case REQUEST_NOTIFICATIONS_READ: + case REQUEST_TYPING_SEND: + case REQUEST_LOGOUT: + case REQUEST_DELETE_FRIEND: + case REQUEST_REQUEST_FRIEND: + case REQUEST_APPROVE_FRIEND: + case REQUEST_CANCEL_REQUEST: + case REQUEST_UNREAD_THREADS: + return REQUEST_POST; + +// case REQUEST_HOME: +// case REQUEST_DTSG: +// case REQUEST_MESSAGES_RECEIVE: +// case REQUEST_FEEDS: +// case REQUEST_PAGES: +// case REQUEST_NOTIFICATIONS: +// case REQUEST_RECONNECT: +// case REQUEST_LOAD_FRIEND: +// case REQUEST_LOAD_FRIENDS: +// case REQUEST_USER_INFO: +// case REQUEST_LOAD_REQUESTS: +// case REQUEST_SEARCH: + default: + return REQUEST_GET; + } +} + +std::string facebook_client::choose_proto(RequestType request_type) +{ + if (choose_security_level(request_type) == NLHRF_SSL) + return HTTP_PROTO_SECURE; + else + return HTTP_PROTO_REGULAR; +} + +std::string facebook_client::choose_server(RequestType request_type, std::string* data, std::string* get_data) +{ + switch (request_type) + { + case REQUEST_LOGIN: + return FACEBOOK_SERVER_LOGIN; + + case REQUEST_MESSAGES_RECEIVE: + { + std::string server = FACEBOOK_SERVER_CHAT; + utils::text::replace_first(&server, "%s", this->chat_conn_num_.empty() ? "0" : this->chat_conn_num_); + utils::text::replace_first(&server, "%s", this->chat_channel_host_); + return server; + } + + case REQUEST_HOME: + case REQUEST_DTSG: + case REQUEST_APPROVE_FRIEND: + case REQUEST_LOAD_REQUESTS: + case REQUEST_SEARCH: + case REQUEST_USER_INFO: + return FACEBOOK_SERVER_MOBILE; + +// case REQUEST_LOGOUT: +// case REQUEST_BUDDY_LIST: +// case REQUEST_LOAD_FRIEND: +// case REQUEST_LOAD_FRIENDS: +// case REQUEST_FEEDS: +// case REQUEST_PAGES: +// case REQUEST_NOTIFICATIONS: +// case REQUEST_RECONNECT: +// case REQUEST_POST_STATUS: +// case REQUEST_IDENTITY_SWITCH: +// case REQUEST_STATUS_COMPOSER: +// case REQUEST_LINK_SCRAPER: +// case REQUEST_MESSAGE_SEND: +// case REQUEST_MESSAGE_SEND2: +// case REQUEST_THREAD_INFO: +// case REQUEST_VISIBILITY: +// case REQUEST_POKE: +// case REQUEST_ASYNC: +// case REQUEST_MARK_READ: +// case REQUEST_NOTIFICATIONS_READ: +// case REQUEST_TYPING_SEND: +// case REQUEST_SETUP_MACHINE: +// case REQUEST_DELETE_FRIEND: +// case REQUEST_REQUEST_FRIEND: +// case REQUEST_CANCEL_REQUEST: +// case REQUEST_UNREAD_THREADS: + default: + return FACEBOOK_SERVER_REGULAR; + } +} + +std::string facebook_client::choose_action(RequestType request_type, std::string* data, std::string* get_data) +{ + switch (request_type) + { + case REQUEST_LOGIN: + return "/login.php?login_attempt=1"; + + case REQUEST_SETUP_MACHINE: + return "/checkpoint/?next"; + + case REQUEST_LOGOUT: + return "/logout.php"; + + case REQUEST_HOME: + return "/profile.php?v=info"; + + case REQUEST_DTSG: + return "/editprofile.php?edit=current_city&type=basic"; + + case REQUEST_BUDDY_LIST: + return "/ajax/chat/buddy_list.php?__a=1"; + + case REQUEST_LOAD_FRIEND: + { + std::string action = "/ajax/chat/user_info.php?__a=1&viewer=%s&__user=%s"; + utils::text::replace_all(&action, "%s", self_.user_id); + if (get_data != NULL) { + action += "&" + (*get_data); + } + return action; + } + + case REQUEST_LOAD_FRIENDS: + { + std::string action = "/ajax/chat/user_info_all.php?__a=1&viewer=%s&__user=%s"; + utils::text::replace_all(&action, "%s", self_.user_id); + return action; + } + + case REQUEST_USER_INFO: + { + std::string action = "/%sv=info"; + if (get_data != NULL) { + utils::text::replace_all(&action, "%s", *get_data); + } + return action; + } + + case REQUEST_LOAD_REQUESTS: + { + return "/friends/"; + } + + case REQUEST_SEARCH: + { + std::string action = "/search/?search=people&query="; + if (get_data != NULL) { + action += *get_data; + } + return action; + } + + case REQUEST_UNREAD_THREADS: + { + return "/ajax/mercury/unread_threads.php?__a=1"; + } + + case REQUEST_DELETE_FRIEND: + { + std::string action = "/ajax/profile/removefriendconfirm.php?__a=1"; + if (get_data != NULL) { + action += *get_data; + } + return action; + } + + case REQUEST_REQUEST_FRIEND: + { + return "/ajax/add_friend/action.php?__a=1"; + } + + case REQUEST_APPROVE_FRIEND: + { + std::string action = "/a/notifications.php?__a=1"; + if (get_data != NULL) { + action += "&" + (*get_data); + } + return action; + } + + case REQUEST_CANCEL_REQUEST: + { + return "/ajax/friends/requests/cancel.php?__a=1"; + } + + case REQUEST_FEEDS: + { + std::string action = "/ajax/intent.php?filter="; + action += get_newsfeed_type(); + action += "&request_type=4&__a=1&newest=%s&ignore_self=true&load_newer=true&__user=%s"; + std::string newest = utils::conversion::to_string((void*)&this->last_feeds_update_, UTILS_CONV_TIME_T); + utils::text::replace_first(&action, "%s", newest); + utils::text::replace_first(&action, "%s", self_.user_id); + return action; + } + + case REQUEST_PAGES: + { + return "/bookmarks/pages"; + } + + case REQUEST_NOTIFICATIONS: + { + std::string action = "/ajax/notifications/get.php?__a=1&user=%s&time=0&version=2&__user=%s"; + // TODO: use better format notifications request + // std::string action = "/ajax/notifications/client/get.php?__a=1&user=%s&time=0&version=2&__user=%s"; + utils::text::replace_all(&action, "%s", self_.user_id); + return action; + } + + case REQUEST_RECONNECT: + { + std::string action = "/ajax/presence/reconnect.php?__a=1&reason=%s&fb_dtsg=%s&__user=%s"; + + if (this->chat_reconnect_reason_.empty()) + this->chat_reconnect_reason_ = "6"; + + utils::text::replace_first(&action, "%s", this->chat_reconnect_reason_); + utils::text::replace_first(&action, "%s", this->dtsg_); + utils::text::replace_first(&action, "%s", this->self_.user_id); + return action; + } + + case REQUEST_POST_STATUS: + return "/ajax/updatestatus.php?__a=1"; + + case REQUEST_IDENTITY_SWITCH: + return "/identity_switch.php?__a=1"; + + case REQUEST_STATUS_COMPOSER: + return "/ajax/profile/composer.php?__a=1"; + + case REQUEST_LINK_SCRAPER: + { + std::string action = "/ajax/composerx/attachment/link/scraper/?__a=1&composerurihash=2&scrape_url="; + if (get_data != NULL) { + action += utils::url::encode(*get_data); + } + return action; + } + + case REQUEST_MESSAGE_SEND: + return "/ajax/mercury/send_messages.php?__a=1"; + + case REQUEST_MESSAGE_SEND2: + return "/ajax/messaging/send.php"; + + case REQUEST_THREAD_INFO: + return "/ajax/mercury/thread_info.php?__a=1"; + + case REQUEST_MESSAGES_RECEIVE: + { + std::string action = "/pull?channel=" + (this->chat_channel_.empty() ? "p_" + self_.user_id : this->chat_channel_); + action += "&seq=" + (this->chat_sequence_num_.empty() ? "0" : this->chat_sequence_num_); + action += "&partition=" + (this->chat_channel_partition_.empty() ? "0" : this->chat_channel_partition_); + action += "&clientid=" + this->chat_clientid_; + action += "&cb=" + utils::text::rand_string(4, "0123456789abcdefghijklmnopqrstuvwxyz"); + action += "&idle=0&state=active"; + if (!this->chat_sticky_num_.empty()) + action += "&sticky_token=" + this->chat_sticky_num_; + + return action; + } + + case REQUEST_VISIBILITY: + return "/ajax/chat/privacy/visibility.php?__a=1"; + + case REQUEST_POKE: + return "/pokes/dialog/?__a=1"; + + case REQUEST_ASYNC: + { + std::string action = "/ajax/messaging/async.php?__a=1"; + if (get_data != NULL) { + action += "&" + (*get_data); + } + return action; + } + + case REQUEST_MARK_READ: + return "/ajax/mercury/change_read_status.php?__a=1"; + + case REQUEST_NOTIFICATIONS_READ: + { + std::string action = "/ajax/notifications/mark_read.php?__a=1"; + if (get_data != NULL) { + action += "&" + (*get_data); + } + return action; + } + + case REQUEST_TYPING_SEND: + return "/ajax/messaging/typ.php?__a=1"; + + default: + return "/?_fb_noscript=1"; + } +} + +bool facebook_client::notify_errors(RequestType request_type) +{ + switch (request_type) + { + case REQUEST_BUDDY_LIST: + case REQUEST_MESSAGE_SEND2: + case REQUEST_MESSAGE_SEND: + case REQUEST_ASYNC: + return false; + + default: + return true; + } +} + +NETLIBHTTPHEADER* facebook_client::get_request_headers(int request_type, int* headers_count) +{ + if (request_type == REQUEST_POST) + *headers_count = 5; + else + *headers_count = 4; + + NETLIBHTTPHEADER* headers = (NETLIBHTTPHEADER*)mir_calloc(sizeof(NETLIBHTTPHEADER)*(*headers_count)); + + if (request_type == REQUEST_POST) + { + headers[4].szName = "Content-Type"; + headers[4].szValue = "application/x-www-form-urlencoded; charset=utf-8"; + } + + headers[3].szName = "Cookie"; + headers[3].szValue = load_cookies(); + headers[2].szName = "User-Agent"; + headers[2].szValue = (char *)g_strUserAgent.c_str(); + headers[1].szName = "Accept"; + headers[1].szValue = "*/*"; + headers[0].szName = "Accept-Language"; + headers[0].szValue = "en,en-US;q=0.9"; + + return headers; +} + +std::string facebook_client::get_newsfeed_type() +{ + BYTE feed_type = parent->getByte(FACEBOOK_KEY_FEED_TYPE, 0); + if (feed_type >= SIZEOF(feed_types)) + feed_type = 0; + return feed_types[feed_type].id; +} + +std::string facebook_client::get_server_type() +{ + BYTE server_type = parent->getByte(FACEBOOK_KEY_SERVER_TYPE, 0); + if (server_type >= SIZEOF(server_types)) + server_type = 0; + return server_types[server_type].id; +} + +std::string facebook_client::get_privacy_type() +{ + BYTE privacy_type = parent->getByte(FACEBOOK_KEY_PRIVACY_TYPE, 0); + if (privacy_type >= SIZEOF(privacy_types)) + privacy_type = 0; + return privacy_types[privacy_type].id; +} + + +char* facebook_client::load_cookies() +{ + ScopedLock s(cookies_lock_); + + std::string cookieString = "isfbe=false;"; + + if (!cookies.empty()) + for (std::map< std::string, std::string >::iterator iter = cookies.begin(); iter != cookies.end(); ++iter) + { + cookieString.append(iter->first); + cookieString.append(1, '='); + cookieString.append(iter->second); + cookieString.append(1, ';'); + } + + return mir_strdup(cookieString.c_str()); +} + +void facebook_client::store_headers(http::response* resp, NETLIBHTTPHEADER* headers, int headersCount) +{ + ScopedLock c(cookies_lock_); + + for (int i = 0; i < headersCount; i++) + { + std::string header_name = headers[i].szName; // TODO: Casting? + std::string header_value = headers[i].szValue; // TODO: Casting? + + if (header_name == "Set-Cookie") + { + std::string cookie_name = header_value.substr(0, header_value.find("=")); + std::string cookie_value = header_value.substr(header_value.find("=") + 1, header_value.find(";") - header_value.find("=") - 1); + if (cookie_value == "deleted") + { + parent->debugLogA(" Deleted cookie '%s'", cookie_name.c_str()); + cookies.erase(cookie_name); + } else { + parent->debugLogA(" New cookie '%s'", cookie_name.c_str()); + cookies[cookie_name] = cookie_value; + } + } + else + { // TODO RM: (un)comment + //parent->debugLogA("----- Got header '%s': %s", header_name.c_str(), header_value.c_str()); + resp->headers[header_name] = header_value; + } + } +} + +void facebook_client::clear_cookies() +{ + ScopedLock s(cookies_lock_); + + if (!cookies.empty()) + cookies.clear(); +} + +void facebook_client::clear_notifications() +{ + for (std::map::iterator it = notifications.begin(); it != notifications.end(); ) { + if (it->second->hWndPopup != NULL) + PUDeletePopup(it->second->hWndPopup); // close popup + + delete it->second; + it = notifications.erase(it); + } + + notifications.clear(); +} + +void loginError(FacebookProto *proto, std::string error_str) { + error_str = utils::text::trim( + utils::text::special_expressions_decode( + utils::text::remove_html( + utils::text::edit_html(error_str)))); + + proto->debugLogA(" ! ! Login error: %s", error_str.length() ? error_str.c_str() : "Unknown error"); + + TCHAR buf[200]; + mir_sntprintf(buf, SIZEOF(buf), TranslateT("Login error: %s"), + (!error_str.length()) ? TranslateT("Unknown error") : ptrT(mir_utf8decodeT(error_str.c_str()))); + proto->facy.client_notify(buf); +} + +bool facebook_client::login(const char *username, const char *password) +{ + handle_entry("login"); + + username_ = username; + password_ = password; + + if (cookies.empty()) { + // Set device ID + ptrA device( parent->getStringA(FACEBOOK_KEY_DEVICE_ID)); + if (device != NULL) + cookies["datr"] = device; + + // Get initial cookies + flap(REQUEST_HOME); + } + + // Prepare login data + std::string data = "persistent=1"; + data += "&email=" + utils::url::encode(username); + data += "&pass=" + utils::url::encode(password); + + ptrA locale(parent->getStringA(FACEBOOK_KEY_LOCALE)); + if (locale != NULL) + data += "&locale=" + std::string(locale); + + // Send validation + http::response resp = flap(REQUEST_LOGIN, &data); + + // Save Device ID + if (cookies["datr"].length()) + parent->setString(FACEBOOK_KEY_DEVICE_ID, cookies["datr"].c_str()); + + if (resp.code == HTTP_CODE_FOUND && resp.headers.find("Location") != resp.headers.end()) + { + // Check for invalid requests + if (resp.headers["Location"].find("invalid_request.php") != std::string::npos) { + client_notify(TranslateT("Login error: Invalid request.")); + parent->debugLogA(" ! ! Login error: Invalid request."); + return handle_error("login", FORCE_QUIT); + } + + // Check whether some Facebook things are required + if (resp.headers["Location"].find("help.php") != std::string::npos) + { + client_notify(TranslateT("Login error: Some Facebook things are required.")); + parent->debugLogA(" ! ! Login error: Some Facebook things are required."); + // return handle_error("login", FORCE_QUIT); + } + + // Check whether setting Machine name is required + if (resp.headers["Location"].find("/checkpoint/") != std::string::npos) + { + resp = flap(REQUEST_SETUP_MACHINE, NULL, NULL, REQUEST_GET); + + if (resp.data.find("login_approvals_no_phones") != std::string::npos) { + // Code approval - but no phones in account + loginError(parent, utils::text::source_get_value(&resp.data, 4, "login_approvals_no_phones", "", "")); + return handle_error("login", FORCE_QUIT); + } + + std::string inner_data; + if (resp.data.find("name=\"submit[Continue]\"") != std::string::npos) { + + // Check if we need to approve also last unapproved device + if (resp.data.find("name=\"name_action_selected\"") == std::string::npos) { + // 1) Continue + inner_data = "submit[Continue]=Continue"; + inner_data += "&nh=" + utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\""); + inner_data += "&fb_dtsg=" + utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""); + resp = flap(REQUEST_SETUP_MACHINE, &inner_data); + + // 2) Approve last unknown login + // inner_data = "submit[I%20don't%20recognize]=I%20don't%20recognize"; // Don't recognize - this will force to change account password + inner_data = "submit[This%20is%20Okay]=This%20is%20Okay"; // Recognize + inner_data += "&nh=" + utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\""); + inner_data += "&fb_dtsg=" + utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""); + resp = flap(REQUEST_SETUP_MACHINE, &inner_data); + + // 3) Save last device + inner_data = "submit[Continue]=Continue"; + inner_data += "&nh=" + utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\""); + inner_data += "&fb_dtsg=" + utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""); + inner_data += "&name_action_selected=save_device"; // Save device - or "dont_save" + resp = flap(REQUEST_SETUP_MACHINE, &inner_data); + } + + // Save this actual device + inner_data = "submit[Continue]=Continue"; + inner_data += "&nh=" + utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\""); + inner_data += "&fb_dtsg=" + utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""); + inner_data += "&name_action_selected=save_device"; // Save device - or "dont_save" + resp = flap(REQUEST_SETUP_MACHINE, &inner_data); + } + + // Save actual machine name + // inner_data = "machine_name=Miranda%20NG&submit[Don't%20Save]=Don't%20Save"; // Don't save + inner_data = "machine_name=Miranda%20NG&submit[Save%20Device]=Save%20Device"; // Save + inner_data += "&lsd=" + utils::text::source_get_value(&resp.data, 3, "name=\"lsd\"", "value=\"", "\""); + inner_data += "&nh=" + utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\""); + inner_data += "&fb_dtsg=" + utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""); + + resp = flap(REQUEST_SETUP_MACHINE, &inner_data); + } + } + + switch (resp.code) + { + case HTTP_CODE_FAKE_DISCONNECTED: + { + // When is error only because timeout, try login once more + if (handle_error("login")) + return login(username, password); + else + return handle_error("login", FORCE_QUIT); + } + + case HTTP_CODE_OK: // OK page returned, but that is regular login page we don't want in fact + { + // Check whether captcha code is required + if (resp.data.find("id=\"captcha\"") != std::string::npos) + { + client_notify(TranslateT("Login error: Captcha code is required. Bad login credentials?")); + parent->debugLogA(" ! ! Login error: Captcha code is required."); + return handle_error("login", FORCE_QUIT); + } + + // Get and notify error message + loginError(parent, utils::text::source_get_value(&resp.data, 4, "login_error_box", "", "")); + } + case HTTP_CODE_FORBIDDEN: // Forbidden + case HTTP_CODE_NOT_FOUND: // Not Found + default: + return handle_error("login", FORCE_QUIT); + + case HTTP_CODE_FOUND: // Found and redirected to Home, Logged in, everything is OK + { + if (cookies.find("c_user") != cookies.end()) { + this->self_.user_id = cookies.find("c_user")->second; + parent->setString(FACEBOOK_KEY_ID, this->self_.user_id.c_str()); + parent->debugLogA(" Got self user id: %s", this->self_.user_id.c_str()); + return handle_success("login"); + } else { + client_notify(TranslateT("Login error, probably bad login credentials.")); + parent->debugLogA(" ! ! Login error, probably bad login credentials."); + return handle_error("login", FORCE_QUIT); + } + } + } +} + +bool facebook_client::logout() +{ + if (parent->getByte(FACEBOOK_KEY_DISABLE_LOGOUT, 0)) + return true; + + handle_entry("logout"); + + std::string data = "fb_dtsg=" + (this->dtsg_.length() ? this->dtsg_ : "0"); + data += "&ref=mb&h=" + this->logout_hash_; + + http::response resp = flap(REQUEST_LOGOUT, &data); + + if (hFcbCon) + Netlib_CloseHandle(hFcbCon); + hFcbCon = NULL; + + // Process result + username_ = password_ = self_.user_id = ""; + + switch (resp.code) + { + case HTTP_CODE_OK: + case HTTP_CODE_FOUND: + return handle_success("logout"); + + default: + return false; // Logout not finished properly, but..okay, who cares :P + } +} + +bool facebook_client::home() +{ + handle_entry("home"); + + // get fb_dtsg + http::response resp = flap(REQUEST_DTSG); + + // Check whether HTTPS connection is required and we don't have it enabled + if (!this->https_ && resp.headers["Location"].find("https://") != std::string::npos) { + client_notify(TranslateT("Your account requires HTTPS connection. Activating.")); + parent->setByte(FACEBOOK_KEY_FORCE_HTTPS, 1); + this->https_ = true; + return home(); + } + + this->dtsg_ = utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""); + parent->debugLogA(" Got self dtsg: %s", this->dtsg_.c_str()); + + if (this->dtsg_.empty()) + return false; + + resp = flap(REQUEST_HOME); + + switch (resp.code) + { + case HTTP_CODE_OK: + { + // Get real name + this->self_.real_name = utils::text::source_get_value(&resp.data, 2, "", ""); + + // Try to get name again, if we've got some some weird version of Facebook + if (this->self_.real_name.empty()) + this->self_.real_name = utils::text::source_get_value(&resp.data, 4, "id=\"root", "", ""); + + // Get and strip optional nickname + std::string::size_type pos = this->self_.real_name.find(""); + if (pos != std::string::npos) { + this->self_.nick = utils::text::source_get_value(&this->self_.real_name, 2, "(", ")"); + parent->debugLogA(" Got self nick name: %s", this->self_.nick.c_str()); + + this->self_.real_name = this->self_.real_name.substr(0, pos - 1); + } + parent->debugLogA(" Got self real name: %s", this->self_.real_name.c_str()); + + if (this->self_.real_name.empty()) { + client_notify(TranslateT("Something happened to Facebook. Maybe there was some major update so you should wait for an update.")); + return handle_error("home", FORCE_QUIT); + } + + // Save name and nickname + parent->SaveName(NULL, &this->self_); + + // Get avatar + this->self_.image_url = utils::text::source_get_value(&resp.data, 3, "class=\"l\"", "debugLogA(" Got self avatar: %s", this->self_.image_url.c_str()); + parent->CheckAvatarChange(NULL, this->self_.image_url); + + // Get logout hash + this->logout_hash_ = utils::text::source_get_value2(&resp.data, "/logout.php?h=", "&\""); + parent->debugLogA(" Got self logout hash: %s", this->logout_hash_.c_str()); + + return handle_success("home"); + } + case HTTP_CODE_FOUND: + // Work-around for replica_down, f**king hell what's that? + parent->debugLogA(" REPLICA_DOWN is back in force!"); + return this->home(); + + default: + return handle_error("home", FORCE_QUIT); + } +} + +bool facebook_client::chat_state(bool online) +{ + handle_entry("chat_state"); + + std::string data = (online ? "visibility=1" : "visibility=0"); + data += "&window_id=0"; + data += "&fb_dtsg=" + (dtsg_.length() ? dtsg_ : "0"); + data += "&phstamp=0&__user=" + self_.user_id; + http::response resp = flap(REQUEST_VISIBILITY, &data); + + return handle_success("chat_state"); +} + +bool facebook_client::reconnect() +{ + handle_entry("reconnect"); + + // Request reconnect + http::response resp = flap(REQUEST_RECONNECT); + + switch (resp.code) + { + case HTTP_CODE_OK: + { + this->chat_channel_ = utils::text::source_get_value(&resp.data, 2, "\"user_channel\":\"", "\""); + parent->debugLogA(" Got self channel: %s", this->chat_channel_.c_str()); + + this->chat_channel_partition_ = utils::text::source_get_value2(&resp.data, "\"partition\":", ",}"); + parent->debugLogA(" Got self channel partition: %s", this->chat_channel_partition_.c_str()); + + this->chat_channel_host_ = utils::text::source_get_value(&resp.data, 2, "\"host\":\"", "\""); + parent->debugLogA(" Got self channel host: %s", this->chat_channel_host_.c_str()); + + this->chat_sequence_num_ = utils::text::source_get_value2(&resp.data, "\"seq\":", ",}"); + parent->debugLogA(" Got self sequence number: %s", this->chat_sequence_num_.c_str()); + + this->chat_conn_num_ = utils::text::source_get_value2(&resp.data, "\"max_conn\":", ",}"); + parent->debugLogA(" Got self max_conn: %s", this->chat_conn_num_.c_str()); + + return handle_success("reconnect"); + } + + default: + return handle_error("reconnect", FORCE_DISCONNECT); + } +} + +bool facebook_client::buddy_list() +{ + handle_entry("buddy_list"); + + // Prepare update data + std::string data = "user=" + this->self_.user_id + "&fetch_mobile=true&phstamp=0&fb_dtsg=" + this->dtsg_ + "&__user=" + this->self_.user_id; + + { + ScopedLock s(buddies_lock_); + + data += "&cached_user_info_ids="; + int counter = 0; + for (List::Item< facebook_user >* i = buddies.begin(); i != NULL; i = i->next, counter++) + { + data += i->data->user_id + "%2C"; + } + } + + // Get buddy list + http::response resp = flap(REQUEST_BUDDY_LIST, &data); + + switch (resp.code) { + case HTTP_CODE_OK: + parent->ForkThread(&FacebookProto::ProcessBuddyList, new std::string(resp.data)); + return handle_success("buddy_list"); + + case HTTP_CODE_FAKE_ERROR: + case HTTP_CODE_FAKE_DISCONNECTED: + default: + return handle_error("buddy_list"); + } +} + +bool facebook_client::load_friends() +{ + handle_entry("load_friends"); + + // Get buddy list + http::response resp = flap(REQUEST_LOAD_FRIENDS); + + switch (resp.code) { + case HTTP_CODE_OK: + parent->ForkThread(&FacebookProto::ProcessFriendList, new std::string(resp.data)); + return handle_success("load_friends"); + + case HTTP_CODE_FAKE_ERROR: + case HTTP_CODE_FAKE_DISCONNECTED: + default: + return handle_error("load_friends"); + } +} + +bool facebook_client::feeds() +{ + handle_entry("feeds"); + + // Get feeds + http::response resp = flap(REQUEST_FEEDS); + + switch (resp.code) { + case HTTP_CODE_OK: + if (resp.data.find("\"num_stories\":0") == std::string::npos) + parent->ForkThread(&FacebookProto::ProcessFeeds, new std::string(resp.data)); + + return handle_success("feeds"); + + case HTTP_CODE_FAKE_ERROR: + case HTTP_CODE_FAKE_DISCONNECTED: + default: + return handle_error("feeds"); + } +} + +bool facebook_client::load_pages() +{ + if (!parent->getByte(FACEBOOK_KEY_LOAD_PAGES, DEFAULT_LOAD_PAGES)) + return true; + + handle_entry("load_pages"); + + // Get feeds + http::response resp = flap(REQUEST_PAGES); + + switch (resp.code) { + case HTTP_CODE_OK: + { + std::string content = utils::text::source_get_value(&resp.data, 2, "id=\"bookmarksSeeAllSection\"", ""); + + std::string::size_type start, end; + start = content.find(""); + + start = content.find("debugLogA(" Got page: %s (%s)", title.c_str(), id.c_str()); + pages[id] = title; + } + + return handle_success("load_pages"); + } + case HTTP_CODE_FAKE_ERROR: + case HTTP_CODE_FAKE_DISCONNECTED: + default: + return handle_error("load_pages"); + } +} + +bool facebook_client::channel() +{ + handle_entry("channel"); + + // Get update + http::response resp = flap(REQUEST_MESSAGES_RECEIVE); + + if (resp.data.empty()) { + // Something went wrong + return handle_error("channel"); + } + + std::string type = utils::text::source_get_value(&resp.data, 2, "\"t\":\"", "\""); + if (type == "continue" || type == "heartbeat") { + // Everything is OK, no new message received + } + else if (type == "lb") { + // Some new stuff (idk how does it work yet) + this->chat_channel_host_ = utils::text::source_get_value(&resp.data, 2, "\"vip\":\"", "\""); + parent->debugLogA(" Got self channel host: %s", this->chat_channel_host_.c_str()); + + this->chat_sticky_num_ = utils::text::source_get_value2(&resp.data, "\"sticky\":\"", "\""); + parent->debugLogA(" Got self sticky number: %s", this->chat_sticky_num_.c_str()); + } + else if (type == "fullReload" || type == "refresh") { + // Something went wrong (server flooding?) + parent->debugLogA("! ! ! Requested %s", type.c_str()); + + this->chat_sequence_num_ = utils::text::source_get_value2(&resp.data, "\"seq\":", ",}"); + parent->debugLogA(" Got self sequence number: %s", this->chat_sequence_num_.c_str()); + + this->chat_reconnect_reason_ = utils::text::source_get_value2(&resp.data, "\"reason\":", ",}"); + parent->debugLogA(" Reconnect reason: %s", this->chat_reconnect_reason_.c_str()); + + if (type == "refresh") + return this->reconnect(); + } + else { + // Something has been received, throw to new thread to process + std::string* response_data = new std::string(resp.data); + parent->ForkThread(&FacebookProto::ProcessMessages, response_data); + + // Increment sequence number + this->chat_sequence_num_ = utils::text::source_get_value2(&resp.data, "\"seq\":", ",}"); + parent->debugLogA(" Got self sequence number: %s", this->chat_sequence_num_.c_str()); + } + + // Return + switch (resp.code) + { + case HTTP_CODE_OK: + return handle_success("channel"); + + case HTTP_CODE_GATEWAY_TIMEOUT: + // Maybe we have same clientid as other connected client, try to generate different one + this->chat_clientid_ = utils::text::rand_string(8, "0123456789abcdef"); + + // Intentionally fall to handle_error() below + case HTTP_CODE_FAKE_DISCONNECTED: + case HTTP_CODE_FAKE_ERROR: + default: + return handle_error("channel"); + } +} + +bool facebook_client::send_message(std::string message_recipient, std::string message_text, std::string *error_text, MessageMethod method) +{ + ScopedLock s(send_message_lock_); + + handle_entry("send_message"); + + http::response resp; + + switch (method) { + case MESSAGE_INBOX: + { + parent->debugLogA(" > Sending message through INBOX"); + std::string data = "action=send"; + data += "&body=" + utils::url::encode(message_text); + data += "&recipients[0]=" + message_recipient; + data += "&__user=" + this->self_.user_id; + data += "&__a=1"; + data += "&fb_dtsg=" + (dtsg_.length() ? dtsg_ : "0"); + data += "&phstamp=0"; + + resp = flap(REQUEST_MESSAGE_SEND2, &data); + break; + } + case MESSAGE_MERCURY: + { + parent->debugLogA(" > Sending message through CHAT"); + std::string data = "message_batch[0][action_type]=ma-type:user-generated-message"; + data += "&message_batch[0][thread_id]"; + data += "&message_batch[0][author]=fbid:" + this->self_.user_id; + data += "&message_batch[0][author_email]"; + data += "&message_batch[0][coordinates]"; + data += "&message_batch[0][timestamp]=" + utils::time::mili_timestamp(); + data += "&message_batch[0][timestamp_absolute]"; + data += "&message_batch[0][timestamp_relative]"; + data += "&message_batch[0][is_unread]=false"; + data += "&message_batch[0][is_cleared]=false"; + data += "&message_batch[0][is_forward]=false"; + data += "&message_batch[0][spoof_warning]=false"; + data += "&message_batch[0][source]=source:chat:web"; + data += "&message_batch[0][source_tags][0]=source:chat"; + data += "&message_batch[0][body]=" + utils::url::encode(message_text); + data += "&message_batch[0][has_attachment]=false"; + data += "&message_batch[0][html_body]=false"; + data += "&message_batch[0][specific_to_list][0]=fbid:" + message_recipient; + data += "&message_batch[0][specific_to_list][1]=fbid:" + this->self_.user_id; + data += "&message_batch[0][status]=0"; + data += "&message_batch[0][message_id]"; + data += "&message_batch[0][client_thread_id]=user:" + message_recipient; + data += "&client=mercury"; + data += "&fb_dtsg=" + (dtsg_.length() ? dtsg_ : "0"); + data += "&__user=" + this->self_.user_id; + data += "&__a=1"; + data += "&phstamp=0"; + + resp = flap(REQUEST_MESSAGE_SEND, &data); + break; + } + case MESSAGE_TID: + { + parent->debugLogA(" > Sending message through MERCURY (TID)"); + std::string data = "message_batch[0][action_type]=ma-type:user-generated-message"; + data += "&message_batch[0][thread_id]=" + message_recipient; + data += "&message_batch[0][author]=fbid:" + this->self_.user_id; + data += "&message_batch[0][timestamp]=" + utils::time::mili_timestamp(); + data += "&message_batch[0][timestamp_absolute]="; + data += "&message_batch[0][timestamp_relative]="; + data += "&message_batch[0][is_unread]=false"; + data += "&message_batch[0][is_cleared]=false"; + data += "&message_batch[0][is_forward]=false"; + data += "&message_batch[0][source]=source:chat:web"; + data += "&message_batch[0][body]=" + utils::url::encode(message_text); + data += "&message_batch[0][has_attachment]=false"; + data += "&message_batch[0][is_html]=false"; + data += "&message_batch[0][message_id]="; + data += "&fb_dtsg=" + (dtsg_.length() ? dtsg_ : "0"); + data += "&__user=" + this->self_.user_id; + data += "&phstamp=0"; + + resp = flap(REQUEST_MESSAGE_SEND, &data); + break; + } + case MESSAGE_ASYNC: + { + parent->debugLogA(" > Sending message through ASYNC"); + std::string data = "action=send"; + data += "&body=" + utils::url::encode(message_text); + data += "&recipients[0]=" + message_recipient; + data += "&lsd="; + data += "&fb_dtsg=" + (dtsg_.length() ? dtsg_ : "0"); + + resp = flap(REQUEST_ASYNC, &data); + break; + } + } + + *error_text = resp.error_text; + + switch (resp.error_number) + { + case 0: // Everything is OK + { + // Remember this message id + std::string mid = utils::text::source_get_value(&resp.data, 2, "\"message_id\":\"", "\""); + messages_ignore.insert(std::make_pair(mid, false)); + } break; + + //case 1356002: // You are offline - wtf?? + + case 1356003: // Contact is offline + { + MCONTACT hContact = parent->ContactIDToHContact(message_recipient); + if (hContact != NULL) + parent->setWord(hContact, "Status", ID_STATUS_OFFLINE); + return false; + } break; + + case 1356026: // Contact has alternative client + { + client_notify(TranslateT("Need confirmation for sending messages to other clients.\nOpen Facebook website and try to send message to this contact again!")); + return false; + } break; + + default: // Other error + parent->debugLogA(" !!! Send message error #%d: %s", resp.error_number, resp.error_text); + return false; + } + + switch (resp.code) + { + case HTTP_CODE_OK: + return handle_success("send_message"); + + case HTTP_CODE_FAKE_ERROR: + case HTTP_CODE_FAKE_DISCONNECTED: + default: + *error_text = Translate("Timeout when sending message."); + + handle_error("send_message"); + return false; + } +} + +bool facebook_client::post_status(status_data *status) +{ + if (status == NULL || (status->text.empty() && status->url.empty())) + return false; + + handle_entry("post_status"); + + if (status->isPage) { + std::string data = "fb_dtsg=" + (this->dtsg_.length() ? this->dtsg_ : "0"); + data += "&user_id=" + status->user_id; + data += "&url=" + std::string(FACEBOOK_URL_HOMEPAGE); + flap(REQUEST_IDENTITY_SWITCH, &data); + } + + std::string data; + RequestType request = REQUEST_POST_STATUS; + + if (!status->url.empty()) { + data = "fb_dtsg=" + (this->dtsg_.length() ? this->dtsg_ : "0"); + data += "&composerid=u_jsonp_2_b"; + data += "&targetid=" + (status->user_id.empty() ? this->self_.user_id : status->user_id); + data += "&istimeline=1&composercontext=composer&onecolumn=1&nctr[_mod]=pagelet_timeline_recent&__a=1&ttstamp=0"; + data += "&__user=" + (status->isPage && !status->user_id.empty() ? status->user_id : this->self_.user_id); + data += "&loaded_components[0]=maininput&loaded_components[1]=backdateicon&loaded_components[2]=withtaggericon&loaded_components[3]=cameraicon&loaded_components[4]=placetaggericon&loaded_components[5]=mainprivacywidget&loaded_components[6]=withtaggericon&loaded_components[7]=backdateicon&loaded_components[8]=placetaggericon&loaded_components[9]=cameraicon&loaded_components[10]=mainprivacywidget&loaded_components[11]=maininput&loaded_components[12]=explicitplaceinput&loaded_components[13]=hiddenplaceinput&loaded_components[14]=placenameinput&loaded_components[15]=hiddensessionid&loaded_components[16]=withtagger&loaded_components[17]=backdatepicker&loaded_components[18]=placetagger&loaded_components[19]=citysharericon"; + http::response resp = flap(REQUEST_LINK_SCRAPER, &data, &status->url); + resp.data = utils::text::special_expressions_decode(utils::text::slashu_to_utf8(resp.data)); + + data = "&xhpc_context=profile&xhpc_ismeta=1&xhpc_timeline=1&xhpc_composerid=u_jsonp_2_0&is_explicit_place=&composertags_place=&composer_session_id=&composertags_city=&disable_location_sharing=false&composer_predicted_city=&nctr[_mod]=pagelet_composer&__a=1&__dyn=&__req=1f&ttstamp=0"; + std::string form = utils::text::source_get_value(&resp.data, 2, ""); + utils::text::replace_all(&form, "\\\"", "\""); + data += "&" + utils::text::source_get_form_data(&form) + "&"; + //data += "&no_picture=0"; + + request = REQUEST_STATUS_COMPOSER; + } + + std::string text = utils::url::encode(status->text); + + data += "fb_dtsg=" + (this->dtsg_.length() ? this->dtsg_ : "0"); + data += "&xhpc_targetid=" + (status->user_id.empty() ? this->self_.user_id : status->user_id); + data += "&__user=" + (status->isPage && !status->user_id.empty() ? status->user_id : this->self_.user_id); + data += "&xhpc_message=" + text; + data += "&xhpc_message_text=" + text; + if (!status->isPage) + data += "&audience[0][value]=" + get_privacy_type(); + if (!status->place.empty()) { + data += "&composertags_place_name="; + data += utils::url::encode(status->place); + } + for(std::vector::size_type i = 0; i < status->users.size(); i++) { + data += "&composertags_with[" + utils::conversion::to_string(&i, UTILS_CONV_UNSIGNED_NUMBER); + data += "]=" + status->users[i]->user_id; + data += "&text_composertags_with[" + utils::conversion::to_string(&i, UTILS_CONV_UNSIGNED_NUMBER); + data += "]=" + status->users[i]->real_name; + delete status->users[i]; + } + status->users.clear(); + + data += "&xhpc_context=profile&xhpc_ismeta=1&xhpc_timeline=1&xhpc_composerid=u_0_2y&is_explicit_place=&composertags_place=&composertags_city="; + + http::response resp = flap(request, &data); + + if (status->isPage) { + std::string data = "fb_dtsg=" + (this->dtsg_.length() ? this->dtsg_ : "0"); + data += "&user_id=" + this->self_.user_id; + data += "&url=" + std::string(FACEBOOK_URL_HOMEPAGE); + flap(REQUEST_IDENTITY_SWITCH, &data); + } + + if (resp.isValid()) { + parent->NotifyEvent(parent->m_tszUserName, TranslateT("Status update was successful."), NULL, FACEBOOK_EVENT_OTHER); + return handle_success("post_status"); + } + + return handle_error("post_status"); +} + +////////////////////////////////////////////////////////////////////////////// + +bool facebook_client::save_url(const std::string &url,const std::tstring &filename, HANDLE &nlc) +{ + NETLIBHTTPREQUEST req = {sizeof(req)}; + NETLIBHTTPREQUEST *resp; + req.requestType = REQUEST_GET; + req.szUrl = const_cast(url.c_str()); + req.flags = NLHRF_HTTP11 | NLHRF_REDIRECT | NLHRF_PERSISTENT | NLHRF_NODUMP; + req.nlc = nlc; + + resp = reinterpret_cast(CallService(MS_NETLIB_HTTPTRANSACTION, + reinterpret_cast(this->parent->m_hNetlibUser), reinterpret_cast(&req))); + + bool ret = false; + + if (resp) { + nlc = resp->nlc; + parent->debugLogA("@@@@@ Saving avatar URL %s to path %s", url.c_str(), _T2A(filename.c_str())); + + // Create folder if necessary + std::tstring dir = filename.substr(0,filename.rfind('\\')); + if (_taccess(dir.c_str(), 0)) + CreateDirectoryTreeT(dir.c_str()); + + // Write to file + FILE *f = _tfopen(filename.c_str(), _T("wb")); + if (f != NULL) { + fwrite(resp->pData,1,resp->dataLength,f); + fclose(f); + + ret = _taccess(filename.c_str(), 0) == 0; + } + + CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp); + } else { + nlc = NULL; + } + + return ret; +} diff --git a/protocols/FacebookRM/src/contacts.cpp b/protocols/FacebookRM/src/contacts.cpp index 4b7b0c4bdb..241782ddca 100644 --- a/protocols/FacebookRM/src/contacts.cpp +++ b/protocols/FacebookRM/src/contacts.cpp @@ -1,418 +1,418 @@ -/* - -Facebook plugin for Miranda Instant Messenger -_____________________________________________ - -Copyright © 2009-11 Michal Zelinka, 2011-13 Robert Pösel - -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 2 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 . - -*/ - -#include "common.h" - -void updateStringUtf(FacebookProto *proto, MCONTACT hContact, const char *key, std::string value) { - bool update_required = true; - - DBVARIANT dbv; - if (!proto->getStringUtf(hContact, key, &dbv)) { - update_required = strcmp(dbv.pszVal, value.c_str()) != 0; - db_free(&dbv); - } - - if (update_required) { - proto->setStringUtf(hContact, key, value.c_str()); - } -} - -void FacebookProto::SaveName(MCONTACT hContact, const facebook_user *fbu) -{ - // Save nick - std::string nick = fbu->real_name; - if (!getBool(FACEBOOK_KEY_NAME_AS_NICK, 1) && !fbu->nick.empty()) - nick = fbu->nick; - - updateStringUtf(this, hContact, FACEBOOK_KEY_NICK, nick); - - // Explode whole name into first, second and last name - std::vector names; - utils::text::explode(fbu->real_name, " ", &names); - - updateStringUtf(this, hContact, FACEBOOK_KEY_FIRST_NAME, names.front().c_str()); - updateStringUtf(this, hContact, FACEBOOK_KEY_LAST_NAME, names.back().c_str()); - - std::string middle = ""; - if (names.size() > 2) { - for (std::string::size_type i = 1; i < names.size() - 1; i++) { - if (!middle.empty()) - middle += " "; - - middle += names.at(i); - } - } - updateStringUtf(this, hContact, FACEBOOK_KEY_SECOND_NAME, middle); -} - -bool FacebookProto::IsMyContact(MCONTACT hContact, bool include_chat) -{ - const char *proto = GetContactProto(hContact); - if (proto && !strcmp(m_szModuleName, proto)) { - if (include_chat) - return true; - return !isChatRoom(hContact); - } - return false; -} - -MCONTACT FacebookProto::ChatIDToHContact(std::tstring chat_id) -{ - for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) { - if (!IsMyContact(hContact, true)) - continue; - - ptrT id(getTStringA(hContact, "ChatRoomID")); - if (id && !_tcscmp(id, chat_id.c_str())) - return hContact; - } - - return 0; -} - -MCONTACT FacebookProto::ContactIDToHContact(std::string user_id) -{ - for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) { - if (!IsMyContact(hContact)) - continue; - - ptrA id(getStringA(hContact, FACEBOOK_KEY_ID)); - if (id && !strcmp(id, user_id.c_str())) - return hContact; - } - - return 0; -} - -std::string FacebookProto::ThreadIDToContactID(std::string thread_id) -{ - std::string user_id; - - for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) { - if (!IsMyContact(hContact)) - continue; - - ptrA tid(getStringA(hContact, FACEBOOK_KEY_TID)); - if (tid && !strcmp(tid, thread_id.c_str())) { - user_id = ptrA( getStringA(hContact, FACEBOOK_KEY_ID)); - return user_id; - } - } - - // We don't have any contact with thish thread_id cached, we must ask server - - std::string data = "client=mercury"; - data += "&__user=" + facy.self_.user_id; - data += "&fb_dtsg=" + (facy.dtsg_.length() ? facy.dtsg_ : "0"); - data += "&__a=1&__dyn=&__req=&ttstamp=0"; - data += "&threads[thread_ids][0]=" + utils::url::encode(thread_id); - - http::response resp = facy.flap(REQUEST_THREAD_INFO, &data); - - if (resp.code == HTTP_CODE_OK) { - CODE_BLOCK_TRY - - facebook_json_parser* p = new facebook_json_parser(this); - p->parse_thread_info(&resp.data, &user_id); - delete p; - - debugLogA("***** Thread info processed"); - - CODE_BLOCK_CATCH - - debugLogA("***** Error processing thread info: %s", e.what()); - - CODE_BLOCK_END - } - - return user_id; -} - -void FacebookProto::LoadContactInfo(facebook_user* fbu) -{ - // TODO: support for more friends at once - std::string get_query = "&ids[0]=" + utils::url::encode(fbu->user_id); - - http::response resp = facy.flap(REQUEST_LOAD_FRIEND, NULL, &get_query); - - if (resp.code == HTTP_CODE_OK) { - CODE_BLOCK_TRY - - facebook_json_parser* p = new facebook_json_parser(this); - p->parse_user_info(&resp.data, fbu); - delete p; - - debugLogA("***** Thread info processed"); - - CODE_BLOCK_CATCH - - debugLogA("***** Error processing thread info: %s", e.what()); - - CODE_BLOCK_END - } -} - -MCONTACT FacebookProto::AddToContactList(facebook_user* fbu, ContactType type, bool dont_check) -{ - MCONTACT hContact; - - if (!dont_check) { - // First, check if this contact exists - hContact = ContactIDToHContact(fbu->user_id); - if(hContact) - return hContact; - } - - // If not, make a new contact! - hContact = (MCONTACT)CallService(MS_DB_CONTACT_ADD, 0, 0); - if(hContact) - { - if(CallService(MS_PROTO_ADDTOCONTACT,hContact,(LPARAM)m_szModuleName) == 0) - { - setString(hContact, FACEBOOK_KEY_ID, fbu->user_id.c_str()); - - std::string homepage = FACEBOOK_URL_PROFILE + fbu->user_id; - setString(hContact, "Homepage", homepage.c_str()); - setTString(hContact, "MirVer", fbu->getMirVer()); - - db_unset(hContact, "CList", "MyHandle"); - - ptrT group( getTStringA(NULL, FACEBOOK_KEY_DEF_GROUP)); - if (group) - db_set_ts(hContact, "CList", "Group", group); - - if (!fbu->real_name.empty()) { - SaveName(hContact, fbu); - } - - if (fbu->gender) - setByte(hContact, "Gender", fbu->gender); - - if (!fbu->image_url.empty()) - setString(hContact, FACEBOOK_KEY_AV_URL, fbu->image_url.c_str()); - - setByte(hContact, FACEBOOK_KEY_CONTACT_TYPE, type); - - if (getByte(FACEBOOK_KEY_DISABLE_STATUS_NOTIFY, 0)) - CallService(MS_IGNORE_IGNORE, hContact, (LPARAM)IGNOREEVENT_USERONLINE); - - return hContact; - } - - CallService(MS_DB_CONTACT_DELETE,hContact,0); - } - - return 0; -} - -void FacebookProto::SetAllContactStatuses(int status) -{ - for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) { - if (isChatRoom(hContact)) - continue; - - if (getWord(hContact, "Status", 0) != status) - setWord(hContact, "Status", status); - } -} - -void FacebookProto::DeleteContactFromServer(void *data) -{ - facy.handle_entry("DeleteContactFromServer"); - - if (data == NULL) - return; - - std::string id = (*(std::string*)data); - delete data; - - std::string query = "norefresh=true&unref=button_dropdown&confirmed=1&phstamp=0&__a=1"; - query += "&fb_dtsg=" + facy.dtsg_; - query += "&uid=" + id; - query += "&__user=" + facy.self_.user_id; - - std::string get_query = "norefresh=true&unref=button_dropdown&uid=" + id; - - // Get unread inbox threads - http::response resp = facy.flap(REQUEST_DELETE_FRIEND, &query, &get_query); - - if (resp.data.find("\"payload\":null", 0) != std::string::npos) - { - facebook_user* fbu = facy.buddies.find(id); - if (fbu != NULL) - fbu->deleted = true; - - MCONTACT hContact = ContactIDToHContact(id); - - // If contact wasn't deleted from database - if (hContact != NULL) { - setWord(hContact, "Status", ID_STATUS_OFFLINE); - setByte(hContact, FACEBOOK_KEY_CONTACT_TYPE, CONTACT_NONE); - setDword(hContact, FACEBOOK_KEY_DELETED, ::time(NULL)); - } - - NotifyEvent(m_tszUserName, TranslateT("Contact was removed from your server list."), NULL, FACEBOOK_EVENT_OTHER); - } else { - facy.client_notify(TranslateT("Error occurred when removing contact from server.")); - } - - if (resp.code != HTTP_CODE_OK) - facy.handle_error("DeleteContactFromServer"); -} - -void FacebookProto::AddContactToServer(void *data) -{ - facy.handle_entry("AddContactToServer"); - - if (data == NULL) - return; - - std::string id = (*(std::string*)data); - delete data; - - std::string query = "action=add_friend&how_found=profile_button&ref_param=ts&outgoing_id=&unwanted=&logging_location=&no_flyout_on_click=false&ego_log_data=&lsd="; - query += "&fb_dtsg=" + facy.dtsg_; - query += "&to_friend=" + id; - query += "&__user=" + facy.self_.user_id; - - // Get unread inbox threads - http::response resp = facy.flap(REQUEST_REQUEST_FRIEND, &query); - - if (resp.data.find("\"success\":true", 0) != std::string::npos) { - MCONTACT hContact = ContactIDToHContact(id); - - // If contact wasn't deleted from database - if (hContact != NULL) - setByte(hContact, FACEBOOK_KEY_CONTACT_TYPE, CONTACT_REQUEST); - - NotifyEvent(m_tszUserName, TranslateT("Request for friendship was sent."), NULL, FACEBOOK_EVENT_OTHER); - } - else facy.client_notify(TranslateT("Error occurred when requesting friendship.")); - - if (resp.code != HTTP_CODE_OK) - facy.handle_error("AddContactToServer"); - -} - -void FacebookProto::ApproveContactToServer(void *data) -{ - facy.handle_entry("ApproveContactToServer"); - - if (data == NULL) - return; - - MCONTACT hContact = *(MCONTACT*)data; - delete data; - - std::string post_data = "fb_dtsg=" + facy.dtsg_; - post_data += "&charset_test=%e2%82%ac%2c%c2%b4%2c%e2%82%ac%2c%c2%b4%2c%e6%b0%b4%2c%d0%94%2c%d0%84&confirm_button="; - - std::string get_data = "id="; - - ptrA id(getStringA(hContact, FACEBOOK_KEY_ID)); - get_data += id; - - http::response resp = facy.flap(REQUEST_APPROVE_FRIEND, &post_data, &get_data); - - setByte(hContact, FACEBOOK_KEY_CONTACT_TYPE, CONTACT_FRIEND); -} - -void FacebookProto::CancelFriendsRequest(void *data) -{ - facy.handle_entry("CancelFriendsRequest"); - - if (data == NULL) - return; - - MCONTACT hContact = *(MCONTACT*)data; - delete data; - - std::string query = "phstamp=0&confirmed=1"; - query += "&fb_dtsg=" + facy.dtsg_; - query += "&__user=" + facy.self_.user_id; - - ptrA id(getStringA(hContact, FACEBOOK_KEY_ID)); - query += "&friend=" + std::string(id); - - // Get unread inbox threads - http::response resp = facy.flap(REQUEST_CANCEL_REQUEST, &query); - - if (resp.data.find("\"payload\":null", 0) != std::string::npos) - { - setByte(hContact, FACEBOOK_KEY_CONTACT_TYPE, CONTACT_NONE); - NotifyEvent(m_tszUserName, TranslateT("Request for friendship was canceled."), NULL, FACEBOOK_EVENT_OTHER); - } - else facy.client_notify(TranslateT("Error occurred when canceling friendship request.")); - - if (resp.code != HTTP_CODE_OK) - facy.handle_error("CancelFriendsRequest"); -} - -void FacebookProto::SendPokeWorker(void *p) -{ - facy.handle_entry("SendPokeWorker"); - - if (p == NULL) - return; - - std::string id = (*(std::string*)p); - delete p; - - std::string data = "poke_target=" + id; - data += "&do_confirm=0&phstamp=0"; - data += "&fb_dtsg=" + (facy.dtsg_.length() ? facy.dtsg_ : "0"); - data += "&__user=" + facy.self_.user_id; - - // Send poke - http::response resp = facy.flap(REQUEST_POKE, &data); - - if (resp.data.find("\"payload\":null", 0) != std::string::npos) { - resp.data = utils::text::slashu_to_utf8( - utils::text::source_get_value(&resp.data, 2, "__html\":\"", "\"}")); - - std::string message = utils::text::source_get_value(&resp.data, 4, "", "<\\/div>"); - - if (message.empty()) // message has different format, show whole message - message = resp.data; - - message = utils::text::special_expressions_decode( - utils::text::remove_html(message)); - - ptrT tmessage( mir_utf8decodeT(message.c_str())); - NotifyEvent(m_tszUserName, tmessage, NULL, FACEBOOK_EVENT_OTHER); - } - - facy.handle_success("SendPokeWorker"); -} - - -HANDLE FacebookProto::GetAwayMsg(MCONTACT hContact) -{ - return 0; // Status messages are disabled -} - -int FacebookProto::OnContactDeleted(WPARAM wParam,LPARAM) -{ - CancelFriendship(wParam, 1); - - return 0; -} +/* + +Facebook plugin for Miranda Instant Messenger +_____________________________________________ + +Copyright � 2009-11 Michal Zelinka, 2011-13 Robert P�sel + +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 2 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 . + +*/ + +#include "common.h" + +void updateStringUtf(FacebookProto *proto, MCONTACT hContact, const char *key, std::string value) { + bool update_required = true; + + DBVARIANT dbv; + if (!proto->getStringUtf(hContact, key, &dbv)) { + update_required = strcmp(dbv.pszVal, value.c_str()) != 0; + db_free(&dbv); + } + + if (update_required) { + proto->setStringUtf(hContact, key, value.c_str()); + } +} + +void FacebookProto::SaveName(MCONTACT hContact, const facebook_user *fbu) +{ + // Save nick + std::string nick = fbu->real_name; + if (!getBool(FACEBOOK_KEY_NAME_AS_NICK, 1) && !fbu->nick.empty()) + nick = fbu->nick; + + updateStringUtf(this, hContact, FACEBOOK_KEY_NICK, nick); + + // Explode whole name into first, second and last name + std::vector names; + utils::text::explode(fbu->real_name, " ", &names); + + updateStringUtf(this, hContact, FACEBOOK_KEY_FIRST_NAME, names.size() > 0 ? names.front().c_str() : ""); + updateStringUtf(this, hContact, FACEBOOK_KEY_LAST_NAME, names.size() > 1 ? names.back().c_str() : ""); + + std::string middle = ""; + if (names.size() > 2) { + for (std::string::size_type i = 1; i < names.size() - 1; i++) { + if (!middle.empty()) + middle += " "; + + middle += names.at(i); + } + } + updateStringUtf(this, hContact, FACEBOOK_KEY_SECOND_NAME, middle); +} + +bool FacebookProto::IsMyContact(MCONTACT hContact, bool include_chat) +{ + const char *proto = GetContactProto(hContact); + if (proto && !strcmp(m_szModuleName, proto)) { + if (include_chat) + return true; + return !isChatRoom(hContact); + } + return false; +} + +MCONTACT FacebookProto::ChatIDToHContact(std::tstring chat_id) +{ + for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) { + if (!IsMyContact(hContact, true)) + continue; + + ptrT id(getTStringA(hContact, "ChatRoomID")); + if (id && !_tcscmp(id, chat_id.c_str())) + return hContact; + } + + return 0; +} + +MCONTACT FacebookProto::ContactIDToHContact(std::string user_id) +{ + for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) { + if (!IsMyContact(hContact)) + continue; + + ptrA id(getStringA(hContact, FACEBOOK_KEY_ID)); + if (id && !strcmp(id, user_id.c_str())) + return hContact; + } + + return 0; +} + +std::string FacebookProto::ThreadIDToContactID(std::string thread_id) +{ + std::string user_id; + + for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) { + if (!IsMyContact(hContact)) + continue; + + ptrA tid(getStringA(hContact, FACEBOOK_KEY_TID)); + if (tid && !strcmp(tid, thread_id.c_str())) { + user_id = ptrA( getStringA(hContact, FACEBOOK_KEY_ID)); + return user_id; + } + } + + // We don't have any contact with thish thread_id cached, we must ask server + + std::string data = "client=mercury"; + data += "&__user=" + facy.self_.user_id; + data += "&fb_dtsg=" + (facy.dtsg_.length() ? facy.dtsg_ : "0"); + data += "&__a=1&__dyn=&__req=&ttstamp=0"; + data += "&threads[thread_ids][0]=" + utils::url::encode(thread_id); + + http::response resp = facy.flap(REQUEST_THREAD_INFO, &data); + + if (resp.code == HTTP_CODE_OK) { + CODE_BLOCK_TRY + + facebook_json_parser* p = new facebook_json_parser(this); + p->parse_thread_info(&resp.data, &user_id); + delete p; + + debugLogA("***** Thread info processed"); + + CODE_BLOCK_CATCH + + debugLogA("***** Error processing thread info: %s", e.what()); + + CODE_BLOCK_END + } + + return user_id; +} + +void FacebookProto::LoadContactInfo(facebook_user* fbu) +{ + // TODO: support for more friends at once + std::string get_query = "&ids[0]=" + utils::url::encode(fbu->user_id); + + http::response resp = facy.flap(REQUEST_LOAD_FRIEND, NULL, &get_query); + + if (resp.code == HTTP_CODE_OK) { + CODE_BLOCK_TRY + + facebook_json_parser* p = new facebook_json_parser(this); + p->parse_user_info(&resp.data, fbu); + delete p; + + debugLogA("***** Thread info processed"); + + CODE_BLOCK_CATCH + + debugLogA("***** Error processing thread info: %s", e.what()); + + CODE_BLOCK_END + } +} + +MCONTACT FacebookProto::AddToContactList(facebook_user* fbu, ContactType type, bool dont_check) +{ + MCONTACT hContact; + + if (!dont_check) { + // First, check if this contact exists + hContact = ContactIDToHContact(fbu->user_id); + if(hContact) + return hContact; + } + + // If not, make a new contact! + hContact = (MCONTACT)CallService(MS_DB_CONTACT_ADD, 0, 0); + if(hContact) + { + if(CallService(MS_PROTO_ADDTOCONTACT,hContact,(LPARAM)m_szModuleName) == 0) + { + setString(hContact, FACEBOOK_KEY_ID, fbu->user_id.c_str()); + + std::string homepage = FACEBOOK_URL_PROFILE + fbu->user_id; + setString(hContact, "Homepage", homepage.c_str()); + setTString(hContact, "MirVer", fbu->getMirVer()); + + db_unset(hContact, "CList", "MyHandle"); + + ptrT group( getTStringA(NULL, FACEBOOK_KEY_DEF_GROUP)); + if (group) + db_set_ts(hContact, "CList", "Group", group); + + if (!fbu->real_name.empty()) { + SaveName(hContact, fbu); + } + + if (fbu->gender) + setByte(hContact, "Gender", fbu->gender); + + if (!fbu->image_url.empty()) + setString(hContact, FACEBOOK_KEY_AV_URL, fbu->image_url.c_str()); + + setByte(hContact, FACEBOOK_KEY_CONTACT_TYPE, type); + + if (getByte(FACEBOOK_KEY_DISABLE_STATUS_NOTIFY, 0)) + CallService(MS_IGNORE_IGNORE, hContact, (LPARAM)IGNOREEVENT_USERONLINE); + + return hContact; + } + + CallService(MS_DB_CONTACT_DELETE,hContact,0); + } + + return 0; +} + +void FacebookProto::SetAllContactStatuses(int status) +{ + for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) { + if (isChatRoom(hContact)) + continue; + + if (getWord(hContact, "Status", 0) != status) + setWord(hContact, "Status", status); + } +} + +void FacebookProto::DeleteContactFromServer(void *data) +{ + facy.handle_entry("DeleteContactFromServer"); + + if (data == NULL) + return; + + std::string id = (*(std::string*)data); + delete data; + + std::string query = "norefresh=true&unref=button_dropdown&confirmed=1&phstamp=0&__a=1"; + query += "&fb_dtsg=" + facy.dtsg_; + query += "&uid=" + id; + query += "&__user=" + facy.self_.user_id; + + std::string get_query = "norefresh=true&unref=button_dropdown&uid=" + id; + + // Get unread inbox threads + http::response resp = facy.flap(REQUEST_DELETE_FRIEND, &query, &get_query); + + if (resp.data.find("\"payload\":null", 0) != std::string::npos) + { + facebook_user* fbu = facy.buddies.find(id); + if (fbu != NULL) + fbu->deleted = true; + + MCONTACT hContact = ContactIDToHContact(id); + + // If contact wasn't deleted from database + if (hContact != NULL) { + setWord(hContact, "Status", ID_STATUS_OFFLINE); + setByte(hContact, FACEBOOK_KEY_CONTACT_TYPE, CONTACT_NONE); + setDword(hContact, FACEBOOK_KEY_DELETED, ::time(NULL)); + } + + NotifyEvent(m_tszUserName, TranslateT("Contact was removed from your server list."), NULL, FACEBOOK_EVENT_OTHER); + } else { + facy.client_notify(TranslateT("Error occurred when removing contact from server.")); + } + + if (resp.code != HTTP_CODE_OK) + facy.handle_error("DeleteContactFromServer"); +} + +void FacebookProto::AddContactToServer(void *data) +{ + facy.handle_entry("AddContactToServer"); + + if (data == NULL) + return; + + std::string id = (*(std::string*)data); + delete data; + + std::string query = "action=add_friend&how_found=profile_button&ref_param=ts&outgoing_id=&unwanted=&logging_location=&no_flyout_on_click=false&ego_log_data=&lsd="; + query += "&fb_dtsg=" + facy.dtsg_; + query += "&to_friend=" + id; + query += "&__user=" + facy.self_.user_id; + + // Get unread inbox threads + http::response resp = facy.flap(REQUEST_REQUEST_FRIEND, &query); + + if (resp.data.find("\"success\":true", 0) != std::string::npos) { + MCONTACT hContact = ContactIDToHContact(id); + + // If contact wasn't deleted from database + if (hContact != NULL) + setByte(hContact, FACEBOOK_KEY_CONTACT_TYPE, CONTACT_REQUEST); + + NotifyEvent(m_tszUserName, TranslateT("Request for friendship was sent."), NULL, FACEBOOK_EVENT_OTHER); + } + else facy.client_notify(TranslateT("Error occurred when requesting friendship.")); + + if (resp.code != HTTP_CODE_OK) + facy.handle_error("AddContactToServer"); + +} + +void FacebookProto::ApproveContactToServer(void *data) +{ + facy.handle_entry("ApproveContactToServer"); + + if (data == NULL) + return; + + MCONTACT hContact = *(MCONTACT*)data; + delete data; + + std::string post_data = "fb_dtsg=" + facy.dtsg_; + post_data += "&charset_test=%e2%82%ac%2c%c2%b4%2c%e2%82%ac%2c%c2%b4%2c%e6%b0%b4%2c%d0%94%2c%d0%84&confirm_button="; + + std::string get_data = "id="; + + ptrA id(getStringA(hContact, FACEBOOK_KEY_ID)); + get_data += id; + + http::response resp = facy.flap(REQUEST_APPROVE_FRIEND, &post_data, &get_data); + + setByte(hContact, FACEBOOK_KEY_CONTACT_TYPE, CONTACT_FRIEND); +} + +void FacebookProto::CancelFriendsRequest(void *data) +{ + facy.handle_entry("CancelFriendsRequest"); + + if (data == NULL) + return; + + MCONTACT hContact = *(MCONTACT*)data; + delete data; + + std::string query = "phstamp=0&confirmed=1"; + query += "&fb_dtsg=" + facy.dtsg_; + query += "&__user=" + facy.self_.user_id; + + ptrA id(getStringA(hContact, FACEBOOK_KEY_ID)); + query += "&friend=" + std::string(id); + + // Get unread inbox threads + http::response resp = facy.flap(REQUEST_CANCEL_REQUEST, &query); + + if (resp.data.find("\"payload\":null", 0) != std::string::npos) + { + setByte(hContact, FACEBOOK_KEY_CONTACT_TYPE, CONTACT_NONE); + NotifyEvent(m_tszUserName, TranslateT("Request for friendship was canceled."), NULL, FACEBOOK_EVENT_OTHER); + } + else facy.client_notify(TranslateT("Error occurred when canceling friendship request.")); + + if (resp.code != HTTP_CODE_OK) + facy.handle_error("CancelFriendsRequest"); +} + +void FacebookProto::SendPokeWorker(void *p) +{ + facy.handle_entry("SendPokeWorker"); + + if (p == NULL) + return; + + std::string id = (*(std::string*)p); + delete p; + + std::string data = "poke_target=" + id; + data += "&do_confirm=0&phstamp=0"; + data += "&fb_dtsg=" + (facy.dtsg_.length() ? facy.dtsg_ : "0"); + data += "&__user=" + facy.self_.user_id; + + // Send poke + http::response resp = facy.flap(REQUEST_POKE, &data); + + if (resp.data.find("\"payload\":null", 0) != std::string::npos) { + resp.data = utils::text::slashu_to_utf8( + utils::text::source_get_value(&resp.data, 2, "__html\":\"", "\"}")); + + std::string message = utils::text::source_get_value(&resp.data, 4, "", "<\\/div>"); + + if (message.empty()) // message has different format, show whole message + message = resp.data; + + message = utils::text::special_expressions_decode( + utils::text::remove_html(message)); + + ptrT tmessage( mir_utf8decodeT(message.c_str())); + NotifyEvent(m_tszUserName, tmessage, NULL, FACEBOOK_EVENT_OTHER); + } + + facy.handle_success("SendPokeWorker"); +} + + +HANDLE FacebookProto::GetAwayMsg(MCONTACT hContact) +{ + return 0; // Status messages are disabled +} + +int FacebookProto::OnContactDeleted(WPARAM wParam,LPARAM) +{ + CancelFriendship(wParam, 1); + + return 0; +} -- cgit v1.2.3