diff options
Diffstat (limited to 'protocols')
-rw-r--r-- | protocols/FacebookRM/src/json.cpp | 102 | ||||
-rw-r--r-- | protocols/FacebookRM/src/json.h | 2 | ||||
-rw-r--r-- | protocols/FacebookRM/src/process.cpp | 213 | ||||
-rw-r--r-- | protocols/FacebookRM/src/proto.cpp | 28 | ||||
-rw-r--r-- | protocols/FacebookRM/src/proto.h | 2 | ||||
-rw-r--r-- | protocols/FacebookRM/src/theme.cpp | 11 |
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 ×tamp = (*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)
{
|