diff options
author | zemiacsik <zemi@centrum.sk> | 2018-01-12 18:26:01 +0100 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2018-01-12 20:26:01 +0300 |
commit | 03d22907643a74684e690b8bc73580c1fcd591fd (patch) | |
tree | 047d4ab6b76e9670314b5b53b490081ce59e1150 /protocols | |
parent | 191f7f57e767f16dbd23a0baafae935d9be6662a (diff) |
Facebook: initial changes to support loading of unread chat messages (#1095)
* Facebook: initial changes to support loading of unread chat messages
* Facebook: load unread messages in one request + load last messages when chat opens (if is set)
* shrinked regex pattern, commented unused code
* Facebook: fixed loading of whole history
Diffstat (limited to 'protocols')
-rw-r--r-- | protocols/FacebookRM/src/client.h | 2 | ||||
-rw-r--r-- | protocols/FacebookRM/src/history.cpp | 30 | ||||
-rw-r--r-- | protocols/FacebookRM/src/json.cpp | 243 | ||||
-rw-r--r-- | protocols/FacebookRM/src/process.cpp | 8 | ||||
-rw-r--r-- | protocols/FacebookRM/src/proto.h | 2 |
5 files changed, 163 insertions, 122 deletions
diff --git a/protocols/FacebookRM/src/client.h b/protocols/FacebookRM/src/client.h index bcc161d3fc..a71fab6aff 100644 --- a/protocols/FacebookRM/src/client.h +++ b/protocols/FacebookRM/src/client.h @@ -223,7 +223,7 @@ public: HttpRequest* memoriesRequest();
// history.cpp
- HttpRequest* threadInfoRequest(bool isChat, const char *id, const char* timestamp = nullptr, int limit = -1);
+ HttpRequest* threadInfoRequest(bool isChat, const char *id, const char* timestamp = nullptr, int limit = -1, bool loadMessages = false);
HttpRequest* threadInfoRequest(const LIST<char> &ids, int offset, int limit);
HttpRequest* unreadThreadsRequest();
diff --git a/protocols/FacebookRM/src/history.cpp b/protocols/FacebookRM/src/history.cpp index eb1e480766..82d37ddae1 100644 --- a/protocols/FacebookRM/src/history.cpp +++ b/protocols/FacebookRM/src/history.cpp @@ -22,7 +22,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. #include "stdafx.h" -HttpRequest* facebook_client::threadInfoRequest(bool isChat, const char *id, const char* timestamp, int limit) +HttpRequest* facebook_client::threadInfoRequest(bool isChat, const char *id, const char* timestamp, int limit, bool loadMessages) { HttpRequest *p = new HttpRequest(REQUEST_POST, FACEBOOK_SERVER_REGULAR "/api/graphqlbatch/"); @@ -47,13 +47,13 @@ HttpRequest* facebook_client::threadInfoRequest(bool isChat, const char *id, con query_params << CHAR_PARAM("id", id_.c_str()) << INT_PARAM("message_limit", (limit == -1) ? 50 : limit) - << INT_PARAM("load_messages", 0) + << BOOL_PARAM("load_messages", loadMessages) << BOOL_PARAM("load_read_receipts", false); - if (timestamp != nullptr) - query_params << CHAR_PARAM("before", timestamp); - else + if (timestamp == nullptr || strcmp(timestamp, "") == 0) query_params << NULL_PARAM("before"); + else + query_params << CHAR_PARAM("before", timestamp); o0 << CHAR_PARAM("doc_id", "1549485615075443") << JSON_PARAM("query_params", query_params); root << JSON_PARAM("o0", o0); @@ -79,20 +79,28 @@ HttpRequest* facebook_client::threadInfoRequest(const LIST<char> &ids, int offse << CHAR_PARAM("__rev", __rev()) << CHAR_PARAM("fb_dtsg", dtsg_.c_str()); + JSONNode root; for (int i = 0; i < ids.getCount(); i++) { // NOTE: Remove "id." prefix as here we need to give threadFbId and not threadId std::string id_ = ids[i]; // FIXME: Rewrite this without std::string... if (id_.substr(0, 3) == "id.") id_ = id_.substr(3); - // Load messages - CMStringA begin(::FORMAT, "messages[%s][%s]", "thread_fbids", ptrA(mir_urlEncode(id_.c_str())).get()); - p->Body - << INT_PARAM(CMStringA(::FORMAT, "%s[offset]", begin.c_str()), offset) - << INT_PARAM(CMStringA(::FORMAT, "%s[limit]", begin.c_str()), limit) - << CHAR_PARAM(CMStringA(::FORMAT, "threads[%s][%i]", "thread_fbids", i), id_.c_str()); + // Build query + JSONNode oX, query_params; + query_params + << CHAR_PARAM("id", id_.c_str()) + << INT_PARAM("message_limit", limit) + << BOOL_PARAM("load_messages", true) + << BOOL_PARAM("load_read_receipts", false) + << NULL_PARAM("before"); + + oX << CHAR_PARAM("doc_id", "1508526735892416") << JSON_PARAM("query_params", query_params); + root << JSON_PARAM(("o" + std::to_string(i)).c_str(), oX); } + p->Body << CHAR_PARAM("queries", root.write().c_str()); + return p; } diff --git a/protocols/FacebookRM/src/json.cpp b/protocols/FacebookRM/src/json.cpp index af232b9bd6..1ffd30b203 100644 --- a/protocols/FacebookRM/src/json.cpp +++ b/protocols/FacebookRM/src/json.cpp @@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "stdafx.h" +#include <regex> LRESULT CALLBACK PopupDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); @@ -1165,127 +1166,159 @@ int FacebookProto::ParseUnreadThreads(std::string *data, std::vector< std::strin int FacebookProto::ParseThreadMessages(std::string *data, std::vector< facebook_message >* messages, bool unreadOnly) { - std::string jsonData = data->substr(9); + /*size_t len = data->find("\r\n"); + if (len != data->npos) + data->erase(len);*/ + + // since it could loop over multiple queries not all can be valid + // so return EXIT_FAILURE only if none is processed + bool hasResult = false; + + // pattern for one query + std::regex r("\\{\"o\\d\":\\{\"data\":\\{\"message_thread\":\\{.+\\}{4,5}"); // (\\{|$) + std::sregex_iterator i = std::sregex_iterator(data->begin(), data->end(), r); + std::sregex_iterator end; + // loop over queries + for (; i != end; ++i) { + + std::smatch m = *i; + std::string match = m.str(); + + JSONNode root = JSONNode::parse(match.c_str()); + if (!root) + //return EXIT_FAILURE; + continue; - JSONNode root = JSONNode::parse(jsonData.c_str()); - if (!root) - return EXIT_FAILURE; + // query number "o0", "o1", .. but they are not ordered + std::string oX = std::string("o") + std::string(1, match.at(3)); - const JSONNode &payload = root["payload"]; - if (!payload) - return EXIT_FAILURE; + const JSONNode &thread = root[oX.c_str()]["data"]["message_thread"]; + if (!thread) + //return EXIT_FAILURE; + continue; - const JSONNode &actions = payload["actions"]; - const JSONNode &threads = payload["threads"]; - if (!actions || !threads) - return EXIT_FAILURE; + const JSONNode &nodes = thread["messages"]["nodes"]; + if (!nodes) + //return EXIT_FAILURE; + continue; - for (auto &it : actions) { - const JSONNode &author_ = it["author"]; - const JSONNode &other_user_fbid_ = it["other_user_fbid"]; - const JSONNode &body_ = it["body"]; - const JSONNode &thread_id_ = it["thread_id"]; - const JSONNode &thread_fbid_ = it["thread_fbid"]; - const JSONNode &mid_ = it["message_id"]; - const JSONNode ×tamp_ = it["timestamp"]; - const JSONNode &filtered_ = it["is_filtered_content"]; - const JSONNode &is_unread_ = it["is_unread"]; + // TODO! process commented sections and better pair json (this is just quick attempt, + I do not know what everything means yet) - // Either there is "body" (for classic messages), or "log_message_type" and "log_message_body" (for log messages) - const JSONNode &log_type_ = it["log_message_type"]; - const JSONNode &log_body_ = it["log_message_body"]; - const JSONNode &log_data_ = it["log_message_data"]; // additional data for this log message + const JSONNode &other_user_fbid_ = thread["thread_key"]["other_user_id"]; + const JSONNode &thread_fbid_ = thread["thread_key"]["thread_fbid"]; - if (!author_ || (!body_ && !log_body_) || !mid_ || (!thread_fbid_ && !thread_id_) || !timestamp_) { - debugLogA("ParseThreadMessages: ignoring message (%s) - missing attribute", mid_.as_string().c_str()); - continue; - } + for (auto it = nodes.begin(); it != nodes.end(); ++it) { + const JSONNode &author_ = (*it)["message_sender"]["id"]; + const JSONNode &body_ = (*it)["message"]["text"]; + const JSONNode &thread_id_ = (*it)["offline_threading_id"]; + const JSONNode &mid_ = (*it)["message_id"]; + const JSONNode ×tamp_ = (*it)["timestamp_precise"]; + // const JSONNode &filtered_ = (*it)["is_filtered_content"]; + const JSONNode &is_unread_ = (*it)["unread"]; - std::string thread_id = thread_id_.as_string(); - std::string thread_fbid = thread_fbid_.as_string(); - std::string message_id = mid_.as_string(); - std::string message_text = body_ ? body_.as_string() : log_body_.as_string(); - std::string author_id = author_.as_string(); - std::string other_user_fbid = other_user_fbid_ ? other_user_fbid_.as_string() : ""; - std::string::size_type pos = author_id.find(":"); // strip "fbid:" prefix - if (pos != std::string::npos) - author_id = author_id.substr(pos + 1); + // Either there is "body" (for classic messages), or "log_message_type" and "log_message_body" (for log messages) + const JSONNode &log_type_ = (*it)["log_message_type"]; + const JSONNode &log_body_ = (*it)["log_message_body"]; + const JSONNode &log_data_ = (*it)["log_message_data"]; // additional data for this log message - // Process attachements and stickers - ParseAttachments(message_text, it, other_user_fbid, true); + if (!author_ || (!body_ && !log_body_) || !mid_ || (!thread_fbid_ && !thread_id_) || !timestamp_) { + debugLogA("ParseThreadMessages: ignoring message (%s) - missing attribute", mid_.as_string().c_str()); + continue; + } - if (filtered_.as_bool() && message_text.empty()) - message_text = Translate("This message is no longer available, because it was marked as abusive or spam."); + std::string thread_id = thread_id_.as_string(); + std::string thread_fbid = thread_fbid_.as_string(); + std::string message_id = mid_.as_string(); + std::string message_text = body_ ? body_.as_string() : log_body_.as_string(); + std::string author_id = author_.as_string(); + std::string other_user_fbid = other_user_fbid_ ? other_user_fbid_.as_string() : ""; + std::string::size_type pos = author_id.find(":"); // strip "fbid:" prefix + if (pos != std::string::npos) + author_id = author_id.substr(pos + 1); + + // Process attachements and stickers + 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()) { + debugLogA("ParseThreadMessages: ignoring message (%s) - empty message text", mid_.as_string().c_str()); + continue; + } - message_text = utils::text::trim(utils::text::slashu_to_utf8(message_text), true); - if (message_text.empty()) { - debugLogA("ParseThreadMessages: ignoring message (%s) - empty message text", mid_.as_string().c_str()); - continue; - } + bool isUnread = is_unread_.as_bool(); - bool isUnread = is_unread_.as_bool(); + // Ignore read messages if we want only unread messages + if (unreadOnly && !isUnread) + continue; - // Ignore read messages if we want only unread messages - if (unreadOnly && !isUnread) - continue; + facebook_message message; + message.message_text = message_text; + message.time = utils::time::from_string(timestamp_.as_string()); + message.message_id = message_id; + message.isIncoming = (author_id != facy.self_.user_id); + message.isUnread = isUnread; + + message.isChat = other_user_fbid.empty(); + if (message.isChat) { + message.user_id = author_id; + message.thread_id = "id." + thread_fbid; + } + else { + message.user_id = other_user_fbid; + message.thread_id = thread_id; + } - facebook_message message; - message.message_text = message_text; - message.time = utils::time::from_string(timestamp_.as_string()); - message.message_id = message_id; - message.isIncoming = (author_id != facy.self_.user_id); - message.isUnread = isUnread; + ParseMessageType(message, log_type_, log_body_, log_data_); - message.isChat = other_user_fbid.empty(); - if (message.isChat) { - message.user_id = author_id; - message.thread_id = "id." + thread_fbid; + messages->push_back(message); + hasResult = true; } - else { - message.user_id = other_user_fbid; - message.thread_id = thread_id; - } - - ParseMessageType(message, log_type_, log_body_, log_data_); - - messages->push_back(message); } - return EXIT_SUCCESS; + return hasResult ? EXIT_SUCCESS : EXIT_FAILURE; } -int FacebookProto::ParseHistory(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); + size_t len = data->find("\r\n"); + if (len != data->npos) + data->erase(len); - JSONNode root = JSONNode::parse(jsonData.c_str()); + JSONNode root = JSONNode::parse(data->c_str()); if (!root) return EXIT_FAILURE; - const JSONNode &payload = root["payload"]; - if (!payload) + const JSONNode &thread = root["o0"]["data"]["message_thread"]; + if (!thread) return EXIT_FAILURE; - const JSONNode &actions = payload["actions"]; - if (!actions) + const JSONNode &nodes = thread["messages"]["nodes"]; + if (!nodes) return EXIT_FAILURE; + // TODO! process commented sections and better pair json (this is just quick attempt, + I do not know what everything means yet) + bool first = true; - for (auto &it : actions) { - const JSONNode &author = it["author"]; - const JSONNode &other_user_fbid = it["other_user_fbid"]; - const JSONNode &body = it["body"]; - const JSONNode &tid = it["thread_id"]; - const JSONNode &mid = it["message_id"]; - const JSONNode ×tamp = it["timestamp"]; - const JSONNode &filtered = it["is_filtered_content"]; - const JSONNode &is_unread = it["is_unread"]; + const JSONNode &other_user_fbid_ = thread["thread_key"]["other_user_id"]; + const JSONNode &thread_fbid_ = thread["thread_key"]["thread_fbid"]; + + for (auto it = nodes.begin(); it != nodes.end(); ++it) { + const JSONNode &author = (*it)["message_sender"]["id"]; + const JSONNode &body = (*it)["message"]["text"]; + const JSONNode &tid = (*it)["offline_threading_id"]; + const JSONNode &mid = (*it)["message_id"]; + const JSONNode ×tamp = (*it)["timestamp_precise"]; + // const JSONNode &filtered = (*it)["is_filtered_content"]; + const JSONNode &is_unread = (*it)["unread"]; // Either there is "body" (for classic messages), or "log_message_type" and "log_message_body" (for log messages) - const JSONNode &log_type_ = it["log_message_type"]; - const JSONNode &log_body_ = it["log_message_body"]; - const JSONNode &log_data_ = it["log_message_data"]; + const JSONNode &log_type_ = (*it)["log_message_type"]; + const JSONNode &log_body_ = (*it)["log_message_body"]; + const JSONNode &log_data_ = (*it)["log_message_data"]; if (!author || (!body && !log_body_) || !mid || !tid || !timestamp) { debugLogA("ParseHistory: ignoring message (%s) - missing attribute", mid.as_string().c_str()); @@ -1301,16 +1334,16 @@ int FacebookProto::ParseHistory(std::string *data, std::vector< facebook_message std::string message_id = mid.as_string(); std::string message_text = body ? body.as_string() : log_body_.as_string(); std::string author_id = author.as_string(); - std::string other_user_id = other_user_fbid ? other_user_fbid.as_string() : ""; + std::string other_user_id = other_user_fbid_ ? other_user_fbid_.as_string() : ""; std::string::size_type pos = author_id.find(":"); // strip "fbid:" prefix if (pos != std::string::npos) author_id = author_id.substr(pos + 1); // Process attachements and stickers - ParseAttachments(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."); + //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()) { @@ -1395,26 +1428,26 @@ int FacebookProto::ParseUserInfo(std::string *data, facebook_user* fbu) int FacebookProto::ParseMessagesCount(std::string *data, int *messagesCount, int *unreadCount) { - std::string jsonData = data->substr(9); + size_t len = data->find("\r\n"); + if (len != data->npos) + data->erase(len); - JSONNode root = JSONNode::parse(jsonData.c_str()); + JSONNode root = JSONNode::parse(data->c_str()); if (!root) return EXIT_FAILURE; - const JSONNode &threads = root["payload"].at("threads"); - if (!threads) + const JSONNode &thread = root["o0"]["data"]["message_thread"]; + if (!thread) return EXIT_FAILURE; - for (auto &it : threads) { - const JSONNode &message_count_ = it["message_count"]; - const JSONNode &unread_count_ = it["unread_count"]; + const JSONNode &message_count_ = thread["messages_count"]; + const JSONNode &unread_count_ = thread["unread_count"]; - if (!message_count_ || !unread_count_) - return EXIT_FAILURE; + if (!message_count_ || !unread_count_) + return EXIT_FAILURE; - *messagesCount = message_count_.as_int(); - *unreadCount = unread_count_.as_int(); - } + *messagesCount = message_count_.as_int(); + *unreadCount = unread_count_.as_int(); return EXIT_SUCCESS; } diff --git a/protocols/FacebookRM/src/process.cpp b/protocols/FacebookRM/src/process.cpp index 39053d2d8a..9ec6e093f6 100644 --- a/protocols/FacebookRM/src/process.cpp +++ b/protocols/FacebookRM/src/process.cpp @@ -228,8 +228,8 @@ void FacebookProto::ProcessUnreadMessage(void *pParam) } else facy.handle_error("ProcessUnreadMessage"); - offset += limit; - limit = 20; // TODO: use better limits? + // offset += limit; + // limit = 20; // TODO: use better limits? threads->clear(); // TODO: if we have limit messages from one user, there may be more unread messages... continue with it... otherwise remove that threadd from threads list -- or do it in json parser? hm = allow more than "limit" unread messages to be parsed } @@ -264,7 +264,7 @@ void FacebookProto::LoadLastMessages(void *pParam) int count = min(FACEBOOK_MESSAGES_ON_OPEN_LIMIT, getByte(FACEBOOK_KEY_MESSAGES_ON_OPEN_COUNT, DEFAULT_MESSAGES_ON_OPEN_COUNT)); - http::response resp = facy.sendRequest(facy.threadInfoRequest(isChat, (const char*)item_id, nullptr, count)); + http::response resp = facy.sendRequest(facy.threadInfoRequest(isChat, (const char*)item_id, nullptr, count, true)); if (resp.code != HTTP_CODE_OK || resp.data.empty()) { facy.handle_error("LoadLastMessages"); return; @@ -362,7 +362,7 @@ void FacebookProto::LoadHistory(void *pParam) break; // Load batch of messages - resp = facy.sendRequest(facy.threadInfoRequest(isChat, item_id, firstTimestamp.c_str(), messagesPerBatch)); + resp = facy.sendRequest(facy.threadInfoRequest(isChat, item_id, firstTimestamp.c_str(), messagesPerBatch, true)); if (resp.code != HTTP_CODE_OK || resp.data.empty()) { facy.handle_error("LoadHistory"); diff --git a/protocols/FacebookRM/src/proto.h b/protocols/FacebookRM/src/proto.h index d5e3617546..a9b2e798dc 100644 --- a/protocols/FacebookRM/src/proto.h +++ b/protocols/FacebookRM/src/proto.h @@ -36,7 +36,7 @@ class FacebookProto : public PROTO<FacebookProto> 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 ParseHistory(std::string* data, std::vector<facebook_message>* messages, std::string* firstTimestamp); 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* >*); |