/*
Facebook plugin for Miranda Instant Messenger
_____________________________________________
Copyright © 2009-11 Michal Zelinka, 2011-13 Robert Pösel
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "common.h"
void facebook_client::client_notify(TCHAR* message)
{
parent->NotifyEvent(parent->m_tszUserName, message, NULL, FACEBOOK_EVENT_CLIENT);
}
http::response facebook_client::flap(RequestType request_type, std::string* request_data, std::string* request_get_data, int method)
{
NETLIBHTTPREQUEST nlhr = {sizeof(NETLIBHTTPREQUEST)};
nlhr.requestType = !method ? choose_method(request_type) : method;
std::string url = choose_proto(request_type);
url.append(choose_server(request_type, request_data, request_get_data));
url.append(choose_action(request_type, request_data, request_get_data));
nlhr.szUrl = (char*)url.c_str();
nlhr.flags = NLHRF_HTTP11 | choose_security_level(request_type);
nlhr.headers = get_request_headers(nlhr.requestType, &nlhr.headersCount);
#ifdef _DEBUG
nlhr.flags |= NLHRF_DUMPASTEXT;
#else
nlhr.flags |= NLHRF_NODUMP;
#endif
switch (request_type)
{
case REQUEST_MESSAGES_RECEIVE:
nlhr.timeout = 1000 * 65; break;
default:
nlhr.timeout = 1000 * 20; break;
}
if (request_data != NULL)
{
nlhr.pData = (char*)(*request_data).c_str();
nlhr.dataLength = (int)request_data->length();
}
parent->debugLogA("@@@@@ Sending request to '%s'", nlhr.szUrl);
switch (request_type)
{
case REQUEST_LOGIN:
nlhr.nlc = NULL;
break;
case REQUEST_MESSAGES_RECEIVE:
nlhr.nlc = hMsgCon;
nlhr.flags |= NLHRF_PERSISTENT;
break;
default:
WaitForSingleObject(fcb_conn_lock_, INFINITE);
nlhr.nlc = hFcbCon;
nlhr.flags |= NLHRF_PERSISTENT;
break;
}
NETLIBHTTPREQUEST* pnlhr = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)handle_, (LPARAM)&nlhr);
mir_free(nlhr.headers[3].szValue);
mir_free(nlhr.headers);
http::response resp;
switch (request_type)
{
case REQUEST_LOGIN:
case REQUEST_SETUP_MACHINE:
break;
case REQUEST_MESSAGES_RECEIVE:
hMsgCon = pnlhr ? pnlhr->nlc : NULL;
break;
default:
ReleaseMutex(fcb_conn_lock_);
hFcbCon = pnlhr ? pnlhr->nlc : NULL;
break;
}
if (pnlhr != NULL)
{
parent->debugLogA("@@@@@ Got response with code %d", pnlhr->resultCode);
store_headers(&resp, pnlhr->headers, pnlhr->headersCount);
resp.code = pnlhr->resultCode;
resp.data = pnlhr->pData ? pnlhr->pData : "";
CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)pnlhr);
} else {
parent->debugLogA("!!!!! No response from server (time-out)");
resp.code = HTTP_CODE_FAKE_DISCONNECTED;
// Better to have something set explicitely as this value is compaired in all communication requests
}
// Get Facebook's error message
if (resp.code == HTTP_CODE_OK) {
std::string::size_type pos = resp.data.find("\"error\":");
if (pos != std::string::npos) {
pos += 8;
int error_num = atoi(resp.data.substr(pos, resp.data.find(",", pos) - pos).c_str());
if (error_num != 0) {
std::string error = "";
pos = resp.data.find("\"errorDescription\":\"", pos);
if (pos != std::string::npos) {
pos += 20;
error = resp.data.substr(pos, resp.data.find("\"", pos) - pos);
error = utils::text::trim(utils::text::special_expressions_decode(utils::text::slashu_to_utf8(error)));
error = ptrA( mir_utf8decodeA(error.c_str()));
}
std::string title = "";
pos = resp.data.find("\"errorSummary\":\"", pos);
if (pos != std::string::npos) {
pos += 16;
title = resp.data.substr(pos, resp.data.find("\"", pos) - pos);
title = utils::text::trim(utils::text::special_expressions_decode(utils::text::slashu_to_utf8(title)));
title = ptrA( mir_utf8decodeA(title.c_str()));
}
bool silent = resp.data.find("\"silentError\":1") != std::string::npos;
resp.error_number = error_num;
resp.error_text = error;
resp.error_title = title;
resp.code = HTTP_CODE_FAKE_ERROR;
parent->debugLogA(" ! ! Received Facebook error: %d -- %s", error_num, error.c_str());
if (notify_errors(request_type) && !silent)
client_notify(_A2T(error.c_str()));
}
}
}
return resp;
}
bool facebook_client::handle_entry(std::string method)
{
parent->debugLogA(" >> Entering %s()", method.c_str());
return true;
}
bool facebook_client::handle_success(std::string method)
{
parent->debugLogA(" << Quitting %s()", method.c_str());
reset_error();
return true;
}
bool facebook_client::handle_error(std::string method, int action)
{
increment_error();
parent->debugLogA("!!!!! %s(): Something with Facebook went wrong", method.c_str());
bool result = (error_count_ <= (UINT)parent->getByte(FACEBOOK_KEY_TIMEOUTS_LIMIT, FACEBOOK_TIMEOUTS_LIMIT));
if (action == FORCE_DISCONNECT || action == FORCE_QUIT)
result = false;
if (!result) {
reset_error();
if (action != FORCE_QUIT)
parent->SetStatus(ID_STATUS_OFFLINE);
}
return result;
}
//////////////////////////////////////////////////////////////////////////////
DWORD facebook_client::choose_security_level(RequestType request_type)
{
if (this->https_)
if (request_type != REQUEST_MESSAGES_RECEIVE || parent->getByte(FACEBOOK_KEY_FORCE_HTTPS_CHANNEL, DEFAULT_FORCE_HTTPS_CHANNEL))
return NLHRF_SSL;
switch (request_type) {
case REQUEST_LOGIN:
case REQUEST_SETUP_MACHINE:
return NLHRF_SSL;
// case REQUEST_LOGOUT:
// case REQUEST_HOME:
// case REQUEST_DTSG:
// case REQUEST_BUDDY_LIST:
// case REQUEST_LOAD_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_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_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_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";
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=&cb=&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 loginError(FacebookProto *proto, std::string error_str) {
error_str = utils::text::trim(
utils::text::special_expressions_decode(
utils::text::remove_html(
utils::text::edit_html(error_str))));
proto->debugLogA(" ! ! Login error: %s", error_str.length() ? error_str.c_str() : "Unknown error");
TCHAR buf[200];
mir_sntprintf(buf, SIZEOF(buf), TranslateT("Login error: %s"),
(!error_str.length()) ? TranslateT("Unknown error") : ptrT(mir_utf8decodeT(error_str.c_str())));
proto->facy.client_notify(buf);
}
bool facebook_client::login(const char *username, const char *password)
{
handle_entry("login");
username_ = username;
password_ = password;
if (cookies.empty()) {
// Set device ID
ptrA device( parent->getStringA(FACEBOOK_KEY_DEVICE_ID));
if (device != NULL)
cookies["datr"] = device;
// Get initial cookies
flap(REQUEST_HOME);
}
// Prepare login data
std::string data = "persistent=1";
data += "&email=" + utils::url::encode(username);
data += "&pass=" + utils::url::encode(password);
ptrA locale(parent->getStringA(FACEBOOK_KEY_LOCALE));
if (locale != NULL)
data += "&locale=" + std::string(locale);
// Send validation
http::response resp = flap(REQUEST_LOGIN, &data);
// Save Device ID
if (cookies["datr"].length())
parent->setString(FACEBOOK_KEY_DEVICE_ID, cookies["datr"].c_str());
if (resp.code == HTTP_CODE_FOUND && resp.headers.find("Location") != resp.headers.end())
{
// Check for invalid requests
if (resp.headers["Location"].find("invalid_request.php") != std::string::npos) {
client_notify(TranslateT("Login error: Invalid request."));
parent->debugLogA(" ! ! Login error: Invalid request.");
return handle_error("login", FORCE_QUIT);
}
// Check whether some Facebook things are required
if (resp.headers["Location"].find("help.php") != std::string::npos)
{
client_notify(TranslateT("Login error: Some Facebook things are required."));
parent->debugLogA(" ! ! Login error: Some Facebook things are required.");
// return handle_error("login", FORCE_QUIT);
}
// Check whether setting Machine name is required
if (resp.headers["Location"].find("/checkpoint/") != std::string::npos)
{
resp = flap(REQUEST_SETUP_MACHINE, NULL, NULL, REQUEST_GET);
if (resp.data.find("login_approvals_no_phones") != std::string::npos) {
// Code approval - but no phones in account
loginError(parent, utils::text::source_get_value(&resp.data, 4, "login_approvals_no_phones", "
", "
"));
return handle_error("login", FORCE_QUIT);
}
std::string inner_data;
if (resp.data.find("name=\"submit[Continue]\"") != std::string::npos) {
// Check if we need to approve also last unapproved device
if (resp.data.find("name=\"name_action_selected\"") == std::string::npos) {
// 1) Continue
inner_data = "submit[Continue]=Continue";
inner_data += "&nh=" + utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\"");
inner_data += "&fb_dtsg=" + utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\"");
resp = flap(REQUEST_SETUP_MACHINE, &inner_data);
// 2) Approve last unknown login
// inner_data = "submit[I%20don't%20recognize]=I%20don't%20recognize"; // Don't recognize - this will force to change account password
inner_data = "submit[This%20is%20Okay]=This%20is%20Okay"; // Recognize
inner_data += "&nh=" + utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\"");
inner_data += "&fb_dtsg=" + utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\"");
resp = flap(REQUEST_SETUP_MACHINE, &inner_data);
// 3) Save last device
inner_data = "submit[Continue]=Continue";
inner_data += "&nh=" + utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\"");
inner_data += "&fb_dtsg=" + utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\"");
inner_data += "&name_action_selected=save_device"; // Save device - or "dont_save"
resp = flap(REQUEST_SETUP_MACHINE, &inner_data);
}
// Save this actual device
inner_data = "submit[Continue]=Continue";
inner_data += "&nh=" + utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\"");
inner_data += "&fb_dtsg=" + utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\"");
inner_data += "&name_action_selected=save_device"; // Save device - or "dont_save"
resp = flap(REQUEST_SETUP_MACHINE, &inner_data);
}
// Save actual machine name
// inner_data = "machine_name=Miranda%20NG&submit[Don't%20Save]=Don't%20Save"; // Don't save
inner_data = "machine_name=Miranda%20NG&submit[Save%20Device]=Save%20Device"; // Save
inner_data += "&lsd=" + utils::text::source_get_value(&resp.data, 3, "name=\"lsd\"", "value=\"", "\"");
inner_data += "&nh=" + utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\"");
inner_data += "&fb_dtsg=" + utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\"");
resp = flap(REQUEST_SETUP_MACHINE, &inner_data);
}
}
switch (resp.code)
{
case HTTP_CODE_FAKE_DISCONNECTED:
{
// When is error only because timeout, try login once more
if (handle_error("login"))
return login(username, password);
else
return handle_error("login", FORCE_QUIT);
}
case HTTP_CODE_OK: // OK page returned, but that is regular login page we don't want in fact
{
// Check whether captcha code is required
if (resp.data.find("id=\"captcha\"") != std::string::npos)
{
client_notify(TranslateT("Login error: Captcha code is required. Bad login credentials?"));
parent->debugLogA(" ! ! Login error: Captcha code is required.");
return handle_error("login", FORCE_QUIT);
}
// Get and notify error message
loginError(parent, utils::text::source_get_value(&resp.data, 4, "login_error_box", "", "
"));
}
case HTTP_CODE_FORBIDDEN: // Forbidden
case HTTP_CODE_NOT_FOUND: // Not Found
default:
return handle_error("login", FORCE_QUIT);
case HTTP_CODE_FOUND: // Found and redirected to Home, Logged in, everything is OK
{
if (cookies.find("c_user") != cookies.end()) {
this->self_.user_id = cookies.find("c_user")->second;
parent->setString(FACEBOOK_KEY_ID, this->self_.user_id.c_str());
parent->debugLogA(" Got self user id: %s", this->self_.user_id.c_str());
return handle_success("login");
} else {
client_notify(TranslateT("Login error, probably bad login credentials."));
parent->debugLogA(" ! ! Login error, probably bad login credentials.");
return handle_error("login", FORCE_QUIT);
}
}
}
}
bool facebook_client::logout()
{
if (parent->getByte(FACEBOOK_KEY_DISABLE_LOGOUT, 0))
return true;
handle_entry("logout");
std::string data = "fb_dtsg=" + (this->dtsg_.length() ? this->dtsg_ : "0");
data += "&ref=mb&h=" + this->logout_hash_;
http::response resp = flap(REQUEST_LOGOUT, &data);
if (hFcbCon)
Netlib_CloseHandle(hFcbCon);
hFcbCon = NULL;
// Process result
username_ = password_ = self_.user_id = "";
switch (resp.code)
{
case HTTP_CODE_OK:
case HTTP_CODE_FOUND:
return handle_success("logout");
default:
return false; // Logout not finished properly, but..okay, who cares :P
}
}
bool facebook_client::home()
{
handle_entry("home");
// get fb_dtsg
http::response resp = flap(REQUEST_DTSG);
// Check whether HTTPS connection is required and we don't have it enabled
if (!this->https_ && resp.headers["Location"].find("https://") != std::string::npos) {
client_notify(TranslateT("Your account requires HTTPS connection. Activating."));
parent->setByte(FACEBOOK_KEY_FORCE_HTTPS, 1);
this->https_ = true;
return home();
}
this->dtsg_ = utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\"");
parent->debugLogA(" Got self dtsg: %s", this->dtsg_.c_str());
if (this->dtsg_.empty())
return false;
resp = flap(REQUEST_HOME);
switch (resp.code)
{
case HTTP_CODE_OK:
{
// Get real_name
this->self_.real_name = utils::text::source_get_value(&resp.data, 2, "", "");
if (!this->self_.real_name.empty()) {
parent->SaveName(NULL, &this->self_);
parent->debugLogA(" Got self real name: %s", this->self_.real_name.c_str());
} else {
client_notify(TranslateT("Something happened to Facebook. Maybe there was some major update so you should wait for an update."));
return handle_error("home", FORCE_QUIT);
}
// Get avatar
this->self_.image_url = utils::text::source_get_value(&resp.data, 3, "class=\"l\"", "debugLogA(" Got self avatar: %s", this->self_.image_url.c_str());
parent->CheckAvatarChange(NULL, this->self_.image_url);
// Get logout hash
this->logout_hash_ = utils::text::source_get_value2(&resp.data, "/logout.php?h=", "&\"");
parent->debugLogA(" Got self logout hash: %s", this->logout_hash_.c_str());
return handle_success("home");
}
case HTTP_CODE_FOUND:
// Work-around for replica_down, f**king hell what's that?
parent->debugLogA(" REPLICA_DOWN is back in force!");
return this->home();
default:
return handle_error("home", FORCE_QUIT);
}
}
bool facebook_client::chat_state(bool online)
{
handle_entry("chat_state");
std::string data = (online ? "visibility=1" : "visibility=0");
data += "&window_id=0";
data += "&fb_dtsg=" + (dtsg_.length() ? dtsg_ : "0");
data += "&phstamp=0&__user=" + self_.user_id;
http::response resp = flap(REQUEST_VISIBILITY, &data);
return handle_success("chat_state");
}
bool facebook_client::reconnect()
{
handle_entry("reconnect");
// Request reconnect
http::response resp = flap(REQUEST_RECONNECT);
switch (resp.code)
{
case HTTP_CODE_OK:
{
this->chat_channel_ = utils::text::source_get_value(&resp.data, 2, "\"user_channel\":\"", "\"");
parent->debugLogA(" Got self channel: %s", this->chat_channel_.c_str());
this->chat_channel_partition_ = utils::text::source_get_value2(&resp.data, "\"partition\":", ",}");
parent->debugLogA(" Got self channel partition: %s", this->chat_channel_partition_.c_str());
this->chat_channel_host_ = utils::text::source_get_value(&resp.data, 2, "\"host\":\"", "\"");
parent->debugLogA(" Got self channel host: %s", this->chat_channel_host_.c_str());
this->chat_sequence_num_ = utils::text::source_get_value2(&resp.data, "\"seq\":", ",}");
parent->debugLogA(" Got self sequence number: %s", this->chat_sequence_num_.c_str());
this->chat_conn_num_ = utils::text::source_get_value2(&resp.data, "\"max_conn\":", ",}");
parent->debugLogA(" Got self max_conn: %s", this->chat_conn_num_.c_str());
return handle_success("reconnect");
}
default:
return handle_error("reconnect", FORCE_DISCONNECT);
}
}
bool facebook_client::buddy_list()
{
handle_entry("buddy_list");
// Prepare update data
std::string data = "user=" + this->self_.user_id + "&fetch_mobile=true&phstamp=0&fb_dtsg=" + this->dtsg_ + "&__user=" + this->self_.user_id;
{
ScopedLock s(buddies_lock_);
data += "&cached_user_info_ids=";
int counter = 0;
for (List::Item< facebook_user >* i = buddies.begin(); i != NULL; i = i->next, counter++)
{
data += i->data->user_id + "%2C";
}
}
// Get buddy list
http::response resp = flap(REQUEST_BUDDY_LIST, &data);
switch (resp.code) {
case HTTP_CODE_OK:
parent->ForkThread(&FacebookProto::ProcessBuddyList, new std::string(resp.data));
return handle_success("buddy_list");
case HTTP_CODE_FAKE_ERROR:
case HTTP_CODE_FAKE_DISCONNECTED:
default:
return handle_error("buddy_list");
}
}
bool facebook_client::load_friends()
{
handle_entry("load_friends");
// Get buddy list
http::response resp = flap(REQUEST_LOAD_FRIENDS);
switch (resp.code) {
case HTTP_CODE_OK:
parent->ForkThread(&FacebookProto::ProcessFriendList, new std::string(resp.data));
return handle_success("load_friends");
case HTTP_CODE_FAKE_ERROR:
case HTTP_CODE_FAKE_DISCONNECTED:
default:
return handle_error("load_friends");
}
}
bool facebook_client::feeds()
{
handle_entry("feeds");
// Get feeds
http::response resp = flap(REQUEST_FEEDS);
switch (resp.code) {
case HTTP_CODE_OK:
if (resp.data.find("\"num_stories\":0") == std::string::npos)
parent->ForkThread(&FacebookProto::ProcessFeeds, new std::string(resp.data));
return handle_success("feeds");
case HTTP_CODE_FAKE_ERROR:
case HTTP_CODE_FAKE_DISCONNECTED:
default:
return handle_error("feeds");
}
}
bool facebook_client::load_pages()
{
if (!parent->getByte(FACEBOOK_KEY_LOAD_PAGES, DEFAULT_LOAD_PAGES))
return true;
handle_entry("load_pages");
// Get feeds
http::response resp = flap(REQUEST_PAGES);
switch (resp.code) {
case HTTP_CODE_OK:
{
std::string content = utils::text::source_get_value(&resp.data, 2, "id=\"bookmarksSeeAllSection\"", "");
std::string::size_type start, end;
start = content.find("");
start = content.find("debugLogA(" Got page: %s (%s)", title.c_str(), id.c_str());
pages[id] = title;
}
return handle_success("load_pages");
}
case HTTP_CODE_FAKE_ERROR:
case HTTP_CODE_FAKE_DISCONNECTED:
default:
return handle_error("load_pages");
}
}
bool facebook_client::channel()
{
handle_entry("channel");
// Get update
http::response resp = flap(REQUEST_MESSAGES_RECEIVE);
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_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)
{
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, "