/*
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)
{
	http::response resp;
	
	if (parent->isOffline()) {
		resp.code = HTTP_CODE_FAKE_OFFLINE;
		return resp;
	}
	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);
	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::html_entities_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::html_entities_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_USER_INFO:
//	case REQUEST_USER_INFO_ALL:
//	case REQUEST_USER_INFO_MOBILE:
//	case REQUEST_LOAD_FRIENDSHIPS:
//	case REQUEST_SEARCH:
//  case REQUEST_DELETE_FRIEND:
//	case REQUEST_ADD_FRIEND:
//	case REQUEST_APPROVE_FRIEND:
//	case REQUEST_CANCEL_FRIENDSHIP:
//	case REQUEST_FRIENDSHIP:
//	case REQUEST_FEEDS:
//	case REQUEST_PAGES:
//	case REQUEST_NOTIFICATIONS:
//	case REQUEST_RECONNECT:
//	case REQUEST_POST_STATUS:
//	case REQUEST_IDENTITY_SWITCH:
//	case REQUEST_LINK_SCRAPER:
//	case REQUEST_MESSAGE_SEND_CHAT:
//	case REQUEST_MESSAGE_SEND_INBOX:
//	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_LINK_SCRAPER:
	case REQUEST_MESSAGE_SEND_CHAT:
	case REQUEST_MESSAGE_SEND_INBOX:
	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_ADD_FRIEND:
	case REQUEST_CANCEL_FRIENDSHIP:
	case REQUEST_FRIENDSHIP:
	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_USER_INFO:
//	case REQUEST_USER_INFO_ALL:
//	case REQUEST_USER_INFO_MOBILE:
//	case REQUEST_LOAD_FRIENDSHIPS:
//	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_LOAD_FRIENDSHIPS:
	case REQUEST_SEARCH:
	case REQUEST_USER_INFO_MOBILE:
		return FACEBOOK_SERVER_MOBILE;
//	case REQUEST_LOGOUT:
//	case REQUEST_BUDDY_LIST:
//	case REQUEST_USER_INFO:
//	case REQUEST_USER_INFO_ALL:
//	case REQUEST_FEEDS:
//	case REQUEST_PAGES:
//	case REQUEST_NOTIFICATIONS:
//	case REQUEST_RECONNECT:
//	case REQUEST_POST_STATUS:
//	case REQUEST_IDENTITY_SWITCH:
//	case REQUEST_LINK_SCRAPER:
//	case REQUEST_MESSAGE_SEND_CHAT:
//	case REQUEST_MESSAGE_SEND_INBOX:
//	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_ADD_FRIEND:
//	case REQUEST_CANCEL_FRIENDSHIP:
//	case REQUEST_FRIENDSHIP:
//	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_USER_INFO:
	{
		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_USER_INFO_ALL:
	{
		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_MOBILE:
	{		
		std::string action = "/%sv=info";
		if (get_data != NULL) {
			utils::text::replace_all(&action, "%s", *get_data);
		}
		return action;
	}
	case REQUEST_LOAD_FRIENDSHIPS:
	{
		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_ADD_FRIEND:
	{
		return "/ajax/add_friend/action.php?__a=1";
	}
	case REQUEST_CANCEL_FRIENDSHIP:
	{
		return "/ajax/friends/requests/cancel.php?__a=1";
	}
	case REQUEST_FRIENDSHIP:
	{
		return "/requests/friends/ajax/?__a=1";
	}
	case REQUEST_FEEDS:
	{
		std::string action = "/ajax/home/generic.php?" + get_newsfeed_type();
		action += "&__user=" + self_.user_id + "&__a=1";
		/*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_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_CHAT:
		return "/ajax/mercury/send_messages.php?__a=1";
	case REQUEST_MESSAGE_SEND_INBOX:
		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");
		
		// FIXME: fix this as I don't know how it works yet (because of quick stable release)
		if (!parent->isInvisible())
			action += "&idle=-1&state=active";
		else
			action += "&idle=1";
		action += "&cap=0";
		if (!this->chat_sticky_num_.empty())
			action += "&sticky_token=" + this->chat_sticky_num_;
		if (!this->chat_traceid_.empty())
			action += "&traceid=" + this->chat_traceid_;
		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_SEND_INBOX:
	case REQUEST_MESSAGE_SEND_CHAT:
	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;
	std::string ret = "sk=";
	ret += feed_types[feed_type].id;
	ret += "&key=";
	ret += (feed_type < 2 ? "nf" : feed_types[feed_type].id);
	return ret;
}
std::string facebook_client::get_server_type()
{
	BYTE server_type = parent->getByte(FACEBOOK_KEY_SERVER_TYPE, 0);
	if (server_type >= SIZEOF(server_types))
		server_type = 0;
	return server_types[server_type].id;
}
std::string facebook_client::get_privacy_type()
{
	BYTE privacy_type = parent->getByte(FACEBOOK_KEY_PRIVACY_TYPE, 0);
	if (privacy_type >= SIZEOF(privacy_types))
		privacy_type = 0;
	return privacy_types[privacy_type].id;
}
char* facebook_client::load_cookies()
{
	ScopedLock s(cookies_lock_);
	std::string cookieString = "isfbe=false;";
	if (!cookies.empty())
		for (std::map< std::string, std::string >::iterator iter = cookies.begin(); iter != cookies.end(); ++iter)
		{
			cookieString.append(iter->first);
			cookieString.append(1, '=');
			cookieString.append(iter->second);
			cookieString.append(1, ';');
		}
	
	return mir_strdup(cookieString.c_str());
}
void facebook_client::store_headers(http::response* resp, NETLIBHTTPHEADER* headers, int headersCount)
{
	ScopedLock c(cookies_lock_);
	for (int i = 0; i < headersCount; i++)
	{
		std::string header_name = headers[i].szName; // TODO: Casting?
		std::string header_value = headers[i].szValue; // TODO: Casting?
		if (header_name == "Set-Cookie")
		{
			std::string cookie_name = header_value.substr(0, header_value.find("="));
			std::string cookie_value = header_value.substr(header_value.find("=") + 1, header_value.find(";") - header_value.find("=") - 1);
			if (cookie_value == "deleted")
			{
				parent->debugLogA("      Deleted cookie '%s'", cookie_name.c_str());
				cookies.erase(cookie_name);
			} else {
				parent->debugLogA("      New cookie '%s'", cookie_name.c_str());
				cookies[cookie_name] = cookie_value;
			}
		}
		else
		{ // TODO RM: (un)comment
			//parent->debugLogA("----- Got header '%s': %s", header_name.c_str(), header_value.c_str());
			resp->headers[header_name] = header_value;
		}
	}
}
void facebook_client::clear_cookies()
{
	ScopedLock s(cookies_lock_);
	if (!cookies.empty())
		cookies.clear();
}
void facebook_client::clear_notifications()
{	
	for (std::map::iterator it = notifications.begin(); it != notifications.end(); ) {
		if (it->second->hWndPopup != NULL)
			PUDeletePopup(it->second->hWndPopup); // close popup
		delete it->second;
		it = notifications.erase(it);
	}
	notifications.clear();
}
void facebook_client::clear_chatrooms()
{
	for (std::map::iterator it = chat_rooms.begin(); it != chat_rooms.end();) {
		delete it->second;
		it = chat_rooms.erase(it);
	}
	chat_rooms.clear();
}
/**
 * Clears readers info for all contacts from readers list and db
 */
