diff options
author | Robert Pösel <robyer@seznam.cz> | 2014-05-10 17:38:54 +0000 |
---|---|---|
committer | Robert Pösel <robyer@seznam.cz> | 2014-05-10 17:38:54 +0000 |
commit | e3c0c006f5e87f16060b09b59f24e02a3faccf44 (patch) | |
tree | 4a893b9f7cbccad54f9737620917aa70cbdca58f /protocols/FacebookRM/src/communication.cpp | |
parent | 4ff152f48ca945be4a0065a5f57246449c741f8a (diff) |
Facebook: Fix not working login for some people
git-svn-id: http://svn.miranda-ng.org/main/trunk@9156 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c
Diffstat (limited to 'protocols/FacebookRM/src/communication.cpp')
-rw-r--r-- | protocols/FacebookRM/src/communication.cpp | 2916 |
1 files changed, 1460 insertions, 1456 deletions
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 <http://www.gnu.org/licenses/>.
-
-*/
-
-#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<std::string, facebook_notification*>::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", "<div", ">", "</div>"));
- 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", "<div", ">", "</div>"));
- }
- 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, "<strong class=\"profileName\">", "</strong>");
-
- // Get and strip optional nickname
- std::string::size_type pos = this->self_.real_name.find("<span class=\"alternate_name\">");
- if (pos != std::string::npos) {
- this->self_.nick = utils::text::source_get_value(&this->self_.real_name, 2, "<span class=\"alternate_name\">(", ")</span>");
- 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\"", "<img src=\"", "\"");
- parent->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\"", "</code>");
-
- std::string::size_type start, end;
- start = content.find("<li", 0);
- while (start != std::string::npos) {
- end = content.find("<li", start+1);
- if (end == std::string::npos)
- end = content.length();
-
- std::string item = content.substr(start, end - start);
- //item = utils::text::source_get_value(&item, 2, "data-gt=", ">");
-
- start = content.find("<li", start+1);
-
- std::string id = utils::text::source_get_value(&item, 3, "data-gt=", "bmid":"", """);
- std::string title = utils::text::special_expressions_decode(utils::text::slashu_to_utf8(utils::text::source_get_value(&item, 3, "data-gt=", "title=\"", "\"")));
- std::string href = utils::text::source_get_value(&item, 3, "data-gt=", "href=\"", "\"");
-
- // Ignore pages channel
- if (href.find("/pages/feed") != std::string::npos)
- continue;
-
- if (id.empty() || title.empty())
- continue;
-
- parent->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, "<form", "</form>");
- 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<facebook_user*>::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<char*>(url.c_str());
- req.flags = NLHRF_HTTP11 | NLHRF_REDIRECT | NLHRF_PERSISTENT | NLHRF_NODUMP;
- req.nlc = nlc;
-
- resp = reinterpret_cast<NETLIBHTTPREQUEST*>(CallService(MS_NETLIB_HTTPTRANSACTION,
- reinterpret_cast<WPARAM>(this->parent->m_hNetlibUser), reinterpret_cast<LPARAM>(&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 <http://www.gnu.org/licenses/>. + +*/ + +#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<std::string, facebook_notification*>::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", "<div", ">", "</div>")); + 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", "<div", ">", "</div>")); + } + 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, "<strong class=\"profileName\">", "</strong>"); + + // 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", "<strong", ">", "</strong>"); + + // Get and strip optional nickname + std::string::size_type pos = this->self_.real_name.find("<span class=\"alternate_name\">"); + if (pos != std::string::npos) { + this->self_.nick = utils::text::source_get_value(&this->self_.real_name, 2, "<span class=\"alternate_name\">(", ")</span>"); + 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\"", "<img src=\"", "\""); + parent->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\"", "</code>"); + + std::string::size_type start, end; + start = content.find("<li", 0); + while (start != std::string::npos) { + end = content.find("<li", start+1); + if (end == std::string::npos) + end = content.length(); + + std::string item = content.substr(start, end - start); + //item = utils::text::source_get_value(&item, 2, "data-gt=", ">"); + + start = content.find("<li", start+1); + + std::string id = utils::text::source_get_value(&item, 3, "data-gt=", "bmid":"", """); + std::string title = utils::text::special_expressions_decode(utils::text::slashu_to_utf8(utils::text::source_get_value(&item, 3, "data-gt=", "title=\"", "\""))); + std::string href = utils::text::source_get_value(&item, 3, "data-gt=", "href=\"", "\""); + + // Ignore pages channel + if (href.find("/pages/feed") != std::string::npos) + continue; + + if (id.empty() || title.empty()) + continue; + + parent->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, "<form", "</form>"); + 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<facebook_user*>::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<char*>(url.c_str()); + req.flags = NLHRF_HTTP11 | NLHRF_REDIRECT | NLHRF_PERSISTENT | NLHRF_NODUMP; + req.nlc = nlc; + + resp = reinterpret_cast<NETLIBHTTPREQUEST*>(CallService(MS_NETLIB_HTTPTRANSACTION, + reinterpret_cast<WPARAM>(this->parent->m_hNetlibUser), reinterpret_cast<LPARAM>(&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; +} |