From 8ae09e329384682579d59fc92cd7ed5de37e1351 Mon Sep 17 00:00:00 2001
From: George Hazan <ghazan@miranda.im>
Date: Thu, 28 Dec 2017 20:30:32 +0300
Subject: Facebook:

- fixes problems with decoding attachments;
- major rework of class structure;
- version bump
---
 protocols/FacebookRM/src/client.h            |   2 +-
 protocols/FacebookRM/src/contacts.cpp        |   8 +-
 protocols/FacebookRM/src/json.cpp            | 347 ++++++++++++---------------
 protocols/FacebookRM/src/json.h              |  49 ----
 protocols/FacebookRM/src/process.cpp         |  20 +-
 protocols/FacebookRM/src/proto.h             |  20 +-
 protocols/FacebookRM/src/requests/contacts.h |   8 +-
 protocols/FacebookRM/src/requests/history.h  |  66 +++--
 protocols/FacebookRM/src/stdafx.h            |   1 -
 protocols/FacebookRM/src/version.h           |   6 +-
 10 files changed, 243 insertions(+), 284 deletions(-)
 delete mode 100644 protocols/FacebookRM/src/json.h

(limited to 'protocols')

diff --git a/protocols/FacebookRM/src/client.h b/protocols/FacebookRM/src/client.h
index 4d7fe6ef1e..8d80ea72ef 100644
--- a/protocols/FacebookRM/src/client.h
+++ b/protocols/FacebookRM/src/client.h
@@ -161,7 +161,7 @@ public:
 	}
 
 	__inline const char *__rev() {
-		return "2828561"; // FIXME: Some version of communication protocol? This version is from 12.2.2017
+		return "3496859"; // FIXME: Some version of communication protocol? This version is from 3.12.2017
 	}
 
 	////////////////////////////////////////////////////////////
