", "");
		// Get and strip optional nickname
		std::string::size_type pos = this->self_.real_name.find("
");
		if (pos != std::string::npos) {
			this->self_.nick = utils::text::source_get_value(&this->self_.real_name, 2, "(", ")");
			parent->debugLogA("    Got self nick name: %s", this->self_.nick.c_str());
			this->self_.real_name = this->self_.real_name.substr(0, pos - 1);
		}
		this->self_.real_name = utils::text::remove_html(this->self_.real_name);
		parent->debugLogA("    Got self real name: %s", this->self_.real_name.c_str());
		parent->SaveName(NULL, &this->self_);
		// Get avatar
		this->self_.image_url = utils::text::source_get_value(&resp.data, 3, "id=\"root", " self_.image_url.empty()) {
			this->self_.image_url = utils::text::source_get_value(&resp.data, 3, "id=\"root", "/photo.php?", "\"");
			
			// Prepare this special url (not direct image url) to be handled correctly in CheckAvatarChange()
			// It must contain "/" at the beginning and also shouldn't contain "?" as parameters after that are stripped
			if (!this->self_.image_url.empty())
				this->self_.image_url = "/" + this->self_.image_url;
		}
		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());
		if (this->self_.real_name.empty() || this->self_.image_url.empty() || this->logout_hash_.empty()) {
			parent->debugLogA("!!! Empty nick/avatar/hash. Source code:\n%s", resp.data.c_str());
			client_notify(TranslateT("Could not load all required data. Plugin may still work correctly, but you should report this and wait for plugin update."));
		}
		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_;
	data += "&__user=" + self_.user_id;
	http::response resp = flap(REQUEST_VISIBILITY, &data);
	if (!resp.error_title.empty())
		return handle_error("chat_state");
	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());
		this->chat_sticky_num_ = utils::text::source_get_value(&resp.data, 2, "\"sticky_token\":\"", "\"");
		parent->debugLogA("    Got self sticky_token: %s", this->chat_sticky_num_.c_str());
		//std::string retry_interval = utils::text::source_get_value2(&resp.data, "\"retry_interval\":", ",}");
		//parent->debugLogA("    Got self retry_interval: %s", retry_interval.c_str());
		//std::string visibility = utils::text::source_get_value2(&resp.data, "\"visibility\":", ",}");
		//parent->debugLogA("    Got self visibility: %s", visibility.c_str());
		// Send activity_ping after each reconnect
		activity_ping();
		return handle_success("reconnect");
	}
	default:
		return handle_error("reconnect", FORCE_DISCONNECT);
	}
}
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");
	}
	// Load traceId, if present
	std::string traceId = utils::text::source_get_value(&resp.data, 2, "\"tr\":\"", "\"");
	if (!traceId.empty()) {
		this->chat_traceid_ = traceId;
	}
	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_sticky_pool_ = utils::text::source_get_value(&resp.data, 2, "\"pool\":\"", "\"");
		parent->debugLogA("    Got self sticky pool: %s", this->chat_sticky_pool_.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") {
		// Requested reload of page or relogin (due to some settings change, removing this session, etc.)
		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());
		if (type == "refresh") {
			this->chat_reconnect_reason_ = utils::text::source_get_value2(&resp.data, "\"reason\":", ",}");
			parent->debugLogA("    Got reconnect reason: %s", this->chat_reconnect_reason_.c_str());
			return this->reconnect();
		}
	}
	else if (!type.empty()) {
		// 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);
		// Get new sequence number
		std::string seq = utils::text::source_get_value2(&resp.data, "\"seq\":", ",}");
		parent->debugLogA("    Got self sequence number: %s", seq.c_str());
		if (type == "msg") {
			// Update msgs_recv number for every "msg" type we receive (during fullRefresh/reload responses it stays the same)
			this->chat_msgs_recv_++;
		}
		// Check if it's different from our old one (which means we should increment our old one)
		if (seq != this->chat_sequence_num_) {
			// Facebook now often return much bigger number which results in skipping few data requests, so we increment it manually
			// Bigger skips (when there is some problem or something) are handled when fullreload/refresh response type
			int iseq = 0;
			if (utils::conversion::from_string(iseq, this->chat_sequence_num_, std::dec)) {
				// Increment and convert it back to string
				iseq++;
				std::string newSeq = utils::conversion::to_string(&iseq, UTILS_CONV_SIGNED_NUMBER);
				// Check if we have different seq than the one from Facebook
				if (newSeq != seq) {
					parent->debugLogA("!!! Use self incremented sequence number: %s (instead of: %s)", newSeq.c_str(), seq.c_str());
					seq = newSeq;
				}
			}
		}
		this->chat_sequence_num_ = seq;
	}
	else {
		// No type? This shouldn't happen unless there is a big API change.
		return handle_error("channel");
	}
	// 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", &this->random_);
		// Intentionally fall to handle_error() below
	case HTTP_CODE_FAKE_DISCONNECTED:
	case HTTP_CODE_FAKE_ERROR:
	default:
		return handle_error("channel");
	}
}
bool facebook_client::activity_ping()
{
	handle_entry("activity_ping");
	http::response resp = flap(REQUEST_ACTIVE_PING);
	// Remember this last ping time
	parent->m_pingTS = ::time(NULL);
	if (resp.data.empty() || resp.data.find("\"t\":\"pong\"") == resp.data.npos) {
		// Something went wrong
		return handle_error("activity_ping");
	}
	return handle_success("activity_ping");
}
int facebook_client::send_message(int seqid, MCONTACT hContact, const std::string &message_text, std::string *error_text, const std::string &captcha_persist_data, const std::string &captcha)
{
	handle_entry("send_message");
	http::response resp;
	std::string data;
	if (!captcha.empty()) {
		data += "&captcha_persist_data=" + captcha_persist_data;
		data += "&recaptcha_challenge_field=";
		data += "&captcha_response=" + captcha;
	}
	boolean isChatRoom = parent->isChatRoom(hContact);
	ptrA userId( parent->getStringA(hContact, FACEBOOK_KEY_ID));
	ptrA threadId( parent->getStringA(hContact, FACEBOOK_KEY_TID));
	
	// Check if we have userId/threadId to be able to send message
	if ((isChatRoom && (threadId == NULL || !mir_strcmp(threadId, "null")))
		|| (!isChatRoom && (userId == NULL || !mir_strcmp(userId, "null")))) {
		// This shouldn't happen unless user manually deletes some data via Database Editor++
		*error_text = Translate("Contact doesn't have required data in database.");
		handle_error("send_message");
		return SEND_MESSAGE_ERROR;
	}
	data += "&message_batch[0][action_type]=ma-type:user-generated-message";
	
	if (isChatRoom) {
		data += "&message_batch[0][thread_id]=" + std::string(threadId);
	} else {
		data += "&message_batch[0][specific_to_list][0]=fbid:" + std::string(userId);
		data += "&message_batch[0][specific_to_list][1]=fbid:" + this->self_.user_id;
		data += "&message_batch[0][client_thread_id]=user:" + std::string(userId);
	}
	data += "&message_batch[0][thread_fbid]";
	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][timestamp_time_passed]";
	data += "&message_batch[0][is_unread]=false";
	data += "&message_batch[0][is_forward]=false";
	data += "&message_batch[0][is_filtered_content]=false";
	data += "&message_batch[0][is_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][signatureID]";
	data += "&message_batch[0][ui_push_phase]";
	data += "&message_batch[0][status]=0";
	data += "&message_batch[0][message_id]";
	data += "&message_batch[0][manual_retry_cnt]";
	data += "&client=mercury&__a=1&__dyn&__req&__rev";
	data += "&fb_dtsg=" + this->dtsg_;
	data += "&__user=" + this->self_.user_id;
	data += "&ttstamp=" + ttstamp_;
	{
		ScopedLock s(send_message_lock_);
		resp = flap(REQUEST_MESSAGE_SEND_CHAT, &data);
		*error_text = resp.error_text;
		if (resp.error_number == 0) {
			// Everything is OK
			// Remember this message id
			std::string mid = utils::text::source_get_value(&resp.data, 2, "\"message_id\":\"", "\"");
			if (mid.empty())
				mid = utils::text::source_get_value(&resp.data, 2, "\"mid\":\"", "\"");
			// For classic contacts remember last message id
			if (!parent->isChatRoom(hContact))
				parent->setString(hContact, FACEBOOK_KEY_MESSAGE_ID, mid.c_str());
			// Get timestamp
			std::string timestamp = utils::text::source_get_value(&resp.data, 2, "\"timestamp\":", ",");
			time_t time = utils::time::from_string(timestamp);
			// Remember last action timestamp for history sync
			parent->setDword(FACEBOOK_KEY_LAST_ACTION_TS, time);
			// For classic conversation we try to replace timestamp of added event in OnPreCreateEvent()
			if (seqid > 0)
				messages_timestamp.insert(std::make_pair(seqid, time));
			// We have this message in database, so ignore further tries (in channel) to add it again
			messages_ignore.insert(std::make_pair(mid, 0));
		}
	}
	switch (resp.error_number) {
	case 0: 
		// Everything is OK
		break;
	// case 1356002: // You are offline (probably you can't use mercury or some other request when chat is offline)
	case 1356003: // Contact is offline
		parent->setWord(hContact, "Status", ID_STATUS_OFFLINE);
		return SEND_MESSAGE_ERROR;
	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 SEND_MESSAGE_ERROR;
	case 1357007: // Security check (captcha) is required
		{
			std::string imageUrl = utils::text::html_entities_decode(utils::text::slashu_to_utf8(utils::text::source_get_value(&resp.data, 3, "img class=\\\"img\\\"", "src=\\\"", "\\\"")));
			std::string captchaPersistData = utils::text::source_get_value(&resp.data, 3, "\\\"captcha_persist_data\\\"", "value=\\\"", "\\\"");
			parent->debugLogA("    Got imageUrl (first): %s", imageUrl.c_str());
			parent->debugLogA("    Got captchaPersistData (first): %s", captchaPersistData.c_str());
			std::string capStr = "new_captcha_type=TFBCaptcha&skipped_captcha_data=" + captchaPersistData;
			capStr += "&__dyn=&__req=&__rev=&__user=" + this->self_.user_id;
			http::response capResp = flap(REQUEST_CAPTCHA_REFRESH, NULL, &capStr);
			if (capResp.code == HTTP_CODE_OK) {
				imageUrl = utils::text::html_entities_decode(utils::text::slashu_to_utf8(utils::text::source_get_value(&capResp.data, 3, "img class=\\\"img\\\"", "src=\\\"", "\\\"")));
				captchaPersistData = utils::text::source_get_value(&capResp.data, 3, "\\\"captcha_persist_data\\\"", "value=\\\"", "\\\"");
				parent->debugLogA("    Got imageUrl (second): %s", imageUrl.c_str());
				parent->debugLogA("    Got captchaPersistData (second): %s", captchaPersistData.c_str());
				std::string result;
				if (!parent->RunCaptchaForm(imageUrl, result)) {
					*error_text = Translate("User cancel captcha challenge.");
					return SEND_MESSAGE_CANCEL;
				}
				return send_message(seqid, hContact, message_text, error_text, captchaPersistData, result);
			}
		}
		return SEND_MESSAGE_CANCEL; // Cancel because we failed to load captcha image so we can't continue only with error
	//case 1404123: // Blocked sending messages (with URLs) because Facebook think our computer is infected with malware
	default: // Other error
		parent->debugLogA("!!! Send message error #%d: %s", resp.error_number, resp.error_text.c_str());
		return SEND_MESSAGE_ERROR;
	}
	switch (resp.code) {
	case HTTP_CODE_OK:
		handle_success("send_message");
		return SEND_MESSAGE_OK;
	case HTTP_CODE_FAKE_ERROR:
	case HTTP_CODE_FAKE_DISCONNECTED:
	default:
		*error_text = Translate("Timeout when sending message.");
		handle_error("send_message");
		return SEND_MESSAGE_ERROR;
	}
}
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_;
		data += "&user_id=" + status->user_id;
		data += "&url=" + std::string(FACEBOOK_URL_HOMEPAGE);
		flap(REQUEST_IDENTITY_SWITCH, &data);
	}
	std::string data;
	if (!status->url.empty()) {
		data = "fb_dtsg=" + this->dtsg_;
		data += "&targetid=" + (status->user_id.empty() ? this->self_.user_id : status->user_id);
		data += "&xhpc_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=" + ttstamp_;
		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);
		std::string temp = utils::text::html_entities_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=" + ttstamp_;
		std::string form = utils::text::source_get_value(&temp, 2, "
self_.image_url.empty()) {
			this->self_.image_url = utils::text::source_get_value(&resp.data, 3, "id=\"root", "/photo.php?", "\"");
			
			// Prepare this special url (not direct image url) to be handled correctly in CheckAvatarChange()
			// It must contain "/" at the beginning and also shouldn't contain "?" as parameters after that are stripped
			if (!this->self_.image_url.empty())
				this->self_.image_url = "/" + this->self_.image_url;
		}
		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());
		if (this->self_.real_name.empty() || this->self_.image_url.empty() || this->logout_hash_.empty()) {
			parent->debugLogA("!!! Empty nick/avatar/hash. Source code:\n%s", resp.data.c_str());
			client_notify(TranslateT("Could not load all required data. Plugin may still work correctly, but you should report this and wait for plugin update."));
		}
		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_;
	data += "&__user=" + self_.user_id;
	http::response resp = flap(REQUEST_VISIBILITY, &data);
	if (!resp.error_title.empty())
		return handle_error("chat_state");
	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());
		this->chat_sticky_num_ = utils::text::source_get_value(&resp.data, 2, "\"sticky_token\":\"", "\"");
		parent->debugLogA("    Got self sticky_token: %s", this->chat_sticky_num_.c_str());
		//std::string retry_interval = utils::text::source_get_value2(&resp.data, "\"retry_interval\":", ",}");
		//parent->debugLogA("    Got self retry_interval: %s", retry_interval.c_str());
		//std::string visibility = utils::text::source_get_value2(&resp.data, "\"visibility\":", ",}");
		//parent->debugLogA("    Got self visibility: %s", visibility.c_str());
		// Send activity_ping after each reconnect
		activity_ping();
		return handle_success("reconnect");
	}
	default:
		return handle_error("reconnect", FORCE_DISCONNECT);
	}
}
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");
	}
	// Load traceId, if present
	std::string traceId = utils::text::source_get_value(&resp.data, 2, "\"tr\":\"", "\"");
	if (!traceId.empty()) {
		this->chat_traceid_ = traceId;
	}
	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_sticky_pool_ = utils::text::source_get_value(&resp.data, 2, "\"pool\":\"", "\"");
		parent->debugLogA("    Got self sticky pool: %s", this->chat_sticky_pool_.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") {
		// Requested reload of page or relogin (due to some settings change, removing this session, etc.)
		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());
		if (type == "refresh") {
			this->chat_reconnect_reason_ = utils::text::source_get_value2(&resp.data, "\"reason\":", ",}");
			parent->debugLogA("    Got reconnect reason: %s", this->chat_reconnect_reason_.c_str());
			return this->reconnect();
		}
	}
	else if (!type.empty()) {
		// 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);
		// Get new sequence number
		std::string seq = utils::text::source_get_value2(&resp.data, "\"seq\":", ",}");
		parent->debugLogA("    Got self sequence number: %s", seq.c_str());
		if (type == "msg") {
			// Update msgs_recv number for every "msg" type we receive (during fullRefresh/reload responses it stays the same)
			this->chat_msgs_recv_++;
		}
		// Check if it's different from our old one (which means we should increment our old one)
		if (seq != this->chat_sequence_num_) {
			// Facebook now often return much bigger number which results in skipping few data requests, so we increment it manually
			// Bigger skips (when there is some problem or something) are handled when fullreload/refresh response type
			int iseq = 0;
			if (utils::conversion::from_string(iseq, this->chat_sequence_num_, std::dec)) {
				// Increment and convert it back to string
				iseq++;
				std::string newSeq = utils::conversion::to_string(&iseq, UTILS_CONV_SIGNED_NUMBER);
				// Check if we have different seq than the one from Facebook
				if (newSeq != seq) {
					parent->debugLogA("!!! Use self incremented sequence number: %s (instead of: %s)", newSeq.c_str(), seq.c_str());
					seq = newSeq;
				}
			}
		}
		this->chat_sequence_num_ = seq;
	}
	else {
		// No type? This shouldn't happen unless there is a big API change.
		return handle_error("channel");
	}
	// 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", &this->random_);
		// Intentionally fall to handle_error() below
	case HTTP_CODE_FAKE_DISCONNECTED:
	case HTTP_CODE_FAKE_ERROR:
	default:
		return handle_error("channel");
	}
}
bool facebook_client::activity_ping()
{
	handle_entry("activity_ping");
	http::response resp = flap(REQUEST_ACTIVE_PING);
	// Remember this last ping time
	parent->m_pingTS = ::time(NULL);
	if (resp.data.empty() || resp.data.find("\"t\":\"pong\"") == resp.data.npos) {
		// Something went wrong
		return handle_error("activity_ping");
	}
	return handle_success("activity_ping");
}
int facebook_client::send_message(int seqid, MCONTACT hContact, const std::string &message_text, std::string *error_text, const std::string &captcha_persist_data, const std::string &captcha)
{
	handle_entry("send_message");
	http::response resp;
	std::string data;
	if (!captcha.empty()) {
		data += "&captcha_persist_data=" + captcha_persist_data;
		data += "&recaptcha_challenge_field=";
		data += "&captcha_response=" + captcha;
	}
	boolean isChatRoom = parent->isChatRoom(hContact);
	ptrA userId( parent->getStringA(hContact, FACEBOOK_KEY_ID));
	ptrA threadId( parent->getStringA(hContact, FACEBOOK_KEY_TID));
	
	// Check if we have userId/threadId to be able to send message
	if ((isChatRoom && (threadId == NULL || !mir_strcmp(threadId, "null")))
		|| (!isChatRoom && (userId == NULL || !mir_strcmp(userId, "null")))) {
		// This shouldn't happen unless user manually deletes some data via Database Editor++
		*error_text = Translate("Contact doesn't have required data in database.");
		handle_error("send_message");
		return SEND_MESSAGE_ERROR;
	}
	data += "&message_batch[0][action_type]=ma-type:user-generated-message";
	
	if (isChatRoom) {
		data += "&message_batch[0][thread_id]=" + std::string(threadId);
	} else {
		data += "&message_batch[0][specific_to_list][0]=fbid:" + std::string(userId);
		data += "&message_batch[0][specific_to_list][1]=fbid:" + this->self_.user_id;
		data += "&message_batch[0][client_thread_id]=user:" + std::string(userId);
	}
	data += "&message_batch[0][thread_fbid]";
	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][timestamp_time_passed]";
	data += "&message_batch[0][is_unread]=false";
	data += "&message_batch[0][is_forward]=false";
	data += "&message_batch[0][is_filtered_content]=false";
	data += "&message_batch[0][is_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][signatureID]";
	data += "&message_batch[0][ui_push_phase]";
	data += "&message_batch[0][status]=0";
	data += "&message_batch[0][message_id]";
	data += "&message_batch[0][manual_retry_cnt]";
	data += "&client=mercury&__a=1&__dyn&__req&__rev";
	data += "&fb_dtsg=" + this->dtsg_;
	data += "&__user=" + this->self_.user_id;
	data += "&ttstamp=" + ttstamp_;
	{
		ScopedLock s(send_message_lock_);
		resp = flap(REQUEST_MESSAGE_SEND_CHAT, &data);
		*error_text = resp.error_text;
		if (resp.error_number == 0) {
			// Everything is OK
			// Remember this message id
			std::string mid = utils::text::source_get_value(&resp.data, 2, "\"message_id\":\"", "\"");
			if (mid.empty())
				mid = utils::text::source_get_value(&resp.data, 2, "\"mid\":\"", "\"");
			// For classic contacts remember last message id
			if (!parent->isChatRoom(hContact))
				parent->setString(hContact, FACEBOOK_KEY_MESSAGE_ID, mid.c_str());
			// Get timestamp
			std::string timestamp = utils::text::source_get_value(&resp.data, 2, "\"timestamp\":", ",");
			time_t time = utils::time::from_string(timestamp);
			// Remember last action timestamp for history sync
			parent->setDword(FACEBOOK_KEY_LAST_ACTION_TS, time);
			// For classic conversation we try to replace timestamp of added event in OnPreCreateEvent()
			if (seqid > 0)
				messages_timestamp.insert(std::make_pair(seqid, time));
			// We have this message in database, so ignore further tries (in channel) to add it again
			messages_ignore.insert(std::make_pair(mid, 0));
		}
	}
	switch (resp.error_number) {
	case 0: 
		// Everything is OK
		break;
	// case 1356002: // You are offline (probably you can't use mercury or some other request when chat is offline)
	case 1356003: // Contact is offline
		parent->setWord(hContact, "Status", ID_STATUS_OFFLINE);
		return SEND_MESSAGE_ERROR;
	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 SEND_MESSAGE_ERROR;
	case 1357007: // Security check (captcha) is required
		{
			std::string imageUrl = utils::text::html_entities_decode(utils::text::slashu_to_utf8(utils::text::source_get_value(&resp.data, 3, "img class=\\\"img\\\"", "src=\\\"", "\\\"")));
			std::string captchaPersistData = utils::text::source_get_value(&resp.data, 3, "\\\"captcha_persist_data\\\"", "value=\\\"", "\\\"");
			parent->debugLogA("    Got imageUrl (first): %s", imageUrl.c_str());
			parent->debugLogA("    Got captchaPersistData (first): %s", captchaPersistData.c_str());
			std::string capStr = "new_captcha_type=TFBCaptcha&skipped_captcha_data=" + captchaPersistData;
			capStr += "&__dyn=&__req=&__rev=&__user=" + this->self_.user_id;
			http::response capResp = flap(REQUEST_CAPTCHA_REFRESH, NULL, &capStr);
			if (capResp.code == HTTP_CODE_OK) {
				imageUrl = utils::text::html_entities_decode(utils::text::slashu_to_utf8(utils::text::source_get_value(&capResp.data, 3, "img class=\\\"img\\\"", "src=\\\"", "\\\"")));
				captchaPersistData = utils::text::source_get_value(&capResp.data, 3, "\\\"captcha_persist_data\\\"", "value=\\\"", "\\\"");
				parent->debugLogA("    Got imageUrl (second): %s", imageUrl.c_str());
				parent->debugLogA("    Got captchaPersistData (second): %s", captchaPersistData.c_str());
				std::string result;
				if (!parent->RunCaptchaForm(imageUrl, result)) {
					*error_text = Translate("User cancel captcha challenge.");
					return SEND_MESSAGE_CANCEL;
				}
				return send_message(seqid, hContact, message_text, error_text, captchaPersistData, result);
			}
		}
		return SEND_MESSAGE_CANCEL; // Cancel because we failed to load captcha image so we can't continue only with error
	//case 1404123: // Blocked sending messages (with URLs) because Facebook think our computer is infected with malware
	default: // Other error
		parent->debugLogA("!!! Send message error #%d: %s", resp.error_number, resp.error_text.c_str());
		return SEND_MESSAGE_ERROR;
	}
	switch (resp.code) {
	case HTTP_CODE_OK:
		handle_success("send_message");
		return SEND_MESSAGE_OK;
	case HTTP_CODE_FAKE_ERROR:
	case HTTP_CODE_FAKE_DISCONNECTED:
	default:
		*error_text = Translate("Timeout when sending message.");
		handle_error("send_message");
		return SEND_MESSAGE_ERROR;
	}
}
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_;
		data += "&user_id=" + status->user_id;
		data += "&url=" + std::string(FACEBOOK_URL_HOMEPAGE);
		flap(REQUEST_IDENTITY_SWITCH, &data);
	}
	std::string data;
	if (!status->url.empty()) {
		data = "fb_dtsg=" + this->dtsg_;
		data += "&targetid=" + (status->user_id.empty() ? this->self_.user_id : status->user_id);
		data += "&xhpc_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=" + ttstamp_;
		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);
		std::string temp = utils::text::html_entities_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=" + ttstamp_;
		std::string form = utils::text::source_get_value(&temp, 2, "