", "");
// Get and strip optional nickname
std::string::size_type pos = this->self_.real_name.find("
");
if (pos != std::string::npos) {
this->self_.nick = utils::text::source_get_value(&this->self_.real_name, 2, "(", ")");
parent->debugLogA(" Got self nick name: %s", this->self_.nick.c_str());
this->self_.real_name = this->self_.real_name.substr(0, pos - 1);
}
// Another attempt to get optional nickname
if (this->self_.nick.empty())
this->self_.nick = utils::text::html_entities_decode(utils::text::slashu_to_utf8(utils::text::source_get_value(&resp.data, 3, "class=\\\"alternate_name\\\"", ">(", ")\\u003C\\/")));
this->self_.real_name = utils::text::remove_html(this->self_.real_name);
parent->debugLogA(" Got self real name (nickname): %s (%s)", this->self_.real_name.c_str(), this->self_.nick.c_str());
parent->SaveName(NULL, &this->self_);
// Get avatar (from touch version)
if (!touchData.empty())
this->self_.image_url = utils::text::html_entities_decode(utils::text::slashu_to_utf8(utils::text::source_get_value(&touchData, 2, "\"pic\":\"", "\"")));
// Another attempt to get avatar(from mbasic version)
if (this->self_.image_url.empty())
this->self_.image_url = utils::text::source_get_value(&resp.data, 3, "id=\"root", "self_.image_url.empty()) {
this->self_.image_url = utils::text::source_get_value(&resp.data, 3, "id=\"root", "/photo.php?", "\"");
// Prepare this special url (not direct image url) to be handled correctly in CheckAvatarChange()
// It must contain "/" at the beginning and also shouldn't contain "?" as parameters after that are stripped
if (!this->self_.image_url.empty())
this->self_.image_url = "/" + this->self_.image_url;
}
// Final attempt to get avatar as on some pages is only link to photo page and not link to image itself
if (this->self_.image_url.empty()) {
HttpRequest *request = new ProfilePictureRequest(this->mbasicWorks, self_.user_id.c_str());
http::response resp2 = sendRequest(request);
// Get avatar (from mbasic version of photo page)
this->self_.image_url = utils::text::html_entities_decode(utils::text::source_get_value(&resp2.data, 3, "id=\"root", "self_.image_url.empty())
this->self_.image_url = utils::text::html_entities_decode(utils::text::source_get_value(&resp2.data, 3, "id=\"root", "background-image: url("", "")"));
// Sometimes even Facebook doesn't show any picture at all! So just ignore this error in that case...
if (this->self_.image_url.empty()) {
parent->debugLogA("!!! Empty avatar even from avatar page. Source code:\n%s", resp2.data.c_str());
// Set dumb avatar "url" (see how it works in CheckAvatarChange()) so we can't tell if/when the avatar changed, but it will be loaded at least once
this->self_.image_url = "/NO_AVATAR/";
}
}
parent->debugLogA(" Got self avatar: %s", this->self_.image_url.c_str());
parent->CheckAvatarChange(NULL, this->self_.image_url);
// Get logout hash
this->logout_hash_ = utils::text::source_get_value2(&resp.data, "/logout.php?h=", "&\"");
parent->debugLogA(" Got self logout hash: %s", this->logout_hash_.c_str());
if (this->self_.real_name.empty() || this->self_.image_url.empty() || this->logout_hash_.empty()) {
parent->debugLogA("!!! Empty nick/avatar/hash. Source code:\n%s", resp.data.c_str());
client_notify(TranslateT("Could not load all required data. Plugin may still work correctly, but you should report this and wait for plugin update."));
}
}
return handle_success("home");
case HTTP_CODE_FOUND:
// Work-around for replica_down, f**king hell what's that?
parent->debugLogA("!!! REPLICA_DOWN is back in force!");
return this->home();
default:
return handle_error("home", FORCE_QUIT);
}
}
bool facebook_client::chat_state(bool online)
{
handle_entry("chat_state");
HttpRequest *request = new SetVisibilityRequest(this, online);
http::response resp = sendRequest(request);
if (!resp.error_title.empty())
return handle_error("chat_state");
return handle_success("chat_state");
}
bool facebook_client::reconnect()
{
handle_entry("reconnect");
// Request reconnect
http::response resp = sendRequest(new ReconnectRequest(this));
switch (resp.code) {
case HTTP_CODE_OK:
{
this->chat_channel_ = utils::text::source_get_value(&resp.data, 2, "\"user_channel\":\"", "\"");
parent->debugLogA(" Got self channel: %s", this->chat_channel_.c_str());
this->chat_channel_partition_ = utils::text::source_get_value2(&resp.data, "\"partition\":", ",}");
parent->debugLogA(" Got self channel partition: %s", this->chat_channel_partition_.c_str());
this->chat_channel_host_ = utils::text::source_get_value(&resp.data, 2, "\"host\":\"", "\"");
parent->debugLogA(" Got self channel host: %s", this->chat_channel_host_.c_str());
this->chat_sequence_num_ = utils::text::source_get_value2(&resp.data, "\"seq\":", ",}");
parent->debugLogA(" Got self sequence number: %s", this->chat_sequence_num_.c_str());
this->chat_conn_num_ = utils::text::source_get_value2(&resp.data, "\"max_conn\":", ",}");
parent->debugLogA(" Got self max_conn: %s", this->chat_conn_num_.c_str());
this->chat_sticky_num_ = utils::text::source_get_value(&resp.data, 2, "\"sticky_token\":\"", "\"");
parent->debugLogA(" Got self sticky_token: %s", this->chat_sticky_num_.c_str());
//std::string retry_interval = utils::text::source_get_value2(&resp.data, "\"retry_interval\":", ",}");
//parent->debugLogA(" Got self retry_interval: %s", retry_interval.c_str());
//std::string visibility = utils::text::source_get_value2(&resp.data, "\"visibility\":", ",}");
//parent->debugLogA(" Got self visibility: %s", visibility.c_str());
// Send activity_ping after each reconnect
activity_ping();
}
return handle_success("reconnect");
default:
return handle_error("reconnect", FORCE_DISCONNECT);
}
}
bool facebook_client::channel()
{
handle_entry("channel");
// Get updates
ChannelRequest *request = new ChannelRequest(this, ChannelRequest::PULL);
http::response resp = sendRequest(request);
if (resp.data.empty()) // Something went wrong
return handle_error("channel");
// Load traceId, if present
std::string traceId = utils::text::source_get_value(&resp.data, 2, "\"tr\":\"", "\"");
if (!traceId.empty())
this->chat_traceid_ = traceId;
std::string type = utils::text::source_get_value(&resp.data, 2, "\"t\":\"", "\"");
parent->debugLogA("Pull response type = %s", type.c_str());
if (type == "continue" || type == "heartbeat") {
// Everything is OK, no new message received
}
else if (type == "lb") {
// Some new stuff (idk how does it work yet)
this->chat_sticky_pool_ = utils::text::source_get_value(&resp.data, 2, "\"pool\":\"", "\"");
parent->debugLogA(" Got self sticky pool: %s", this->chat_sticky_pool_.c_str());
this->chat_sticky_num_ = utils::text::source_get_value2(&resp.data, "\"sticky\":\"", "\"");
parent->debugLogA(" Got self sticky number: %s", this->chat_sticky_num_.c_str());
}
else if (type == "refresh") {
// Requested relogin (due to some settings change, removing this session, etc.)
parent->debugLogA("!!! Requested refresh");
this->chat_sequence_num_ = utils::text::source_get_value2(&resp.data, "\"seq\":", ",}");
parent->debugLogA(" Got self sequence number: %s", this->chat_sequence_num_.c_str());
this->chat_reconnect_reason_ = utils::text::source_get_value2(&resp.data, "\"reason\":", ",}");
parent->debugLogA(" Got reconnect reason: %s", this->chat_reconnect_reason_.c_str());
return this->reconnect();
}
else if (!type.empty()) { // for "msg", "fullReload" and maybe also other types
// Something has been received, throw to new thread to process
std::string* response_data = new std::string(resp.data);
parent->ForkThread(&FacebookProto::ProcessMessages, response_data);
// Get new sequence number
std::string seq = utils::text::source_get_value2(&resp.data, "\"seq\":", ",}");
parent->debugLogA(" Got self sequence number: %s", seq.c_str());
if (type == "msg") {
// Update msgs_recv number for every "msg" type we receive (during fullRefresh/reload responses it stays the same)
this->chat_msgs_recv_++;
}
else if (type == "fullReload") {
// At fullReload we force our seq number to received value (there may have been some error or something)
this->chat_sequence_num_ = seq;
}
// Check if it's different from our old one (which means we should increment our old one)
if (seq != this->chat_sequence_num_) {
// Facebook now often return much bigger number which results in skipping few data requests, so we increment it manually
// Bigger skips (when there is some problem or something) are handled when fullreload/refresh response type
int iseq = 0;
if (utils::conversion::from_string(iseq, this->chat_sequence_num_, std::dec)) {
// Increment and convert it back to string
iseq++;
std::string newSeq = utils::conversion::to_string(&iseq, UTILS_CONV_SIGNED_NUMBER);
// Check if we have different seq than the one from Facebook
if (newSeq != seq) {
parent->debugLogA("!!! Use self incremented sequence number: %s (instead of: %s)", newSeq.c_str(), seq.c_str());
seq = newSeq;
}
}
}
this->chat_sequence_num_ = seq;
}
else // No type? This shouldn't happen unless there is a big API change.
return handle_error("channel");
// Return
switch (resp.code) {
case HTTP_CODE_OK:
return handle_success("channel");
case HTTP_CODE_GATEWAY_TIMEOUT:
// Maybe we have same clientid as other connected client, try to generate different one
this->chat_clientid_ = utils::text::rand_string(8, "0123456789abcdef", &this->random_);
// Intentionally fall to handle_error() below
case HTTP_CODE_FAKE_DISCONNECTED:
case HTTP_CODE_FAKE_ERROR:
default:
return handle_error("channel");
}
}
bool facebook_client::activity_ping()
{
// Don't send ping when we are not online
if (parent->m_iStatus != ID_STATUS_ONLINE)
return true;
handle_entry("activity_ping");
ChannelRequest *request = new ChannelRequest(this, ChannelRequest::PING);
http::response resp = sendRequest(request);
// Remember this last ping time
parent->m_pingTS = ::time(nullptr);
if (resp.data.empty() || resp.data.find("\"t\":\"pong\"") == resp.data.npos) {
// Something went wrong
return handle_error("activity_ping");
}
return handle_success("activity_ping");
}
int facebook_client::send_message(int seqid, MCONTACT hContact, const std::string &message_text, std::string *error_text, const std::string &captcha_persist_data, const std::string &captcha)
{
handle_entry("send_message");
bool isChatRoom = parent->isChatRoom(hContact);
ptrA userId(parent->getStringA(hContact, FACEBOOK_KEY_ID));
ptrA threadId(parent->getStringA(hContact, FACEBOOK_KEY_TID));
// Check if we have userId/threadId to be able to send message
if ((isChatRoom && (threadId == NULL || !mir_strcmp(threadId, "null"))) || (!isChatRoom && (userId == NULL || !mir_strcmp(userId, "null")))) {
// This shouldn't happen unless user manually deletes some data via Database Editor++
*error_text = Translate("Contact doesn't have required data in database.");
handle_error("send_message");
return SEND_MESSAGE_ERROR;
}
// Probably we can generate any random messageID, it just have to be numeric and don't start with "0". We will receive it in response as "client_message_id".
std::string messageId = utils::text::rand_string(10, "123456789", &this->random_);
http::response resp;
{
HttpRequest *request = new SendMessageRequest(this, userId, threadId, messageId.c_str(), message_text.c_str(), isChatRoom, captcha.c_str(), captcha_persist_data.c_str());
ScopedLock s(send_message_lock_);
resp = sendRequest(request);
*error_text = resp.error_text;
if (resp.error_number == 0) {
// Everything is OK
// Remember this message id
std::string mid = utils::text::source_get_value(&resp.data, 2, "\"message_id\":\"", "\"");
if (mid.empty())
mid = utils::text::source_get_value(&resp.data, 2, "\"mid\":\"", "\""); // TODO: This is probably not used anymore
// For classic contacts remember last message id
if (!parent->isChatRoom(hContact))
parent->setString(hContact, FACEBOOK_KEY_MESSAGE_ID, mid.c_str());
// Get timestamp
std::string timestamp = utils::text::source_get_value(&resp.data, 2, "\"timestamp\":", ",");
time_t time = utils::time::from_string(timestamp);
// Remember last action timestamp for history sync
parent->setDword(FACEBOOK_KEY_LAST_ACTION_TS, time);
// For classic conversation we try to replace timestamp of added event in OnPreCreateEvent()
if (seqid > 0)
messages_timestamp.insert(std::make_pair(seqid, time));
// We have this message in database, so ignore further tries (in channel) to add it again
messages_ignore.insert(std::make_pair(mid, 0));
}
}
switch (resp.error_number) {
case 0:
// Everything is OK
break;
// case 1356002: // You are offline (probably you can't use mercury or some other request when chat is offline)
case 1356003: // Contact is offline
parent->setWord(hContact, "Status", ID_STATUS_OFFLINE);
return SEND_MESSAGE_ERROR;
case 1356026: // Contact has alternative client
client_notify(TranslateT("Need confirmation for sending messages to other clients.\nOpen Facebook website and try to send message to this contact again!"));
return SEND_MESSAGE_ERROR;
case 1357007: // Security check (captcha) is required
{
std::string imageUrl = utils::text::html_entities_decode(utils::text::slashu_to_utf8(utils::text::source_get_value(&resp.data, 3, "img class=\\\"img\\\"", "src=\\\"", "\\\"")));
std::string captchaPersistData = utils::text::source_get_value(&resp.data, 3, "\\\"captcha_persist_data\\\"", "value=\\\"", "\\\"");
parent->debugLogA(" Got imageUrl (first): %s", imageUrl.c_str());
parent->debugLogA(" Got captchaPersistData (first): %s", captchaPersistData.c_str());
HttpRequest *request = new RefreshCaptchaRequest(this, captchaPersistData.c_str());
http::response capResp = sendRequest(request);
if (capResp.code == HTTP_CODE_OK) {
imageUrl = utils::text::html_entities_decode(utils::text::slashu_to_utf8(utils::text::source_get_value(&capResp.data, 3, "img class=\\\"img\\\"", "src=\\\"", "\\\"")));
captchaPersistData = utils::text::source_get_value(&capResp.data, 3, "\\\"captcha_persist_data\\\"", "value=\\\"", "\\\"");
parent->debugLogA(" Got imageUrl (second): %s", imageUrl.c_str());
parent->debugLogA(" Got captchaPersistData (second): %s", captchaPersistData.c_str());
std::string result;
if (!parent->RunCaptchaForm(imageUrl, result)) {
*error_text = Translate("User cancel captcha challenge.");
return SEND_MESSAGE_CANCEL;
}
return send_message(seqid, hContact, message_text, error_text, captchaPersistData, result);
}
}
return SEND_MESSAGE_CANCEL; // Cancel because we failed to load captcha image so we can't continue only with error
//case 1404123: // Blocked sending messages (with URLs) because Facebook think our computer is infected with malware
default: // Other error
parent->debugLogA("!!! Send message error #%d: %s", resp.error_number, resp.error_text.c_str());
return SEND_MESSAGE_ERROR;
}
switch (resp.code) {
case HTTP_CODE_OK:
handle_success("send_message");
return SEND_MESSAGE_OK;
case HTTP_CODE_FAKE_ERROR:
case HTTP_CODE_FAKE_DISCONNECTED:
default:
*error_text = Translate("Timeout when sending message.");
handle_error("send_message");
return SEND_MESSAGE_ERROR;
}
}
bool facebook_client::post_status(status_data *status)
{
if (status == nullptr || (status->text.empty() && status->url.empty()))
return false;
handle_entry("post_status");
if (status->isPage) {
// Switch to page identity by which name we will share this post
sendRequest(new SwitchIdentityRequest(this->dtsg_.c_str(), status->user_id.c_str()));
}
std::string linkData;
if (!status->url.empty()) {
HttpRequest *request = new LinkScraperRequest(this, status);
http::response resp = sendRequest(request);
std::string temp = utils::text::html_entities_decode(utils::text::slashu_to_utf8(resp.data));
std::string form = utils::text::source_get_value(&temp, 2, "