/*
Facebook plugin for Miranda Instant Messenger
_____________________________________________
Copyright � 2009-11 Michal Zelinka, 2011-15 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(std::string *data, List::List< facebook_user >* buddy_list)
{
facebook_user* current = NULL;
std::string jsonData = data->substr(9);
JSONROOT root(jsonData.c_str());
if (root == NULL)
return EXIT_FAILURE;
JSONNODE *payload = json_get(root, "payload");
if (payload == NULL) {
return EXIT_FAILURE;
}
JSONNODE *list = json_get(payload, "buddy_list");
if (list == NULL) {
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(json_as_pstring(name));
if (thumbSrc != NULL)
current->image_url = utils::text::slashu_to_utf8(json_as_pstring(thumbSrc));
}
}
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(json_as_pstring(name));
if (thumbSrc)
fbu->image_url = utils::text::slashu_to_utf8(json_as_pstring(thumbSrc));
if (vanity)
fbu->username = utils::text::slashu_to_utf8(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(std::string *data, std::map< std::string, facebook_user* >* friends)
{
std::string jsonData = data->substr(9);
JSONROOT root(jsonData.c_str());
if (root == NULL)
return EXIT_FAILURE;
JSONNODE *payload = json_get(root, "payload");
if (payload == NULL) {
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));
}
return EXIT_SUCCESS;
}
int facebook_json_parser::parse_notifications(std::string *data, std::map< std::string, facebook_notification* > *notifications)
{
std::string jsonData = data->substr(9);
JSONROOT root(jsonData.c_str());
if (root == NULL)
return EXIT_FAILURE;
JSONNODE *payload = json_get(root, "payload");
if (payload == NULL) {
return EXIT_FAILURE;
}
JSONNODE *list = json_get(payload, "notifications");
if (list == NULL) {
return EXIT_FAILURE;
}
// Create notifications chatroom (if it doesn't exists), because we will be writing to it new notifications here
proto->PrepareNotificationsChatRoom();
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 || time == NULL || json_as_int(unread) == 0)
continue;
std::string text = utils::text::html_entities_decode(utils::text::slashu_to_utf8(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, "time = utils::time::from_string(json_as_pstring(time));
// Write notification to chatroom
proto->UpdateNotificationsChatRoom(notification);
// If it's unseen, remember it, otherwise forget it
if (notifications->find(notification->id) == notifications->end())
notifications->insert(std::make_pair(notification->id, notification));
else
delete notification;
}
return EXIT_SUCCESS;
}
bool ignore_duplicits(FacebookProto *proto, const std::string &mid, const 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 ID: %s", mid.c_str());
it->second++; // increase counter (for deleting it later)
return true;
}
// remember this id to ignore duplicits
proto->facy.messages_ignore.insert(std::make_pair(mid, 1));
return false;
}
void parseAttachments(FacebookProto *proto, std::string *message_text, JSONNODE *it, const std::string &thread_id, std::string other_user_fbid)
{
// 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)
link = utils::url::decode(utils::text::source_get_value(&link, 2, "?uri=", "&")); // fix photo url
else if (link.find("/") == 0)
link = HTTP_PROTO_SECURE FACEBOOK_SERVER_REGULAR + link; // make absolute url
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";
// Stickers as smileys
if (type == "sticker" && proto->getByte(FACEBOOK_KEY_CUSTOM_SMILEYS, DEFAULT_CUSTOM_SMILEYS)) {
JSONNODE *metadata = json_get(itAttachment, "metadata");
if (metadata != NULL) {
JSONNODE *stickerId_ = json_get(metadata, "stickerID");
if (stickerId_ != NULL) {
std::string sticker = "[[sticker:" + json_as_pstring(stickerId_) + "]]";
attachments_text += sticker;
if (other_user_fbid.empty() && !thread_id.empty()) {
other_user_fbid = proto->ThreadIDToContactID(thread_id);
}
// FIXME: rewrite smileyadd to use custom smileys per protocol and not per contact and then remove this ugliness
if (!other_user_fbid.empty()) {
MCONTACT hContact = proto->ContactIDToHContact(other_user_fbid);
proto->StickerAsSmiley(sticker, link, hContact);
}
}
}
}
}
}
}
// 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(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::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);
JSONROOT root(jsonData.c_str());
if (root == NULL)
return EXIT_FAILURE;
JSONNODE *ms = json_get(root, "ms");
if (ms == NULL) {
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;
time_t timestamp = utils::time::from_string(json_as_pstring(time_));
MCONTACT hContact = NULL;
std::tstring reader;
std::string readerId = json_as_pstring(reader_);
JSONNODE *threadid = json_get(it, "tid");
if (threadid == NULL) {
// classic contact
hContact = proto->ContactIDToHContact(readerId);
} else {
// multi user chat
if (!proto->m_enableChat)
continue;
std::tstring tid = ptrT(json_as_string(threadid));
std::map::iterator it = proto->facy.chat_rooms.find(tid);
if (it != proto->facy.chat_rooms.end()) {
facebook_chatroom *chatroom = it->second;
std::map participants = chatroom->participants;
std::map::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)
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");
// looks like there is either "tid" or "other_user_fbid" - or "tid" is removed forever?
JSONNODE *tid = json_get(msg, "tid");
JSONNODE *other_user_id_ = json_get(msg, "other_user_fbid");
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);
std::string thread_id = (tid != NULL ? json_as_pstring(tid) : "");
std::string other_user_id = (other_user_id_ != NULL ? json_as_pstring(other_user_id_) : "");
// Process attachements and stickers
parseAttachments(proto, &message_text, msg, thread_id, other_user_id);
// 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::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(json_as_pstring(timestamp));
message->user_id = id;
message->message_id = message_id;
message->sender_name = 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 && json_as_pstring(gthreadinfo) != "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
JSONNODE *nodes = json_get(it, "nodes");
// Create notifications chatroom (if it doesn't exists), because we will be writing to it new notifications here
proto->PrepareNotificationsChatRoom();
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;
time_t timestamp = utils::time::from_string(json_as_pstring(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 = json_as_pstring(url);
notification->id = json_as_pstring(alert_id);
notification->time = timestamp;
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") {
JSONNODE *data = json_get(it, "data");
if (data == NULL)
continue;
JSONNODE *appId_ = json_get(data, "app_id");
JSONNODE *type_ = json_get(data, "type");
if ((appId_ && json_as_pstring(appId_) == "2356318349") || (type_ && json_as_pstring(type_) == "friend_confirmed")) {
// Friendship notifications
JSONNODE *body_ = json_get(data, "body");
JSONNODE *html_ = (body_ ? json_get(body_, "__html") : NULL);
JSONNODE *href_ = json_get(data, "href");
JSONNODE *unread_ = json_get(data, "unread");
JSONNODE *alertId_ = json_get(data, "alert_id");
if (html_ == NULL || href_ == NULL || unread_ == NULL || json_as_int(unread_) == 0)
continue;
std::string text = utils::text::remove_html(utils::text::slashu_to_utf8(json_as_pstring(html_)));
std::string url = json_as_pstring(href_);
std::string alert_id = (alertId_ ? json_as_pstring(alertId_) : "");
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
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
if (!proto->m_enableChat)
continue;
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 = ptrT(json_as_string(thread_));
std::string from_id = json_as_pstring(from_);
std::map::iterator it = proto->facy.chat_rooms.find(tid);
if (it != proto->facy.chat_rooms.end()) {
facebook_chatroom *chatroom = it->second;
std::map participants = chatroom->participants;
std::map::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 (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, "story_time");
JSONNODE *story_ = json_get(it, "story_xhp");
std::string text = json_as_pstring(story_);
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_ != NULL ? json_as_pstring(actor_) : "");
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, ...
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");
JSONNODE *log_type_ = json_get(action_, "log_message_type");
if (!log_data_ || !log_body_ || !thread_id_ || !log_type_)
continue;
std::tstring thread_id = ptrT(json_as_string(thread_id_));
std::string type = json_as_pstring(log_type_);
std::string message_text = json_as_pstring(log_body_);
if (type == "log:video-call") {
JSONNODE *other_user_fbid_ = json_get(action_, "other_user_fbid");
std::string id = json_as_pstring(other_user_fbid_);
JSONNODE *timestamp = json_get(action_, "timestamp");
JSONNODE *mid_ = json_get(action_, "message_id");
std::string message_id = json_as_pstring(mid_);
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(json_as_pstring(timestamp));
message->user_id = id;
message->message_id = message_id;
message->sender_name.clear();
message->thread_id = json_as_pstring(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;
JSONNODE *name_ = json_get(log_data_, "name");
std::string name = json_as_pstring(name_);
// 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_);
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;
}
return EXIT_SUCCESS;
}
int facebook_json_parser::parse_unread_threads(std::string *data, std::vector< std::string >* threads, bool inboxOnly)
{
std::string jsonData = data->substr(9);
JSONROOT root(jsonData.c_str());
if (root == NULL)
return EXIT_FAILURE;
JSONNODE *payload = json_get(root, "payload");
if (payload == NULL) {
return EXIT_FAILURE;
}
JSONNODE *unread_threads = json_get(payload, "unread_thread_ids");
if (unread_threads == NULL) {
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));
}
}
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);
JSONROOT root(jsonData.c_str());
if (root == NULL)
return EXIT_FAILURE;
JSONNODE *payload = json_get(root, "payload");
if (payload == NULL) {
return EXIT_FAILURE;
}
JSONNODE *actions = json_get(payload, "actions");
JSONNODE *threads = json_get(payload, "threads");
if (actions == NULL || threads == NULL) {
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(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 = ptrT(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, thread_id, ""); // FIXME: is here supported other_user_fbid ?
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::slashu_to_utf8(message_text), true);
if (message_text.empty())
continue;
bool isUnread = (is_unread != NULL && json_as_bool(is_unread));
// 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 != NULL)
message->sender_name = json_as_pstring(author_email);
message->time = utils::time::from_string(json_as_pstring(timestamp));
message->thread_id = thread_id;
message->message_id = message_id;
message->isIncoming = (author_id != proto->facy.self_.user_id);
message->isUnread = isUnread;
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 {
delete message;
continue;
}
}
messages->push_back(message);
}
return EXIT_SUCCESS;
}
int facebook_json_parser::parse_thread_info(std::string *data, std::string* user_id)
{
std::string jsonData = data->substr(9);
JSONROOT root(jsonData.c_str());
if (root == NULL)
return EXIT_FAILURE;
JSONNODE *payload = json_get(root, "payload");
if (payload == NULL) {
return EXIT_FAILURE;
}
JSONNODE *threads = json_get(payload, "threads");
if (threads == NULL) {
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;
}
return EXIT_SUCCESS;
}
int facebook_json_parser::parse_user_info(std::string *data, facebook_user* fbu)
{
std::string jsonData = data->substr(9);
JSONROOT root(jsonData.c_str());
if (root == NULL)
return EXIT_FAILURE;
JSONNODE *payload = json_get(root, "payload");
if (payload == NULL) {
return EXIT_FAILURE;
}
JSONNODE *profiles = json_get(payload, "profiles");
if (profiles == NULL) {
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;
}
}
return EXIT_SUCCESS;
}
int facebook_json_parser::parse_chat_info(std::string *data, facebook_chatroom* fbc)
{
std::string jsonData = data->substr(9);
JSONROOT root(jsonData.c_str());
if (root == NULL)
return EXIT_FAILURE;
JSONNODE *payload = json_get(root, "payload");
if (payload == NULL) {
return EXIT_FAILURE;
}
//JSONNODE *actions = json_get(payload, "actions");
JSONNODE *threads = json_get(payload, "threads");
if (/*actions == NULL || */threads == NULL) {
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 *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?
if (thread_id_ == NULL || is_canonical_user_ == NULL || json_as_bool(is_canonical_user_))
continue;
std::tstring tid = ptrT(json_as_string(thread_id_));
// TODO: allow more users to parse at once
if (fbc->thread_id != tid) {
continue;
}
JSONNODE *participants = json_get(it, "participants");
for (unsigned int j = 0; j < json_size(participants); j++) {
JSONNODE *jt = json_at(participants, j);
std::string user_id = json_as_pstring(jt);
fbc->participants.insert(std::make_pair(user_id.substr(5), ""));
}
fbc->chat_name = ptrT(json_as_string(name_));
}
return EXIT_SUCCESS;
}