void facebook_client::clear_readers()
{
	for (std::map::iterator it = readers.begin(); it != readers.end();) {
		parent->delSetting(it->first, FACEBOOK_KEY_MESSAGE_READ);
		it = readers.erase(it);
	}
	readers.clear();
}
/**
 * Inserts info to readers list, db and writes to statusbar
 */
void facebook_client::insert_reader(MCONTACT hContact, time_t timestamp)
{
	parent->setDword(hContact, FACEBOOK_KEY_MESSAGE_READ, timestamp);
	readers.insert(std::make_pair(hContact, timestamp));
	parent->MessageRead(hContact);
}
/**
 * Removes info from readers list, db and clears statusbar
 */
void facebook_client::erase_reader(MCONTACT hContact)
{
	parent->delSetting(hContact, FACEBOOK_KEY_MESSAGE_READ);
	readers.erase(hContact);
	CallService(MS_MSG_SETSTATUSTEXT, (WPARAM)hContact, NULL);
}
void loginError(FacebookProto *proto, std::string error_str) {
	error_str = utils::text::trim(
			utils::text::html_entities_decode(
				utils::text::remove_html(
					utils::text::edit_html(error_str))));
	
	proto->debugLogA(" ! !  Login error: %s", !error_str.empty() ? error_str.c_str() : "Unknown error");
	TCHAR buf[200];
	mir_sntprintf(buf, SIZEOF(buf), TranslateT("Login error: %s"), 
		(error_str.empty()) ? 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"].empty())
		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::url::encode(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::url::encode(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::url::encode(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::url::encode(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);
			} else if (resp.data.find("name=\"submit[OK]\"") != std::string::npos) {
				
				inner_data = "submit[OK]=OK";
				inner_data += "&nh=" + utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\"");
				inner_data += "&fb_dtsg=" + utils::url::encode(utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""));
				resp = flap(REQUEST_SETUP_MACHINE, &inner_data);
				if (resp.data.find("security-essentials") != std::string::npos) {
					// Computer was probably infected by malware and needs cleaning (actually this may happen because Miranda doesn't support FB's captcha)
					inner_data = "submit[Continue]=Continue";
					inner_data += "&nh=" + utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\"");
					inner_data += "&fb_dtsg=" + utils::url::encode(utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""));
					
					// Mark that used cleaned his computer already, because he must confirm it anyway to be able to continue
					inner_data += "&confirm=1";
					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
		std::string error = utils::text::source_get_value(&resp.data, 4, "login_error_box", "", "
");
		if (error.empty())
			error = utils::text::source_get_value(&resp.data, 3, "