From f7c563ada4247c1cbed54001bf357289f15f7d3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20P=C3=B6sel?= Date: Tue, 30 Jul 2013 00:43:40 +0000 Subject: Facebook: Most awesome variant of getting unread messages at login - but NEED TESTING (and some improvements later) - loads up to 21 unread messages per contact - loads messages also from "Other" messaging tab - uses most effective requests with ability for future function "loading contact history from server" git-svn-id: http://svn.miranda-ng.org/main/trunk@5528 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- protocols/FacebookRM/src/communication.cpp | 22 +-- protocols/FacebookRM/src/constants.h | 1 - protocols/FacebookRM/src/entities.h | 5 + protocols/FacebookRM/src/json.cpp | 255 ++++++++++++++++++++++------- protocols/FacebookRM/src/json.h | 2 + protocols/FacebookRM/src/process.cpp | 233 +++++++++++++++----------- 6 files changed, 337 insertions(+), 181 deletions(-) diff --git a/protocols/FacebookRM/src/communication.cpp b/protocols/FacebookRM/src/communication.cpp index ab54ee1f9d..ae677ed30e 100644 --- a/protocols/FacebookRM/src/communication.cpp +++ b/protocols/FacebookRM/src/communication.cpp @@ -250,7 +250,6 @@ DWORD facebook_client::choose_security_level(RequestType request_type) // case REQUEST_MARK_READ: // case REQUEST_NOTIFICATIONS_READ: // case REQUEST_UNREAD_THREADS: -// case REQUEST_UNREAD_MESSAGES: // case REQUEST_TYPING_SEND: default: return (DWORD)0; @@ -282,6 +281,7 @@ int facebook_client::choose_method(RequestType request_type) case REQUEST_REQUEST_FRIEND: case REQUEST_APPROVE_FRIEND: case REQUEST_CANCEL_REQUEST: + case REQUEST_UNREAD_THREADS: return REQUEST_POST; // case REQUEST_HOME: @@ -295,8 +295,6 @@ int facebook_client::choose_method(RequestType request_type) // case REQUEST_USER_INFO: // case REQUEST_LOAD_REQUESTS: // case REQUEST_SEARCH: -// case REQUEST_UNREAD_THREADS: -// case REQUEST_UNREAD_MESSAGES: default: return REQUEST_GET; } @@ -330,8 +328,6 @@ std::string facebook_client::choose_server(RequestType request_type, std::string case REQUEST_APPROVE_FRIEND: case REQUEST_LOAD_REQUESTS: case REQUEST_SEARCH: - case REQUEST_UNREAD_THREADS: - case REQUEST_UNREAD_MESSAGES: case REQUEST_USER_INFO: return FACEBOOK_SERVER_MOBILE; @@ -359,6 +355,7 @@ std::string facebook_client::choose_server(RequestType request_type, std::string // case REQUEST_DELETE_FRIEND: // case REQUEST_REQUEST_FRIEND: // case REQUEST_CANCEL_REQUEST: +// case REQUEST_UNREAD_THREADS: default: return FACEBOOK_SERVER_REGULAR; } @@ -418,20 +415,7 @@ std::string facebook_client::choose_action(RequestType request_type, std::string case REQUEST_UNREAD_THREADS: { - std::string action = "/messages/?folder=unread"; - if (get_data != NULL) { - action += *get_data; - } - return action; - } - - case REQUEST_UNREAD_MESSAGES: - { - std::string action = "/messages/read/?"; - if (get_data != NULL) { - action += *get_data; - } - return action; + return "/ajax/mercury/unread_threads.php?__a=1"; } case REQUEST_DELETE_FRIEND: diff --git a/protocols/FacebookRM/src/constants.h b/protocols/FacebookRM/src/constants.h index 7334ddf262..628e5e6895 100644 --- a/protocols/FacebookRM/src/constants.h +++ b/protocols/FacebookRM/src/constants.h @@ -117,7 +117,6 @@ enum RequestType { REQUEST_THREAD_INFO, // getting thread info REQUEST_UNREAD_THREADS, // getting unread threads - REQUEST_UNREAD_MESSAGES, // getting unread messages REQUEST_ASYNC, // marking messages read and getting other things REQUEST_MARK_READ, // marking messages read (new) }; diff --git a/protocols/FacebookRM/src/entities.h b/protocols/FacebookRM/src/entities.h index b80a75da1b..d1f1a48dcc 100644 --- a/protocols/FacebookRM/src/entities.h +++ b/protocols/FacebookRM/src/entities.h @@ -66,11 +66,14 @@ struct facebook_message std::string sender_name; std::string message_id; DWORD time; + bool isIncoming; + bool isUnread; facebook_message() { this->user_id = this->message_text = this->sender_name = this->message_id = ""; this->time = 0; + this->isUnread = this->isIncoming = true; } facebook_message(const facebook_message& msg) @@ -80,6 +83,8 @@ struct facebook_message this->sender_name = msg.sender_name; this->message_id = msg.message_id; this->time = msg.time; + this->isIncoming = msg.isIncoming; + this->isUnread = msg.isUnread; } }; diff --git a/protocols/FacebookRM/src/json.cpp b/protocols/FacebookRM/src/json.cpp index 9ce8d6c302..f3c75c4b40 100644 --- a/protocols/FacebookRM/src/json.cpp +++ b/protocols/FacebookRM/src/json.cpp @@ -231,7 +231,8 @@ int facebook_json_parser::parse_notifications(void *data, std::vector< facebook_ return EXIT_SUCCESS; } -bool ignore_duplicits(FacebookProto *proto, std::string mid, std::string text) { +bool ignore_duplicits(FacebookProto *proto, std::string mid, std::string text) +{ std::map::iterator it = proto->facy.messages_ignore.find(mid); if (it != proto->facy.messages_ignore.end()) { std::string msg = "????? Ignoring duplicit/sent message\n" + text; @@ -246,6 +247,59 @@ bool ignore_duplicits(FacebookProto *proto, std::string mid, std::string text) { return false; } +void parseAttachments(FacebookProto *proto, std::string *message_text, JSONNODE *it) +{ + // Process attachements and stickers + JSONNODE *has_attachment = json_get(it, "has_attachment"); + if (has_attachment != NULL && json_as_bool(has_attachment)) { + JSONNODE *admin_snippet = json_get(it, "admin_snippet"); + if (admin_snippet != NULL) { + // Append attachements + std::string attachments_text = ""; + JSONNODE *attachments = json_get(it, "attachments"); + for (unsigned int j = 0; j < json_size(attachments); j++) { + JSONNODE *itAttachment = json_at(attachments, j); + + // TODO: behave different for stickers and classic attachements? + // JSONNODE *attach_type = json_get(itAttachment, "attach_type"); // "sticker", "photo", "file" + + JSONNODE *name = json_get(itAttachment, "name"); + JSONNODE *url = json_get(itAttachment, "url"); + if (url != NULL) { + std::string link = json_as_string(url); + + if (link.find("/ajax/mercury/attachments/photo/view/") != std::string::npos) + // fix photo url + link = utils::url::decode(utils::text::source_get_value(&link, 2, "?uri=", "&")); + else if (link.find("/") == 0) { + // make absolute url + bool useHttps = proto->getByte(FACEBOOK_KEY_FORCE_HTTPS, 1) > 0; + link = (useHttps ? HTTP_PROTO_SECURE : HTTP_PROTO_REGULAR) + std::string(FACEBOOK_SERVER_REGULAR) + link; + } + + if (!link.empty()) { + std::string filename; + if (name != NULL) + filename = json_as_string(name); + if (filename == "null") + filename.clear(); + + attachments_text += "\n" + (!filename.empty() ? "<" + filename + "> " : "") + link + "\n"; + } + } + } + + if (!attachments_text.empty()) { + // TODO: have this as extra event, not replace or append message content + if (!message_text->empty()) + *message_text += "\n\n"; + *message_text += json_as_string(admin_snippet); + *message_text += attachments_text; + } + } + } +} + int facebook_json_parser::parse_messages(void* data, std::vector< facebook_message* >* messages, std::vector< facebook_notification* >* notifications) { std::string jsonData = static_cast< std::string* >(data)->substr(9); @@ -316,24 +370,17 @@ int facebook_json_parser::parse_messages(void* data, std::vector< facebook_messa if (to == NULL) continue; - // TODO: put also outgoing messages into messages array and process it elsewhere - HANDLE hContact = proto->ContactIDToHContact(json_as_string(to)); - if (!hContact) // TODO: add this contact? - continue; - - DBEVENTINFO dbei = {0}; - dbei.cbSize = sizeof(dbei); - dbei.eventType = EVENTTYPE_MESSAGE; - dbei.flags = DBEF_SENT | DBEF_UTF; - dbei.szModule = proto->m_szModuleName; - - bool local_time = proto->getByte(FACEBOOK_KEY_LOCAL_TIMESTAMP, 0) != 0; - dbei.timestamp = local_time ? ::time(NULL) : utils::time::fix_timestamp(json_as_float(time)); + JSONNODE *to_name = json_get(it, "to_name"); - dbei.cbBlob = (DWORD)message_text.length() + 1; - dbei.pBlob = (PBYTE)message_text.c_str(); - db_event_add(hContact, &dbei); + facebook_message* message = new facebook_message(); + message->message_text = message_text; + message->sender_name = utils::text::special_expressions_decode(utils::text::slashu_to_utf8(json_as_string(to_name))); + message->time = utils::time::fix_timestamp(json_as_float(time)); + message->user_id = json_as_string(to); // TODO: Check if we have contact with this ID in friendlist and otherwise do something different? + message->message_id = message_id; + message->isIncoming = false; + messages->push_back(message); } else if (t == "messaging") { // we use this only for incomming messages (and getting seen info) @@ -380,50 +427,7 @@ int facebook_json_parser::parse_messages(void* data, std::vector< facebook_messa std::string message_text = json_as_string(body); // Process attachements and stickers - JSONNODE *has_attachment = json_get(msg, "has_attachment"); - if (has_attachment != NULL && json_as_bool(has_attachment)) { - JSONNODE *admin_snippet = json_get(msg, "admin_snippet"); - if (admin_snippet != NULL) { - // TODO: have this as extra event, not replace or append message content - if (!message_text.empty()) - message_text += "\n\n"; - message_text += json_as_string(admin_snippet); - - // Append attachements - JSONNODE *attachments = json_get(msg, "attachments"); - for (unsigned int j = 0; j < json_size(attachments); j++) { - JSONNODE *itAttachment = json_at(attachments, j); - - // TODO: behave different for stickers and classic attachements? - // JSONNODE *attach_type = json_get(itAttachment, "attach_type"); // "sticker", "photo", "file" - - JSONNODE *name = json_get(itAttachment, "name"); - JSONNODE *url = json_get(itAttachment, "url"); - if (url != NULL) { - std::string link = json_as_string(url); - - if (link.find("/ajax/mercury/attachments/photo/view/") != std::string::npos) - // fix photo url - link = utils::url::decode(utils::text::source_get_value(&link, 2, "?uri=", "&")); - else if (link.find("/") == 0) { - // make absolute url - bool useHttps = proto->getByte(FACEBOOK_KEY_FORCE_HTTPS, 1) > 0; - link = (useHttps ? HTTP_PROTO_SECURE : HTTP_PROTO_REGULAR) + std::string(FACEBOOK_SERVER_REGULAR) + link; - } - - if (!link.empty()) { - std::string filename; - if (name != NULL) - filename = json_as_string(name); - if (filename == "null") - filename.clear(); - - message_text += "\n" + (!filename.empty() ? "<" + filename + "> " : "") + link + "\n"; - } - } - } - } - } + parseAttachments(proto, &message_text, it); // Ignore messages from myself, as there is no id of recipient if (id == proto->facy.self_.user_id) @@ -588,3 +592,132 @@ int facebook_json_parser::parse_messages(void* data, std::vector< facebook_messa json_delete(root); return EXIT_SUCCESS; } + +int facebook_json_parser::parse_unread_threads(void* data, std::vector< std::string >* threads) +{ + std::string jsonData = static_cast< std::string* >(data)->substr(9); + + JSONNODE *root = json_parse(jsonData.c_str()); + if (root == NULL) + return EXIT_FAILURE; + + JSONNODE *payload = json_get(root, "payload"); + if (payload == NULL) { + json_delete(root); + return EXIT_FAILURE; + } + + JSONNODE *unread_threads = json_get(payload, "unread_thread_ids"); + if (unread_threads == NULL) { + json_delete(root); + return EXIT_FAILURE; + } + + for (unsigned int i = 0; i < json_size(unread_threads); i++) { + JSONNODE *it = json_at(unread_threads, i); + + JSONNODE *folder = json_get(it, "folder"); + JSONNODE *thread_ids = json_get(it, "thread_ids"); + + for (unsigned int j = 0; j < json_size(thread_ids); j++) { + JSONNODE *id = json_at(thread_ids, j); + threads->push_back(json_as_string(id)); + } + } + + json_delete(root); + return EXIT_SUCCESS; +} + +int facebook_json_parser::parse_thread_messages(void* data, std::vector< facebook_message* >* messages, bool unreadOnly, int limit) +{ + std::string jsonData = static_cast< std::string* >(data)->substr(9); + + JSONNODE *root = json_parse(jsonData.c_str()); + if (root == NULL) + return EXIT_FAILURE; + + JSONNODE *payload = json_get(root, "payload"); + if (payload == NULL) { + json_delete(root); + return EXIT_FAILURE; + } + + JSONNODE *actions = json_get(payload, "actions"); + JSONNODE *threads = json_get(payload, "threads"); + if (actions == NULL || threads == NULL) { + json_delete(root); + return EXIT_FAILURE; + } + + std::map thread_ids; + for (unsigned int i = 0; i < json_size(threads); i++) { + JSONNODE *it = json_at(threads, i); + JSONNODE *canonical = json_get(it, "canonical_fbid"); + JSONNODE *thread_id = json_get(it, "thread_id"); + JSONNODE *unread_count = json_get(it, "unread_count"); // TODO: use it to check against number of loaded messages... but how? + + if (canonical == NULL || thread_id == NULL) + continue; + + std::string id = json_as_string(canonical); + if (id == "null") + continue; + + std::string tid = json_as_string(thread_id); + thread_ids.insert(std::make_pair(tid, id)); + } + + for (unsigned int i = 0; i < json_size(actions); i++) { + JSONNODE *it = json_at(actions, i); + + JSONNODE *author = json_get(it, "author"); + JSONNODE *author_email = json_get(it, "author_email"); + JSONNODE *body = json_get(it, "body"); + JSONNODE *tid = json_get(it, "thread_id"); + JSONNODE *mid = json_get(it, "message_id"); + JSONNODE *timestamp = json_get(it, "timestamp"); + + if (author == NULL || body == NULL || mid == NULL || tid == NULL || timestamp == NULL) + continue; + + // Ignore read messages if we want only unread messages + JSONNODE *is_unread = json_get(it, "is_unread"); + if (unreadOnly && (is_unread == NULL || !json_as_bool(is_unread))) + continue; + + // Try to get user id from "threads" array (and simultaneously ignore multi user threads) + std::map::iterator iter = thread_ids.find(json_as_string(tid)); + if (iter == thread_ids.end()) + continue; // not found or ignored multi user thread + + std::string user_id = iter->second; + std::string message_id = json_as_string(mid); + std::string message_text = json_as_string(body); + std::string author_id = json_as_string(author); + std::string::size_type pos = author_id.find(":"); + if (pos != std::string::npos) + author_id = author_id.substr(pos+1); + + // Process attachements and stickers + parseAttachments(proto, &message_text, it); + + message_text = utils::text::trim(utils::text::special_expressions_decode(utils::text::slashu_to_utf8(message_text)), true); + if (message_text.empty()) + continue; + + facebook_message* message = new facebook_message(); + message->message_text = message_text; + if (author_email != NULL) + message->sender_name = json_as_string(author_email); + message->time = utils::time::fix_timestamp(json_as_float(timestamp)); + message->user_id = user_id; // TODO: Check if we have contact with this ID in friendlist and otherwise do something different? + message->message_id = message_id; + message->isIncoming = (author_id != proto->facy.self_.user_id); + + messages->push_back(message); + } + + json_delete(root); + return EXIT_SUCCESS; +} diff --git a/protocols/FacebookRM/src/json.h b/protocols/FacebookRM/src/json.h index de16fe76db..8294c2ec2e 100644 --- a/protocols/FacebookRM/src/json.h +++ b/protocols/FacebookRM/src/json.h @@ -34,6 +34,8 @@ public: int parse_friends(void*, std::map< std::string, facebook_user* >*); int parse_notifications(void*, std::vector< facebook_notification* >*); int parse_messages(void*, std::vector< facebook_message* >*, std::vector< facebook_notification* >*); + int parse_unread_threads(void*, std::vector< std::string >*); + int parse_thread_messages(void*, std::vector< facebook_message* >*, bool unreadOnly, int limit); facebook_json_parser(FacebookProto* proto) { diff --git a/protocols/FacebookRM/src/process.cpp b/protocols/FacebookRM/src/process.cpp index 64b85103ac..bb8b95f3ea 100644 --- a/protocols/FacebookRM/src/process.cpp +++ b/protocols/FacebookRM/src/process.cpp @@ -263,129 +263,150 @@ void FacebookProto::ProcessFriendList(void* data) void FacebookProto::ProcessUnreadMessages(void*) { - facy.handle_entry("messages"); + facy.handle_entry("ProcessUnreadMessages"); - int count = 0; - std::string page; + bool loadOther = true; // TODO: db setting? or use everytime? - while (count < 30) // allow max 30 unread threads to be parsed - { - std::string get_data = "&page=" + page; + std::string data = "folders[0]=inbox"; + if (loadOther) + data += "&folders[1]=other"; + data += "&client=mercury"; + data += "__user=" + facy.self_.user_id; + data += "&fb_dtsg=" + (facy.dtsg_.length() ? facy.dtsg_ : "0"); + data += "&__a=1&__dyn=&__req=&ttstamp=0"; - http::response resp = facy.flap(REQUEST_UNREAD_THREADS, NULL, &get_data); + http::response resp = facy.flap(REQUEST_UNREAD_THREADS, &data); + facy.validate_response(&resp); - // Process result data - facy.validate_response(&resp); + if (resp.code == HTTP_CODE_OK) { + + CODE_BLOCK_TRY + + std::vector threads; - if (resp.code == HTTP_CODE_OK) - { - std::string items = utils::text::source_get_value(&resp.data, 2, "
"); + facebook_json_parser* p = new facebook_json_parser(this); + p->parse_unread_threads(&resp.data, &threads); + delete p; - std::string::size_type pos = 0; - std::string::size_type pos2 = 0; + ForkThread(&FacebookProto::ProcessUnreadMessage, new std::vector(threads)); - while ((pos = items.find("id=\"threadlist_row_", pos)) != std::string::npos) { - std::string item = items.substr(pos, items.find("
", pos) - pos); - pos++; count++; + LOG("***** Unread threads list processed"); - std::string tid = utils::text::source_get_value2(&item, "?tid=", "&\""); - if (tid.empty()) - continue; + CODE_BLOCK_CATCH - ForkThread(&FacebookProto::ProcessUnreadMessage, new std::string(tid)); - } - - page = utils::text::source_get_value(&items, 3, "id=\"see_older_threads\"", "page=", "&"); - if (page.empty()) - break; // No more results - } - } + LOG("***** Error processing unread threads list: %s", e.what()); + CODE_BLOCK_END + + facy.handle_success("ProcessUnreadMessages"); + } else { + facy.handle_error("ProcessUnreadMessages"); + } } -void FacebookProto::ProcessUnreadMessage(void *tid_data) +void FacebookProto::ProcessUnreadMessage(void *p) { - if (tid_data == NULL) + if (p == NULL) return; - // TODO: get them from /ajax/mercury/thread_info.php - - std::string get_data = "tid=" + *(std::string*)tid_data; - delete (std::string*)tid_data; - - http::response resp = facy.flap(REQUEST_UNREAD_MESSAGES, NULL, &get_data); - facy.validate_response(&resp); - - if (resp.code != HTTP_CODE_OK) { - LOG(" !! !! Error when getting messages list"); - return; - } + facy.handle_entry("ProcessUnreadMessage"); - if (resp.data.find("