summaryrefslogtreecommitdiff
path: root/protocols
diff options
context:
space:
mode:
authorRobert Pösel <robyer@seznam.cz>2016-08-31 10:11:26 +0000
committerRobert Pösel <robyer@seznam.cz>2016-08-31 10:11:26 +0000
commita5a6c8afb1d68330dc1e112ca9bdb0e38c435ad8 (patch)
tree4e46c9a32742d43b992e5ea00f5d6cf3e3594156 /protocols
parent052f82ecce5ebd44ea631bc0a8affb1864267301 (diff)
Facebook: Implement loading history from server (via contact menu)
git-svn-id: http://svn.miranda-ng.org/main/trunk@17220 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c
Diffstat (limited to 'protocols')
-rw-r--r--protocols/FacebookRM/src/json.cpp102
-rw-r--r--protocols/FacebookRM/src/json.h2
-rw-r--r--protocols/FacebookRM/src/process.cpp213
-rw-r--r--protocols/FacebookRM/src/proto.cpp28
-rw-r--r--protocols/FacebookRM/src/proto.h2
-rw-r--r--protocols/FacebookRM/src/theme.cpp11
6 files changed, 358 insertions, 0 deletions
diff --git a/protocols/FacebookRM/src/json.cpp b/protocols/FacebookRM/src/json.cpp
index 6d4e7471ef..b8ad460442 100644
--- a/protocols/FacebookRM/src/json.cpp
+++ b/protocols/FacebookRM/src/json.cpp
@@ -1166,6 +1166,81 @@ 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)
+{
+ std::string jsonData = data->substr(9);
+
+ JSONNode root = JSONNode::parse(jsonData.c_str());
+ if (!root)
+ return EXIT_FAILURE;
+
+ const JSONNode &payload = root["payload"];
+ if (!payload)
+ return EXIT_FAILURE;
+
+ const JSONNode &actions = payload["actions"];
+ if (!actions)
+ return EXIT_FAILURE;
+
+ bool first = true;
+
+ for (auto it = actions.begin(); it != actions.end(); ++it) {
+ 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 &timestamp = (*it)["timestamp"];
+ const JSONNode &filtered = (*it)["is_filtered_content"];
+ const JSONNode &is_unread = (*it)["is_unread"];
+
+ if (!author || !body || !mid || !tid || !timestamp) {
+ proto->debugLogA("parse_history: ignoring message (%s) - missing attribute", mid.as_string().c_str());
+ continue;
+ }
+
+ if (first) {
+ *firstTimestamp = timestamp.as_string();
+ first = false;
+ }
+
+ std::string thread_id = tid.as_string();
+ std::string message_id = mid.as_string();
+ std::string message_text = 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::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(proto, &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());
+ continue;
+ }
+
+ facebook_message message;
+ message.message_text = message_text;
+ 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.isUnread = is_unread.as_bool();
+ message.isChat = false;
+ message.user_id = other_user_id;
+
+ messages->push_back(message);
+ }
+
+ return EXIT_SUCCESS;
+}
+
int facebook_json_parser::parse_thread_info(std::string *data, std::string* user_id)
{
std::string jsonData = data->substr(9);
@@ -1280,3 +1355,30 @@ 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)
+{
+ std::string jsonData = data->substr(9);
+
+ JSONNode root = JSONNode::parse(jsonData.c_str());
+ if (!root)
+ return EXIT_FAILURE;
+
+ const JSONNode &threads = root["payload"].at("threads");
+ if (!threads)
+ return EXIT_FAILURE;
+
+ for (auto it = threads.begin(); it != threads.end(); ++it) {
+ const JSONNode &message_count_ = (*it)["message_count"];
+ const JSONNode &unread_count_ = (*it)["unread_count"];
+
+ if (!message_count_|| !unread_count_)
+ return EXIT_FAILURE;
+
+ *messagesCount = message_count_.as_int();
+ *unreadCount = unread_count_.as_int();
+ }
+
+ return EXIT_SUCCESS;
+}
+
diff --git a/protocols/FacebookRM/src/json.h b/protocols/FacebookRM/src/json.h
index 666968385c..add7b2827a 100644
--- a/protocols/FacebookRM/src/json.h
+++ b/protocols/FacebookRM/src/json.h
@@ -35,10 +35,12 @@ public:
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 >*, std::map< std::string, facebook_chatroom* >*, 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)
{
diff --git a/protocols/FacebookRM/src/process.cpp b/protocols/FacebookRM/src/process.cpp
index ea4dfdc7e2..a3db0d8812 100644
--- a/protocols/FacebookRM/src/process.cpp
+++ b/protocols/FacebookRM/src/process.cpp
@@ -456,6 +456,219 @@ void FacebookProto::LoadLastMessages(void *pParam)
OnDbEventRead(hContact, NULL);
}
+void FacebookProto::LoadHistory(void *pParam)
+{
+ if (pParam == NULL)
+ return;
+
+ MCONTACT hContact = *(MCONTACT*)pParam;
+ delete (MCONTACT*)pParam;
+
+ if (!isOnline())
+ return;
+
+ facy.handle_entry("LoadHistory");
+
+ std::string data = "client=mercury";
+ data += "&__user=" + facy.self_.user_id;
+ data += "&__dyn=" + facy.__dyn();
+ data += "&__req=" + facy.__req();
+ data += "&fb_dtsg=" + facy.dtsg_;
+ data += "&ttstamp=" + facy.ttstamp_;
+ data += "&__rev=" + facy.__rev();
+ data += "&__pc=PHASED:DEFAULT&__be=-1&__a=1";
+
+ bool isChat = isChatRoom(hContact);
+ if (isChat) // TODO: Support chats?
+ return;
+ /*if (isChat && (!m_enableChat || IsSpecialChatRoom(hContact))) // disabled chats or special chatroom (e.g. nofitications)
+ return;*/
+
+ ptrA item_id(getStringA(hContact, isChat ? FACEBOOK_KEY_TID : FACEBOOK_KEY_ID));
+ if (item_id == NULL) {
+ debugLogA("!!! LoadHistory(): Contact has no TID/ID");
+ return;
+ }
+
+ std::string id = utils::url::encode(std::string(item_id));
+ std::string type = isChat ? "thread_ids" : "user_ids";
+
+ // first get info about this thread and how many messages is there
+
+ // request info about thread
+ data += "&threads[" + type + "][0]=" + id;
+
+ http::response resp = facy.flap(REQUEST_THREAD_INFO, &data); // NOTE: Request revised 17.8.2016
+ if (resp.code != HTTP_CODE_OK || resp.data.empty()) {
+ facy.handle_error("LoadHistory");
+ return;
+ }
+
+ 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;
+ facy.handle_error("LoadHistory");
+ return;
+ }
+
+ // Temporarily disable marking messages as read for this contact
+ facy.ignore_read.insert(hContact);
+
+ POPUPDATAW pd = { sizeof(pd) };
+ pd.iSeconds = 5;
+ pd.lchContact = hContact;
+ pd.lchIcon = IcoLib_GetIconByHandle(GetIconHandle("conversation")); // TODO: Use better icon
+ wcsncpy(pd.lptzContactName, m_tszUserName, MAX_CONTACTNAME);
+ wcsncpy(pd.lptzText, TranslateT("Loading history started."), MAX_SECONDLINE);
+
+ HWND popupHwnd = NULL;
+ if (ServiceExists(MS_POPUP_ADDPOPUPW)) {
+ popupHwnd = (HWND)CallService(MS_POPUP_ADDPOPUPW, (WPARAM)&pd, (LPARAM)APF_RETURN_HWND);
+ }
+
+ std::vector<facebook_message> messages;
+ std::string firstTimestamp = "";
+ std::string firstMessageId = "";
+ std::string lastMessageId = "";
+ int loadedMessages = 0;
+ int messagesPerBatch = messagesCount > 10000 ? 500 : 100;
+ for (int batch = 0; batch < messagesCount; batch += messagesPerBatch) {
+ if (!isOnline())
+ break;
+
+ // Request batch of messages from thread
+ std::string data = "client=mercury";
+ data += "&__user=" + facy.self_.user_id;
+ data += "&__dyn=" + facy.__dyn();
+ data += "&__req=" + facy.__req();
+ data += "&fb_dtsg=" + facy.dtsg_;
+ data += "&ttstamp=" + facy.ttstamp_;
+ data += "&__rev=" + facy.__rev();
+ data += "&__pc=PHASED:DEFAULT&__be=-1&__a=1";
+
+ // 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
+ data += "&messages[" + type + "][" + id;
+ data += "][offset]=" + utils::conversion::to_string(&batch, UTILS_CONV_UNSIGNED_NUMBER);
+ data += "&messages[" + type + "][" + id;
+ data += "][timestamp]=" + firstTimestamp;
+ data += "&messages[" + type + "][" + id;
+ data += "][limit]=" + utils::conversion::to_string(&messagesPerBatch, UTILS_CONV_UNSIGNED_NUMBER);
+
+ resp = facy.flap(REQUEST_THREAD_INFO, &data); // NOTE: Request revised 17.8.2016
+ if (resp.code != HTTP_CODE_OK || resp.data.empty()) {
+ facy.handle_error("LoadHistory");
+ break;
+ }
+
+ // Parse the result
+ CODE_BLOCK_TRY
+
+ messages.clear();
+
+ p->parse_history(&resp.data, &messages, &firstTimestamp);
+
+ // Receive messages
+ std::string previousFirstMessageId = firstMessageId;
+ for (std::vector<facebook_message*>::size_type i = 0; i < messages.size(); i++) {
+ facebook_message &msg = messages[i];
+
+ // First message might overlap (as we are using it's timestamp for the next loading), so we need to check for it
+ if (i == 0) {
+ firstMessageId = msg.message_id;
+ }
+ if (previousFirstMessageId == msg.message_id) {
+ continue;
+ }
+ lastMessageId = msg.message_id;
+
+ if (msg.isIncoming && msg.isUnread && msg.type == MESSAGE) {
+ PROTORECVEVENT recv = { 0 };
+ recv.szMessage = const_cast<char*>(msg.message_text.c_str());
+ recv.timestamp = msg.time;
+ ProtoChainRecvMsg(hContact, &recv);
+ }
+ else {
+ DBEVENTINFO dbei = { 0 };
+ dbei.cbSize = sizeof(dbei);
+
+ if (msg.type == CALL)
+ dbei.eventType = FACEBOOK_EVENTTYPE_CALL;
+ else
+ dbei.eventType = EVENTTYPE_MESSAGE;
+
+ dbei.flags = DBEF_UTF;
+
+ if (!msg.isIncoming)
+ dbei.flags |= DBEF_SENT;
+
+ if (!msg.isUnread)
+ dbei.flags |= DBEF_READ;
+
+ dbei.szModule = m_szModuleName;
+ dbei.timestamp = msg.time;
+ dbei.cbBlob = (DWORD)msg.message_text.length() + 1;
+ dbei.pBlob = (PBYTE)msg.message_text.c_str();
+ db_event_add(hContact, &dbei);
+ }
+
+ loadedMessages++;
+ }
+
+ // Save last message id of first batch which is latest message completely, because we're going backwards
+ if (batch == 0 && !lastMessageId.empty()) {
+ setString(hContact, FACEBOOK_KEY_MESSAGE_ID, lastMessageId.c_str());
+ }
+
+ debugLogA("*** Load history messages processed");
+
+ CODE_BLOCK_CATCH
+
+ debugLogA("*** Error processing load history messages: %s", e.what());
+
+ break;
+
+ CODE_BLOCK_END
+
+ // Update progress popup
+ CMStringW text;
+ text.AppendFormat(TranslateT("Loading messages: %d/%d"), loadedMessages, messagesCount);
+
+ if (ServiceExists(MS_POPUP_CHANGETEXTW) && popupHwnd) {
+ PUChangeTextW(popupHwnd, text);
+ }
+ else if (ServiceExists(MS_POPUP_ADDPOPUPW)) {
+ wcsncpy(pd.lptzText, text, MAX_SECONDLINE);
+ pd.iSeconds = 1;
+ popupHwnd = (HWND)CallService(MS_POPUP_ADDPOPUPW, (WPARAM)&pd, (LPARAM)0);
+ }
+
+ // There is no more messages
+ if (messages.empty() || loadedMessages > messagesCount) {
+ break;
+ }
+ }
+
+ delete p;
+
+ facy.handle_success("LoadHistory");
+
+ // Enable marking messages as read for this contact
+ facy.ignore_read.erase(hContact);
+
+ if (ServiceExists(MS_POPUP_CHANGETEXTW) && popupHwnd) {
+ PUChangeTextW(popupHwnd, TranslateT("Loading history completed."));
+ } else if (ServiceExists(MS_POPUP_ADDPOPUPW)) {
+ pd.iSeconds = 5;
+ wcsncpy(pd.lptzText, TranslateT("Loading history completed."), MAX_SECONDLINE);
+ popupHwnd = (HWND)CallService(MS_POPUP_ADDPOPUPW, (WPARAM)&pd, (LPARAM)0);
+ }
+ // PUDeletePopup(popupHwnd);
+}
+
std::string truncateUtf8(std::string &text, size_t maxLength) {
// To not split some unicode character we need to transform it to wchar_t first, then split it, and then convert it back, because we want std::string as result
// TODO: Probably there is much simpler and nicer way
diff --git a/protocols/FacebookRM/src/proto.cpp b/protocols/FacebookRM/src/proto.cpp
index 4af360a747..ea84b7033a 100644
--- a/protocols/FacebookRM/src/proto.cpp
+++ b/protocols/FacebookRM/src/proto.cpp
@@ -769,6 +769,34 @@ INT_PTR FacebookProto::Poke(WPARAM wParam, LPARAM)
return 0;
}
+INT_PTR FacebookProto::LoadHistory(WPARAM wParam, LPARAM)
+{
+ if (wParam == NULL || isOffline())
+ return 1;
+
+ MCONTACT hContact = MCONTACT(wParam);
+
+ // Ignore groupchats // TODO: Support for groupchats?
+ if (isChatRoom(hContact))
+ return 0;
+
+ ptrW name(getWStringA(hContact, FACEBOOK_KEY_NICK));
+ if (name == NULL)
+ name = getWStringA(hContact, FACEBOOK_KEY_ID);
+ if (name == NULL)
+ return 1;
+
+ CMStringW title;
+ title.AppendFormat(L"%s - %s", m_tszUserName, name);
+ CMStringW message("This will load all messages from the server. It might take a while, so be patient.\n\nDo you want to continue?");
+
+ if (MessageBox(0, message, title, MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2) == IDYES) {
+ ForkThread(&FacebookProto::LoadHistory, new MCONTACT(hContact));
+ }
+
+ return 0;
+}
+
INT_PTR FacebookProto::CancelFriendship(WPARAM wParam, LPARAM lParam)
{
if (wParam == NULL || isOffline())
diff --git a/protocols/FacebookRM/src/proto.h b/protocols/FacebookRM/src/proto.h
index 6aa1690dd7..eb1b81f63e 100644
--- a/protocols/FacebookRM/src/proto.h
+++ b/protocols/FacebookRM/src/proto.h
@@ -119,6 +119,7 @@ public:
INT_PTR __cdecl VisitConversation(WPARAM, LPARAM);
INT_PTR __cdecl VisitNotifications(WPARAM, LPARAM);
INT_PTR __cdecl Poke(WPARAM, LPARAM);
+ INT_PTR __cdecl LoadHistory(WPARAM, LPARAM);
INT_PTR __cdecl CancelFriendship(WPARAM, LPARAM);
INT_PTR __cdecl RequestFriendship(WPARAM, LPARAM);
INT_PTR __cdecl ApproveFriendship(WPARAM, LPARAM);
@@ -174,6 +175,7 @@ public:
void __cdecl SearchIdAckThread(void*);
void __cdecl ProcessPages(void*);
void __cdecl LoadLastMessages(void*);
+ void __cdecl LoadHistory(void*);
void __cdecl ProcessMemories(void*);
// Worker threads
diff --git a/protocols/FacebookRM/src/theme.cpp b/protocols/FacebookRM/src/theme.cpp
index 70ca0eb696..8e7ae32cf7 100644
--- a/protocols/FacebookRM/src/theme.cpp
+++ b/protocols/FacebookRM/src/theme.cpp
@@ -35,6 +35,7 @@ HGENMENU g_hContactMenuAuthDeny;
HGENMENU g_hContactMenuPoke;
HGENMENU g_hContactMenuPostStatus;
HGENMENU g_hContactMenuVisitConversation;
+HGENMENU g_hContactMenuLoadHistory;
static IconItem icons[] =
{
@@ -104,6 +105,7 @@ static int PrebuildContactMenu(WPARAM wParam, LPARAM lParam)
Menu_ShowItem(g_hContactMenuPoke, false);
Menu_ShowItem(g_hContactMenuPostStatus, false);
Menu_ShowItem(g_hContactMenuVisitConversation, false);
+ Menu_ShowItem(g_hContactMenuLoadHistory, false);
// Process them in correct account
FacebookProto *proto = GetInstanceByHContact(MCONTACT(wParam));
@@ -156,6 +158,14 @@ void InitContactMenus()
CreateServiceFunction(mi.pszService, GlobalService<&FacebookProto::Poke>);
g_hContactMenuPoke = Menu_AddContactMenuItem(&mi);
+ SET_UID(mi, 0x58e75db0, 0xb9e0, 0x4aa8, 0xbb, 0x42, 0x8d, 0x7d, 0xd1, 0xf6, 0x8e, 0x99);
+ mi.position = -2000006005;
+ mi.hIcolibItem = GetIconHandle("conversation"); // TODO: Use better icon
+ mi.name.a = LPGEN("Load history");
+ mi.pszService = "FacebookProto/LoadHistory";
+ CreateServiceFunction(mi.pszService, GlobalService<&FacebookProto::LoadHistory>);
+ g_hContactMenuLoadHistory = Menu_AddContactMenuItem(&mi);
+
SET_UID(mi, 0x619efdcb, 0x99c0, 0x44a8, 0xbf, 0x28, 0xc3, 0xe0, 0x2f, 0xb3, 0x7e, 0x77);
mi.position = -2000006010;
mi.hIcolibItem = Skin_GetIconHandle(SKINICON_AUTH_REVOKE);
@@ -209,6 +219,7 @@ int FacebookProto::OnPrebuildContactMenu(WPARAM wParam, LPARAM)
Menu_ShowItem(g_hContactMenuVisitFriendship, !bIsChatroom && !bIsPage);
Menu_ShowItem(g_hContactMenuVisitConversation, true);
Menu_ShowItem(g_hContactMenuPostStatus, !bIsChatroom);
+ Menu_ShowItem(g_hContactMenuLoadHistory, !bIsChatroom);
if (!isOffline() && !bIsChatroom && !bIsPage)
{