From 4c818adaabffd85c54df9c9e4b59a8b441119641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20P=C3=B6sel?= Date: Wed, 28 May 2014 14:01:22 +0000 Subject: Facebook: Fixed crash when notifying long newsfeeds git-svn-id: http://svn.miranda-ng.org/main/trunk@9333 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- protocols/FacebookRM/src/events.cpp | 233 ++-- protocols/FacebookRM/src/process.cpp | 2149 +++++++++++++++++----------------- 2 files changed, 1195 insertions(+), 1187 deletions(-) (limited to 'protocols/FacebookRM') diff --git a/protocols/FacebookRM/src/events.cpp b/protocols/FacebookRM/src/events.cpp index 8604d58492..7ebe65983d 100644 --- a/protocols/FacebookRM/src/events.cpp +++ b/protocols/FacebookRM/src/events.cpp @@ -1,115 +1,118 @@ -/* - -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" - -HWND FacebookProto::NotifyEvent(TCHAR* title, TCHAR* info, MCONTACT contact, DWORD flags, std::string *url, std::string *notification_id) -{ - char name[256]; - - switch (flags) - { - case FACEBOOK_EVENT_CLIENT: - if (!getByte(FACEBOOK_KEY_EVENT_CLIENT_ENABLE, DEFAULT_EVENT_CLIENT_ENABLE)) - return NULL; - mir_snprintf(name, SIZEOF(name), "%s_%s", m_szModuleName, "Client"); - flags |= NIIF_WARNING; - break; - - case FACEBOOK_EVENT_NEWSFEED: - if (!getByte(FACEBOOK_KEY_EVENT_FEEDS_ENABLE, DEFAULT_EVENT_FEEDS_ENABLE)) - return NULL; - mir_snprintf(name, SIZEOF(name), "%s_%s", m_szModuleName, "Newsfeed"); - SkinPlaySound("NewsFeed"); - flags |= NIIF_INFO; - break; - - case FACEBOOK_EVENT_NOTIFICATION: - if (!getByte(FACEBOOK_KEY_EVENT_NOTIFICATIONS_ENABLE, DEFAULT_EVENT_NOTIFICATIONS_ENABLE)) - return NULL; - mir_snprintf(name, SIZEOF(name), "%s_%s", m_szModuleName, "Notification"); - SkinPlaySound("Notification"); - flags |= NIIF_INFO; - break; - - case FACEBOOK_EVENT_OTHER: - if (!getByte(FACEBOOK_KEY_EVENT_OTHER_ENABLE, DEFAULT_EVENT_OTHER_ENABLE)) - return NULL; - mir_snprintf(name, SIZEOF(name), "%s_%s", m_szModuleName, "Other"); - SkinPlaySound("OtherEvent"); - flags |= NIIF_INFO; - break; - } - - if (!getByte(FACEBOOK_KEY_SYSTRAY_NOTIFY,DEFAULT_SYSTRAY_NOTIFY)) - { - if (ServiceExists(MS_POPUP_ADDPOPUPCLASS)) { - - // TODO: if popup with particular ID is already showed, just update his content - // ... but f***ed up Popup Classes won't allow it now - they need to return hPopupWindow somehow - /* if (popup exists) { - if (PUChangeTextT(hWndPopup, info) > 0) // success - return; - }*/ - - POPUPDATACLASS pd = { sizeof(pd) }; - pd.ptszTitle = title; - pd.ptszText = info; - pd.pszClassName = name; - pd.hContact = contact; - - if (url != NULL || notification_id != NULL) { - popup_data *data = new popup_data(this); - if (url != NULL) - data->url = *url; - if (notification_id != NULL) - data->notification_id = *notification_id; - pd.PluginData = data; - } - - return (HWND) CallService(MS_POPUP_ADDPOPUPCLASS, 0, (LPARAM)&pd); - } - } else { - if (ServiceExists(MS_CLIST_SYSTRAY_NOTIFY)) - { - MIRANDASYSTRAYNOTIFY err; - int niif_flags = flags; - REMOVE_FLAG(niif_flags, FACEBOOK_EVENT_CLIENT | - FACEBOOK_EVENT_NEWSFEED | - FACEBOOK_EVENT_NOTIFICATION | - FACEBOOK_EVENT_OTHER); - err.szProto = m_szModuleName; - err.cbSize = sizeof(err); - err.dwInfoFlags = NIIF_INTERN_TCHAR | niif_flags; - err.tszInfoTitle = title; - err.tszInfo = info; - err.uTimeout = 10000; - if (CallService(MS_CLIST_SYSTRAY_NOTIFY, 0, (LPARAM) & err) == 0) - return NULL; - } - } - - if (FLAG_CONTAINS(flags, FACEBOOK_EVENT_CLIENT)) - MessageBox(NULL, info, title, MB_OK | MB_ICONINFORMATION); - - return NULL; -} +/* + +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" + +HWND FacebookProto::NotifyEvent(TCHAR* title, TCHAR* info, MCONTACT contact, DWORD flags, std::string *url, std::string *notification_id) +{ + if (title == NULL || info == NULL) + return NULL; + + char name[256]; + + switch (flags) + { + case FACEBOOK_EVENT_CLIENT: + if (!getByte(FACEBOOK_KEY_EVENT_CLIENT_ENABLE, DEFAULT_EVENT_CLIENT_ENABLE)) + return NULL; + mir_snprintf(name, SIZEOF(name), "%s_%s", m_szModuleName, "Client"); + flags |= NIIF_WARNING; + break; + + case FACEBOOK_EVENT_NEWSFEED: + if (!getByte(FACEBOOK_KEY_EVENT_FEEDS_ENABLE, DEFAULT_EVENT_FEEDS_ENABLE)) + return NULL; + mir_snprintf(name, SIZEOF(name), "%s_%s", m_szModuleName, "Newsfeed"); + SkinPlaySound("NewsFeed"); + flags |= NIIF_INFO; + break; + + case FACEBOOK_EVENT_NOTIFICATION: + if (!getByte(FACEBOOK_KEY_EVENT_NOTIFICATIONS_ENABLE, DEFAULT_EVENT_NOTIFICATIONS_ENABLE)) + return NULL; + mir_snprintf(name, SIZEOF(name), "%s_%s", m_szModuleName, "Notification"); + SkinPlaySound("Notification"); + flags |= NIIF_INFO; + break; + + case FACEBOOK_EVENT_OTHER: + if (!getByte(FACEBOOK_KEY_EVENT_OTHER_ENABLE, DEFAULT_EVENT_OTHER_ENABLE)) + return NULL; + mir_snprintf(name, SIZEOF(name), "%s_%s", m_szModuleName, "Other"); + SkinPlaySound("OtherEvent"); + flags |= NIIF_INFO; + break; + } + + if (!getByte(FACEBOOK_KEY_SYSTRAY_NOTIFY,DEFAULT_SYSTRAY_NOTIFY)) + { + if (ServiceExists(MS_POPUP_ADDPOPUPCLASS)) { + + // TODO: if popup with particular ID is already showed, just update his content + // ... but f***ed up Popup Classes won't allow it now - they need to return hPopupWindow somehow + /* if (popup exists) { + if (PUChangeTextT(hWndPopup, info) > 0) // success + return; + }*/ + + POPUPDATACLASS pd = { sizeof(pd) }; + pd.ptszTitle = title; + pd.ptszText = info; + pd.pszClassName = name; + pd.hContact = contact; + + if (url != NULL || notification_id != NULL) { + popup_data *data = new popup_data(this); + if (url != NULL) + data->url = *url; + if (notification_id != NULL) + data->notification_id = *notification_id; + pd.PluginData = data; + } + + return (HWND) CallService(MS_POPUP_ADDPOPUPCLASS, 0, (LPARAM)&pd); + } + } else { + if (ServiceExists(MS_CLIST_SYSTRAY_NOTIFY)) + { + MIRANDASYSTRAYNOTIFY err; + int niif_flags = flags; + REMOVE_FLAG(niif_flags, FACEBOOK_EVENT_CLIENT | + FACEBOOK_EVENT_NEWSFEED | + FACEBOOK_EVENT_NOTIFICATION | + FACEBOOK_EVENT_OTHER); + err.szProto = m_szModuleName; + err.cbSize = sizeof(err); + err.dwInfoFlags = NIIF_INTERN_TCHAR | niif_flags; + err.tszInfoTitle = title; + err.tszInfo = info; + err.uTimeout = 10000; + if (CallService(MS_CLIST_SYSTRAY_NOTIFY, 0, (LPARAM) & err) == 0) + return NULL; + } + } + + if (FLAG_CONTAINS(flags, FACEBOOK_EVENT_CLIENT)) + MessageBox(NULL, info, title, MB_OK | MB_ICONINFORMATION); + + return NULL; +} diff --git a/protocols/FacebookRM/src/process.cpp b/protocols/FacebookRM/src/process.cpp index 26d7829b0f..11991db5b4 100644 --- a/protocols/FacebookRM/src/process.cpp +++ b/protocols/FacebookRM/src/process.cpp @@ -1,1072 +1,1077 @@ -/* - -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" - -void FacebookProto::ProcessBuddyList(void*) -{ - ScopedLock s(facy.buddies_lock_); - - if (isOffline()) - return; - - facy.handle_entry("ProcessBuddyList"); - - // Prepare update data - std::string post_data = "user=" + facy.self_.user_id + "&fetch_mobile=true&phstamp=0&fb_dtsg=" + facy.dtsg_ + "&__user=" + facy.self_.user_id + "&cached_user_info_ids="; - - int counter = 0; - for (List::Item< facebook_user >* i = facy.buddies.begin(); i != NULL; i = i->next, counter++) - { - post_data += i->data->user_id + "%2C"; - } - - // Get buddy list - http::response resp = facy.flap(REQUEST_BUDDY_LIST, &post_data); - - if (resp.code != HTTP_CODE_OK) { - facy.handle_error("buddy_list"); - return; - } - - debugLogA("***** Starting processing buddy list"); - - CODE_BLOCK_TRY - - facebook_json_parser* p = new facebook_json_parser(this); - p->parse_buddy_list(&resp.data, &facy.buddies); - delete p; - - for (List::Item< facebook_user >* i = facy.buddies.begin(); i != NULL;) - { - facebook_user* fbu = i->data; - - std::string status; - switch (fbu->status_id) { - case ID_STATUS_OFFLINE: - status = "Offline"; break; - case ID_STATUS_ONLINE: - status = "Online"; break; - case ID_STATUS_ONTHEPHONE: - status = "Mobile"; break; - } - - if (fbu->idle) - status += " (idle)"; - - debugLogA(" Now %s: %s", status.c_str(), fbu->user_id.c_str()); - - if (!fbu->deleted) - { - if (!fbu->handle) // just been added - fbu->handle = AddToContactList(fbu, CONTACT_FRIEND); - - ptrT client( getTStringA(fbu->handle, "MirVer")); - if (!client || _tcscmp(client, fbu->getMirVer())) - setTString(fbu->handle, "MirVer", fbu->getMirVer()); - - if (getDword(fbu->handle, "IdleTS", 0) != fbu->last_active) { - if ((fbu->idle || fbu->status_id == ID_STATUS_OFFLINE) && fbu->last_active > 0) - setDword(fbu->handle, "IdleTS", fbu->last_active); - else - delSetting(fbu->handle, "IdleTS"); - } - } - - if (fbu->status_id == ID_STATUS_OFFLINE || fbu->deleted) - { - if (fbu->handle) - setWord(fbu->handle, "Status", ID_STATUS_OFFLINE); - - std::string to_delete(i->key); - i = i->next; - facy.buddies.erase(to_delete); - } else { - i = i->next; - - if (!fbu->handle) // just been added - fbu->handle = AddToContactList(fbu, CONTACT_FRIEND); - - if (getWord(fbu->handle, "Status", 0) != fbu->status_id) - setWord(fbu->handle, "Status", fbu->status_id); - - if (getDword(fbu->handle, "LastActiveTS", 0) != fbu->last_active) { - if (fbu->last_active > 0) - setDword(fbu->handle, "LastActiveTS", fbu->last_active); - else - delSetting(fbu->handle, "LastActiveTS"); - } - - 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; - - ptrT szTitle( mir_utf8decodeT(fbu->real_name.c_str())); - NotifyEvent(szTitle, TranslateT("Contact is back on server-list."), fbu->handle, FACEBOOK_EVENT_OTHER, &url); - } - - // Check avatar change - CheckAvatarChange(fbu->handle, fbu->image_url); - } - } - - debugLogA("***** Buddy list processed"); - - CODE_BLOCK_CATCH - - debugLogA("***** Error processing buddy list: %s", e.what()); - - CODE_BLOCK_END -} - -void FacebookProto::ProcessFriendList(void*) -{ - ScopedLock s(facy.buddies_lock_); - - if (isOffline()) - return; - - facy.handle_entry("load_friends"); - - // Get buddy list - http::response resp = facy.flap(REQUEST_LOAD_FRIENDS); - - if (resp.code != HTTP_CODE_OK) { - facy.handle_error("load_friends"); - return; - } - - debugLogA("***** Starting processing friend list"); - - CODE_BLOCK_TRY - - std::map friends; - - facebook_json_parser* p = new facebook_json_parser(this); - p->parse_friends(&resp.data, &friends); - delete p; - - - // Check and update old contacts - for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) { - if ( isChatRoom(hContact)) - continue; - - DBVARIANT dbv; - facebook_user *fbu; - if (!getString(hContact, FACEBOOK_KEY_ID, &dbv)) { - std::string id = dbv.pszVal; - db_free(&dbv); - - std::map< std::string, facebook_user* >::iterator iter; - - if ((iter = friends.find(id)) != friends.end()) { - // Found contact, update it and remove from map - fbu = iter->second; - - // TODO RM: remove, because contacts cant change it, so its only for "first run" - // - but what with contacts, that was added after logon? - // Update gender - if (getByte(hContact, "Gender", 0) != fbu->gender) - setByte(hContact, "Gender", fbu->gender); - - // TODO: remove this in some future version? - // Remove old useless "RealName" field - ptrA realname(getStringA(hContact, "RealName")); - if (realname != NULL) { - delSetting(hContact, "RealName"); - } - - // Update real name and nick - if (!fbu->real_name.empty()) { - SaveName(hContact, fbu); - } - - if (getByte(hContact, FACEBOOK_KEY_CONTACT_TYPE, 0) != CONTACT_FRIEND) { - setByte(hContact, 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(hContact, FACEBOOK_KEY_DELETED, 0)) { - delSetting(hContact, FACEBOOK_KEY_DELETED); - - std::string url = FACEBOOK_URL_PROFILE + fbu->user_id; - - ptrT szTitle( mir_utf8decodeT(fbu->real_name.c_str())); - NotifyEvent(szTitle, TranslateT("Contact is back on server-list."), hContact, FACEBOOK_EVENT_OTHER, &url); - } - - // Check avatar change - CheckAvatarChange(hContact, fbu->image_url); - - // Mark this contact as deleted ("processed") and delete them later (as there may be some duplicit contacts to use) - fbu->deleted = true; - } else { - // Contact was removed from "server-list", notify it - - // Wasnt we already been notified about this contact? And was this real friend? - if (!getDword(hContact, FACEBOOK_KEY_DELETED, 0) && getByte(hContact, FACEBOOK_KEY_CONTACT_TYPE, 0) == CONTACT_FRIEND) { - setDword(hContact, FACEBOOK_KEY_DELETED, ::time(NULL)); - setByte(hContact, FACEBOOK_KEY_CONTACT_TYPE, CONTACT_NONE); - - std::string contactname = id; - if (!getStringUtf(hContact, FACEBOOK_KEY_NICK, &dbv)) { - contactname = dbv.pszVal; - db_free(&dbv); - } - - std::string url = FACEBOOK_URL_PROFILE + id; - - ptrT szTitle( mir_utf8decodeT(contactname.c_str())); - NotifyEvent(szTitle, TranslateT("Contact is no longer on server-list."), hContact, FACEBOOK_EVENT_OTHER, &url); - } - } - } - } - - // Check remaining contacts in map and add them to contact list - for (std::map< std::string, facebook_user* >::iterator it = friends.begin(); it != friends.end(); ) { - if (!it->second->deleted) - AddToContactList(it->second, CONTACT_FRIEND, true); // we know this contact doesn't exists, so we force add it - - delete it->second; - it = friends.erase(it); - } - friends.clear(); - - debugLogA("***** Friend list processed"); - - CODE_BLOCK_CATCH - - debugLogA("***** Error processing friend list: %s", e.what()); - - CODE_BLOCK_END -} - -void FacebookProto::ProcessUnreadMessages(void*) -{ - facy.handle_entry("ProcessUnreadMessages"); - - // receive messages from all folders by default, use hidden setting to receive only inbox messages - bool inboxOnly = getBool(FACEBOOK_KEY_INBOX_ONLY, 0); - - std::string data = "folders[0]=inbox"; - if (!inboxOnly) - data += "&folders[1]=other"; - data += "&client=mercury"; - data += "__user=" + facy.self_.user_id; - data += "&fb_dtsg=" + (facy.dtsg_.length() ? facy.dtsg_ : "0"); - data += "&__a=1&__dyn=&__req=&ttstamp=0"; - - http::response resp = facy.flap(REQUEST_UNREAD_THREADS, &data); - - if (resp.code != HTTP_CODE_OK) { - facy.handle_error("ProcessUnreadMessages"); - return; - } - - CODE_BLOCK_TRY - - std::vector threads; - - facebook_json_parser* p = new facebook_json_parser(this); - p->parse_unread_threads(&resp.data, &threads, inboxOnly); - delete p; - - ForkThread(&FacebookProto::ProcessUnreadMessage, new std::vector(threads)); - - debugLogA("***** Unread threads list processed"); - - CODE_BLOCK_CATCH - - debugLogA("***** Error processing unread threads list: %s", e.what()); - - CODE_BLOCK_END - - facy.handle_success("ProcessUnreadMessages"); -} - -void FacebookProto::ProcessUnreadMessage(void *p) -{ - if (p == NULL) - return; - - facy.handle_entry("ProcessUnreadMessage"); - - std::vector threads = *(std::vector*)p; - delete (std::vector*)p; - - int offset = 0; - int limit = 21; - - bool local_timestamp = getBool(FACEBOOK_KEY_LOCAL_TIMESTAMP_UNREAD, 0); - - // receive messages from all folders by default, use hidden setting to receive only inbox messages - bool inboxOnly = getBool(FACEBOOK_KEY_INBOX_ONLY, 0); - - http::response resp; - - while (!threads.empty()) { - std::string data = "client=mercury"; - data += "&__user=" + facy.self_.user_id; - data += "&fb_dtsg=" + (facy.dtsg_.length() ? facy.dtsg_ : "0"); - data += "&__a=1&__dyn=&__req=&ttstamp=0"; - - for (std::vector::size_type i = 0; i < threads.size(); i++) { - std::string thread_id = utils::url::encode(threads[i]); - - // request messages from thread - data += "&messages[thread_ids][" + thread_id; - data += "][offset]=" + utils::conversion::to_string(&offset, UTILS_CONV_SIGNED_NUMBER); - data += "&messages[thread_ids][" + thread_id; - data += "][limit]=" + utils::conversion::to_string(&limit, UTILS_CONV_SIGNED_NUMBER); - - // request info about thread - data += "&threads[thread_ids][" + utils::conversion::to_string(&i, UTILS_CONV_UNSIGNED_NUMBER); - data += "]=" + thread_id; - } - - resp = facy.flap(REQUEST_THREAD_INFO, &data); - - if (resp.code == HTTP_CODE_OK) { - - CODE_BLOCK_TRY - - std::vector messages; - std::map chatrooms; - - facebook_json_parser* p = new facebook_json_parser(this); - p->parse_thread_messages(&resp.data, &messages, &chatrooms, true, inboxOnly, limit); - delete p; - - for (std::map::iterator it = chatrooms.begin(); it != chatrooms.end(); ) { - - facebook_chatroom *room = it->second; - MCONTACT hChatContact = NULL; - if (GetChatUsers(room->thread_id.c_str()) == NULL) { - AddChat(room->thread_id.c_str(), room->chat_name.c_str()); - hChatContact = ChatIDToHContact(room->thread_id); - // Set thread id (TID) for later - setTString(hChatContact, FACEBOOK_KEY_TID, room->thread_id.c_str()); - - for (std::map::iterator jt = room->participants.begin(); jt != room->participants.end(); ) { - AddChatContact(room->thread_id.c_str(), jt->first.c_str(), jt->second.c_str()); - ++jt; - } - } - - if (!hChatContact) - hChatContact = ChatIDToHContact(room->thread_id); - - ForkThread(&FacebookProto::ReadMessageWorker, (void*)hChatContact); - - delete it->second; - it = chatrooms.erase(it); - } - chatrooms.clear(); - - ReceiveMessages(messages, local_timestamp, true); - - debugLogA("***** Unread messages processed"); - - CODE_BLOCK_CATCH - - debugLogA("***** Error processing unread messages: %s", e.what()); - - CODE_BLOCK_END - - facy.handle_success("ProcessUnreadMessage"); - } else { - facy.handle_error("ProcessUnreadMessage"); - } - - offset += limit; - limit = 20; // TODO: use better limits? - - threads.clear(); // TODO: if we have limit messages from one user, there may be more unread messages... continue with it... otherwise remove that threadd from threads list -- or do it in json parser? hm = allow more than "limit" unread messages to be parsed - } -} - -void FacebookProto::ReceiveMessages(std::vector messages, bool local_timestamp, bool check_duplicates) -{ - bool naseemsSpamMode = getBool(FACEBOOK_KEY_NASEEMS_SPAM_MODE, false); - - // TODO: make this checking more lightweight as now it is not effective at all... - if (check_duplicates) { - // 1. check if there are some message that we already have (compare FACEBOOK_KEY_MESSAGE_ID = last received message ID) - for (std::vector::size_type i = 0; i < messages.size(); i++) { - - MCONTACT hContact = messages[i]->isChat - ? ChatIDToHContact(std::tstring(_A2T(messages[i]->thread_id.c_str()))) - : ContactIDToHContact(messages[i]->user_id); - - if (hContact == NULL) - continue; - - ptrA lastId(getStringA(hContact, FACEBOOK_KEY_MESSAGE_ID)); - if (lastId == NULL) - continue; - - if (!messages[i]->message_id.compare(lastId)) { - // Equal, ignore all older messages (including this) from same contact - for (std::vector::size_type j = 0; j < messages.size(); j++) { - bool equalsId = messages[i]->isChat - ? (messages[j]->thread_id == messages[i]->thread_id) - : (messages[j]->user_id == messages[i]->user_id); - - if (equalsId && messages[j]->time <= messages[i]->time) - messages[j]->flag_ = 1; - } - } - } - - // 2. remove all marked messages from list - for (std::vector::iterator it = messages.begin(); it != messages.end();) { - if ((*it)->flag_ == 1) - it = messages.erase(it); - else - ++it; - } - } - - for(std::vector::size_type i = 0; i < messages.size(); i++) { - DWORD timestamp = local_timestamp || !messages[i]->time ? ::time(NULL) : messages[i]->time; - - if (messages[i]->isChat) { - // Multi-user message - debugLogA(" Got chat message: %s", messages[i]->message_text.c_str()); - - std::tstring tthread_id = _A2T(messages[i]->thread_id.c_str()); - - // RM TODO: better use check if chatroom exists/is in db/is online... no? - // like: if (ChatIDToHContact(tthread_id) == NULL) { - if (GetChatUsers(tthread_id.c_str()) == NULL) { - // In Naseem's spam mode we ignore outgoing messages sent from other instances - if (naseemsSpamMode && !messages[i]->isIncoming) { - delete messages[i]; - continue; - } - - // TODO: We don't have this chat, lets load info about it (name, list of users, last messages, etc.) - //LoadChatInfo(&fbu); // In this method use json's parse_chat_info - std::tstring tthread_name = tthread_id; // TODO: use correct name for chat, not thread_id - - AddChat(tthread_id.c_str(), tthread_name.c_str()); - } - - MCONTACT hChatContact = ChatIDToHContact(tthread_id); - - // Save last (this) message ID - setString(hChatContact, FACEBOOK_KEY_MESSAGE_ID, messages[i]->message_id.c_str()); - - // Save TID if not exists already - ptrA tid(getStringA(hChatContact, FACEBOOK_KEY_TID)); - if (!tid || strcmp(tid, messages[i]->thread_id.c_str())) - setString(hChatContact, FACEBOOK_KEY_TID, messages[i]->thread_id.c_str()); - - ParseSmileys(messages[i]->message_text, hChatContact); - - // TODO: support also system messages (rename chat, user quit, etc.)! (here? or it is somewhere else? - // ... we must add some new "type" field into facebook_message structure and use it also for Pokes and similar) - UpdateChat(tthread_id.c_str(), messages[i]->user_id.c_str(), messages[i]->sender_name.c_str(), messages[i]->message_text.c_str(), timestamp); - - // Automatically mark message as read because chatroom doesn't support onRead event (yet) - ForkThread(&FacebookProto::ReadMessageWorker, (void*)hChatContact); - } else { - // Single-user message - debugLogA(" Got message: %s", messages[i]->message_text.c_str()); - - facebook_user fbu; - fbu.user_id = messages[i]->user_id; - fbu.real_name = messages[i]->sender_name; - - MCONTACT hContact = ContactIDToHContact(fbu.user_id); - if (hContact == NULL) { - // In Naseem's spam mode we ignore outgoing messages sent from other instances - if (naseemsSpamMode && !messages[i]->isIncoming) { - delete messages[i]; - continue; - } - - // We don't have this contact, lets load info about him - LoadContactInfo(&fbu); - - hContact = AddToContactList(&fbu, CONTACT_NONE); - } - - // Save last (this) message ID - setString(hContact, FACEBOOK_KEY_MESSAGE_ID, messages[i]->message_id.c_str()); - - // Save TID if not exists already - ptrA tid(getStringA(hContact, FACEBOOK_KEY_TID)); - if (!tid || strcmp(tid, messages[i]->thread_id.c_str())) - setString(hContact, FACEBOOK_KEY_TID, messages[i]->thread_id.c_str()); - - ParseSmileys(messages[i]->message_text, hContact); - - if (messages[i]->isIncoming) { - PROTORECVEVENT recv = { 0 }; - recv.flags = PREF_UTF; - recv.szMessage = const_cast(messages[i]->message_text.c_str()); - recv.timestamp = timestamp; - ProtoChainRecvMsg(hContact, &recv); - } else { - DBEVENTINFO dbei = { 0 }; - dbei.cbSize = sizeof(dbei); - dbei.eventType = EVENTTYPE_MESSAGE; - dbei.flags = DBEF_SENT | DBEF_UTF; - dbei.szModule = m_szModuleName; - dbei.timestamp = timestamp; - dbei.cbBlob = (DWORD)messages[i]->message_text.length() + 1; - dbei.pBlob = (PBYTE)messages[i]->message_text.c_str(); - db_event_add(hContact, &dbei); - } - } - delete messages[i]; - } - messages.clear(); -} - -void FacebookProto::ProcessMessages(void* data) -{ - if (data == NULL) - return; - - std::string* resp = (std::string*)data; - - // receive messages from all folders by default, use hidden setting to receive only inbox messages - bool inboxOnly = getBool(FACEBOOK_KEY_INBOX_ONLY, 0); - - if (isOffline()) - goto exit; - - debugLogA("***** Starting processing messages"); - - CODE_BLOCK_TRY - - std::vector< facebook_message* > messages; - - facebook_json_parser* p = new facebook_json_parser(this); - p->parse_messages(data, &messages, &facy.notifications, inboxOnly); - delete p; - - bool local_timestamp = getBool(FACEBOOK_KEY_LOCAL_TIMESTAMP, 0); - - ReceiveMessages(messages, local_timestamp); - - ShowNotifications(); - - debugLogA("***** Messages processed"); - - CODE_BLOCK_CATCH - - debugLogA("***** Error processing messages: %s", e.what()); - - CODE_BLOCK_END - -exit: - delete resp; -} - -void FacebookProto::ShowNotifications() { - if (!getByte(FACEBOOK_KEY_EVENT_NOTIFICATIONS_ENABLE, DEFAULT_EVENT_NOTIFICATIONS_ENABLE)) - return; - - for (std::map::iterator it = facy.notifications.begin(); it != facy.notifications.end(); ++it) { - if (it->second != NULL && !it->second->seen) { - debugLogA(" Got notification: %s", it->second->text.c_str()); - ptrT szText(mir_utf8decodeT(it->second->text.c_str())); - it->second->hWndPopup = NotifyEvent(m_tszUserName, szText, ContactIDToHContact(it->second->user_id), FACEBOOK_EVENT_NOTIFICATION, &it->second->link, &it->second->id); - it->second->seen = true; - } - } -} - -void FacebookProto::ProcessNotifications(void*) -{ - if (isOffline()) - return; - - facy.handle_entry("notifications"); - - // Get notifications - http::response resp = facy.flap(REQUEST_NOTIFICATIONS); - - if (resp.code != HTTP_CODE_OK) { - facy.handle_error("notifications"); - return; - } - - - // Process notifications - debugLogA("***** Starting processing notifications"); - - CODE_BLOCK_TRY - - facebook_json_parser* p = new facebook_json_parser(this); - p->parse_notifications(&(resp.data), &facy.notifications); - delete p; - - ShowNotifications(); - - debugLogA("***** Notifications processed"); - - CODE_BLOCK_CATCH - - debugLogA("***** Error processing notifications: %s", e.what()); - - CODE_BLOCK_END -} - -void FacebookProto::ProcessFriendRequests(void*) -{ - facy.handle_entry("friendRequests"); - - // Get notifications - http::response resp = facy.flap(REQUEST_LOAD_REQUESTS); - - if (resp.code != HTTP_CODE_OK) { - facy.handle_error("friendRequests"); - return; - } - - // Parse it - std::string reqs = utils::text::source_get_value(&resp.data, 2, "class=\"mRequestItem", "al aps\">"); - - std::string::size_type pos = 0; - std::string::size_type pos2 = 0; - bool last = false; - - while (!last && !reqs.empty()) { - std::string req; - if ((pos2 = reqs.find("class=\"mRequestItem", pos)) != std::string::npos) { - req = reqs.substr(pos, pos2 - pos); - pos = pos2 + 19; - } else { - req = reqs.substr(pos); - last = true; - } - - std::string get = utils::text::source_get_value(&req, 3, ""); - std::string time = utils::text::source_get_value2(&get, "seenrequesttime=", "&\""); - - facebook_user *fbu = new facebook_user(); - fbu->real_name = utils::text::source_get_value(&req, 2, "class=\"actor\">", "user_id = utils::text::source_get_value(&get, 2, "id=", "&"); - - if (fbu->user_id.length() && fbu->real_name.length()) - { - MCONTACT hContact = AddToContactList(fbu, CONTACT_APPROVE); - setByte(hContact, FACEBOOK_KEY_CONTACT_TYPE, CONTACT_APPROVE); - - bool seen = false; - - DBVARIANT dbv; - if (!getString(hContact, "RequestTime", &dbv)) { - seen = !strcmp(dbv.pszVal, time.c_str()); - db_free(&dbv); - } - - if (!seen) { - // This is new request - setString(hContact, "RequestTime", time.c_str()); - - //blob is: uin(DWORD), hContact(HANDLE), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ), reason(ASCIIZ) - //blob is: 0(DWORD), hContact(HANDLE), nick(ASCIIZ), ""(ASCIIZ), ""(ASCIIZ), ""(ASCIIZ), ""(ASCIIZ) - DBEVENTINFO dbei = {0}; - dbei.cbSize = sizeof(DBEVENTINFO); - dbei.szModule = m_szModuleName; - dbei.timestamp = ::time(NULL); - dbei.flags = DBEF_UTF; - dbei.eventType = EVENTTYPE_AUTHREQUEST; - dbei.cbBlob = (DWORD)(sizeof(DWORD)*2 + fbu->real_name.length() + 5); - - PBYTE pCurBlob = dbei.pBlob = (PBYTE) mir_alloc(dbei.cbBlob); - *(PDWORD)pCurBlob = 0; pCurBlob += sizeof(DWORD); // UID - *(PDWORD)pCurBlob = (DWORD)hContact; pCurBlob += sizeof(DWORD); // Contact Handle - strcpy((char*)pCurBlob, fbu->real_name.data()); pCurBlob += fbu->real_name.length()+1; // Nickname - *pCurBlob = '\0'; pCurBlob++; // First Name - *pCurBlob = '\0'; pCurBlob++; // Last Name - *pCurBlob = '\0'; pCurBlob++; // E-mail - *pCurBlob = '\0'; // Reason - - db_event_add(0, &dbei); - - debugLogA(" (New) Friendship request from: %s (%s) [%s]", fbu->real_name.c_str(), fbu->user_id.c_str(), time.c_str()); - } else { - debugLogA(" (Old) Friendship request from: %s (%s) [%s]", fbu->real_name.c_str(), fbu->user_id.c_str(), time.c_str()); - } - } else { - debugLogA(" !!! Wrong friendship request"); - debugLogA("%s", req.c_str()); - } - } - - facy.handle_success("friendRequests"); -} - -void FacebookProto::ProcessFeeds(void* data) -{ - if (!isOnline()) - return; - - facy.handle_entry("feeds"); - - // Get feeds - http::response resp = facy.flap(REQUEST_FEEDS); - - if (resp.code != HTTP_CODE_OK) { - facy.handle_error("feeds"); - return; - } - - if (resp.data.empty() /*|| resp.data.find("\"num_stories\":0") != std::string::npos*/) - return; - - CODE_BLOCK_TRY - - debugLogA("***** Starting processing feeds"); - - std::vector< facebook_newsfeed* > news; - - std::string::size_type pos = 0; - UINT limit = 0; - - DWORD new_time = facy.last_feeds_update_; - - while ((pos = resp.data.find("
", pos) - pos); - pos += 5; - - std::string post_header = utils::text::source_get_value(&post, 3, "", ""); - std::string post_message = utils::text::source_get_value(&post, 3, " userContent\"", ">", "", "", "", ""); - - std::string time = utils::text::source_get_value(&post_time, 2, "data-utime=\"", "\""); - std::string time_text = utils::text::source_get_value(&post_time, 2, ">", ""); - - DWORD ttime = utils::conversion::to_timestamp(time); - if (ttime > facy.last_feeds_update_) - new_time = ttime; - else - continue; - - std::string post_place = utils::text::source_get_value(&post, 4, "", "", ""); - - std::string premsg = "\n" + time_text; - - post_place = utils::text::trim( - utils::text::remove_html(post_place)); - if (!post_place.empty()) { - premsg += " - " + post_place; - } - premsg += "\n"; - - // in title keep only name, end of events like "X shared link" put into message - std::string::size_type pos2 = post_header.find("‎"); - - if (pos2 != std::string::npos) { - utils::text::replace_first(&post_header, "‎", " → "); - } else { - pos2 = post_header.find(" 2) - premsg += a; - post_header = post_header.substr(0, pos2); - } - } - - // Strip "Translate" link - pos2 = post_message.find("role=\"button\">"); - if (pos2 != std::string::npos) { - post_message = post_message.substr(0, pos2 + 14); - } - - post_message = premsg + post_message; - - // append attachement to message (if any) - //post_message += utils::text::trim(post_attach); - - facebook_newsfeed* nf = new facebook_newsfeed; - - nf->title = utils::text::trim( - utils::text::special_expressions_decode( - utils::text::remove_html(post_header))); - - nf->user_id = utils::text::source_get_value(&post_header, 2, "user.php?id=", "&"); - - nf->link = utils::text::special_expressions_decode(post_link); - //utils::text::source_get_value(&post_link, 2, "href=\\\"", "\\\">")); - - nf->text = utils::text::trim( - utils::text::special_expressions_decode( - utils::text::remove_html( - utils::text::edit_html(post_message)))); - - if (!nf->title.length() || !nf->text.length()) - { - delete nf; - continue; - } - - if (nf->text.length() > 500) - { - nf->text = nf->text.substr(0, 500); - nf->text += "..."; - } - - news.push_back(nf); - pos++; - limit++; - } - - for(std::vector::size_type i=0; ititle.c_str(), news[i]->text.c_str()); - ptrT szTitle( mir_utf8decodeT(news[i]->title.c_str())); - ptrT szText( mir_utf8decodeT(news[i]->text.c_str())); - NotifyEvent(szTitle,szText,this->ContactIDToHContact(news[i]->user_id),FACEBOOK_EVENT_NEWSFEED, &news[i]->link); - delete news[i]; - } - news.clear(); - - this->facy.last_feeds_update_ = new_time; - //this->facy.last_feeds_update_ = ::time(NULL); - - debugLogA("***** Feeds processed"); - - CODE_BLOCK_CATCH - - debugLogA("***** Error processing feeds: %s", e.what()); - - CODE_BLOCK_END - - facy.handle_success("feeds"); -} - -void FacebookProto::ProcessPages(void *data) -{ - if (!getByte(FACEBOOK_KEY_LOAD_PAGES, DEFAULT_LOAD_PAGES)) - return; - - facy.handle_entry("load_pages"); - - // Get feeds - http::response resp = facy.flap(REQUEST_PAGES); - - if (resp.code != HTTP_CODE_OK) { - facy.handle_error("load_pages"); - return; - } - - std::string content = utils::text::source_get_value(&resp.data, 2, "id=\"bookmarksSeeAllSection\"", ""); - - std::string::size_type start, end; - start = content.find(""); - - start = content.find(""); - - std::string::size_type pos = 0; - std::string::size_type pos2 = 0; - - while ((pos = items.find("", pos)) != std::string::npos) { - std::string item = items.substr(pos, items.find("", pos) - pos); - pos++; count++; - - std::string id = utils::text::source_get_value2(&item, "?id=", "&\""); - if (id.empty()) - id = utils::text::source_get_value2(&item, "?ids=", "&\""); - - std::string name = utils::text::source_get_value(&item, 4, "", "", "", ""); - - if ((pos2 = name.find("")) != std::string::npos) { - nick = name.substr(pos2 + 30, name.length() - pos2 - 31); // also remove brackets around nickname - name = name.substr(0, pos2 - 1); - } - - if ((pos2 = name.find(" ")) != std::string::npos) { - surname = name.substr(pos2 + 1, name.length() - pos2 - 1); - name = name.substr(0, pos2); - } - - // ignore self contact and empty ids - if (id.empty() || id == facy.self_.user_id) - continue; - - ptrT tid( mir_utf8decodeT(id.c_str())); - ptrT tname( mir_utf8decodeT(utils::text::special_expressions_decode(name).c_str())); - ptrT tsurname( mir_utf8decodeT(utils::text::special_expressions_decode(surname).c_str())); - ptrT tnick( mir_utf8decodeT(utils::text::special_expressions_decode(nick).c_str())); - ptrT tcommon( mir_utf8decodeT(utils::text::special_expressions_decode(common).c_str())); - - PROTOSEARCHRESULT isr = {0}; - isr.cbSize = sizeof(isr); - isr.flags = PSR_TCHAR; - isr.id = tid; - isr.nick = tnick; - isr.firstName = tname; - isr.lastName = tsurname; - isr.email = tcommon; - - ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, targ, (LPARAM)&isr); - } - - ssid = utils::text::source_get_value(&items, 3, "id=\"more_objects\"", "ssid=", "&"); - if (ssid.empty()) - break; // No more results - } - } - - ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, targ, 0); - - facy.handle_success("searchAckThread"); - - mir_free(targ); - mir_free(arg); -} - -void FacebookProto::SearchIdAckThread(void *targ) -{ - facy.handle_entry("searchIdAckThread"); - - char *arg = mir_utf8encodeT((TCHAR*)targ); - std::string search = utils::url::encode(arg) + "?"; - - if (!isOffline()) - { - http::response resp = 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 = facy.flap(REQUEST_USER_INFO, NULL, &search); - } - - if (resp.code == HTTP_CODE_OK) - { - std::string about = utils::text::source_get_value(&resp.data, 2, "
", ". + +*/ + +#include "common.h" + +#define MAX_NEWSFEED_LEN 500 + +void FacebookProto::ProcessBuddyList(void*) +{ + ScopedLock s(facy.buddies_lock_); + + if (isOffline()) + return; + + facy.handle_entry("ProcessBuddyList"); + + // Prepare update data + std::string post_data = "user=" + facy.self_.user_id + "&fetch_mobile=true&phstamp=0&fb_dtsg=" + facy.dtsg_ + "&__user=" + facy.self_.user_id + "&cached_user_info_ids="; + + int counter = 0; + for (List::Item< facebook_user >* i = facy.buddies.begin(); i != NULL; i = i->next, counter++) + { + post_data += i->data->user_id + "%2C"; + } + + // Get buddy list + http::response resp = facy.flap(REQUEST_BUDDY_LIST, &post_data); + + if (resp.code != HTTP_CODE_OK) { + facy.handle_error("buddy_list"); + return; + } + + debugLogA("***** Starting processing buddy list"); + + CODE_BLOCK_TRY + + facebook_json_parser* p = new facebook_json_parser(this); + p->parse_buddy_list(&resp.data, &facy.buddies); + delete p; + + for (List::Item< facebook_user >* i = facy.buddies.begin(); i != NULL;) + { + facebook_user* fbu = i->data; + + std::string status; + switch (fbu->status_id) { + case ID_STATUS_OFFLINE: + status = "Offline"; break; + case ID_STATUS_ONLINE: + status = "Online"; break; + case ID_STATUS_ONTHEPHONE: + status = "Mobile"; break; + } + + if (fbu->idle) + status += " (idle)"; + + debugLogA(" Now %s: %s", status.c_str(), fbu->user_id.c_str()); + + if (!fbu->deleted) + { + if (!fbu->handle) // just been added + fbu->handle = AddToContactList(fbu, CONTACT_FRIEND); + + ptrT client( getTStringA(fbu->handle, "MirVer")); + if (!client || _tcscmp(client, fbu->getMirVer())) + setTString(fbu->handle, "MirVer", fbu->getMirVer()); + + if (getDword(fbu->handle, "IdleTS", 0) != fbu->last_active) { + if ((fbu->idle || fbu->status_id == ID_STATUS_OFFLINE) && fbu->last_active > 0) + setDword(fbu->handle, "IdleTS", fbu->last_active); + else + delSetting(fbu->handle, "IdleTS"); + } + } + + if (fbu->status_id == ID_STATUS_OFFLINE || fbu->deleted) + { + if (fbu->handle) + setWord(fbu->handle, "Status", ID_STATUS_OFFLINE); + + std::string to_delete(i->key); + i = i->next; + facy.buddies.erase(to_delete); + } else { + i = i->next; + + if (!fbu->handle) // just been added + fbu->handle = AddToContactList(fbu, CONTACT_FRIEND); + + if (getWord(fbu->handle, "Status", 0) != fbu->status_id) + setWord(fbu->handle, "Status", fbu->status_id); + + if (getDword(fbu->handle, "LastActiveTS", 0) != fbu->last_active) { + if (fbu->last_active > 0) + setDword(fbu->handle, "LastActiveTS", fbu->last_active); + else + delSetting(fbu->handle, "LastActiveTS"); + } + + 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; + + ptrT szTitle( mir_utf8decodeT(fbu->real_name.c_str())); + NotifyEvent(szTitle, TranslateT("Contact is back on server-list."), fbu->handle, FACEBOOK_EVENT_OTHER, &url); + } + + // Check avatar change + CheckAvatarChange(fbu->handle, fbu->image_url); + } + } + + debugLogA("***** Buddy list processed"); + + CODE_BLOCK_CATCH + + debugLogA("***** Error processing buddy list: %s", e.what()); + + CODE_BLOCK_END +} + +void FacebookProto::ProcessFriendList(void*) +{ + ScopedLock s(facy.buddies_lock_); + + if (isOffline()) + return; + + facy.handle_entry("load_friends"); + + // Get buddy list + http::response resp = facy.flap(REQUEST_LOAD_FRIENDS); + + if (resp.code != HTTP_CODE_OK) { + facy.handle_error("load_friends"); + return; + } + + debugLogA("***** Starting processing friend list"); + + CODE_BLOCK_TRY + + std::map friends; + + facebook_json_parser* p = new facebook_json_parser(this); + p->parse_friends(&resp.data, &friends); + delete p; + + + // Check and update old contacts + for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) { + if ( isChatRoom(hContact)) + continue; + + DBVARIANT dbv; + facebook_user *fbu; + if (!getString(hContact, FACEBOOK_KEY_ID, &dbv)) { + std::string id = dbv.pszVal; + db_free(&dbv); + + std::map< std::string, facebook_user* >::iterator iter; + + if ((iter = friends.find(id)) != friends.end()) { + // Found contact, update it and remove from map + fbu = iter->second; + + // TODO RM: remove, because contacts cant change it, so its only for "first run" + // - but what with contacts, that was added after logon? + // Update gender + if (getByte(hContact, "Gender", 0) != fbu->gender) + setByte(hContact, "Gender", fbu->gender); + + // TODO: remove this in some future version? + // Remove old useless "RealName" field + ptrA realname(getStringA(hContact, "RealName")); + if (realname != NULL) { + delSetting(hContact, "RealName"); + } + + // Update real name and nick + if (!fbu->real_name.empty()) { + SaveName(hContact, fbu); + } + + if (getByte(hContact, FACEBOOK_KEY_CONTACT_TYPE, 0) != CONTACT_FRIEND) { + setByte(hContact, 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(hContact, FACEBOOK_KEY_DELETED, 0)) { + delSetting(hContact, FACEBOOK_KEY_DELETED); + + std::string url = FACEBOOK_URL_PROFILE + fbu->user_id; + + ptrT szTitle( mir_utf8decodeT(fbu->real_name.c_str())); + NotifyEvent(szTitle, TranslateT("Contact is back on server-list."), hContact, FACEBOOK_EVENT_OTHER, &url); + } + + // Check avatar change + CheckAvatarChange(hContact, fbu->image_url); + + // Mark this contact as deleted ("processed") and delete them later (as there may be some duplicit contacts to use) + fbu->deleted = true; + } else { + // Contact was removed from "server-list", notify it + + // Wasnt we already been notified about this contact? And was this real friend? + if (!getDword(hContact, FACEBOOK_KEY_DELETED, 0) && getByte(hContact, FACEBOOK_KEY_CONTACT_TYPE, 0) == CONTACT_FRIEND) { + setDword(hContact, FACEBOOK_KEY_DELETED, ::time(NULL)); + setByte(hContact, FACEBOOK_KEY_CONTACT_TYPE, CONTACT_NONE); + + std::string contactname = id; + if (!getStringUtf(hContact, FACEBOOK_KEY_NICK, &dbv)) { + contactname = dbv.pszVal; + db_free(&dbv); + } + + std::string url = FACEBOOK_URL_PROFILE + id; + + ptrT szTitle( mir_utf8decodeT(contactname.c_str())); + NotifyEvent(szTitle, TranslateT("Contact is no longer on server-list."), hContact, FACEBOOK_EVENT_OTHER, &url); + } + } + } + } + + // Check remaining contacts in map and add them to contact list + for (std::map< std::string, facebook_user* >::iterator it = friends.begin(); it != friends.end(); ) { + if (!it->second->deleted) + AddToContactList(it->second, CONTACT_FRIEND, true); // we know this contact doesn't exists, so we force add it + + delete it->second; + it = friends.erase(it); + } + friends.clear(); + + debugLogA("***** Friend list processed"); + + CODE_BLOCK_CATCH + + debugLogA("***** Error processing friend list: %s", e.what()); + + CODE_BLOCK_END +} + +void FacebookProto::ProcessUnreadMessages(void*) +{ + facy.handle_entry("ProcessUnreadMessages"); + + // receive messages from all folders by default, use hidden setting to receive only inbox messages + bool inboxOnly = getBool(FACEBOOK_KEY_INBOX_ONLY, 0); + + std::string data = "folders[0]=inbox"; + if (!inboxOnly) + data += "&folders[1]=other"; + data += "&client=mercury"; + data += "__user=" + facy.self_.user_id; + data += "&fb_dtsg=" + (facy.dtsg_.length() ? facy.dtsg_ : "0"); + data += "&__a=1&__dyn=&__req=&ttstamp=0"; + + http::response resp = facy.flap(REQUEST_UNREAD_THREADS, &data); + + if (resp.code != HTTP_CODE_OK) { + facy.handle_error("ProcessUnreadMessages"); + return; + } + + CODE_BLOCK_TRY + + std::vector threads; + + facebook_json_parser* p = new facebook_json_parser(this); + p->parse_unread_threads(&resp.data, &threads, inboxOnly); + delete p; + + ForkThread(&FacebookProto::ProcessUnreadMessage, new std::vector(threads)); + + debugLogA("***** Unread threads list processed"); + + CODE_BLOCK_CATCH + + debugLogA("***** Error processing unread threads list: %s", e.what()); + + CODE_BLOCK_END + + facy.handle_success("ProcessUnreadMessages"); +} + +void FacebookProto::ProcessUnreadMessage(void *p) +{ + if (p == NULL) + return; + + facy.handle_entry("ProcessUnreadMessage"); + + std::vector threads = *(std::vector*)p; + delete (std::vector*)p; + + int offset = 0; + int limit = 21; + + bool local_timestamp = getBool(FACEBOOK_KEY_LOCAL_TIMESTAMP_UNREAD, 0); + + // receive messages from all folders by default, use hidden setting to receive only inbox messages + bool inboxOnly = getBool(FACEBOOK_KEY_INBOX_ONLY, 0); + + http::response resp; + + while (!threads.empty()) { + std::string data = "client=mercury"; + data += "&__user=" + facy.self_.user_id; + data += "&fb_dtsg=" + (facy.dtsg_.length() ? facy.dtsg_ : "0"); + data += "&__a=1&__dyn=&__req=&ttstamp=0"; + + for (std::vector::size_type i = 0; i < threads.size(); i++) { + std::string thread_id = utils::url::encode(threads[i]); + + // request messages from thread + data += "&messages[thread_ids][" + thread_id; + data += "][offset]=" + utils::conversion::to_string(&offset, UTILS_CONV_SIGNED_NUMBER); + data += "&messages[thread_ids][" + thread_id; + data += "][limit]=" + utils::conversion::to_string(&limit, UTILS_CONV_SIGNED_NUMBER); + + // request info about thread + data += "&threads[thread_ids][" + utils::conversion::to_string(&i, UTILS_CONV_UNSIGNED_NUMBER); + data += "]=" + thread_id; + } + + resp = facy.flap(REQUEST_THREAD_INFO, &data); + + if (resp.code == HTTP_CODE_OK) { + + CODE_BLOCK_TRY + + std::vector messages; + std::map chatrooms; + + facebook_json_parser* p = new facebook_json_parser(this); + p->parse_thread_messages(&resp.data, &messages, &chatrooms, true, inboxOnly, limit); + delete p; + + for (std::map::iterator it = chatrooms.begin(); it != chatrooms.end(); ) { + + facebook_chatroom *room = it->second; + MCONTACT hChatContact = NULL; + if (GetChatUsers(room->thread_id.c_str()) == NULL) { + AddChat(room->thread_id.c_str(), room->chat_name.c_str()); + hChatContact = ChatIDToHContact(room->thread_id); + // Set thread id (TID) for later + setTString(hChatContact, FACEBOOK_KEY_TID, room->thread_id.c_str()); + + for (std::map::iterator jt = room->participants.begin(); jt != room->participants.end(); ) { + AddChatContact(room->thread_id.c_str(), jt->first.c_str(), jt->second.c_str()); + ++jt; + } + } + + if (!hChatContact) + hChatContact = ChatIDToHContact(room->thread_id); + + ForkThread(&FacebookProto::ReadMessageWorker, (void*)hChatContact); + + delete it->second; + it = chatrooms.erase(it); + } + chatrooms.clear(); + + ReceiveMessages(messages, local_timestamp, true); + + debugLogA("***** Unread messages processed"); + + CODE_BLOCK_CATCH + + debugLogA("***** Error processing unread messages: %s", e.what()); + + CODE_BLOCK_END + + facy.handle_success("ProcessUnreadMessage"); + } else { + facy.handle_error("ProcessUnreadMessage"); + } + + offset += limit; + limit = 20; // TODO: use better limits? + + threads.clear(); // TODO: if we have limit messages from one user, there may be more unread messages... continue with it... otherwise remove that threadd from threads list -- or do it in json parser? hm = allow more than "limit" unread messages to be parsed + } +} + +void FacebookProto::ReceiveMessages(std::vector messages, bool local_timestamp, bool check_duplicates) +{ + bool naseemsSpamMode = getBool(FACEBOOK_KEY_NASEEMS_SPAM_MODE, false); + + // TODO: make this checking more lightweight as now it is not effective at all... + if (check_duplicates) { + // 1. check if there are some message that we already have (compare FACEBOOK_KEY_MESSAGE_ID = last received message ID) + for (std::vector::size_type i = 0; i < messages.size(); i++) { + + MCONTACT hContact = messages[i]->isChat + ? ChatIDToHContact(std::tstring(_A2T(messages[i]->thread_id.c_str()))) + : ContactIDToHContact(messages[i]->user_id); + + if (hContact == NULL) + continue; + + ptrA lastId(getStringA(hContact, FACEBOOK_KEY_MESSAGE_ID)); + if (lastId == NULL) + continue; + + if (!messages[i]->message_id.compare(lastId)) { + // Equal, ignore all older messages (including this) from same contact + for (std::vector::size_type j = 0; j < messages.size(); j++) { + bool equalsId = messages[i]->isChat + ? (messages[j]->thread_id == messages[i]->thread_id) + : (messages[j]->user_id == messages[i]->user_id); + + if (equalsId && messages[j]->time <= messages[i]->time) + messages[j]->flag_ = 1; + } + } + } + + // 2. remove all marked messages from list + for (std::vector::iterator it = messages.begin(); it != messages.end();) { + if ((*it)->flag_ == 1) + it = messages.erase(it); + else + ++it; + } + } + + for(std::vector::size_type i = 0; i < messages.size(); i++) { + DWORD timestamp = local_timestamp || !messages[i]->time ? ::time(NULL) : messages[i]->time; + + if (messages[i]->isChat) { + // Multi-user message + debugLogA(" Got chat message: %s", messages[i]->message_text.c_str()); + + std::tstring tthread_id = _A2T(messages[i]->thread_id.c_str()); + + // RM TODO: better use check if chatroom exists/is in db/is online... no? + // like: if (ChatIDToHContact(tthread_id) == NULL) { + if (GetChatUsers(tthread_id.c_str()) == NULL) { + // In Naseem's spam mode we ignore outgoing messages sent from other instances + if (naseemsSpamMode && !messages[i]->isIncoming) { + delete messages[i]; + continue; + } + + // TODO: We don't have this chat, lets load info about it (name, list of users, last messages, etc.) + //LoadChatInfo(&fbu); // In this method use json's parse_chat_info + std::tstring tthread_name = tthread_id; // TODO: use correct name for chat, not thread_id + + AddChat(tthread_id.c_str(), tthread_name.c_str()); + } + + MCONTACT hChatContact = ChatIDToHContact(tthread_id); + + // Save last (this) message ID + setString(hChatContact, FACEBOOK_KEY_MESSAGE_ID, messages[i]->message_id.c_str()); + + // Save TID if not exists already + ptrA tid(getStringA(hChatContact, FACEBOOK_KEY_TID)); + if (!tid || strcmp(tid, messages[i]->thread_id.c_str())) + setString(hChatContact, FACEBOOK_KEY_TID, messages[i]->thread_id.c_str()); + + ParseSmileys(messages[i]->message_text, hChatContact); + + // TODO: support also system messages (rename chat, user quit, etc.)! (here? or it is somewhere else? + // ... we must add some new "type" field into facebook_message structure and use it also for Pokes and similar) + UpdateChat(tthread_id.c_str(), messages[i]->user_id.c_str(), messages[i]->sender_name.c_str(), messages[i]->message_text.c_str(), timestamp); + + // Automatically mark message as read because chatroom doesn't support onRead event (yet) + ForkThread(&FacebookProto::ReadMessageWorker, (void*)hChatContact); + } else { + // Single-user message + debugLogA(" Got message: %s", messages[i]->message_text.c_str()); + + facebook_user fbu; + fbu.user_id = messages[i]->user_id; + fbu.real_name = messages[i]->sender_name; + + MCONTACT hContact = ContactIDToHContact(fbu.user_id); + if (hContact == NULL) { + // In Naseem's spam mode we ignore outgoing messages sent from other instances + if (naseemsSpamMode && !messages[i]->isIncoming) { + delete messages[i]; + continue; + } + + // We don't have this contact, lets load info about him + LoadContactInfo(&fbu); + + hContact = AddToContactList(&fbu, CONTACT_NONE); + } + + // Save last (this) message ID + setString(hContact, FACEBOOK_KEY_MESSAGE_ID, messages[i]->message_id.c_str()); + + // Save TID if not exists already + ptrA tid(getStringA(hContact, FACEBOOK_KEY_TID)); + if (!tid || strcmp(tid, messages[i]->thread_id.c_str())) + setString(hContact, FACEBOOK_KEY_TID, messages[i]->thread_id.c_str()); + + ParseSmileys(messages[i]->message_text, hContact); + + if (messages[i]->isIncoming) { + PROTORECVEVENT recv = { 0 }; + recv.flags = PREF_UTF; + recv.szMessage = const_cast(messages[i]->message_text.c_str()); + recv.timestamp = timestamp; + ProtoChainRecvMsg(hContact, &recv); + } else { + DBEVENTINFO dbei = { 0 }; + dbei.cbSize = sizeof(dbei); + dbei.eventType = EVENTTYPE_MESSAGE; + dbei.flags = DBEF_SENT | DBEF_UTF; + dbei.szModule = m_szModuleName; + dbei.timestamp = timestamp; + dbei.cbBlob = (DWORD)messages[i]->message_text.length() + 1; + dbei.pBlob = (PBYTE)messages[i]->message_text.c_str(); + db_event_add(hContact, &dbei); + } + } + delete messages[i]; + } + messages.clear(); +} + +void FacebookProto::ProcessMessages(void* data) +{ + if (data == NULL) + return; + + std::string* resp = (std::string*)data; + + // receive messages from all folders by default, use hidden setting to receive only inbox messages + bool inboxOnly = getBool(FACEBOOK_KEY_INBOX_ONLY, 0); + + if (isOffline()) + goto exit; + + debugLogA("***** Starting processing messages"); + + CODE_BLOCK_TRY + + std::vector< facebook_message* > messages; + + facebook_json_parser* p = new facebook_json_parser(this); + p->parse_messages(data, &messages, &facy.notifications, inboxOnly); + delete p; + + bool local_timestamp = getBool(FACEBOOK_KEY_LOCAL_TIMESTAMP, 0); + + ReceiveMessages(messages, local_timestamp); + + ShowNotifications(); + + debugLogA("***** Messages processed"); + + CODE_BLOCK_CATCH + + debugLogA("***** Error processing messages: %s", e.what()); + + CODE_BLOCK_END + +exit: + delete resp; +} + +void FacebookProto::ShowNotifications() { + if (!getByte(FACEBOOK_KEY_EVENT_NOTIFICATIONS_ENABLE, DEFAULT_EVENT_NOTIFICATIONS_ENABLE)) + return; + + for (std::map::iterator it = facy.notifications.begin(); it != facy.notifications.end(); ++it) { + if (it->second != NULL && !it->second->seen) { + debugLogA(" Got notification: %s", it->second->text.c_str()); + ptrT szText(mir_utf8decodeT(it->second->text.c_str())); + it->second->hWndPopup = NotifyEvent(m_tszUserName, szText, ContactIDToHContact(it->second->user_id), FACEBOOK_EVENT_NOTIFICATION, &it->second->link, &it->second->id); + it->second->seen = true; + } + } +} + +void FacebookProto::ProcessNotifications(void*) +{ + if (isOffline()) + return; + + facy.handle_entry("notifications"); + + // Get notifications + http::response resp = facy.flap(REQUEST_NOTIFICATIONS); + + if (resp.code != HTTP_CODE_OK) { + facy.handle_error("notifications"); + return; + } + + + // Process notifications + debugLogA("***** Starting processing notifications"); + + CODE_BLOCK_TRY + + facebook_json_parser* p = new facebook_json_parser(this); + p->parse_notifications(&(resp.data), &facy.notifications); + delete p; + + ShowNotifications(); + + debugLogA("***** Notifications processed"); + + CODE_BLOCK_CATCH + + debugLogA("***** Error processing notifications: %s", e.what()); + + CODE_BLOCK_END +} + +void FacebookProto::ProcessFriendRequests(void*) +{ + facy.handle_entry("friendRequests"); + + // Get notifications + http::response resp = facy.flap(REQUEST_LOAD_REQUESTS); + + if (resp.code != HTTP_CODE_OK) { + facy.handle_error("friendRequests"); + return; + } + + // Parse it + std::string reqs = utils::text::source_get_value(&resp.data, 2, "class=\"mRequestItem", "al aps\">"); + + std::string::size_type pos = 0; + std::string::size_type pos2 = 0; + bool last = false; + + while (!last && !reqs.empty()) { + std::string req; + if ((pos2 = reqs.find("class=\"mRequestItem", pos)) != std::string::npos) { + req = reqs.substr(pos, pos2 - pos); + pos = pos2 + 19; + } else { + req = reqs.substr(pos); + last = true; + } + + std::string get = utils::text::source_get_value(&req, 3, ""); + std::string time = utils::text::source_get_value2(&get, "seenrequesttime=", "&\""); + + facebook_user *fbu = new facebook_user(); + fbu->real_name = utils::text::source_get_value(&req, 2, "class=\"actor\">", "user_id = utils::text::source_get_value(&get, 2, "id=", "&"); + + if (fbu->user_id.length() && fbu->real_name.length()) + { + MCONTACT hContact = AddToContactList(fbu, CONTACT_APPROVE); + setByte(hContact, FACEBOOK_KEY_CONTACT_TYPE, CONTACT_APPROVE); + + bool seen = false; + + DBVARIANT dbv; + if (!getString(hContact, "RequestTime", &dbv)) { + seen = !strcmp(dbv.pszVal, time.c_str()); + db_free(&dbv); + } + + if (!seen) { + // This is new request + setString(hContact, "RequestTime", time.c_str()); + + //blob is: uin(DWORD), hContact(HANDLE), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ), reason(ASCIIZ) + //blob is: 0(DWORD), hContact(HANDLE), nick(ASCIIZ), ""(ASCIIZ), ""(ASCIIZ), ""(ASCIIZ), ""(ASCIIZ) + DBEVENTINFO dbei = {0}; + dbei.cbSize = sizeof(DBEVENTINFO); + dbei.szModule = m_szModuleName; + dbei.timestamp = ::time(NULL); + dbei.flags = DBEF_UTF; + dbei.eventType = EVENTTYPE_AUTHREQUEST; + dbei.cbBlob = (DWORD)(sizeof(DWORD)*2 + fbu->real_name.length() + 5); + + PBYTE pCurBlob = dbei.pBlob = (PBYTE) mir_alloc(dbei.cbBlob); + *(PDWORD)pCurBlob = 0; pCurBlob += sizeof(DWORD); // UID + *(PDWORD)pCurBlob = (DWORD)hContact; pCurBlob += sizeof(DWORD); // Contact Handle + strcpy((char*)pCurBlob, fbu->real_name.data()); pCurBlob += fbu->real_name.length()+1; // Nickname + *pCurBlob = '\0'; pCurBlob++; // First Name + *pCurBlob = '\0'; pCurBlob++; // Last Name + *pCurBlob = '\0'; pCurBlob++; // E-mail + *pCurBlob = '\0'; // Reason + + db_event_add(0, &dbei); + + debugLogA(" (New) Friendship request from: %s (%s) [%s]", fbu->real_name.c_str(), fbu->user_id.c_str(), time.c_str()); + } else { + debugLogA(" (Old) Friendship request from: %s (%s) [%s]", fbu->real_name.c_str(), fbu->user_id.c_str(), time.c_str()); + } + } else { + debugLogA(" !!! Wrong friendship request"); + debugLogA("%s", req.c_str()); + } + } + + facy.handle_success("friendRequests"); +} + +void FacebookProto::ProcessFeeds(void* data) +{ + if (!isOnline()) + return; + + facy.handle_entry("feeds"); + + // Get feeds + http::response resp = facy.flap(REQUEST_FEEDS); + + if (resp.code != HTTP_CODE_OK) { + facy.handle_error("feeds"); + return; + } + + if (resp.data.empty() /*|| resp.data.find("\"num_stories\":0") != std::string::npos*/) + return; + + CODE_BLOCK_TRY + + debugLogA("***** Starting processing feeds"); + + std::vector< facebook_newsfeed* > news; + + std::string::size_type pos = 0; + UINT limit = 0; + + DWORD new_time = facy.last_feeds_update_; + + while ((pos = resp.data.find("
", pos) - pos); + pos += 5; + + std::string post_header = utils::text::source_get_value(&post, 3, "", ""); + std::string post_message = utils::text::source_get_value(&post, 3, " userContent\"", ">", "", "", "", ""); + + std::string time = utils::text::source_get_value(&post_time, 2, "data-utime=\"", "\""); + std::string time_text = utils::text::source_get_value(&post_time, 2, ">", ""); + + DWORD ttime = utils::conversion::to_timestamp(time); + if (ttime > facy.last_feeds_update_) + new_time = ttime; + else + continue; + + std::string post_place = utils::text::source_get_value(&post, 4, "", "", ""); + + std::string premsg = "\n" + time_text; + + post_place = utils::text::trim( + utils::text::remove_html(post_place)); + if (!post_place.empty()) { + premsg += " - " + post_place; + } + premsg += "\n"; + + // in title keep only name, end of events like "X shared link" put into message + std::string::size_type pos2 = post_header.find("‎"); + + if (pos2 != std::string::npos) { + utils::text::replace_first(&post_header, "‎", " → "); + } else { + pos2 = post_header.find(" 2) + premsg += a; + post_header = post_header.substr(0, pos2); + } + } + + // Strip "Translate" link + pos2 = post_message.find("role=\"button\">"); + if (pos2 != std::string::npos) { + post_message = post_message.substr(0, pos2 + 14); + } + + post_message = premsg + post_message; + + // append attachement to message (if any) + //post_message += utils::text::trim(post_attach); + + facebook_newsfeed* nf = new facebook_newsfeed; + + nf->title = utils::text::trim( + utils::text::special_expressions_decode( + utils::text::remove_html(post_header))); + + nf->user_id = utils::text::source_get_value(&post_header, 2, "user.php?id=", "&"); + + nf->link = utils::text::special_expressions_decode(post_link); + //utils::text::source_get_value(&post_link, 2, "href=\\\"", "\\\">")); + + nf->text = utils::text::trim( + utils::text::special_expressions_decode( + utils::text::remove_html( + utils::text::edit_html(post_message)))); + + if (!nf->title.length() || !nf->text.length()) + { + delete nf; + continue; + } + + news.push_back(nf); + pos++; + limit++; + } + + for(std::vector::size_type i=0; ititle.c_str(), news[i]->text.c_str()); + ptrT tszTitle( mir_utf8decodeT(news[i]->title.c_str())); + ptrT tszText( mir_utf8decodeT(news[i]->text.c_str())); + + // Truncate text of newsfeed when it's too long + if (_tcslen(tszText) > MAX_NEWSFEED_LEN) { + TCHAR buf[MAX_NEWSFEED_LEN + 3 + 1] = { 0 }; + _tcsncpy(buf, tszText, MAX_NEWSFEED_LEN); + _tcsncpy(&buf[MAX_NEWSFEED_LEN], _T("..."), 3); + tszText = buf; + } + + NotifyEvent(tszTitle, tszText, this->ContactIDToHContact(news[i]->user_id), FACEBOOK_EVENT_NEWSFEED, &news[i]->link); + delete news[i]; + } + news.clear(); + + this->facy.last_feeds_update_ = new_time; + //this->facy.last_feeds_update_ = ::time(NULL); + + debugLogA("***** Feeds processed"); + + CODE_BLOCK_CATCH + + debugLogA("***** Error processing feeds: %s", e.what()); + + CODE_BLOCK_END + + facy.handle_success("feeds"); +} + +void FacebookProto::ProcessPages(void *data) +{ + if (!getByte(FACEBOOK_KEY_LOAD_PAGES, DEFAULT_LOAD_PAGES)) + return; + + facy.handle_entry("load_pages"); + + // Get feeds + http::response resp = facy.flap(REQUEST_PAGES); + + if (resp.code != HTTP_CODE_OK) { + facy.handle_error("load_pages"); + return; + } + + std::string content = utils::text::source_get_value(&resp.data, 2, "id=\"bookmarksSeeAllSection\"", ""); + + std::string::size_type start, end; + start = content.find(""); + + start = content.find(""); + + std::string::size_type pos = 0; + std::string::size_type pos2 = 0; + + while ((pos = items.find("", pos)) != std::string::npos) { + std::string item = items.substr(pos, items.find("", pos) - pos); + pos++; count++; + + std::string id = utils::text::source_get_value2(&item, "?id=", "&\""); + if (id.empty()) + id = utils::text::source_get_value2(&item, "?ids=", "&\""); + + std::string name = utils::text::source_get_value(&item, 4, "", "", "", ""); + + if ((pos2 = name.find("")) != std::string::npos) { + nick = name.substr(pos2 + 30, name.length() - pos2 - 31); // also remove brackets around nickname + name = name.substr(0, pos2 - 1); + } + + if ((pos2 = name.find(" ")) != std::string::npos) { + surname = name.substr(pos2 + 1, name.length() - pos2 - 1); + name = name.substr(0, pos2); + } + + // ignore self contact and empty ids + if (id.empty() || id == facy.self_.user_id) + continue; + + ptrT tid( mir_utf8decodeT(id.c_str())); + ptrT tname( mir_utf8decodeT(utils::text::special_expressions_decode(name).c_str())); + ptrT tsurname( mir_utf8decodeT(utils::text::special_expressions_decode(surname).c_str())); + ptrT tnick( mir_utf8decodeT(utils::text::special_expressions_decode(nick).c_str())); + ptrT tcommon( mir_utf8decodeT(utils::text::special_expressions_decode(common).c_str())); + + PROTOSEARCHRESULT isr = {0}; + isr.cbSize = sizeof(isr); + isr.flags = PSR_TCHAR; + isr.id = tid; + isr.nick = tnick; + isr.firstName = tname; + isr.lastName = tsurname; + isr.email = tcommon; + + ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, targ, (LPARAM)&isr); + } + + ssid = utils::text::source_get_value(&items, 3, "id=\"more_objects\"", "ssid=", "&"); + if (ssid.empty()) + break; // No more results + } + } + + ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, targ, 0); + + facy.handle_success("searchAckThread"); + + mir_free(targ); + mir_free(arg); +} + +void FacebookProto::SearchIdAckThread(void *targ) +{ + facy.handle_entry("searchIdAckThread"); + + char *arg = mir_utf8encodeT((TCHAR*)targ); + std::string search = utils::url::encode(arg) + "?"; + + if (!isOffline()) + { + http::response resp = 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 = facy.flap(REQUEST_USER_INFO, NULL, &search); + } + + if (resp.code == HTTP_CODE_OK) + { + std::string about = utils::text::source_get_value(&resp.data, 2, "
", "