From 2565775ba80f0a732bae1e28bcc43ca410166825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20P=C3=B6sel?= Date: Tue, 15 Jul 2014 15:35:25 +0000 Subject: Facebook: Fixed creating duplicit chat contacts git-svn-id: http://svn.miranda-ng.org/main/trunk@9807 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- protocols/FacebookRM/src/json.cpp | 2101 ++++++++++++++++++------------------- 1 file changed, 1046 insertions(+), 1055 deletions(-) (limited to 'protocols') diff --git a/protocols/FacebookRM/src/json.cpp b/protocols/FacebookRM/src/json.cpp index 782fb3b40c..7e2993e5e8 100644 --- a/protocols/FacebookRM/src/json.cpp +++ b/protocols/FacebookRM/src/json.cpp @@ -1,1056 +1,1047 @@ -/* - -Facebook plugin for Miranda Instant Messenger -_____________________________________________ - -Copyright � 2009-11 Michal Zelinka, 2011-13 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 . - -*/ - -#include "common.h" - -int facebook_json_parser::parse_buddy_list(void* data, List::List< facebook_user >* buddy_list) -{ - facebook_user* current = NULL; - 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 *list = json_get(payload, "buddy_list"); - if (list == NULL) { - json_delete(root); - return EXIT_FAILURE; - } - - // Set all contacts in map to offline - for (List::Item< facebook_user >* i = buddy_list->begin(); i != NULL; i = i->next) { - i->data->status_id = ID_STATUS_OFFLINE; - } - - // Load last active times - JSONNODE *lastActive = json_get(list, "last_active_times"); - if (lastActive != NULL) { - for (unsigned int i = 0; i < json_size(lastActive); i++) { - JSONNODE *it = json_at(lastActive, i); - const char *id = json_name(it); - - current = buddy_list->find(id); - if (current == NULL) { - buddy_list->insert(std::make_pair(id, new facebook_user())); - current = buddy_list->find(id); - current->user_id = id; - } - - current->last_active = json_as_int(it); - } - } - - // Find mobile friends - JSONNODE *mobileFriends = json_get(list, "mobile_friends"); - if (mobileFriends != NULL) { - for (unsigned int i = 0; i < json_size(mobileFriends); i++) { - JSONNODE *it = json_at(mobileFriends, i); - std::string id = json_as_pstring(it); - - current = buddy_list->find(id); - if (current == NULL) { - buddy_list->insert(std::make_pair(id, new facebook_user())); - current = buddy_list->find(id); - current->user_id = id; - } - - current->status_id = ID_STATUS_OFFLINE; - current->client = CLIENT_MOBILE; - } - } - - // Find now available contacts - JSONNODE *nowAvailable = json_get(list, "nowAvailableList"); - if (nowAvailable != NULL) { - for (unsigned int i = 0; i < json_size(nowAvailable); i++) { - JSONNODE *it = json_at(nowAvailable, i); - const char *id = json_name(it); - - current = buddy_list->find(id); - if (current == NULL) { - buddy_list->insert(std::make_pair(id, new facebook_user())); - current = buddy_list->find(id); - current->user_id = id; - } - - current->status_id = ID_STATUS_ONLINE; - - JSONNODE *p = json_get(it, "p"); - if (p != NULL) { - JSONNODE *status = json_get(p, "status"); // this seems to be "active" everytime - JSONNODE *webStatus = json_get(p, "webStatus"); // "active", "idle" or "offline" - JSONNODE *fbAppStatus = json_get(p, "fbAppStatus"); // "offline" or "active" - JSONNODE *messengerStatus = json_get(p, "messengerStatus"); // "offline" or "active" - JSONNODE *otherStatus = json_get(p, "otherStatus"); // "offline" or "active" - this seems to be "active" when webStatus is "idle" or "active" only - - // this may never happen - if (json_as_pstring(status) != "active") - current->status_id = ID_STATUS_OFFLINE; - - bool b; - - // "webStatus" and "otherStatus" are marked as "WEB" on FB website - if ((b = json_as_pstring(webStatus) == "active") || json_as_pstring(otherStatus) == "active") { - current->status_id = ID_STATUS_ONLINE; - current->client = b ? CLIENT_WEB : CLIENT_OTHER; - } - - // "fbAppStatus" and "messengerStatus" are marked as "MOBILE" on FB website - if ((b = json_as_pstring(fbAppStatus) == "active") || json_as_pstring(messengerStatus) == "active") { - current->status_id = ID_STATUS_ONTHEPHONE; - current->client = b ? CLIENT_APP : CLIENT_MESSENGER; - } - - // this is not marked anyhow on website (yet?) - current->idle = json_as_pstring(webStatus) == "idle" - || json_as_pstring(otherStatus) == "idle" - || json_as_pstring(fbAppStatus) == "idle" - || json_as_pstring(messengerStatus) == "idle"; - } - } - } - - // Get aditional informations about contacts (if available) - JSONNODE *userInfos = json_get(list, "userInfos"); - if (userInfos != NULL) { - for (unsigned int i = 0; i < json_size(userInfos); i++) { - JSONNODE *it = json_at(userInfos, i); - const char *id = json_name(it); - - current = buddy_list->find(id); - if (current == NULL) - continue; - - JSONNODE *name = json_get(it, "name"); - JSONNODE *thumbSrc = json_get(it, "thumbSrc"); - - if (name != NULL) - current->real_name = utils::text::slashu_to_utf8(utils::text::special_expressions_decode(json_as_pstring(name))); - if (thumbSrc != NULL) - current->image_url = utils::text::slashu_to_utf8(utils::text::special_expressions_decode(json_as_pstring(thumbSrc))); - } - } - - json_delete(root); - return EXIT_SUCCESS; -} - -void parseUser(JSONNODE *it, facebook_user *fbu) -{ - fbu->user_id = json_name(it); - - JSONNODE *id = json_get(it, "id"); - if (id == NULL || json_as_pstring(id) == "0" || json_as_pstring(id).empty()) { - // this user has deleted account or is just unavailable for us (e.g., ignore list) -> don't read dummy name and avatar and rather ignore that completely - return; - } - - JSONNODE *name = json_get(it, "name"); - JSONNODE *thumbSrc = json_get(it, "thumbSrc"); - JSONNODE *gender = json_get(it, "gender"); - JSONNODE *vanity = json_get(it, "vanity"); // username - //JSONNODE *uri = json_get(it, "uri"); // profile url - //JSONNODE *is_friend = json_get(it, "is_friend"); // e.g. "True" - //JSONNODE *type = json_get(it, "type"); // e.g. "friend" (classic contact) or "user" (disabled/deleted account) - - - if (name) - fbu->real_name = utils::text::slashu_to_utf8(utils::text::special_expressions_decode(json_as_pstring(name))); - if (thumbSrc) - fbu->image_url = utils::text::slashu_to_utf8(utils::text::special_expressions_decode(json_as_pstring(thumbSrc))); - if (vanity) - fbu->username = utils::text::slashu_to_utf8(utils::text::special_expressions_decode(json_as_pstring(vanity))); - - if (gender) - switch (json_as_int(gender)) { - case 1: // female - fbu->gender = 70; - break; - case 2: // male - fbu->gender = 77; - break; - // case 7: not available female? - } -} - -int facebook_json_parser::parse_friends(void* data, std::map< std::string, facebook_user* >* friends) -{ - 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; - } - - for (unsigned int i = 0; i < json_size(payload); i++) { - JSONNODE *it = json_at(payload, i); - - facebook_user *fbu = new facebook_user(); - parseUser(it, fbu); - - friends->insert(std::make_pair(fbu->user_id, fbu)); - } - - json_delete(root); - return EXIT_SUCCESS; -} - - -int facebook_json_parser::parse_notifications(void *data, std::map< std::string, facebook_notification* > *notifications) -{ - 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 *list = json_get(payload, "notifications"); - if (list == NULL) { - json_delete(root); - return EXIT_FAILURE; - } - - for (unsigned int i = 0; i < json_size(list); i++) { - JSONNODE *it = json_at(list, i); - const char *id = json_name(it); - - JSONNODE *markup = json_get(it, "markup"); - JSONNODE *unread = json_get(it, "unread"); - //JSONNODE *time = json_get(it, "time"); - - // Ignore empty and old notifications - if (markup == NULL || unread == NULL || json_as_int(unread) == 0) - continue; - - std::string text = utils::text::slashu_to_utf8(utils::text::special_expressions_decode(json_as_pstring(markup))); - - facebook_notification* notification = new facebook_notification(); - - notification->id = id; - notification->link = utils::text::source_get_value(&text, 3, "text = utils::text::remove_html(utils::text::source_get_value(&text, 1, "find(notification->id) == notifications->end()) - notifications->insert(std::make_pair(notification->id, notification)); - else - delete notification; - } - - json_delete(root); - return EXIT_SUCCESS; -} - -bool ignore_duplicits(FacebookProto *proto, std::string mid, std::string text) -{ - ScopedLock s(proto->facy.send_message_lock_); - - std::map::iterator it = proto->facy.messages_ignore.find(mid); - if (it != proto->facy.messages_ignore.end()) { - proto->debugLogA("????? Ignoring duplicit/sent message\n%s", text.c_str()); - - it->second = true; // mark to delete it at the end - return true; - } - - // remember this id to ignore duplicits - proto->facy.messages_ignore.insert(std::make_pair(mid, true)); - 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)) { - // Append attachements - std::string type = ""; - 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); - JSONNODE *attach_type = json_get(itAttachment, "attach_type"); // "sticker", "photo", "file" - if (attach_type != NULL) { - // Get attachment type - "file" has priority over other types - if (type.empty() || type != "file") - type = json_as_pstring(attach_type); - } - JSONNODE *name = json_get(itAttachment, "name"); - JSONNODE *url = json_get(itAttachment, "url"); - if (url != NULL) { - std::string link = json_as_pstring(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_pstring(name); - if (filename == "null") - filename.clear(); - - attachments_text += "\n" + (!filename.empty() ? "<" + filename + "> " : "") + link + "\n"; - } - } - } - - // TODO: have this as extra event, not replace or append message content - if (!message_text->empty()) - *message_text += "\n\n"; - - if (!attachments_text.empty()) { - // we can't use this as offline messages doesn't have it - /* JSONNODE *admin_snippet = json_get(it, "admin_snippet"); - if (admin_snippet != NULL) { - *message_text += json_as_pstring(admin_snippet); - } */ - - std::tstring newText; - if (type == "sticker") - newText = TranslateT("a sticker"); - else if (type == "file") - newText = (json_size(attachments) > 1) ? TranslateT("files") : TranslateT("a file"); - else if (type == "photo") - newText = (json_size(attachments) > 1) ? TranslateT("photos") : TranslateT("a photo"); - else - newText = _A2T(type.c_str()); - - TCHAR title[200]; - mir_sntprintf(title, SIZEOF(title), TranslateT("User sent %s:"), newText.c_str()); - - *message_text += ptrA(mir_utf8encodeT(title)); - *message_text += attachments_text; - } else { - // TODO: better support for these attachments (parse it from "m_messaging" instead of "messaging" - *message_text += ptrA(mir_utf8encodeT(TranslateT("User sent an unsupported attachment. Open your browser to see it."))); - } - } -} - -int facebook_json_parser::parse_messages(void* data, std::vector< facebook_message* >* messages, std::map< std::string, facebook_notification* >* notifications, bool inboxOnly) -{ - // remove old received messages from map - for (std::map::iterator it = proto->facy.messages_ignore.begin(); it != proto->facy.messages_ignore.end();) { - if (it->second) - it = proto->facy.messages_ignore.erase(it); - else - ++it; - } - - std::string jsonData = static_cast< std::string* >(data)->substr(9); - - JSONNODE *root = json_parse(jsonData.c_str()); - if (root == NULL) - return EXIT_FAILURE; - - JSONNODE *ms = json_get(root, "ms"); - if (ms == NULL) { - json_delete(root); - return EXIT_FAILURE; - } - - for (unsigned int i = 0; i < json_size(ms); i++) { - JSONNODE *it = json_at(ms, i); - - JSONNODE *type = json_get(it, "type"); - if (type == NULL) - continue; - - std::string t = json_as_pstring(type); - if (t == "messaging") { - // various messaging stuff (received and sent messages, getting seen info) - - JSONNODE *type = json_get(it, "event"); - if (type == NULL) - continue; - - std::string t = json_as_pstring(type); - - if (t == "read_receipt") { - // user read message - JSONNODE *reader = json_get(it, "reader"); - JSONNODE *time = json_get(it, "time"); - - if (reader == NULL || time == NULL) - continue; - - // check if we should use use local_timestamp for unread messages and use it for read time too - bool local_timestamp = proto->getBool(FACEBOOK_KEY_LOCAL_TIMESTAMP_UNREAD, 0); - time_t timestamp = local_timestamp ? ::time(NULL) : utils::time::fix_timestamp(json_as_float(time)); - - TCHAR ttime[64]; - _tcsftime(ttime, SIZEOF(ttime), _T("%X"), localtime(×tamp)); - - JSONNODE *threadid = json_get(it, "tid"); - if (threadid != NULL) { // multi user chat - std::tstring tid = json_as_string(threadid); - std::string reader_id = json_as_pstring(reader); - - std::map::iterator chatroom = proto->facy.chat_rooms.find(tid); - if (chatroom != proto->facy.chat_rooms.end()) { - std::map::const_iterator participant = chatroom->second.participants.find(reader_id); - if (participant == chatroom->second.participants.end()) { - // TODO: load name of this participant - std::string name = reader_id; - proto->AddChatContact(tid.c_str(), reader_id.c_str(), name.c_str()); - } - - participant = chatroom->second.participants.find(reader_id); - if (participant != chatroom->second.participants.end()) { - MCONTACT hChatContact = proto->ChatIDToHContact(tid); - - if (!chatroom->second.message_readers.empty()) - chatroom->second.message_readers += ", "; - chatroom->second.message_readers += participant->second; - - ptrT readers(mir_utf8decodeT(chatroom->second.message_readers.c_str())); - - StatusTextData st = { 0 }; - st.cbSize = sizeof(st); - st.hIcon = Skin_GetIconByHandle(GetIconHandle("read")); - - mir_sntprintf(st.tszText, SIZEOF(st.tszText), TranslateT("Message read: %s by %s"), ttime, readers); - - CallService(MS_MSG_SETSTATUSTEXT, (WPARAM)hChatContact, (LPARAM)&st); - } - } - } else { // classic contact - MCONTACT hContact = proto->ContactIDToHContact(json_as_pstring(reader)); - if (hContact) { - proto->facy.readers.insert(std::make_pair(hContact, timestamp)); - proto->MessageRead(hContact); - } - } - } else if (t == "deliver") { - // inbox message (multiuser or direct) - - JSONNODE *msg = json_get(it, "message"); - JSONNODE *folder = json_get(it, "folder"); - - if (inboxOnly && json_as_pstring(folder) != "inbox") - continue; - - JSONNODE *sender_fbid = json_get(msg, "sender_fbid"); - JSONNODE *sender_name = json_get(msg, "sender_name"); - JSONNODE *body = json_get(msg, "body"); - JSONNODE *tid = json_get(msg, "tid"); - JSONNODE *mid = json_get(msg, "mid"); - JSONNODE *timestamp = json_get(msg, "timestamp"); - JSONNODE *filtered = json_get(it, "is_filtered_content"); - - if (sender_fbid == NULL || sender_name == NULL || body == NULL || mid == NULL || timestamp == NULL) - continue; - - std::string id = json_as_pstring(sender_fbid); - std::string message_id = json_as_pstring(mid); - std::string message_text = json_as_pstring(body); - - // Process attachements and stickers - parseAttachments(proto, &message_text, msg); - - // Ignore duplicits or messages sent from miranda - if (body == NULL || ignore_duplicits(proto, message_id, message_text)) - continue; - - if (json_as_bool(filtered) && 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::special_expressions_decode(utils::text::slashu_to_utf8(message_text)), true); - if (message_text.empty()) - continue; - - facebook_message* message = new facebook_message(); - message->isUnread = true; - message->isIncoming = (id != proto->facy.self_.user_id); - message->message_text = message_text; - message->time = utils::time::fix_timestamp(json_as_float(timestamp)); - message->user_id = id; - message->message_id = message_id; - message->sender_name = utils::text::special_expressions_decode(utils::text::slashu_to_utf8(json_as_pstring(sender_name))); // TODO: or if not incomming use my own name from facy.self_ ? - message->thread_id = json_as_pstring(tid); // TODO: or if not incomming use my own id from facy.self_ ? - - JSONNODE *gthreadinfo = json_get(msg, "group_thread_info"); - message->isChat = (gthreadinfo != NULL); - - if (!message->isChat && !message->isIncoming) { - message->sender_name = ""; - message->user_id = proto->ThreadIDToContactID(message->thread_id); // TODO: Check if we have contact with this user_id in friendlist and otherwise do something different? - } - - messages->push_back(message); - } - } else if (t == "notification_json") { - // event notification - JSONNODE *nodes = json_get(it, "nodes"); - - for (unsigned int j = 0; j < json_size(nodes); j++) { - JSONNODE *itNodes = json_at(nodes, j); - - //JSONNODE *text = json_get(itNodes, "title/text"); // use this when popups will be ready to allow changes of their content - JSONNODE *text_ = json_get(itNodes, "unaggregatedTitle"); // notifications one by one, not grouped - if (text_ == NULL) - continue; - JSONNODE *text = json_get(text_, "text"); - JSONNODE *url = json_get(itNodes, "url"); - JSONNODE *alert_id = json_get(itNodes, "alert_id"); - - JSONNODE *time_ = json_get(itNodes, "timestamp"); - if (time_ == NULL) - continue; - JSONNODE *time = json_get(time_, "time"); - if (time == NULL || text == NULL || url == NULL || alert_id == NULL) - continue; - - unsigned __int64 timestamp = json_as_float(time); - if (timestamp > proto->facy.last_notification_time_) { - // Only new notifications - proto->facy.last_notification_time_ = timestamp; - - facebook_notification* notification = new facebook_notification(); - notification->text = utils::text::slashu_to_utf8(json_as_pstring(text)); - notification->link = utils::text::special_expressions_decode(json_as_pstring(url)); - notification->id = json_as_pstring(alert_id); - - std::string::size_type pos = notification->id.find(":"); - if (pos != std::string::npos) - notification->id = notification->id.substr(pos+1); - - if (notifications->find(notification->id) == notifications->end()) - notifications->insert(std::make_pair(notification->id, notification)); - else - delete notification; - } - } - } else if (t == "typ") { - // chat typing notification - - JSONNODE *from = json_get(it, "from"); - if (from == NULL) - continue; - - facebook_user fbu; - fbu.user_id = json_as_pstring(from); - - MCONTACT hContact = proto->AddToContactList(&fbu, CONTACT_FRIEND); // only friends are able to send typing notifications - - JSONNODE *st = json_get(it, "st"); - if (json_as_int(st) == 1) - proto->StartTyping(hContact); - else - proto->StopTyping(hContact); - } else if (t == "ttyp") { - // multi chat typing notification - - JSONNODE *from_ = json_get(it, "from"); - JSONNODE *thread_ = json_get(it, "thread"); - JSONNODE *st_ = json_get(it, "st"); - - if (from_ == NULL || thread_ == NULL || st_ == NULL) - continue; - - std::tstring tid = json_as_string(thread_); - std::string from_id = json_as_pstring(from_); - - std::map::iterator chatroom = proto->facy.chat_rooms.find(tid); - if (chatroom != proto->facy.chat_rooms.end()) { - std::map::const_iterator participant = chatroom->second.participants.find(from_id); - if (participant == chatroom->second.participants.end()) { - // TODO: load name of this participant - std::string name = from_id; - proto->AddChatContact(tid.c_str(), from_id.c_str(), name.c_str()); - } - - participant = chatroom->second.participants.find(from_id); - if (participant != chatroom->second.participants.end()) { - MCONTACT hChatContact = proto->ChatIDToHContact(tid); - ptrT name(mir_utf8decodeT(participant->second.c_str())); - - if (json_as_int(st_) == 1) { - StatusTextData st = { 0 }; - st.cbSize = sizeof(st); - - mir_sntprintf(st.tszText, SIZEOF(st.tszText), TranslateT("%s is typing a message..."), name); - - CallService(MS_MSG_SETSTATUSTEXT, (WPARAM)hChatContact, (LPARAM)&st); - } - else { - CallService(MS_MSG_SETSTATUSTEXT, (WPARAM)hChatContact, NULL); - } - - // TODO: support proper MS_PROTO_CONTACTISTYPING service for chatrooms (when it will be implemented) - } - } - } else if (t == "privacy_changed") { - // settings changed - - JSONNODE *event_type = json_get(it, "event"); - JSONNODE *event_data = json_get(it, "data"); - - if (event_type == NULL || event_data == NULL) - continue; - - std::string t = json_as_pstring(event_type); - if (t == "visibility_update") { - // change of chat status - JSONNODE *visibility = json_get(event_data, "visibility"); - - bool isVisible = (visibility != NULL) && json_as_bool(visibility); - proto->debugLogA(" Requested chat switch to %s", isVisible ? "Online" : "Offline"); - proto->SetStatus(isVisible ? ID_STATUS_ONLINE : ID_STATUS_INVISIBLE); - } - } else if (t == "buddylist_overlay") { - // we opened/closed chat window - pretty useless info for us - continue; - } else if (t == "ticker_update:home") { - JSONNODE *actor_ = json_get(it, "actor"); - JSONNODE *time_ = json_get(it, "actor"); - JSONNODE *story_ = json_get(it, "story_xhp"); - - time_t time = json_as_float(time_); - std::string text = json_as_pstring(story_); - text = utils::text::slashu_to_utf8(text); - - text = utils::text::trim( - utils::text::special_expressions_decode( - utils::text::source_get_value(&text, 3, "", ""))); - - // TODO: notify ticker updates - /* if (!text.empty()) { - proto->NotifyEvent() - }*/ - } else if (t == "mercury") { - // rename multi user chat, ... - - JSONNODE *actions_ = json_get(it, "actions"); - if (actions_ == NULL) - continue; - - for (unsigned int i = 0; i < json_size(actions_); i++) { - JSONNODE *action_ = json_at(actions_, i); - - JSONNODE *thread_id_ = json_get(action_, "thread_id"); - JSONNODE *log_body_ = json_get(action_, "log_message_body"); - JSONNODE *log_data_ = json_get(action_, "log_message_data"); - if (!log_data_ || !thread_id_) - continue; - - JSONNODE *name_ = json_get(log_data_, "name"); - std::string name = json_as_pstring(name_); - std::tstring thread_id = json_as_string(thread_id_); - std::string message = json_as_pstring(log_body_); - - // proto->RenameChat(thread_id.c_str(), name.c_str()); // this don't work, why? - proto->setStringUtf(proto->ChatIDToHContact(thread_id), FACEBOOK_KEY_NICK, name.c_str()); - - proto->UpdateChat(thread_id.c_str(), NULL, NULL, message.c_str()); - } - } else if (t == "notifications_read" || t == "notifications_seen") { - JSONNODE *alerts = json_get(it, "alert_ids"); - - for (unsigned int j = 0; j < json_size(alerts); j++) { - JSONNODE *itAlerts = json_at(alerts, j); - std::string id = json_as_pstring(itAlerts); - - std::map::iterator it = notifications->find(id); - if (it != notifications->end()) { - if (it->second->hWndPopup != NULL) - PUDeletePopup(it->second->hWndPopup); // close popup - - delete it->second; - notifications->erase(it); - } - } - } else - continue; - } - - json_delete(root); - return EXIT_SUCCESS; -} - -int facebook_json_parser::parse_unread_threads(void* data, std::vector< std::string >* threads, bool inboxOnly) -{ - 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"); - - if (inboxOnly && json_as_pstring(folder) != "inbox") - continue; - - for (unsigned int j = 0; j < json_size(thread_ids); j++) { - JSONNODE *id = json_at(thread_ids, j); - threads->push_back(json_as_pstring(id)); - } - } - - json_delete(root); - return EXIT_SUCCESS; -} - -int facebook_json_parser::parse_thread_messages(void* data, std::vector< facebook_message* >* messages, std::map< std::string, facebook_chatroom* >* chatrooms, bool unreadOnly, bool inboxOnly, 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; - } - - JSONNODE *roger = json_get(payload, "roger"); - if (roger != NULL) { - for (unsigned int i = 0; i < json_size(roger); i++) { - JSONNODE *it = json_at(roger, i); - - facebook_chatroom *room = new facebook_chatroom(); - room->thread_id = _A2T(json_name(it)); - - for (unsigned int j = 0; j < json_size(it); j++) { - JSONNODE *jt = json_at(it, j); - std::string user_id = json_name(jt); - room->participants.insert(std::make_pair(user_id, user_id)); // TODO: get name somehow - } - - chatrooms->insert(std::make_pair((char*)_T2A(room->thread_id.c_str()), room)); - } - } - - std::map thread_ids; - for (unsigned int i = 0; i < json_size(threads); i++) { - JSONNODE *it = json_at(threads, i); - JSONNODE *is_canonical_user = json_get(it, "is_canonical_user"); - JSONNODE *canonical = json_get(it, "canonical_fbid"); - JSONNODE *thread_id = json_get(it, "thread_id"); - JSONNODE *thread_fbid = json_get(it, "thread_fbid"); - JSONNODE *name = json_get(it, "name"); - //JSONNODE *message_count = json_get(it, "message_count"); - JSONNODE *unread_count = json_get(it, "unread_count"); // TODO: use it to check against number of loaded messages... but how? - JSONNODE *folder = json_get(it, "folder"); - - if (canonical == NULL || thread_id == NULL) - continue; - - std::string id = json_as_pstring(canonical); - std::string tid = json_as_pstring(thread_id); - std::string tfbid = json_as_pstring(thread_fbid); - - // FIXME: fix this ugliness better (it was quick workaround for stable release) - std::map::iterator iter = chatrooms->find(tid); - if (iter != chatrooms->end()) { - if (json_as_bool(is_canonical_user)) { - chatrooms->erase(iter); // this is not chatroom - } else { - iter->second->chat_name = json_as_string(name); // TODO: create name from users if there is no name... - } - } - - iter = chatrooms->find(tfbid); - if (iter != chatrooms->end()) { - if (json_as_bool(is_canonical_user)) { - chatrooms->erase(iter); // this is not chatroom - } - else { - iter->second->chat_name = json_as_string(name); // TODO: create name from users if there is no name... - } - } - - iter = chatrooms->find(id); - if (iter != chatrooms->end()) { - chatrooms->erase(iter); // this is not chatroom - } - // END OF FIXME - - if (inboxOnly && json_as_pstring(folder) != "inbox") - continue; - - if (id == "null") - continue; - - 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"); - JSONNODE *filtered = json_get(it, "is_filtered_content"); - JSONNODE *folder = json_get(it, "folder"); - JSONNODE *is_unread = json_get(it, "is_unread"); - - if (author == NULL || body == NULL || mid == NULL || tid == NULL || timestamp == NULL) - continue; - - if (inboxOnly && json_as_pstring(folder) != "inbox") - continue; - - std::string thread_id = json_as_pstring(tid); - std::string message_id = json_as_pstring(mid); - std::string message_text = json_as_pstring(body); - std::string author_id = json_as_pstring(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); - - if (json_as_bool(filtered) && 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::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_pstring(author_email); - message->time = utils::time::fix_timestamp(json_as_float(timestamp)); - message->thread_id = thread_id; - message->message_id = message_id; - message->isIncoming = (author_id != proto->facy.self_.user_id); - message->isUnread = (is_unread != NULL && json_as_bool(is_unread)); - - // Ignore read messages if we want only unread messages - if (unreadOnly && !message->isUnread) - continue; - - std::map::iterator iter = chatrooms->find(thread_id); - if (iter != chatrooms->end()) { - // this is chatroom message - message->isChat = true; - message->user_id = author_id; - } else { - // this is standard message - message->isChat = false; - std::map::iterator iter = thread_ids.find(thread_id); - if (iter != thread_ids.end()) { - message->user_id = iter->second; // TODO: Check if we have contact with this ID in friendlist and otherwise do something different? - } else { - continue; - } - } - - messages->push_back(message); - } - - json_delete(root); - return EXIT_SUCCESS; -} - -int facebook_json_parser::parse_thread_info(void* data, std::string* user_id) -{ - 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 *threads = json_get(payload, "threads"); - if (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 *message_count = json_get(it, "message_count"); // TODO: this could be useful for loading history from server - - if (canonical == NULL || thread_id == NULL) - continue; - - std::string id = json_as_pstring(canonical); - if (id == "null") - continue; - - *user_id = id; - } - - json_delete(root); - return EXIT_SUCCESS; -} - - -int facebook_json_parser::parse_user_info(void* data, facebook_user* fbu) -{ - 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 *profiles = json_get(payload, "profiles"); - if (profiles == NULL) { - json_delete(root); - return EXIT_FAILURE; - } - - std::map user_ids; - for (unsigned int i = 0; i < json_size(profiles); i++) { - JSONNODE *it = json_at(profiles, i); - - // TODO: allow more users to parse at once - std::string id = json_name(it); - - if (fbu->user_id == id) { - parseUser(it, fbu); - break; - } - } - - json_delete(root); - return EXIT_SUCCESS; -} - -/* TODO: implement this somehow -int facebook_json_parser::parse_chat_info(void* data, facebook_chat* fbu) { - // This is a recent 5 person listing of participants. - JSONNODE *participants_ids = json_get(gthreadinfo, "participant_ids"); - JSONNODE *participants_names = json_get(gthreadinfo, "participant_names"); - - JSONNODE *thread_name_ = json_get(gthreadinfo, "name"); - std::tstring name = json_as_string(thread_name_); - - // if there is no name for this room, set own name - if (name.empty()) { - unsigned int namesCount = 3; // how many names should be in room name; max. 5 - - for (unsigned int n = 0; n < json_size(participants_names) && n < namesCount; n++) { - JSONNODE *nameItr = json_at(participants_names, n); - - if (!name.empty()) - name += _T(", "); - - name += json_as_string(nameItr); - } - JSONNODE *count_ = json_get(gthreadinfo, "participant_total_count"); - unsigned int count = json_as_int(count_); - - if (count > namesCount) { - TCHAR more[200]; - mir_sntprintf(more, SIZEOF(more), TranslateT("%s and more (%d)"), name.c_str(), count - namesCount); - name = more; - } - } - - if (name.empty()) - name = thread_id; - - for (unsigned int n = 0; n < json_size(participants_ids); n++) { - JSONNODE *idItr = json_at(participants_ids, n); - std::string pId = json_as_pstring(idItr); - - JSONNODE *nameItr = json_at(participants_names, n); - std::string pName = json_as_pstring(nameItr); - - if (!proto->IsChatContact(thread_id.c_str(), pId.c_str())) { - proto->AddChatContact(thread_id.c_str(), pId.c_str(), pName.c_str()); - } - } - - std::string senderName = json_as_pstring(sender_name); - std::string::size_type pos; - if ((pos = senderName.find(" ")) != std::string::npos) { - senderName = senderName.substr(0, pos); - } - - // Last fall-back for adding this sender (in case he was not in the participants) - is this even needed? - if (!proto->IsChatContact(thread_id.c_str(), id.c_str())) { - proto->AddChatContact(thread_id.c_str(), id.c_str(), senderName.c_str()); - } -} +/* + +Facebook plugin for Miranda Instant Messenger +_____________________________________________ + +Copyright � 2009-11 Michal Zelinka, 2011-13 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 . + +*/ + +#include "common.h" + +int facebook_json_parser::parse_buddy_list(void* data, List::List< facebook_user >* buddy_list) +{ + facebook_user* current = NULL; + 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 *list = json_get(payload, "buddy_list"); + if (list == NULL) { + json_delete(root); + return EXIT_FAILURE; + } + + // Set all contacts in map to offline + for (List::Item< facebook_user >* i = buddy_list->begin(); i != NULL; i = i->next) { + i->data->status_id = ID_STATUS_OFFLINE; + } + + // Load last active times + JSONNODE *lastActive = json_get(list, "last_active_times"); + if (lastActive != NULL) { + for (unsigned int i = 0; i < json_size(lastActive); i++) { + JSONNODE *it = json_at(lastActive, i); + const char *id = json_name(it); + + current = buddy_list->find(id); + if (current == NULL) { + buddy_list->insert(std::make_pair(id, new facebook_user())); + current = buddy_list->find(id); + current->user_id = id; + } + + current->last_active = json_as_int(it); + } + } + + // Find mobile friends + JSONNODE *mobileFriends = json_get(list, "mobile_friends"); + if (mobileFriends != NULL) { + for (unsigned int i = 0; i < json_size(mobileFriends); i++) { + JSONNODE *it = json_at(mobileFriends, i); + std::string id = json_as_pstring(it); + + current = buddy_list->find(id); + if (current == NULL) { + buddy_list->insert(std::make_pair(id, new facebook_user())); + current = buddy_list->find(id); + current->user_id = id; + } + + current->status_id = ID_STATUS_OFFLINE; + current->client = CLIENT_MOBILE; + } + } + + // Find now available contacts + JSONNODE *nowAvailable = json_get(list, "nowAvailableList"); + if (nowAvailable != NULL) { + for (unsigned int i = 0; i < json_size(nowAvailable); i++) { + JSONNODE *it = json_at(nowAvailable, i); + const char *id = json_name(it); + + current = buddy_list->find(id); + if (current == NULL) { + buddy_list->insert(std::make_pair(id, new facebook_user())); + current = buddy_list->find(id); + current->user_id = id; + } + + current->status_id = ID_STATUS_ONLINE; + + JSONNODE *p = json_get(it, "p"); + if (p != NULL) { + JSONNODE *status = json_get(p, "status"); // this seems to be "active" everytime + JSONNODE *webStatus = json_get(p, "webStatus"); // "active", "idle" or "offline" + JSONNODE *fbAppStatus = json_get(p, "fbAppStatus"); // "offline" or "active" + JSONNODE *messengerStatus = json_get(p, "messengerStatus"); // "offline" or "active" + JSONNODE *otherStatus = json_get(p, "otherStatus"); // "offline" or "active" - this seems to be "active" when webStatus is "idle" or "active" only + + // this may never happen + if (json_as_pstring(status) != "active") + current->status_id = ID_STATUS_OFFLINE; + + bool b; + + // "webStatus" and "otherStatus" are marked as "WEB" on FB website + if ((b = json_as_pstring(webStatus) == "active") || json_as_pstring(otherStatus) == "active") { + current->status_id = ID_STATUS_ONLINE; + current->client = b ? CLIENT_WEB : CLIENT_OTHER; + } + + // "fbAppStatus" and "messengerStatus" are marked as "MOBILE" on FB website + if ((b = json_as_pstring(fbAppStatus) == "active") || json_as_pstring(messengerStatus) == "active") { + current->status_id = ID_STATUS_ONTHEPHONE; + current->client = b ? CLIENT_APP : CLIENT_MESSENGER; + } + + // this is not marked anyhow on website (yet?) + current->idle = json_as_pstring(webStatus) == "idle" + || json_as_pstring(otherStatus) == "idle" + || json_as_pstring(fbAppStatus) == "idle" + || json_as_pstring(messengerStatus) == "idle"; + } + } + } + + // Get aditional informations about contacts (if available) + JSONNODE *userInfos = json_get(list, "userInfos"); + if (userInfos != NULL) { + for (unsigned int i = 0; i < json_size(userInfos); i++) { + JSONNODE *it = json_at(userInfos, i); + const char *id = json_name(it); + + current = buddy_list->find(id); + if (current == NULL) + continue; + + JSONNODE *name = json_get(it, "name"); + JSONNODE *thumbSrc = json_get(it, "thumbSrc"); + + if (name != NULL) + current->real_name = utils::text::slashu_to_utf8(utils::text::special_expressions_decode(json_as_pstring(name))); + if (thumbSrc != NULL) + current->image_url = utils::text::slashu_to_utf8(utils::text::special_expressions_decode(json_as_pstring(thumbSrc))); + } + } + + json_delete(root); + return EXIT_SUCCESS; +} + +void parseUser(JSONNODE *it, facebook_user *fbu) +{ + fbu->user_id = json_name(it); + + JSONNODE *id = json_get(it, "id"); + if (id == NULL || json_as_pstring(id) == "0" || json_as_pstring(id).empty()) { + // this user has deleted account or is just unavailable for us (e.g., ignore list) -> don't read dummy name and avatar and rather ignore that completely + return; + } + + JSONNODE *name = json_get(it, "name"); + JSONNODE *thumbSrc = json_get(it, "thumbSrc"); + JSONNODE *gender = json_get(it, "gender"); + JSONNODE *vanity = json_get(it, "vanity"); // username + //JSONNODE *uri = json_get(it, "uri"); // profile url + //JSONNODE *is_friend = json_get(it, "is_friend"); // e.g. "True" + //JSONNODE *type = json_get(it, "type"); // e.g. "friend" (classic contact) or "user" (disabled/deleted account) + + + if (name) + fbu->real_name = utils::text::slashu_to_utf8(utils::text::special_expressions_decode(json_as_pstring(name))); + if (thumbSrc) + fbu->image_url = utils::text::slashu_to_utf8(utils::text::special_expressions_decode(json_as_pstring(thumbSrc))); + if (vanity) + fbu->username = utils::text::slashu_to_utf8(utils::text::special_expressions_decode(json_as_pstring(vanity))); + + if (gender) + switch (json_as_int(gender)) { + case 1: // female + fbu->gender = 70; + break; + case 2: // male + fbu->gender = 77; + break; + // case 7: not available female? + } +} + +int facebook_json_parser::parse_friends(void* data, std::map< std::string, facebook_user* >* friends) +{ + 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; + } + + for (unsigned int i = 0; i < json_size(payload); i++) { + JSONNODE *it = json_at(payload, i); + + facebook_user *fbu = new facebook_user(); + parseUser(it, fbu); + + friends->insert(std::make_pair(fbu->user_id, fbu)); + } + + json_delete(root); + return EXIT_SUCCESS; +} + + +int facebook_json_parser::parse_notifications(void *data, std::map< std::string, facebook_notification* > *notifications) +{ + 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 *list = json_get(payload, "notifications"); + if (list == NULL) { + json_delete(root); + return EXIT_FAILURE; + } + + for (unsigned int i = 0; i < json_size(list); i++) { + JSONNODE *it = json_at(list, i); + const char *id = json_name(it); + + JSONNODE *markup = json_get(it, "markup"); + JSONNODE *unread = json_get(it, "unread"); + //JSONNODE *time = json_get(it, "time"); + + // Ignore empty and old notifications + if (markup == NULL || unread == NULL || json_as_int(unread) == 0) + continue; + + std::string text = utils::text::slashu_to_utf8(utils::text::special_expressions_decode(json_as_pstring(markup))); + + facebook_notification* notification = new facebook_notification(); + + notification->id = id; + notification->link = utils::text::source_get_value(&text, 3, "text = utils::text::remove_html(utils::text::source_get_value(&text, 1, "find(notification->id) == notifications->end()) + notifications->insert(std::make_pair(notification->id, notification)); + else + delete notification; + } + + json_delete(root); + return EXIT_SUCCESS; +} + +bool ignore_duplicits(FacebookProto *proto, std::string mid, std::string text) +{ + ScopedLock s(proto->facy.send_message_lock_); + + std::map::iterator it = proto->facy.messages_ignore.find(mid); + if (it != proto->facy.messages_ignore.end()) { + proto->debugLogA("????? Ignoring duplicit/sent message\n%s", text.c_str()); + + it->second = true; // mark to delete it at the end + return true; + } + + // remember this id to ignore duplicits + proto->facy.messages_ignore.insert(std::make_pair(mid, true)); + 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)) { + // Append attachements + std::string type = ""; + 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); + JSONNODE *attach_type = json_get(itAttachment, "attach_type"); // "sticker", "photo", "file" + if (attach_type != NULL) { + // Get attachment type - "file" has priority over other types + if (type.empty() || type != "file") + type = json_as_pstring(attach_type); + } + JSONNODE *name = json_get(itAttachment, "name"); + JSONNODE *url = json_get(itAttachment, "url"); + if (url != NULL) { + std::string link = json_as_pstring(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_pstring(name); + if (filename == "null") + filename.clear(); + + attachments_text += "\n" + (!filename.empty() ? "<" + filename + "> " : "") + link + "\n"; + } + } + } + + // TODO: have this as extra event, not replace or append message content + if (!message_text->empty()) + *message_text += "\n\n"; + + if (!attachments_text.empty()) { + // we can't use this as offline messages doesn't have it + /* JSONNODE *admin_snippet = json_get(it, "admin_snippet"); + if (admin_snippet != NULL) { + *message_text += json_as_pstring(admin_snippet); + } */ + + std::tstring newText; + if (type == "sticker") + newText = TranslateT("a sticker"); + else if (type == "file") + newText = (json_size(attachments) > 1) ? TranslateT("files") : TranslateT("a file"); + else if (type == "photo") + newText = (json_size(attachments) > 1) ? TranslateT("photos") : TranslateT("a photo"); + else + newText = _A2T(type.c_str()); + + TCHAR title[200]; + mir_sntprintf(title, SIZEOF(title), TranslateT("User sent %s:"), newText.c_str()); + + *message_text += ptrA(mir_utf8encodeT(title)); + *message_text += attachments_text; + } else { + // TODO: better support for these attachments (parse it from "m_messaging" instead of "messaging" + *message_text += ptrA(mir_utf8encodeT(TranslateT("User sent an unsupported attachment. Open your browser to see it."))); + } + } +} + +int facebook_json_parser::parse_messages(void* data, std::vector< facebook_message* >* messages, std::map< std::string, facebook_notification* >* notifications, bool inboxOnly) +{ + // remove old received messages from map + for (std::map::iterator it = proto->facy.messages_ignore.begin(); it != proto->facy.messages_ignore.end();) { + if (it->second) + it = proto->facy.messages_ignore.erase(it); + else + ++it; + } + + std::string jsonData = static_cast< std::string* >(data)->substr(9); + + JSONNODE *root = json_parse(jsonData.c_str()); + if (root == NULL) + return EXIT_FAILURE; + + JSONNODE *ms = json_get(root, "ms"); + if (ms == NULL) { + json_delete(root); + return EXIT_FAILURE; + } + + for (unsigned int i = 0; i < json_size(ms); i++) { + JSONNODE *it = json_at(ms, i); + + JSONNODE *type = json_get(it, "type"); + if (type == NULL) + continue; + + std::string t = json_as_pstring(type); + if (t == "messaging") { + // various messaging stuff (received and sent messages, getting seen info) + + JSONNODE *type = json_get(it, "event"); + if (type == NULL) + continue; + + std::string t = json_as_pstring(type); + + if (t == "read_receipt") { + // user read message + JSONNODE *reader = json_get(it, "reader"); + JSONNODE *time = json_get(it, "time"); + + if (reader == NULL || time == NULL) + continue; + + // check if we should use use local_timestamp for unread messages and use it for read time too + bool local_timestamp = proto->getBool(FACEBOOK_KEY_LOCAL_TIMESTAMP_UNREAD, 0); + time_t timestamp = local_timestamp ? ::time(NULL) : utils::time::fix_timestamp(json_as_float(time)); + + TCHAR ttime[64]; + _tcsftime(ttime, SIZEOF(ttime), _T("%X"), localtime(×tamp)); + + JSONNODE *threadid = json_get(it, "tid"); + if (threadid != NULL) { // multi user chat + std::tstring tid = json_as_string(threadid); + std::string reader_id = json_as_pstring(reader); + + std::map::iterator chatroom = proto->facy.chat_rooms.find(tid); + if (chatroom != proto->facy.chat_rooms.end()) { + std::map::const_iterator participant = chatroom->second.participants.find(reader_id); + if (participant == chatroom->second.participants.end()) { + // TODO: load name of this participant + std::string name = reader_id; + proto->AddChatContact(tid.c_str(), reader_id.c_str(), name.c_str()); + } + + participant = chatroom->second.participants.find(reader_id); + if (participant != chatroom->second.participants.end()) { + MCONTACT hChatContact = proto->ChatIDToHContact(tid); + + if (!chatroom->second.message_readers.empty()) + chatroom->second.message_readers += ", "; + chatroom->second.message_readers += participant->second; + + ptrT readers(mir_utf8decodeT(chatroom->second.message_readers.c_str())); + + StatusTextData st = { 0 }; + st.cbSize = sizeof(st); + st.hIcon = Skin_GetIconByHandle(GetIconHandle("read")); + + mir_sntprintf(st.tszText, SIZEOF(st.tszText), TranslateT("Message read: %s by %s"), ttime, readers); + + CallService(MS_MSG_SETSTATUSTEXT, (WPARAM)hChatContact, (LPARAM)&st); + } + } + } else { // classic contact + MCONTACT hContact = proto->ContactIDToHContact(json_as_pstring(reader)); + if (hContact) { + proto->facy.readers.insert(std::make_pair(hContact, timestamp)); + proto->MessageRead(hContact); + } + } + } else if (t == "deliver") { + // inbox message (multiuser or direct) + + JSONNODE *msg = json_get(it, "message"); + JSONNODE *folder = json_get(it, "folder"); + + if (inboxOnly && json_as_pstring(folder) != "inbox") + continue; + + JSONNODE *sender_fbid = json_get(msg, "sender_fbid"); + JSONNODE *sender_name = json_get(msg, "sender_name"); + JSONNODE *body = json_get(msg, "body"); + JSONNODE *tid = json_get(msg, "tid"); + JSONNODE *mid = json_get(msg, "mid"); + JSONNODE *timestamp = json_get(msg, "timestamp"); + JSONNODE *filtered = json_get(it, "is_filtered_content"); + + if (sender_fbid == NULL || sender_name == NULL || body == NULL || mid == NULL || timestamp == NULL) + continue; + + std::string id = json_as_pstring(sender_fbid); + std::string message_id = json_as_pstring(mid); + std::string message_text = json_as_pstring(body); + + // Process attachements and stickers + parseAttachments(proto, &message_text, msg); + + // Ignore duplicits or messages sent from miranda + if (body == NULL || ignore_duplicits(proto, message_id, message_text)) + continue; + + if (json_as_bool(filtered) && 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::special_expressions_decode(utils::text::slashu_to_utf8(message_text)), true); + if (message_text.empty()) + continue; + + facebook_message* message = new facebook_message(); + message->isUnread = true; + message->isIncoming = (id != proto->facy.self_.user_id); + message->message_text = message_text; + message->time = utils::time::fix_timestamp(json_as_float(timestamp)); + message->user_id = id; + message->message_id = message_id; + message->sender_name = utils::text::special_expressions_decode(utils::text::slashu_to_utf8(json_as_pstring(sender_name))); // TODO: or if not incomming use my own name from facy.self_ ? + message->thread_id = json_as_pstring(tid); // TODO: or if not incomming use my own id from facy.self_ ? + + JSONNODE *gthreadinfo = json_get(msg, "group_thread_info"); + message->isChat = (gthreadinfo != NULL); + + if (!message->isChat && !message->isIncoming) { + message->sender_name = ""; + message->user_id = proto->ThreadIDToContactID(message->thread_id); // TODO: Check if we have contact with this user_id in friendlist and otherwise do something different? + } + + messages->push_back(message); + } + } else if (t == "notification_json") { + // event notification + JSONNODE *nodes = json_get(it, "nodes"); + + for (unsigned int j = 0; j < json_size(nodes); j++) { + JSONNODE *itNodes = json_at(nodes, j); + + //JSONNODE *text = json_get(itNodes, "title/text"); // use this when popups will be ready to allow changes of their content + JSONNODE *text_ = json_get(itNodes, "unaggregatedTitle"); // notifications one by one, not grouped + if (text_ == NULL) + continue; + JSONNODE *text = json_get(text_, "text"); + JSONNODE *url = json_get(itNodes, "url"); + JSONNODE *alert_id = json_get(itNodes, "alert_id"); + + JSONNODE *time_ = json_get(itNodes, "timestamp"); + if (time_ == NULL) + continue; + JSONNODE *time = json_get(time_, "time"); + if (time == NULL || text == NULL || url == NULL || alert_id == NULL) + continue; + + unsigned __int64 timestamp = json_as_float(time); + if (timestamp > proto->facy.last_notification_time_) { + // Only new notifications + proto->facy.last_notification_time_ = timestamp; + + facebook_notification* notification = new facebook_notification(); + notification->text = utils::text::slashu_to_utf8(json_as_pstring(text)); + notification->link = utils::text::special_expressions_decode(json_as_pstring(url)); + notification->id = json_as_pstring(alert_id); + + std::string::size_type pos = notification->id.find(":"); + if (pos != std::string::npos) + notification->id = notification->id.substr(pos+1); + + if (notifications->find(notification->id) == notifications->end()) + notifications->insert(std::make_pair(notification->id, notification)); + else + delete notification; + } + } + } else if (t == "typ") { + // chat typing notification + + JSONNODE *from = json_get(it, "from"); + if (from == NULL) + continue; + + facebook_user fbu; + fbu.user_id = json_as_pstring(from); + + MCONTACT hContact = proto->AddToContactList(&fbu, CONTACT_FRIEND); // only friends are able to send typing notifications + + JSONNODE *st = json_get(it, "st"); + if (json_as_int(st) == 1) + proto->StartTyping(hContact); + else + proto->StopTyping(hContact); + } else if (t == "ttyp") { + // multi chat typing notification + + JSONNODE *from_ = json_get(it, "from"); + JSONNODE *thread_ = json_get(it, "thread"); + JSONNODE *st_ = json_get(it, "st"); + + if (from_ == NULL || thread_ == NULL || st_ == NULL) + continue; + + std::tstring tid = json_as_string(thread_); + std::string from_id = json_as_pstring(from_); + + std::map::iterator chatroom = proto->facy.chat_rooms.find(tid); + if (chatroom != proto->facy.chat_rooms.end()) { + std::map::const_iterator participant = chatroom->second.participants.find(from_id); + if (participant == chatroom->second.participants.end()) { + // TODO: load name of this participant + std::string name = from_id; + proto->AddChatContact(tid.c_str(), from_id.c_str(), name.c_str()); + } + + participant = chatroom->second.participants.find(from_id); + if (participant != chatroom->second.participants.end()) { + MCONTACT hChatContact = proto->ChatIDToHContact(tid); + ptrT name(mir_utf8decodeT(participant->second.c_str())); + + if (json_as_int(st_) == 1) { + StatusTextData st = { 0 }; + st.cbSize = sizeof(st); + + mir_sntprintf(st.tszText, SIZEOF(st.tszText), TranslateT("%s is typing a message..."), name); + + CallService(MS_MSG_SETSTATUSTEXT, (WPARAM)hChatContact, (LPARAM)&st); + } + else { + CallService(MS_MSG_SETSTATUSTEXT, (WPARAM)hChatContact, NULL); + } + + // TODO: support proper MS_PROTO_CONTACTISTYPING service for chatrooms (when it will be implemented) + } + } + } else if (t == "privacy_changed") { + // settings changed + + JSONNODE *event_type = json_get(it, "event"); + JSONNODE *event_data = json_get(it, "data"); + + if (event_type == NULL || event_data == NULL) + continue; + + std::string t = json_as_pstring(event_type); + if (t == "visibility_update") { + // change of chat status + JSONNODE *visibility = json_get(event_data, "visibility"); + + bool isVisible = (visibility != NULL) && json_as_bool(visibility); + proto->debugLogA(" Requested chat switch to %s", isVisible ? "Online" : "Offline"); + proto->SetStatus(isVisible ? ID_STATUS_ONLINE : ID_STATUS_INVISIBLE); + } + } else if (t == "buddylist_overlay") { + // we opened/closed chat window - pretty useless info for us + continue; + } else if (t == "ticker_update:home") { + JSONNODE *actor_ = json_get(it, "actor"); + JSONNODE *time_ = json_get(it, "actor"); + JSONNODE *story_ = json_get(it, "story_xhp"); + + time_t time = json_as_float(time_); + std::string text = json_as_pstring(story_); + text = utils::text::slashu_to_utf8(text); + + text = utils::text::trim( + utils::text::special_expressions_decode( + utils::text::source_get_value(&text, 3, "", ""))); + + // TODO: notify ticker updates + /* if (!text.empty()) { + proto->NotifyEvent() + }*/ + } else if (t == "mercury") { + // rename multi user chat, ... + + JSONNODE *actions_ = json_get(it, "actions"); + if (actions_ == NULL) + continue; + + for (unsigned int i = 0; i < json_size(actions_); i++) { + JSONNODE *action_ = json_at(actions_, i); + + JSONNODE *thread_id_ = json_get(action_, "thread_id"); + JSONNODE *log_body_ = json_get(action_, "log_message_body"); + JSONNODE *log_data_ = json_get(action_, "log_message_data"); + if (!log_data_ || !thread_id_) + continue; + + JSONNODE *name_ = json_get(log_data_, "name"); + std::string name = json_as_pstring(name_); + std::tstring thread_id = json_as_string(thread_id_); + std::string message = json_as_pstring(log_body_); + + // proto->RenameChat(thread_id.c_str(), name.c_str()); // this don't work, why? + proto->setStringUtf(proto->ChatIDToHContact(thread_id), FACEBOOK_KEY_NICK, name.c_str()); + + proto->UpdateChat(thread_id.c_str(), NULL, NULL, message.c_str()); + } + } else if (t == "notifications_read" || t == "notifications_seen") { + JSONNODE *alerts = json_get(it, "alert_ids"); + + for (unsigned int j = 0; j < json_size(alerts); j++) { + JSONNODE *itAlerts = json_at(alerts, j); + std::string id = json_as_pstring(itAlerts); + + std::map::iterator it = notifications->find(id); + if (it != notifications->end()) { + if (it->second->hWndPopup != NULL) + PUDeletePopup(it->second->hWndPopup); // close popup + + delete it->second; + notifications->erase(it); + } + } + } else + continue; + } + + json_delete(root); + return EXIT_SUCCESS; +} + +int facebook_json_parser::parse_unread_threads(void* data, std::vector< std::string >* threads, bool inboxOnly) +{ + 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"); + + if (inboxOnly && json_as_pstring(folder) != "inbox") + continue; + + for (unsigned int j = 0; j < json_size(thread_ids); j++) { + JSONNODE *id = json_at(thread_ids, j); + threads->push_back(json_as_pstring(id)); + } + } + + json_delete(root); + return EXIT_SUCCESS; +} + +int facebook_json_parser::parse_thread_messages(void* data, std::vector< facebook_message* >* messages, std::map< std::string, facebook_chatroom* >* chatrooms, bool unreadOnly, bool inboxOnly, 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; + } + + JSONNODE *roger = json_get(payload, "roger"); + if (roger != NULL) { + for (unsigned int i = 0; i < json_size(roger); i++) { + JSONNODE *it = json_at(roger, i); + std::tstring id = _A2T(json_name(it)); + + // Ignore "wrong" (duplicit) identifiers - these that doesn't begin with "id." + if (id.substr(0, 3) == _T("id.")) { + facebook_chatroom *room = new facebook_chatroom(); + room->thread_id = id; + + chatrooms->insert(std::make_pair((char*)_T2A(room->thread_id.c_str()), room)); + } + } + } + + std::map thread_ids; + for (unsigned int i = 0; i < json_size(threads); i++) { + JSONNODE *it = json_at(threads, i); + JSONNODE *is_canonical_user = json_get(it, "is_canonical_user"); + JSONNODE *canonical = json_get(it, "canonical_fbid"); + JSONNODE *thread_id = json_get(it, "thread_id"); + JSONNODE *name = json_get(it, "name"); + //JSONNODE *message_count = json_get(it, "message_count"); + JSONNODE *unread_count = json_get(it, "unread_count"); // TODO: use it to check against number of loaded messages... but how? + JSONNODE *folder = json_get(it, "folder"); + + if (canonical == NULL || thread_id == NULL) + continue; + + std::string id = json_as_pstring(canonical); + std::string tid = json_as_pstring(thread_id); + + std::map::iterator iter = chatrooms->find(tid); + if (iter != chatrooms->end()) { + if (json_as_bool(is_canonical_user)) { + chatrooms->erase(iter); // this is not chatroom + } else { + iter->second->chat_name = json_as_string(name); // TODO: create name from users if there is no name... + + JSONNODE *participants = json_get(it, "participants"); + for (unsigned int j = 0; j < json_size(participants); j++) { + JSONNODE *jt = json_at(it, j); + std::string user_id = json_name(jt); + iter->second->participants.insert(std::make_pair(user_id, user_id)); // TODO: get name somehow + } + } + } + + iter = chatrooms->find(id); + if (iter != chatrooms->end()) { + chatrooms->erase(iter); // this is not chatroom + } + + if (inboxOnly && json_as_pstring(folder) != "inbox") + continue; + + if (id == "null") + continue; + + 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"); + JSONNODE *filtered = json_get(it, "is_filtered_content"); + JSONNODE *folder = json_get(it, "folder"); + JSONNODE *is_unread = json_get(it, "is_unread"); + + if (author == NULL || body == NULL || mid == NULL || tid == NULL || timestamp == NULL) + continue; + + if (inboxOnly && json_as_pstring(folder) != "inbox") + continue; + + std::string thread_id = json_as_pstring(tid); + std::string message_id = json_as_pstring(mid); + std::string message_text = json_as_pstring(body); + std::string author_id = json_as_pstring(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); + + if (json_as_bool(filtered) && 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::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_pstring(author_email); + message->time = utils::time::fix_timestamp(json_as_float(timestamp)); + message->thread_id = thread_id; + message->message_id = message_id; + message->isIncoming = (author_id != proto->facy.self_.user_id); + message->isUnread = (is_unread != NULL && json_as_bool(is_unread)); + + // Ignore read messages if we want only unread messages + if (unreadOnly && !message->isUnread) + continue; + + std::map::iterator iter = chatrooms->find(thread_id); + if (iter != chatrooms->end()) { + // this is chatroom message + message->isChat = true; + message->user_id = author_id; + } else { + // this is standard message + message->isChat = false; + std::map::iterator iter = thread_ids.find(thread_id); + if (iter != thread_ids.end()) { + message->user_id = iter->second; // TODO: Check if we have contact with this ID in friendlist and otherwise do something different? + } else { + continue; + } + } + + messages->push_back(message); + } + + json_delete(root); + return EXIT_SUCCESS; +} + +int facebook_json_parser::parse_thread_info(void* data, std::string* user_id) +{ + 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 *threads = json_get(payload, "threads"); + if (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 *message_count = json_get(it, "message_count"); // TODO: this could be useful for loading history from server + + if (canonical == NULL || thread_id == NULL) + continue; + + std::string id = json_as_pstring(canonical); + if (id == "null") + continue; + + *user_id = id; + } + + json_delete(root); + return EXIT_SUCCESS; +} + + +int facebook_json_parser::parse_user_info(void* data, facebook_user* fbu) +{ + 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 *profiles = json_get(payload, "profiles"); + if (profiles == NULL) { + json_delete(root); + return EXIT_FAILURE; + } + + std::map user_ids; + for (unsigned int i = 0; i < json_size(profiles); i++) { + JSONNODE *it = json_at(profiles, i); + + // TODO: allow more users to parse at once + std::string id = json_name(it); + + if (fbu->user_id == id) { + parseUser(it, fbu); + break; + } + } + + json_delete(root); + return EXIT_SUCCESS; +} + +/* TODO: implement this somehow +int facebook_json_parser::parse_chat_info(void* data, facebook_chat* fbu) { + // This is a recent 5 person listing of participants. + JSONNODE *participants_ids = json_get(gthreadinfo, "participant_ids"); + JSONNODE *participants_names = json_get(gthreadinfo, "participant_names"); + + JSONNODE *thread_name_ = json_get(gthreadinfo, "name"); + std::tstring name = json_as_string(thread_name_); + + // if there is no name for this room, set own name + if (name.empty()) { + unsigned int namesCount = 3; // how many names should be in room name; max. 5 + + for (unsigned int n = 0; n < json_size(participants_names) && n < namesCount; n++) { + JSONNODE *nameItr = json_at(participants_names, n); + + if (!name.empty()) + name += _T(", "); + + name += json_as_string(nameItr); + } + JSONNODE *count_ = json_get(gthreadinfo, "participant_total_count"); + unsigned int count = json_as_int(count_); + + if (count > namesCount) { + TCHAR more[200]; + mir_sntprintf(more, SIZEOF(more), TranslateT("%s and more (%d)"), name.c_str(), count - namesCount); + name = more; + } + } + + if (name.empty()) + name = thread_id; + + for (unsigned int n = 0; n < json_size(participants_ids); n++) { + JSONNODE *idItr = json_at(participants_ids, n); + std::string pId = json_as_pstring(idItr); + + JSONNODE *nameItr = json_at(participants_names, n); + std::string pName = json_as_pstring(nameItr); + + if (!proto->IsChatContact(thread_id.c_str(), pId.c_str())) { + proto->AddChatContact(thread_id.c_str(), pId.c_str(), pName.c_str()); + } + } + + std::string senderName = json_as_pstring(sender_name); + std::string::size_type pos; + if ((pos = senderName.find(" ")) != std::string::npos) { + senderName = senderName.substr(0, pos); + } + + // Last fall-back for adding this sender (in case he was not in the participants) - is this even needed? + if (!proto->IsChatContact(thread_id.c_str(), id.c_str())) { + proto->AddChatContact(thread_id.c_str(), id.c_str(), senderName.c_str()); + } +} */ \ No newline at end of file -- cgit v1.2.3