int facebook_json_parser::parse_messages(std::string *data, std::vector< facebook_message* >* messages, std::map< std::string, facebook_notification* >* notifications, bool inboxOnly) { // remove old received messages from map for (std::map<std::string, int>::iterator it = proto->facy.messages_ignore.begin(); it != proto->facy.messages_ignore.end();) { if (it->second > FACEBOOK_IGNORE_COUNTER_LIMIT) { it = proto->facy.messages_ignore.erase(it); } else { it->second++; // increase counter on each request ++it; } } std::string jsonData = data->substr(9); JSONNode root = JSONNode::parse(jsonData.c_str()); if (!root) return EXIT_FAILURE; const JSONNode &ms = root["ms"]; if (!ms) return EXIT_FAILURE; for (auto it = ms.begin(); it != ms.end(); ++it) { const JSONNode &type = (*it)["type"]; if (!type) continue; std::string t = type.as_string(); if (t == "messaging") { // various messaging stuff (received and sent messages, getting seen info) const JSONNode &type = (*it)["event"]; if (!type) continue; std::string t = type.as_string(); if (t == "read_receipt") { // user read message const JSONNode &reader_ = (*it)["reader"]; const JSONNode &time_ = (*it)["time"]; if (!reader_ || !time_) continue; time_t timestamp = utils::time::from_string(time_.as_string()); MCONTACT hContact = NULL; std::tstring reader; std::string readerId = reader_.as_string(); const JSONNode &threadid = (*it)["tid"]; if (!threadid) { // classic contact hContact = proto->ContactIDToHContact(readerId); } else { // multi user chat if (!proto->m_enableChat) continue; std::string tid = threadid.as_string(); std::map<std::string, facebook_chatroom*>::iterator it = proto->facy.chat_rooms.find(tid); if (it != proto->facy.chat_rooms.end()) { facebook_chatroom *chatroom = it->second; std::map<std::string, std::string> participants = chatroom->participants; std::map<std::string, std::string>::const_iterator participant = participants.find(readerId); if (participant == participants.end()) { // TODO: load name of this participant std::string name = readerId; participants.insert(std::make_pair(readerId, name)); proto->AddChatContact(tid.c_str(), readerId.c_str(), name.c_str()); } participant = participants.find(readerId); if (participant != participants.end()) { // TODO: remember just reader ids to avoid eventual duplication of names reader = _A2T(participant->second.c_str(), CP_UTF8); hContact = proto->ChatIDToHContact(tid); } } } if (hContact) proto->facy.insert_reader(hContact, timestamp, reader); } else if (t == "deliver") { // inbox message (multiuser or direct) const JSONNode &msg = (*it)["message"]; const JSONNode &folder = (*it)["folder"]; if (inboxOnly && folder.as_string() != "inbox") continue; const JSONNode &sender_fbid = msg["sender_fbid"]; const JSONNode &sender_name = msg["sender_name"]; const JSONNode &body = msg["body"]; // looks like there is either "tid" or "other_user_fbid" (or both) const JSONNode &tid = msg["tid"]; const JSONNode &mid = msg["mid"]; const JSONNode ×tamp = msg["timestamp"]; if (!sender_fbid || !sender_name || !body || !mid || !timestamp) continue; const JSONNode &is_filtered = (*it)["is_filtered_content"]; // TODO: is it still here? perhaps it is replaced with msg["is_spoof_warning"] or something else? //const JSONNode &is_spoof_warning = msg["is_spoof_warning"]; //const JSONNode &is_silent = msg["is_silent"]; //const JSONNode &is_unread = msg["is_unread"]; std::string id = sender_fbid.as_string(); std::string message_id = mid.as_string(); std::string message_text = body.as_string(); std::string thread_id = tid.as_string(); std::string other_user_id = msg["other_user_fbid"].as_string(); // Process attachements and stickers parseAttachments(proto, &message_text, msg, thread_id, other_user_id); // Ignore duplicits or messages sent from miranda if (!body || ignore_duplicits(proto, message_id, message_text)) continue; if (is_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()) 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::from_string(timestamp.as_string()); message->user_id = id; message->message_id = message_id; message->sender_name = utils::text::slashu_to_utf8(sender_name.as_string()); // TODO: or if not incomming use my own name from facy.self_ ? message->thread_id = tid.as_string(); // TODO: or if not incomming use my own id from facy.self_ ? const JSONNode >hreadinfo = msg["group_thread_info"]; message->isChat = (gthreadinfo && gthreadinfo.as_string() != "null"); if (!message->isChat && !message->isIncoming) { message->sender_name.clear(); message->user_id = !other_user_id.empty() ? other_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 const JSONNode &nodes = (*it)["nodes"]; // Create notifications chatroom (if it doesn't exists), because we will be writing to it new notifications here proto->PrepareNotificationsChatRoom(); for (auto itNodes = nodes.begin(); itNodes != nodes.end(); ++itNodes) { const JSONNode &text_ = (*itNodes)["unaggregatedTitle"]; // notifications one by one, not grouped if (!text_) continue; const JSONNode &text = text_["text"]; const JSONNode &url = (*itNodes)["url"]; const JSONNode &alert_id = (*itNodes)["alert_id"]; const JSONNode &time_ = (*itNodes)["timestamp"]; if (!time_) continue; const JSONNode &time = time_["time"]; if (!time || !text || !url || !alert_id) continue; time_t timestamp = utils::time::from_string(time.as_string()); 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(text.as_string()); notification->link = url.as_string(); notification->id = alert_id.as_string(); notification->time = timestamp; // Fix notification ID std::string::size_type pos = notification->id.find(":"); if (pos != std::string::npos) notification->id = notification->id.substr(pos + 1); // Write notification to chatroom proto->UpdateNotificationsChatRoom(notification); // If it's unseen, remember it, otherwise forget it (here it will always be unseen) if (notifications->find(notification->id) == notifications->end() && !notification->seen) notifications->insert(std::make_pair(notification->id, notification)); else delete notification; } } } else if (t == "m_notification") { const JSONNode &data = (*it)["data"]; if (!data) continue; const JSONNode &appId_ = data["app_id"]; const JSONNode &type_ = data["type"]; if (appId_.as_string() == "2356318349" || type_.as_string() == "friend_confirmed") { // Friendship notifications const JSONNode &body_ = data["body"]; const JSONNode &html_ = body_["__html"]; const JSONNode &href_ = data["href"]; const JSONNode &unread_ = data["unread"]; const JSONNode &alertId_ = data["alert_id"]; if (!html_ || !href_ || !unread_ || unread_.as_int() == 0) continue; std::string text = utils::text::remove_html(utils::text::slashu_to_utf8(html_.as_string())); std::string url = href_.as_string(); std::string alert_id = alertId_.as_string(); proto->NotifyEvent(proto->m_tszUserName, ptrT(mir_utf8decodeT(text.c_str())), NULL, FACEBOOK_EVENT_FRIENDSHIP, &url, alert_id.empty() ? NULL : &alert_id); } } else if (t == "jewel_requests_add") { // New friendship request, load them all with real names (because there is only user_id in "from" field) proto->ForkThread(&FacebookProto::ProcessFriendRequests, NULL); } else if (t == "typ") { // chat typing notification const JSONNode &from = (*it)["from"]; if (!from) continue; facebook_user fbu; fbu.user_id = from.as_string(); MCONTACT hContact = proto->AddToContactList(&fbu, CONTACT_FRIEND); // only friends are able to send typing notifications const JSONNode &st = (*it)["st"]; if (st.as_int() == 1) proto->StartTyping(hContact); else proto->StopTyping(hContact); } else if (t == "ttyp") { // multi chat typing notification if (!proto->m_enableChat) continue; const JSONNode &from_ = (*it)["from"]; const JSONNode &thread_ = (*it)["thread"]; const JSONNode &st_ = (*it)["st"]; if (!from_ || !thread_ || !st_) continue; std::string tid = thread_.as_string(); std::string from_id = from_.as_string(); std::map<std::string, facebook_chatroom*>::iterator it = proto->facy.chat_rooms.find(thread_.as_string()); if (it != proto->facy.chat_rooms.end()) { facebook_chatroom *chatroom = it->second; std::map<std::string, std::string> participants = chatroom->participants; std::map<std::string, std::string>::const_iterator participant = participants.find(from_id); if (participant == 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 = participants.find(from_id); if (participant != participants.end()) { MCONTACT hChatContact = proto->ChatIDToHContact(tid); ptrT name(mir_utf8decodeT(participant->second.c_str())); if (st_.as_int() == 1) { StatusTextData st = { 0 }; st.cbSize = sizeof(st); mir_sntprintf(st.tszText, _countof(st.tszText), TranslateT("%s is typing a message..."), name); CallService(MS_MSG_SETSTATUSTEXT, (WPARAM)hChatContact, (LPARAM)&st); } else { CallService(MS_MSG_SETSTATUSTEXT, (WPARAM)hChatContact); } // TODO: support proper MS_PROTO_CONTACTISTYPING service for chatrooms (when it will be implemented) } } } else if (t == "privacy_changed") { // settings changed const JSONNode &event_type = (*it)["event"]; const JSONNode &event_data = (*it)["data"]; if (!event_type || !event_data) continue; std::string t = event_type.as_string(); if (t == "visibility_update") { // change of chat status const JSONNode &visibility = event_data["visibility"]; bool isVisible = visibility && visibility.as_bool(); 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") { const JSONNode &actor_ = (*it)["actor"]; const JSONNode &story_ = (*it)["story_xhp"]; std::string text = story_.as_string(); text = utils::text::html_entities_decode(utils::text::slashu_to_utf8(text)); std::string url = utils::text::source_get_value(&text, 3, "\"tickerStoryLink\"", "href=\"", "\""); std::string story_type = utils::text::source_get_value2(&text, "\"type\":\"", "\""); std::string story_class = utils::text::source_get_value2(&text, "\"entstory_class\":\"", "\""); text = utils::text::trim(utils::text::remove_html(text)); std::string userId = actor_.as_string(); MCONTACT hContact = proto->ContactIDToHContact(userId); proto->debugLogA("+++ Got ticker type='%s' class='%s'", story_type.c_str(), story_class.c_str()); if (!text.empty()) proto->NotifyEvent(proto->m_tszUserName, ptrT(mir_utf8decodeT(text.c_str())), hContact, FACEBOOK_EVENT_TICKER, &url); } else if (t == "mercury") { // rename multi user chat, video call, ... const JSONNode &actions_ = (*it)["actions"]; if (!actions_) continue; for (unsigned int i = 0; i < actions_.size(); i++) { const JSONNode &action_ = actions_[i]; const JSONNode &thread_id_ = action_["thread_id"]; const JSONNode &log_body_ = action_["log_message_body"]; const JSONNode &log_data_ = action_["log_message_data"]; const JSONNode &log_type_ = action_["log_message_type"]; if (!log_data_ || !log_body_ || !thread_id_ || !log_type_) continue; std::string thread_id = thread_id_.as_string(); std::string type = log_type_.as_string(); std::string message_text = log_body_.as_string(); if (type == "log:video-call") { std::string id = action_["other_user_fbid"].as_string(); std::string message_id = action_["message_id"].as_string(); facebook_message* message = new facebook_message(); message->isChat = false; message->isUnread = true; message->isIncoming = (id != proto->facy.self_.user_id); message->message_text = message_text; message->time = utils::time::from_string(action_["timestamp"].as_string()); message->user_id = id; message->message_id = message_id; message->sender_name.clear(); message->thread_id = thread_id; message->type = CALL; messages->push_back(message); } else { // TODO: check for other types, now we expect this is rename chat if (!proto->m_enableChat) continue; std::string name = log_data_["name"].as_string(); // 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_text.c_str()); } } } else if (t == "notifications_read" || t == "notifications_seen") { ScopedLock s(proto->facy.notifications_lock_); const JSONNode &alerts = (*it)["alert_ids"]; for (auto itAlerts = alerts.begin(); itAlerts != alerts.end(); ++itAlerts) { std::string id = (*itAlerts).as_string(); std::map<std::string, facebook_notification*>::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; } return EXIT_SUCCESS; }
int facebook_json_parser::parse_thread_messages(std::string *data, std::vector< facebook_message* >* messages, std::map< std::string, facebook_chatroom* >* chatrooms, bool unreadOnly, bool inboxOnly) { 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"]; const JSONNode &threads = payload["threads"]; if (!actions || !threads) return EXIT_FAILURE; const JSONNode &roger = payload["roger"]; for (auto it = roger.begin(); it != roger.end(); ++it) { std::string id = (*it).name(); // Ignore "wrong" (duplicit) identifiers - these that doesn't begin with "id." if (id.substr(0, 3) == "id.") { facebook_chatroom *room = new facebook_chatroom(id); chatrooms->insert(std::make_pair(id, room)); } } std::map<std::string, std::string> thread_ids; for (auto it = threads.begin(); it != threads.end(); ++it) { const JSONNode &is_canonical_user = (*it)["is_canonical_user"]; const JSONNode &canonical = (*it)["canonical_fbid"]; const JSONNode &thread_id = (*it)["thread_id"]; const JSONNode &name = (*it)["name"]; //const JSONNode &message_count = (*it)["message_count"); //const JSONNode &unread_count = (*it)["unread_count"); // TODO: use it to check against number of loaded messages... but how? const JSONNode &folder = (*it)["folder"]; if (!canonical || !thread_id) continue; std::string id = canonical.as_string(); std::string tid = thread_id.as_string(); std::map<std::string, facebook_chatroom*>::iterator iter = chatrooms->find(tid); if (iter != chatrooms->end()) { if (is_canonical_user.as_bool()) { chatrooms->erase(iter); // this is not chatroom } else { iter->second->chat_name = std::tstring(ptrT(mir_utf8decodeT(name.as_string().c_str()))); // TODO: create name from users if there is no name... const JSONNode &participants = (*it)["participants"]; for (auto jt = participants.begin(); jt != participants.end(); ++jt) { std::string user_id = (*jt).name(); 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 && folder.as_string() != "inbox") continue; if (id == "null") continue; thread_ids.insert(std::make_pair(tid, id)); } for (auto it = actions.begin(); it != actions.end(); ++it) { const JSONNode &author = (*it)["author"]; const JSONNode &author_email = (*it)["author_email"]; 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 &folder = (*it)["folder"]; const JSONNode &is_unread = (*it)["is_unread"]; if (!author || !body || !mid || !tid || !timestamp) continue; if (inboxOnly && folder.as_string() != "inbox") continue; 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::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, thread_id, ""); // FIXME: is here supported other_user_fbid ? 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()) continue; bool isUnread = is_unread.as_bool(); // Ignore read messages if we want only unread messages if (unreadOnly && !isUnread) continue; facebook_message* message = new facebook_message(); message->message_text = message_text; if (author_email) message->sender_name = author_email.as_string(); 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 = isUnread; std::map<std::string, facebook_chatroom*>::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<std::string, std::string>::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 { delete message; continue; } } messages->push_back(message); } return EXIT_SUCCESS; }
int facebook_json_parser::parse_messages(std::string *pData, std::vector<facebook_message>* messages, std::map< std::string, facebook_notification* >* notifications) { // remove old received messages from map for (std::map<std::string, int>::iterator it = proto->facy.messages_ignore.begin(); it != proto->facy.messages_ignore.end();) { if (it->second > FACEBOOK_IGNORE_COUNTER_LIMIT) { it = proto->facy.messages_ignore.erase(it); } else { it->second++; // increase counter on each request ++it; } } JSONNode root = JSONNode::parse(pData->substr(9).c_str()); if (!root) return EXIT_FAILURE; const JSONNode &ms = root["ms"]; if (!ms) return EXIT_FAILURE; for (auto it = ms.begin(); it != ms.end(); ++it) { const JSONNode &type = (*it)["type"]; if (!type) continue; std::string t = type.as_string(); if (t == "delta") { // new messaging stuff const JSONNode &delta_ = (*it)["delta"]; if (!delta_) continue; const JSONNode &cls_ = delta_["class"]; std::string cls = cls_.as_string(); if (cls == "NewMessage") { const JSONNode &meta_ = delta_["messageMetadata"]; if (!meta_) { proto->debugLogA("json::parse_messages - No messageMetadata element"); continue; } const JSONNode &sender_fbid = meta_["actorFbId"]; // who send the message const JSONNode &body = delta_["body"]; // message text, could be empty if there is only attachment (or sticker) const JSONNode &mid = meta_["messageId"]; const JSONNode ×tamp = meta_["timestamp"]; if (!sender_fbid || !mid || !timestamp) continue; std::string id = sender_fbid.as_string(); std::string message_id = mid.as_string(); std::string message_text = body.as_string(); const JSONNode &other_user_id_ = meta_["threadKey"]["otherUserFbId"]; // for whom the message is (only for single conversations) const JSONNode &thread_fbid_ = meta_["threadKey"]["threadFbId"]; // thread of the message (only for multi chat conversations) std::string other_user_id = other_user_id_ ? other_user_id_.as_string() : ""; std::string thread_id = !other_user_id_ && thread_fbid_ ? "id." + thread_fbid_.as_string() : ""; // NOTE: we must add "id." prefix as this is threadFbId and we want threadId (but only for multi chats) // Process attachements and stickers parseAttachments(proto, &message_text, delta_, other_user_id, false); // Ignore duplicits or messages sent from miranda if (ignore_duplicits(proto, message_id, message_text)) continue; message_text = utils::text::trim(message_text, true); facebook_message message; message.isChat = other_user_id.empty(); message.isIncoming = (id != proto->facy.self_.user_id); message.isUnread = message.isIncoming; message.message_text = message_text; message.time = utils::time::from_string(timestamp.as_string()); message.user_id = message.isChat ? id : other_user_id; message.message_id = message_id; message.thread_id = thread_id; messages->push_back(message); } else if (cls == "ReadReceipt") { // user read message // when read const JSONNode &time_ = delta_["actionTimestampMs"]; if (!time_) continue; time_t timestamp = utils::time::from_string(time_.as_string()); // for multi chats (not available for single) const JSONNode &actor_ = delta_["actorFbId"]; // who read the message const JSONNode &thread_ = delta_["threadKey"]["threadFbId"]; // chat thread // for single chats (not available for multi) const JSONNode &reader_ = delta_["threadKey"]["otherUserFbId"]; // who read the message if (actor_ && thread_) { // multi chat // ignore if disabled if (!proto->m_enableChat) continue; std::string readerId = actor_.as_string(); std::string tid = "id." + thread_.as_string(); // NOTE: threadFbId means just numeric id of thread, without "id." prefix. We add it here to have it consistent with other methods (where threadId is used) MCONTACT hContact = proto->ChatIDToHContact(tid); proto->facy.insert_reader(hContact, timestamp, readerId); } else if (reader_) { // single chat std::string userId = reader_.as_string(); MCONTACT hContact = proto->ContactIDToHContact(userId); proto->facy.insert_reader(hContact, timestamp); } } else if (cls == "NoOp") { // contains numNoOps=1 (or probably other number) in delta element, but I don't know what is it for continue; } else { // DeliveryReceipt, MarkRead, ThreadDelete proto->debugLogA("json::parse_messages - Unknown class '%s'", cls.c_str()); } } else if (t == "messaging") { // various messaging stuff (received and sent messages, getting seen info) const JSONNode &ev_ = (*it)["event"]; if (!ev_) continue; std::string ev = ev_.as_string(); if (ev == "deliver") { // inbox message (multiuser or direct) const JSONNode &msg = (*it)["message"]; const JSONNode &sender_fbid = msg["sender_fbid"]; const JSONNode &sender_name = msg["sender_name"]; const JSONNode &body = msg["body"]; // looks like there is either "tid" or "other_user_fbid" (or both) const JSONNode &tid = msg["tid"]; const JSONNode &mid = msg["mid"]; const JSONNode ×tamp = msg["timestamp"]; if (!sender_fbid || !sender_name || !body || !mid || !timestamp) continue; const JSONNode &is_filtered = (*it)["is_filtered_content"]; // TODO: is it still here? perhaps it is replaced with msg["is_spoof_warning"] or something else? //const JSONNode &is_spoof_warning = msg["is_spoof_warning"]; //const JSONNode &is_silent = msg["is_silent"]; //const JSONNode &is_unread = msg["is_unread"]; std::string id = sender_fbid.as_string(); std::string message_id = mid.as_string(); std::string message_text = body.as_string(); std::string thread_id = tid.as_string(); std::string other_user_id = msg["other_user_fbid"].as_string(); // Process attachements and stickers parseAttachments(proto, &message_text, msg, other_user_id, true); // Ignore duplicits or messages sent from miranda if (!body || ignore_duplicits(proto, message_id, message_text)) continue; if (is_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(message_text, true); facebook_message message; message.isChat = other_user_id.empty(); message.isIncoming = (id != proto->facy.self_.user_id); message.isUnread = message.isIncoming; message.message_text = message_text; message.time = utils::time::from_string(timestamp.as_string()); message.user_id = (!message.isChat && !message.isIncoming) ? other_user_id : id; message.message_id = message_id; message.thread_id = thread_id; if (message.user_id.empty()) { proto->debugLogA(" !!! JSON: deliver message event with empty user_id (thread_id %s)\n%s", message.thread_id.empty() ? "empty too" : "exists", (*it).as_string().c_str()); if (!message.thread_id.empty()) { 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 const JSONNode &nodes = (*it)["nodes"]; // Create notifications chatroom (if it doesn't exists), because we will be writing to it new notifications here proto->PrepareNotificationsChatRoom(); for (auto itNodes = nodes.begin(); itNodes != nodes.end(); ++itNodes) { const JSONNode &text_ = (*itNodes)["unaggregatedTitle"]; // notifications one by one, not grouped if (!text_) continue; const JSONNode &text = text_["text"]; const JSONNode &url = (*itNodes)["url"]; const JSONNode &alert_id = (*itNodes)["alert_id"]; const JSONNode &icon_ = (*itNodes)["icon"]["uri"]; const JSONNode &time_ = (*itNodes)["timestamp"]; if (!time_) continue; const JSONNode &time = time_["time"]; if (!time || !text || !url || !alert_id) continue; time_t timestamp = utils::time::from_string(time.as_string()); 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(text.as_string()); notification->link = url.as_string(); notification->id = alert_id.as_string(); notification->time = timestamp; notification->setIcon(icon_.as_string()); // Fix notification ID std::string::size_type pos = notification->id.find(":"); if (pos != std::string::npos) notification->id = notification->id.substr(pos + 1); // Write notification to chatroom proto->UpdateNotificationsChatRoom(notification); // If it's unseen, remember it, otherwise forget it (here it will always be unseen) if (notifications->find(notification->id) == notifications->end() && !notification->seen) notifications->insert(std::make_pair(notification->id, notification)); else delete notification; } } } else if (t == "m_notification") { const JSONNode &data = (*it)["data"]; if (!data) continue; const JSONNode &appId_ = data["app_id"]; const JSONNode &type_ = data["type"]; if (appId_.as_string() == "2356318349" || type_.as_string() == "friend_confirmed") { // Friendship notifications const JSONNode &body_ = data["body"]; const JSONNode &html_ = body_["__html"]; const JSONNode &href_ = data["href"]; const JSONNode &unread_ = data["unread"]; const JSONNode &alertId_ = data["alert_id"]; if (!html_ || !href_ || !unread_ || unread_.as_int() == 0) continue; std::string text = utils::text::remove_html(utils::text::slashu_to_utf8(html_.as_string())); std::string url = href_.as_string(); std::string alert_id = alertId_.as_string(); // Notify it, if user wants to be notified if (proto->getByte(FACEBOOK_KEY_EVENT_FRIENDSHIP_ENABLE, DEFAULT_EVENT_FRIENDSHIP_ENABLE)) { proto->NotifyEvent(proto->m_tszUserName, ptrT(mir_utf8decodeT(text.c_str())), NULL, FACEBOOK_EVENT_FRIENDSHIP, &url, alert_id.empty() ? NULL : &alert_id); } } } else if (t == "jewel_requests_add") { // New friendship request, load them all with real names (because there is only user_id in "from" field) proto->ForkThread(&FacebookProto::ProcessFriendRequests, NULL); } else if (t == "typ") { // chat typing notification const JSONNode &from = (*it)["from"]; if (!from) continue; facebook_user fbu; fbu.user_id = from.as_string(); fbu.type = CONTACT_FRIEND; // only friends are able to send typing notifications MCONTACT hContact = proto->AddToContactList(&fbu); const JSONNode &st = (*it)["st"]; if (st.as_int() == 1) proto->StartTyping(hContact); else proto->StopTyping(hContact); } else if (t == "ttyp") { // multi chat typing notification if (!proto->m_enableChat) continue; const JSONNode &from_ = (*it)["from"]; const JSONNode &thread_ = (*it)["thread"]; const JSONNode &st_ = (*it)["st"]; if (!from_ || !thread_ || !st_) continue; std::string tid = thread_.as_string(); std::string from_id = from_.as_string(); auto itRoom = proto->facy.chat_rooms.find(thread_.as_string()); if (itRoom != proto->facy.chat_rooms.end()) { facebook_chatroom *chatroom = itRoom->second; std::map<std::string, std::string> participants = chatroom->participants; auto participant = participants.find(from_id); if (participant == 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 = participants.find(from_id); if (participant != participants.end()) { MCONTACT hChatContact = proto->ChatIDToHContact(tid); ptrT name(mir_utf8decodeT(participant->second.c_str())); if (st_.as_int() == 1) { StatusTextData st = { 0 }; st.cbSize = sizeof(st); mir_sntprintf(st.tszText, TranslateT("%s is typing a message..."), name); CallService(MS_MSG_SETSTATUSTEXT, (WPARAM)hChatContact, (LPARAM)&st); } else CallService(MS_MSG_SETSTATUSTEXT, (WPARAM)hChatContact); // TODO: support proper MS_PROTO_CONTACTISTYPING service for chatrooms (when it will be implemented) } } } else if (t == "privacy_changed") { // settings changed const JSONNode &event_type = (*it)["event"]; const JSONNode &event_data = (*it)["data"]; if (!event_type || !event_data) continue; std::string et = event_type.as_string(); if (et == "visibility_update") { // change of chat status const JSONNode &visibility = event_data["visibility"]; bool isVisible = visibility && visibility.as_bool(); proto->debugLogA(" Requested chat switch to %s", isVisible ? "Online" : "Offline"); // If actual status is not what server says, change it (handle also local away status, which means online) if (isVisible != (proto->m_iStatus != ID_STATUS_INVISIBLE)) proto->SetStatus(isVisible ? ID_STATUS_ONLINE : ID_STATUS_INVISIBLE); } } else if (t == "chatproxy-presence") { const JSONNode &buddyList = (*it)["buddyList"]; if (!buddyList) continue; for (auto itNodes = buddyList.begin(); itNodes != buddyList.end(); ++itNodes) { std::string id = (*itNodes).name(); MCONTACT hContact = proto->ContactIDToHContact(id); if (!hContact) { // FIXME: What to do, when we don't have this contact? What does it mean? // fbu->type = CONTACT_FRIEND; // add this contact as a friend? // fbu->handle = AddToContactList(fbu); continue; } // TODO: Check for friends existence/inexistence? Here we should get all friends (but we're already doing friendslist request, so we should have fresh data already) const JSONNode &p_ = (*itNodes)["p"]; // possible values: 0, 2 (something more?) (might not be present) const JSONNode &lat_ = (*itNodes)["lat"]; // timestamp of last activity (could be 0) (is always present) const JSONNode &vc_ = (*itNodes)["vc"]; // possible values: 0, 8, 10 (something more?) (might not be present) int status = ID_STATUS_DND; // DND to easily spot some problem, as we expect it will always be p==0 or p==2 below // Probably means presence: 0 = away, 2 = online, when not present then that probably means don't change that status if (p_) { int p = p_.as_int(); if (p == 0) status = ID_STATUS_AWAY; else if (p == 2) status = ID_STATUS_ONLINE; if (proto->getWord(hContact, "Status", 0) != status) proto->setWord(hContact, "Status", status); } // Last active time if (lat_) { time_t last_active = utils::time::from_string(lat_.as_string()); if (proto->getDword(hContact, "LastActiveTS", 0) != last_active) { if (last_active > 0) proto->setDword(hContact, "LastActiveTS", last_active); else proto->delSetting(hContact, "LastActiveTS"); } } // Probably means client: guess 0 = web, 8 = messenger, 10 = something else? if (vc_) { int vc = vc_.as_int(); TCHAR *client; if (vc == 0) { client = _T(FACEBOOK_CLIENT_WEB); } else if (vc == 8) { client = _T(FACEBOOK_CLIENT_MESSENGER); // I was online on Miranda, but when looked at myself at messenger.com I had icon of Messenger. } else if (vc == 10) { client = _T(FACEBOOK_CLIENT_MOBILE); } else { client = _T(FACEBOOK_CLIENT_OTHER); } ptrT oldClient(proto->getTStringA(hContact, "MirVer")); if (!oldClient || mir_tstrcmp(oldClient, client)) proto->setTString(hContact, "MirVer", client); } } } else if (t == "buddylist_overlay") { // TODO: This is now supported also via /ajax/mercury/tabs_presence.php request (probably) // additional info about user status (status, used client) const JSONNode &overlay = (*it)["overlay"]; if (!overlay) continue; for (auto itNodes = overlay.begin(); itNodes != overlay.end(); ++itNodes) { std::string id = (*itNodes).name(); MCONTACT hContact = proto->ContactIDToHContact(id); if (!hContact) { // FIXME: What to do, when we don't have this contact? What does it mean? // fbu->type = CONTACT_FRIEND; // add this contact as a friend? // fbu->handle = AddToContactList(fbu); continue; } // TODO: Check for friends existence/inexistence? /* if (getByte(fbu->handle, FACEBOOK_KEY_CONTACT_TYPE, 0) != CONTACT_FRIEND) { setByte(fbu->handle, FACEBOOK_KEY_CONTACT_TYPE, CONTACT_FRIEND); // TODO: remove that popup and use "Contact added you" event? } // Wasn't contact removed from "server-list" someday? if (getDword(fbu->handle, FACEBOOK_KEY_DELETED, 0)) { delSetting(fbu->handle, FACEBOOK_KEY_DELETED); std::string url = FACEBOOK_URL_PROFILE + fbu->user_id; std::string contactname = getContactName(this, fbu->handle, !fbu->real_name.empty() ? fbu->real_name.c_str() : fbu->user_id.c_str()); ptrT szTitle(mir_utf8decodeT(contactname.c_str())); NotifyEvent(szTitle, TranslateT("Contact is back on server-list."), fbu->handle, FACEBOOK_EVENT_FRIENDSHIP, &url); } */ /* ptrT client(getTStringA(fbu->handle, "MirVer")); if (!client || mir_tstrcmp(client, fbu->getMirVer())) setTString(fbu->handle, "MirVer", fbu->getMirVer()); */ const JSONNode &a_ = (*itNodes)["a"]; // possible values: 0, 2 (something more?) const JSONNode &la_ = (*itNodes)["la"]; // timestamp of last activity (could be 0) const JSONNode &s_ = (*itNodes)["s"]; // possible values: push (something more?) const JSONNode &vc_ = (*itNodes)["vc"]; // possible values: 0, 8, 10 (something more?) // Friller account has also these: // const JSONNode &ol_ = (*itNodes)["ol"]; // possible values: -1 (when goes to offline), 0 (when goes back online) (something more?) // const JSONNode &p_ = (*itNodes)["p"]; // class with fbAppStatus, messengerStatus, otherStatus, status, webStatus int status = ID_STATUS_FREECHAT; // FREECHAT to easily spot some problem, as we expect it will always be p==0 or p==2 below if (a_) { int a = a_.as_int(); if (a == 0) status = ID_STATUS_OFFLINE; else if (a == 2) status = ID_STATUS_ONLINE; } else { status = ID_STATUS_OFFLINE; } if (proto->getWord(hContact, "Status", 0) != status) proto->setWord(hContact, "Status", status); if (la_ && status != ID_STATUS_ONLINE) { time_t last_active = utils::time::from_string(la_.as_string()); // we should set IdleTS only when contact is IDLE, or OFFLINE if (proto->getDword(hContact, "IdleTS", 0) != last_active) { if (/*(fbu->idle || status == ID_STATUS_OFFLINE) &&*/ last_active > 0) proto->setDword(hContact, "IdleTS", last_active); else proto->delSetting(hContact, "IdleTS"); } /*if (proto->getDword(hContact, "LastActiveTS", 0) != last_active) { if (last_active > 0) proto->setDword(hContact, "LastActiveTS", last_active); else proto->delSetting(hContact, "LastActiveTS"); }*/ } else { proto->delSetting(hContact, "IdleTS"); } if (s_) { // what to do with this? } // Probably means client: guess 0 = web, 8 = messenger, 10 = something else? if (vc_) { TCHAR *client = _T(FACEBOOK_CLIENT_WEB); /*if (vc == 0) { // means active some time ago? (on messenger or also on web) client = _T(FACEBOOK_CLIENT_WEB); } else if (vc == 8) { client = _T(FACEBOOK_CLIENT_MESSENGER); // I was online on Miranda, but when looked at myself at messenger.com I had icon of Messenger. } else if (vc == 10) { // means actually active on messenger client = _T(FACEBOOK_CLIENT_MOBILE); } else { client = _T(FACEBOOK_CLIENT_OTHER); }*/ ptrT oldClient(proto->getTStringA(hContact, "MirVer")); if (!oldClient || mir_tstrcmp(oldClient, client)) proto->setTString(hContact, "MirVer", client); } } } else if (t == "ticker_update:home") { if (!proto->getByte(FACEBOOK_KEY_EVENT_TICKER_ENABLE, DEFAULT_EVENT_TICKER_ENABLE)) continue; const JSONNode &actor_ = (*it)["actor"]; const JSONNode &story_ = (*it)["story_xhp"]; std::string text = story_.as_string(); text = utils::text::html_entities_decode(utils::text::slashu_to_utf8(text)); std::string url = utils::text::source_get_value(&text, 3, "\"tickerStoryLink\"", "href=\"", "\""); std::string story_type = utils::text::source_get_value2(&text, "\"type\":\"", "\""); std::string story_class = utils::text::source_get_value2(&text, "\"entstory_class\":\"", "\""); text = utils::text::trim(utils::text::remove_html(text)); std::string userId = actor_.as_string(); MCONTACT hContact = proto->ContactIDToHContact(userId); proto->debugLogA("+++ Got ticker type='%s' class='%s'", story_type.c_str(), story_class.c_str()); if (!text.empty()) proto->NotifyEvent(proto->m_tszUserName, ptrT(mir_utf8decodeT(text.c_str())), hContact, FACEBOOK_EVENT_TICKER, &url); } else if (t == "mercury") { // rename multi user chat, video call, ... const JSONNode &actions_ = (*it)["actions"]; if (!actions_) continue; for (unsigned int i = 0; i < actions_.size(); i++) { const JSONNode &action_ = actions_[i]; const JSONNode &thread_id_ = action_["thread_id"]; const JSONNode &log_body_ = action_["log_message_body"]; const JSONNode &log_data_ = action_["log_message_data"]; const JSONNode &log_type_ = action_["log_message_type"]; if (!log_data_ || !log_body_ || !thread_id_ || !log_type_) continue; std::string thread_id = thread_id_.as_string(); std::string logType = log_type_.as_string(); std::string message_text = log_body_.as_string(); if (logType == "log:video-call") { std::string id = action_["other_user_fbid"].as_string(); std::string message_id = action_["message_id"].as_string(); facebook_message message; message.isChat = false; message.isUnread = true; message.isIncoming = (id != proto->facy.self_.user_id); message.message_text = message_text; message.time = utils::time::from_string(action_["timestamp"].as_string()); message.user_id = id; message.message_id = message_id; message.thread_id = thread_id; message.type = CALL; messages->push_back(message); } else { // TODO: check for other types, now we expect this is rename chat if (!proto->m_enableChat) continue; std::string name = log_data_["name"].as_string(); // 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_text.c_str()); } } } else if (t == "notifications_read" || t == "notifications_seen") { ScopedLock s(proto->facy.notifications_lock_); const JSONNode &alerts = (*it)["alert_ids"]; for (auto itAlerts = alerts.begin(); itAlerts != alerts.end(); ++itAlerts) { std::string id = (*itAlerts).as_string(); auto itAlert = notifications->find(id); if (itAlert != notifications->end()) { if (itAlert->second->hWndPopup != NULL) PUDeletePopup(itAlert->second->hWndPopup); // close popup delete itAlert->second; notifications->erase(itAlert); } } } else continue; } return EXIT_SUCCESS; }