", "");
// 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);
}
this->self_.real_name = utils::text::remove_html(this->self_.real_name);
parent->debugLogA(" Got self real name: %s", this->self_.real_name.c_str());
parent->SaveName(NULL, &this->self_);
// Get avatar
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;
}
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");
std::string data = (online ? "visibility=1" : "visibility=0");
data += "&window_id=0";
data += "&fb_dtsg=" + dtsg_;
data += "&__user=" + self_.user_id;
http::response resp = flap(REQUEST_VISIBILITY, &data);
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 = flap(REQUEST_RECONNECT);
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());
// TODO: I'm not sure this goes to 0 after reconnect, or it is always same as chat_sequence_num_ (when watching website it was always same)
this->chat_msgs_recv_ = 0;
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 update
http::response resp = flap(REQUEST_MESSAGES_RECEIVE);
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\":\"", "\"");
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 == "fullReload" || type == "refresh") {
// Requested reload of page or relogin (due to some settings change, removing this session, etc.)
parent->debugLogA("!!! Requested %s", type.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());
if (type == "refresh") {
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()) {
// 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
// TODO: I'm not sure this is updated regarding "msg" received and reseted after reconnect, or it is always same as chat_sequence_num_ (when watching website it was always same)
this->chat_msgs_recv_++;
}
// 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()
{
handle_entry("activity_ping");
http::response resp = flap(REQUEST_ACTIVE_PING);
// Remember this last ping time
parent->m_pingTS = ::time(NULL);
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");
http::response resp;
std::string data;
if (!captcha.empty()) {
data += "&captcha_persist_data=" + captcha_persist_data;
data += "&recaptcha_challenge_field=";
data += "&captcha_response=" + captcha;
}
boolean 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;
}
data += "&message_batch[0][action_type]=ma-type:user-generated-message";
if (isChatRoom) {
data += "&message_batch[0][thread_id]=" + std::string(threadId);
} else {
data += "&message_batch[0][specific_to_list][0]=fbid:" + std::string(userId);
data += "&message_batch[0][specific_to_list][1]=fbid:" + this->self_.user_id;
data += "&message_batch[0][client_thread_id]=user:" + std::string(userId);
}
data += "&message_batch[0][thread_fbid]";
data += "&message_batch[0][author]=fbid:" + this->self_.user_id;
data += "&message_batch[0][author_email]";
data += "&message_batch[0][coordinates]";
data += "&message_batch[0][timestamp]=" + utils::time::mili_timestamp();
data += "&message_batch[0][timestamp_absolute]";
data += "&message_batch[0][timestamp_relative]";
data += "&message_batch[0][timestamp_time_passed]";
data += "&message_batch[0][is_unread]=false";
data += "&message_batch[0][is_forward]=false";
data += "&message_batch[0][is_filtered_content]=false";
data += "&message_batch[0][is_spoof_warning]=false";
data += "&message_batch[0][source]=source:chat:web";
data += "&message_batch[0][source_tags][0]=source:chat";
data += "&message_batch[0][body]=" + utils::url::encode(message_text);
data += "&message_batch[0][has_attachment]=false";
data += "&message_batch[0][html_body]=false";
data += "&message_batch[0][signatureID]";
data += "&message_batch[0][ui_push_phase]";
data += "&message_batch[0][status]=0";
data += "&message_batch[0][message_id]";
data += "&message_batch[0][manual_retry_cnt]";
data += "&client=mercury&__a=1&__dyn&__req&__rev";
data += "&fb_dtsg=" + this->dtsg_;
data += "&__user=" + this->self_.user_id;
data += "&ttstamp=" + ttstamp();
{
ScopedLock s(send_message_lock_);
resp = flap(REQUEST_MESSAGE_SEND_CHAT, &data);
*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\":\"", "\"");
// 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());
std::string data = "new_captcha_type=TFBCaptcha&skipped_captcha_data=" + captchaPersistData;
data += "&__dyn=&__req=&__rev=&__user=" + this->self_.user_id;
http::response resp = flap(REQUEST_CAPTCHA_REFRESH, NULL, &data);
if (resp.code == HTTP_CODE_OK) {
imageUrl = utils::text::html_entities_decode(utils::text::slashu_to_utf8(utils::text::source_get_value(&resp.data, 3, "img class=\\\"img\\\"", "src=\\\"", "\\\"")));
captchaPersistData = utils::text::source_get_value(&resp.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
}
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 == NULL || (status->text.empty() && status->url.empty()))
return false;
handle_entry("post_status");
if (status->isPage) {
std::string data = "fb_dtsg=" + this->dtsg_;
data += "&user_id=" + status->user_id;
data += "&url=" + std::string(FACEBOOK_URL_HOMEPAGE);
flap(REQUEST_IDENTITY_SWITCH, &data);
}
std::string data;
if (!status->url.empty()) {
data = "fb_dtsg=" + this->dtsg_;
data += "&targetid=" + (status->user_id.empty() ? this->self_.user_id : status->user_id);
data += "&xhpc_targetid=" + (status->user_id.empty() ? this->self_.user_id : status->user_id);
data += "&istimeline=1&composercontext=composer&onecolumn=1&nctr[_mod]=pagelet_timeline_recent&__a=1&ttstamp=" + ttstamp();
data += "&__user=" + (status->isPage && !status->user_id.empty() ? status->user_id : this->self_.user_id);
data += "&loaded_components[0]=maininput&loaded_components[1]=backdateicon&loaded_components[2]=withtaggericon&loaded_components[3]=cameraicon&loaded_components[4]=placetaggericon&loaded_components[5]=mainprivacywidget&loaded_components[6]=withtaggericon&loaded_components[7]=backdateicon&loaded_components[8]=placetaggericon&loaded_components[9]=cameraicon&loaded_components[10]=mainprivacywidget&loaded_components[11]=maininput&loaded_components[12]=explicitplaceinput&loaded_components[13]=hiddenplaceinput&loaded_components[14]=placenameinput&loaded_components[15]=hiddensessionid&loaded_components[16]=withtagger&loaded_components[17]=backdatepicker&loaded_components[18]=placetagger&loaded_components[19]=citysharericon";
http::response resp = flap(REQUEST_LINK_SCRAPER, &data, &status->url);
std::string temp = utils::text::html_entities_decode(utils::text::slashu_to_utf8(resp.data));
data = "&xhpc_context=profile&xhpc_ismeta=1&xhpc_timeline=1&xhpc_composerid=u_jsonp_2_0&is_explicit_place=&composertags_place=&composer_session_id=&composertags_city=&disable_location_sharing=false&composer_predicted_city=&nctr[_mod]=pagelet_composer&__a=1&__dyn=&__req=1f&ttstamp=" + ttstamp();
std::string form = utils::text::source_get_value(&temp, 2, "