/*
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 (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;
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()) {
std::string tidA = _T2A(tid.c_str());
std::string search = utils::url::encode(tidA) + "?";
http::response resp = proto->facy.flap(REQUEST_USER_INFO, NULL, &search);
if (resp.code == HTTP_CODE_FOUND && resp.headers.find("Location") != resp.headers.end()) {
search = utils::text::source_get_value(&resp.headers["Location"], 2, FACEBOOK_SERVER_MOBILE"/", "_rdr", true);
resp = proto->facy.flap(REQUEST_USER_INFO, NULL, &search);
}
if (resp.code == HTTP_CODE_OK) {
std::string about = utils::text::source_get_value(&resp.data, 2, "