diff --git a/protocols/FacebookRM/src/contacts.cpp b/protocols/FacebookRM/src/contacts.cpp
index 104877514a..5a3f14ac93 100644
--- a/protocols/FacebookRM/src/contacts.cpp
+++ b/protocols/FacebookRM/src/contacts.cpp
@@ -177,7 +177,7 @@ std::string FacebookProto::ThreadIDToContactID(const std::string &thread_id)
 
 	if (resp.code == HTTP_CODE_OK) {
 		try {
-			facebook_json_parser(this).parse_thread_info(&resp.data, &user_id);
+			ParseThreadInfo(&resp.data, &user_id);
 
 			if (!user_id.empty())
 				facy.thread_id_to_user_id.insert(std::make_pair(thread_id, user_id));
@@ -208,7 +208,7 @@ void FacebookProto::LoadContactInfo(facebook_user* fbu)
 
 	if (resp.code == HTTP_CODE_OK) {
 		try {
-			facebook_json_parser(this).parse_user_info(&resp.data, fbu);
+			ParseUserInfo(&resp.data, fbu);
 			debugLogA("*** Contact thread info processed");
 		}
 		catch (const std::exception &e) {
@@ -276,7 +276,7 @@ void FacebookProto::LoadParticipantsNames(facebook_chatroom *fbc)
 		if (resp.code == HTTP_CODE_OK) {
 			try {
 				// TODO: We can cache these results and next time (e.g. for different chatroom) we can use that already cached names
-				facebook_json_parser(this).parse_chat_participant_names(&resp.data, &fbc->participants);
+				ParseChatParticipants(&resp.data, &fbc->participants);
 				debugLogA("*** Participant names processed");
 			}
 			catch (const std::exception &e) {
@@ -315,7 +315,7 @@ void FacebookProto::LoadChatInfo(facebook_chatroom *fbc)
 	}
 
 	try {
-		facebook_json_parser(this).parse_chat_info(&resp.data, fbc);
+		ParseChatInfo(&resp.data, fbc);
 
 		// Load missing participants names
 		LoadParticipantsNames(fbc);
diff --git a/protocols/FacebookRM/src/json.cpp b/protocols/FacebookRM/src/json.cpp
index 2deb45172f..369e2fdafd 100644
--- a/protocols/FacebookRM/src/json.cpp
+++ b/protocols/FacebookRM/src/json.cpp
@@ -73,7 +73,7 @@ void parseUser(const JSONNode &it, facebook_user *fbu)
 	}
 }
 
-void parseMessageType(FacebookProto *proto, facebook_message &message, const JSONNode &log_type_, const JSONNode &log_body_, const JSONNode &log_data_) {
+void FacebookProto::ParseMessageType(facebook_message &message, const JSONNode &log_type_, const JSONNode &log_body_, const JSONNode &log_data_) {
 	if (!log_type_ || !log_body_ || !log_data_)
 		return;
 
@@ -118,11 +118,11 @@ void parseMessageType(FacebookProto *proto, facebook_message &message, const JSO
 		message.type = THREAD_IMAGE;
 	}
 	else {
-		proto->debugLogA("!!! Unknown log type - %s", logType.c_str());
+		debugLogA("!!! Unknown log type - %s", logType.c_str());
 	}
 }
 
-int facebook_json_parser::parse_chat_participant_names(std::string *data, std::map<std::string, chatroom_participant>* participants)
+int FacebookProto::ParseChatParticipants(std::string *data, std::map<std::string, chatroom_participant>* participants)
 {
 	std::string jsonData = data->substr(9);
 
@@ -154,7 +154,7 @@ int facebook_json_parser::parse_chat_participant_names(std::string *data, std::m
 			else if (type == "page") {
 				user.role = ROLE_NONE;
 				// Use prefix for "page" users
-				user.nick = proto->m_pagePrefix + " " + userName;
+				user.nick = m_pagePrefix + " " + userName;
 			}
 
 			user.loaded = true;
@@ -164,7 +164,7 @@ int facebook_json_parser::parse_chat_participant_names(std::string *data, std::m
 	return EXIT_SUCCESS;
 }
 
-int facebook_json_parser::parse_friends(std::string *data, std::map< std::string, facebook_user* >* friends, bool loadAllContacts)
+int FacebookProto::ParseFriends(std::string *data, std::map< std::string, facebook_user* >* friends, bool loadAllContacts)
 {
 	std::string jsonData = data->substr(9);
 
@@ -193,7 +193,7 @@ int facebook_json_parser::parse_friends(std::string *data, std::map< std::string
 }
 
 
-int facebook_json_parser::parse_notifications(std::string *data, std::map< std::string, facebook_notification* > *notifications)
+int FacebookProto::ParseNotifications(std::string *data, std::map< std::string, facebook_notification* > *notifications)
 {
 	std::string jsonData = data->substr(9);
 
@@ -206,7 +206,7 @@ int facebook_json_parser::parse_notifications(std::string *data, std::map< std::
 		return EXIT_FAILURE;
 
 	// Create notifications chatroom (if it doesn't exists), because we will be writing to it new notifications here
-	proto->PrepareNotificationsChatRoom();
+	PrepareNotificationsChatRoom();
 
 	for (auto it = list.begin(); it != list.end(); ++it) {
 		const JSONNode &id_ = (*it)["alert_id"];
@@ -235,7 +235,7 @@ int facebook_json_parser::parse_notifications(std::string *data, std::map< std::
 		notification->setIcon(icon_.as_string());
 
 		// Write notification to chatroom
-		proto->UpdateNotificationsChatRoom(notification);
+		UpdateNotificationsChatRoom(notification);
 
 		// If it's unseen, remember it, otherwise forget it
 		if (notifications->find(notification->id) == notifications->end())
@@ -247,19 +247,19 @@ int facebook_json_parser::parse_notifications(std::string *data, std::map< std::
 	return EXIT_SUCCESS;
 }
 
-bool ignore_duplicits(FacebookProto *proto, const std::string &mid)
+bool FacebookProto::IgnoreDuplicates(const std::string &mid)
 {
-	ScopedLock s(proto->facy.send_message_lock_);
+	ScopedLock s(facy.send_message_lock_);
 
-	std::map<std::string, int>::iterator it = proto->facy.messages_ignore.find(mid);
-	if (it != proto->facy.messages_ignore.end()) {
-		proto->debugLogA("??? Ignoring duplicit/sent message ID: %s", mid.c_str());
+	std::map<std::string, int>::iterator it = facy.messages_ignore.find(mid);
+	if (it != facy.messages_ignore.end()) {
+		debugLogA("??? Ignoring duplicit/sent message ID: %s", mid.c_str());
 		it->second++; // increase counter (for deleting it later)
 		return true;
 	}
 
 	// remember this id to ignore duplicits
-	proto->facy.messages_ignore.insert(std::make_pair(mid, 1));
+	facy.messages_ignore.insert(std::make_pair(mid, 1));
 	return false;
 }
 
@@ -270,10 +270,10 @@ std::string absolutizeUrl(std::string &url) {
 	return url;
 }
 
-void parseAttachments(FacebookProto *proto, std::string *message_text, const JSONNode &delta_, std::string other_user_fbid, bool legacy)
+void FacebookProto::ParseAttachments(std::string &message_text, const JSONNode &delta_, std::string other_user_fbid, bool legacy)
 {
 	std::string attachments_text;
-	std::string type;
+	std::wstring newText;
 
 	const JSONNode &attachments_ = delta_["attachments"];
 	if (!attachments_ || attachments_.empty())
@@ -282,36 +282,60 @@ void parseAttachments(FacebookProto *proto, std::string *message_text, const JSO
 	for (auto itAttachment = attachments_.begin(); itAttachment != attachments_.end(); ++itAttachment) {
 		const JSONNode &attach_ = legacy ? (*itAttachment) : (*itAttachment)["mercury"];
 
-		type = attach_["attach_type"].as_string();  // "sticker", "photo", "file", "share", "animated_image", "video"
-
-		if (type == "photo") {
-			std::string filename = attach_["name"].as_string();
-			std::string link = attach_["hires_url"].as_string();
-			if (link.empty()) {
-				link = attach_["large_preview_url"].as_string();
+		if (const JSONNode sticker_ = attach_["sticker_attachment"]) {
+			newText = TranslateT("a sticker");
+			attachments_text += "\n";
+
+			std::string link = sticker_["url"].as_string();
+			if (!link.empty())
+				attachments_text += absolutizeUrl(link) + "\n";
+
+			std::string label = sticker_["label"].as_string();
+			if (!label.empty())
+				attachments_text += label + "\n";
+
+			const JSONNode &stickerId_ = sticker_["id"];
+			if (stickerId_) {
+				// Stickers as smileys
+				if (getByte(FACEBOOK_KEY_CUSTOM_SMILEYS, DEFAULT_CUSTOM_SMILEYS) && !facy.loading_history) {
+					// FIXME: rewrite smileyadd to use custom smileys per protocol and not per contact and then remove this ugliness
+					if (!other_user_fbid.empty()) {
+						std::string sticker = "[[sticker:" + stickerId_.as_string() + "]]";
+						StickerAsSmiley(sticker, link, ContactIDToHContact(other_user_fbid));
+					}
+				}
 			}
-			if (link.empty()) {
-				link = attach_["preview_url"].as_string();
+		}
+		else if (const JSONNode blob_ = attach_["blob_attachment"]) {
+			std::string type = blob_["__typename"].as_string();
+			if (type == "MessageAnimatedImage") { // a GIF
+				newText = TranslateT("a GIF");
+
+				std::string link = blob_["animated_image"]["uri"].as_string();
+				if (!link.empty())
+					attachments_text += "\n" + absolutizeUrl(link) + "\n";
 			}
+			else {
+				newText = (attachments_.size() > 1) ? TranslateT("files") : TranslateT("a file");
 
-			if (!link.empty()) {
-				attachments_text += "\n" + (!filename.empty() ? "<" + filename + "> " : "") + absolutizeUrl(link) + "\n";
-			}
-		}
-		else if (type == "file" || type == "video") {
-			std::string filename = attach_["name"].as_string();
-			std::string link = attach_["url"].as_string();
+				std::string filename = blob_["filename"].as_string();
+				std::string filesize = attach_["fileSize"].as_string();
+				if (!filesize.empty())
+					filename += ", " + filesize + " bytes";
 
-			if (!link.empty()) {
-				attachments_text += "\n" + (!filename.empty() ? "<" + filename + "> " : "") + absolutizeUrl(link) + "\n";
+				std::string link = blob_["url"].as_string();
+				if (!link.empty())
+					attachments_text += "\n" + (!filename.empty() ? "<" + filename + "> " : "") + absolutizeUrl(link) + "\n";
 			}
 		}
-		else if (type == "share") {
-			const JSONNode &share = attach_["share"];
-			if (share) {
-				std::string title = share["title"] ? share["title"].as_string() : "";
-				std::string description = share["description"] ? share["description"].as_string() : "";
-				std::string link = share["uri"].as_string();
+		else if (const JSONNode story_ = attach_["extensible_attachment"]["story_attachment"]) {
+			std::string type = story_["target"]["__typename"].as_string();
+			if (type == "ExternalUrl") {
+				newText = TranslateT("a link");
+
+				std::string title = story_["title_with_entities"]["text"].as_string();
+				std::string description = story_["description"]["text"].as_string();
+				std::string link = story_["url"].as_string();
 
 				// shorten long descriptions
 				description = utils::text::truncate_utf8(description, MAX_LINK_DESCRIPTION_LEN);
@@ -330,83 +354,29 @@ void parseAttachments(FacebookProto *proto, std::string *message_text, const JSO
 					attachments_text += absolutizeUrl(link) + "\n";
 				}
 			}
+			else debugLogA("json::parseAttachments (%s) - Unknown extensible attachment type %s", legacy ? "legacy" : "not legacy", type.c_str());
 		}
-		else if (type == "sticker") {
-			std::string link = attach_["url"].as_string();
-			if (!link.empty()) {
-				attachments_text += "\n" + absolutizeUrl(link) + "\n";
-			}
-
-			const JSONNode &metadata = attach_["metadata"];
-			if (metadata) {
-				const JSONNode &stickerId_ = metadata["stickerID"];
-				if (stickerId_) {
-					std::string sticker = "[[sticker:" + stickerId_.as_string() + "]]\n";
-					attachments_text += sticker;
-
-					// Stickers as smileys
-					if (proto->getByte(FACEBOOK_KEY_CUSTOM_SMILEYS, DEFAULT_CUSTOM_SMILEYS) && !proto->facy.loading_history) {
-						// FIXME: rewrite smileyadd to use custom smileys per protocol and not per contact and then remove this ugliness
-						if (!other_user_fbid.empty()) {
-							MCONTACT hContact = proto->ContactIDToHContact(other_user_fbid);
-							proto->StickerAsSmiley(sticker, link, hContact);
-						}
-					}
-				}
-			}
-		}
-		else if (type == "animated_image") {
-			std::string link = attach_["hires_url"].as_string();
-			if (link.empty()) {
-				link = attach_["large_preview_url"].as_string();
-			}
-			if (link.empty()) {
-				link = attach_["preview_url"].as_string();
-			}
-
-			if (!link.empty()) {
-				attachments_text += "\n" + absolutizeUrl(link) + "\n";
-			}
-		}
-		else {
-			proto->debugLogA("json::parseAttachments (%s) - Unknown attachment type '%s'", legacy ? "legacy" : "not legacy", type.c_str());
-		}
+		else debugLogA("json::parseAttachments (%s) - Unknown attachment type", legacy ? "legacy" : "not legacy");
 	}
 
 	// TODO: have this as extra event, not replace or append message content
-	if (!message_text->empty())
-		*message_text += "\n\n";
+	if (!message_text.empty())
+		message_text += "\n\n";
 
 	if (!attachments_text.empty()) {
-		std::wstring newText;
-		if (type == "sticker")
-			newText = TranslateT("a sticker");
-		else if (type == "share")
-			newText = TranslateT("a link");
-		else if (type == "file")
-			newText = (attachments_.size() > 1) ? TranslateT("files") : TranslateT("a file");
-		else if (type == "photo")
-			newText = (attachments_.size() > 1) ? TranslateT("photos") : TranslateT("a photo");
-		else if (type == "video")
-			newText = TranslateT("a video");
-		else if (type == "animated_image")
-			newText = TranslateT("a GIF");
-		else
-			newText = _A2T(type.c_str());
-
 		wchar_t title[200];
 		mir_snwprintf(title, TranslateT("User sent %s:"), newText.c_str());
 
-		*message_text += T2Utf(title);
-		*message_text += attachments_text;
+		message_text += T2Utf(title);
+		message_text += attachments_text;
 	}
 	else {
-		*message_text += T2Utf(TranslateT("User sent an unsupported attachment. Open your browser to see it."));
-		proto->debugLogA("json::parseAttachments (%s) - Unsupported attachment:\n%s", legacy ? "legacy" : "not legacy", attachments_.as_string().c_str());
+		message_text += T2Utf(TranslateT("User sent an unsupported attachment. Open your browser to see it."));
+		debugLogA("json::parseAttachments (%s) - Unsupported attachment:\n%s", legacy ? "legacy" : "not legacy", attachments_.as_string().c_str());
 	}
 }
 
-bool parseMessageMetadata(FacebookProto *proto, facebook_message &message, const JSONNode &meta_)
+bool FacebookProto::ParseMessageMetadata(facebook_message &message, const JSONNode &meta_)
 {
 	if (!meta_)
 		return false;
@@ -426,7 +396,7 @@ bool parseMessageMetadata(FacebookProto *proto, facebook_message &message, const
 	std::string threadFbId = (!otherUserFbId_ && threadFbId_ ? "id." + threadFbId_.as_string() : ""); // NOTE: we must add "id." prefix as this is threadFbId and we want threadId (but only for multi chats)
 
 	message.isChat = otherUserFbId.empty();
-	message.isIncoming = (actorFbId_.as_string() != proto->facy.self_.user_id);
+	message.isIncoming = (actorFbId_.as_string() != facy.self_.user_id);
 	message.isUnread = message.isIncoming;
 	message.message_text = (adminText_ ? adminText_.as_string() : "");
 	message.time = utils::time::from_string(timestamp_.as_string());
@@ -436,20 +406,20 @@ bool parseMessageMetadata(FacebookProto *proto, facebook_message &message, const
 	return true;
 }
 
-bool processSpecialMessage(FacebookProto *proto, std::vector<facebook_message>* messages, const JSONNode &meta_, MessageType messageType, const std::string &messageData = "")
+bool FacebookProto::ProcessSpecialMessage(std::vector<facebook_message>* messages, const JSONNode &meta_, MessageType messageType, const std::string &messageData)
 {
 	facebook_message message;
 	message.type = messageType;
 	message.data = messageData;
 
 	// Parse message metadata
-	if (!parseMessageMetadata(proto, message, meta_)) {
-		proto->debugLogA("json::processSpecialMessage - given empty messageMetadata");
+	if (!ParseMessageMetadata(message, meta_)) {
+		debugLogA("json::ProcessSpecialMessage - given empty messageMetadata");
 		return false;
 	}
 
 	// Ignore duplicits or messages sent from miranda
-	if (ignore_duplicits(proto, message.message_id)) {
+	if (IgnoreDuplicates(message.message_id)) {
 		return false;
 	}
 
@@ -458,12 +428,12 @@ bool processSpecialMessage(FacebookProto *proto, std::vector<facebook_message>*
 }
 
 
-int facebook_json_parser::parse_messages(std::string *pData, std::vector<facebook_message>* messages, std::map< std::string, facebook_notification* >* notifications)
+int FacebookProto::ParseMessages(std::string *pData, std::vector<facebook_message>* messages, std::map< std::string, facebook_notification* >* notifications)
 {
 	// remove old received messages from map		
-	for (std::map<std::string, int>::iterator it = proto->facy.messages_ignore.begin(); it != proto->facy.messages_ignore.end();) {
+	for (std::map<std::string, int>::iterator it = facy.messages_ignore.begin(); it != facy.messages_ignore.end();) {
 		if (it->second > FACEBOOK_IGNORE_COUNTER_LIMIT) {
-			it = proto->facy.messages_ignore.erase(it);
+			it = facy.messages_ignore.erase(it);
 		}
 		else {
 			it->second++; // increase counter on each request
@@ -499,20 +469,20 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 
 				// Parse message metadata				
 				const JSONNode &meta_ = delta_["messageMetadata"];
-				if (!parseMessageMetadata(proto, message, meta_)) {
-					proto->debugLogA("json::parse_messages - No messageMetadata element");
+				if (!ParseMessageMetadata(message, meta_)) {
+					debugLogA("json::ParseMessages - No messageMetadata element");
 					continue;
 				}
 
 				// Ignore duplicits or messages sent from miranda
-				if (ignore_duplicits(proto, message.message_id))
+				if (IgnoreDuplicates(message.message_id))
 					continue;
 
 				const JSONNode &body_ = delta_["body"];
 				std::string messageText = body_.as_string();
 
 				// Process attachements and stickers
-				parseAttachments(proto, &messageText, delta_, (message.isChat ? "" : message.user_id), false);
+				ParseAttachments(messageText, delta_, (message.isChat ? "" : message.user_id), false);
 
 				message.message_text = utils::text::trim(messageText, true);
 				messages->push_back(message);
@@ -544,21 +514,21 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 					// multi chat
 					
 					// ignore if disabled
-					if (!proto->m_enableChat)
+					if (!m_enableChat)
 						continue;
 
 					std::string readerId = actor_.as_string();
 					std::string tid = "id." + thread_.as_string(); // NOTE: threadFbId means just numeric id of thread, without "id." prefix. We add it here to have it consistent with other methods (where threadId is used)
 
-					MCONTACT hContact = proto->ChatIDToHContact(tid);
-					proto->facy.insert_reader(hContact, timestamp, readerId);
+					MCONTACT hContact = ChatIDToHContact(tid);
+					facy.insert_reader(hContact, timestamp, readerId);
 				}
 				else if (reader_) {
 					// single chat
 					std::string userId = reader_.as_string();
 
-					MCONTACT hContact = proto->ContactIDToHContact(userId);
-					proto->facy.insert_reader(hContact, timestamp);
+					MCONTACT hContact = ContactIDToHContact(userId);
+					facy.insert_reader(hContact, timestamp);
 				}
 			}
 			else if (cls == "MarkRead") { // revised 5.3.2017
@@ -601,10 +571,10 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 					//const JSONNode &thread_icon_ = untyped_["thread_icon"]; // emoji symbol
 				}
 				else {
-					proto->debugLogA("json::parse_messages - Unknown AdminTextMessage type '%s'", deltaType.c_str());
+					debugLogA("json::ParseMessages - Unknown AdminTextMessage type '%s'", deltaType.c_str());
 				}
 
-				if (!processSpecialMessage(proto, messages, meta_, messageType)) {
+				if (!ProcessSpecialMessage(messages, meta_, messageType)) {
 					// Message wasn't added - either it is duplicate or there was some error
 					continue;
 				}
@@ -619,17 +589,14 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 				const JSONNode &eventType_ = delta_["eventType"]; // e.g. "VOICE_EVENT", "VIDEO_EVENT"
 
 				std::string eventType = eventType_.as_string();
-				if (eventType == "VOICE_EVENT") {
+				if (eventType == "VOICE_EVENT")
 					messageType = PHONE_CALL;
-				}
-				else if (eventType == "VIDEO_EVENT") {
+				else if (eventType == "VIDEO_EVENT")
 					messageType = VIDEO_CALL;
-				}
-				else {
-					proto->debugLogA("json::parse_messages - Unknown RTCEventLog type '%s'", eventType.c_str());
-				}
+				else
+					debugLogA("json::ParseMessages - Unknown RTCEventLog type '%s'", eventType.c_str());
 
-				if (!processSpecialMessage(proto, messages, meta_, messageType)) {
+				if (!ProcessSpecialMessage(messages, meta_, messageType)) {
 					// Message wasn't added - either it is duplicate or there was some error
 					continue;
 				}
@@ -641,7 +608,7 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 				const JSONNode &name_ = delta_["name"]; // new name of the chat (could be empty)
 				std::string data = (name_ ? name_.as_string() : "");
 
-				processSpecialMessage(proto, messages, meta_, THREAD_NAME, data);
+				ProcessSpecialMessage(messages, meta_, THREAD_NAME, data);
 			}
 			else if (cls == "ThreadMuteSettings") {
 				//const JSONNode &expireTime_ = delta_["expireTime"]; // timestamp until which this thread will be muted; could be 0 = unmuted
@@ -654,7 +621,7 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 				const JSONNode &leftParticipantFbId_ = delta_["leftParticipantFbId"];
 				std::string data = (leftParticipantFbId_ ? leftParticipantFbId_.as_string() : "");
 
-				processSpecialMessage(proto, messages, meta_, UNSUBSCRIBE, data);
+				ProcessSpecialMessage(messages, meta_, UNSUBSCRIBE, data);
 			}
 			else if (cls == "ParticipantsAddedToGroupThread") {
 				// user was added to multi user chat
@@ -676,11 +643,11 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 					}
 				}
 
-				processSpecialMessage(proto, messages, meta_, SUBSCRIBE, data);
+				ProcessSpecialMessage(messages, meta_, SUBSCRIBE, data);
 			}
 			else {
 				// DeliveryReceipt, MarkRead, ThreadDelete
-				proto->debugLogA("json::parse_messages - Unknown delta class '%s'", cls.c_str());
+				debugLogA("json::ParseMessages - Unknown delta class '%s'", cls.c_str());
 			}
 		}
 		else if (t == "notification_json") {
@@ -688,7 +655,7 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 			const JSONNode &nodes = (*it)["nodes"];
 
 			// Create notifications chatroom (if it doesn't exists), because we will be writing to it new notifications here
-			proto->PrepareNotificationsChatRoom();
+			PrepareNotificationsChatRoom();
 
 			for (auto itNodes = nodes.begin(); itNodes != nodes.end(); ++itNodes) {
 				const JSONNode &text_ = (*itNodes)["unaggregatedTitle"]; // notifications one by one, not grouped
@@ -708,9 +675,9 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 					continue;
 
 				time_t timestamp = utils::time::from_string(time.as_string());
-				if (timestamp > proto->facy.last_notification_time_) {
+				if (timestamp > facy.last_notification_time_) {
 					// Only new notifications
-					proto->facy.last_notification_time_ = timestamp;
+					facy.last_notification_time_ = timestamp;
 
 					facebook_notification* notification = new facebook_notification();
 					notification->text = utils::text::slashu_to_utf8(text.as_string());
@@ -725,7 +692,7 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 						notification->id = notification->id.substr(pos + 1);
 
 					// Write notification to chatroom
-					proto->UpdateNotificationsChatRoom(notification);
+					UpdateNotificationsChatRoom(notification);
 
 					// If it's unseen, remember it, otherwise forget it (here it will always be unseen)
 					if (notifications->find(notification->id) == notifications->end() && !notification->seen)
@@ -761,14 +728,14 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 				std::string alert_id = alertId_.as_string();
 
 				// Notify it, if user wants to be notified
-				if (proto->getByte(FACEBOOK_KEY_EVENT_FRIENDSHIP_ENABLE, DEFAULT_EVENT_FRIENDSHIP_ENABLE)) {
-					proto->NotifyEvent(proto->m_tszUserName, ptrW(mir_utf8decodeW(text.c_str())), NULL, EVENT_FRIENDSHIP, &url, alert_id.empty() ? nullptr : &alert_id);
+				if (getByte(FACEBOOK_KEY_EVENT_FRIENDSHIP_ENABLE, DEFAULT_EVENT_FRIENDSHIP_ENABLE)) {
+					NotifyEvent(m_tszUserName, ptrW(mir_utf8decodeW(text.c_str())), NULL, EVENT_FRIENDSHIP, &url, alert_id.empty() ? nullptr : &alert_id);
 				}
 			}
 		}
 		else if (t == "jewel_requests_add") {
 			// New friendship request, load them all with real names (because there is only user_id in "from" field)
-			proto->ForkThread(&FacebookProto::ProcessFriendRequests, nullptr);
+			ForkThread(&FacebookProto::ProcessFriendRequests, nullptr);
 		}
 		/*else if (t == "jewel_requests_handled") { // revised 5.3.2017
 			// When some request is approved (or perhaps even ignored/removed)
@@ -789,22 +756,22 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 			const JSONNode &st_ = (*it)["st"]; // typing status - 1 = started typing, 0 = stopped typing
 
 			// Ignore wrong (without "from") typing notifications or that are not meant for us (but e.g. for our page)
-			if (!from_ || to_.as_string() != proto->facy.self_.user_id)
+			if (!from_ || to_.as_string() != facy.self_.user_id)
 				continue;
 
 			facebook_user fbu;
 			fbu.user_id = from_.as_string();
 			fbu.type = CONTACT_FRIEND; // only friends are able to send typing notifications
-			MCONTACT hContact = proto->AddToContactList(&fbu);
+			MCONTACT hContact = AddToContactList(&fbu);
 
 			if (st_.as_int() == 1)
-				proto->StartTyping(hContact);
+				StartTyping(hContact);
 			else
-				proto->StopTyping(hContact);
+				StopTyping(hContact);
 		}
 		else if (t == "ttyp") {
 			// multi chat typing notification
-			if (!proto->m_enableChat)
+			if (!m_enableChat)
 				continue;
 
 			const JSONNode &from_ = (*it)["from"];
@@ -816,8 +783,8 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 			std::string tid = thread_.as_string();
 			std::string from_id = from_.as_string();
 
-			auto itRoom = proto->facy.chat_rooms.find(thread_.as_string());
-			if (itRoom != proto->facy.chat_rooms.end()) {
+			auto itRoom = facy.chat_rooms.find(thread_.as_string());
+			if (itRoom != facy.chat_rooms.end()) {
 				facebook_chatroom *chatroom = itRoom->second;
 				std::map<std::string, chatroom_participant> participants = chatroom->participants;
 
@@ -827,12 +794,12 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 					chatroom_participant new_participant;
 					new_participant.user_id = from_id;
 					new_participant.nick = from_id;
-					proto->AddChatContact(tid.c_str(), new_participant, true);
+					AddChatContact(tid.c_str(), new_participant, true);
 				}
 
 				participant = participants.find(from_id);
 				if (participant != participants.end()) {
-					MCONTACT hChatContact = proto->ChatIDToHContact(tid);
+					MCONTACT hChatContact = ChatIDToHContact(tid);
 					ptrW name(mir_utf8decodeW(participant->second.nick.c_str()));
 
 					if (st_.as_int() == 1)
@@ -858,11 +825,11 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 				const JSONNode &visibility = event_data["visibility"];
 
 				bool isVisible = visibility && visibility.as_bool();
-				proto->debugLogA("    Requested chat switch to %s", isVisible ? "Online" : "Offline");
+				debugLogA("    Requested chat switch to %s", isVisible ? "Online" : "Offline");
 
 				// If actual status is not what server says, change it (handle also local away status, which means online)
-				if (isVisible != (proto->m_iStatus != ID_STATUS_INVISIBLE))
-					proto->SetStatus(isVisible ? ID_STATUS_ONLINE : ID_STATUS_INVISIBLE);
+				if (isVisible != (m_iStatus != ID_STATUS_INVISIBLE))
+					SetStatus(isVisible ? ID_STATUS_ONLINE : ID_STATUS_INVISIBLE);
 			}
 		}
 		else if (t == "chatproxy-presence") {
@@ -875,7 +842,7 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 			for (auto itNodes = buddyList.begin(); itNodes != buddyList.end(); ++itNodes) {
 				std::string id = (*itNodes).name();
 
-				MCONTACT hContact = proto->ContactIDToHContact(id);
+				MCONTACT hContact = ContactIDToHContact(id);
 				if (!hContact) {
 					// Facebook now sends info also about some nonfriends, so we just ignore status change of contacts we don't have in list
 					continue;
@@ -898,7 +865,7 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 					else if (p == 2)
 						status = ID_STATUS_ONLINE;
 
-					proto->setWord(hContact, "Status", status);
+					setWord(hContact, "Status", status);
 				}
 
 				// Last active time
@@ -906,13 +873,13 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 					time_t last_active = utils::time::from_string(lat_.as_string());
 
 					if (last_active > 0)
-						proto->setDword(hContact, "LastActiveTS", last_active);
+						setDword(hContact, "LastActiveTS", last_active);
 					else
-						proto->delSetting(hContact, "LastActiveTS");
+						delSetting(hContact, "LastActiveTS");
 
 					// Set users inactive for too long as offline
 					if (last_active > 0 && last_active < offlineThreshold) {
-						proto->setWord(hContact, "Status", ID_STATUS_OFFLINE);
+						setWord(hContact, "Status", ID_STATUS_OFFLINE);
 					}
 				}
 
@@ -933,7 +900,7 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 					else {
 						client = FACEBOOK_CLIENT_OTHER;
 					}
-					proto->setWString(hContact, "MirVer", client);
+					setWString(hContact, "MirVer", client);
 				}
 			}
 		}
@@ -949,7 +916,7 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 			for (auto itNodes = overlay_.begin(); itNodes != overlay_.end(); ++itNodes) {
 				std::string id = (*itNodes).name();
 
-				MCONTACT hContact = proto->ContactIDToHContact(id);
+				MCONTACT hContact = ContactIDToHContact(id);
 				if (!hContact) {
 					// Facebook now sends info also about some nonfriends, so we just ignore status change of contacts we don't have in list
 					continue;
@@ -987,17 +954,17 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 					time_t last_active = utils::time::from_string(la_.as_string());
 					
 					// we should set IdleTS only when contact is IDLE, or OFFLINE
-					//if (proto->getDword(hContact, "IdleTS", 0) != last_active) {
+					//if (getDword(hContact, "IdleTS", 0) != last_active) {
 					//	if (/*(fbu->idle || status == ID_STATUS_OFFLINE) &&*/ last_active > 0)
-					//		proto->setDword(hContact, "IdleTS", last_active);
+					//		setDword(hContact, "IdleTS", last_active);
 					//	else
-					//		proto->delSetting(hContact, "IdleTS");
+					//		delSetting(hContact, "IdleTS");
 					//}
 
 					/*if (last_active > 0)
-						proto->setDword(hContact, "LastActiveTS", last_active);
+						setDword(hContact, "LastActiveTS", last_active);
 					else
-						proto->delSetting(hContact, "LastActiveTS");
+						delSetting(hContact, "LastActiveTS");
 					*/
 
 					// Set users inactive for too long as offline
@@ -1005,10 +972,10 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 						status = ID_STATUS_OFFLINE;
 				}
 				else {
-					proto->delSetting(hContact, "IdleTS");
+					delSetting(hContact, "IdleTS");
 				}
 
-				proto->setWord(hContact, "Status", status);
+				setWord(hContact, "Status", status);
 
 				if (s_) {
 					// what to do with this?
@@ -1032,11 +999,11 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 						client = FACEBOOK_CLIENT_OTHER;
 					}*/
 
-					proto->setWString(hContact, "MirVer", client);
+					setWString(hContact, "MirVer", client);
 				}
 			}
 		} else if (t == "ticker_update:home") {
-			if (!proto->getByte(FACEBOOK_KEY_EVENT_TICKER_ENABLE, DEFAULT_EVENT_TICKER_ENABLE))
+			if (!getByte(FACEBOOK_KEY_EVENT_TICKER_ENABLE, DEFAULT_EVENT_TICKER_ENABLE))
 				continue;
 
 			const JSONNode &actor_ = (*it)["actor"];
@@ -1053,15 +1020,15 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 
 			std::string userId = actor_.as_string();
 
-			MCONTACT hContact = proto->ContactIDToHContact(userId);
+			MCONTACT hContact = ContactIDToHContact(userId);
 
-			proto->debugLogA("+++ Got ticker type='%s' class='%s'", story_type.c_str(), story_class.c_str());
+			debugLogA("+++ Got ticker type='%s' class='%s'", story_type.c_str(), story_class.c_str());
 
 			if (!text.empty())
-				proto->NotifyEvent(proto->m_tszUserName, ptrW(mir_utf8decodeW(text.c_str())), hContact, EVENT_TICKER, &url);
+				NotifyEvent(m_tszUserName, ptrW(mir_utf8decodeW(text.c_str())), hContact, EVENT_TICKER, &url);
 		}
 		else if (t == "notifications_read" || t == "notifications_seen") { // revised 5.3.2017
-			ScopedLock s(proto->facy.notifications_lock_);
+			ScopedLock s(facy.notifications_lock_);
 
 			const JSONNode &alerts = (*it)["alert_ids"];
 
@@ -1117,7 +1084,7 @@ int facebook_json_parser::parse_messages(std::string *pData, std::vector<faceboo
 	return EXIT_SUCCESS;
 }
 
-int facebook_json_parser::parse_unread_threads(std::string *data, std::vector< std::string >* threads)
+int FacebookProto::ParseUnreadThreads(std::string *data, std::vector< std::string >* threads)
 {
 	std::string jsonData = data->substr(9);
 
@@ -1144,7 +1111,7 @@ int facebook_json_parser::parse_unread_threads(std::string *data, std::vector< s
 	return EXIT_SUCCESS;
 }
 
-int facebook_json_parser::parse_thread_messages(std::string *data, std::vector< facebook_message >* messages, bool unreadOnly)
+int FacebookProto::ParseThreadMessages(std::string *data, std::vector< facebook_message >* messages, bool unreadOnly)
 {
 	std::string jsonData = data->substr(9);
 
@@ -1178,7 +1145,7 @@ int facebook_json_parser::parse_thread_messages(std::string *data, std::vector<
 		const JSONNode &log_data_ = (*it)["log_message_data"]; // additional data for this log message
 
 		if (!author_ || (!body_ && !log_body_) || !mid_ || (!thread_fbid_ && !thread_id_) || !timestamp_) {
-			proto->debugLogA("parse_thread_messages: ignoring message (%s) - missing attribute", mid_.as_string().c_str());
+			debugLogA("ParseThreadMessages: ignoring message (%s) - missing attribute", mid_.as_string().c_str());
 			continue;
 		}
 
@@ -1193,14 +1160,14 @@ int facebook_json_parser::parse_thread_messages(std::string *data, std::vector<
 			author_id = author_id.substr(pos + 1);
 
 		// Process attachements and stickers
-		parseAttachments(proto, &message_text, *it, other_user_fbid, true);
+		ParseAttachments(message_text, *it, other_user_fbid, true);
 
 		if (filtered_.as_bool() && message_text.empty())
 			message_text = Translate("This message is no longer available, because it was marked as abusive or spam.");
 
 		message_text = utils::text::trim(utils::text::slashu_to_utf8(message_text), true);
 		if (message_text.empty()) {
-			proto->debugLogA("parse_thread_messages: ignoring message (%s) - empty message text", mid_.as_string().c_str());
+			debugLogA("ParseThreadMessages: ignoring message (%s) - empty message text", mid_.as_string().c_str());
 			continue;
 		}
 
@@ -1214,7 +1181,7 @@ int facebook_json_parser::parse_thread_messages(std::string *data, std::vector<
 		message.message_text = message_text;
 		message.time = utils::time::from_string(timestamp_.as_string());
 		message.message_id = message_id;
-		message.isIncoming = (author_id != proto->facy.self_.user_id);
+		message.isIncoming = (author_id != facy.self_.user_id);
 		message.isUnread = isUnread;
 
 		message.isChat = other_user_fbid.empty();
@@ -1227,7 +1194,7 @@ int facebook_json_parser::parse_thread_messages(std::string *data, std::vector<
 			message.thread_id = thread_id;
 		}
 
-		parseMessageType(proto, message, log_type_, log_body_, log_data_);
+		ParseMessageType(message, log_type_, log_body_, log_data_);
 
 		messages->push_back(message);
 	}
@@ -1235,7 +1202,7 @@ int facebook_json_parser::parse_thread_messages(std::string *data, std::vector<
 	return EXIT_SUCCESS;
 }
 
-int facebook_json_parser::parse_history(std::string *data, std::vector< facebook_message >* messages, std::string *firstTimestamp)
+int FacebookProto::ParseHistory(std::string *data, std::vector< facebook_message >* messages, std::string *firstTimestamp)
 {
 	std::string jsonData = data->substr(9);
 
@@ -1269,7 +1236,7 @@ int facebook_json_parser::parse_history(std::string *data, std::vector< facebook
 		const JSONNode &log_data_ = (*it)["log_message_data"];
 
 		if (!author || (!body && !log_body_) || !mid || !tid || !timestamp) {
-			proto->debugLogA("parse_history: ignoring message (%s) - missing attribute", mid.as_string().c_str());
+			debugLogA("ParseHistory: ignoring message (%s) - missing attribute", mid.as_string().c_str());
 			continue;
 		}
 
@@ -1288,14 +1255,14 @@ int facebook_json_parser::parse_history(std::string *data, std::vector< facebook
 			author_id = author_id.substr(pos + 1);
 
 		// Process attachements and stickers
-		parseAttachments(proto, &message_text, *it, other_user_id, true);
+		ParseAttachments(message_text, *it, other_user_id, true);
 
 		if (filtered.as_bool() && message_text.empty())
 			message_text = Translate("This message is no longer available, because it was marked as abusive or spam.");
 
 		message_text = utils::text::trim(utils::text::slashu_to_utf8(message_text), true);
 		if (message_text.empty()) {
-			proto->debugLogA("parse_history: ignoring message (%s) - empty message text", mid.as_string().c_str());
+			debugLogA("ParseHistory: ignoring message (%s) - empty message text", mid.as_string().c_str());
 			continue;
 		}
 
@@ -1304,12 +1271,12 @@ int facebook_json_parser::parse_history(std::string *data, std::vector< facebook
 		message.time = utils::time::from_string(timestamp.as_string());
 		message.thread_id = thread_id;
 		message.message_id = message_id;
-		message.isIncoming = (author_id != proto->facy.self_.user_id);
+		message.isIncoming = (author_id != facy.self_.user_id);
 		message.isUnread = is_unread.as_bool();
 		message.isChat = false;
 		message.user_id = other_user_id;
 
-		parseMessageType(proto, message, log_type_, log_body_, log_data_);
+		ParseMessageType(message, log_type_, log_body_, log_data_);
 
 		messages->push_back(message);
 	}
@@ -1317,7 +1284,7 @@ int facebook_json_parser::parse_history(std::string *data, std::vector< facebook
 	return EXIT_SUCCESS;
 }
 
-int facebook_json_parser::parse_thread_info(std::string *data, std::string* user_id)
+int FacebookProto::ParseThreadInfo(std::string *data, std::string* user_id)
 {
 	std::string jsonData = data->substr(9);
 
@@ -1348,7 +1315,7 @@ int facebook_json_parser::parse_thread_info(std::string *data, std::string* user
 }
 
 
-int facebook_json_parser::parse_user_info(std::string *data, facebook_user* fbu)
+int FacebookProto::ParseUserInfo(std::string *data, facebook_user* fbu)
 {
 	std::string jsonData = data->substr(9);
 
@@ -1374,7 +1341,7 @@ int facebook_json_parser::parse_user_info(std::string *data, facebook_user* fbu)
 	return EXIT_SUCCESS;
 }
 
-int facebook_json_parser::parse_chat_info(std::string *data, facebook_chatroom* fbc)
+int FacebookProto::ParseChatInfo(std::string *data, facebook_chatroom* fbc)
 {
 	std::string jsonData = data->substr(9);
 
@@ -1432,7 +1399,7 @@ int facebook_json_parser::parse_chat_info(std::string *data, facebook_chatroom*
 	return EXIT_SUCCESS;
 }
 
-int facebook_json_parser::parse_messages_count(std::string *data, int *messagesCount, int *unreadCount)
+int FacebookProto::ParseMessagesCount(std::string *data, int *messagesCount, int *unreadCount)
 {
 	std::string jsonData = data->substr(9);
 
diff --git a/protocols/FacebookRM/src/json.h b/protocols/FacebookRM/src/json.h
deleted file mode 100644
index d62666a66d..0000000000
--- a/protocols/FacebookRM/src/json.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
-
-Facebook plugin for Miranda Instant Messenger
-_____________________________________________
-
-Copyright © 2009-11 Michal Zelinka, 2011-17 Robert Pösel
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 2 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#pragma once
-
-// Parser front-end
-
-#define lltoa _i64toa
-
-class facebook_json_parser
-{
-public:
-	FacebookProto* proto;
-	int parse_friends(std::string*, std::map< std::string, facebook_user* >*, bool);
-	int parse_notifications(std::string*, std::map< std::string, facebook_notification* >*);
-	int parse_messages(std::string*, std::vector< facebook_message >*, std::map< std::string, facebook_notification* >*);
-	int parse_unread_threads(std::string*, std::vector< std::string >*);
-	int parse_thread_messages(std::string*, std::vector< facebook_message >*, bool unreadOnly);
-	int parse_history(std::string*, std::vector< facebook_message >*, std::string *);
-	int parse_thread_info(std::string* data, std::string* user_id);
-	int parse_user_info(std::string* data, facebook_user* fbu);
-	int parse_chat_info(std::string* data, facebook_chatroom* fbc);
-	int parse_chat_participant_names(std::string *data, std::map<std::string, chatroom_participant>* participants);
-	int parse_messages_count(std::string *data, int *messagesCount, int *unreadCount);
-
-	facebook_json_parser(FacebookProto* proto)
-	{
-		this->proto = proto;
-	}
-};
diff --git a/protocols/FacebookRM/src/process.cpp b/protocols/FacebookRM/src/process.cpp
index fb02f5c868..eb856d06f3 100644
--- a/protocols/FacebookRM/src/process.cpp
+++ b/protocols/FacebookRM/src/process.cpp
@@ -62,7 +62,7 @@ void FacebookProto::ProcessFriendList(void*)
 		bool loadAllContacts = getBool(FACEBOOK_KEY_LOAD_ALL_CONTACTS, DEFAULT_LOAD_ALL_CONTACTS);
 		bool pagesAlwaysOnline = getBool(FACEBOOK_KEY_PAGES_ALWAYS_ONLINE, DEFAULT_PAGES_ALWAYS_ONLINE);
 
-		facebook_json_parser(this).parse_friends(&resp.data, &friends, loadAllContacts);
+		ParseFriends(&resp.data, &friends, loadAllContacts);
 
 		// Check and update old contacts
 		for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) {
@@ -193,7 +193,7 @@ void FacebookProto::ProcessUnreadMessages(void*)
 
 	try {
 		std::vector<std::string> threads;
-		facebook_json_parser(this).parse_unread_threads(&resp.data, &threads);
+		ParseUnreadThreads(&resp.data, &threads);
 
 		ForkThread(&FacebookProto::ProcessUnreadMessage, new std::vector<std::string>(threads));
 
@@ -243,7 +243,7 @@ void FacebookProto::ProcessUnreadMessage(void *pParam)
 		if (resp.code == HTTP_CODE_OK) {
 			try {
 				std::vector<facebook_message> messages;
-				facebook_json_parser(this).parse_thread_messages(&resp.data, &messages, false);
+				ParseThreadMessages(&resp.data, &messages, false);
 
 				ReceiveMessages(messages, true);
 				debugLogA("*** Unread messages processed");
@@ -308,7 +308,7 @@ void FacebookProto::LoadLastMessages(void *pParam)
 
 	try {
 		std::vector<facebook_message> messages;
-		facebook_json_parser(this).parse_thread_messages(&resp.data, &messages, false);
+		ParseThreadMessages(&resp.data, &messages, false);
 
 		ReceiveMessages(messages, true);
 		debugLogA("*** Thread messages processed");
@@ -365,9 +365,7 @@ void FacebookProto::LoadHistory(void *pParam)
 	int messagesCount = -1;
 	int unreadCount = -1;
 
-	facebook_json_parser* p = new facebook_json_parser(this);
-	if (p->parse_messages_count(&resp.data, &messagesCount, &unreadCount) == EXIT_FAILURE) {
-		delete p;
+	if (ParseMessagesCount(&resp.data, &messagesCount, &unreadCount) == EXIT_FAILURE) {
 		facy.handle_error("LoadHistory");
 		return;
 	}
@@ -411,7 +409,7 @@ void FacebookProto::LoadHistory(void *pParam)
 		try {
 			messages.clear();
 
-			p->parse_history(&resp.data, &messages, &firstTimestamp);
+			ParseHistory(&resp.data, &messages, &firstTimestamp);
 
 			// Receive messages
 			std::string previousFirstMessageId = firstMessageId;
@@ -484,8 +482,6 @@ void FacebookProto::LoadHistory(void *pParam)
 		}
 	}
 
-	delete p;
-
 	facy.handle_success("LoadHistory");
 
 	// Enable marking messages as read for this contact
@@ -972,7 +968,7 @@ void FacebookProto::ProcessMessages(void* data)
 
 	try {
 		std::vector<facebook_message> messages;
-		facebook_json_parser(this).parse_messages(resp, &messages, &facy.notifications);
+		ParseMessages(resp, &messages, &facy.notifications);
 
 		ReceiveMessages(messages);
 
@@ -1031,7 +1027,7 @@ void FacebookProto::ProcessNotifications(void *p)
 	try {
 		size_t numNotifications = facy.notifications.size();
 
-		facebook_json_parser(this).parse_notifications(&resp.data, &facy.notifications);
+		ParseNotifications(&resp.data, &facy.notifications);
 
 		if (manuallyTriggered) {
 			numNotifications = facy.notifications.size() - numNotifications;
diff --git a/protocols/FacebookRM/src/proto.h b/protocols/FacebookRM/src/proto.h
index dff762bfaa..f31981a500 100644
--- a/protocols/FacebookRM/src/proto.h
+++ b/protocols/FacebookRM/src/proto.h
@@ -26,6 +26,25 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 class FacebookProto : public PROTO<FacebookProto>
 {
+	bool IgnoreDuplicates(const std::string &mid);
+	bool ProcessSpecialMessage(std::vector<facebook_message>* messages, const JSONNode &meta_, MessageType messageType, const std::string &messageData = "");
+
+	void ParseAttachments(std::string &message_text, const JSONNode &delta_, std::string other_user_fbid, bool legacy);
+	void ParseMessageType(facebook_message &message, const JSONNode &log_type_, const JSONNode &log_body_, const JSONNode &log_data_);
+	bool ParseMessageMetadata(facebook_message &message, const JSONNode &meta_);
+
+	int ParseChatInfo(std::string* data, facebook_chatroom* fbc);
+	int ParseChatParticipants(std::string *data, std::map<std::string, chatroom_participant>* participants);
+	int ParseFriends(std::string*, std::map< std::string, facebook_user* >*, bool);
+	int ParseHistory(std::string*, std::vector< facebook_message >*, std::string *);
+	int ParseMessages(std::string*, std::vector< facebook_message >*, std::map< std::string, facebook_notification* >*);
+	int ParseMessagesCount(std::string *data, int *messagesCount, int *unreadCount);
+	int ParseNotifications(std::string*, std::map< std::string, facebook_notification* >*);
+	int ParseThreadInfo(std::string* data, std::string* user_id);
+	int ParseThreadMessages(std::string*, std::vector< facebook_message >*, bool unreadOnly);
+	int ParseUnreadThreads(std::string*, std::vector< std::string >*);
+	int ParseUserInfo(std::string* data, facebook_user* fbu);
+
 public:
 	FacebookProto(const char *proto_name, const wchar_t *username);
 	~FacebookProto();
@@ -48,7 +67,6 @@ public:
 	inline bool isInvisible()
 	{
 		return m_invisible;
-		//return (m_iStatus == ID_STATUS_INVISIBLE);
 	}
 
 	inline int IdleSeconds()
diff --git a/protocols/FacebookRM/src/requests/contacts.h b/protocols/FacebookRM/src/requests/contacts.h
index 8fa60fced8..960fb269b3 100644
--- a/protocols/FacebookRM/src/requests/contacts.h
+++ b/protocols/FacebookRM/src/requests/contacts.h
@@ -36,7 +36,7 @@ public:
 
 
 // getting info about particular friend
-// revised 17.8.2016
+// revised 3.12.2017
 class UserInfoRequest : public HttpRequest
 {
 public:
@@ -60,7 +60,11 @@ public:
 			<< CHAR_VALUE("__rev", fc->__rev())
 			<< "__a=1"
 			<< "__pc=PHASED:DEFAULT"
-			<< "__be=-1";
+			<< "__be=1"
+			<< "jazoest="
+			<< "__spin_r="
+			<< "__spin_b="
+			<< "__spin_t=";
 	}
 };
 
diff --git a/protocols/FacebookRM/src/requests/history.h b/protocols/FacebookRM/src/requests/history.h
index 9ac3d52168..b7cf3f42c1 100644
--- a/protocols/FacebookRM/src/requests/history.h
+++ b/protocols/FacebookRM/src/requests/history.h
@@ -24,20 +24,20 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #define _FACEBOOK_REQUEST_HISTORY_H_
 
 // getting thread info and messages
-// revised 17.8.2016
+// revised 3.12.2017
 class ThreadInfoRequest : public HttpRequest
 {
 public:
 	// Request only messages history
 	ThreadInfoRequest(facebook_client *fc, bool isChat, const char *id, int offset, const char *timestamp, int limit) :
-		HttpRequest(REQUEST_POST, FACEBOOK_SERVER_REGULAR "/ajax/mercury/thread_info.php")
+		HttpRequest(REQUEST_POST, FACEBOOK_SERVER_REGULAR "/api/graphqlbatch/")
 	{
-		Url
-			<< "dpr=1";
-
 		setCommonBody(fc);
 
-		const char *type = isChat ? "thread_fbids" : "user_ids";
+		// FIXME: FB removed /ajax/mercury/thread_info requests and now all messaging stuff does through /api/graphqlbatch/ - all loading of threads, (unread) messages, list of contacts in groupchat, etc.
+		// All these request must be rewritten to the new request. Preparation is below but unfinished.
+
+		//const char *type = isChat ? "thread_fbids" : "user_ids";
 		std::string id_ = id; // FIXME: Rewrite this without std::string...
 		if (isChat) {
 			// NOTE: Remove "id." prefix as here we need to give threadFbId and not threadId
@@ -46,6 +46,34 @@ public:
 		}
 		ptrA idEncoded(mir_urlEncode(id_.c_str()));
 
+
+		JSONNode root, o0, query_params;
+
+		int before = -1;
+
+		query_params
+			<< CHAR_PARAM("id", id_.c_str()) // TODO: Do I have to encode the id? And remove that first "id." at the begin as we do above?
+			<< INT_PARAM("message_limit", limit)
+			<< INT_PARAM("load_messages", 1)
+			<< BOOL_PARAM("load_read_receipts", false);
+
+		if (before != -1)
+			query_params << INT_PARAM("before", before);
+		else
+			query_params << NULL_PARAM("before");
+
+		o0	<< CHAR_PARAM("doc_id", id) << JSON_PARAM("query_params", query_params);
+
+		root << JSON_PARAM("o0", o0);
+
+		Body
+			<< "batch_name=MessengerGraphQLThreadFetcherRe"
+			<< CHAR_VALUE("queries", root.write().c_str());
+
+		// example request data we need to send: { "o0":{"doc_id":"456789456123","query_params" : {"id":"123456789","message_limit" : 20,"load_messages" : 1,"load_read_receipts" : false,"before" : null}} }
+
+		
+		/*
 		//if (loadMessages) {
 			// Grrr, offset doesn't work at all, we need to use timestamps to get back in history...
 			// And we don't know, what's timestamp of first message, so we need to get from latest to oldest
@@ -55,7 +83,7 @@ public:
 				<< CMStringA(::FORMAT, "%s[offset]=%i", begin.c_str(), offset).c_str()
 				<< CMStringA(::FORMAT, "%s[timestamp]=%s", begin.c_str(), timestamp).c_str()
 				<< CMStringA(::FORMAT, "%s[limit]=%i", begin.c_str(), limit).c_str();
-		//}
+		//}*/
 
 		/*if (loadThreadInfo) {
 			data += "&threads[" + type + "][0]=" + idEncoded;
@@ -64,11 +92,8 @@ public:
 
 	// Request only thread info // TODO: Make it array of ids
 	ThreadInfoRequest(facebook_client *fc, bool isChat, const char *id) :
-		HttpRequest(REQUEST_POST, FACEBOOK_SERVER_REGULAR "/ajax/mercury/thread_info.php")
+		HttpRequest(REQUEST_POST, FACEBOOK_SERVER_REGULAR "/api/graphqlbatch/")
 	{
-		Url
-			<< "dpr=1";
-
 		setCommonBody(fc);
 
 		const char *type = isChat ? "thread_fbids" : "user_ids";
@@ -86,11 +111,8 @@ public:
 
 	// Request both thread info and messages for single contact/chat
 	ThreadInfoRequest(facebook_client *fc, bool isChat, const char *id, int limit) :
-		HttpRequest(REQUEST_POST, FACEBOOK_SERVER_REGULAR "/ajax/mercury/thread_info.php")
+		HttpRequest(REQUEST_POST, FACEBOOK_SERVER_REGULAR "/api/graphqlbatch/")
 	{
-		Url
-			<< "dpr=1";
-
 		setCommonBody(fc);
 
 		const char *type = isChat ? "thread_fbids" : "user_ids";
@@ -116,11 +138,8 @@ public:
 
 	// Request both thread info and messages for more threads
 	ThreadInfoRequest(facebook_client *fc, const LIST<char> &ids, int offset, int limit) :
-		HttpRequest(REQUEST_POST, FACEBOOK_SERVER_REGULAR "/ajax/mercury/thread_info.php")
+		HttpRequest(REQUEST_POST, FACEBOOK_SERVER_REGULAR "/api/graphqlbatch/")
 	{
-		Url
-			<< "dpr=1";
-
 		setCommonBody(fc);
 
 		for (int i = 0; i < ids.getCount(); i++) {
@@ -146,7 +165,6 @@ private:
 	void setCommonBody(facebook_client *fc)
 	{
 		Body
-			<< "client=mercury"
 			<< CHAR_VALUE("__user", fc->self_.user_id.c_str())
 			<< CHAR_VALUE("__dyn", fc->__dyn())
 			<< CHAR_VALUE("__req", fc->__req())
@@ -155,7 +173,11 @@ private:
 			<< CHAR_VALUE("ttstamp", fc->ttstamp_.c_str())
 			<< "__a=1"
 			<< "__pc=PHASED:DEFAULT"
-			<< "__be=-1";
+			<< "__be=1"
+			<< "jazoest="
+			<< "__spin_r="
+			<< "__spin_b="
+			<< "__spin_t=";
 	}
 };
 
@@ -183,6 +205,8 @@ public:
 			<< "__a=1"
 			<< "__pc=PHASED:DEFAULT"
 			<< "__be=-1";
+
+		//queries={"o0":{"doc_id":"2003371749678240","query_params":{"limit":99,"before":null,"tags":["PENDING","unread"],"includeDeliveryReceipts":true,"includeSeqID":false}}}
 	}
 };
 
diff --git a/protocols/FacebookRM/src/stdafx.h b/protocols/FacebookRM/src/stdafx.h
index 0e466e9715..007711c0c4 100644
--- a/protocols/FacebookRM/src/stdafx.h
+++ b/protocols/FacebookRM/src/stdafx.h
@@ -74,7 +74,6 @@ class FacebookProto;
 #include "list.hpp"
 #include "client.h"
 #include "proto.h"
-#include "json.h"
 #include "db.h"
 #include "dialogs.h"
 #include "theme.h"
diff --git a/protocols/FacebookRM/src/version.h b/protocols/FacebookRM/src/version.h
index 08b34f8856..943d643eb5 100644
--- a/protocols/FacebookRM/src/version.h
+++ b/protocols/FacebookRM/src/version.h
@@ -1,7 +1,7 @@
 #define __MAJOR_VERSION            0
-#define __MINOR_VERSION            3
-#define __RELEASE_NUM              3
-#define __BUILD_NUM                2
+#define __MINOR_VERSION            4
+#define __RELEASE_NUM              0
+#define __BUILD_NUM                1
 
 #include <stdver.h>
 
-- 
cgit v1.2.3