mirror of
https://github.com/tdlight-team/tdlight-telegram-bot-api.git
synced 2024-12-27 04:35:50 +01:00
9040 lines
364 KiB
C++
9040 lines
364 KiB
C++
//
|
|
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
|
|
//
|
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
//
|
|
#include "telegram-bot-api/Client.h"
|
|
|
|
#include "telegram-bot-api/ClientParameters.h"
|
|
|
|
#include "td/actor/PromiseFuture.h"
|
|
#include "td/actor/SleepActor.h"
|
|
|
|
#include "td/db/TQueue.h"
|
|
|
|
#include "td/utils/base64.h"
|
|
#include "td/utils/filesystem.h"
|
|
#include "td/utils/HttpUrl.h"
|
|
#include "td/utils/JsonBuilder.h"
|
|
#include "td/utils/logging.h"
|
|
#include "td/utils/misc.h"
|
|
#include "td/utils/PathView.h"
|
|
#include "td/utils/port/Clocks.h"
|
|
#include "td/utils/port/path.h"
|
|
#include "td/utils/port/Stat.h"
|
|
#include "td/utils/Random.h"
|
|
#include "td/utils/Slice.h"
|
|
#include "td/utils/Span.h"
|
|
#include "td/utils/StackAllocator.h"
|
|
#include "td/utils/Status.h"
|
|
#include "td/utils/StringBuilder.h"
|
|
#include "td/utils/Time.h"
|
|
#include "td/utils/utf8.h"
|
|
|
|
#include <cstdlib>
|
|
|
|
namespace telegram_bot_api {
|
|
|
|
using td::Jsonable;
|
|
using td::JsonValueScope;
|
|
using td::JsonValue;
|
|
|
|
using td_api::make_object;
|
|
using td_api::move_object_as;
|
|
|
|
void Client::fail_query_with_error(PromisedQueryPtr query, int32 error_code, Slice error_message,
|
|
Slice default_message) {
|
|
if (error_code == 429) {
|
|
Slice prefix = "Too Many Requests: retry after ";
|
|
if (begins_with(error_message, prefix)) {
|
|
auto r_retry_after = td::to_integer_safe<int>(error_message.substr(prefix.size()));
|
|
if (r_retry_after.is_ok() && r_retry_after.ok() > 0) {
|
|
return query->set_retry_after_error(r_retry_after.ok());
|
|
}
|
|
}
|
|
LOG(ERROR) << "Wrong error message: " << error_message;
|
|
return fail_query(500, error_message, std::move(query));
|
|
}
|
|
int32 real_error_code = error_code;
|
|
Slice real_error_message = error_message;
|
|
if (error_code < 300 || error_code == 404) {
|
|
error_code = 400;
|
|
}
|
|
if (error_code == 400) {
|
|
if (!default_message.empty()) {
|
|
error_message = default_message;
|
|
}
|
|
if (error_message == "MESSAGE_NOT_MODIFIED") {
|
|
error_message = Slice(
|
|
"message is not modified: specified new message content and reply markup are exactly the same as a current "
|
|
"content and reply markup of the message");
|
|
} else if (error_message == "WC_CONVERT_URL_INVALID" || error_message == "EXTERNAL_URL_INVALID") {
|
|
error_message = "Wrong HTTP URL specified";
|
|
} else if (error_message == "WEBPAGE_CURL_FAILED") {
|
|
error_message = "Failed to get HTTP URL content";
|
|
} else if (error_message == "WEBPAGE_MEDIA_EMPTY") {
|
|
error_message = "Wrong type of the web page content";
|
|
} else if (error_message == "MEDIA_GROUPED_INVALID") {
|
|
error_message = "Can't use the media of the specified type in the album";
|
|
} else if (error_message == "REPLY_MARKUP_TOO_LONG") {
|
|
error_message = Slice("reply markup is too long");
|
|
} else if (error_message == "INPUT_USER_DEACTIVATED") {
|
|
error_code = 403;
|
|
error_message = Slice("Forbidden: user is deactivated");
|
|
} else if (error_message == "USER_IS_BLOCKED") {
|
|
error_code = 403;
|
|
error_message = Slice("bot was blocked by the user");
|
|
} else if (error_message == "USER_ADMIN_INVALID") {
|
|
error_code = 400;
|
|
error_message = Slice("user is an administrator of the chat");
|
|
} else if (error_message == "File generation failed") {
|
|
error_code = 400;
|
|
error_message = Slice("can't upload file by URL");
|
|
} else if (error_message == "CHAT_ABOUT_NOT_MODIFIED") {
|
|
error_code = 400;
|
|
error_message = Slice("chat description is not modified");
|
|
} else if (error_message == "PACK_SHORT_NAME_INVALID") {
|
|
error_code = 400;
|
|
error_message = Slice("invalid sticker set name is specified");
|
|
} else if (error_message == "PACK_SHORT_NAME_OCCUPIED") {
|
|
error_code = 400;
|
|
error_message = Slice("sticker set name is already occupied");
|
|
} else if (error_message == "STICKER_EMOJI_INVALID") {
|
|
error_code = 400;
|
|
error_message = Slice("invalid sticker emojis");
|
|
} else if (error_message == "QUERY_ID_INVALID") {
|
|
error_code = 400;
|
|
error_message = Slice("query is too old and response timeout expired or query ID is invalid");
|
|
} else if (error_message == "MESSAGE_DELETE_FORBIDDEN") {
|
|
error_code = 400;
|
|
error_message = Slice("message can't be deleted");
|
|
} else if (error_message == "Requested data is inaccessible") {
|
|
LOG(ERROR) << "Receive 'Requested data is inaccessible' from " << *query;
|
|
}
|
|
}
|
|
Slice prefix;
|
|
switch (error_code) {
|
|
case 400:
|
|
prefix = Slice("Bad Request");
|
|
break;
|
|
case 401:
|
|
prefix = Slice("Unauthorized");
|
|
break;
|
|
case 403:
|
|
prefix = Slice("Forbidden");
|
|
break;
|
|
case 500:
|
|
prefix = Slice("Internal Server Error");
|
|
break;
|
|
default:
|
|
LOG(ERROR) << "Unsupported error " << real_error_code << ": " << real_error_message;
|
|
return fail_query(400, PSLICE() << "Bad Request: " << error_message, std::move(query));
|
|
}
|
|
|
|
if (begins_with(error_message, prefix)) {
|
|
return fail_query(error_code, error_message, std::move(query));
|
|
} else {
|
|
td::string error_str = prefix.str();
|
|
if (error_message.empty()) {
|
|
LOG(ERROR) << "Empty error message with code " << real_error_code;
|
|
} else {
|
|
error_str += ": ";
|
|
if (error_message.size() >= 2u &&
|
|
(error_message[1] == '_' || ('A' <= error_message[1] && error_message[1] <= 'Z'))) {
|
|
error_str += error_message.str();
|
|
} else {
|
|
error_str += td::to_lower(error_message[0]);
|
|
error_str += error_message.substr(1).str();
|
|
}
|
|
}
|
|
return fail_query(error_code, error_str, std::move(query));
|
|
}
|
|
}
|
|
|
|
void Client::fail_query_with_error(PromisedQueryPtr &&query, object_ptr<td_api::error> error, Slice default_message) {
|
|
fail_query_with_error(std::move(query), error->code_, error->message_, default_message);
|
|
}
|
|
|
|
Client::Client(td::ActorShared<> parent, const td::string &bot_token, bool is_test_dc, int64 tqueue_id,
|
|
std::shared_ptr<const ClientParameters> parameters, td::ActorId<BotStatActor> stat_actor)
|
|
: parent_(std::move(parent))
|
|
, bot_token_(bot_token)
|
|
, bot_token_id_("<unknown>")
|
|
, is_test_dc_(is_test_dc)
|
|
, tqueue_id_(tqueue_id)
|
|
, parameters_(std::move(parameters))
|
|
, stat_actor_(std::move(stat_actor)) {
|
|
messages_lru_root_.lru_next = &messages_lru_root_;
|
|
messages_lru_root_.lru_prev = &messages_lru_root_;
|
|
|
|
static auto is_inited = init_methods();
|
|
CHECK(is_inited);
|
|
}
|
|
|
|
bool Client::init_methods() {
|
|
methods_.emplace("getme", &Client::process_get_me_query);
|
|
methods_.emplace("getmycommands", &Client::process_get_my_commands_query);
|
|
methods_.emplace("setmycommands", &Client::process_set_my_commands_query);
|
|
methods_.emplace("getuserprofilephotos", &Client::process_get_user_profile_photos_query);
|
|
methods_.emplace("sendmessage", &Client::process_send_message_query);
|
|
methods_.emplace("sendanimation", &Client::process_send_animation_query);
|
|
methods_.emplace("sendaudio", &Client::process_send_audio_query);
|
|
methods_.emplace("senddice", &Client::process_send_dice_query);
|
|
methods_.emplace("senddocument", &Client::process_send_document_query);
|
|
methods_.emplace("sendphoto", &Client::process_send_photo_query);
|
|
methods_.emplace("sendsticker", &Client::process_send_sticker_query);
|
|
methods_.emplace("sendvideo", &Client::process_send_video_query);
|
|
methods_.emplace("sendvideonote", &Client::process_send_video_note_query);
|
|
methods_.emplace("sendvoice", &Client::process_send_voice_query);
|
|
methods_.emplace("sendgame", &Client::process_send_game_query);
|
|
methods_.emplace("sendinvoice", &Client::process_send_invoice_query);
|
|
methods_.emplace("sendlocation", &Client::process_send_location_query);
|
|
methods_.emplace("sendvenue", &Client::process_send_venue_query);
|
|
methods_.emplace("sendcontact", &Client::process_send_contact_query);
|
|
methods_.emplace("sendpoll", &Client::process_send_poll_query);
|
|
methods_.emplace("stoppoll", &Client::process_stop_poll_query);
|
|
methods_.emplace("copymessage", &Client::process_copy_message_query);
|
|
methods_.emplace("forwardmessage", &Client::process_forward_message_query);
|
|
methods_.emplace("sendmediagroup", &Client::process_send_media_group_query);
|
|
methods_.emplace("sendchataction", &Client::process_send_chat_action_query);
|
|
methods_.emplace("editmessagetext", &Client::process_edit_message_text_query);
|
|
methods_.emplace("editmessagelivelocation", &Client::process_edit_message_live_location_query);
|
|
methods_.emplace("stopmessagelivelocation", &Client::process_edit_message_live_location_query);
|
|
methods_.emplace("editmessagemedia", &Client::process_edit_message_media_query);
|
|
methods_.emplace("editmessagecaption", &Client::process_edit_message_caption_query);
|
|
methods_.emplace("editmessagereplymarkup", &Client::process_edit_message_reply_markup_query);
|
|
methods_.emplace("deletemessage", &Client::process_delete_message_query);
|
|
methods_.emplace("setgamescore", &Client::process_set_game_score_query);
|
|
methods_.emplace("getgamehighscores", &Client::process_get_game_high_scores_query);
|
|
methods_.emplace("answerinlinequery", &Client::process_answer_inline_query_query);
|
|
methods_.emplace("answercallbackquery", &Client::process_answer_callback_query_query);
|
|
methods_.emplace("answershippingquery", &Client::process_answer_shipping_query_query);
|
|
methods_.emplace("answerprecheckoutquery", &Client::process_answer_pre_checkout_query_query);
|
|
methods_.emplace("exportchatinvitelink", &Client::process_export_chat_invite_link_query);
|
|
methods_.emplace("getchat", &Client::process_get_chat_query);
|
|
methods_.emplace("setchatphoto", &Client::process_set_chat_photo_query);
|
|
methods_.emplace("deletechatphoto", &Client::process_delete_chat_photo_query);
|
|
methods_.emplace("setchattitle", &Client::process_set_chat_title_query);
|
|
methods_.emplace("setchatpermissions", &Client::process_set_chat_permissions_query);
|
|
methods_.emplace("setchatdescription", &Client::process_set_chat_description_query);
|
|
methods_.emplace("pinchatmessage", &Client::process_pin_chat_message_query);
|
|
methods_.emplace("unpinchatmessage", &Client::process_unpin_chat_message_query);
|
|
methods_.emplace("unpinallchatmessages", &Client::process_unpin_all_chat_messages_query);
|
|
methods_.emplace("setchatstickerset", &Client::process_set_chat_sticker_set_query);
|
|
methods_.emplace("deletechatstickerset", &Client::process_delete_chat_sticker_set_query);
|
|
methods_.emplace("getchatmember", &Client::process_get_chat_member_query);
|
|
methods_.emplace("getchatadministrators", &Client::process_get_chat_administrators_query);
|
|
methods_.emplace("getchatmembercount", &Client::process_get_chat_member_count_query);
|
|
methods_.emplace("getchatmemberscount", &Client::process_get_chat_member_count_query);
|
|
methods_.emplace("leavechat", &Client::process_leave_chat_query);
|
|
methods_.emplace("promotechatmember", &Client::process_promote_chat_member_query);
|
|
methods_.emplace("setchatadministratorcustomtitle", &Client::process_set_chat_administrator_custom_title_query);
|
|
methods_.emplace("banchatmember", &Client::process_ban_chat_member_query);
|
|
methods_.emplace("kickchatmember", &Client::process_ban_chat_member_query);
|
|
methods_.emplace("restrictchatmember", &Client::process_restrict_chat_member_query);
|
|
methods_.emplace("unbanchatmember", &Client::process_unban_chat_member_query);
|
|
methods_.emplace("getstickerset", &Client::process_get_sticker_set_query);
|
|
methods_.emplace("uploadstickerfile", &Client::process_upload_sticker_file_query);
|
|
methods_.emplace("createnewstickerset", &Client::process_create_new_sticker_set_query);
|
|
methods_.emplace("addstickertoset", &Client::process_add_sticker_to_set_query);
|
|
methods_.emplace("setstickersetthumb", &Client::process_set_sticker_set_thumb_query);
|
|
methods_.emplace("setstickerpositioninset", &Client::process_set_sticker_position_in_set_query);
|
|
methods_.emplace("deletestickerfromset", &Client::process_delete_sticker_from_set_query);
|
|
methods_.emplace("setpassportdataerrors", &Client::process_set_passport_data_errors_query);
|
|
methods_.emplace("sendcustomrequest", &Client::process_send_custom_request_query);
|
|
methods_.emplace("answercustomquery", &Client::process_answer_custom_query_query);
|
|
methods_.emplace("getupdates", &Client::process_get_updates_query);
|
|
methods_.emplace("setwebhook", &Client::process_set_webhook_query);
|
|
methods_.emplace("deletewebhook", &Client::process_set_webhook_query);
|
|
methods_.emplace("getwebhookinfo", &Client::process_get_webhook_info_query);
|
|
methods_.emplace("getfile", &Client::process_get_file_query);
|
|
return true;
|
|
}
|
|
|
|
class Client::JsonFile : public Jsonable {
|
|
public:
|
|
JsonFile(const td_api::file *file, const Client *client) : file_(file), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
client_->json_store_file(object, file_, true);
|
|
}
|
|
|
|
private:
|
|
const td_api::file *file_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonDatedFile : public Jsonable {
|
|
public:
|
|
JsonDatedFile(const td_api::datedFile *file, const Client *client) : file_(file), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
client_->json_store_file(object, file_->file_.get());
|
|
object("file_date", file_->date_);
|
|
}
|
|
|
|
private:
|
|
const td_api::datedFile *file_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonDatedFiles : public Jsonable {
|
|
public:
|
|
JsonDatedFiles(const td::vector<td_api::object_ptr<td_api::datedFile>> &files, const Client *client)
|
|
: files_(files), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto array = scope->enter_array();
|
|
for (auto &file : files_) {
|
|
array << JsonDatedFile(file.get(), client_);
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td::vector<td_api::object_ptr<td_api::datedFile>> &files_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonUser : public Jsonable {
|
|
public:
|
|
JsonUser(int32 user_id, const Client *client, bool full_bot_info = false)
|
|
: user_id_(user_id), client_(client), full_bot_info_(full_bot_info) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
auto user_info = client_->get_user_info(user_id_);
|
|
object("id", user_id_);
|
|
bool is_bot = user_info != nullptr && user_info->type == UserInfo::Type::Bot;
|
|
object("is_bot", td::JsonBool(is_bot));
|
|
object("first_name", user_info == nullptr ? "" : user_info->first_name);
|
|
if (user_info != nullptr && !user_info->last_name.empty()) {
|
|
object("last_name", user_info->last_name);
|
|
}
|
|
if (user_info != nullptr && !user_info->username.empty()) {
|
|
object("username", user_info->username);
|
|
}
|
|
if (user_info != nullptr && !user_info->language_code.empty()) {
|
|
object("language_code", user_info->language_code);
|
|
}
|
|
if (is_bot && full_bot_info_) {
|
|
object("can_join_groups", td::JsonBool(user_info->can_join_groups));
|
|
object("can_read_all_group_messages", td::JsonBool(user_info->can_read_all_group_messages));
|
|
object("supports_inline_queries", td::JsonBool(user_info->is_inline_bot));
|
|
}
|
|
}
|
|
|
|
private:
|
|
int32 user_id_;
|
|
const Client *client_;
|
|
bool full_bot_info_;
|
|
};
|
|
|
|
class Client::JsonUsers : public Jsonable {
|
|
public:
|
|
JsonUsers(const td::vector<int32> &user_ids, const Client *client) : user_ids_(user_ids), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto array = scope->enter_array();
|
|
for (auto &user_id : user_ids_) {
|
|
array << JsonUser(user_id, client_);
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td::vector<int32> &user_ids_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonEntity : public Jsonable {
|
|
public:
|
|
JsonEntity(const td_api::textEntity *entity, const Client *client) : entity_(entity), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("offset", entity_->offset_);
|
|
object("length", entity_->length_);
|
|
switch (entity_->type_->get_id()) {
|
|
case td_api::textEntityTypeMention::ID:
|
|
object("type", "mention");
|
|
break;
|
|
case td_api::textEntityTypeHashtag::ID:
|
|
object("type", "hashtag");
|
|
break;
|
|
case td_api::textEntityTypeCashtag::ID:
|
|
object("type", "cashtag");
|
|
break;
|
|
case td_api::textEntityTypeBotCommand::ID:
|
|
object("type", "bot_command");
|
|
break;
|
|
case td_api::textEntityTypeUrl::ID:
|
|
object("type", "url");
|
|
break;
|
|
case td_api::textEntityTypeEmailAddress::ID:
|
|
object("type", "email");
|
|
break;
|
|
case td_api::textEntityTypePhoneNumber::ID:
|
|
object("type", "phone_number");
|
|
break;
|
|
case td_api::textEntityTypeBankCardNumber::ID:
|
|
object("type", "bank_card_number");
|
|
break;
|
|
case td_api::textEntityTypeBold::ID:
|
|
object("type", "bold");
|
|
break;
|
|
case td_api::textEntityTypeItalic::ID:
|
|
object("type", "italic");
|
|
break;
|
|
case td_api::textEntityTypeUnderline::ID:
|
|
object("type", "underline");
|
|
break;
|
|
case td_api::textEntityTypeStrikethrough::ID:
|
|
object("type", "strikethrough");
|
|
break;
|
|
case td_api::textEntityTypeCode::ID:
|
|
object("type", "code");
|
|
break;
|
|
case td_api::textEntityTypePre::ID:
|
|
object("type", "pre");
|
|
break;
|
|
case td_api::textEntityTypePreCode::ID: {
|
|
auto entity = static_cast<const td_api::textEntityTypePreCode *>(entity_->type_.get());
|
|
object("type", "pre");
|
|
object("language", entity->language_);
|
|
break;
|
|
}
|
|
case td_api::textEntityTypeTextUrl::ID: {
|
|
auto entity = static_cast<const td_api::textEntityTypeTextUrl *>(entity_->type_.get());
|
|
object("type", "text_link");
|
|
object("url", entity->url_);
|
|
break;
|
|
}
|
|
case td_api::textEntityTypeMentionName::ID: {
|
|
auto entity = static_cast<const td_api::textEntityTypeMentionName *>(entity_->type_.get());
|
|
object("type", "text_mention");
|
|
object("user", JsonUser(entity->user_id_, client_));
|
|
break;
|
|
}
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td_api::textEntity *entity_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonVectorEntities : public Jsonable {
|
|
public:
|
|
JsonVectorEntities(const td::vector<object_ptr<td_api::textEntity>> &entities, const Client *client)
|
|
: entities_(entities), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto array = scope->enter_array();
|
|
for (auto &entity : entities_) {
|
|
if (entity->type_->get_id() != td_api::textEntityTypeBankCardNumber::ID) {
|
|
array << JsonEntity(entity.get(), client_);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td::vector<object_ptr<td_api::textEntity>> &entities_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonLocation : public Jsonable {
|
|
public:
|
|
explicit JsonLocation(const td_api::location *location, double expires_in = 0.0, int32 live_period = 0,
|
|
int32 heading = 0, int32 proximity_alert_radius = 0)
|
|
: location_(location)
|
|
, expires_in_(expires_in)
|
|
, live_period_(live_period)
|
|
, heading_(heading)
|
|
, proximity_alert_radius_(proximity_alert_radius) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("latitude", location_->latitude_);
|
|
object("longitude", location_->longitude_);
|
|
if (expires_in_ > 0.0) {
|
|
object("live_period", live_period_);
|
|
if (heading_ > 0) {
|
|
object("heading", heading_);
|
|
}
|
|
if (proximity_alert_radius_ > 0) {
|
|
object("proximity_alert_radius", proximity_alert_radius_);
|
|
}
|
|
}
|
|
if (location_->horizontal_accuracy_ > 0) {
|
|
object("horizontal_accuracy", location_->horizontal_accuracy_);
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td_api::location *location_;
|
|
double expires_in_;
|
|
int32 live_period_;
|
|
int32 heading_;
|
|
int32 proximity_alert_radius_;
|
|
};
|
|
|
|
class Client::JsonChatPermissions : public Jsonable {
|
|
public:
|
|
explicit JsonChatPermissions(const td_api::chatPermissions *chat_permissions) : chat_permissions_(chat_permissions) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
Client::json_store_permissions(object, chat_permissions_);
|
|
}
|
|
|
|
private:
|
|
const td_api::chatPermissions *chat_permissions_;
|
|
};
|
|
|
|
class Client::JsonChatPhotoInfo : public Jsonable {
|
|
public:
|
|
explicit JsonChatPhotoInfo(const td_api::chatPhotoInfo *chat_photo) : chat_photo_(chat_photo) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("small_file_id", chat_photo_->small_->remote_->id_);
|
|
object("small_file_unique_id", chat_photo_->small_->remote_->unique_id_);
|
|
object("big_file_id", chat_photo_->big_->remote_->id_);
|
|
object("big_file_unique_id", chat_photo_->big_->remote_->unique_id_);
|
|
}
|
|
|
|
private:
|
|
const td_api::chatPhotoInfo *chat_photo_;
|
|
};
|
|
|
|
class Client::JsonChatLocation : public Jsonable {
|
|
public:
|
|
explicit JsonChatLocation(const td_api::chatLocation *chat_location) : chat_location_(chat_location) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("location", JsonLocation(chat_location_->location_.get()));
|
|
object("address", chat_location_->address_);
|
|
}
|
|
|
|
private:
|
|
const td_api::chatLocation *chat_location_;
|
|
};
|
|
|
|
class Client::JsonMessage : public Jsonable {
|
|
public:
|
|
JsonMessage(const MessageInfo *message, bool need_reply, const td::string &source, const Client *client)
|
|
: message_(message), need_reply_(need_reply), source_(source), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const;
|
|
|
|
private:
|
|
const MessageInfo *message_;
|
|
bool need_reply_;
|
|
const td::string &source_;
|
|
const Client *client_;
|
|
|
|
void add_caption(td::JsonObjectScope &object, const object_ptr<td_api::formattedText> &caption) const {
|
|
CHECK(caption != nullptr);
|
|
if (!caption->text_.empty()) {
|
|
object("caption", caption->text_);
|
|
|
|
if (!caption->entities_.empty()) {
|
|
object("caption_entities", JsonVectorEntities(caption->entities_, client_));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
class Client::JsonChat : public Jsonable {
|
|
public:
|
|
JsonChat(int64 chat_id, bool is_full, const Client *client, int64 pinned_message_id = -1)
|
|
: chat_id_(chat_id), is_full_(is_full), client_(client), pinned_message_id_(pinned_message_id) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto chat_info = client_->get_chat(chat_id_);
|
|
CHECK(chat_info != nullptr);
|
|
auto object = scope->enter_object();
|
|
object("id", chat_id_);
|
|
switch (chat_info->type) {
|
|
case ChatInfo::Type::Private: {
|
|
auto user_info = client_->get_user_info(chat_info->user_id);
|
|
CHECK(user_info != nullptr);
|
|
object("first_name", user_info->first_name);
|
|
if (!user_info->last_name.empty()) {
|
|
object("last_name", user_info->last_name);
|
|
}
|
|
if (!user_info->username.empty()) {
|
|
object("username", user_info->username);
|
|
}
|
|
object("type", "private");
|
|
if (is_full_) {
|
|
if (!user_info->bio.empty()) {
|
|
object("bio", user_info->bio);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case ChatInfo::Type::Group: {
|
|
object("title", chat_info->title);
|
|
object("type", "group");
|
|
|
|
const auto *permissions = chat_info->permissions.get();
|
|
|
|
auto group_info = client_->get_group_info(chat_info->group_id);
|
|
CHECK(group_info != nullptr);
|
|
if (is_full_) {
|
|
if (!group_info->description.empty()) {
|
|
object("description", group_info->description);
|
|
}
|
|
if (!group_info->invite_link.empty()) {
|
|
object("invite_link", group_info->invite_link);
|
|
}
|
|
object("permissions", JsonChatPermissions(permissions));
|
|
}
|
|
auto everyone_is_administrator = permissions->can_send_messages_ && permissions->can_send_media_messages_ &&
|
|
permissions->can_send_polls_ && permissions->can_send_other_messages_ &&
|
|
permissions->can_add_web_page_previews_ && permissions->can_change_info_ &&
|
|
permissions->can_invite_users_ && permissions->can_pin_messages_;
|
|
object("all_members_are_administrators", td::JsonBool(everyone_is_administrator));
|
|
break;
|
|
}
|
|
case ChatInfo::Type::Supergroup: {
|
|
object("title", chat_info->title);
|
|
|
|
auto supergroup_info = client_->get_supergroup_info(chat_info->supergroup_id);
|
|
CHECK(supergroup_info != nullptr);
|
|
if (!supergroup_info->username.empty()) {
|
|
object("username", supergroup_info->username);
|
|
}
|
|
|
|
if (supergroup_info->is_supergroup) {
|
|
object("type", "supergroup");
|
|
} else {
|
|
object("type", "channel");
|
|
}
|
|
if (is_full_) {
|
|
if (!supergroup_info->description.empty()) {
|
|
object("description", supergroup_info->description);
|
|
}
|
|
if (!supergroup_info->invite_link.empty()) {
|
|
object("invite_link", supergroup_info->invite_link);
|
|
}
|
|
if (supergroup_info->sticker_set_id != 0) {
|
|
auto sticker_set_name = client_->get_sticker_set_name(supergroup_info->sticker_set_id);
|
|
if (!sticker_set_name.empty()) {
|
|
object("sticker_set_name", sticker_set_name);
|
|
} else {
|
|
LOG(ERROR) << "Not found chat sticker set " << supergroup_info->sticker_set_id;
|
|
}
|
|
}
|
|
if (supergroup_info->can_set_sticker_set) {
|
|
object("can_set_sticker_set", td::JsonTrue());
|
|
}
|
|
if (supergroup_info->is_supergroup) {
|
|
object("permissions", JsonChatPermissions(chat_info->permissions.get()));
|
|
}
|
|
if (supergroup_info->slow_mode_delay != 0) {
|
|
object("slow_mode_delay", supergroup_info->slow_mode_delay);
|
|
}
|
|
if (supergroup_info->linked_chat_id != 0) {
|
|
object("linked_chat_id", supergroup_info->linked_chat_id);
|
|
}
|
|
if (supergroup_info->location != nullptr) {
|
|
object("location", JsonChatLocation(supergroup_info->location.get()));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case ChatInfo::Type::Unknown:
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
if (is_full_) {
|
|
if (chat_info->photo != nullptr) {
|
|
object("photo", JsonChatPhotoInfo(chat_info->photo.get()));
|
|
}
|
|
if (pinned_message_id_ != 0) {
|
|
CHECK(pinned_message_id_ != -1);
|
|
const MessageInfo *pinned_message = client_->get_message(chat_id_, pinned_message_id_);
|
|
if (pinned_message != nullptr) {
|
|
object("pinned_message", JsonMessage(pinned_message, false, "pin in JsonChat", client_));
|
|
} else {
|
|
LOG(ERROR) << "Pinned unknown, inaccessible or deleted message " << pinned_message_id_;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
int64 chat_id_;
|
|
bool is_full_;
|
|
const Client *client_;
|
|
int64 pinned_message_id_;
|
|
};
|
|
|
|
class Client::JsonMessageSender : public Jsonable {
|
|
public:
|
|
JsonMessageSender(const td_api::MessageSender *sender, const Client *client) : sender_(sender), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
CHECK(sender_ != nullptr);
|
|
switch (sender_->get_id()) {
|
|
case td_api::messageSenderUser::ID: {
|
|
auto sender_user_id = static_cast<const td_api::messageSenderUser *>(sender_)->user_id_;
|
|
JsonUser(sender_user_id, client_).store(scope);
|
|
break;
|
|
}
|
|
case td_api::messageSenderChat::ID: {
|
|
auto sender_chat_id = static_cast<const td_api::messageSenderChat *>(sender_)->chat_id_;
|
|
JsonChat(sender_chat_id, false, client_).store(scope);
|
|
break;
|
|
}
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td_api::MessageSender *sender_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonMessages : public Jsonable {
|
|
public:
|
|
explicit JsonMessages(const td::vector<td::string> &messages) : messages_(messages) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto array = scope->enter_array();
|
|
for (auto &message : messages_) {
|
|
array << td::JsonRaw(message);
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td::vector<td::string> &messages_;
|
|
};
|
|
|
|
class Client::JsonAnimation : public Jsonable {
|
|
public:
|
|
JsonAnimation(const td_api::animation *animation, bool as_document, const Client *client)
|
|
: animation_(animation), as_document_(as_document), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
if (!animation_->file_name_.empty()) {
|
|
object("file_name", animation_->file_name_);
|
|
}
|
|
if (!animation_->mime_type_.empty()) {
|
|
object("mime_type", animation_->mime_type_);
|
|
}
|
|
if (!as_document_) {
|
|
object("duration", animation_->duration_);
|
|
object("width", animation_->width_);
|
|
object("height", animation_->height_);
|
|
}
|
|
client_->json_store_thumbnail(object, animation_->thumbnail_.get());
|
|
client_->json_store_file(object, animation_->animation_.get());
|
|
}
|
|
|
|
private:
|
|
const td_api::animation *animation_;
|
|
bool as_document_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonAudio : public Jsonable {
|
|
public:
|
|
JsonAudio(const td_api::audio *audio, const Client *client) : audio_(audio), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("duration", audio_->duration_);
|
|
if (!audio_->file_name_.empty()) {
|
|
object("file_name", audio_->file_name_);
|
|
}
|
|
if (!audio_->mime_type_.empty()) {
|
|
object("mime_type", audio_->mime_type_);
|
|
}
|
|
if (!audio_->title_.empty()) {
|
|
object("title", audio_->title_);
|
|
}
|
|
if (!audio_->performer_.empty()) {
|
|
object("performer", audio_->performer_);
|
|
}
|
|
client_->json_store_thumbnail(object, audio_->album_cover_thumbnail_.get());
|
|
client_->json_store_file(object, audio_->audio_.get());
|
|
}
|
|
|
|
private:
|
|
const td_api::audio *audio_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonDocument : public Jsonable {
|
|
public:
|
|
JsonDocument(const td_api::document *document, const Client *client) : document_(document), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
if (!document_->file_name_.empty()) {
|
|
object("file_name", document_->file_name_);
|
|
}
|
|
if (!document_->mime_type_.empty()) {
|
|
object("mime_type", document_->mime_type_);
|
|
}
|
|
client_->json_store_thumbnail(object, document_->thumbnail_.get());
|
|
client_->json_store_file(object, document_->document_.get());
|
|
}
|
|
|
|
private:
|
|
const td_api::document *document_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonPhotoSize : public Jsonable {
|
|
public:
|
|
JsonPhotoSize(const td_api::photoSize *photo_size, const Client *client) : photo_size_(photo_size), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
client_->json_store_file(object, photo_size_->photo_.get());
|
|
object("width", photo_size_->width_);
|
|
object("height", photo_size_->height_);
|
|
}
|
|
|
|
private:
|
|
const td_api::photoSize *photo_size_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonThumbnail : public Jsonable {
|
|
public:
|
|
JsonThumbnail(const td_api::thumbnail *thumbnail, const Client *client) : thumbnail_(thumbnail), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
client_->json_store_file(object, thumbnail_->file_.get());
|
|
object("width", thumbnail_->width_);
|
|
object("height", thumbnail_->height_);
|
|
}
|
|
|
|
private:
|
|
const td_api::thumbnail *thumbnail_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonPhoto : public Jsonable {
|
|
public:
|
|
JsonPhoto(const td_api::photo *photo, const Client *client) : photo_(photo), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto array = scope->enter_array();
|
|
for (auto &photo_size : photo_->sizes_) {
|
|
if (photo_size->type_ != "i" && photo_size->type_ != "t" && !photo_size->photo_->remote_->id_.empty()) {
|
|
array << JsonPhotoSize(photo_size.get(), client_);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td_api::photo *photo_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonChatPhoto : public Jsonable {
|
|
public:
|
|
JsonChatPhoto(const td_api::chatPhoto *photo, const Client *client) : photo_(photo), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto array = scope->enter_array();
|
|
for (auto &photo_size : photo_->sizes_) {
|
|
if (photo_size->type_ != "i" && photo_size->type_ != "t" && !photo_size->photo_->remote_->id_.empty()) {
|
|
array << JsonPhotoSize(photo_size.get(), client_);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td_api::chatPhoto *photo_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonMaskPosition : public Jsonable {
|
|
public:
|
|
explicit JsonMaskPosition(const td_api::maskPosition *mask_position) : mask_position_(mask_position) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("point", Client::MASK_POINTS[Client::mask_point_to_index(mask_position_->point_)]);
|
|
object("x_shift", mask_position_->x_shift_);
|
|
object("y_shift", mask_position_->y_shift_);
|
|
object("scale", mask_position_->scale_);
|
|
}
|
|
|
|
private:
|
|
const td_api::maskPosition *mask_position_;
|
|
};
|
|
|
|
class Client::JsonSticker : public Jsonable {
|
|
public:
|
|
JsonSticker(const td_api::sticker *sticker, const Client *client) : sticker_(sticker), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("width", sticker_->width_);
|
|
object("height", sticker_->height_);
|
|
if (!sticker_->emoji_.empty()) {
|
|
object("emoji", sticker_->emoji_);
|
|
}
|
|
auto set_name = client_->get_sticker_set_name(sticker_->set_id_);
|
|
if (!set_name.empty()) {
|
|
object("set_name", set_name);
|
|
}
|
|
object("is_animated", td::JsonBool(sticker_->is_animated_));
|
|
if (sticker_->mask_position_ != nullptr) {
|
|
object("mask_position", JsonMaskPosition(sticker_->mask_position_.get()));
|
|
}
|
|
client_->json_store_thumbnail(object, sticker_->thumbnail_.get());
|
|
client_->json_store_file(object, sticker_->sticker_.get());
|
|
}
|
|
|
|
private:
|
|
const td_api::sticker *sticker_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonStickers : public Jsonable {
|
|
public:
|
|
JsonStickers(const td::vector<object_ptr<td_api::sticker>> &stickers, const Client *client)
|
|
: stickers_(stickers), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto array = scope->enter_array();
|
|
for (auto &sticker : stickers_) {
|
|
array << JsonSticker(sticker.get(), client_);
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td::vector<object_ptr<td_api::sticker>> &stickers_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonVideo : public Jsonable {
|
|
public:
|
|
JsonVideo(const td_api::video *video, const Client *client) : video_(video), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("duration", video_->duration_);
|
|
object("width", video_->width_);
|
|
object("height", video_->height_);
|
|
if (!video_->file_name_.empty()) {
|
|
object("file_name", video_->file_name_);
|
|
}
|
|
if (!video_->mime_type_.empty()) {
|
|
object("mime_type", video_->mime_type_);
|
|
}
|
|
client_->json_store_thumbnail(object, video_->thumbnail_.get());
|
|
client_->json_store_file(object, video_->video_.get());
|
|
}
|
|
|
|
private:
|
|
const td_api::video *video_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonVideoNote : public Jsonable {
|
|
public:
|
|
JsonVideoNote(const td_api::videoNote *video_note, const Client *client) : video_note_(video_note), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("duration", video_note_->duration_);
|
|
object("length", video_note_->length_);
|
|
client_->json_store_thumbnail(object, video_note_->thumbnail_.get());
|
|
client_->json_store_file(object, video_note_->video_.get());
|
|
}
|
|
|
|
private:
|
|
const td_api::videoNote *video_note_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonVoiceNote : public Jsonable {
|
|
public:
|
|
JsonVoiceNote(const td_api::voiceNote *voice_note, const Client *client) : voice_note_(voice_note), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("duration", voice_note_->duration_);
|
|
if (!voice_note_->mime_type_.empty()) {
|
|
object("mime_type", voice_note_->mime_type_);
|
|
}
|
|
client_->json_store_file(object, voice_note_->voice_.get());
|
|
}
|
|
|
|
private:
|
|
const td_api::voiceNote *voice_note_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonVenue : public Jsonable {
|
|
public:
|
|
explicit JsonVenue(const td_api::venue *venue) : venue_(venue) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("location", JsonLocation(venue_->location_.get()));
|
|
object("title", venue_->title_);
|
|
object("address", venue_->address_);
|
|
if (venue_->provider_ == "foursquare") {
|
|
if (!venue_->id_.empty()) {
|
|
object("foursquare_id", venue_->id_);
|
|
}
|
|
if (!venue_->type_.empty()) {
|
|
object("foursquare_type", venue_->type_);
|
|
}
|
|
}
|
|
if (venue_->provider_ == "gplaces") {
|
|
if (!venue_->id_.empty()) {
|
|
object("google_place_id", venue_->id_);
|
|
}
|
|
if (!venue_->type_.empty()) {
|
|
object("google_place_type", venue_->type_);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td_api::venue *venue_;
|
|
};
|
|
|
|
class Client::JsonContact : public Jsonable {
|
|
public:
|
|
explicit JsonContact(const td_api::contact *contact) : contact_(contact) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("phone_number", contact_->phone_number_);
|
|
object("first_name", contact_->first_name_);
|
|
if (!contact_->last_name_.empty()) {
|
|
object("last_name", contact_->last_name_);
|
|
}
|
|
if (!contact_->vcard_.empty()) {
|
|
object("vcard", contact_->vcard_);
|
|
}
|
|
if (contact_->user_id_) {
|
|
object("user_id", contact_->user_id_);
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td_api::contact *contact_;
|
|
};
|
|
|
|
class Client::JsonDice : public Jsonable {
|
|
public:
|
|
JsonDice(const td::string &emoji, int32 value) : emoji_(emoji), value_(value) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("emoji", emoji_);
|
|
object("value", value_);
|
|
}
|
|
|
|
private:
|
|
const td::string &emoji_;
|
|
int32 value_;
|
|
};
|
|
|
|
class Client::JsonGame : public Jsonable {
|
|
public:
|
|
JsonGame(const td_api::game *game, const Client *client) : game_(game), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("title", game_->title_);
|
|
if (!game_->text_->text_.empty()) {
|
|
object("text", game_->text_->text_);
|
|
}
|
|
if (!game_->text_->entities_.empty()) {
|
|
object("text_entities", JsonVectorEntities(game_->text_->entities_, client_));
|
|
}
|
|
object("description", game_->description_);
|
|
CHECK(game_->photo_ != nullptr);
|
|
object("photo", JsonPhoto(game_->photo_.get(), client_));
|
|
if (game_->animation_ != nullptr) {
|
|
object("animation", JsonAnimation(game_->animation_.get(), false, client_));
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td_api::game *game_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonInvoice : public Jsonable {
|
|
public:
|
|
explicit JsonInvoice(const td_api::messageInvoice *invoice) : invoice_(invoice) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("title", invoice_->title_);
|
|
object("description", invoice_->description_);
|
|
object("start_parameter", invoice_->start_parameter_);
|
|
object("currency", invoice_->currency_);
|
|
object("total_amount", invoice_->total_amount_);
|
|
// skip photo
|
|
// skip is_test
|
|
// skip need_shipping_address
|
|
// skip receipt_message_id
|
|
}
|
|
|
|
private:
|
|
const td_api::messageInvoice *invoice_;
|
|
};
|
|
|
|
class Client::JsonPollOption : public Jsonable {
|
|
public:
|
|
explicit JsonPollOption(const td_api::pollOption *option) : option_(option) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("text", option_->text_);
|
|
object("voter_count", option_->voter_count_);
|
|
// ignore is_chosen
|
|
}
|
|
|
|
private:
|
|
const td_api::pollOption *option_;
|
|
};
|
|
|
|
class Client::JsonPoll : public Jsonable {
|
|
public:
|
|
JsonPoll(const td_api::poll *poll, const Client *client) : poll_(poll), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("id", td::to_string(poll_->id_));
|
|
object("question", poll_->question_);
|
|
object("options", td::json_array(poll_->options_, [](auto &option) { return JsonPollOption(option.get()); }));
|
|
object("total_voter_count", poll_->total_voter_count_);
|
|
if (poll_->open_period_ != 0 && poll_->close_date_ != 0) {
|
|
object("open_period", poll_->open_period_);
|
|
object("close_date", poll_->close_date_);
|
|
}
|
|
object("is_closed", td::JsonBool(poll_->is_closed_));
|
|
object("is_anonymous", td::JsonBool(poll_->is_anonymous_));
|
|
switch (poll_->type_->get_id()) {
|
|
case td_api::pollTypeQuiz::ID: {
|
|
object("type", "quiz");
|
|
object("allows_multiple_answers", td::JsonFalse());
|
|
auto quiz = static_cast<const td_api::pollTypeQuiz *>(poll_->type_.get());
|
|
int32 correct_option_id = quiz->correct_option_id_;
|
|
if (correct_option_id != -1) {
|
|
object("correct_option_id", correct_option_id);
|
|
}
|
|
auto *explanation = quiz->explanation_.get();
|
|
if (!explanation->text_.empty()) {
|
|
object("explanation", explanation->text_);
|
|
object("explanation_entities", JsonVectorEntities(explanation->entities_, client_));
|
|
}
|
|
break;
|
|
}
|
|
case td_api::pollTypeRegular::ID:
|
|
object("type", "regular");
|
|
object("allows_multiple_answers",
|
|
td::JsonBool(static_cast<const td_api::pollTypeRegular *>(poll_->type_.get())->allow_multiple_answers_));
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td_api::poll *poll_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonPollAnswer : public Jsonable {
|
|
public:
|
|
JsonPollAnswer(const td_api::updatePollAnswer *poll_answer, const Client *client)
|
|
: poll_answer_(poll_answer), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("poll_id", td::to_string(poll_answer_->poll_id_));
|
|
object("user", JsonUser(poll_answer_->user_id_, client_));
|
|
object("option_ids", td::json_array(poll_answer_->option_ids_, [](int32 option_id) { return option_id; }));
|
|
}
|
|
|
|
private:
|
|
const td_api::updatePollAnswer *poll_answer_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonAddress : public Jsonable {
|
|
public:
|
|
explicit JsonAddress(const td_api::address *address) : address_(address) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("country_code", address_->country_code_);
|
|
object("state", address_->state_);
|
|
object("city", address_->city_);
|
|
object("street_line1", address_->street_line1_);
|
|
object("street_line2", address_->street_line2_);
|
|
object("post_code", address_->postal_code_);
|
|
}
|
|
|
|
private:
|
|
const td_api::address *address_;
|
|
};
|
|
|
|
class Client::JsonOrderInfo : public Jsonable {
|
|
public:
|
|
explicit JsonOrderInfo(const td_api::orderInfo *order_info) : order_info_(order_info) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
if (!order_info_->name_.empty()) {
|
|
object("name", order_info_->name_);
|
|
}
|
|
if (!order_info_->phone_number_.empty()) {
|
|
object("phone_number", order_info_->phone_number_);
|
|
}
|
|
if (!order_info_->email_address_.empty()) {
|
|
object("email", order_info_->email_address_);
|
|
}
|
|
if (order_info_->shipping_address_ != nullptr) {
|
|
object("shipping_address", JsonAddress(order_info_->shipping_address_.get()));
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td_api::orderInfo *order_info_;
|
|
};
|
|
|
|
class Client::JsonSuccessfulPaymentBot : public Jsonable {
|
|
public:
|
|
explicit JsonSuccessfulPaymentBot(const td_api::messagePaymentSuccessfulBot *successful_payment)
|
|
: successful_payment_(successful_payment) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("currency", successful_payment_->currency_);
|
|
object("total_amount", successful_payment_->total_amount_);
|
|
if (!td::check_utf8(successful_payment_->invoice_payload_)) {
|
|
LOG(WARNING) << "Receive non-UTF-8 invoice payload";
|
|
object("invoice_payload", td::JsonRawString(successful_payment_->invoice_payload_));
|
|
} else {
|
|
object("invoice_payload", successful_payment_->invoice_payload_);
|
|
}
|
|
if (!successful_payment_->shipping_option_id_.empty()) {
|
|
object("shipping_option_id", successful_payment_->shipping_option_id_);
|
|
}
|
|
if (successful_payment_->order_info_ != nullptr) {
|
|
object("order_info", JsonOrderInfo(successful_payment_->order_info_.get()));
|
|
}
|
|
|
|
object("telegram_payment_charge_id", successful_payment_->telegram_payment_charge_id_);
|
|
object("provider_payment_charge_id", successful_payment_->provider_payment_charge_id_);
|
|
}
|
|
|
|
private:
|
|
const td_api::messagePaymentSuccessfulBot *successful_payment_;
|
|
};
|
|
|
|
class Client::JsonEncryptedPassportElement : public Jsonable {
|
|
public:
|
|
JsonEncryptedPassportElement(const td_api::encryptedPassportElement *element, const Client *client)
|
|
: element_(element), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
auto id = element_->type_->get_id();
|
|
object("type", Client::get_passport_element_type(id));
|
|
switch (id) {
|
|
case td_api::passportElementTypePhoneNumber::ID:
|
|
object("phone_number", element_->value_);
|
|
break;
|
|
case td_api::passportElementTypeEmailAddress::ID:
|
|
object("email", element_->value_);
|
|
break;
|
|
case td_api::passportElementTypePersonalDetails::ID:
|
|
case td_api::passportElementTypePassport::ID:
|
|
case td_api::passportElementTypeDriverLicense::ID:
|
|
case td_api::passportElementTypeIdentityCard::ID:
|
|
case td_api::passportElementTypeInternalPassport::ID:
|
|
case td_api::passportElementTypeAddress::ID:
|
|
object("data", td::base64_encode(element_->data_));
|
|
break;
|
|
}
|
|
switch (id) {
|
|
case td_api::passportElementTypeUtilityBill::ID:
|
|
case td_api::passportElementTypeBankStatement::ID:
|
|
case td_api::passportElementTypeRentalAgreement::ID:
|
|
case td_api::passportElementTypePassportRegistration::ID:
|
|
case td_api::passportElementTypeTemporaryRegistration::ID:
|
|
object("files", JsonDatedFiles(element_->files_, client_));
|
|
if (!element_->translation_.empty()) {
|
|
object("translation", JsonDatedFiles(element_->translation_, client_));
|
|
}
|
|
break;
|
|
}
|
|
switch (id) {
|
|
case td_api::passportElementTypePassport::ID:
|
|
case td_api::passportElementTypeDriverLicense::ID:
|
|
case td_api::passportElementTypeIdentityCard::ID:
|
|
case td_api::passportElementTypeInternalPassport::ID:
|
|
CHECK(element_->front_side_ != nullptr);
|
|
object("front_side", JsonDatedFile(element_->front_side_.get(), client_));
|
|
if (element_->reverse_side_ != nullptr) {
|
|
CHECK(id == td_api::passportElementTypeIdentityCard::ID ||
|
|
id == td_api::passportElementTypeDriverLicense::ID);
|
|
object("reverse_side", JsonDatedFile(element_->reverse_side_.get(), client_));
|
|
} else {
|
|
CHECK(id == td_api::passportElementTypePassport::ID || id == td_api::passportElementTypeInternalPassport::ID);
|
|
}
|
|
if (element_->selfie_ != nullptr) {
|
|
object("selfie", JsonDatedFile(element_->selfie_.get(), client_));
|
|
}
|
|
if (!element_->translation_.empty()) {
|
|
object("translation", JsonDatedFiles(element_->translation_, client_));
|
|
}
|
|
break;
|
|
}
|
|
object("hash", td::base64_encode(element_->hash_));
|
|
}
|
|
|
|
private:
|
|
const td_api::encryptedPassportElement *element_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonEncryptedCredentials : public Jsonable {
|
|
public:
|
|
explicit JsonEncryptedCredentials(const td_api::encryptedCredentials *credentials) : credentials_(credentials) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("data", td::base64_encode(credentials_->data_));
|
|
object("hash", td::base64_encode(credentials_->hash_));
|
|
object("secret", td::base64_encode(credentials_->secret_));
|
|
}
|
|
|
|
private:
|
|
const td_api::encryptedCredentials *credentials_;
|
|
};
|
|
|
|
class Client::JsonPassportData : public Jsonable {
|
|
public:
|
|
JsonPassportData(const td_api::messagePassportDataReceived *passport_data, const Client *client)
|
|
: passport_data_(passport_data), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("data", td::json_array(passport_data_->elements_, [client = client_](auto &element) {
|
|
return JsonEncryptedPassportElement(element.get(), client);
|
|
}));
|
|
object("credentials", JsonEncryptedCredentials(passport_data_->credentials_.get()));
|
|
}
|
|
|
|
private:
|
|
const td_api::messagePassportDataReceived *passport_data_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonProximityAlertTriggered : public Jsonable {
|
|
public:
|
|
JsonProximityAlertTriggered(const td_api::messageProximityAlertTriggered *proximity_alert_triggered,
|
|
const Client *client)
|
|
: proximity_alert_triggered_(proximity_alert_triggered), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("traveler", JsonMessageSender(proximity_alert_triggered_->traveler_.get(), client_));
|
|
object("watcher", JsonMessageSender(proximity_alert_triggered_->watcher_.get(), client_));
|
|
object("distance", proximity_alert_triggered_->distance_);
|
|
}
|
|
|
|
private:
|
|
const td_api::messageProximityAlertTriggered *proximity_alert_triggered_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonCallbackGame : public Jsonable {
|
|
public:
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
}
|
|
};
|
|
|
|
class Client::JsonInlineKeyboardButton : public Jsonable {
|
|
public:
|
|
explicit JsonInlineKeyboardButton(const td_api::inlineKeyboardButton *button) : button_(button) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("text", button_->text_);
|
|
switch (button_->type_->get_id()) {
|
|
case td_api::inlineKeyboardButtonTypeUrl::ID: {
|
|
auto type = static_cast<const td_api::inlineKeyboardButtonTypeUrl *>(button_->type_.get());
|
|
object("url", type->url_);
|
|
break;
|
|
}
|
|
case td_api::inlineKeyboardButtonTypeLoginUrl::ID: {
|
|
auto type = static_cast<const td_api::inlineKeyboardButtonTypeLoginUrl *>(button_->type_.get());
|
|
object("url", type->url_);
|
|
break;
|
|
}
|
|
case td_api::inlineKeyboardButtonTypeCallback::ID:
|
|
case td_api::inlineKeyboardButtonTypeCallbackWithPassword::ID: {
|
|
auto data = get_callback_data(button_->type_);
|
|
if (!td::check_utf8(data)) {
|
|
object("callback_data", td::JsonRawString(data));
|
|
} else {
|
|
object("callback_data", data);
|
|
}
|
|
break;
|
|
}
|
|
case td_api::inlineKeyboardButtonTypeCallbackGame::ID:
|
|
object("callback_game", JsonCallbackGame());
|
|
break;
|
|
case td_api::inlineKeyboardButtonTypeSwitchInline::ID: {
|
|
auto type = static_cast<const td_api::inlineKeyboardButtonTypeSwitchInline *>(button_->type_.get());
|
|
if (type->in_current_chat_) {
|
|
object("switch_inline_query_current_chat", type->query_);
|
|
} else {
|
|
object("switch_inline_query", type->query_);
|
|
}
|
|
break;
|
|
}
|
|
case td_api::inlineKeyboardButtonTypeBuy::ID:
|
|
object("pay", td::JsonTrue());
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td_api::inlineKeyboardButton *button_;
|
|
};
|
|
|
|
class Client::JsonInlineKeyboard : public Jsonable {
|
|
public:
|
|
explicit JsonInlineKeyboard(const td_api::replyMarkupInlineKeyboard *inline_keyboard)
|
|
: inline_keyboard_(inline_keyboard) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto array = scope->enter_array();
|
|
for (auto &row : inline_keyboard_->rows_) {
|
|
array << td::json_array(row, [](auto &button) { return JsonInlineKeyboardButton(button.get()); });
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td_api::replyMarkupInlineKeyboard *inline_keyboard_;
|
|
};
|
|
|
|
class Client::JsonReplyMarkup : public Jsonable {
|
|
public:
|
|
explicit JsonReplyMarkup(const td_api::ReplyMarkup *reply_markup) : reply_markup_(reply_markup) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
CHECK(reply_markup_->get_id() == td_api::replyMarkupInlineKeyboard::ID);
|
|
auto object = scope->enter_object();
|
|
object("inline_keyboard",
|
|
JsonInlineKeyboard(static_cast<const td_api::replyMarkupInlineKeyboard *>(reply_markup_)));
|
|
}
|
|
|
|
private:
|
|
const td_api::ReplyMarkup *reply_markup_;
|
|
};
|
|
|
|
void Client::JsonMessage::store(JsonValueScope *scope) const {
|
|
CHECK(message_ != nullptr);
|
|
auto object = scope->enter_object();
|
|
object("message_id", as_client_message_id(message_->id));
|
|
if (message_->sender_user_id != 0) {
|
|
object("from", JsonUser(message_->sender_user_id, client_));
|
|
}
|
|
if (!message_->author_signature.empty()) {
|
|
object("author_signature", message_->author_signature);
|
|
}
|
|
if (message_->sender_chat_id != 0) {
|
|
object("sender_chat", JsonChat(message_->sender_chat_id, false, client_));
|
|
}
|
|
object("chat", JsonChat(message_->chat_id, false, client_));
|
|
object("date", message_->date);
|
|
if (message_->edit_date > 0) {
|
|
object("edit_date", message_->edit_date);
|
|
}
|
|
if (message_->initial_send_date > 0) {
|
|
if (message_->initial_sender_user_id != 0) {
|
|
object("forward_from", JsonUser(message_->initial_sender_user_id, client_));
|
|
}
|
|
if (message_->initial_sender_chat_id != 0) {
|
|
object("forward_from_chat", JsonChat(message_->initial_sender_chat_id, false, client_));
|
|
}
|
|
if (message_->initial_chat_id != 0) {
|
|
object("forward_from_chat", JsonChat(message_->initial_chat_id, false, client_));
|
|
if (message_->initial_message_id != 0) {
|
|
object("forward_from_message_id", as_client_message_id(message_->initial_message_id));
|
|
}
|
|
}
|
|
if (!message_->initial_author_signature.empty()) {
|
|
object("forward_signature", message_->initial_author_signature);
|
|
}
|
|
if (!message_->initial_sender_name.empty()) {
|
|
object("forward_sender_name", message_->initial_sender_name);
|
|
}
|
|
object("forward_date", message_->initial_send_date);
|
|
}
|
|
if (message_->reply_to_message_id > 0 && need_reply_ && !message_->is_reply_to_message_deleted) {
|
|
const MessageInfo *reply_to_message = client_->get_message(message_->chat_id, message_->reply_to_message_id);
|
|
if (reply_to_message != nullptr) {
|
|
object("reply_to_message", JsonMessage(reply_to_message, false, "reply in " + source_, client_));
|
|
} else {
|
|
LOG(WARNING) << "Replied to unknown or deleted message " << message_->reply_to_message_id << " in chat "
|
|
<< message_->chat_id << " while storing " << source_ << " " << message_->id;
|
|
}
|
|
}
|
|
if (message_->media_album_id > 0) {
|
|
object("media_group_id", td::to_string(message_->media_album_id));
|
|
}
|
|
switch (message_->content->get_id()) {
|
|
case td_api::messageText::ID: {
|
|
auto message_text = static_cast<const td_api::messageText *>(message_->content.get());
|
|
object("text", message_text->text_->text_);
|
|
if (!message_text->text_->entities_.empty()) {
|
|
object("entities", JsonVectorEntities(message_text->text_->entities_, client_));
|
|
}
|
|
break;
|
|
}
|
|
case td_api::messageAnimation::ID: {
|
|
auto message_animation = static_cast<const td_api::messageAnimation *>(message_->content.get());
|
|
object("animation", JsonAnimation(message_animation->animation_.get(), false, client_));
|
|
object("document", JsonAnimation(message_animation->animation_.get(), true, client_));
|
|
add_caption(object, message_animation->caption_);
|
|
break;
|
|
}
|
|
case td_api::messageAudio::ID: {
|
|
auto message_audio = static_cast<const td_api::messageAudio *>(message_->content.get());
|
|
object("audio", JsonAudio(message_audio->audio_.get(), client_));
|
|
add_caption(object, message_audio->caption_);
|
|
break;
|
|
}
|
|
case td_api::messageDocument::ID: {
|
|
auto message_document = static_cast<const td_api::messageDocument *>(message_->content.get());
|
|
object("document", JsonDocument(message_document->document_.get(), client_));
|
|
add_caption(object, message_document->caption_);
|
|
break;
|
|
}
|
|
case td_api::messagePhoto::ID: {
|
|
auto message_photo = static_cast<const td_api::messagePhoto *>(message_->content.get());
|
|
if (message_photo->photo_ == nullptr) {
|
|
LOG(ERROR) << "Got empty messagePhoto";
|
|
break;
|
|
}
|
|
object("photo", JsonPhoto(message_photo->photo_.get(), client_));
|
|
add_caption(object, message_photo->caption_);
|
|
break;
|
|
}
|
|
case td_api::messageSticker::ID: {
|
|
auto message_sticker = static_cast<const td_api::messageSticker *>(message_->content.get());
|
|
object("sticker", JsonSticker(message_sticker->sticker_.get(), client_));
|
|
break;
|
|
}
|
|
case td_api::messageVideo::ID: {
|
|
auto message_video = static_cast<const td_api::messageVideo *>(message_->content.get());
|
|
object("video", JsonVideo(message_video->video_.get(), client_));
|
|
add_caption(object, message_video->caption_);
|
|
break;
|
|
}
|
|
case td_api::messageVideoNote::ID: {
|
|
auto message_video_note = static_cast<const td_api::messageVideoNote *>(message_->content.get());
|
|
object("video_note", JsonVideoNote(message_video_note->video_note_.get(), client_));
|
|
break;
|
|
}
|
|
case td_api::messageVoiceNote::ID: {
|
|
auto message_voice_note = static_cast<const td_api::messageVoiceNote *>(message_->content.get());
|
|
object("voice", JsonVoiceNote(message_voice_note->voice_note_.get(), client_));
|
|
add_caption(object, message_voice_note->caption_);
|
|
break;
|
|
}
|
|
case td_api::messageContact::ID: {
|
|
auto message_contact = static_cast<const td_api::messageContact *>(message_->content.get());
|
|
object("contact", JsonContact(message_contact->contact_.get()));
|
|
break;
|
|
}
|
|
case td_api::messageDice::ID: {
|
|
auto message_dice = static_cast<const td_api::messageDice *>(message_->content.get());
|
|
object("dice", JsonDice(message_dice->emoji_, message_dice->value_));
|
|
break;
|
|
}
|
|
case td_api::messageGame::ID: {
|
|
auto message_game = static_cast<const td_api::messageGame *>(message_->content.get());
|
|
object("game", JsonGame(message_game->game_.get(), client_));
|
|
break;
|
|
}
|
|
case td_api::messageInvoice::ID: {
|
|
auto message_invoice = static_cast<const td_api::messageInvoice *>(message_->content.get());
|
|
object("invoice", JsonInvoice(message_invoice));
|
|
break;
|
|
}
|
|
case td_api::messageLocation::ID: {
|
|
auto message_location = static_cast<const td_api::messageLocation *>(message_->content.get());
|
|
object("location", JsonLocation(message_location->location_.get(), message_location->expires_in_,
|
|
message_location->live_period_, message_location->heading_,
|
|
message_location->proximity_alert_radius_));
|
|
break;
|
|
}
|
|
case td_api::messageVenue::ID: {
|
|
auto message_venue = static_cast<const td_api::messageVenue *>(message_->content.get());
|
|
object("location", JsonLocation(message_venue->venue_->location_.get()));
|
|
object("venue", JsonVenue(message_venue->venue_.get()));
|
|
break;
|
|
}
|
|
case td_api::messagePoll::ID: {
|
|
auto message_poll = static_cast<const td_api::messagePoll *>(message_->content.get());
|
|
object("poll", JsonPoll(message_poll->poll_.get(), client_));
|
|
break;
|
|
}
|
|
case td_api::messageChatAddMembers::ID: {
|
|
auto message_add_members = static_cast<const td_api::messageChatAddMembers *>(message_->content.get());
|
|
int32 user_id = client_->choose_added_member_id(message_add_members);
|
|
if (user_id > 0) {
|
|
object("new_chat_participant", JsonUser(user_id, client_));
|
|
object("new_chat_member", JsonUser(user_id, client_));
|
|
object("new_chat_members", JsonUsers(message_add_members->member_user_ids_, client_));
|
|
} else {
|
|
LOG(ERROR) << "Can't choose added member for new_chat_member field";
|
|
}
|
|
break;
|
|
}
|
|
case td_api::messageChatJoinByLink::ID: {
|
|
if (message_->sender_user_id > 0) {
|
|
object("new_chat_participant", JsonUser(message_->sender_user_id, client_));
|
|
object("new_chat_member", JsonUser(message_->sender_user_id, client_));
|
|
object("new_chat_members", JsonUsers({message_->sender_user_id}, client_));
|
|
}
|
|
break;
|
|
}
|
|
case td_api::messageChatDeleteMember::ID: {
|
|
auto message_delete_member = static_cast<const td_api::messageChatDeleteMember *>(message_->content.get());
|
|
int32 user_id = message_delete_member->user_id_;
|
|
object("left_chat_participant", JsonUser(user_id, client_));
|
|
object("left_chat_member", JsonUser(user_id, client_));
|
|
break;
|
|
}
|
|
case td_api::messageChatChangeTitle::ID: {
|
|
auto message_change_title = static_cast<const td_api::messageChatChangeTitle *>(message_->content.get());
|
|
object("new_chat_title", message_change_title->title_);
|
|
break;
|
|
}
|
|
case td_api::messageChatChangePhoto::ID: {
|
|
auto message_change_photo = static_cast<const td_api::messageChatChangePhoto *>(message_->content.get());
|
|
if (message_change_photo->photo_ == nullptr) {
|
|
LOG(ERROR) << "Got empty messageChatChangePhoto";
|
|
break;
|
|
}
|
|
object("new_chat_photo", JsonChatPhoto(message_change_photo->photo_.get(), client_));
|
|
break;
|
|
}
|
|
case td_api::messageChatDeletePhoto::ID:
|
|
object("delete_chat_photo", td::JsonTrue());
|
|
break;
|
|
case td_api::messageBasicGroupChatCreate::ID:
|
|
object("group_chat_created", td::JsonTrue());
|
|
break;
|
|
case td_api::messageSupergroupChatCreate::ID: {
|
|
auto chat = client_->get_chat(message_->chat_id);
|
|
if (chat->type != ChatInfo::Type::Supergroup) {
|
|
LOG(ERROR) << "Receive messageSupergroupChatCreate in the non-supergroup chat " << message_->chat_id;
|
|
break;
|
|
}
|
|
auto supergroup_info = client_->get_supergroup_info(chat->supergroup_id);
|
|
CHECK(supergroup_info != nullptr);
|
|
if (supergroup_info->is_supergroup) {
|
|
object("supergroup_chat_created", td::JsonTrue());
|
|
} else {
|
|
object("channel_chat_created", td::JsonTrue());
|
|
}
|
|
break;
|
|
}
|
|
case td_api::messageChatUpgradeTo::ID: {
|
|
auto message_chat_upgrade_to = static_cast<const td_api::messageChatUpgradeTo *>(message_->content.get());
|
|
auto chat_id = get_supergroup_chat_id(message_chat_upgrade_to->supergroup_id_);
|
|
object("migrate_to_chat_id", td::JsonLong(chat_id));
|
|
break;
|
|
}
|
|
case td_api::messageChatUpgradeFrom::ID: {
|
|
auto message_chat_upgrade_from = static_cast<const td_api::messageChatUpgradeFrom *>(message_->content.get());
|
|
auto chat_id = get_basic_group_chat_id(message_chat_upgrade_from->basic_group_id_);
|
|
object("migrate_from_chat_id", td::JsonLong(chat_id));
|
|
break;
|
|
}
|
|
case td_api::messagePinMessage::ID: {
|
|
auto message_pin_message = static_cast<const td_api::messagePinMessage *>(message_->content.get());
|
|
auto message_id = message_pin_message->message_id_;
|
|
if (message_id > 0) {
|
|
const MessageInfo *pinned_message = client_->get_message(message_->chat_id, message_id);
|
|
if (pinned_message != nullptr) {
|
|
object("pinned_message", JsonMessage(pinned_message, false, "pin in " + source_, client_));
|
|
} else {
|
|
LOG_IF(ERROR, need_reply_) << "Pinned unknown, inaccessible or deleted message " << message_id;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case td_api::messageGameScore::ID:
|
|
break;
|
|
case td_api::messagePaymentSuccessful::ID:
|
|
break;
|
|
case td_api::messagePaymentSuccessfulBot::ID: {
|
|
auto message_payment_sent_bot = static_cast<const td_api::messagePaymentSuccessfulBot *>(message_->content.get());
|
|
object("successful_payment", JsonSuccessfulPaymentBot(message_payment_sent_bot));
|
|
break;
|
|
}
|
|
case td_api::messageCall::ID:
|
|
break;
|
|
case td_api::messageScreenshotTaken::ID:
|
|
break;
|
|
case td_api::messageChatSetTtl::ID:
|
|
break;
|
|
case td_api::messageUnsupported::ID:
|
|
break;
|
|
case td_api::messageContactRegistered::ID:
|
|
break;
|
|
case td_api::messageExpiredPhoto::ID:
|
|
break;
|
|
case td_api::messageExpiredVideo::ID:
|
|
break;
|
|
case td_api::messageCustomServiceAction::ID:
|
|
break;
|
|
case td_api::messageWebsiteConnected::ID: {
|
|
auto chat = client_->get_chat(message_->chat_id);
|
|
if (chat->type != ChatInfo::Type::Private) {
|
|
break;
|
|
}
|
|
|
|
auto message_website_connected = static_cast<const td_api::messageWebsiteConnected *>(message_->content.get());
|
|
if (!message_website_connected->domain_name_.empty()) {
|
|
object("connected_website", message_website_connected->domain_name_);
|
|
}
|
|
break;
|
|
}
|
|
case td_api::messagePassportDataSent::ID:
|
|
break;
|
|
case td_api::messagePassportDataReceived::ID: {
|
|
auto message_passport_data_received =
|
|
static_cast<const td_api::messagePassportDataReceived *>(message_->content.get());
|
|
object("passport_data", JsonPassportData(message_passport_data_received, client_));
|
|
break;
|
|
}
|
|
case td_api::messageProximityAlertTriggered::ID: {
|
|
auto content = static_cast<const td_api::messageProximityAlertTriggered *>(message_->content.get());
|
|
object("proximity_alert_triggered", JsonProximityAlertTriggered(content, client_));
|
|
break;
|
|
}
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
if (message_->reply_markup != nullptr) {
|
|
object("reply_markup", JsonReplyMarkup(message_->reply_markup.get()));
|
|
}
|
|
if (message_->via_bot_user_id > 0) {
|
|
object("via_bot", JsonUser(message_->via_bot_user_id, client_));
|
|
}
|
|
}
|
|
|
|
class Client::JsonDeletedMessage : public Jsonable {
|
|
public:
|
|
JsonDeletedMessage(int64 chat_id, int64 message_id, const Client *client)
|
|
: chat_id_(chat_id), message_id_(message_id), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("message_id", as_client_message_id(message_id_));
|
|
object("chat", JsonChat(chat_id_, false, client_));
|
|
object("date", 0);
|
|
}
|
|
|
|
private:
|
|
int64 chat_id_;
|
|
int64 message_id_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonMessageId : public Jsonable {
|
|
public:
|
|
explicit JsonMessageId(int64 message_id) : message_id_(message_id) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("message_id", as_client_message_id(message_id_));
|
|
}
|
|
|
|
private:
|
|
int64 message_id_;
|
|
};
|
|
|
|
class Client::JsonInlineQuery : public Jsonable {
|
|
public:
|
|
JsonInlineQuery(int64 inline_query_id, int32 sender_user_id, const td_api::location *user_location,
|
|
const td::string &query, const td::string &offset, const Client *client)
|
|
: inline_query_id_(inline_query_id)
|
|
, sender_user_id_(sender_user_id)
|
|
, user_location_(user_location)
|
|
, query_(query)
|
|
, offset_(offset)
|
|
, client_(client) {
|
|
}
|
|
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("id", td::to_string(inline_query_id_));
|
|
object("from", JsonUser(sender_user_id_, client_));
|
|
if (user_location_ != nullptr) {
|
|
object("location", JsonLocation(user_location_));
|
|
}
|
|
object("query", query_);
|
|
object("offset", offset_);
|
|
}
|
|
|
|
private:
|
|
int64 inline_query_id_;
|
|
int32 sender_user_id_;
|
|
const td_api::location *user_location_;
|
|
const td::string &query_;
|
|
const td::string &offset_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonChosenInlineResult : public Jsonable {
|
|
public:
|
|
JsonChosenInlineResult(int32 sender_user_id, const td_api::location *user_location, const td::string &query,
|
|
const td::string &result_id, const td::string &inline_message_id, const Client *client)
|
|
: sender_user_id_(sender_user_id)
|
|
, user_location_(user_location)
|
|
, query_(query)
|
|
, result_id_(result_id)
|
|
, inline_message_id_(inline_message_id)
|
|
, client_(client) {
|
|
}
|
|
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("from", JsonUser(sender_user_id_, client_));
|
|
if (user_location_ != nullptr) {
|
|
object("location", JsonLocation(user_location_));
|
|
}
|
|
if (!inline_message_id_.empty()) {
|
|
object("inline_message_id", inline_message_id_);
|
|
}
|
|
object("query", query_);
|
|
object("result_id", result_id_);
|
|
}
|
|
|
|
private:
|
|
int32 sender_user_id_;
|
|
const td_api::location *user_location_;
|
|
const td::string &query_;
|
|
const td::string &result_id_;
|
|
const td::string &inline_message_id_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonCallbackQuery : public Jsonable {
|
|
public:
|
|
JsonCallbackQuery(int64 callback_query_id, int32 sender_user_id, int64 chat_id, int64 message_id,
|
|
const MessageInfo *message_info, int64 chat_instance, td_api::CallbackQueryPayload *payload,
|
|
const Client *client)
|
|
: callback_query_id_(callback_query_id)
|
|
, sender_user_id_(sender_user_id)
|
|
, chat_id_(chat_id)
|
|
, message_id_(message_id)
|
|
, message_info_(message_info)
|
|
, chat_instance_(chat_instance)
|
|
, payload_(payload)
|
|
, client_(client) {
|
|
}
|
|
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("id", td::to_string(callback_query_id_));
|
|
object("from", JsonUser(sender_user_id_, client_));
|
|
if (message_info_ != nullptr) {
|
|
object("message", JsonMessage(message_info_, true, "callback query", client_));
|
|
} else {
|
|
object("message", JsonDeletedMessage(chat_id_, message_id_, client_));
|
|
}
|
|
object("chat_instance", td::to_string(chat_instance_));
|
|
client_->json_store_callback_query_payload(object, payload_);
|
|
}
|
|
|
|
private:
|
|
int64 callback_query_id_;
|
|
int32 sender_user_id_;
|
|
int64 chat_id_;
|
|
int64 message_id_;
|
|
const MessageInfo *message_info_;
|
|
int64 chat_instance_;
|
|
td_api::CallbackQueryPayload *payload_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonInlineCallbackQuery : public Jsonable {
|
|
public:
|
|
JsonInlineCallbackQuery(int64 callback_query_id, int32 sender_user_id, const td::string &inline_message_id,
|
|
int64 chat_instance, td_api::CallbackQueryPayload *payload, const Client *client)
|
|
: callback_query_id_(callback_query_id)
|
|
, sender_user_id_(sender_user_id)
|
|
, inline_message_id_(inline_message_id)
|
|
, chat_instance_(chat_instance)
|
|
, payload_(payload)
|
|
, client_(client) {
|
|
}
|
|
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("id", td::to_string(callback_query_id_));
|
|
object("from", JsonUser(sender_user_id_, client_));
|
|
object("inline_message_id", inline_message_id_);
|
|
object("chat_instance", td::to_string(chat_instance_));
|
|
client_->json_store_callback_query_payload(object, payload_);
|
|
}
|
|
|
|
private:
|
|
int64 callback_query_id_;
|
|
int32 sender_user_id_;
|
|
const td::string &inline_message_id_;
|
|
int64 chat_instance_;
|
|
td_api::CallbackQueryPayload *payload_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonShippingQuery : public Jsonable {
|
|
public:
|
|
JsonShippingQuery(const td_api::updateNewShippingQuery *query, const Client *client)
|
|
: query_(query), client_(client) {
|
|
}
|
|
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("id", td::to_string(query_->id_));
|
|
object("from", JsonUser(query_->sender_user_id_, client_));
|
|
if (!td::check_utf8(query_->invoice_payload_)) {
|
|
LOG(WARNING) << "Receive non-UTF-8 invoice payload";
|
|
object("invoice_payload", td::JsonRawString(query_->invoice_payload_));
|
|
} else {
|
|
object("invoice_payload", query_->invoice_payload_);
|
|
}
|
|
object("shipping_address", JsonAddress(query_->shipping_address_.get()));
|
|
}
|
|
|
|
private:
|
|
const td_api::updateNewShippingQuery *query_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonPreCheckoutQuery : public Jsonable {
|
|
public:
|
|
JsonPreCheckoutQuery(const td_api::updateNewPreCheckoutQuery *query, const Client *client)
|
|
: query_(query), client_(client) {
|
|
}
|
|
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("id", td::to_string(query_->id_));
|
|
object("from", JsonUser(query_->sender_user_id_, client_));
|
|
object("currency", query_->currency_);
|
|
object("total_amount", query_->total_amount_);
|
|
if (!td::check_utf8(query_->invoice_payload_)) {
|
|
LOG(WARNING) << "Receive non-UTF-8 invoice payload";
|
|
object("invoice_payload", td::JsonRawString(query_->invoice_payload_));
|
|
} else {
|
|
object("invoice_payload", query_->invoice_payload_);
|
|
}
|
|
if (!query_->shipping_option_id_.empty()) {
|
|
object("shipping_option_id", query_->shipping_option_id_);
|
|
}
|
|
if (query_->order_info_ != nullptr) {
|
|
object("order_info", JsonOrderInfo(query_->order_info_.get()));
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td_api::updateNewPreCheckoutQuery *query_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonCustomJson : public Jsonable {
|
|
public:
|
|
explicit JsonCustomJson(const td::string &json) : json_(json) {
|
|
}
|
|
|
|
void store(JsonValueScope *scope) const {
|
|
*scope << td::JsonRaw(json_);
|
|
}
|
|
|
|
private:
|
|
const td::string &json_;
|
|
};
|
|
|
|
class Client::JsonBotCommand : public Jsonable {
|
|
public:
|
|
explicit JsonBotCommand(const td_api::botCommand *command) : command_(command) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("command", command_->command_);
|
|
object("description", command_->description_);
|
|
}
|
|
|
|
private:
|
|
const td_api::botCommand *command_;
|
|
};
|
|
|
|
class Client::JsonChatPhotos : public Jsonable {
|
|
public:
|
|
JsonChatPhotos(const td_api::chatPhotos *photos, const Client *client) : photos_(photos), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("total_count", photos_->total_count_);
|
|
object("photos", td::json_array(photos_->photos_,
|
|
[client = client_](auto &photo) { return JsonChatPhoto(photo.get(), client); }));
|
|
}
|
|
|
|
private:
|
|
const td_api::chatPhotos *photos_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonChatMember : public Jsonable {
|
|
public:
|
|
JsonChatMember(const td_api::chatMember *member, Client::ChatType chat_type, const Client *client)
|
|
: member_(member), chat_type_(chat_type), client_(client) {
|
|
}
|
|
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("user", JsonUser(member_->user_id_, client_));
|
|
object("status", Client::get_chat_member_status(member_->status_));
|
|
switch (member_->status_->get_id()) {
|
|
case td_api::chatMemberStatusCreator::ID: {
|
|
auto creator = static_cast<const td_api::chatMemberStatusCreator *>(member_->status_.get());
|
|
if (!creator->custom_title_.empty()) {
|
|
object("custom_title", creator->custom_title_);
|
|
}
|
|
object("is_anonymous", td::JsonBool(creator->is_anonymous_));
|
|
// object("is_member", creator->is_member_); only creator itself knows that he is a left creator
|
|
break;
|
|
}
|
|
case td_api::chatMemberStatusAdministrator::ID: {
|
|
auto administrator = static_cast<const td_api::chatMemberStatusAdministrator *>(member_->status_.get());
|
|
object("can_be_edited", td::JsonBool(administrator->can_be_edited_));
|
|
object("can_change_info", td::JsonBool(administrator->can_change_info_));
|
|
if (chat_type_ == Client::ChatType::Channel) {
|
|
object("can_post_messages", td::JsonBool(administrator->can_post_messages_));
|
|
object("can_edit_messages", td::JsonBool(administrator->can_edit_messages_));
|
|
}
|
|
object("can_delete_messages", td::JsonBool(administrator->can_delete_messages_));
|
|
object("can_invite_users", td::JsonBool(administrator->can_invite_users_));
|
|
object("can_restrict_members", td::JsonBool(administrator->can_restrict_members_));
|
|
if (chat_type_ == Client::ChatType::Group || chat_type_ == Client::ChatType::Supergroup) {
|
|
object("can_pin_messages", td::JsonBool(administrator->can_pin_messages_));
|
|
}
|
|
object("can_promote_members", td::JsonBool(administrator->can_promote_members_));
|
|
if (!administrator->custom_title_.empty()) {
|
|
object("custom_title", administrator->custom_title_);
|
|
}
|
|
object("is_anonymous", td::JsonBool(administrator->is_anonymous_));
|
|
break;
|
|
}
|
|
case td_api::chatMemberStatusMember::ID:
|
|
break;
|
|
case td_api::chatMemberStatusRestricted::ID:
|
|
if (chat_type_ == Client::ChatType::Supergroup) {
|
|
auto restricted = static_cast<const td_api::chatMemberStatusRestricted *>(member_->status_.get());
|
|
object("until_date", restricted->restricted_until_date_);
|
|
Client::json_store_permissions(object, restricted->permissions_.get());
|
|
object("is_member", td::JsonBool(restricted->is_member_));
|
|
}
|
|
break;
|
|
case td_api::chatMemberStatusLeft::ID:
|
|
break;
|
|
case td_api::chatMemberStatusBanned::ID: {
|
|
auto banned = static_cast<const td_api::chatMemberStatusBanned *>(member_->status_.get());
|
|
object("until_date", banned->banned_until_date_);
|
|
break;
|
|
}
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td_api::chatMember *member_;
|
|
Client::ChatType chat_type_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonChatMembers : public Jsonable {
|
|
public:
|
|
JsonChatMembers(const td::vector<object_ptr<td_api::chatMember>> &members, Client::ChatType chat_type,
|
|
bool administrators_only, const Client *client)
|
|
: members_(members), chat_type_(chat_type), administrators_only_(administrators_only), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto array = scope->enter_array();
|
|
for (auto &member : members_) {
|
|
CHECK(member != nullptr);
|
|
bool is_member_bot = member->bot_info_ != nullptr;
|
|
if (!is_member_bot) {
|
|
// bot info may be unknown
|
|
auto user_info = client_->get_user_info(member->user_id_);
|
|
if (user_info != nullptr && user_info->type == UserInfo::Type::Bot) {
|
|
is_member_bot = true;
|
|
}
|
|
}
|
|
if (is_member_bot && member->user_id_ != client_->my_id_) {
|
|
continue;
|
|
}
|
|
if (administrators_only_) {
|
|
auto status = Client::get_chat_member_status(member->status_);
|
|
if (status != "creator" && status != "administrator") {
|
|
continue;
|
|
}
|
|
}
|
|
array << JsonChatMember(member.get(), chat_type_, client_);
|
|
}
|
|
}
|
|
|
|
private:
|
|
const td::vector<object_ptr<td_api::chatMember>> &members_;
|
|
Client::ChatType chat_type_;
|
|
bool administrators_only_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonGameHighScore : public Jsonable {
|
|
public:
|
|
JsonGameHighScore(const td_api::gameHighScore *score, const Client *client) : score_(score), client_(client) {
|
|
}
|
|
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
object("position", score_->position_);
|
|
object("user", JsonUser(score_->user_id_, client_));
|
|
object("score", score_->score_);
|
|
}
|
|
|
|
private:
|
|
const td_api::gameHighScore *score_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonUpdateTypes : public Jsonable {
|
|
public:
|
|
explicit JsonUpdateTypes(td::uint32 update_types) : update_types_(update_types) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto array = scope->enter_array();
|
|
for (int32 i = 0; i < static_cast<int32>(UpdateType::Size); i++) {
|
|
if (((update_types_ >> i) & 1) != 0) {
|
|
auto update_type = static_cast<UpdateType>(i);
|
|
if (update_type != UpdateType::CustomEvent && update_type != UpdateType::CustomQuery) {
|
|
array << get_update_type_name(update_type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
td::uint32 update_types_;
|
|
};
|
|
|
|
class Client::JsonWebhookInfo : public Jsonable {
|
|
public:
|
|
explicit JsonWebhookInfo(const Client *client) : client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
td::CSlice url = client_->webhook_url_;
|
|
if (td::check_utf8(url)) {
|
|
object("url", url);
|
|
} else {
|
|
object("url", td::JsonRawString(url));
|
|
}
|
|
object("has_custom_certificate", td::JsonBool(client_->has_webhook_certificate_));
|
|
object("pending_update_count", td::narrow_cast<int32>(client_->get_pending_update_count()));
|
|
if (client_->last_webhook_error_date_ > 0) {
|
|
object("last_error_date", client_->last_webhook_error_date_);
|
|
td::CSlice error_message = client_->last_webhook_error_.message();
|
|
if (td::check_utf8(error_message)) {
|
|
object("last_error_message", error_message);
|
|
} else {
|
|
object("last_error_message", td::JsonRawString(error_message));
|
|
}
|
|
}
|
|
if (client_->webhook_max_connections_ > 0) {
|
|
object("max_connections", client_->webhook_max_connections_);
|
|
}
|
|
if (!url.empty()) {
|
|
object("ip_address", client_->webhook_ip_address_.empty() ? "<unknown>" : client_->webhook_ip_address_);
|
|
}
|
|
if (client_->allowed_update_types_ != DEFAULT_ALLOWED_UPDATE_TYPES) {
|
|
object("allowed_updates", JsonUpdateTypes(client_->allowed_update_types_));
|
|
}
|
|
}
|
|
|
|
private:
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::JsonStickerSet : public Jsonable {
|
|
public:
|
|
JsonStickerSet(const td_api::stickerSet *sticker_set, const Client *client)
|
|
: sticker_set_(sticker_set), client_(client) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto object = scope->enter_object();
|
|
if (sticker_set_->id_ == Client::GREAT_MINDS_SET_ID) {
|
|
object("name", GREAT_MINDS_SET_NAME);
|
|
} else {
|
|
object("name", sticker_set_->name_);
|
|
}
|
|
object("title", sticker_set_->title_);
|
|
if (sticker_set_->thumbnail_ != nullptr) {
|
|
client_->json_store_thumbnail(object, sticker_set_->thumbnail_.get());
|
|
}
|
|
object("is_animated", td::JsonBool(sticker_set_->is_animated_));
|
|
object("contains_masks", td::JsonBool(sticker_set_->is_masks_));
|
|
object("stickers", JsonStickers(sticker_set_->stickers_, client_));
|
|
}
|
|
|
|
private:
|
|
const td_api::stickerSet *sticker_set_;
|
|
const Client *client_;
|
|
};
|
|
|
|
class Client::TdOnOkCallback : public TdQueryCallback {
|
|
public:
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
auto error = move_object_as<td_api::error>(result);
|
|
if (error->code_ != 401 && error->code_ != 406 && error->code_ != 500) {
|
|
LOG(ERROR) << "Query has failed: " << td::oneline(to_string(error));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
class Client::TdOnAuthorizationCallback : public TdQueryCallback {
|
|
public:
|
|
explicit TdOnAuthorizationCallback(Client *client) : client_(client) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
bool was_ready = client_->authorization_state_->get_id() != td_api::authorizationStateWaitPhoneNumber::ID;
|
|
if (result->get_id() == td_api::error::ID) {
|
|
auto error = move_object_as<td_api::error>(result);
|
|
if (error->code_ == 429 || error->code_ >= 500 || (error->code_ != 401 && was_ready)) {
|
|
// try again
|
|
return client_->on_update_authorization_state();
|
|
}
|
|
|
|
LOG(WARNING) << "Logging out due to " << td::oneline(to_string(error));
|
|
client_->log_out();
|
|
} else if (was_ready) {
|
|
client_->on_update_authorization_state();
|
|
}
|
|
}
|
|
|
|
private:
|
|
Client *client_;
|
|
};
|
|
|
|
class Client::TdOnInitCallback : public TdQueryCallback {
|
|
public:
|
|
explicit TdOnInitCallback(Client *client) : client_(client) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
LOG(WARNING) << "Failed to initialize due to " << td::oneline(to_string(result));
|
|
client_->close();
|
|
}
|
|
}
|
|
|
|
private:
|
|
Client *client_;
|
|
};
|
|
|
|
class Client::TdOnGetUserProfilePhotosCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnGetUserProfilePhotosCallback(const Client *client, PromisedQueryPtr query)
|
|
: client_(client), query_(std::move(query)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result));
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::chatPhotos::ID);
|
|
auto profile_photos = move_object_as<td_api::chatPhotos>(result);
|
|
answer_query(JsonChatPhotos(profile_photos.get(), client_), std::move(query_));
|
|
}
|
|
|
|
private:
|
|
const Client *client_;
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
class Client::TdOnSendMessageCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnSendMessageCallback(Client *client, PromisedQueryPtr query) : client_(client), query_(std::move(query)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result));
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::message::ID);
|
|
auto query_id = client_->get_send_message_query_id(std::move(query_), false);
|
|
client_->on_sent_message(move_object_as<td_api::message>(result), query_id);
|
|
}
|
|
|
|
private:
|
|
Client *client_;
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
class Client::TdOnSendMessageAlbumCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnSendMessageAlbumCallback(Client *client, PromisedQueryPtr query) : client_(client), query_(std::move(query)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result));
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::messages::ID);
|
|
auto messages = move_object_as<td_api::messages>(result);
|
|
auto query_id = client_->get_send_message_query_id(std::move(query_), true);
|
|
for (auto &message : messages->messages_) {
|
|
client_->on_sent_message(std::move(message), query_id);
|
|
}
|
|
}
|
|
|
|
private:
|
|
Client *client_;
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
class Client::TdOnDeleteFailedToSendMessageCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnDeleteFailedToSendMessageCallback(Client *client, int64 chat_id, int64 message_id)
|
|
: client_(client)
|
|
, chat_id_(chat_id)
|
|
, message_id_(message_id)
|
|
, old_chat_description_(client->get_chat_description(chat_id)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
auto error = move_object_as<td_api::error>(result);
|
|
if (error->code_ != 401) {
|
|
LOG(ERROR) << "Can't delete failed to send message " << message_id_ << " because of "
|
|
<< td::oneline(to_string(error)) << " in " << client_->get_chat_description(chat_id_)
|
|
<< ". Old chat description: " << old_chat_description_;
|
|
}
|
|
return;
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::ok::ID);
|
|
if (client_->get_message(chat_id_, message_id_) != nullptr) {
|
|
LOG(ERROR) << "Have cache for message " << message_id_ << " in the chat " << chat_id_;
|
|
client_->delete_message(chat_id_, message_id_, false);
|
|
}
|
|
}
|
|
|
|
private:
|
|
Client *client_;
|
|
int64 chat_id_;
|
|
int64 message_id_;
|
|
td::string old_chat_description_;
|
|
};
|
|
|
|
class Client::TdOnEditMessageCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnEditMessageCallback(const Client *client, PromisedQueryPtr query) : client_(client), query_(std::move(query)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result));
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::message::ID);
|
|
auto message = move_object_as<td_api::message>(result);
|
|
int64 chat_id = message->chat_id_;
|
|
int64 message_id = message->id_;
|
|
|
|
auto message_info = client_->get_message(chat_id, message_id);
|
|
if (message_info == nullptr) {
|
|
return fail_query_with_error(std::move(query_), 400, "message not found");
|
|
}
|
|
message_info->is_content_changed = false;
|
|
answer_query(JsonMessage(message_info, false, "edited message", client_), std::move(query_));
|
|
}
|
|
|
|
private:
|
|
const Client *client_;
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
class Client::TdOnEditInlineMessageCallback : public TdQueryCallback {
|
|
public:
|
|
explicit TdOnEditInlineMessageCallback(PromisedQueryPtr query) : query_(std::move(query)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result));
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::ok::ID);
|
|
answer_query(td::JsonTrue(), std::move(query_));
|
|
}
|
|
|
|
private:
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
class Client::TdOnStopPollCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnStopPollCallback(const Client *client, int64 chat_id, int64 message_id, PromisedQueryPtr query)
|
|
: client_(client), chat_id_(chat_id), message_id_(message_id), query_(std::move(query)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result));
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::ok::ID);
|
|
auto message_info = client_->get_message(chat_id_, message_id_);
|
|
if (message_info == nullptr) {
|
|
return fail_query_with_error(std::move(query_), 400, "message not found");
|
|
}
|
|
if (message_info->content->get_id() != td_api::messagePoll::ID) {
|
|
LOG(ERROR) << "Poll not found in " << message_id_ << " in " << chat_id_;
|
|
return fail_query_with_error(std::move(query_), 400, "message poll not found");
|
|
}
|
|
auto message_poll = static_cast<const td_api::messagePoll *>(message_info->content.get());
|
|
answer_query(JsonPoll(message_poll->poll_.get(), client_), std::move(query_));
|
|
}
|
|
|
|
private:
|
|
const Client *client_;
|
|
int64 chat_id_;
|
|
int64 message_id_;
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
class Client::TdOnOkQueryCallback : public TdQueryCallback {
|
|
public:
|
|
explicit TdOnOkQueryCallback(PromisedQueryPtr query) : query_(std::move(query)) {
|
|
CHECK(query_ != nullptr);
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result));
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::ok::ID);
|
|
answer_query(td::JsonTrue(), std::move(query_));
|
|
}
|
|
|
|
private:
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
template <class OnSuccess>
|
|
class Client::TdOnCheckUserCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnCheckUserCallback(const Client *client, PromisedQueryPtr query, OnSuccess on_success)
|
|
: client_(client), query_(std::move(query)), on_success_(std::move(on_success)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result), "user not found");
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::user::ID);
|
|
auto user = move_object_as<td_api::user>(result);
|
|
auto user_info = client_->get_user_info(user->id_);
|
|
CHECK(user_info != nullptr); // it must have already been got through updates
|
|
|
|
return client_->check_user_read_access(user_info, std::move(query_), std::move(on_success_));
|
|
}
|
|
|
|
private:
|
|
const Client *client_;
|
|
PromisedQueryPtr query_;
|
|
OnSuccess on_success_;
|
|
};
|
|
|
|
template <class OnSuccess>
|
|
class Client::TdOnCheckUserNoFailCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnCheckUserNoFailCallback(PromisedQueryPtr query, OnSuccess on_success)
|
|
: query_(std::move(query)), on_success_(std::move(on_success)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
on_success_(std::move(query_));
|
|
}
|
|
|
|
private:
|
|
PromisedQueryPtr query_;
|
|
OnSuccess on_success_;
|
|
};
|
|
|
|
template <class OnSuccess>
|
|
class Client::TdOnCheckChatCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnCheckChatCallback(const Client *client, bool only_supergroup, AccessRights access_rights, PromisedQueryPtr query,
|
|
OnSuccess on_success)
|
|
: client_(client)
|
|
, only_supergroup_(only_supergroup)
|
|
, access_rights_(access_rights)
|
|
, query_(std::move(query))
|
|
, on_success_(std::move(on_success)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result), "chat not found");
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::chat::ID);
|
|
auto chat = move_object_as<td_api::chat>(result);
|
|
auto chat_info = client_->get_chat(chat->id_);
|
|
CHECK(chat_info != nullptr); // it must have already been got through updates
|
|
CHECK(chat_info->title == chat->title_);
|
|
if (only_supergroup_ && chat_info->type != ChatInfo::Type::Supergroup) {
|
|
return fail_query(400, "Bad Request: chat not found", std::move(query_));
|
|
}
|
|
|
|
return client_->check_chat_access(chat->id_, access_rights_, chat_info, std::move(query_), std::move(on_success_));
|
|
}
|
|
|
|
private:
|
|
const Client *client_;
|
|
bool only_supergroup_;
|
|
AccessRights access_rights_;
|
|
PromisedQueryPtr query_;
|
|
OnSuccess on_success_;
|
|
};
|
|
|
|
template <class OnSuccess>
|
|
class Client::TdOnSearchStickerSetCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnSearchStickerSetCallback(PromisedQueryPtr query, OnSuccess on_success)
|
|
: query_(std::move(query)), on_success_(std::move(on_success)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result), "sticker set not found");
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::stickerSet::ID);
|
|
auto sticker_set = move_object_as<td_api::stickerSet>(result);
|
|
on_success_(sticker_set->id_, std::move(query_));
|
|
}
|
|
|
|
private:
|
|
PromisedQueryPtr query_;
|
|
OnSuccess on_success_;
|
|
};
|
|
|
|
class Client::TdOnResolveBotUsernameCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnResolveBotUsernameCallback(Client *client, td::string username)
|
|
: client_(client), username_(std::move(username)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return client_->on_resolve_bot_username(username_, 0);
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::chat::ID);
|
|
auto chat = move_object_as<td_api::chat>(result);
|
|
auto chat_info = client_->get_chat(chat->id_);
|
|
CHECK(chat_info != nullptr); // it must have already been got through updates
|
|
if (chat_info->type != ChatInfo::Type::Private) {
|
|
return client_->on_resolve_bot_username(username_, 0);
|
|
}
|
|
auto user_info = client_->get_user_info(chat_info->user_id);
|
|
CHECK(user_info != nullptr);
|
|
if (user_info->type != UserInfo::Type::Bot) {
|
|
return client_->on_resolve_bot_username(username_, 0);
|
|
}
|
|
|
|
client_->on_resolve_bot_username(username_, chat_info->user_id);
|
|
}
|
|
|
|
private:
|
|
Client *client_;
|
|
td::string username_;
|
|
};
|
|
|
|
template <class OnSuccess>
|
|
class Client::TdOnCheckMessageCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnCheckMessageCallback(Client *client, int64 chat_id, bool allow_empty, Slice message_type, PromisedQueryPtr query,
|
|
OnSuccess on_success)
|
|
: client_(client)
|
|
, chat_id_(chat_id)
|
|
, allow_empty_(allow_empty)
|
|
, message_type_(message_type)
|
|
, query_(std::move(query))
|
|
, on_success_(std::move(on_success)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
auto error = move_object_as<td_api::error>(result);
|
|
if (error->code_ == 429) {
|
|
LOG(WARNING) << "Failed to get " << message_type_;
|
|
}
|
|
if (allow_empty_) {
|
|
return on_success_(chat_id_, 0, std::move(query_));
|
|
}
|
|
return fail_query_with_error(std::move(query_), std::move(error), PSLICE() << message_type_ << " not found");
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::message::ID);
|
|
auto full_message_id = client_->add_message(move_object_as<td_api::message>(result));
|
|
CHECK(full_message_id.chat_id == chat_id_);
|
|
on_success_(full_message_id.chat_id, full_message_id.message_id, std::move(query_));
|
|
}
|
|
|
|
private:
|
|
Client *client_;
|
|
int64 chat_id_;
|
|
bool allow_empty_;
|
|
Slice message_type_;
|
|
PromisedQueryPtr query_;
|
|
OnSuccess on_success_;
|
|
};
|
|
|
|
template <class OnSuccess>
|
|
class Client::TdOnCheckRemoteFileIdCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnCheckRemoteFileIdCallback(PromisedQueryPtr query, OnSuccess on_success)
|
|
: query_(std::move(query)), on_success_(std::move(on_success)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result), "invalid file_id");
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::file::ID);
|
|
on_success_(move_object_as<td_api::file>(result), std::move(query_));
|
|
}
|
|
|
|
private:
|
|
PromisedQueryPtr query_;
|
|
OnSuccess on_success_;
|
|
};
|
|
|
|
template <class OnSuccess>
|
|
class Client::TdOnGetChatMemberCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnGetChatMemberCallback(PromisedQueryPtr query, OnSuccess on_success)
|
|
: query_(std::move(query)), on_success_(std::move(on_success)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result), "user not found");
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::chatMember::ID);
|
|
on_success_(move_object_as<td_api::chatMember>(result), std::move(query_));
|
|
}
|
|
|
|
private:
|
|
PromisedQueryPtr query_;
|
|
OnSuccess on_success_;
|
|
};
|
|
|
|
class Client::TdOnDownloadFileCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnDownloadFileCallback(Client *client, int32 file_id) : client_(client), file_id_(file_id) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
auto error = move_object_as<td_api::error>(result);
|
|
return client_->on_file_download(file_id_, Status::Error(error->code_, error->message_));
|
|
}
|
|
CHECK(result->get_id() == td_api::file::ID);
|
|
if (client_->is_file_being_downloaded(file_id_)) { // if download is yet not finished
|
|
client_->download_started_file_ids_.insert(file_id_);
|
|
}
|
|
client_->on_update_file(move_object_as<td_api::file>(result));
|
|
}
|
|
|
|
private:
|
|
Client *client_;
|
|
int32 file_id_;
|
|
};
|
|
|
|
class Client::TdOnCancelDownloadFileCallback : public TdQueryCallback {
|
|
public:
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
LOG(ERROR) << "Failed to cancel download file";
|
|
return;
|
|
}
|
|
CHECK(result->get_id() == td_api::ok::ID);
|
|
}
|
|
};
|
|
|
|
class Client::TdOnGetReplyMessageCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnGetReplyMessageCallback(Client *client, int64 chat_id) : client_(client), chat_id_(chat_id) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return client_->on_get_reply_message(chat_id_, nullptr);
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::message::ID);
|
|
client_->on_get_reply_message(chat_id_, move_object_as<td_api::message>(result));
|
|
}
|
|
|
|
private:
|
|
Client *client_;
|
|
int64 chat_id_;
|
|
};
|
|
|
|
class Client::TdOnGetEditedMessageCallback : public TdQueryCallback {
|
|
public:
|
|
explicit TdOnGetEditedMessageCallback(Client *client) : client_(client) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
auto error = move_object_as<td_api::error>(result);
|
|
if (error->code_ == 429) {
|
|
LOG(WARNING) << "Failed to get edited message";
|
|
}
|
|
return client_->on_get_edited_message(nullptr);
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::message::ID);
|
|
client_->on_get_edited_message(move_object_as<td_api::message>(result));
|
|
}
|
|
|
|
private:
|
|
Client *client_;
|
|
};
|
|
|
|
class Client::TdOnGetCallbackQueryMessageCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnGetCallbackQueryMessageCallback(Client *client, int32 user_id, int state)
|
|
: client_(client), user_id_(user_id), state_(state) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
auto error = move_object_as<td_api::error>(result);
|
|
if (error->code_ == 429) {
|
|
LOG(WARNING) << "Failed to get callback query message";
|
|
}
|
|
return client_->on_get_callback_query_message(nullptr, user_id_, state_);
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::message::ID);
|
|
client_->on_get_callback_query_message(move_object_as<td_api::message>(result), user_id_, state_);
|
|
}
|
|
|
|
private:
|
|
Client *client_;
|
|
int32 user_id_;
|
|
int state_;
|
|
};
|
|
|
|
class Client::TdOnGetStickerSetCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnGetStickerSetCallback(Client *client, int64 set_id, int32 new_callback_query_user_id, int64 new_message_chat_id)
|
|
: client_(client)
|
|
, set_id_(set_id)
|
|
, new_callback_query_user_id_(new_callback_query_user_id)
|
|
, new_message_chat_id_(new_message_chat_id) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
auto error = move_object_as<td_api::error>(result);
|
|
if (error->message_ != "STICKERSET_INVALID" && error->code_ != 401 && error->code_ != 500) {
|
|
LOG(ERROR) << "Failed to get sticker set " << set_id_ << " from callback query by user "
|
|
<< new_callback_query_user_id_ << "/new message in chat " << new_message_chat_id_ << ": "
|
|
<< td::oneline(to_string(error));
|
|
}
|
|
return client_->on_get_sticker_set(set_id_, new_callback_query_user_id_, new_message_chat_id_, nullptr);
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::stickerSet::ID);
|
|
client_->on_get_sticker_set(set_id_, new_callback_query_user_id_, new_message_chat_id_,
|
|
move_object_as<td_api::stickerSet>(result));
|
|
}
|
|
|
|
private:
|
|
Client *client_;
|
|
int64 set_id_;
|
|
int32 new_callback_query_user_id_;
|
|
int64 new_message_chat_id_;
|
|
};
|
|
|
|
class Client::TdOnGetChatStickerSetCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnGetChatStickerSetCallback(Client *client, int64 chat_id, int64 pinned_message_id, PromisedQueryPtr query)
|
|
: client_(client), chat_id_(chat_id), pinned_message_id_(pinned_message_id), query_(std::move(query)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
auto chat_info = client_->get_chat(chat_id_);
|
|
CHECK(chat_info != nullptr);
|
|
CHECK(chat_info->type == ChatInfo::Type::Supergroup);
|
|
client_->set_supergroup_sticker_set_id(chat_info->supergroup_id, 0);
|
|
} else {
|
|
CHECK(result->get_id() == td_api::stickerSet::ID);
|
|
auto sticker_set = move_object_as<td_api::stickerSet>(result);
|
|
client_->on_get_sticker_set_name(sticker_set->id_, sticker_set->name_);
|
|
}
|
|
|
|
answer_query(JsonChat(chat_id_, true, client_, pinned_message_id_), std::move(query_));
|
|
}
|
|
|
|
private:
|
|
Client *client_;
|
|
int64 chat_id_;
|
|
int64 pinned_message_id_;
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
class Client::TdOnGetChatPinnedMessageCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnGetChatPinnedMessageCallback(Client *client, int64 chat_id, PromisedQueryPtr query)
|
|
: client_(client), chat_id_(chat_id), query_(std::move(query)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
int64 pinned_message_id = 0;
|
|
if (result->get_id() == td_api::error::ID) {
|
|
auto error = move_object_as<td_api::error>(result);
|
|
if (error->code_ == 429) {
|
|
return fail_query_with_error(std::move(query_), std::move(error));
|
|
} else if (error->code_ != 404 && error->message_ != "CHANNEL_PRIVATE") {
|
|
LOG(ERROR) << "Failed to get chat pinned message: " << to_string(error);
|
|
}
|
|
} else {
|
|
CHECK(result->get_id() == td_api::message::ID);
|
|
auto full_message_id = client_->add_message(move_object_as<td_api::message>(result));
|
|
pinned_message_id = full_message_id.message_id;
|
|
CHECK(full_message_id.chat_id == chat_id_);
|
|
CHECK(pinned_message_id > 0);
|
|
}
|
|
|
|
auto chat_info = client_->get_chat(chat_id_);
|
|
CHECK(chat_info != nullptr);
|
|
if (chat_info->type == ChatInfo::Type::Supergroup) {
|
|
auto supergroup_info = client_->get_supergroup_info(chat_info->supergroup_id);
|
|
CHECK(supergroup_info != nullptr);
|
|
|
|
auto sticker_set_id = supergroup_info->sticker_set_id;
|
|
if (sticker_set_id != 0 && client_->get_sticker_set_name(sticker_set_id).empty()) {
|
|
return client_->send_request(
|
|
make_object<td_api::getStickerSet>(sticker_set_id),
|
|
std::make_unique<TdOnGetChatStickerSetCallback>(client_, chat_id_, pinned_message_id, std::move(query_)));
|
|
}
|
|
}
|
|
|
|
answer_query(JsonChat(chat_id_, true, client_, pinned_message_id), std::move(query_));
|
|
}
|
|
|
|
private:
|
|
Client *client_;
|
|
int64 chat_id_;
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
class Client::TdOnGetChatPinnedMessageToUnpinCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnGetChatPinnedMessageToUnpinCallback(Client *client, int64 chat_id, PromisedQueryPtr query)
|
|
: client_(client), chat_id_(chat_id), query_(std::move(query)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
int64 pinned_message_id = 0;
|
|
if (result->get_id() == td_api::error::ID) {
|
|
auto error = move_object_as<td_api::error>(result);
|
|
if (error->code_ == 429) {
|
|
return fail_query_with_error(std::move(query_), std::move(error));
|
|
} else {
|
|
return fail_query_with_error(std::move(query_), make_object<td_api::error>(400, "Message to unpin not found"));
|
|
}
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::message::ID);
|
|
auto full_message_id = client_->add_message(move_object_as<td_api::message>(result));
|
|
pinned_message_id = full_message_id.message_id;
|
|
CHECK(full_message_id.chat_id == chat_id_);
|
|
CHECK(pinned_message_id > 0);
|
|
|
|
client_->send_request(make_object<td_api::unpinChatMessage>(chat_id_, pinned_message_id),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query_)));
|
|
}
|
|
|
|
private:
|
|
Client *client_;
|
|
int64 chat_id_;
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
class Client::TdOnGetMyCommandsCallback : public TdQueryCallback {
|
|
public:
|
|
explicit TdOnGetMyCommandsCallback(PromisedQueryPtr query) : query_(std::move(query)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result));
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::userFullInfo::ID);
|
|
auto user_full_info = move_object_as<td_api::userFullInfo>(result);
|
|
td::vector<object_ptr<td_api::botCommand>> commands;
|
|
if (user_full_info->bot_info_ != nullptr) {
|
|
commands = std::move(user_full_info->bot_info_->commands_);
|
|
}
|
|
answer_query(td::json_array(commands, [](auto &command) { return JsonBotCommand(command.get()); }),
|
|
std::move(query_));
|
|
}
|
|
|
|
private:
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
class Client::TdOnGetChatFullInfoCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnGetChatFullInfoCallback(Client *client, int64 chat_id, PromisedQueryPtr query)
|
|
: client_(client), chat_id_(chat_id), query_(std::move(query)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result));
|
|
}
|
|
|
|
// we don't need the result, everything is already received through updates
|
|
|
|
client_->send_request(make_object<td_api::getChatPinnedMessage>(chat_id_),
|
|
std::make_unique<TdOnGetChatPinnedMessageCallback>(client_, chat_id_, std::move(query_)));
|
|
}
|
|
|
|
private:
|
|
Client *client_;
|
|
int64 chat_id_;
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
class Client::TdOnGetGroupMembersCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnGetGroupMembersCallback(const Client *client, bool administrators_only, PromisedQueryPtr query)
|
|
: client_(client), administrators_only_(administrators_only), query_(std::move(query)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result));
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::basicGroupFullInfo::ID);
|
|
auto group_full_info = move_object_as<td_api::basicGroupFullInfo>(result);
|
|
answer_query(JsonChatMembers(group_full_info->members_, Client::ChatType::Group, administrators_only_, client_),
|
|
std::move(query_));
|
|
}
|
|
|
|
private:
|
|
const Client *client_;
|
|
bool administrators_only_;
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
class Client::TdOnGetSupergroupMembersCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnGetSupergroupMembersCallback(const Client *client, Client::ChatType chat_type, PromisedQueryPtr query)
|
|
: client_(client), chat_type_(chat_type), query_(std::move(query)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result));
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::chatMembers::ID);
|
|
auto chat_members = move_object_as<td_api::chatMembers>(result);
|
|
answer_query(JsonChatMembers(chat_members->members_, chat_type_, false, client_), std::move(query_));
|
|
}
|
|
|
|
private:
|
|
const Client *client_;
|
|
Client::ChatType chat_type_;
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
class Client::TdOnGetSupergroupMembersCountCallback : public TdQueryCallback {
|
|
public:
|
|
explicit TdOnGetSupergroupMembersCountCallback(PromisedQueryPtr query) : query_(std::move(query)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result));
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::supergroupFullInfo::ID);
|
|
auto supergroup_full_info = move_object_as<td_api::supergroupFullInfo>(result);
|
|
if (supergroup_full_info->member_count_ == 0) {
|
|
return fail_query(400, "Bad Request: need administrator rights", std::move(query_));
|
|
}
|
|
return answer_query(td::VirtuallyJsonableInt(supergroup_full_info->member_count_), std::move(query_));
|
|
}
|
|
|
|
private:
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
class Client::TdOnGenerateChatInviteLinkCallback : public TdQueryCallback {
|
|
public:
|
|
explicit TdOnGenerateChatInviteLinkCallback(PromisedQueryPtr query) : query_(std::move(query)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result));
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::chatInviteLink::ID);
|
|
auto invite_link = move_object_as<td_api::chatInviteLink>(result);
|
|
return answer_query(td::VirtuallyJsonableString(invite_link->invite_link_), std::move(query_));
|
|
}
|
|
|
|
private:
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
class Client::TdOnGetGameHighScoresCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnGetGameHighScoresCallback(const Client *client, PromisedQueryPtr query)
|
|
: client_(client), query_(std::move(query)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result));
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::gameHighScores::ID);
|
|
auto game_high_scores = move_object_as<td_api::gameHighScores>(result);
|
|
answer_query(td::json_array(game_high_scores->scores_,
|
|
[client = client_](auto &score) { return JsonGameHighScore(score.get(), client); }),
|
|
std::move(query_));
|
|
}
|
|
|
|
private:
|
|
const Client *client_;
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
class Client::TdOnReturnFileCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnReturnFileCallback(const Client *client, PromisedQueryPtr query) : client_(client), query_(std::move(query)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result));
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::file::ID);
|
|
auto file = move_object_as<td_api::file>(result);
|
|
answer_query(JsonFile(file.get(), client_), std::move(query_));
|
|
}
|
|
|
|
private:
|
|
const Client *client_;
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
class Client::TdOnReturnStickerSetCallback : public TdQueryCallback {
|
|
public:
|
|
TdOnReturnStickerSetCallback(Client *client, bool return_sticker_set, PromisedQueryPtr query)
|
|
: client_(client), return_sticker_set_(return_sticker_set), query_(std::move(query)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result));
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::stickerSet::ID);
|
|
auto sticker_set = move_object_as<td_api::stickerSet>(result);
|
|
client_->on_get_sticker_set_name(sticker_set->id_, sticker_set->name_);
|
|
if (return_sticker_set_) {
|
|
answer_query(JsonStickerSet(sticker_set.get(), client_), std::move(query_));
|
|
} else {
|
|
answer_query(td::JsonTrue(), std::move(query_));
|
|
}
|
|
}
|
|
|
|
private:
|
|
Client *client_;
|
|
bool return_sticker_set_;
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
class Client::TdOnSendCustomRequestCallback : public TdQueryCallback {
|
|
public:
|
|
explicit TdOnSendCustomRequestCallback(PromisedQueryPtr query) : query_(std::move(query)) {
|
|
}
|
|
|
|
void on_result(object_ptr<td_api::Object> result) override {
|
|
if (result->get_id() == td_api::error::ID) {
|
|
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result));
|
|
}
|
|
|
|
CHECK(result->get_id() == td_api::customRequestResult::ID);
|
|
auto res = move_object_as<td_api::customRequestResult>(result);
|
|
answer_query(JsonCustomJson(res->result_), std::move(query_));
|
|
}
|
|
|
|
private:
|
|
PromisedQueryPtr query_;
|
|
};
|
|
|
|
void Client::close() {
|
|
need_close_ = true;
|
|
if (td_client_.empty()) {
|
|
set_timeout_in(0);
|
|
} else if (!closing_) {
|
|
do_send_request(make_object<td_api::close>(), std::make_unique<TdOnOkCallback>());
|
|
}
|
|
}
|
|
|
|
void Client::log_out() {
|
|
if (!td_client_.empty() && !logging_out_ && !closing_) {
|
|
do_send_request(make_object<td_api::logOut>(), std::make_unique<TdOnOkCallback>());
|
|
}
|
|
}
|
|
|
|
std::size_t Client::get_pending_update_count() const {
|
|
return parameters_->shared_data_->tqueue_->get_size(tqueue_id_);
|
|
}
|
|
|
|
ServerBotInfo Client::get_bot_info() const {
|
|
ServerBotInfo res;
|
|
res.id_ = bot_token_id_;
|
|
res.token_ = bot_token_;
|
|
auto user_info = get_user_info(my_id_);
|
|
if (user_info != nullptr) {
|
|
res.username_ = user_info->username;
|
|
} else if (!was_authorized_) {
|
|
res.username_ = "<unauthorized>";
|
|
} else {
|
|
res.username_ = "<unknown>";
|
|
}
|
|
res.webhook_ = webhook_url_;
|
|
res.has_webhook_certificate_ = has_webhook_certificate_;
|
|
auto &tqueue = parameters_->shared_data_->tqueue_;
|
|
res.head_update_id_ = tqueue->get_head(tqueue_id_).value();
|
|
res.tail_update_id_ = tqueue->get_tail(tqueue_id_).value();
|
|
res.pending_update_count_ = tqueue->get_size(tqueue_id_);
|
|
res.webhook_max_connections_ = webhook_max_connections_;
|
|
res.start_timestamp_ = start_timestamp_;
|
|
return res;
|
|
}
|
|
|
|
void Client::start_up() {
|
|
start_timestamp_ = td::Time::now();
|
|
next_bot_updates_warning_date_ = start_timestamp_ + 600;
|
|
schedule_next_delete_messages_lru();
|
|
webhook_set_date_ = start_timestamp_;
|
|
|
|
sticker_set_names_[GREAT_MINDS_SET_ID] = GREAT_MINDS_SET_NAME.str();
|
|
|
|
auto colon_pos = bot_token_.find_first_of(':');
|
|
if (colon_pos == td::string::npos) {
|
|
LOG(WARNING) << "Wrong bot token " << bot_token_;
|
|
logging_out_ = true;
|
|
return finish_closing();
|
|
}
|
|
bot_token_id_ = bot_token_.substr(0, colon_pos);
|
|
|
|
auto base64_bot_token = bot_token_.substr(colon_pos + 1);
|
|
if (td::base64url_decode(base64_bot_token).is_error() || base64_bot_token.size() < 24) {
|
|
LOG(WARNING) << "Wrong bot token " << bot_token_;
|
|
logging_out_ = true;
|
|
return finish_closing();
|
|
}
|
|
|
|
bot_token_with_dc_ = bot_token_ + (is_test_dc_ ? ":T" : "");
|
|
|
|
auto context = std::make_shared<td::ActorContext>();
|
|
set_context(context);
|
|
set_tag(bot_token_id_);
|
|
|
|
auto r_absolute_dir = td::realpath(td::string(".") + TD_DIR_SLASH, true);
|
|
CHECK(r_absolute_dir.is_ok());
|
|
absolute_dir_ = r_absolute_dir.move_as_ok();
|
|
auto suff = bot_token_with_dc_ + TD_DIR_SLASH;
|
|
#if TD_PORT_WINDOWS
|
|
for (auto &c : suff) {
|
|
if (c == ':') {
|
|
c = '~';
|
|
}
|
|
}
|
|
#endif
|
|
dir_ = td::string(".") + TD_DIR_SLASH + suff;
|
|
if (absolute_dir_.back() != TD_DIR_SLASH) {
|
|
absolute_dir_ += TD_DIR_SLASH;
|
|
}
|
|
absolute_dir_ += suff;
|
|
|
|
class TdCallback : public td::TdCallback {
|
|
public:
|
|
explicit TdCallback(td::ActorId<Client> client) : client_(std::move(client)) {
|
|
}
|
|
void on_result(td::uint64 id, object_ptr<td_api::Object> result) override {
|
|
send_closure_later(client_, &Client::on_result, id, std::move(result));
|
|
}
|
|
void on_error(td::uint64 id, object_ptr<td_api::error> result) override {
|
|
send_closure_later(client_, &Client::on_result, id, std::move(result));
|
|
}
|
|
|
|
private:
|
|
td::ActorId<Client> client_;
|
|
};
|
|
td::ClientActor::Options options;
|
|
options.net_query_stats = parameters_->net_query_stats_;
|
|
td_client_ = td::create_actor<td::ClientActor>("TdClientActor", td::make_unique<TdCallback>(actor_id(this)),
|
|
std::move(options));
|
|
}
|
|
|
|
void Client::send(PromisedQueryPtr query) {
|
|
if (!query->is_internal()) {
|
|
send_closure(
|
|
stat_actor_, &BotStatActor::add_event<ServerBotStat::Request>,
|
|
ServerBotStat::Request{query->query_size(), query->file_count(), query->files_size(), query->files_max_size()},
|
|
td::Time::now());
|
|
}
|
|
cmd_queue_.emplace(std::move(query));
|
|
loop();
|
|
}
|
|
|
|
void Client::raw_event(const td::Event::Raw &event) {
|
|
long_poll_wakeup(true);
|
|
}
|
|
|
|
void Client::loop() {
|
|
if (logging_out_ || closing_ || was_authorized_) {
|
|
while (!cmd_queue_.empty()) {
|
|
auto query = std::move(cmd_queue_.front());
|
|
cmd_queue_.pop();
|
|
on_cmd(std::move(query));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::on_get_reply_message(int64 chat_id, object_ptr<td_api::message> reply_to_message) {
|
|
auto &queue = new_message_queues_[chat_id];
|
|
CHECK(queue.has_active_request_);
|
|
queue.has_active_request_ = false;
|
|
|
|
CHECK(!queue.queue_.empty());
|
|
object_ptr<td_api::message> &message = queue.queue_.front().message;
|
|
CHECK(chat_id == message->chat_id_);
|
|
int64 &reply_to_message_id = get_reply_to_message_id(message);
|
|
CHECK(reply_to_message_id > 0);
|
|
if (reply_to_message == nullptr) {
|
|
LOG(INFO) << "Can't find message " << reply_to_message_id << " in chat " << chat_id
|
|
<< ". It is already deleted or inaccessible because of the chosen privacy mode";
|
|
reply_to_message_id = 0;
|
|
} else {
|
|
CHECK(chat_id == reply_to_message->chat_id_);
|
|
CHECK(reply_to_message_id == reply_to_message->id_);
|
|
LOG(INFO) << "Receive reply to message " << reply_to_message_id << " in chat " << chat_id;
|
|
add_message(std::move(reply_to_message));
|
|
}
|
|
|
|
process_new_message_queue(chat_id);
|
|
}
|
|
|
|
void Client::on_get_edited_message(object_ptr<td_api::message> edited_message) {
|
|
if (edited_message == nullptr) {
|
|
LOG(INFO) << "Can't find just edited message. It is already deleted or inaccessible because of chosen privacy mode";
|
|
} else {
|
|
add_new_message(std::move(edited_message), true);
|
|
}
|
|
}
|
|
|
|
void Client::on_get_callback_query_message(object_ptr<td_api::message> message, int32 user_id, int state) {
|
|
CHECK(user_id != 0);
|
|
auto &queue = new_callback_query_queues_[user_id];
|
|
CHECK(queue.has_active_request_);
|
|
queue.has_active_request_ = false;
|
|
|
|
CHECK(!queue.queue_.empty());
|
|
int64 chat_id = queue.queue_.front()->chat_id_;
|
|
int64 message_id = queue.queue_.front()->message_id_;
|
|
if (message == nullptr) {
|
|
if (state == 0) {
|
|
LOG(INFO) << "Can't find callback query message " << message_id << " in chat " << chat_id
|
|
<< ". It may be already deleted";
|
|
} else {
|
|
CHECK(state == 1);
|
|
auto message_info = get_message_editable(chat_id, message_id);
|
|
if (message_info == nullptr) {
|
|
LOG(INFO) << "Can't find callback query message " << message_id << " in chat " << chat_id
|
|
<< ". It may be already deleted, while searcing for its reply to message";
|
|
process_new_callback_query_queue(user_id, state);
|
|
return;
|
|
}
|
|
LOG(INFO) << "Can't find callback query reply to message " << message_info->reply_to_message_id << " in chat "
|
|
<< chat_id << ". It may be already deleted";
|
|
message_info->is_reply_to_message_deleted = true;
|
|
}
|
|
} else {
|
|
LOG(INFO) << "Receive callback query " << (state == 1 ? "reply to " : "") << "message " << message_id << " in chat "
|
|
<< chat_id;
|
|
add_message(std::move(message));
|
|
}
|
|
process_new_callback_query_queue(user_id, state + 1);
|
|
}
|
|
|
|
void Client::on_get_sticker_set(int64 set_id, int32 new_callback_query_user_id, int64 new_message_chat_id,
|
|
object_ptr<td_api::stickerSet> sticker_set) {
|
|
if (new_callback_query_user_id != 0) {
|
|
auto &queue = new_callback_query_queues_[new_callback_query_user_id];
|
|
CHECK(queue.has_active_request_);
|
|
queue.has_active_request_ = false;
|
|
|
|
CHECK(!queue.queue_.empty());
|
|
}
|
|
if (new_message_chat_id != 0) {
|
|
auto &queue = new_message_queues_[new_message_chat_id];
|
|
CHECK(queue.has_active_request_);
|
|
queue.has_active_request_ = false;
|
|
|
|
CHECK(!queue.queue_.empty());
|
|
}
|
|
|
|
CHECK(set_id != 0);
|
|
if (set_id != GREAT_MINDS_SET_ID) {
|
|
td::string &set_name = sticker_set_names_[set_id];
|
|
if (sticker_set != nullptr) {
|
|
set_name = std::move(sticker_set->name_);
|
|
}
|
|
}
|
|
|
|
if (new_callback_query_user_id != 0) {
|
|
process_new_callback_query_queue(new_callback_query_user_id, 2);
|
|
}
|
|
if (new_message_chat_id != 0) {
|
|
process_new_message_queue(new_message_chat_id);
|
|
}
|
|
}
|
|
|
|
void Client::on_get_sticker_set_name(int64 set_id, const td::string &name) {
|
|
CHECK(set_id != 0);
|
|
if (set_id != GREAT_MINDS_SET_ID) {
|
|
sticker_set_names_[set_id] = name;
|
|
}
|
|
}
|
|
|
|
template <class OnSuccess>
|
|
void Client::check_user_read_access(const UserInfo *user_info, PromisedQueryPtr query, OnSuccess on_success) {
|
|
CHECK(user_info != nullptr);
|
|
if (!user_info->have_access) {
|
|
return fail_query(400, "Bad Request: have no access to the user", std::move(query));
|
|
}
|
|
on_success(std::move(query));
|
|
}
|
|
|
|
template <class OnSuccess>
|
|
void Client::check_user(int32 user_id, PromisedQueryPtr query, OnSuccess on_success) {
|
|
const UserInfo *user_info = get_user_info(user_id);
|
|
if (user_info != nullptr) {
|
|
return check_user_read_access(user_info, std::move(query), std::move(on_success));
|
|
}
|
|
send_request(make_object<td_api::getUser>(user_id),
|
|
std::make_unique<TdOnCheckUserCallback<OnSuccess>>(this, std::move(query), std::move(on_success)));
|
|
}
|
|
|
|
template <class OnSuccess>
|
|
void Client::check_user_no_fail(int32 user_id, PromisedQueryPtr query, OnSuccess on_success) {
|
|
const UserInfo *user_info = get_user_info(user_id);
|
|
if (user_info != nullptr) {
|
|
on_success(std::move(query));
|
|
return;
|
|
}
|
|
send_request(make_object<td_api::getUser>(user_id),
|
|
std::make_unique<TdOnCheckUserNoFailCallback<OnSuccess>>(std::move(query), std::move(on_success)));
|
|
}
|
|
|
|
template <class OnSuccess>
|
|
void Client::check_chat_access(int64 chat_id, AccessRights access_rights, const ChatInfo *chat_info,
|
|
PromisedQueryPtr query, OnSuccess on_success) const {
|
|
CHECK(chat_info != nullptr);
|
|
bool need_write_access = access_rights == AccessRights::Write;
|
|
bool need_edit_access = access_rights == AccessRights::Edit || need_write_access;
|
|
bool need_read_access = true;
|
|
switch (chat_info->type) {
|
|
case ChatInfo::Type::Private: {
|
|
auto user_info = get_user_info(chat_info->user_id);
|
|
CHECK(user_info != nullptr);
|
|
if (user_info->type == UserInfo::Type::Deleted && need_edit_access) {
|
|
return fail_query(403, "Forbidden: user is deactivated", std::move(query));
|
|
}
|
|
if (user_info->type == UserInfo::Type::Unknown) {
|
|
return fail_query(400, "Bad Request: private chat not found", std::move(query));
|
|
}
|
|
break;
|
|
}
|
|
case ChatInfo::Type::Group: {
|
|
auto group_info = get_group_info(chat_info->group_id);
|
|
CHECK(group_info != nullptr);
|
|
if (!group_info->is_active && need_write_access) {
|
|
if (group_info->upgraded_to_supergroup_id != 0) {
|
|
std::unordered_map<td::string, std::unique_ptr<td::VirtuallyJsonable>> parameters;
|
|
auto updagraded_to_chat_id = get_supergroup_chat_id(group_info->upgraded_to_supergroup_id);
|
|
parameters.emplace("migrate_to_chat_id", std::make_unique<td::VirtuallyJsonableLong>(updagraded_to_chat_id));
|
|
return fail_query(400, "Bad Request: group chat was upgraded to a supergroup chat", std::move(query),
|
|
std::move(parameters));
|
|
} else {
|
|
LOG(WARNING) << "Group chat " << chat_info->group_id << " with " << group_info->member_count
|
|
<< " members and title \"" << chat_info->title << "\" is deactivated";
|
|
return fail_query(400, "Bad Request: group chat was deactivated", std::move(query));
|
|
}
|
|
}
|
|
if (group_info->is_active && group_info->kicked && need_edit_access) {
|
|
return fail_query(403, "Forbidden: bot was kicked from the group chat", std::move(query));
|
|
}
|
|
if (group_info->is_active && group_info->left && need_edit_access) {
|
|
return fail_query(403, "Forbidden: bot is not a member of the group chat", std::move(query));
|
|
}
|
|
break;
|
|
}
|
|
case ChatInfo::Type::Supergroup: {
|
|
auto supergroup_info = get_supergroup_info(chat_info->supergroup_id);
|
|
CHECK(supergroup_info != nullptr);
|
|
bool is_public = !supergroup_info->username.empty() || supergroup_info->has_location;
|
|
if (supergroup_info->status->get_id() == td_api::chatMemberStatusBanned::ID) {
|
|
if (supergroup_info->is_supergroup) {
|
|
return fail_query(403, "Forbidden: bot was kicked from the supergroup chat", std::move(query));
|
|
} else {
|
|
return fail_query(403, "Forbidden: bot was kicked from the channel chat", std::move(query));
|
|
}
|
|
}
|
|
bool need_more_access_rights = is_public ? need_edit_access : need_read_access;
|
|
if (supergroup_info->status->get_id() == td_api::chatMemberStatusLeft::ID && need_more_access_rights) {
|
|
if (supergroup_info->is_supergroup) {
|
|
return fail_query(403, "Forbidden: bot is not a member of the supergroup chat", std::move(query));
|
|
} else {
|
|
return fail_query(403, "Forbidden: bot is not a member of the channel chat", std::move(query));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case ChatInfo::Type::Unknown:
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
on_success(chat_id, std::move(query));
|
|
}
|
|
|
|
template <class OnSuccess>
|
|
void Client::check_chat(Slice chat_id_str, AccessRights access_rights, PromisedQueryPtr query, OnSuccess on_success) {
|
|
if (chat_id_str.empty()) {
|
|
return fail_query(400, "Bad Request: chat_id is empty", std::move(query));
|
|
}
|
|
|
|
if (chat_id_str[0] == '@') {
|
|
return send_request(make_object<td_api::searchPublicChat>(chat_id_str.str()),
|
|
std::make_unique<TdOnCheckChatCallback<OnSuccess>>(this, true, access_rights, std::move(query),
|
|
std::move(on_success)));
|
|
}
|
|
|
|
auto chat_id = td::to_integer<int64>(chat_id_str);
|
|
auto chat_info = get_chat(chat_id);
|
|
if (chat_info != nullptr) {
|
|
return check_chat_access(chat_id, access_rights, chat_info, std::move(query), std::move(on_success));
|
|
}
|
|
send_request(make_object<td_api::getChat>(chat_id),
|
|
std::make_unique<TdOnCheckChatCallback<OnSuccess>>(this, false, access_rights, std::move(query),
|
|
std::move(on_success)));
|
|
}
|
|
|
|
template <class OnSuccess>
|
|
void Client::check_remote_file_id(td::string file_id, PromisedQueryPtr query, OnSuccess on_success) {
|
|
if (file_id.empty()) {
|
|
return fail_query(400, "Bad Request: file_id not specified", std::move(query));
|
|
}
|
|
|
|
send_request(make_object<td_api::getRemoteFile>(std::move(file_id), nullptr),
|
|
std::make_unique<TdOnCheckRemoteFileIdCallback<OnSuccess>>(std::move(query), std::move(on_success)));
|
|
}
|
|
|
|
bool Client::is_chat_member(const object_ptr<td_api::ChatMemberStatus> &status) {
|
|
switch (status->get_id()) {
|
|
case td_api::chatMemberStatusBanned::ID:
|
|
case td_api::chatMemberStatusLeft::ID:
|
|
return false;
|
|
case td_api::chatMemberStatusRestricted::ID:
|
|
return static_cast<const td_api::chatMemberStatusRestricted *>(status.get())->is_member_;
|
|
default:
|
|
// ignore Creator.is_member_
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool Client::have_message_access(int64 chat_id) const {
|
|
auto chat_info = get_chat(chat_id);
|
|
CHECK(chat_info != nullptr);
|
|
switch (chat_info->type) {
|
|
case ChatInfo::Type::Private:
|
|
case ChatInfo::Type::Group:
|
|
return true;
|
|
case ChatInfo::Type::Supergroup: {
|
|
auto supergroup_info = get_supergroup_info(chat_info->supergroup_id);
|
|
CHECK(supergroup_info != nullptr);
|
|
return is_chat_member(supergroup_info->status);
|
|
}
|
|
case ChatInfo::Type::Unknown:
|
|
default:
|
|
UNREACHABLE();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
template <class OnSuccess>
|
|
void Client::check_message(Slice chat_id_str, int64 message_id, bool allow_empty, AccessRights access_rights,
|
|
Slice message_type, PromisedQueryPtr query, OnSuccess on_success) {
|
|
check_chat(chat_id_str, access_rights, std::move(query),
|
|
[this, message_id, allow_empty, message_type, on_success = std::move(on_success)](
|
|
int64 chat_id, PromisedQueryPtr query) mutable {
|
|
if (!have_message_access(chat_id)) {
|
|
return fail_query_with_error(std::move(query), 400, "MESSAGE_NOT_FOUND",
|
|
PSLICE() << message_type << " not found");
|
|
}
|
|
|
|
send_request(make_object<td_api::getMessage>(chat_id, message_id),
|
|
std::make_unique<TdOnCheckMessageCallback<OnSuccess>>(
|
|
this, chat_id, allow_empty, message_type, std::move(query), std::move(on_success)));
|
|
});
|
|
}
|
|
|
|
template <class OnSuccess>
|
|
void Client::resolve_sticker_set(const td::string &sticker_set_name, PromisedQueryPtr query, OnSuccess on_success) {
|
|
if (sticker_set_name.empty()) {
|
|
return fail_query(400, "Bad Request: sticker_set_name is empty", std::move(query));
|
|
}
|
|
|
|
send_request(make_object<td_api::searchStickerSet>(sticker_set_name),
|
|
std::make_unique<TdOnSearchStickerSetCallback<OnSuccess>>(std::move(query), std::move(on_success)));
|
|
}
|
|
|
|
void Client::fix_reply_markup_bot_user_ids(object_ptr<td_api::ReplyMarkup> &reply_markup) const {
|
|
if (reply_markup == nullptr || reply_markup->get_id() != td_api::replyMarkupInlineKeyboard::ID) {
|
|
return;
|
|
}
|
|
auto inline_keyboard = static_cast<td_api::replyMarkupInlineKeyboard *>(reply_markup.get());
|
|
for (auto &row : inline_keyboard->rows_) {
|
|
for (auto &button : row) {
|
|
CHECK(button != nullptr);
|
|
CHECK(button->type_ != nullptr);
|
|
if (button->type_->get_id() != td_api::inlineKeyboardButtonTypeLoginUrl::ID) {
|
|
continue;
|
|
}
|
|
auto login_url_button = static_cast<td_api::inlineKeyboardButtonTypeLoginUrl *>(button->type_.get());
|
|
if (login_url_button->id_ % 1000 != 0) {
|
|
continue;
|
|
}
|
|
auto it = temp_to_real_bot_user_id_.find(std::abs(login_url_button->id_));
|
|
CHECK(it != temp_to_real_bot_user_id_.end());
|
|
auto bot_user_id = it->second;
|
|
CHECK(bot_user_id != 0);
|
|
if (login_url_button->id_ < 0) {
|
|
login_url_button->id_ = -bot_user_id;
|
|
} else {
|
|
login_url_button->id_ = bot_user_id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::fix_inline_query_results_bot_user_ids(
|
|
td::vector<object_ptr<td_api::InputInlineQueryResult>> &results) const {
|
|
for (auto &result : results) {
|
|
td_api::downcast_call(
|
|
*result, [this](auto &result_type) { this->fix_reply_markup_bot_user_ids(result_type.reply_markup_); });
|
|
}
|
|
}
|
|
|
|
void Client::resolve_bot_usernames(PromisedQueryPtr query, td::Promise<PromisedQueryPtr> on_success) {
|
|
CHECK(!unresolved_bot_usernames_.empty());
|
|
auto query_id = current_bot_resolve_query_id_++;
|
|
auto &pending_query = pending_bot_resolve_queries_[query_id];
|
|
pending_query.pending_resolve_count = unresolved_bot_usernames_.size();
|
|
pending_query.query = std::move(query);
|
|
pending_query.on_success = std::move(on_success);
|
|
for (auto &username : unresolved_bot_usernames_) {
|
|
auto &query_ids = awaiting_bot_resolve_queries_[username];
|
|
query_ids.push_back(query_id);
|
|
if (query_ids.size() == 1) {
|
|
send_request(make_object<td_api::searchPublicChat>(username),
|
|
std::make_unique<TdOnResolveBotUsernameCallback>(this, username));
|
|
}
|
|
}
|
|
unresolved_bot_usernames_.clear();
|
|
}
|
|
|
|
template <class OnSuccess>
|
|
void Client::resolve_reply_markup_bot_usernames(object_ptr<td_api::ReplyMarkup> reply_markup, PromisedQueryPtr query,
|
|
OnSuccess on_success) {
|
|
if (!unresolved_bot_usernames_.empty()) {
|
|
CHECK(reply_markup != nullptr);
|
|
CHECK(reply_markup->get_id() == td_api::replyMarkupInlineKeyboard::ID);
|
|
return resolve_bot_usernames(
|
|
std::move(query),
|
|
td::PromiseCreator::lambda([this, reply_markup = std::move(reply_markup),
|
|
on_success = std::move(on_success)](td::Result<PromisedQueryPtr> result) mutable {
|
|
if (result.is_ok()) {
|
|
fix_reply_markup_bot_user_ids(reply_markup);
|
|
on_success(std::move(reply_markup), result.move_as_ok());
|
|
}
|
|
}));
|
|
}
|
|
on_success(std::move(reply_markup), std::move(query));
|
|
}
|
|
|
|
template <class OnSuccess>
|
|
void Client::resolve_inline_query_results_bot_usernames(td::vector<object_ptr<td_api::InputInlineQueryResult>> results,
|
|
PromisedQueryPtr query, OnSuccess on_success) {
|
|
if (!unresolved_bot_usernames_.empty()) {
|
|
return resolve_bot_usernames(
|
|
std::move(query),
|
|
td::PromiseCreator::lambda([this, results = std::move(results),
|
|
on_success = std::move(on_success)](td::Result<PromisedQueryPtr> result) mutable {
|
|
if (result.is_ok()) {
|
|
fix_inline_query_results_bot_user_ids(results);
|
|
on_success(std::move(results), result.move_as_ok());
|
|
}
|
|
}));
|
|
}
|
|
on_success(std::move(results), std::move(query));
|
|
}
|
|
|
|
void Client::on_resolve_bot_username(const td::string &username, int32 user_id) {
|
|
auto query_ids_it = awaiting_bot_resolve_queries_.find(username);
|
|
CHECK(query_ids_it != awaiting_bot_resolve_queries_.end());
|
|
CHECK(!query_ids_it->second.empty());
|
|
auto query_ids = std::move(query_ids_it->second);
|
|
awaiting_bot_resolve_queries_.erase(query_ids_it);
|
|
|
|
if (user_id == 0) {
|
|
bot_user_ids_.erase(username);
|
|
} else {
|
|
auto &temp_bot_user_id = bot_user_ids_[username];
|
|
temp_to_real_bot_user_id_[temp_bot_user_id] = user_id;
|
|
temp_bot_user_id = user_id;
|
|
}
|
|
|
|
for (auto query_id : query_ids) {
|
|
auto it = pending_bot_resolve_queries_.find(query_id);
|
|
if (it == pending_bot_resolve_queries_.end()) {
|
|
// the query has already failed
|
|
continue;
|
|
}
|
|
CHECK(it->second.pending_resolve_count > 0);
|
|
it->second.pending_resolve_count--;
|
|
if (it->second.pending_resolve_count == 0 || user_id == 0) {
|
|
if (user_id == 0) {
|
|
fail_query(400, PSTRING() << "Bad Request: bot \"" << username << "\" not found", std::move(it->second.query));
|
|
} else {
|
|
it->second.on_success.set_value(std::move(it->second.query));
|
|
}
|
|
pending_bot_resolve_queries_.erase(it);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <class OnSuccess>
|
|
void Client::get_chat_member(int64 chat_id, int32 user_id, PromisedQueryPtr query, OnSuccess on_success) {
|
|
check_user_no_fail(
|
|
user_id, std::move(query),
|
|
[this, chat_id, user_id, on_success = std::move(on_success)](PromisedQueryPtr query) mutable {
|
|
send_request(make_object<td_api::getChatMember>(chat_id, user_id),
|
|
std::make_unique<TdOnGetChatMemberCallback<OnSuccess>>(std::move(query), std::move(on_success)));
|
|
});
|
|
}
|
|
|
|
void Client::send_request(object_ptr<td_api::Function> &&f, std::unique_ptr<TdQueryCallback> handler) {
|
|
if (logging_out_) {
|
|
return handler->on_result(make_object<td_api::error>(LOGGING_OUT_ERROR_CODE, LOGGING_OUT_ERROR_DESCRIPTION.str()));
|
|
}
|
|
if (closing_) {
|
|
return handler->on_result(make_object<td_api::error>(CLOSING_ERROR_CODE, CLOSING_ERROR_DESCRIPTION.str()));
|
|
}
|
|
|
|
do_send_request(std::move(f), std::move(handler));
|
|
}
|
|
|
|
void Client::do_send_request(object_ptr<td_api::Function> &&f, std::unique_ptr<TdQueryCallback> handler) {
|
|
CHECK(!td_client_.empty());
|
|
auto id = handlers_.create(std::move(handler));
|
|
send_closure(td_client_, &td::ClientActor::request, id, std::move(f));
|
|
}
|
|
|
|
td_api::object_ptr<td_api::Object> Client::execute(object_ptr<td_api::Function> &&f) {
|
|
return td::ClientActor::execute(std::move(f));
|
|
}
|
|
|
|
void Client::on_update_file(object_ptr<td_api::file> file) {
|
|
auto file_id = file->id_;
|
|
if (!is_file_being_downloaded(file_id)) {
|
|
return;
|
|
}
|
|
if (!parameters_->local_mode_ && file->local_->downloaded_size_ > MAX_DOWNLOAD_FILE_SIZE) {
|
|
if (file->local_->is_downloading_active_) {
|
|
send_request(make_object<td_api::cancelDownloadFile>(file_id, false),
|
|
std::make_unique<TdOnCancelDownloadFileCallback>());
|
|
}
|
|
return on_file_download(file_id, Status::Error(400, "Bad Request: file is too big"));
|
|
}
|
|
if (file->local_->is_downloading_completed_) {
|
|
return on_file_download(file_id, std::move(file));
|
|
}
|
|
if (!file->local_->is_downloading_active_ && download_started_file_ids_.count(file_id)) {
|
|
// also includes all 5xx and 429 errors
|
|
auto error = Status::Error(400, "Bad Request: wrong file_id or the file is temporarily unavailable");
|
|
if (logging_out_) {
|
|
error = Status::Error(LOGGING_OUT_ERROR_CODE, LOGGING_OUT_ERROR_DESCRIPTION);
|
|
}
|
|
if (closing_) {
|
|
error = Status::Error(CLOSING_ERROR_CODE, CLOSING_ERROR_DESCRIPTION);
|
|
}
|
|
return on_file_download(file_id, std::move(error));
|
|
}
|
|
}
|
|
|
|
void Client::on_update_authorization_state() {
|
|
CHECK(authorization_state_ != nullptr);
|
|
switch (authorization_state_->get_id()) {
|
|
case td_api::authorizationStateWaitTdlibParameters::ID: {
|
|
send_request(
|
|
make_object<td_api::setOption>("ignore_inline_thumbnails", make_object<td_api::optionValueBoolean>(true)),
|
|
std::make_unique<TdOnOkCallback>());
|
|
send_request(make_object<td_api::setOption>("reuse_uploaded_photos_by_hash",
|
|
make_object<td_api::optionValueBoolean>(true)),
|
|
std::make_unique<TdOnOkCallback>());
|
|
send_request(make_object<td_api::setOption>("disable_persistent_network_statistics",
|
|
make_object<td_api::optionValueBoolean>(true)),
|
|
std::make_unique<TdOnOkCallback>());
|
|
send_request(make_object<td_api::setOption>("disable_time_adjustment_protection",
|
|
make_object<td_api::optionValueBoolean>(true)),
|
|
std::make_unique<TdOnOkCallback>());
|
|
|
|
auto parameters = make_object<td_api::tdlibParameters>();
|
|
|
|
parameters->use_test_dc_ = is_test_dc_;
|
|
parameters->database_directory_ = dir_;
|
|
//parameters->use_file_database_ = false;
|
|
//parameters->use_chat_info_database_ = false;
|
|
//parameters->use_secret_chats_ = false;
|
|
parameters->use_message_database_ = USE_MESSAGE_DATABASE;
|
|
parameters->api_id_ = parameters_->api_id_;
|
|
parameters->api_hash_ = parameters_->api_hash_;
|
|
parameters->system_language_code_ = "en";
|
|
parameters->device_model_ = "server";
|
|
parameters->application_version_ = "5.0";
|
|
parameters->enable_storage_optimizer_ = true;
|
|
parameters->ignore_file_names_ = true;
|
|
|
|
return send_request(make_object<td_api::setTdlibParameters>(std::move(parameters)),
|
|
std::make_unique<TdOnInitCallback>(this));
|
|
}
|
|
case td_api::authorizationStateWaitEncryptionKey::ID:
|
|
return send_request(make_object<td_api::checkDatabaseEncryptionKey>(), std::make_unique<TdOnInitCallback>(this));
|
|
case td_api::authorizationStateWaitPhoneNumber::ID:
|
|
return send_request(make_object<td_api::checkAuthenticationBotToken>(bot_token_),
|
|
std::make_unique<TdOnAuthorizationCallback>(this));
|
|
case td_api::authorizationStateReady::ID: {
|
|
auto user_info = get_user_info(my_id_);
|
|
if (my_id_ <= 0 || user_info == nullptr) {
|
|
return send_request(make_object<td_api::getMe>(), std::make_unique<TdOnAuthorizationCallback>(this));
|
|
}
|
|
|
|
if (!was_authorized_) {
|
|
LOG(WARNING) << "Logged in as @" << user_info->username;
|
|
was_authorized_ = true;
|
|
update_shared_unix_time_difference();
|
|
if (!pending_updates_.empty()) {
|
|
LOG(INFO) << "Process " << pending_updates_.size() << " pending updates";
|
|
for (auto &update : pending_updates_) {
|
|
on_update(std::move(update));
|
|
}
|
|
td::reset_to_empty(pending_updates_);
|
|
}
|
|
}
|
|
return loop();
|
|
}
|
|
case td_api::authorizationStateLoggingOut::ID:
|
|
if (!logging_out_) {
|
|
LOG(WARNING) << "Logging out";
|
|
logging_out_ = true;
|
|
}
|
|
break;
|
|
case td_api::authorizationStateClosing::ID:
|
|
if (!closing_) {
|
|
LOG(WARNING) << "Closing";
|
|
closing_ = true;
|
|
}
|
|
break;
|
|
case td_api::authorizationStateClosed::ID:
|
|
return on_closed();
|
|
default:
|
|
return log_out(); // just in case
|
|
}
|
|
}
|
|
|
|
bool Client::allow_update_before_authorization(const td_api::Object *update) const {
|
|
auto update_id = update->get_id();
|
|
if (update_id == td_api::updateAuthorizationState::ID) {
|
|
return true;
|
|
}
|
|
if (update_id == td_api::updateOption::ID) {
|
|
const auto &name = static_cast<const td_api::updateOption *>(update)->name_;
|
|
return name == "my_id" || name == "unix_time";
|
|
}
|
|
if (update_id == td_api::updateUser::ID) {
|
|
return static_cast<const td_api::updateUser *>(update)->user_->id_ == my_id_;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Client::update_shared_unix_time_difference() {
|
|
CHECK(was_authorized_);
|
|
LOG_IF(ERROR, local_unix_time_difference_ == 0) << "Unix time difference was not updated";
|
|
auto data = parameters_->shared_data_.get();
|
|
if (local_unix_time_difference_ > data->unix_time_difference_) {
|
|
data->unix_time_difference_ = local_unix_time_difference_;
|
|
}
|
|
}
|
|
|
|
void Client::on_update(object_ptr<td_api::Object> result) {
|
|
if (!was_authorized_ && !allow_update_before_authorization(result.get())) {
|
|
pending_updates_.push_back(std::move(result));
|
|
return;
|
|
}
|
|
switch (result->get_id()) {
|
|
case td_api::updateAuthorizationState::ID: {
|
|
auto update = move_object_as<td_api::updateAuthorizationState>(result);
|
|
authorization_state_ = std::move(update->authorization_state_);
|
|
on_update_authorization_state();
|
|
break;
|
|
}
|
|
case td_api::updateNewMessage::ID: {
|
|
auto update = move_object_as<td_api::updateNewMessage>(result);
|
|
add_new_message(std::move(update->message_), false);
|
|
break;
|
|
}
|
|
case td_api::updateMessageSendSucceeded::ID: {
|
|
auto update = move_object_as<td_api::updateMessageSendSucceeded>(result);
|
|
on_message_send_succeeded(std::move(update->message_), update->old_message_id_);
|
|
break;
|
|
}
|
|
case td_api::updateMessageSendFailed::ID: {
|
|
auto update = move_object_as<td_api::updateMessageSendFailed>(result);
|
|
on_message_send_failed(update->message_->chat_id_, update->old_message_id_, update->message_->id_,
|
|
Status::Error(update->error_code_, update->error_message_));
|
|
break;
|
|
}
|
|
case td_api::updateMessageContent::ID: {
|
|
auto update = move_object_as<td_api::updateMessageContent>(result);
|
|
update_message_content(update->chat_id_, update->message_id_, std::move(update->new_content_));
|
|
break;
|
|
}
|
|
case td_api::updateMessageEdited::ID: {
|
|
auto update = move_object_as<td_api::updateMessageEdited>(result);
|
|
auto chat_id = update->chat_id_;
|
|
auto message_id = update->message_id_;
|
|
on_update_message_edited(chat_id, message_id, update->edit_date_, std::move(update->reply_markup_));
|
|
send_request(make_object<td_api::getMessage>(chat_id, message_id),
|
|
std::make_unique<TdOnGetEditedMessageCallback>(this));
|
|
break;
|
|
}
|
|
case td_api::updateDeleteMessages::ID: {
|
|
auto update = move_object_as<td_api::updateDeleteMessages>(result);
|
|
for (auto message_id : update->message_ids_) {
|
|
delete_message(update->chat_id_, message_id, update->from_cache_);
|
|
}
|
|
break;
|
|
}
|
|
case td_api::updateFile::ID: {
|
|
auto update = move_object_as<td_api::updateFile>(result);
|
|
on_update_file(std::move(update->file_));
|
|
break;
|
|
}
|
|
case td_api::updateFileGenerationStart::ID: {
|
|
auto update = move_object_as<td_api::updateFileGenerationStart>(result);
|
|
auto generation_id = update->generation_id_;
|
|
send_request(
|
|
make_object<td_api::finishFileGeneration>(generation_id, make_object<td_api::error>(400, "Wrong file_id")),
|
|
std::make_unique<TdOnOkCallback>());
|
|
break;
|
|
}
|
|
case td_api::updateNewChat::ID: {
|
|
auto update = move_object_as<td_api::updateNewChat>(result);
|
|
auto chat = std::move(update->chat_);
|
|
auto chat_info = add_chat(chat->id_);
|
|
bool need_warning = false;
|
|
switch (chat->type_->get_id()) {
|
|
case td_api::chatTypePrivate::ID: {
|
|
auto info = move_object_as<td_api::chatTypePrivate>(chat->type_);
|
|
chat_info->type = ChatInfo::Type::Private;
|
|
auto user_id = info->user_id_;
|
|
chat_info->user_id = user_id;
|
|
need_warning = get_user_info(user_id) == nullptr;
|
|
break;
|
|
}
|
|
case td_api::chatTypeBasicGroup::ID: {
|
|
auto info = move_object_as<td_api::chatTypeBasicGroup>(chat->type_);
|
|
chat_info->type = ChatInfo::Type::Group;
|
|
auto group_id = info->basic_group_id_;
|
|
chat_info->group_id = group_id;
|
|
need_warning = get_group_info(group_id) == nullptr;
|
|
break;
|
|
}
|
|
case td_api::chatTypeSupergroup::ID: {
|
|
auto info = move_object_as<td_api::chatTypeSupergroup>(chat->type_);
|
|
chat_info->type = ChatInfo::Type::Supergroup;
|
|
auto supergroup_id = info->supergroup_id_;
|
|
chat_info->supergroup_id = supergroup_id;
|
|
need_warning = get_supergroup_info(supergroup_id) == nullptr;
|
|
break;
|
|
}
|
|
case td_api::chatTypeSecret::ID: {
|
|
// unsupported
|
|
break;
|
|
}
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
if (need_warning) {
|
|
LOG(ERROR) << "Received updateNewChat about chat " << chat->id_ << ", but hadn't received corresponding info";
|
|
}
|
|
|
|
chat_info->title = std::move(chat->title_);
|
|
chat_info->photo = std::move(chat->photo_);
|
|
chat_info->permissions = std::move(chat->permissions_);
|
|
break;
|
|
}
|
|
case td_api::updateChatTitle::ID: {
|
|
auto update = move_object_as<td_api::updateChatTitle>(result);
|
|
auto chat_info = add_chat(update->chat_id_);
|
|
CHECK(chat_info->type != ChatInfo::Type::Unknown);
|
|
chat_info->title = std::move(update->title_);
|
|
break;
|
|
}
|
|
case td_api::updateChatPhoto::ID: {
|
|
auto update = move_object_as<td_api::updateChatPhoto>(result);
|
|
auto chat_info = add_chat(update->chat_id_);
|
|
CHECK(chat_info->type != ChatInfo::Type::Unknown);
|
|
chat_info->photo = std::move(update->photo_);
|
|
break;
|
|
}
|
|
case td_api::updateChatPermissions::ID: {
|
|
auto update = move_object_as<td_api::updateChatPermissions>(result);
|
|
auto chat_info = add_chat(update->chat_id_);
|
|
CHECK(chat_info->type != ChatInfo::Type::Unknown);
|
|
chat_info->permissions = std::move(update->permissions_);
|
|
break;
|
|
}
|
|
case td_api::updateUser::ID: {
|
|
auto update = move_object_as<td_api::updateUser>(result);
|
|
add_user(users_, std::move(update->user_));
|
|
break;
|
|
}
|
|
case td_api::updateUserFullInfo::ID: {
|
|
auto update = move_object_as<td_api::updateUserFullInfo>(result);
|
|
auto user_id = update->user_id_;
|
|
set_user_bio(user_id, std::move(update->user_full_info_->bio_));
|
|
break;
|
|
}
|
|
case td_api::updateBasicGroup::ID: {
|
|
auto update = move_object_as<td_api::updateBasicGroup>(result);
|
|
add_group(groups_, std::move(update->basic_group_));
|
|
break;
|
|
}
|
|
case td_api::updateBasicGroupFullInfo::ID: {
|
|
auto update = move_object_as<td_api::updateBasicGroupFullInfo>(result);
|
|
auto group_id = update->basic_group_id_;
|
|
set_group_description(group_id, std::move(update->basic_group_full_info_->description_));
|
|
set_group_invite_link(group_id, std::move(update->basic_group_full_info_->invite_link_));
|
|
break;
|
|
}
|
|
case td_api::updateSupergroup::ID: {
|
|
auto update = move_object_as<td_api::updateSupergroup>(result);
|
|
add_supergroup(supergroups_, std::move(update->supergroup_));
|
|
break;
|
|
}
|
|
case td_api::updateSupergroupFullInfo::ID: {
|
|
auto update = move_object_as<td_api::updateSupergroupFullInfo>(result);
|
|
auto supergroup_id = update->supergroup_id_;
|
|
set_supergroup_description(supergroup_id, std::move(update->supergroup_full_info_->description_));
|
|
set_supergroup_invite_link(supergroup_id, std::move(update->supergroup_full_info_->invite_link_));
|
|
set_supergroup_sticker_set_id(supergroup_id, update->supergroup_full_info_->sticker_set_id_);
|
|
set_supergroup_can_set_sticker_set(supergroup_id, update->supergroup_full_info_->can_set_sticker_set_);
|
|
set_supergroup_slow_mode_delay(supergroup_id, update->supergroup_full_info_->slow_mode_delay_);
|
|
set_supergroup_linked_chat_id(supergroup_id, update->supergroup_full_info_->linked_chat_id_);
|
|
set_supergroup_location(supergroup_id, std::move(update->supergroup_full_info_->location_));
|
|
break;
|
|
}
|
|
case td_api::updateOption::ID: {
|
|
auto update = move_object_as<td_api::updateOption>(result);
|
|
auto name = update->name_;
|
|
if (name == "my_id") {
|
|
if (update->value_->get_id() == td_api::optionValueEmpty::ID) {
|
|
CHECK(logging_out_);
|
|
my_id_ = -1;
|
|
} else {
|
|
CHECK(update->value_->get_id() == td_api::optionValueInteger::ID);
|
|
my_id_ = static_cast<int32>(move_object_as<td_api::optionValueInteger>(update->value_)->value_);
|
|
}
|
|
}
|
|
if (name == "group_anonymous_bot_user_id" && update->value_->get_id() == td_api::optionValueInteger::ID) {
|
|
group_anonymous_bot_user_id_ =
|
|
static_cast<int32>(move_object_as<td_api::optionValueInteger>(update->value_)->value_);
|
|
}
|
|
if (name == "telegram_service_notifications_chat_id" &&
|
|
update->value_->get_id() == td_api::optionValueInteger::ID) {
|
|
service_notifications_user_id_ =
|
|
static_cast<int32>(move_object_as<td_api::optionValueInteger>(update->value_)->value_);
|
|
}
|
|
if (name == "authorization_date") {
|
|
if (update->value_->get_id() == td_api::optionValueEmpty::ID) {
|
|
authorization_date_ = -1;
|
|
} else {
|
|
CHECK(update->value_->get_id() == td_api::optionValueInteger::ID);
|
|
authorization_date_ = static_cast<int32>(move_object_as<td_api::optionValueInteger>(update->value_)->value_);
|
|
}
|
|
}
|
|
if (name == "xallowed_update_types") {
|
|
if (update->value_->get_id() == td_api::optionValueEmpty::ID) {
|
|
allowed_update_types_ = DEFAULT_ALLOWED_UPDATE_TYPES;
|
|
} else {
|
|
CHECK(update->value_->get_id() == td_api::optionValueInteger::ID);
|
|
allowed_update_types_ =
|
|
static_cast<td::uint32>(move_object_as<td_api::optionValueInteger>(update->value_)->value_);
|
|
}
|
|
}
|
|
if (name == "unix_time" && update->value_->get_id() != td_api::optionValueEmpty::ID) {
|
|
CHECK(update->value_->get_id() == td_api::optionValueInteger::ID);
|
|
local_unix_time_difference_ =
|
|
static_cast<double>(move_object_as<td_api::optionValueInteger>(update->value_)->value_) - td::Time::now();
|
|
if (was_authorized_) {
|
|
update_shared_unix_time_difference();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case td_api::updatePoll::ID:
|
|
add_update_poll(move_object_as<td_api::updatePoll>(result));
|
|
break;
|
|
case td_api::updatePollAnswer::ID:
|
|
add_update_poll_answer(move_object_as<td_api::updatePollAnswer>(result));
|
|
break;
|
|
case td_api::updateNewInlineQuery::ID: {
|
|
auto update = move_object_as<td_api::updateNewInlineQuery>(result);
|
|
add_new_inline_query(update->id_, update->sender_user_id_, std::move(update->user_location_), update->query_,
|
|
update->offset_);
|
|
break;
|
|
}
|
|
case td_api::updateNewChosenInlineResult::ID: {
|
|
auto update = move_object_as<td_api::updateNewChosenInlineResult>(result);
|
|
add_new_chosen_inline_result(update->sender_user_id_, std::move(update->user_location_), update->query_,
|
|
update->result_id_, update->inline_message_id_);
|
|
break;
|
|
}
|
|
case td_api::updateNewCallbackQuery::ID:
|
|
add_new_callback_query(move_object_as<td_api::updateNewCallbackQuery>(result));
|
|
break;
|
|
case td_api::updateNewInlineCallbackQuery::ID:
|
|
add_new_inline_callback_query(move_object_as<td_api::updateNewInlineCallbackQuery>(result));
|
|
break;
|
|
case td_api::updateNewShippingQuery::ID:
|
|
add_new_shipping_query(move_object_as<td_api::updateNewShippingQuery>(result));
|
|
break;
|
|
case td_api::updateNewPreCheckoutQuery::ID:
|
|
add_new_pre_checkout_query(move_object_as<td_api::updateNewPreCheckoutQuery>(result));
|
|
break;
|
|
case td_api::updateNewCustomEvent::ID:
|
|
add_new_custom_event(move_object_as<td_api::updateNewCustomEvent>(result));
|
|
break;
|
|
case td_api::updateNewCustomQuery::ID:
|
|
add_new_custom_query(move_object_as<td_api::updateNewCustomQuery>(result));
|
|
break;
|
|
default:
|
|
// we are not interested in this updates
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Client::on_result(td::uint64 id, object_ptr<td_api::Object> result) {
|
|
LOG(DEBUG) << "Receive from Td: " << id << " " << to_string(result);
|
|
if (id == 0) {
|
|
return on_update(std::move(result));
|
|
}
|
|
|
|
auto *handler_ptr = handlers_.get(id);
|
|
CHECK(handler_ptr != nullptr);
|
|
auto handler = std::move(*handler_ptr);
|
|
handler->on_result(std::move(result));
|
|
handlers_.erase(id);
|
|
}
|
|
|
|
void Client::on_closed() {
|
|
LOG(WARNING) << "Closed";
|
|
CHECK(logging_out_ || closing_);
|
|
CHECK(!td_client_.empty());
|
|
td_client_.reset();
|
|
|
|
int http_status_code = logging_out_ ? LOGGING_OUT_ERROR_CODE : CLOSING_ERROR_CODE;
|
|
Slice description = logging_out_ ? LOGGING_OUT_ERROR_DESCRIPTION : CLOSING_ERROR_DESCRIPTION;
|
|
if (webhook_set_query_) {
|
|
fail_query(http_status_code, description, std::move(webhook_set_query_));
|
|
}
|
|
if (!webhook_url_.empty()) {
|
|
webhook_id_.reset();
|
|
}
|
|
if (long_poll_query_) {
|
|
long_poll_wakeup(true);
|
|
CHECK(!long_poll_query_);
|
|
}
|
|
|
|
while (!cmd_queue_.empty()) {
|
|
auto query = std::move(cmd_queue_.front());
|
|
cmd_queue_.pop();
|
|
fail_query(http_status_code, description, std::move(query));
|
|
}
|
|
|
|
while (!yet_unsent_messages_.empty()) {
|
|
auto it = yet_unsent_messages_.begin();
|
|
auto chat_id = it->first.chat_id;
|
|
auto message_id = it->first.message_id;
|
|
if (!USE_MESSAGE_DATABASE) {
|
|
LOG(ERROR) << "Doesn't receive updateMessageSendFailed for message " << message_id << " in chat " << chat_id;
|
|
}
|
|
on_message_send_failed(chat_id, message_id, 0, Status::Error(http_status_code, description));
|
|
}
|
|
|
|
while (!pending_bot_resolve_queries_.empty()) {
|
|
auto it = pending_bot_resolve_queries_.begin();
|
|
fail_query(http_status_code, description, std::move(it->second.query));
|
|
pending_bot_resolve_queries_.erase(it);
|
|
}
|
|
|
|
while (!file_download_listeners_.empty()) {
|
|
auto it = file_download_listeners_.begin();
|
|
auto file_id = it->first;
|
|
LOG(ERROR) << "Doesn't receive updateFile for file " << file_id;
|
|
on_file_download(file_id, Status::Error(http_status_code, description));
|
|
}
|
|
|
|
if (logging_out_) {
|
|
parameters_->shared_data_->webhook_db_->erase(bot_token_with_dc_);
|
|
|
|
class RmWorker : public td::Actor {
|
|
public:
|
|
RmWorker(td::string dir, td::ActorId<Client> parent) : dir_(std::move(dir)), parent_(std::move(parent)) {
|
|
}
|
|
|
|
private:
|
|
td::string dir_;
|
|
td::ActorId<Client> parent_;
|
|
|
|
void start_up() override {
|
|
CHECK(!dir_.empty());
|
|
CHECK(dir_[0] == '.');
|
|
td::rmrf(dir_).ignore();
|
|
stop();
|
|
}
|
|
void tear_down() override {
|
|
send_closure(parent_, &Client::finish_closing);
|
|
}
|
|
};
|
|
// NB: the same scheduler as for database in Td
|
|
auto current_scheduler_id = td::Scheduler::instance()->sched_id();
|
|
auto scheduler_count = td::Scheduler::instance()->sched_count();
|
|
auto scheduler_id = td::min(current_scheduler_id + 1, scheduler_count - 1);
|
|
td::create_actor_on_scheduler<RmWorker>("RmWorker", scheduler_id, dir_, actor_id(this)).release();
|
|
return;
|
|
}
|
|
|
|
finish_closing();
|
|
}
|
|
|
|
void Client::finish_closing() {
|
|
if (clear_tqueue_ && logging_out_) {
|
|
clear_tqueue();
|
|
}
|
|
|
|
set_timeout_in(need_close_ ? 0 : 600);
|
|
}
|
|
|
|
void Client::timeout_expired() {
|
|
LOG(WARNING) << "Stop client";
|
|
stop();
|
|
}
|
|
|
|
void Client::clear_tqueue() {
|
|
CHECK(webhook_id_.empty());
|
|
auto &tqueue = parameters_->shared_data_->tqueue_;
|
|
auto size = tqueue->get_size(tqueue_id_);
|
|
if (size > 0) {
|
|
LOG(INFO) << "Removing " << size << " tqueue events";
|
|
td::MutableSpan<td::TQueue::Event> span;
|
|
auto r_size = tqueue->get(tqueue_id_, tqueue->get_tail(tqueue_id_), true, 0, span);
|
|
CHECK(r_size.is_ok());
|
|
CHECK(r_size.ok() == 0);
|
|
CHECK(tqueue->get_size(tqueue_id_) == 0);
|
|
}
|
|
}
|
|
|
|
bool Client::to_bool(td::MutableSlice value) {
|
|
td::to_lower_inplace(value);
|
|
value = td::trim(value);
|
|
return value == "true" || value == "yes" || value == "1";
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::keyboardButton>> Client::get_keyboard_button(JsonValue &button) {
|
|
if (button.type() == JsonValue::Type::Object) {
|
|
auto &object = button.get_object();
|
|
|
|
TRY_RESULT(text, get_json_object_string_field(object, "text", false));
|
|
|
|
TRY_RESULT(request_phone_number, get_json_object_bool_field(object, "request_phone_number"));
|
|
TRY_RESULT(request_contact, get_json_object_bool_field(object, "request_contact"));
|
|
if (request_phone_number || request_contact) {
|
|
return make_object<td_api::keyboardButton>(text, make_object<td_api::keyboardButtonTypeRequestPhoneNumber>());
|
|
}
|
|
|
|
TRY_RESULT(request_location, get_json_object_bool_field(object, "request_location"));
|
|
if (request_location) {
|
|
return make_object<td_api::keyboardButton>(text, make_object<td_api::keyboardButtonTypeRequestLocation>());
|
|
}
|
|
|
|
if (has_json_object_field(object, "request_poll")) {
|
|
bool force_regular = false;
|
|
bool force_quiz = false;
|
|
TRY_RESULT(request_poll, get_json_object_field(object, "request_poll", JsonValue::Type::Object, false));
|
|
auto &request_poll_object = request_poll.get_object();
|
|
if (has_json_object_field(request_poll_object, "type")) {
|
|
TRY_RESULT(type, get_json_object_string_field(request_poll_object, "type"));
|
|
if (type == "quiz") {
|
|
force_quiz = true;
|
|
} else if (type == "regular") {
|
|
force_regular = true;
|
|
}
|
|
}
|
|
return make_object<td_api::keyboardButton>(
|
|
text, make_object<td_api::keyboardButtonTypeRequestPoll>(force_regular, force_quiz));
|
|
}
|
|
|
|
return make_object<td_api::keyboardButton>(text, nullptr);
|
|
}
|
|
if (button.type() == JsonValue::Type::String) {
|
|
return make_object<td_api::keyboardButton>(button.get_string().str(), nullptr);
|
|
}
|
|
|
|
return Status::Error(400, "KeyboardButton must be a String or an Object");
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::inlineKeyboardButton>> Client::get_inline_keyboard_button(JsonValue &button) {
|
|
if (button.type() != JsonValue::Type::Object) {
|
|
return Status::Error(400, "InlineKeyboardButton must be an Object");
|
|
}
|
|
|
|
auto &object = button.get_object();
|
|
|
|
TRY_RESULT(text, get_json_object_string_field(object, "text", false));
|
|
{
|
|
TRY_RESULT(url, get_json_object_string_field(object, "url"));
|
|
if (!url.empty()) {
|
|
return make_object<td_api::inlineKeyboardButton>(text, make_object<td_api::inlineKeyboardButtonTypeUrl>(url));
|
|
}
|
|
}
|
|
|
|
{
|
|
TRY_RESULT(callback_data, get_json_object_string_field(object, "callback_data"));
|
|
if (!callback_data.empty()) {
|
|
return make_object<td_api::inlineKeyboardButton>(
|
|
text, make_object<td_api::inlineKeyboardButtonTypeCallback>(callback_data));
|
|
}
|
|
}
|
|
|
|
if (has_json_object_field(object, "callback_game")) {
|
|
return make_object<td_api::inlineKeyboardButton>(text, make_object<td_api::inlineKeyboardButtonTypeCallbackGame>());
|
|
}
|
|
|
|
if (has_json_object_field(object, "pay")) {
|
|
return make_object<td_api::inlineKeyboardButton>(text, make_object<td_api::inlineKeyboardButtonTypeBuy>());
|
|
}
|
|
|
|
if (has_json_object_field(object, "switch_inline_query")) {
|
|
TRY_RESULT(switch_inline_query, get_json_object_string_field(object, "switch_inline_query", false));
|
|
return make_object<td_api::inlineKeyboardButton>(
|
|
text, make_object<td_api::inlineKeyboardButtonTypeSwitchInline>(switch_inline_query, false));
|
|
}
|
|
|
|
if (has_json_object_field(object, "switch_inline_query_current_chat")) {
|
|
TRY_RESULT(switch_inline_query, get_json_object_string_field(object, "switch_inline_query_current_chat", false));
|
|
return make_object<td_api::inlineKeyboardButton>(
|
|
text, make_object<td_api::inlineKeyboardButtonTypeSwitchInline>(switch_inline_query, true));
|
|
}
|
|
|
|
if (has_json_object_field(object, "login_url")) {
|
|
TRY_RESULT(login_url, get_json_object_field(object, "login_url", JsonValue::Type::Object, false));
|
|
CHECK(login_url.type() == JsonValue::Type::Object);
|
|
auto &login_url_object = login_url.get_object();
|
|
TRY_RESULT(url, get_json_object_string_field(login_url_object, "url", false));
|
|
TRY_RESULT(bot_username, get_json_object_string_field(login_url_object, "bot_username"));
|
|
TRY_RESULT(request_write_access, get_json_object_bool_field(login_url_object, "request_write_access"));
|
|
TRY_RESULT(forward_text, get_json_object_string_field(login_url_object, "forward_text"));
|
|
|
|
int32 bot_user_id = 0;
|
|
if (bot_username.empty()) {
|
|
bot_user_id = my_id_;
|
|
} else {
|
|
if (bot_username[0] == '@') {
|
|
bot_username = bot_username.substr(1);
|
|
}
|
|
if (bot_username.empty()) {
|
|
return Status::Error(400, "LoginUrl bot username is invalid");
|
|
}
|
|
for (auto c : bot_username) {
|
|
if (c != '_' && !td::is_alnum(c)) {
|
|
return Status::Error(400, "LoginUrl bot username is invalid");
|
|
}
|
|
}
|
|
if (cur_temp_bot_user_id_ >= 100000) {
|
|
return Status::Error(400, "Too much different LoginUrl bot usernames");
|
|
}
|
|
auto &user_id = bot_user_ids_[bot_username];
|
|
if (user_id == 0) {
|
|
user_id = cur_temp_bot_user_id_++;
|
|
user_id *= 1000;
|
|
}
|
|
if (user_id % 1000 == 0) {
|
|
unresolved_bot_usernames_.insert(bot_username);
|
|
}
|
|
bot_user_id = user_id;
|
|
}
|
|
if (!request_write_access) {
|
|
bot_user_id *= -1;
|
|
}
|
|
return make_object<td_api::inlineKeyboardButton>(
|
|
text, make_object<td_api::inlineKeyboardButtonTypeLoginUrl>(url, bot_user_id, forward_text));
|
|
}
|
|
|
|
return Status::Error(400, "Text buttons are unallowed in the inline keyboard");
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::ReplyMarkup>> Client::get_reply_markup(const Query *query) {
|
|
auto reply_markup = query->arg("reply_markup");
|
|
if (reply_markup.empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
LOG(INFO) << "Parsing JSON object: " << reply_markup;
|
|
auto r_value = json_decode(reply_markup);
|
|
if (r_value.is_error()) {
|
|
LOG(INFO) << "Can't parse JSON object: " << r_value.error();
|
|
return Status::Error(400, "Can't parse reply keyboard markup JSON object");
|
|
}
|
|
|
|
return get_reply_markup(r_value.move_as_ok());
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::ReplyMarkup>> Client::get_reply_markup(JsonValue &&value) {
|
|
td::vector<td::vector<object_ptr<td_api::keyboardButton>>> rows;
|
|
td::vector<td::vector<object_ptr<td_api::inlineKeyboardButton>>> inline_rows;
|
|
bool resize = false;
|
|
bool one_time = false;
|
|
bool remove = false;
|
|
bool is_personal = false;
|
|
bool force_reply = false;
|
|
|
|
if (value.type() != JsonValue::Type::Object) {
|
|
return Status::Error(400, "Object expected as reply markup");
|
|
}
|
|
for (auto &field_value : value.get_object()) {
|
|
if (field_value.first == "keyboard") {
|
|
auto keyboard = std::move(field_value.second);
|
|
if (keyboard.type() != JsonValue::Type::Array) {
|
|
return Status::Error(400, "Field \"keyboard\" of the ReplyKeyboardMarkup must be an Array");
|
|
}
|
|
for (auto &row : keyboard.get_array()) {
|
|
td::vector<object_ptr<td_api::keyboardButton>> new_row;
|
|
if (row.type() != JsonValue::Type::Array) {
|
|
return Status::Error(400, "Field \"keyboard\" of the ReplyKeyboardMarkup must be an Array of Arrays");
|
|
}
|
|
for (auto &button : row.get_array()) {
|
|
auto r_button = get_keyboard_button(button);
|
|
if (r_button.is_error()) {
|
|
return Status::Error(400, PSLICE() << "Can't parse keyboard button: " << r_button.error().message());
|
|
}
|
|
new_row.push_back(r_button.move_as_ok());
|
|
}
|
|
|
|
rows.push_back(std::move(new_row));
|
|
}
|
|
} else if (field_value.first == "inline_keyboard") {
|
|
auto inline_keyboard = std::move(field_value.second);
|
|
if (inline_keyboard.type() != JsonValue::Type::Array) {
|
|
return Status::Error(400, "Field \"inline_keyboard\" of the InlineKeyboardMarkup must be an Array");
|
|
}
|
|
for (auto &inline_row : inline_keyboard.get_array()) {
|
|
td::vector<object_ptr<td_api::inlineKeyboardButton>> new_inline_row;
|
|
if (inline_row.type() != JsonValue::Type::Array) {
|
|
return Status::Error(400, "Field \"inline_keyboard\" of the InlineKeyboardMarkup must be an Array of Arrays");
|
|
}
|
|
for (auto &button : inline_row.get_array()) {
|
|
auto r_button = get_inline_keyboard_button(button);
|
|
if (r_button.is_error()) {
|
|
return Status::Error(400, PSLICE() << "Can't parse inline keyboard button: " << r_button.error().message());
|
|
}
|
|
new_inline_row.push_back(r_button.move_as_ok());
|
|
}
|
|
|
|
inline_rows.push_back(std::move(new_inline_row));
|
|
}
|
|
} else if (field_value.first == "resize_keyboard") {
|
|
if (field_value.second.type() != JsonValue::Type::Boolean) {
|
|
return Status::Error(400, "Field \"resize_keyboard\" of the ReplyKeyboardMarkup must be of the type Boolean");
|
|
}
|
|
resize = field_value.second.get_boolean();
|
|
} else if (field_value.first == "one_time_keyboard") {
|
|
if (field_value.second.type() != JsonValue::Type::Boolean) {
|
|
return Status::Error(400, "Field \"one_time_keyboard\" of the ReplyKeyboardMarkup must be of the type Boolean");
|
|
}
|
|
one_time = field_value.second.get_boolean();
|
|
} else if (field_value.first == "hide_keyboard" || field_value.first == "remove_keyboard") {
|
|
if (field_value.second.type() != JsonValue::Type::Boolean) {
|
|
return Status::Error(400, "Field \"remove_keyboard\" of the ReplyKeyboardRemove must be of the type Boolean");
|
|
}
|
|
remove = field_value.second.get_boolean();
|
|
} else if (field_value.first == "personal_keyboard" || field_value.first == "selective") {
|
|
if (field_value.second.type() != JsonValue::Type::Boolean) {
|
|
return Status::Error(400, "Field \"selective\" of the reply markup must be of the type Boolean");
|
|
}
|
|
is_personal = field_value.second.get_boolean();
|
|
} else if (field_value.first == "force_reply_keyboard" || field_value.first == "force_reply") {
|
|
if (field_value.second.type() != JsonValue::Type::Boolean) {
|
|
return Status::Error(400, "Field \"force_reply\" of the reply markup must be of the type Boolean");
|
|
}
|
|
force_reply = field_value.second.get_boolean();
|
|
}
|
|
}
|
|
|
|
object_ptr<td_api::ReplyMarkup> result;
|
|
if (!rows.empty()) {
|
|
result = make_object<td_api::replyMarkupShowKeyboard>(std::move(rows), resize, one_time, is_personal);
|
|
} else if (!inline_rows.empty()) {
|
|
result = make_object<td_api::replyMarkupInlineKeyboard>(std::move(inline_rows));
|
|
} else if (remove) {
|
|
result = make_object<td_api::replyMarkupRemoveKeyboard>(is_personal);
|
|
} else if (force_reply) {
|
|
result = make_object<td_api::replyMarkupForceReply>(is_personal);
|
|
}
|
|
if (result == nullptr || result->get_id() != td_api::replyMarkupInlineKeyboard::ID) {
|
|
unresolved_bot_usernames_.clear();
|
|
}
|
|
|
|
return std::move(result);
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::labeledPricePart>> Client::get_labeled_price_part(JsonValue &value) {
|
|
if (value.type() != JsonValue::Type::Object) {
|
|
return Status::Error(400, "LabeledPrice must be an Object");
|
|
}
|
|
|
|
auto &object = value.get_object();
|
|
|
|
TRY_RESULT(label, get_json_object_string_field(object, "label", false));
|
|
if (label.empty()) {
|
|
return Status::Error(400, "LabeledPrice label must be non-empty");
|
|
}
|
|
|
|
TRY_RESULT(amount, get_json_object_field(object, "amount", JsonValue::Type::Null, false));
|
|
Slice number;
|
|
if (amount.type() == JsonValue::Type::Number) {
|
|
number = amount.get_number();
|
|
} else if (amount.type() == JsonValue::Type::String) {
|
|
number = amount.get_string();
|
|
} else {
|
|
return Status::Error(400, "Field \"amount\" must be of type Number or String");
|
|
}
|
|
auto parsed_amount = td::to_integer_safe<int64>(number);
|
|
if (parsed_amount.is_error()) {
|
|
return Status::Error(400, "Can't parse \"amount\" as Number");
|
|
}
|
|
return make_object<td_api::labeledPricePart>(label, parsed_amount.ok());
|
|
}
|
|
|
|
td::Result<td::vector<td_api::object_ptr<td_api::labeledPricePart>>> Client::get_labeled_price_parts(JsonValue &value) {
|
|
if (value.type() != JsonValue::Type::Array) {
|
|
return Status::Error(400, "Expected an Array of labeled prices");
|
|
}
|
|
|
|
td::vector<object_ptr<td_api::labeledPricePart>> prices;
|
|
for (auto &price : value.get_array()) {
|
|
auto r_labeled_price = get_labeled_price_part(price);
|
|
if (r_labeled_price.is_error()) {
|
|
return Status::Error(400, PSLICE() << "Can't parse labeled price: " << r_labeled_price.error().message());
|
|
}
|
|
prices.push_back(r_labeled_price.move_as_ok());
|
|
}
|
|
if (prices.empty()) {
|
|
return Status::Error(400, "There must be at least one price");
|
|
}
|
|
|
|
return std::move(prices);
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::shippingOption>> Client::get_shipping_option(JsonValue &option) {
|
|
if (option.type() != JsonValue::Type::Object) {
|
|
return Status::Error(400, "ShippingOption must be an Object");
|
|
}
|
|
|
|
auto &object = option.get_object();
|
|
|
|
TRY_RESULT(id, get_json_object_string_field(object, "id", false));
|
|
if (id.empty()) {
|
|
return Status::Error(400, "ShippingOption identifier must be non-empty");
|
|
}
|
|
|
|
TRY_RESULT(title, get_json_object_string_field(object, "title", false));
|
|
if (title.empty()) {
|
|
return Status::Error(400, "ShippingOption title must be non-empty");
|
|
}
|
|
|
|
TRY_RESULT(prices_json, get_json_object_field(object, "prices", JsonValue::Type::Array, false));
|
|
|
|
auto r_prices = get_labeled_price_parts(prices_json);
|
|
if (r_prices.is_error()) {
|
|
return Status::Error(400, PSLICE() << "Can't parse shipping option prices: " << r_prices.error().message());
|
|
}
|
|
|
|
return make_object<td_api::shippingOption>(id, title, r_prices.move_as_ok());
|
|
}
|
|
|
|
td::Result<td::vector<td_api::object_ptr<td_api::shippingOption>>> Client::get_shipping_options(const Query *query) {
|
|
TRY_RESULT(shipping_options, get_required_string_arg(query, "shipping_options"));
|
|
|
|
LOG(INFO) << "Parsing JSON object: " << shipping_options;
|
|
auto r_value = json_decode(shipping_options);
|
|
if (r_value.is_error()) {
|
|
LOG(INFO) << "Can't parse JSON object: " << r_value.error();
|
|
return Status::Error(400, "Can't parse shipping options JSON object");
|
|
}
|
|
|
|
return get_shipping_options(r_value.move_as_ok());
|
|
}
|
|
|
|
td::Result<td::vector<td_api::object_ptr<td_api::shippingOption>>> Client::get_shipping_options(JsonValue &&value) {
|
|
if (value.type() != JsonValue::Type::Array) {
|
|
return Status::Error(400, "Expected an Array of shipping options");
|
|
}
|
|
|
|
td::vector<object_ptr<td_api::shippingOption>> options;
|
|
for (auto &option : value.get_array()) {
|
|
auto r_shipping_option = get_shipping_option(option);
|
|
if (r_shipping_option.is_error()) {
|
|
return Status::Error(400, PSLICE() << "Can't parse shipping option: " << r_shipping_option.error().message());
|
|
}
|
|
options.push_back(r_shipping_option.move_as_ok());
|
|
}
|
|
if (options.empty()) {
|
|
return Status::Error(400, "There must be at least one shipping option");
|
|
}
|
|
|
|
return std::move(options);
|
|
}
|
|
|
|
td_api::object_ptr<td_api::ChatAction> Client::get_chat_action(const Query *query) {
|
|
auto action = query->arg("action");
|
|
td::to_lower_inplace(action);
|
|
if (action == "cancel") {
|
|
return make_object<td_api::chatActionCancel>();
|
|
}
|
|
if (action == "typing") {
|
|
return make_object<td_api::chatActionTyping>();
|
|
}
|
|
if (action == "record_video") {
|
|
return make_object<td_api::chatActionRecordingVideo>();
|
|
}
|
|
if (action == "upload_video") {
|
|
return make_object<td_api::chatActionUploadingVideo>();
|
|
}
|
|
if (action == "record_audio" || action == "record_voice") {
|
|
return make_object<td_api::chatActionRecordingVoiceNote>();
|
|
}
|
|
if (action == "upload_audio" || action == "upload_voice") {
|
|
return make_object<td_api::chatActionUploadingVoiceNote>();
|
|
}
|
|
if (action == "upload_photo") {
|
|
return make_object<td_api::chatActionUploadingPhoto>();
|
|
}
|
|
if (action == "upload_document") {
|
|
return make_object<td_api::chatActionUploadingDocument>();
|
|
}
|
|
if (action == "pick_up_location" || action == "find_location") {
|
|
return make_object<td_api::chatActionChoosingLocation>();
|
|
}
|
|
if (action == "record_video_note") {
|
|
return make_object<td_api::chatActionRecordingVideoNote>();
|
|
}
|
|
if (action == "upload_video_note") {
|
|
return make_object<td_api::chatActionUploadingVideoNote>();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
td_api::object_ptr<td_api::InputFile> Client::get_input_file(const Query *query, Slice field_name,
|
|
bool force_file) const {
|
|
return get_input_file(query, field_name, query->arg(field_name), force_file);
|
|
}
|
|
|
|
td::string Client::get_local_file_path(Slice file_uri) {
|
|
if (td::begins_with(file_uri, "/")) {
|
|
file_uri.remove_prefix(td::begins_with(file_uri, "/localhost") ? 10 : 1);
|
|
}
|
|
#if TD_PORT_WINDOWS
|
|
if (td::begins_with(file_uri, "/")) {
|
|
file_uri.remove_prefix(1);
|
|
}
|
|
#endif
|
|
td::string result(file_uri.size(), '\0');
|
|
auto result_len = url_decode(file_uri, result, false);
|
|
result.resize(result_len);
|
|
return result;
|
|
}
|
|
|
|
td_api::object_ptr<td_api::InputFile> Client::get_input_file(const Query *query, Slice field_name, Slice file_id,
|
|
bool force_file) const {
|
|
if (!file_id.empty()) {
|
|
if (parameters_->local_mode_) {
|
|
Slice file_protocol{"file:/"};
|
|
if (td::begins_with(file_id, file_protocol)) {
|
|
return make_object<td_api::inputFileLocal>(get_local_file_path(file_id.substr(file_protocol.size())));
|
|
}
|
|
}
|
|
Slice attach_protocol{"attach://"};
|
|
if (td::begins_with(file_id, attach_protocol)) {
|
|
field_name = file_id.substr(attach_protocol.size());
|
|
} else {
|
|
if (!force_file) {
|
|
return make_object<td_api::inputFileRemote>(file_id.str());
|
|
}
|
|
}
|
|
}
|
|
auto file = query->file(field_name);
|
|
if (file != nullptr) {
|
|
return make_object<td_api::inputFileLocal>(file->temp_file_name);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
td_api::object_ptr<td_api::inputThumbnail> Client::get_input_thumbnail(const Query *query, Slice field_name) const {
|
|
auto input_file = get_input_file(query, field_name, true);
|
|
if (input_file == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return make_object<td_api::inputThumbnail>(std::move(input_file), 0, 0);
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::InputMessageContent>> Client::get_input_message_content(
|
|
JsonValue &input_message_content, bool is_input_message_content_required) {
|
|
CHECK(input_message_content.type() == JsonValue::Type::Object);
|
|
auto &object = input_message_content.get_object();
|
|
|
|
TRY_RESULT(message_text, get_json_object_string_field(object, "message_text"));
|
|
|
|
if (!message_text.empty()) {
|
|
TRY_RESULT(disable_web_page_preview, get_json_object_bool_field(object, "disable_web_page_preview"));
|
|
TRY_RESULT(parse_mode, get_json_object_string_field(object, "parse_mode"));
|
|
auto entities = get_json_object_field_force(object, "entities");
|
|
TRY_RESULT(input_message_text, get_input_message_text(std::move(message_text), disable_web_page_preview,
|
|
std::move(parse_mode), std::move(entities)));
|
|
return std::move(input_message_text);
|
|
}
|
|
|
|
if (has_json_object_field(object, "latitude") && has_json_object_field(object, "longitude")) {
|
|
TRY_RESULT(latitude, get_json_object_double_field(object, "latitude", false));
|
|
TRY_RESULT(longitude, get_json_object_double_field(object, "longitude", false));
|
|
TRY_RESULT(horizontal_accuracy, get_json_object_double_field(object, "horizontal_accuracy"));
|
|
TRY_RESULT(live_period, get_json_object_int_field(object, "live_period"));
|
|
TRY_RESULT(heading, get_json_object_int_field(object, "heading"));
|
|
TRY_RESULT(proximity_alert_radius, get_json_object_int_field(object, "proximity_alert_radius"));
|
|
auto location = make_object<td_api::location>(latitude, longitude, horizontal_accuracy);
|
|
|
|
if (has_json_object_field(object, "title") && has_json_object_field(object, "address")) {
|
|
TRY_RESULT(title, get_json_object_string_field(object, "title", false));
|
|
TRY_RESULT(address, get_json_object_string_field(object, "address", false));
|
|
td::string provider;
|
|
td::string venue_id;
|
|
td::string venue_type;
|
|
|
|
TRY_RESULT(google_place_id, get_json_object_string_field(object, "google_place_id"));
|
|
TRY_RESULT(google_place_type, get_json_object_string_field(object, "google_place_type"));
|
|
if (!google_place_id.empty() || !google_place_type.empty()) {
|
|
provider = "gplaces";
|
|
venue_id = std::move(google_place_id);
|
|
venue_type = std::move(google_place_type);
|
|
}
|
|
TRY_RESULT(foursquare_id, get_json_object_string_field(object, "foursquare_id"));
|
|
TRY_RESULT(foursquare_type, get_json_object_string_field(object, "foursquare_type"));
|
|
if (!foursquare_id.empty() || !foursquare_type.empty()) {
|
|
provider = "foursquare";
|
|
venue_id = std::move(foursquare_id);
|
|
venue_type = std::move(foursquare_type);
|
|
}
|
|
|
|
return make_object<td_api::inputMessageVenue>(
|
|
make_object<td_api::venue>(std::move(location), title, address, provider, venue_id, venue_type));
|
|
}
|
|
|
|
return make_object<td_api::inputMessageLocation>(std::move(location), live_period, heading, proximity_alert_radius);
|
|
}
|
|
|
|
if (has_json_object_field(object, "phone_number")) {
|
|
TRY_RESULT(phone_number, get_json_object_string_field(object, "phone_number", false));
|
|
TRY_RESULT(first_name, get_json_object_string_field(object, "first_name", false));
|
|
TRY_RESULT(last_name, get_json_object_string_field(object, "last_name"));
|
|
TRY_RESULT(vcard, get_json_object_string_field(object, "vcard"));
|
|
|
|
return make_object<td_api::inputMessageContact>(
|
|
make_object<td_api::contact>(phone_number, first_name, last_name, vcard, 0));
|
|
}
|
|
|
|
if (is_input_message_content_required) {
|
|
return Status::Error(400, "Input message content is not specified");
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
td_api::object_ptr<td_api::messageSendOptions> Client::get_message_send_options(bool disable_notification) {
|
|
return make_object<td_api::messageSendOptions>(disable_notification, false, nullptr);
|
|
}
|
|
|
|
td::Result<td::vector<td_api::object_ptr<td_api::InputInlineQueryResult>>> Client::get_inline_query_results(
|
|
const Query *query) {
|
|
auto results_encoded = query->arg("results");
|
|
if (results_encoded.empty()) {
|
|
return td::vector<object_ptr<td_api::InputInlineQueryResult>>();
|
|
}
|
|
|
|
LOG(INFO) << "Parsing JSON object: " << results_encoded;
|
|
auto r_values = json_decode(results_encoded);
|
|
if (r_values.is_error()) {
|
|
return Status::Error(400,
|
|
PSLICE() << "Can't parse JSON encoded inline query results: " << r_values.error().message());
|
|
}
|
|
|
|
return get_inline_query_results(r_values.move_as_ok());
|
|
}
|
|
|
|
td::Result<td::vector<td_api::object_ptr<td_api::InputInlineQueryResult>>> Client::get_inline_query_results(
|
|
td::JsonValue &&values) {
|
|
if (values.type() == JsonValue::Type::Null) {
|
|
return td::vector<object_ptr<td_api::InputInlineQueryResult>>();
|
|
}
|
|
if (values.type() != JsonValue::Type::Array) {
|
|
return Status::Error(400, "Expected an Array of inline query results");
|
|
}
|
|
|
|
td::vector<object_ptr<td_api::InputInlineQueryResult>> inline_query_results;
|
|
for (auto &value : values.get_array()) {
|
|
auto r_inline_query_result = get_inline_query_result(std::move(value));
|
|
if (r_inline_query_result.is_error()) {
|
|
return Status::Error(400,
|
|
PSLICE() << "Can't parse inline query result: " << r_inline_query_result.error().message());
|
|
}
|
|
inline_query_results.push_back(r_inline_query_result.move_as_ok());
|
|
}
|
|
|
|
return std::move(inline_query_results);
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::InputInlineQueryResult>> Client::get_inline_query_result(td::JsonValue &&value) {
|
|
if (value.type() != JsonValue::Type::Object) {
|
|
return Status::Error(400, "Inline query result must be an object");
|
|
}
|
|
|
|
auto &object = value.get_object();
|
|
|
|
TRY_RESULT(type, get_json_object_string_field(object, "type", false));
|
|
td::to_lower_inplace(type);
|
|
|
|
TRY_RESULT(id, get_json_object_string_field(object, "id", false));
|
|
|
|
bool is_input_message_content_required = (type == "article");
|
|
object_ptr<td_api::InputMessageContent> input_message_content;
|
|
|
|
TRY_RESULT(input_message_content_obj,
|
|
get_json_object_field(object, "input_message_content", JsonValue::Type::Object));
|
|
if (input_message_content_obj.type() == JsonValue::Type::Null) {
|
|
TRY_RESULT(message_text, get_json_object_string_field(object, "message_text", !is_input_message_content_required));
|
|
TRY_RESULT(disable_web_page_preview, get_json_object_bool_field(object, "disable_web_page_preview"));
|
|
TRY_RESULT(parse_mode, get_json_object_string_field(object, "parse_mode"));
|
|
auto entities = get_json_object_field_force(object, "entities");
|
|
|
|
if (is_input_message_content_required || !message_text.empty()) {
|
|
TRY_RESULT(input_message_text, get_input_message_text(std::move(message_text), disable_web_page_preview,
|
|
std::move(parse_mode), std::move(entities)));
|
|
input_message_content = std::move(input_message_text);
|
|
}
|
|
} else {
|
|
TRY_RESULT(input_message_content_result,
|
|
get_input_message_content(input_message_content_obj, is_input_message_content_required));
|
|
input_message_content = std::move(input_message_content_result);
|
|
}
|
|
TRY_RESULT(input_caption, get_json_object_string_field(object, "caption"));
|
|
TRY_RESULT(parse_mode, get_json_object_string_field(object, "parse_mode"));
|
|
auto entities = get_json_object_field_force(object, "caption_entities");
|
|
TRY_RESULT(caption, get_formatted_text(std::move(input_caption), std::move(parse_mode), std::move(entities)));
|
|
|
|
TRY_RESULT(reply_markup_object, get_json_object_field(object, "reply_markup", JsonValue::Type::Object));
|
|
object_ptr<td_api::ReplyMarkup> reply_markup;
|
|
if (reply_markup_object.type() != JsonValue::Type::Null) {
|
|
TRY_RESULT_ASSIGN(reply_markup, get_reply_markup(std::move(reply_markup_object)));
|
|
}
|
|
|
|
object_ptr<td_api::InputInlineQueryResult> result;
|
|
if (type == "article") {
|
|
TRY_RESULT(url, get_json_object_string_field(object, "url"));
|
|
TRY_RESULT(hide_url, get_json_object_bool_field(object, "hide_url"));
|
|
TRY_RESULT(title, get_json_object_string_field(object, "title", false));
|
|
TRY_RESULT(description, get_json_object_string_field(object, "description"));
|
|
TRY_RESULT(thumbnail_url, get_json_object_string_field(object, "thumb_url"));
|
|
TRY_RESULT(thumbnail_width, get_json_object_int_field(object, "thumb_width"));
|
|
TRY_RESULT(thumbnail_height, get_json_object_int_field(object, "thumb_height"));
|
|
|
|
CHECK(input_message_content != nullptr);
|
|
return make_object<td_api::inputInlineQueryResultArticle>(
|
|
id, url, hide_url, title, description, thumbnail_url, thumbnail_width, thumbnail_height,
|
|
std::move(reply_markup), std::move(input_message_content));
|
|
}
|
|
if (type == "audio") {
|
|
TRY_RESULT(audio_url, get_json_object_string_field(object, "audio_url"));
|
|
TRY_RESULT(audio_duration, get_json_object_int_field(object, "audio_duration"));
|
|
TRY_RESULT(title, get_json_object_string_field(object, "title", audio_url.empty()));
|
|
TRY_RESULT(performer, get_json_object_string_field(object, "performer"));
|
|
if (audio_url.empty()) {
|
|
TRY_RESULT_ASSIGN(audio_url, get_json_object_string_field(object, "audio_file_id", false));
|
|
}
|
|
|
|
if (input_message_content == nullptr) {
|
|
input_message_content = make_object<td_api::inputMessageAudio>(nullptr, nullptr, audio_duration, title, performer,
|
|
std::move(caption));
|
|
}
|
|
return make_object<td_api::inputInlineQueryResultAudio>(id, title, performer, audio_url, audio_duration,
|
|
std::move(reply_markup), std::move(input_message_content));
|
|
}
|
|
if (type == "contact") {
|
|
TRY_RESULT(phone_number, get_json_object_string_field(object, "phone_number", false));
|
|
TRY_RESULT(first_name, get_json_object_string_field(object, "first_name", false));
|
|
TRY_RESULT(last_name, get_json_object_string_field(object, "last_name"));
|
|
TRY_RESULT(vcard, get_json_object_string_field(object, "vcard"));
|
|
TRY_RESULT(thumbnail_url, get_json_object_string_field(object, "thumb_url"));
|
|
TRY_RESULT(thumbnail_width, get_json_object_int_field(object, "thumb_width"));
|
|
TRY_RESULT(thumbnail_height, get_json_object_int_field(object, "thumb_height"));
|
|
|
|
if (input_message_content == nullptr) {
|
|
input_message_content = make_object<td_api::inputMessageContact>(
|
|
make_object<td_api::contact>(phone_number, first_name, last_name, vcard, 0));
|
|
}
|
|
return make_object<td_api::inputInlineQueryResultContact>(
|
|
id, make_object<td_api::contact>(phone_number, first_name, last_name, vcard, 0), thumbnail_url, thumbnail_width,
|
|
thumbnail_height, std::move(reply_markup), std::move(input_message_content));
|
|
}
|
|
if (type == "document") {
|
|
TRY_RESULT(title, get_json_object_string_field(object, "title", false));
|
|
TRY_RESULT(description, get_json_object_string_field(object, "description"));
|
|
TRY_RESULT(thumbnail_url, get_json_object_string_field(object, "thumb_url"));
|
|
TRY_RESULT(document_url, get_json_object_string_field(object, "document_url"));
|
|
TRY_RESULT(mime_type, get_json_object_string_field(object, "mime_type", document_url.empty()));
|
|
TRY_RESULT(thumbnail_width, get_json_object_int_field(object, "thumb_width"));
|
|
TRY_RESULT(thumbnail_height, get_json_object_int_field(object, "thumb_height"));
|
|
if (document_url.empty()) {
|
|
TRY_RESULT_ASSIGN(document_url, get_json_object_string_field(object, "document_file_id", false));
|
|
}
|
|
|
|
if (input_message_content == nullptr) {
|
|
input_message_content = make_object<td_api::inputMessageDocument>(nullptr, nullptr, false, std::move(caption));
|
|
}
|
|
return make_object<td_api::inputInlineQueryResultDocument>(
|
|
id, title, description, document_url, mime_type, thumbnail_url, thumbnail_width, thumbnail_height,
|
|
std::move(reply_markup), std::move(input_message_content));
|
|
}
|
|
if (type == "game") {
|
|
TRY_RESULT(game_short_name, get_json_object_string_field(object, "game_short_name", false));
|
|
return make_object<td_api::inputInlineQueryResultGame>(id, game_short_name, std::move(reply_markup));
|
|
}
|
|
if (type == "gif") {
|
|
TRY_RESULT(title, get_json_object_string_field(object, "title"));
|
|
TRY_RESULT(gif_url, get_json_object_string_field(object, "gif_url"));
|
|
TRY_RESULT(thumbnail_url, get_json_object_string_field(object, "thumb_url", gif_url.empty()));
|
|
TRY_RESULT(thumbnail_mime_type, get_json_object_string_field(object, "thumb_mime_type", true));
|
|
TRY_RESULT(gif_duration, get_json_object_int_field(object, "gif_duration"));
|
|
TRY_RESULT(gif_width, get_json_object_int_field(object, "gif_width"));
|
|
TRY_RESULT(gif_height, get_json_object_int_field(object, "gif_height"));
|
|
if (gif_url.empty()) {
|
|
TRY_RESULT_ASSIGN(gif_url, get_json_object_string_field(object, "gif_file_id", false));
|
|
}
|
|
|
|
if (input_message_content == nullptr) {
|
|
input_message_content = make_object<td_api::inputMessageAnimation>(
|
|
nullptr, nullptr, td::vector<int32>(), gif_duration, gif_width, gif_height, std::move(caption));
|
|
}
|
|
return make_object<td_api::inputInlineQueryResultAnimation>(
|
|
id, title, thumbnail_url, thumbnail_mime_type, gif_url, "image/gif", gif_duration, gif_width, gif_height,
|
|
std::move(reply_markup), std::move(input_message_content));
|
|
}
|
|
if (type == "location") {
|
|
TRY_RESULT(latitude, get_json_object_double_field(object, "latitude", false));
|
|
TRY_RESULT(longitude, get_json_object_double_field(object, "longitude", false));
|
|
TRY_RESULT(horizontal_accuracy, get_json_object_double_field(object, "horizontal_accuracy"));
|
|
TRY_RESULT(live_period, get_json_object_int_field(object, "live_period"));
|
|
TRY_RESULT(heading, get_json_object_int_field(object, "heading"));
|
|
TRY_RESULT(proximity_alert_radius, get_json_object_int_field(object, "proximity_alert_radius"));
|
|
TRY_RESULT(title, get_json_object_string_field(object, "title", false));
|
|
TRY_RESULT(thumbnail_url, get_json_object_string_field(object, "thumb_url"));
|
|
TRY_RESULT(thumbnail_width, get_json_object_int_field(object, "thumb_width"));
|
|
TRY_RESULT(thumbnail_height, get_json_object_int_field(object, "thumb_height"));
|
|
|
|
if (input_message_content == nullptr) {
|
|
auto location = make_object<td_api::location>(latitude, longitude, horizontal_accuracy);
|
|
input_message_content =
|
|
make_object<td_api::inputMessageLocation>(std::move(location), live_period, heading, proximity_alert_radius);
|
|
}
|
|
return make_object<td_api::inputInlineQueryResultLocation>(
|
|
id, make_object<td_api::location>(latitude, longitude, horizontal_accuracy), live_period, title, thumbnail_url,
|
|
thumbnail_width, thumbnail_height, std::move(reply_markup), std::move(input_message_content));
|
|
}
|
|
if (type == "mpeg4_gif") {
|
|
TRY_RESULT(title, get_json_object_string_field(object, "title"));
|
|
TRY_RESULT(mpeg4_url, get_json_object_string_field(object, "mpeg4_url"));
|
|
TRY_RESULT(thumbnail_url, get_json_object_string_field(object, "thumb_url", mpeg4_url.empty()));
|
|
TRY_RESULT(thumbnail_mime_type, get_json_object_string_field(object, "thumb_mime_type", true));
|
|
TRY_RESULT(mpeg4_duration, get_json_object_int_field(object, "mpeg4_duration"));
|
|
TRY_RESULT(mpeg4_width, get_json_object_int_field(object, "mpeg4_width"));
|
|
TRY_RESULT(mpeg4_height, get_json_object_int_field(object, "mpeg4_height"));
|
|
if (mpeg4_url.empty()) {
|
|
TRY_RESULT_ASSIGN(mpeg4_url, get_json_object_string_field(object, "mpeg4_file_id", false));
|
|
}
|
|
|
|
if (input_message_content == nullptr) {
|
|
input_message_content = make_object<td_api::inputMessageAnimation>(
|
|
nullptr, nullptr, td::vector<int32>(), mpeg4_duration, mpeg4_width, mpeg4_height, std::move(caption));
|
|
}
|
|
return make_object<td_api::inputInlineQueryResultAnimation>(
|
|
id, title, thumbnail_url, thumbnail_mime_type, mpeg4_url, "video/mp4", mpeg4_duration, mpeg4_width,
|
|
mpeg4_height, std::move(reply_markup), std::move(input_message_content));
|
|
}
|
|
if (type == "photo") {
|
|
TRY_RESULT(title, get_json_object_string_field(object, "title"));
|
|
TRY_RESULT(description, get_json_object_string_field(object, "description"));
|
|
TRY_RESULT(photo_url, get_json_object_string_field(object, "photo_url"));
|
|
TRY_RESULT(thumbnail_url, get_json_object_string_field(object, "thumb_url", photo_url.empty()));
|
|
TRY_RESULT(photo_width, get_json_object_int_field(object, "photo_width"));
|
|
TRY_RESULT(photo_height, get_json_object_int_field(object, "photo_height"));
|
|
if (photo_url.empty()) {
|
|
TRY_RESULT_ASSIGN(photo_url, get_json_object_string_field(object, "photo_file_id", false));
|
|
}
|
|
|
|
if (input_message_content == nullptr) {
|
|
input_message_content =
|
|
make_object<td_api::inputMessagePhoto>(nullptr, nullptr, td::vector<int32>(), 0, 0, std::move(caption), 0);
|
|
}
|
|
return make_object<td_api::inputInlineQueryResultPhoto>(id, title, description, thumbnail_url, photo_url,
|
|
photo_width, photo_height, std::move(reply_markup),
|
|
std::move(input_message_content));
|
|
}
|
|
if (type == "sticker") {
|
|
TRY_RESULT(sticker_file_id, get_json_object_string_field(object, "sticker_file_id", false));
|
|
|
|
if (input_message_content == nullptr) {
|
|
input_message_content = make_object<td_api::inputMessageSticker>(nullptr, nullptr, 0, 0);
|
|
}
|
|
return make_object<td_api::inputInlineQueryResultSticker>(id, "", sticker_file_id, 0, 0, std::move(reply_markup),
|
|
std::move(input_message_content));
|
|
}
|
|
if (type == "venue") {
|
|
TRY_RESULT(latitude, get_json_object_double_field(object, "latitude", false));
|
|
TRY_RESULT(longitude, get_json_object_double_field(object, "longitude", false));
|
|
TRY_RESULT(horizontal_accuracy, get_json_object_double_field(object, "horizontal_accuracy"));
|
|
TRY_RESULT(title, get_json_object_string_field(object, "title", false));
|
|
TRY_RESULT(address, get_json_object_string_field(object, "address", false));
|
|
TRY_RESULT(foursquare_id, get_json_object_string_field(object, "foursquare_id"));
|
|
TRY_RESULT(foursquare_type, get_json_object_string_field(object, "foursquare_type"));
|
|
TRY_RESULT(google_place_id, get_json_object_string_field(object, "google_place_id"));
|
|
TRY_RESULT(google_place_type, get_json_object_string_field(object, "google_place_type"));
|
|
TRY_RESULT(thumbnail_url, get_json_object_string_field(object, "thumb_url"));
|
|
TRY_RESULT(thumbnail_width, get_json_object_int_field(object, "thumb_width"));
|
|
TRY_RESULT(thumbnail_height, get_json_object_int_field(object, "thumb_height"));
|
|
|
|
td::string provider;
|
|
td::string venue_id;
|
|
td::string venue_type;
|
|
if (!google_place_id.empty() || !google_place_type.empty()) {
|
|
provider = "gplaces";
|
|
venue_id = std::move(google_place_id);
|
|
venue_type = std::move(google_place_type);
|
|
}
|
|
if (!foursquare_id.empty() || !foursquare_type.empty()) {
|
|
provider = "foursquare";
|
|
venue_id = std::move(foursquare_id);
|
|
venue_type = std::move(foursquare_type);
|
|
}
|
|
|
|
if (input_message_content == nullptr) {
|
|
input_message_content = make_object<td_api::inputMessageVenue>(
|
|
make_object<td_api::venue>(make_object<td_api::location>(latitude, longitude, horizontal_accuracy), title,
|
|
address, provider, venue_id, venue_type));
|
|
}
|
|
return make_object<td_api::inputInlineQueryResultVenue>(
|
|
id,
|
|
make_object<td_api::venue>(make_object<td_api::location>(latitude, longitude, horizontal_accuracy), title,
|
|
address, provider, venue_id, venue_type),
|
|
thumbnail_url, thumbnail_width, thumbnail_height, std::move(reply_markup), std::move(input_message_content));
|
|
}
|
|
if (type == "video") {
|
|
TRY_RESULT(title, get_json_object_string_field(object, "title", false));
|
|
TRY_RESULT(description, get_json_object_string_field(object, "description"));
|
|
TRY_RESULT(video_url, get_json_object_string_field(object, "video_url"));
|
|
TRY_RESULT(thumbnail_url, get_json_object_string_field(object, "thumb_url", video_url.empty()));
|
|
TRY_RESULT(mime_type, get_json_object_string_field(object, "mime_type", video_url.empty()));
|
|
TRY_RESULT(video_width, get_json_object_int_field(object, "video_width"));
|
|
TRY_RESULT(video_height, get_json_object_int_field(object, "video_height"));
|
|
TRY_RESULT(video_duration, get_json_object_int_field(object, "video_duration"));
|
|
if (video_url.empty()) {
|
|
TRY_RESULT_ASSIGN(video_url, get_json_object_string_field(object, "video_file_id", false));
|
|
}
|
|
|
|
if (input_message_content == nullptr) {
|
|
input_message_content =
|
|
make_object<td_api::inputMessageVideo>(nullptr, nullptr, td::vector<int32>(), video_duration, video_width,
|
|
video_height, false, std::move(caption), 0);
|
|
}
|
|
return make_object<td_api::inputInlineQueryResultVideo>(id, title, description, thumbnail_url, video_url, mime_type,
|
|
video_width, video_height, video_duration,
|
|
std::move(reply_markup), std::move(input_message_content));
|
|
}
|
|
if (type == "voice") {
|
|
TRY_RESULT(title, get_json_object_string_field(object, "title", false));
|
|
TRY_RESULT(voice_note_url, get_json_object_string_field(object, "voice_url"));
|
|
TRY_RESULT(voice_note_duration, get_json_object_int_field(object, "voice_duration"));
|
|
if (voice_note_url.empty()) {
|
|
TRY_RESULT_ASSIGN(voice_note_url, get_json_object_string_field(object, "voice_file_id", false));
|
|
}
|
|
|
|
if (input_message_content == nullptr) {
|
|
input_message_content = make_object<td_api::inputMessageVoiceNote>(nullptr, voice_note_duration,
|
|
"" /* waveform */, std::move(caption));
|
|
}
|
|
return make_object<td_api::inputInlineQueryResultVoiceNote>(
|
|
id, title, voice_note_url, voice_note_duration, std::move(reply_markup), std::move(input_message_content));
|
|
}
|
|
|
|
return Status::Error(400, PSLICE() << "type \"" << type << "\" is unsupported for the inline query result");
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::botCommand>> Client::get_bot_command(JsonValue &&value) {
|
|
if (value.type() != JsonValue::Type::Object) {
|
|
return Status::Error(400, "expected an Object");
|
|
}
|
|
|
|
auto &object = value.get_object();
|
|
|
|
TRY_RESULT(command, get_json_object_string_field(object, "command", false));
|
|
TRY_RESULT(description, get_json_object_string_field(object, "description", false));
|
|
|
|
return make_object<td_api::botCommand>(command, description);
|
|
}
|
|
|
|
td::Result<td::vector<td_api::object_ptr<td_api::botCommand>>> Client::get_bot_commands(const Query *query) {
|
|
auto commands = query->arg("commands");
|
|
if (commands.empty()) {
|
|
return td::vector<object_ptr<td_api::botCommand>>();
|
|
}
|
|
LOG(INFO) << "Parsing JSON object: " << commands;
|
|
auto r_value = json_decode(commands);
|
|
if (r_value.is_error()) {
|
|
LOG(INFO) << "Can't parse JSON object: " << r_value.error();
|
|
return Status::Error(400, "Can't parse commands JSON object");
|
|
}
|
|
|
|
auto value = r_value.move_as_ok();
|
|
if (value.type() != JsonValue::Type::Array) {
|
|
return Status::Error(400, "Expected an Array of BotCommand");
|
|
}
|
|
|
|
td::vector<object_ptr<td_api::botCommand>> bot_commands;
|
|
for (auto &command : value.get_array()) {
|
|
auto r_bot_command = get_bot_command(std::move(command));
|
|
if (r_bot_command.is_error()) {
|
|
return Status::Error(400, PSLICE() << "Can't parse BotCommand: " << r_bot_command.error().message());
|
|
}
|
|
bot_commands.push_back(r_bot_command.move_as_ok());
|
|
}
|
|
return std::move(bot_commands);
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::maskPosition>> Client::get_mask_position(JsonValue &&value) {
|
|
if (value.type() != JsonValue::Type::Object) {
|
|
return Status::Error(400, "MaskPosition must be an Object");
|
|
}
|
|
|
|
auto &object = value.get_object();
|
|
|
|
TRY_RESULT(point_str, get_json_object_string_field(object, "point", false));
|
|
point_str = td::trim(td::to_lower(point_str));
|
|
int32 point;
|
|
for (point = 0; point < MASK_POINTS_SIZE; point++) {
|
|
if (MASK_POINTS[point] == point_str) {
|
|
break;
|
|
}
|
|
}
|
|
if (point == MASK_POINTS_SIZE) {
|
|
return Status::Error(400, "Wrong point specified in MaskPosition");
|
|
}
|
|
|
|
TRY_RESULT(x_shift, get_json_object_double_field(object, "x_shift", false));
|
|
TRY_RESULT(y_shift, get_json_object_double_field(object, "y_shift", false));
|
|
TRY_RESULT(scale, get_json_object_double_field(object, "scale", false));
|
|
|
|
return make_object<td_api::maskPosition>(mask_index_to_point(point), x_shift, y_shift, scale);
|
|
}
|
|
|
|
td::int32 Client::mask_point_to_index(const object_ptr<td_api::MaskPoint> &mask_point) {
|
|
CHECK(mask_point != nullptr);
|
|
switch (mask_point->get_id()) {
|
|
case td_api::maskPointForehead::ID:
|
|
return 0;
|
|
case td_api::maskPointEyes::ID:
|
|
return 1;
|
|
case td_api::maskPointMouth::ID:
|
|
return 2;
|
|
case td_api::maskPointChin::ID:
|
|
return 3;
|
|
default:
|
|
UNREACHABLE();
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
td_api::object_ptr<td_api::MaskPoint> Client::mask_index_to_point(int32 index) {
|
|
switch (index) {
|
|
case 0:
|
|
return make_object<td_api::maskPointForehead>();
|
|
case 1:
|
|
return make_object<td_api::maskPointEyes>();
|
|
case 2:
|
|
return make_object<td_api::maskPointMouth>();
|
|
case 3:
|
|
return make_object<td_api::maskPointChin>();
|
|
default:
|
|
UNREACHABLE();
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::maskPosition>> Client::get_mask_position(const Query *query, Slice field_name) {
|
|
auto mask_position = query->arg(field_name);
|
|
if (mask_position.empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
LOG(INFO) << "Parsing JSON object: " << mask_position;
|
|
auto r_value = json_decode(mask_position);
|
|
if (r_value.is_error()) {
|
|
LOG(INFO) << "Can't parse JSON object: " << r_value.error();
|
|
return Status::Error(400, "Can't parse mask position JSON object");
|
|
}
|
|
|
|
auto r_mask_position = get_mask_position(r_value.move_as_ok());
|
|
if (r_mask_position.is_error()) {
|
|
return Status::Error(400, PSLICE() << "Can't parse mask position: " << r_mask_position.error().message());
|
|
}
|
|
return r_mask_position.move_as_ok();
|
|
}
|
|
|
|
td::Result<td::vector<td_api::object_ptr<td_api::InputSticker>>> Client::get_input_stickers(const Query *query) const {
|
|
auto emojis = query->arg("emojis");
|
|
|
|
td::vector<object_ptr<td_api::InputSticker>> stickers;
|
|
auto sticker = get_input_file(query, "png_sticker");
|
|
if (sticker != nullptr) {
|
|
TRY_RESULT(mask_position, get_mask_position(query, "mask_position"));
|
|
stickers.push_back(
|
|
make_object<td_api::inputStickerStatic>(std::move(sticker), emojis.str(), std::move(mask_position)));
|
|
} else {
|
|
sticker = get_input_file(query, "tgs_sticker", true);
|
|
if (sticker == nullptr) {
|
|
if (query->arg("tgs_sticker").empty()) {
|
|
return Status::Error(400, "Bad Request: there is no sticker file in the request");
|
|
}
|
|
return Status::Error(400, "Bad Request: TGS sticker must be uploaded as an InputFile");
|
|
}
|
|
|
|
stickers.push_back(make_object<td_api::inputStickerAnimated>(std::move(sticker), emojis.str()));
|
|
}
|
|
CHECK(stickers.size() == 1);
|
|
|
|
return std::move(stickers);
|
|
}
|
|
|
|
td::Result<td::string> Client::get_passport_element_hash(Slice encoded_hash) {
|
|
if (!td::is_base64(encoded_hash)) {
|
|
return Status::Error(400, "hash isn't a valid base64-encoded string");
|
|
}
|
|
return td::base64_decode(encoded_hash).move_as_ok();
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::InputPassportElementErrorSource>> Client::get_passport_element_error_source(
|
|
td::JsonObject &object) {
|
|
TRY_RESULT(source, get_json_object_string_field(object, "source"));
|
|
|
|
if (source.empty() || source == "unspecified") {
|
|
TRY_RESULT(element_hash, get_json_object_string_field(object, "element_hash", false));
|
|
TRY_RESULT(hash, get_passport_element_hash(element_hash));
|
|
|
|
return make_object<td_api::inputPassportElementErrorSourceUnspecified>(hash);
|
|
}
|
|
if (source == "data") {
|
|
TRY_RESULT(data_hash, get_json_object_string_field(object, "data_hash", false));
|
|
TRY_RESULT(hash, get_passport_element_hash(data_hash));
|
|
|
|
TRY_RESULT(field_name, get_json_object_string_field(object, "field_name", false));
|
|
return make_object<td_api::inputPassportElementErrorSourceDataField>(field_name, hash);
|
|
}
|
|
if (source == "file" || source == "selfie" || source == "translation_file" || source == "front_side" ||
|
|
source == "reverse_side") {
|
|
TRY_RESULT(file_hash, get_json_object_string_field(object, "file_hash", false));
|
|
TRY_RESULT(hash, get_passport_element_hash(file_hash));
|
|
|
|
if (source == "front_side") {
|
|
return make_object<td_api::inputPassportElementErrorSourceFrontSide>(hash);
|
|
}
|
|
if (source == "reverse_side") {
|
|
return make_object<td_api::inputPassportElementErrorSourceReverseSide>(hash);
|
|
}
|
|
if (source == "selfie") {
|
|
return make_object<td_api::inputPassportElementErrorSourceSelfie>(hash);
|
|
}
|
|
if (source == "translation_file") {
|
|
return make_object<td_api::inputPassportElementErrorSourceTranslationFile>(hash);
|
|
}
|
|
if (source == "file") {
|
|
return make_object<td_api::inputPassportElementErrorSourceFile>(hash);
|
|
}
|
|
UNREACHABLE();
|
|
}
|
|
if (source == "files" || source == "translation_files") {
|
|
td::vector<td::string> input_hashes;
|
|
TRY_RESULT(file_hashes, get_json_object_field(object, "file_hashes", JsonValue::Type::Array, false));
|
|
for (auto &input_hash : file_hashes.get_array()) {
|
|
if (input_hash.type() != JsonValue::Type::String) {
|
|
return Status::Error(400, "hash must be a string");
|
|
}
|
|
TRY_RESULT(hash, get_passport_element_hash(input_hash.get_string()));
|
|
input_hashes.push_back(std::move(hash));
|
|
}
|
|
if (source == "files") {
|
|
return make_object<td_api::inputPassportElementErrorSourceFiles>(std::move(input_hashes));
|
|
}
|
|
if (source == "translation_files") {
|
|
return make_object<td_api::inputPassportElementErrorSourceTranslationFiles>(std::move(input_hashes));
|
|
}
|
|
UNREACHABLE();
|
|
}
|
|
return Status::Error(400, "wrong source specified");
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::inputPassportElementError>> Client::get_passport_element_error(
|
|
JsonValue &&value) {
|
|
if (value.type() != JsonValue::Type::Object) {
|
|
return Status::Error(400, "expected an Object");
|
|
}
|
|
|
|
auto &object = value.get_object();
|
|
|
|
TRY_RESULT(input_type, get_json_object_string_field(object, "type", false));
|
|
auto type = get_passport_element_type(input_type);
|
|
if (type == nullptr) {
|
|
return Status::Error(400, "wrong Telegram Passport element type specified");
|
|
}
|
|
TRY_RESULT(message, get_json_object_string_field(object, "message", false));
|
|
TRY_RESULT(source, get_passport_element_error_source(object));
|
|
|
|
return make_object<td_api::inputPassportElementError>(std::move(type), message, std::move(source));
|
|
}
|
|
|
|
td::Result<td::vector<td_api::object_ptr<td_api::inputPassportElementError>>> Client::get_passport_element_errors(
|
|
const Query *query) {
|
|
auto input_errors = query->arg("errors");
|
|
LOG(INFO) << "Parsing JSON object: " << input_errors;
|
|
auto r_value = json_decode(input_errors);
|
|
if (r_value.is_error()) {
|
|
LOG(INFO) << "Can't parse JSON object: " << r_value.error();
|
|
return Status::Error(400, "Can't parse errors JSON object");
|
|
}
|
|
|
|
auto value = r_value.move_as_ok();
|
|
if (value.type() != JsonValue::Type::Array) {
|
|
return Status::Error(400, "Expected an Array of PassportElementError");
|
|
}
|
|
|
|
td::vector<object_ptr<td_api::inputPassportElementError>> errors;
|
|
for (auto &input_error : value.get_array()) {
|
|
auto r_error = get_passport_element_error(std::move(input_error));
|
|
if (r_error.is_error()) {
|
|
return Status::Error(400, PSLICE() << "Can't parse PassportElementError: " << r_error.error().message());
|
|
}
|
|
errors.push_back(r_error.move_as_ok());
|
|
}
|
|
return std::move(errors);
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::formattedText>> Client::get_caption(const Query *query) {
|
|
JsonValue entities;
|
|
auto r_value = json_decode(query->arg("caption_entities"));
|
|
if (r_value.is_ok()) {
|
|
entities = r_value.move_as_ok();
|
|
} else {
|
|
LOG(INFO) << "Can't parse JSON object: " << r_value.error();
|
|
}
|
|
|
|
return get_formatted_text(query->arg("caption").str(), query->arg("parse_mode").str(), std::move(entities));
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::TextEntityType>> Client::get_text_entity_type(td::JsonObject &object) {
|
|
TRY_RESULT(type, get_json_object_string_field(object, "type", false));
|
|
if (type.empty()) {
|
|
return Status::Error("Type is not specified");
|
|
}
|
|
|
|
if (type == "bold") {
|
|
return make_object<td_api::textEntityTypeBold>();
|
|
}
|
|
if (type == "italic") {
|
|
return make_object<td_api::textEntityTypeItalic>();
|
|
}
|
|
if (type == "underline") {
|
|
return make_object<td_api::textEntityTypeUnderline>();
|
|
}
|
|
if (type == "strikethrough") {
|
|
return make_object<td_api::textEntityTypeStrikethrough>();
|
|
}
|
|
if (type == "code") {
|
|
return make_object<td_api::textEntityTypeCode>();
|
|
}
|
|
if (type == "pre") {
|
|
TRY_RESULT(language, get_json_object_string_field(object, "language"));
|
|
if (language.empty()) {
|
|
return make_object<td_api::textEntityTypePre>();
|
|
}
|
|
return make_object<td_api::textEntityTypePreCode>(language);
|
|
}
|
|
if (type == "text_link") {
|
|
TRY_RESULT(url, get_json_object_string_field(object, "url", false));
|
|
return make_object<td_api::textEntityTypeTextUrl>(url);
|
|
}
|
|
if (type == "text_mention") {
|
|
TRY_RESULT(user, get_json_object_field(object, "user", JsonValue::Type::Object, false));
|
|
CHECK(user.type() == JsonValue::Type::Object);
|
|
TRY_RESULT(user_id, get_json_object_int_field(user.get_object(), "id", false));
|
|
return make_object<td_api::textEntityTypeMentionName>(user_id);
|
|
}
|
|
if (type == "mention" || type == "hashtag" || type == "cashtag" || type == "bot_command" || type == "url" ||
|
|
type == "email" || type == "phone_number" || type == "bank_card_number") {
|
|
return nullptr;
|
|
}
|
|
|
|
return Status::Error("Unsupported type specified");
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::textEntity>> Client::get_text_entity(JsonValue &&value) {
|
|
if (value.type() != JsonValue::Type::Object) {
|
|
return Status::Error(400, "expected an Object");
|
|
}
|
|
|
|
auto &object = value.get_object();
|
|
TRY_RESULT(offset, get_json_object_int_field(object, "offset", false));
|
|
TRY_RESULT(length, get_json_object_int_field(object, "length", false));
|
|
TRY_RESULT(type, get_text_entity_type(object));
|
|
|
|
if (type == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
return make_object<td_api::textEntity>(offset, length, std::move(type));
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::formattedText>> Client::get_formatted_text(td::string text, td::string parse_mode,
|
|
JsonValue &&input_entities) {
|
|
td::to_lower_inplace(parse_mode);
|
|
if (!text.empty() && !parse_mode.empty() && parse_mode != "none") {
|
|
object_ptr<td_api::TextParseMode> text_parse_mode;
|
|
if (parse_mode == "markdown") {
|
|
text_parse_mode = make_object<td_api::textParseModeMarkdown>(1);
|
|
} else if (parse_mode == "markdownv2") {
|
|
text_parse_mode = make_object<td_api::textParseModeMarkdown>(2);
|
|
} else if (parse_mode == "html") {
|
|
text_parse_mode = make_object<td_api::textParseModeHTML>();
|
|
} else {
|
|
return Status::Error(400, "Unsupported parse_mode");
|
|
}
|
|
|
|
auto parsed_text = execute(make_object<td_api::parseTextEntities>(text, std::move(text_parse_mode)));
|
|
if (parsed_text->get_id() == td_api::error::ID) {
|
|
auto error = move_object_as<td_api::error>(parsed_text);
|
|
return Status::Error(error->code_, error->message_);
|
|
}
|
|
|
|
CHECK(parsed_text->get_id() == td_api::formattedText::ID);
|
|
return move_object_as<td_api::formattedText>(parsed_text);
|
|
}
|
|
|
|
td::vector<object_ptr<td_api::textEntity>> entities;
|
|
if (input_entities.type() == JsonValue::Type::Array) {
|
|
for (auto &input_entity : input_entities.get_array()) {
|
|
auto r_entity = get_text_entity(std::move(input_entity));
|
|
if (r_entity.is_error()) {
|
|
return Status::Error(400, PSLICE() << "Can't parse MessageEntity: " << r_entity.error().message());
|
|
}
|
|
if (r_entity.ok() == nullptr) {
|
|
continue;
|
|
}
|
|
entities.push_back(r_entity.move_as_ok());
|
|
}
|
|
}
|
|
|
|
return make_object<td_api::formattedText>(text, std::move(entities));
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::inputMessageText>> Client::get_input_message_text(const Query *query) {
|
|
JsonValue entities;
|
|
auto r_value = json_decode(query->arg("entities"));
|
|
if (r_value.is_ok()) {
|
|
entities = r_value.move_as_ok();
|
|
} else {
|
|
LOG(INFO) << "Can't parse JSON object: " << r_value.error();
|
|
}
|
|
|
|
return get_input_message_text(query->arg("text").str(), to_bool(query->arg("disable_web_page_preview")),
|
|
query->arg("parse_mode").str(), std::move(entities));
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::inputMessageText>> Client::get_input_message_text(td::string text,
|
|
bool disable_web_page_preview,
|
|
td::string parse_mode,
|
|
JsonValue &&input_entities) {
|
|
if (text.empty()) {
|
|
return Status::Error(400, "Message text is empty");
|
|
}
|
|
|
|
TRY_RESULT(formatted_text, get_formatted_text(std::move(text), std::move(parse_mode), std::move(input_entities)));
|
|
|
|
return make_object<td_api::inputMessageText>(std::move(formatted_text), disable_web_page_preview, false);
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::location>> Client::get_location(const Query *query) {
|
|
auto latitude = trim(query->arg("latitude"));
|
|
if (latitude.empty()) {
|
|
return Status::Error(400, "Bad Request: latitude is empty");
|
|
}
|
|
auto longitude = trim(query->arg("longitude"));
|
|
if (longitude.empty()) {
|
|
return Status::Error(400, "Bad Request: longitude is empty");
|
|
}
|
|
auto horizontal_accuracy = trim(query->arg("horizontal_accuracy"));
|
|
|
|
return make_object<td_api::location>(td::to_double(latitude), td::to_double(longitude),
|
|
td::to_double(horizontal_accuracy));
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::chatPermissions>> Client::get_chat_permissions(const Query *query,
|
|
bool &allow_legacy) {
|
|
auto can_send_messages = false;
|
|
auto can_send_media_messages = false;
|
|
auto can_send_polls = false;
|
|
auto can_send_other_messages = false;
|
|
auto can_add_web_page_previews = false;
|
|
auto can_change_info = false;
|
|
auto can_invite_users = false;
|
|
auto can_pin_messages = false;
|
|
|
|
if (query->has_arg("permissions")) {
|
|
allow_legacy = false;
|
|
|
|
auto r_value = json_decode(query->arg("permissions"));
|
|
if (r_value.is_error()) {
|
|
LOG(INFO) << "Can't parse JSON object: " << r_value.error();
|
|
return Status::Error(400, "Can't parse permissions JSON object");
|
|
}
|
|
|
|
auto value = r_value.move_as_ok();
|
|
if (value.type() != JsonValue::Type::Object) {
|
|
return Status::Error(400, "Object expected as permissions");
|
|
}
|
|
auto &object = value.get_object();
|
|
|
|
auto status = [&] {
|
|
TRY_RESULT_ASSIGN(can_send_messages, get_json_object_bool_field(object, "can_send_messages"));
|
|
TRY_RESULT_ASSIGN(can_send_media_messages, get_json_object_bool_field(object, "can_send_media_messages"));
|
|
TRY_RESULT_ASSIGN(can_send_polls, get_json_object_bool_field(object, "can_send_polls"));
|
|
TRY_RESULT_ASSIGN(can_send_other_messages, get_json_object_bool_field(object, "can_send_other_messages"));
|
|
TRY_RESULT_ASSIGN(can_add_web_page_previews, get_json_object_bool_field(object, "can_add_web_page_previews"));
|
|
TRY_RESULT_ASSIGN(can_change_info, get_json_object_bool_field(object, "can_change_info"));
|
|
TRY_RESULT_ASSIGN(can_invite_users, get_json_object_bool_field(object, "can_invite_users"));
|
|
TRY_RESULT_ASSIGN(can_pin_messages, get_json_object_bool_field(object, "can_pin_messages"));
|
|
return Status::OK();
|
|
}();
|
|
|
|
if (status.is_error()) {
|
|
return Status::Error(400, PSLICE() << "Can't parse chat permissions: " << status.error().message());
|
|
}
|
|
} else if (allow_legacy) {
|
|
allow_legacy = false;
|
|
|
|
can_send_messages = to_bool(query->arg("can_send_messages"));
|
|
can_send_media_messages = to_bool(query->arg("can_send_media_messages"));
|
|
can_send_other_messages = to_bool(query->arg("can_send_other_messages"));
|
|
can_add_web_page_previews = to_bool(query->arg("can_add_web_page_previews"));
|
|
|
|
if (can_send_messages && can_send_media_messages && can_send_other_messages && can_add_web_page_previews) {
|
|
// legacy unrestrict
|
|
can_send_polls = true;
|
|
can_change_info = true;
|
|
can_invite_users = true;
|
|
can_pin_messages = true;
|
|
} else if (query->has_arg("can_send_messages") || query->has_arg("can_send_media_messages") ||
|
|
query->has_arg("can_send_other_messages") || query->has_arg("can_add_web_page_previews")) {
|
|
allow_legacy = true;
|
|
}
|
|
}
|
|
|
|
if (can_send_other_messages || can_add_web_page_previews) {
|
|
can_send_media_messages = true;
|
|
}
|
|
return make_object<td_api::chatPermissions>(can_send_messages, can_send_media_messages, can_send_polls,
|
|
can_send_other_messages, can_add_web_page_previews, can_change_info,
|
|
can_invite_users, can_pin_messages);
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::InputMessageContent>> Client::get_input_media(const Query *query,
|
|
JsonValue &&input_media,
|
|
bool for_album) const {
|
|
if (input_media.type() != JsonValue::Type::Object) {
|
|
return Status::Error(400, "expected an Object");
|
|
}
|
|
|
|
auto &object = input_media.get_object();
|
|
|
|
TRY_RESULT(input_caption, get_json_object_string_field(object, "caption"));
|
|
TRY_RESULT(parse_mode, get_json_object_string_field(object, "parse_mode"));
|
|
auto entities = get_json_object_field_force(object, "caption_entities");
|
|
TRY_RESULT(caption, get_formatted_text(std::move(input_caption), std::move(parse_mode), std::move(entities)));
|
|
// TRY_RESULT(ttl, get_json_object_int_field(object, "ttl"));
|
|
int32 ttl = 0;
|
|
TRY_RESULT(media, get_json_object_string_field(object, "media", true));
|
|
|
|
auto input_file = get_input_file(query, Slice(), media, false);
|
|
if (input_file == nullptr) {
|
|
return Status::Error(400, "media not found");
|
|
}
|
|
|
|
TRY_RESULT(thumbnail, get_json_object_string_field(object, "thumb"));
|
|
object_ptr<td_api::inputThumbnail> input_thumbnail;
|
|
auto thumbanil_input_file = get_input_file(query, thumbnail.empty() ? Slice("thumb") : Slice(), thumbnail, true);
|
|
if (thumbanil_input_file != nullptr) {
|
|
input_thumbnail = make_object<td_api::inputThumbnail>(std::move(thumbanil_input_file), 0, 0);
|
|
}
|
|
|
|
TRY_RESULT(type, get_json_object_string_field(object, "type", false));
|
|
if (type == "photo") {
|
|
return make_object<td_api::inputMessagePhoto>(std::move(input_file), std::move(input_thumbnail),
|
|
td::vector<int32>(), 0, 0, std::move(caption), ttl);
|
|
}
|
|
if (type == "video") {
|
|
TRY_RESULT(width, get_json_object_int_field(object, "width"));
|
|
TRY_RESULT(height, get_json_object_int_field(object, "height"));
|
|
TRY_RESULT(duration, get_json_object_int_field(object, "duration"));
|
|
TRY_RESULT(supports_streaming, get_json_object_bool_field(object, "supports_streaming"));
|
|
width = td::clamp(width, 0, MAX_LENGTH);
|
|
height = td::clamp(height, 0, MAX_LENGTH);
|
|
duration = td::clamp(duration, 0, MAX_DURATION);
|
|
|
|
return make_object<td_api::inputMessageVideo>(std::move(input_file), std::move(input_thumbnail),
|
|
td::vector<int32>(), duration, width, height, supports_streaming,
|
|
std::move(caption), ttl);
|
|
}
|
|
if (for_album && type == "animation") {
|
|
return Status::Error(400, PSLICE() << "type \"" << type << "\" can't be used in sendMediaGroup");
|
|
}
|
|
if (type == "animation") {
|
|
TRY_RESULT(width, get_json_object_int_field(object, "width"));
|
|
TRY_RESULT(height, get_json_object_int_field(object, "height"));
|
|
TRY_RESULT(duration, get_json_object_int_field(object, "duration"));
|
|
width = td::clamp(width, 0, MAX_LENGTH);
|
|
height = td::clamp(height, 0, MAX_LENGTH);
|
|
duration = td::clamp(duration, 0, MAX_DURATION);
|
|
return make_object<td_api::inputMessageAnimation>(std::move(input_file), std::move(input_thumbnail),
|
|
td::vector<int32>(), duration, width, height, std::move(caption));
|
|
}
|
|
if (type == "audio") {
|
|
TRY_RESULT(duration, get_json_object_int_field(object, "duration"));
|
|
TRY_RESULT(title, get_json_object_string_field(object, "title"));
|
|
TRY_RESULT(performer, get_json_object_string_field(object, "performer"));
|
|
duration = td::clamp(duration, 0, MAX_DURATION);
|
|
return make_object<td_api::inputMessageAudio>(std::move(input_file), std::move(input_thumbnail), duration, title,
|
|
performer, std::move(caption));
|
|
}
|
|
if (type == "document") {
|
|
TRY_RESULT(disable_content_type_detection, get_json_object_bool_field(object, "disable_content_type_detection"));
|
|
return make_object<td_api::inputMessageDocument>(std::move(input_file), std::move(input_thumbnail),
|
|
disable_content_type_detection || for_album, std::move(caption));
|
|
}
|
|
|
|
return Status::Error(400, PSLICE() << "type \"" << type << "\" is unsupported");
|
|
}
|
|
|
|
td::Result<td_api::object_ptr<td_api::InputMessageContent>> Client::get_input_media(const Query *query,
|
|
Slice field_name,
|
|
bool for_album) const {
|
|
TRY_RESULT(media, get_required_string_arg(query, field_name));
|
|
|
|
LOG(INFO) << "Parsing JSON object: " << media;
|
|
auto r_value = json_decode(media);
|
|
if (r_value.is_error()) {
|
|
LOG(INFO) << "Can't parse JSON object: " << r_value.error();
|
|
return Status::Error(400, "Can't parse input media JSON object");
|
|
}
|
|
|
|
auto r_input_message_content = get_input_media(query, r_value.move_as_ok(), for_album);
|
|
if (r_input_message_content.is_error()) {
|
|
return Status::Error(400, PSLICE() << "Can't parse InputMedia: " << r_input_message_content.error().message());
|
|
}
|
|
return r_input_message_content.move_as_ok();
|
|
}
|
|
|
|
td::Result<td::vector<td_api::object_ptr<td_api::InputMessageContent>>> Client::get_input_message_contents(
|
|
const Query *query, Slice field_name) const {
|
|
TRY_RESULT(media, get_required_string_arg(query, field_name));
|
|
|
|
LOG(INFO) << "Parsing JSON object: " << media;
|
|
auto r_value = json_decode(media);
|
|
if (r_value.is_error()) {
|
|
LOG(INFO) << "Can't parse JSON object: " << r_value.error();
|
|
return Status::Error(400, "Can't parse media JSON object");
|
|
}
|
|
|
|
return get_input_message_contents(query, r_value.move_as_ok());
|
|
}
|
|
|
|
td::Result<td::vector<td_api::object_ptr<td_api::InputMessageContent>>> Client::get_input_message_contents(
|
|
const Query *query, JsonValue &&value) const {
|
|
if (value.type() != JsonValue::Type::Array) {
|
|
return Status::Error(400, "Expected an Array of InputMedia");
|
|
}
|
|
|
|
td::vector<object_ptr<td_api::InputMessageContent>> contents;
|
|
for (auto &input_media : value.get_array()) {
|
|
TRY_RESULT(input_message_content, get_input_media(query, std::move(input_media), true));
|
|
contents.push_back(std::move(input_message_content));
|
|
}
|
|
return std::move(contents);
|
|
}
|
|
|
|
td::Result<td::vector<td::string>> Client::get_poll_options(const Query *query) {
|
|
auto input_options = query->arg("options");
|
|
LOG(INFO) << "Parsing JSON object: " << input_options;
|
|
auto r_value = json_decode(input_options);
|
|
if (r_value.is_error()) {
|
|
LOG(INFO) << "Can't parse JSON object: " << r_value.error();
|
|
return Status::Error(400, "Can't parse options JSON object");
|
|
}
|
|
|
|
auto value = r_value.move_as_ok();
|
|
if (value.type() != JsonValue::Type::Array) {
|
|
return Status::Error(400, "Expected an Array of String as options");
|
|
}
|
|
|
|
td::vector<td::string> options;
|
|
for (auto &input_option : value.get_array()) {
|
|
if (input_option.type() != JsonValue::Type::String) {
|
|
return Status::Error(400, "Expected an option to be of type String");
|
|
}
|
|
options.push_back(input_option.get_string().str());
|
|
}
|
|
return std::move(options);
|
|
}
|
|
|
|
td::int32 Client::get_integer_arg(const Query *query, Slice field_name, int32 default_value, int32 min_value,
|
|
int32 max_value) {
|
|
auto s_arg = query->arg(field_name);
|
|
if (s_arg.empty()) {
|
|
return default_value;
|
|
}
|
|
|
|
return td::clamp(td::to_integer<int32>(s_arg), min_value, max_value);
|
|
}
|
|
|
|
td::Result<td::MutableSlice> Client::get_required_string_arg(const Query *query, Slice field_name) {
|
|
auto s_arg = query->arg(field_name);
|
|
if (s_arg.empty()) {
|
|
return Status::Error(400, PSLICE() << "Parameter \"" << field_name << "\" is required");
|
|
}
|
|
return s_arg;
|
|
}
|
|
|
|
td::int64 Client::get_message_id(const Query *query, Slice field_name) {
|
|
auto s_arg = query->arg(field_name);
|
|
if (s_arg.empty()) {
|
|
return 0;
|
|
}
|
|
|
|
int arg = td::to_integer<int32>(s_arg);
|
|
if (arg < 0) {
|
|
return 0;
|
|
}
|
|
|
|
return as_tdlib_message_id(arg);
|
|
}
|
|
|
|
td::Result<td::Slice> Client::get_inline_message_id(const Query *query, Slice field_name) {
|
|
auto s_arg = query->arg(field_name);
|
|
if (s_arg.empty()) {
|
|
return Status::Error(400, "Message identifier is not specified");
|
|
}
|
|
return s_arg;
|
|
}
|
|
|
|
td::Result<td::int32> Client::get_user_id(const Query *query, Slice field_name) {
|
|
int32 user_id = get_integer_arg(query, field_name, 0, 0);
|
|
if (user_id == 0) {
|
|
return Status::Error(400, PSLICE() << "Invalid " << field_name << " specified");
|
|
}
|
|
return user_id;
|
|
}
|
|
|
|
td::int64 Client::extract_yet_unsent_message_query_id(int64 chat_id, int64 message_id,
|
|
bool *is_reply_to_message_deleted) {
|
|
auto yet_unsent_message_it = yet_unsent_messages_.find({chat_id, message_id});
|
|
CHECK(yet_unsent_message_it != yet_unsent_messages_.end());
|
|
|
|
auto reply_to_message_id = yet_unsent_message_it->second.reply_to_message_id;
|
|
if (is_reply_to_message_deleted != nullptr && yet_unsent_message_it->second.is_reply_to_message_deleted) {
|
|
*is_reply_to_message_deleted = true;
|
|
}
|
|
auto query_id = yet_unsent_message_it->second.send_message_query_id;
|
|
|
|
yet_unsent_messages_.erase(yet_unsent_message_it);
|
|
|
|
if (reply_to_message_id > 0) {
|
|
auto it = yet_unsent_reply_message_ids_.find({chat_id, reply_to_message_id});
|
|
CHECK(it != yet_unsent_reply_message_ids_.end());
|
|
auto erased_count = it->second.erase(message_id);
|
|
CHECK(erased_count > 0);
|
|
if (it->second.empty()) {
|
|
yet_unsent_reply_message_ids_.erase(it);
|
|
}
|
|
}
|
|
|
|
return query_id;
|
|
}
|
|
|
|
void Client::on_message_send_succeeded(object_ptr<td_api::message> &&message, int64 old_message_id) {
|
|
auto full_message_id = add_message(std::move(message), true);
|
|
|
|
int64 chat_id = full_message_id.chat_id;
|
|
int64 new_message_id = full_message_id.message_id;
|
|
CHECK(new_message_id > 0);
|
|
|
|
auto message_info = get_message(chat_id, new_message_id);
|
|
CHECK(message_info != nullptr);
|
|
message_info->is_content_changed = false;
|
|
|
|
auto query_id =
|
|
extract_yet_unsent_message_query_id(chat_id, old_message_id, &message_info->is_reply_to_message_deleted);
|
|
auto &query = pending_send_message_queries_[query_id];
|
|
if (query.is_multisend) {
|
|
query.messages.push_back(td::json_encode<td::string>(JsonMessage(message_info, true, "sent message", this)));
|
|
query.awaited_messages--;
|
|
|
|
if (query.awaited_messages == 0) {
|
|
if (query.error == nullptr) {
|
|
answer_query(JsonMessages(query.messages), std::move(query.query));
|
|
} else {
|
|
fail_query_with_error(std::move(query.query), std::move(query.error));
|
|
}
|
|
pending_send_message_queries_.erase(query_id);
|
|
}
|
|
} else {
|
|
CHECK(query.awaited_messages == 1);
|
|
if (query.query->method() == "copymessage") {
|
|
answer_query(JsonMessageId(new_message_id), std::move(query.query));
|
|
} else {
|
|
answer_query(JsonMessage(message_info, true, "sent message", this), std::move(query.query));
|
|
}
|
|
pending_send_message_queries_.erase(query_id);
|
|
}
|
|
}
|
|
|
|
void Client::on_message_send_failed(int64 chat_id, int64 old_message_id, int64 new_message_id, Status result) {
|
|
auto error = make_object<td_api::error>(result.code(), result.message().str());
|
|
|
|
auto query_id = extract_yet_unsent_message_query_id(chat_id, old_message_id, nullptr);
|
|
auto &query = pending_send_message_queries_[query_id];
|
|
if (query.is_multisend) {
|
|
if (query.error == nullptr) {
|
|
query.error = std::move(error);
|
|
}
|
|
query.awaited_messages--;
|
|
|
|
if (query.awaited_messages == 0) {
|
|
fail_query_with_error(std::move(query.query), std::move(query.error));
|
|
pending_send_message_queries_.erase(query_id);
|
|
}
|
|
} else {
|
|
CHECK(query.awaited_messages == 1);
|
|
fail_query_with_error(std::move(query.query), std::move(error));
|
|
pending_send_message_queries_.erase(query_id);
|
|
}
|
|
|
|
if (new_message_id != 0 && !logging_out_ && !closing_) {
|
|
send_request(make_object<td_api::deleteMessages>(chat_id, td::vector<int64>{new_message_id}, false),
|
|
std::make_unique<TdOnDeleteFailedToSendMessageCallback>(this, chat_id, new_message_id));
|
|
}
|
|
}
|
|
|
|
void Client::on_cmd(PromisedQueryPtr query) {
|
|
LOG(DEBUG) << "Process query " << *query;
|
|
if (!td_client_.empty()) {
|
|
if (query->method() == "close") {
|
|
auto retry_after = static_cast<int>(10 * 60 - (td::Time::now() - start_timestamp_));
|
|
if (retry_after > 0 && start_timestamp_ > parameters_->start_timestamp_ + 10 * 60) {
|
|
return query->set_retry_after_error(retry_after);
|
|
}
|
|
need_close_ = true;
|
|
return do_send_request(make_object<td_api::close>(), std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
}
|
|
if (query->method() == "logout") {
|
|
clear_tqueue_ = true;
|
|
return do_send_request(make_object<td_api::logOut>(), std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
}
|
|
}
|
|
|
|
if (logging_out_) {
|
|
return fail_query(LOGGING_OUT_ERROR_CODE, LOGGING_OUT_ERROR_DESCRIPTION, std::move(query));
|
|
}
|
|
if (closing_) {
|
|
return fail_query(CLOSING_ERROR_CODE, LOGGING_OUT_ERROR_DESCRIPTION, std::move(query));
|
|
}
|
|
CHECK(was_authorized_);
|
|
|
|
unresolved_bot_usernames_.clear();
|
|
|
|
auto method_it = methods_.find(query->method().str());
|
|
if (method_it == methods_.end()) {
|
|
return fail_query(404, "Not Found: method not found", std::move(query));
|
|
}
|
|
|
|
auto result = (this->*(method_it->second))(query);
|
|
if (result.is_error()) {
|
|
fail_query_with_error(std::move(query), result.code(), result.message());
|
|
}
|
|
}
|
|
|
|
td::Status Client::process_get_me_query(PromisedQueryPtr &query) {
|
|
answer_query(JsonUser(my_id_, this, true), std::move(query));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_get_my_commands_query(PromisedQueryPtr &query) {
|
|
send_request(make_object<td_api::getUserFullInfo>(my_id_),
|
|
std::make_unique<TdOnGetMyCommandsCallback>(std::move(query)));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_set_my_commands_query(PromisedQueryPtr &query) {
|
|
TRY_RESULT(bot_commands, get_bot_commands(query.get()));
|
|
send_request(make_object<td_api::setCommands>(std::move(bot_commands)),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_get_user_profile_photos_query(PromisedQueryPtr &query) {
|
|
TRY_RESULT(user_id, get_user_id(query.get()));
|
|
int32 offset = get_integer_arg(query.get(), "offset", 0, 0);
|
|
int32 limit = get_integer_arg(query.get(), "limit", 100, 1, 100);
|
|
|
|
check_user(user_id, std::move(query), [this, user_id, offset, limit](PromisedQueryPtr query) {
|
|
send_request(make_object<td_api::getUserProfilePhotos>(user_id, offset, limit),
|
|
std::make_unique<TdOnGetUserProfilePhotosCallback>(this, std::move(query)));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_send_message_query(PromisedQueryPtr &query) {
|
|
TRY_RESULT(input_message_text, get_input_message_text(query.get()));
|
|
do_send_message(std::move(input_message_text), std::move(query));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_send_animation_query(PromisedQueryPtr &query) {
|
|
auto animation = get_input_file(query.get(), "animation");
|
|
if (animation == nullptr) {
|
|
return Status::Error(400, "There is no animation in the request");
|
|
}
|
|
auto thumbnail = get_input_thumbnail(query.get(), "thumb");
|
|
int32 duration = get_integer_arg(query.get(), "duration", 0, 0, MAX_DURATION);
|
|
int32 width = get_integer_arg(query.get(), "width", 0, 0, MAX_LENGTH);
|
|
int32 height = get_integer_arg(query.get(), "height", 0, 0, MAX_LENGTH);
|
|
TRY_RESULT(caption, get_caption(query.get()));
|
|
do_send_message(
|
|
make_object<td_api::inputMessageAnimation>(std::move(animation), std::move(thumbnail), td::vector<int32>(),
|
|
duration, width, height, std::move(caption)),
|
|
std::move(query));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_send_audio_query(PromisedQueryPtr &query) {
|
|
auto audio = get_input_file(query.get(), "audio");
|
|
if (audio == nullptr) {
|
|
return Status::Error(400, "There is no audio in the request");
|
|
}
|
|
auto thumbnail = get_input_thumbnail(query.get(), "thumb");
|
|
int32 duration = get_integer_arg(query.get(), "duration", 0, 0, MAX_DURATION);
|
|
auto title = query->arg("title").str();
|
|
auto performer = query->arg("performer").str();
|
|
TRY_RESULT(caption, get_caption(query.get()));
|
|
do_send_message(make_object<td_api::inputMessageAudio>(std::move(audio), std::move(thumbnail), duration, title,
|
|
performer, std::move(caption)),
|
|
std::move(query));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_send_dice_query(PromisedQueryPtr &query) {
|
|
auto emoji = query->arg("emoji");
|
|
do_send_message(make_object<td_api::inputMessageDice>(emoji.str(), false), std::move(query));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_send_document_query(PromisedQueryPtr &query) {
|
|
auto document = get_input_file(query.get(), "document");
|
|
if (document == nullptr) {
|
|
return Status::Error(400, "There is no document in the request");
|
|
}
|
|
auto thumbnail = get_input_thumbnail(query.get(), "thumb");
|
|
TRY_RESULT(caption, get_caption(query.get()));
|
|
bool disable_content_type_detection = to_bool(query->arg("disable_content_type_detection"));
|
|
do_send_message(make_object<td_api::inputMessageDocument>(std::move(document), std::move(thumbnail),
|
|
disable_content_type_detection, std::move(caption)),
|
|
std::move(query));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_send_photo_query(PromisedQueryPtr &query) {
|
|
auto photo = get_input_file(query.get(), "photo");
|
|
if (photo == nullptr) {
|
|
return Status::Error(400, "There is no photo in the request");
|
|
}
|
|
TRY_RESULT(caption, get_caption(query.get()));
|
|
auto ttl = 0;
|
|
do_send_message(make_object<td_api::inputMessagePhoto>(std::move(photo), nullptr, td::vector<int32>(), 0, 0,
|
|
std::move(caption), ttl),
|
|
std::move(query));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_send_sticker_query(PromisedQueryPtr &query) {
|
|
auto sticker = get_input_file(query.get(), "sticker");
|
|
if (sticker == nullptr) {
|
|
return Status::Error(400, "There is no sticker in the request");
|
|
}
|
|
do_send_message(make_object<td_api::inputMessageSticker>(std::move(sticker), nullptr, 0, 0), std::move(query));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_send_video_query(PromisedQueryPtr &query) {
|
|
auto video = get_input_file(query.get(), "video");
|
|
if (video == nullptr) {
|
|
return Status::Error(400, "There is no video in the request");
|
|
}
|
|
auto thumbnail = get_input_thumbnail(query.get(), "thumb");
|
|
int32 duration = get_integer_arg(query.get(), "duration", 0, 0, MAX_DURATION);
|
|
int32 width = get_integer_arg(query.get(), "width", 0, 0, MAX_LENGTH);
|
|
int32 height = get_integer_arg(query.get(), "height", 0, 0, MAX_LENGTH);
|
|
bool supports_streaming = to_bool(query->arg("supports_streaming"));
|
|
TRY_RESULT(caption, get_caption(query.get()));
|
|
auto ttl = 0;
|
|
do_send_message(
|
|
make_object<td_api::inputMessageVideo>(std::move(video), std::move(thumbnail), td::vector<int32>(), duration,
|
|
width, height, supports_streaming, std::move(caption), ttl),
|
|
std::move(query));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_send_video_note_query(PromisedQueryPtr &query) {
|
|
auto video_note = get_input_file(query.get(), "video_note");
|
|
if (video_note == nullptr) {
|
|
return Status::Error(400, "There is no video note in the request");
|
|
}
|
|
auto thumbnail = get_input_thumbnail(query.get(), "thumb");
|
|
int32 duration = get_integer_arg(query.get(), "duration", 0, 0, MAX_DURATION);
|
|
int32 length = get_integer_arg(query.get(), "length", 0, 0, MAX_LENGTH);
|
|
do_send_message(
|
|
make_object<td_api::inputMessageVideoNote>(std::move(video_note), std::move(thumbnail), duration, length),
|
|
std::move(query));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_send_voice_query(PromisedQueryPtr &query) {
|
|
auto voice_note = get_input_file(query.get(), "voice");
|
|
if (voice_note == nullptr) {
|
|
return Status::Error(400, "There is no voice in the request");
|
|
}
|
|
int32 duration = get_integer_arg(query.get(), "duration", 0, 0, MAX_DURATION);
|
|
TRY_RESULT(caption, get_caption(query.get()));
|
|
do_send_message(make_object<td_api::inputMessageVoiceNote>(std::move(voice_note), duration, "", std::move(caption)),
|
|
std::move(query));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_send_game_query(PromisedQueryPtr &query) {
|
|
TRY_RESULT(game_short_name, get_required_string_arg(query.get(), "game_short_name"));
|
|
do_send_message(make_object<td_api::inputMessageGame>(my_id_, game_short_name.str()), std::move(query));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_send_invoice_query(PromisedQueryPtr &query) {
|
|
TRY_RESULT(title, get_required_string_arg(query.get(), "title"));
|
|
TRY_RESULT(description, get_required_string_arg(query.get(), "description"));
|
|
TRY_RESULT(payload, get_required_string_arg(query.get(), "payload"));
|
|
if (!td::check_utf8(payload.str())) {
|
|
return Status::Error(400, "The payload must be encoded in UTF-8");
|
|
}
|
|
TRY_RESULT(provider_token, get_required_string_arg(query.get(), "provider_token"));
|
|
auto provider_data = query->arg("provider_data");
|
|
TRY_RESULT(start_parameter, get_required_string_arg(query.get(), "start_parameter"));
|
|
TRY_RESULT(currency, get_required_string_arg(query.get(), "currency"));
|
|
|
|
TRY_RESULT(labeled_price_parts, get_required_string_arg(query.get(), "prices"));
|
|
auto r_value = json_decode(labeled_price_parts);
|
|
if (r_value.is_error()) {
|
|
return Status::Error(400, "Can't parse prices JSON object");
|
|
}
|
|
|
|
TRY_RESULT(prices, get_labeled_price_parts(r_value.ok_ref()));
|
|
|
|
auto photo_url = query->arg("photo_url");
|
|
int32 photo_size = get_integer_arg(query.get(), "photo_size", 0, 0, 1000000000);
|
|
int32 photo_width = get_integer_arg(query.get(), "photo_width", 0, 0, MAX_LENGTH);
|
|
int32 photo_height = get_integer_arg(query.get(), "photo_height", 0, 0, MAX_LENGTH);
|
|
|
|
auto need_name = to_bool(query->arg("need_name"));
|
|
auto need_phone_number = to_bool(query->arg("need_phone_number"));
|
|
auto need_email_address = to_bool(query->arg("need_email"));
|
|
auto need_shipping_address = to_bool(query->arg("need_shipping_address"));
|
|
auto send_phone_number_to_provider = to_bool(query->arg("send_phone_number_to_provider"));
|
|
auto send_email_address_to_provider = to_bool(query->arg("send_email_to_provider"));
|
|
auto is_flexible = to_bool(query->arg("is_flexible"));
|
|
|
|
do_send_message(
|
|
make_object<td_api::inputMessageInvoice>(
|
|
make_object<td_api::invoice>(currency.str(), std::move(prices), false, need_name, need_phone_number,
|
|
need_email_address, need_shipping_address, send_phone_number_to_provider,
|
|
send_email_address_to_provider, is_flexible),
|
|
title.str(), description.str(), photo_url.str(), photo_size, photo_width, photo_height, payload.str(),
|
|
provider_token.str(), provider_data.str(), start_parameter.str()),
|
|
std::move(query));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_send_location_query(PromisedQueryPtr &query) {
|
|
TRY_RESULT(location, get_location(query.get()));
|
|
int32 live_period = get_integer_arg(query.get(), "live_period", 0);
|
|
int32 heading = get_integer_arg(query.get(), "heading", 0);
|
|
int32 proximity_alert_radius = get_integer_arg(query.get(), "proximity_alert_radius", 0);
|
|
|
|
do_send_message(
|
|
make_object<td_api::inputMessageLocation>(std::move(location), live_period, heading, proximity_alert_radius),
|
|
std::move(query));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_send_venue_query(PromisedQueryPtr &query) {
|
|
TRY_RESULT(location, get_location(query.get()));
|
|
|
|
auto title = query->arg("title");
|
|
auto address = query->arg("address");
|
|
td::string provider;
|
|
td::string venue_id;
|
|
td::string venue_type;
|
|
|
|
auto google_place_id = query->arg("google_place_id");
|
|
auto google_place_type = query->arg("google_place_type");
|
|
if (!google_place_id.empty() || !google_place_type.empty()) {
|
|
provider = "gplaces";
|
|
venue_id = google_place_id.str();
|
|
venue_type = google_place_type.str();
|
|
}
|
|
auto foursquare_id = query->arg("foursquare_id");
|
|
auto foursquare_type = query->arg("foursquare_type");
|
|
if (!foursquare_id.empty() || !foursquare_type.empty()) {
|
|
provider = "foursquare";
|
|
venue_id = foursquare_id.str();
|
|
venue_type = foursquare_type.str();
|
|
}
|
|
|
|
do_send_message(make_object<td_api::inputMessageVenue>(make_object<td_api::venue>(
|
|
std::move(location), title.str(), address.str(), provider, venue_id, venue_type)),
|
|
std::move(query));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_send_contact_query(PromisedQueryPtr &query) {
|
|
TRY_RESULT(phone_number, get_required_string_arg(query.get(), "phone_number"));
|
|
TRY_RESULT(first_name, get_required_string_arg(query.get(), "first_name"));
|
|
auto last_name = query->arg("last_name");
|
|
auto vcard = query->arg("vcard");
|
|
do_send_message(make_object<td_api::inputMessageContact>(make_object<td_api::contact>(
|
|
phone_number.str(), first_name.str(), last_name.str(), vcard.str(), 0)),
|
|
std::move(query));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_send_poll_query(PromisedQueryPtr &query) {
|
|
auto question = query->arg("question");
|
|
TRY_RESULT(options, get_poll_options(query.get()));
|
|
bool is_anonymous = true;
|
|
if (query->has_arg("is_anonymous")) {
|
|
is_anonymous = to_bool(query->arg("is_anonymous"));
|
|
}
|
|
|
|
object_ptr<td_api::PollType> poll_type;
|
|
auto type = query->arg("type");
|
|
if (type == "quiz") {
|
|
JsonValue entities;
|
|
auto r_value = json_decode(query->arg("explanation_entities"));
|
|
if (r_value.is_ok()) {
|
|
entities = r_value.move_as_ok();
|
|
} else {
|
|
LOG(INFO) << "Can't parse JSON object: " << r_value.error();
|
|
}
|
|
TRY_RESULT(explanation, get_formatted_text(query->arg("explanation").str(),
|
|
query->arg("explanation_parse_mode").str(), std::move(entities)));
|
|
|
|
poll_type = make_object<td_api::pollTypeQuiz>(get_integer_arg(query.get(), "correct_option_id", -1),
|
|
std::move(explanation));
|
|
} else if (type.empty() || type == "regular") {
|
|
poll_type = make_object<td_api::pollTypeRegular>(to_bool(query->arg("allows_multiple_answers")));
|
|
} else {
|
|
return Status::Error(400, "Unsupported poll type specified");
|
|
}
|
|
int32 open_period = get_integer_arg(query.get(), "open_period", 0, 0, 10 * 60);
|
|
int32 close_date = get_integer_arg(query.get(), "close_date", 0);
|
|
auto is_closed = to_bool(query->arg("is_closed"));
|
|
do_send_message(make_object<td_api::inputMessagePoll>(question.str(), std::move(options), is_anonymous,
|
|
std::move(poll_type), open_period, close_date, is_closed),
|
|
std::move(query));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_stop_poll_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
auto message_id = get_message_id(query.get());
|
|
TRY_RESULT(reply_markup, get_reply_markup(query.get()));
|
|
|
|
resolve_reply_markup_bot_usernames(
|
|
std::move(reply_markup), std::move(query),
|
|
[this, chat_id = chat_id.str(), message_id](object_ptr<td_api::ReplyMarkup> reply_markup,
|
|
PromisedQueryPtr query) {
|
|
check_message(chat_id, message_id, false, AccessRights::Edit, "message with poll to stop", std::move(query),
|
|
[this, reply_markup = std::move(reply_markup)](int64 chat_id, int64 message_id,
|
|
PromisedQueryPtr query) mutable {
|
|
send_request(
|
|
make_object<td_api::stopPoll>(chat_id, message_id, std::move(reply_markup)),
|
|
std::make_unique<TdOnStopPollCallback>(this, chat_id, message_id, std::move(query)));
|
|
});
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_copy_message_query(PromisedQueryPtr &query) {
|
|
TRY_RESULT(from_chat_id, get_required_string_arg(query.get(), "from_chat_id"));
|
|
auto message_id = get_message_id(query.get());
|
|
bool replace_caption = query->has_arg("caption");
|
|
td_api::object_ptr<td_api::formattedText> caption;
|
|
if (replace_caption) {
|
|
TRY_RESULT_ASSIGN(caption, get_caption(query.get()));
|
|
}
|
|
auto options = make_object<td_api::messageCopyOptions>(true, replace_caption, std::move(caption));
|
|
|
|
check_message(
|
|
from_chat_id, message_id, false, AccessRights::Read, "message to copy", std::move(query),
|
|
[this, options = std::move(options)](int64 from_chat_id, int64 message_id, PromisedQueryPtr query) mutable {
|
|
do_send_message(make_object<td_api::inputMessageForwarded>(from_chat_id, message_id, false, std::move(options)),
|
|
std::move(query));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_forward_message_query(PromisedQueryPtr &query) {
|
|
TRY_RESULT(from_chat_id, get_required_string_arg(query.get(), "from_chat_id"));
|
|
auto message_id = get_message_id(query.get());
|
|
|
|
check_message(from_chat_id, message_id, false, AccessRights::Read, "message to forward", std::move(query),
|
|
[this](int64 from_chat_id, int64 message_id, PromisedQueryPtr query) {
|
|
do_send_message(make_object<td_api::inputMessageForwarded>(from_chat_id, message_id, false, nullptr),
|
|
std::move(query));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_send_media_group_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
auto reply_to_message_id = get_message_id(query.get(), "reply_to_message_id");
|
|
auto allow_sending_without_reply = to_bool(query->arg("allow_sending_without_reply"));
|
|
auto disable_notification = to_bool(query->arg("disable_notification"));
|
|
// TRY_RESULT(reply_markup, get_reply_markup(query.get()));
|
|
auto reply_markup = nullptr;
|
|
TRY_RESULT(input_message_contents, get_input_message_contents(query.get(), "media"));
|
|
|
|
resolve_reply_markup_bot_usernames(
|
|
std::move(reply_markup), std::move(query),
|
|
[this, chat_id = chat_id.str(), reply_to_message_id, allow_sending_without_reply, disable_notification,
|
|
input_message_contents = std::move(input_message_contents)](object_ptr<td_api::ReplyMarkup> reply_markup,
|
|
PromisedQueryPtr query) mutable {
|
|
auto on_success = [this, disable_notification, input_message_contents = std::move(input_message_contents),
|
|
reply_markup = std::move(reply_markup)](int64 chat_id, int64 reply_to_message_id,
|
|
PromisedQueryPtr query) mutable {
|
|
send_request(make_object<td_api::sendMessageAlbum>(chat_id, 0, reply_to_message_id,
|
|
get_message_send_options(disable_notification),
|
|
std::move(input_message_contents)),
|
|
std::make_unique<TdOnSendMessageAlbumCallback>(this, std::move(query)));
|
|
};
|
|
check_message(chat_id, reply_to_message_id, reply_to_message_id <= 0 || allow_sending_without_reply,
|
|
AccessRights::Write, "reply message", std::move(query), std::move(on_success));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_send_chat_action_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
object_ptr<td_api::ChatAction> action = get_chat_action(query.get());
|
|
if (action == nullptr) {
|
|
return Status::Error(400, "Wrong parameter action in request");
|
|
}
|
|
|
|
check_chat(chat_id, AccessRights::Write, std::move(query),
|
|
[this, action = std::move(action)](int64 chat_id, PromisedQueryPtr query) mutable {
|
|
send_request(make_object<td_api::sendChatAction>(chat_id, 0, std::move(action)),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_edit_message_text_query(PromisedQueryPtr &query) {
|
|
TRY_RESULT(input_message_text, get_input_message_text(query.get()));
|
|
auto chat_id = query->arg("chat_id");
|
|
auto message_id = get_message_id(query.get());
|
|
TRY_RESULT(reply_markup, get_reply_markup(query.get()));
|
|
|
|
if (chat_id.empty() && message_id == 0) {
|
|
TRY_RESULT(inline_message_id, get_inline_message_id(query.get()));
|
|
resolve_reply_markup_bot_usernames(
|
|
std::move(reply_markup), std::move(query),
|
|
[this, inline_message_id = inline_message_id.str(), input_message_text = std::move(input_message_text)](
|
|
object_ptr<td_api::ReplyMarkup> reply_markup, PromisedQueryPtr query) mutable {
|
|
send_request(make_object<td_api::editInlineMessageText>(inline_message_id, std::move(reply_markup),
|
|
std::move(input_message_text)),
|
|
std::make_unique<TdOnEditInlineMessageCallback>(std::move(query)));
|
|
});
|
|
} else {
|
|
resolve_reply_markup_bot_usernames(
|
|
std::move(reply_markup), std::move(query),
|
|
[this, chat_id = chat_id.str(), message_id, input_message_text = std::move(input_message_text)](
|
|
object_ptr<td_api::ReplyMarkup> reply_markup, PromisedQueryPtr query) mutable {
|
|
check_message(
|
|
chat_id, message_id, false, AccessRights::Edit, "message to edit", std::move(query),
|
|
[this, input_message_text = std::move(input_message_text), reply_markup = std::move(reply_markup)](
|
|
int64 chat_id, int64 message_id, PromisedQueryPtr query) mutable {
|
|
send_request(make_object<td_api::editMessageText>(chat_id, message_id, std::move(reply_markup),
|
|
std::move(input_message_text)),
|
|
std::make_unique<TdOnEditMessageCallback>(this, std::move(query)));
|
|
});
|
|
});
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_edit_message_live_location_query(PromisedQueryPtr &query) {
|
|
object_ptr<td_api::location> location = nullptr;
|
|
int32 heading = get_integer_arg(query.get(), "heading", 0);
|
|
int32 proximity_alert_radius = get_integer_arg(query.get(), "proximity_alert_radius", 0);
|
|
if (query->method() == "editmessagelivelocation") {
|
|
TRY_RESULT_ASSIGN(location, get_location(query.get()));
|
|
}
|
|
auto chat_id = query->arg("chat_id");
|
|
auto message_id = get_message_id(query.get());
|
|
TRY_RESULT(reply_markup, get_reply_markup(query.get()));
|
|
|
|
if (chat_id.empty() && message_id == 0) {
|
|
TRY_RESULT(inline_message_id, get_inline_message_id(query.get()));
|
|
resolve_reply_markup_bot_usernames(
|
|
std::move(reply_markup), std::move(query),
|
|
[this, inline_message_id = inline_message_id.str(), location = std::move(location), heading,
|
|
proximity_alert_radius](object_ptr<td_api::ReplyMarkup> reply_markup, PromisedQueryPtr query) mutable {
|
|
send_request(
|
|
make_object<td_api::editInlineMessageLiveLocation>(inline_message_id, std::move(reply_markup),
|
|
std::move(location), heading, proximity_alert_radius),
|
|
std::make_unique<TdOnEditInlineMessageCallback>(std::move(query)));
|
|
});
|
|
} else {
|
|
resolve_reply_markup_bot_usernames(
|
|
std::move(reply_markup), std::move(query),
|
|
[this, chat_id = chat_id.str(), message_id, location = std::move(location), heading, proximity_alert_radius](
|
|
object_ptr<td_api::ReplyMarkup> reply_markup, PromisedQueryPtr query) mutable {
|
|
check_message(chat_id, message_id, false, AccessRights::Edit, "message to edit", std::move(query),
|
|
[this, location = std::move(location), heading, proximity_alert_radius,
|
|
reply_markup = std::move(reply_markup)](int64 chat_id, int64 message_id,
|
|
PromisedQueryPtr query) mutable {
|
|
send_request(make_object<td_api::editMessageLiveLocation>(
|
|
chat_id, message_id, std::move(reply_markup), std::move(location), heading,
|
|
proximity_alert_radius),
|
|
std::make_unique<TdOnEditMessageCallback>(this, std::move(query)));
|
|
});
|
|
});
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_edit_message_media_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
auto message_id = get_message_id(query.get());
|
|
TRY_RESULT(reply_markup, get_reply_markup(query.get()));
|
|
TRY_RESULT(input_media, get_input_media(query.get(), "media", false));
|
|
|
|
if (chat_id.empty() && message_id == 0) {
|
|
TRY_RESULT(inline_message_id, get_inline_message_id(query.get()));
|
|
resolve_reply_markup_bot_usernames(
|
|
std::move(reply_markup), std::move(query),
|
|
[this, inline_message_id = inline_message_id.str(), input_message_content = std::move(input_media)](
|
|
object_ptr<td_api::ReplyMarkup> reply_markup, PromisedQueryPtr query) mutable {
|
|
send_request(make_object<td_api::editInlineMessageMedia>(inline_message_id, std::move(reply_markup),
|
|
std::move(input_message_content)),
|
|
std::make_unique<TdOnEditInlineMessageCallback>(std::move(query)));
|
|
});
|
|
} else {
|
|
resolve_reply_markup_bot_usernames(
|
|
std::move(reply_markup), std::move(query),
|
|
[this, chat_id = chat_id.str(), message_id, input_message_content = std::move(input_media)](
|
|
object_ptr<td_api::ReplyMarkup> reply_markup, PromisedQueryPtr query) mutable {
|
|
check_message(
|
|
chat_id, message_id, false, AccessRights::Edit, "message to edit", std::move(query),
|
|
[this, reply_markup = std::move(reply_markup), input_message_content = std::move(input_message_content)](
|
|
int64 chat_id, int64 message_id, PromisedQueryPtr query) mutable {
|
|
send_request(make_object<td_api::editMessageMedia>(chat_id, message_id, std::move(reply_markup),
|
|
std::move(input_message_content)),
|
|
std::make_unique<TdOnEditMessageCallback>(this, std::move(query)));
|
|
});
|
|
});
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_edit_message_caption_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
auto message_id = get_message_id(query.get());
|
|
TRY_RESULT(reply_markup, get_reply_markup(query.get()));
|
|
TRY_RESULT(caption, get_caption(query.get()));
|
|
|
|
if (chat_id.empty() && message_id == 0) {
|
|
TRY_RESULT(inline_message_id, get_inline_message_id(query.get()));
|
|
resolve_reply_markup_bot_usernames(
|
|
std::move(reply_markup), std::move(query),
|
|
[this, inline_message_id = inline_message_id.str(), caption = std::move(caption)](
|
|
object_ptr<td_api::ReplyMarkup> reply_markup, PromisedQueryPtr query) mutable {
|
|
send_request(make_object<td_api::editInlineMessageCaption>(inline_message_id, std::move(reply_markup),
|
|
std::move(caption)),
|
|
std::make_unique<TdOnEditInlineMessageCallback>(std::move(query)));
|
|
});
|
|
} else {
|
|
resolve_reply_markup_bot_usernames(
|
|
std::move(reply_markup), std::move(query),
|
|
[this, chat_id = chat_id.str(), message_id, caption = std::move(caption)](
|
|
object_ptr<td_api::ReplyMarkup> reply_markup, PromisedQueryPtr query) mutable {
|
|
check_message(chat_id, message_id, false, AccessRights::Edit, "message to edit", std::move(query),
|
|
[this, reply_markup = std::move(reply_markup), caption = std::move(caption)](
|
|
int64 chat_id, int64 message_id, PromisedQueryPtr query) mutable {
|
|
send_request(make_object<td_api::editMessageCaption>(
|
|
chat_id, message_id, std::move(reply_markup), std::move(caption)),
|
|
std::make_unique<TdOnEditMessageCallback>(this, std::move(query)));
|
|
});
|
|
});
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_edit_message_reply_markup_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
auto message_id = get_message_id(query.get());
|
|
TRY_RESULT(reply_markup, get_reply_markup(query.get()));
|
|
|
|
if (chat_id.empty() && message_id == 0) {
|
|
TRY_RESULT(inline_message_id, get_inline_message_id(query.get()));
|
|
resolve_reply_markup_bot_usernames(
|
|
std::move(reply_markup), std::move(query),
|
|
[this, inline_message_id = inline_message_id.str()](object_ptr<td_api::ReplyMarkup> reply_markup,
|
|
PromisedQueryPtr query) {
|
|
send_request(make_object<td_api::editInlineMessageReplyMarkup>(inline_message_id, std::move(reply_markup)),
|
|
std::make_unique<TdOnEditInlineMessageCallback>(std::move(query)));
|
|
});
|
|
} else {
|
|
resolve_reply_markup_bot_usernames(
|
|
std::move(reply_markup), std::move(query),
|
|
[this, chat_id = chat_id.str(), message_id](object_ptr<td_api::ReplyMarkup> reply_markup,
|
|
PromisedQueryPtr query) {
|
|
check_message(chat_id, message_id, false, AccessRights::Edit, "message to edit", std::move(query),
|
|
[this, reply_markup = std::move(reply_markup)](int64 chat_id, int64 message_id,
|
|
PromisedQueryPtr query) mutable {
|
|
send_request(
|
|
make_object<td_api::editMessageReplyMarkup>(chat_id, message_id, std::move(reply_markup)),
|
|
std::make_unique<TdOnEditMessageCallback>(this, std::move(query)));
|
|
});
|
|
});
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_delete_message_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
auto message_id = get_message_id(query.get());
|
|
|
|
if (chat_id.empty()) {
|
|
return Status::Error(400, "Chat identifier is not specified");
|
|
}
|
|
|
|
if (message_id == 0) {
|
|
return Status::Error(400, "Message identifier is not specified");
|
|
}
|
|
|
|
check_message(chat_id, message_id, false, AccessRights::Write, "message to delete", std::move(query),
|
|
[this](int64 chat_id, int64 message_id, PromisedQueryPtr query) {
|
|
delete_message(chat_id, message_id, false);
|
|
send_request(make_object<td_api::deleteMessages>(chat_id, td::vector<int64>{message_id}, true),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_set_game_score_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
auto message_id = get_message_id(query.get());
|
|
TRY_RESULT(user_id, get_user_id(query.get()));
|
|
auto score = td::to_integer<int32>(query->arg("score"));
|
|
auto force = to_bool(query->arg("force"));
|
|
bool edit_message = true;
|
|
if (query->has_arg("disable_edit_message")) {
|
|
edit_message = !to_bool(query->arg("disable_edit_message"));
|
|
} else if (query->has_arg("edit_message")) {
|
|
edit_message = to_bool(query->arg("edit_message"));
|
|
}
|
|
|
|
if (chat_id.empty() && message_id == 0) {
|
|
TRY_RESULT(inline_message_id, get_inline_message_id(query.get()));
|
|
check_user_no_fail(
|
|
user_id, std::move(query),
|
|
[this, inline_message_id = inline_message_id.str(), edit_message, user_id, score,
|
|
force](PromisedQueryPtr query) {
|
|
send_request(make_object<td_api::setInlineGameScore>(inline_message_id, edit_message, user_id, score, force),
|
|
std::make_unique<TdOnEditInlineMessageCallback>(std::move(query)));
|
|
});
|
|
} else {
|
|
check_message(chat_id, message_id, false, AccessRights::Edit, "message to set game score", std::move(query),
|
|
[this, user_id, score, force, edit_message](int64 chat_id, int64 message_id, PromisedQueryPtr query) {
|
|
check_user_no_fail(
|
|
user_id, std::move(query),
|
|
[this, chat_id, message_id, user_id, score, force, edit_message](PromisedQueryPtr query) {
|
|
send_request(make_object<td_api::setGameScore>(chat_id, message_id, edit_message, user_id,
|
|
score, force),
|
|
std::make_unique<TdOnEditMessageCallback>(this, std::move(query)));
|
|
});
|
|
});
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_get_game_high_scores_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
auto message_id = get_message_id(query.get());
|
|
TRY_RESULT(user_id, get_user_id(query.get()));
|
|
|
|
if (chat_id.empty() && message_id == 0) {
|
|
TRY_RESULT(inline_message_id, get_inline_message_id(query.get()));
|
|
check_user_no_fail(user_id, std::move(query),
|
|
[this, inline_message_id = inline_message_id.str(), user_id](PromisedQueryPtr query) {
|
|
send_request(make_object<td_api::getInlineGameHighScores>(inline_message_id, user_id),
|
|
std::make_unique<TdOnGetGameHighScoresCallback>(this, std::move(query)));
|
|
});
|
|
} else {
|
|
check_message(chat_id, message_id, false, AccessRights::Read, "message to get game high scores", std::move(query),
|
|
[this, user_id](int64 chat_id, int64 message_id, PromisedQueryPtr query) {
|
|
check_user_no_fail(
|
|
user_id, std::move(query), [this, chat_id, message_id, user_id](PromisedQueryPtr query) {
|
|
send_request(make_object<td_api::getGameHighScores>(chat_id, message_id, user_id),
|
|
std::make_unique<TdOnGetGameHighScoresCallback>(this, std::move(query)));
|
|
});
|
|
});
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_answer_inline_query_query(PromisedQueryPtr &query) {
|
|
auto inline_query_id = td::to_integer<int64>(query->arg("inline_query_id"));
|
|
auto is_personal = to_bool(query->arg("is_personal"));
|
|
int32 cache_time = get_integer_arg(query.get(), "cache_time", 300, 0, 24 * 60 * 60);
|
|
auto next_offset = query->arg("next_offset");
|
|
auto switch_pm_text = query->arg("switch_pm_text");
|
|
auto switch_pm_parameter = query->arg("switch_pm_parameter");
|
|
|
|
TRY_RESULT(results, get_inline_query_results(query.get()));
|
|
|
|
resolve_inline_query_results_bot_usernames(
|
|
std::move(results), std::move(query),
|
|
[this, inline_query_id, is_personal, cache_time, next_offset = next_offset.str(),
|
|
switch_pm_text = switch_pm_text.str(), switch_pm_parameter = switch_pm_parameter.str()](
|
|
td::vector<object_ptr<td_api::InputInlineQueryResult>> results, PromisedQueryPtr query) {
|
|
send_request(
|
|
make_object<td_api::answerInlineQuery>(inline_query_id, is_personal, std::move(results), cache_time,
|
|
next_offset, switch_pm_text, switch_pm_parameter),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_answer_callback_query_query(PromisedQueryPtr &query) {
|
|
auto callback_query_id = td::to_integer<int64>(query->arg("callback_query_id"));
|
|
td::string text = query->arg("text").str();
|
|
bool show_alert = to_bool(query->arg("show_alert"));
|
|
td::string url = query->arg("url").str();
|
|
int32 cache_time = get_integer_arg(query.get(), "cache_time", 0, 0, 24 * 30 * 60 * 60);
|
|
|
|
send_request(make_object<td_api::answerCallbackQuery>(callback_query_id, text, show_alert, url, cache_time),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_answer_shipping_query_query(PromisedQueryPtr &query) {
|
|
auto shipping_query_id = td::to_integer<int64>(query->arg("shipping_query_id"));
|
|
auto ok = to_bool(query->arg("ok"));
|
|
td::vector<object_ptr<td_api::shippingOption>> shipping_options;
|
|
td::MutableSlice error_message;
|
|
if (ok) {
|
|
TRY_RESULT_ASSIGN(shipping_options, get_shipping_options(query.get()));
|
|
} else {
|
|
TRY_RESULT_ASSIGN(error_message, get_required_string_arg(query.get(), "error_message"));
|
|
}
|
|
send_request(
|
|
make_object<td_api::answerShippingQuery>(shipping_query_id, std::move(shipping_options), error_message.str()),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_answer_pre_checkout_query_query(PromisedQueryPtr &query) {
|
|
auto pre_checkout_query_id = td::to_integer<int64>(query->arg("pre_checkout_query_id"));
|
|
auto ok = to_bool(query->arg("ok"));
|
|
td::MutableSlice error_message;
|
|
if (!ok) {
|
|
TRY_RESULT_ASSIGN(error_message, get_required_string_arg(query.get(), "error_message"));
|
|
}
|
|
|
|
send_request(make_object<td_api::answerPreCheckoutQuery>(pre_checkout_query_id, error_message.str()),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_export_chat_invite_link_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
|
|
check_chat(chat_id, AccessRights::Write, std::move(query), [this](int64 chat_id, PromisedQueryPtr query) {
|
|
send_request(make_object<td_api::generateChatInviteLink>(chat_id),
|
|
std::make_unique<TdOnGenerateChatInviteLinkCallback>(std::move(query)));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_get_chat_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
|
|
check_chat(chat_id, AccessRights::Read, std::move(query), [this](int64 chat_id, PromisedQueryPtr query) {
|
|
auto chat_info = get_chat(chat_id);
|
|
CHECK(chat_info != nullptr);
|
|
switch (chat_info->type) {
|
|
case ChatInfo::Type::Private:
|
|
return send_request(make_object<td_api::getUserFullInfo>(chat_info->user_id),
|
|
std::make_unique<TdOnGetChatFullInfoCallback>(this, chat_id, std::move(query)));
|
|
case ChatInfo::Type::Group:
|
|
return send_request(make_object<td_api::getBasicGroupFullInfo>(chat_info->group_id),
|
|
std::make_unique<TdOnGetChatFullInfoCallback>(this, chat_id, std::move(query)));
|
|
case ChatInfo::Type::Supergroup:
|
|
return send_request(make_object<td_api::getSupergroupFullInfo>(chat_info->supergroup_id),
|
|
std::make_unique<TdOnGetChatFullInfoCallback>(this, chat_id, std::move(query)));
|
|
case ChatInfo::Type::Unknown:
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_set_chat_photo_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
auto photo = get_input_file(query.get(), "photo", true);
|
|
if (photo == nullptr) {
|
|
if (query->arg("photo").empty()) {
|
|
return Status::Error(400, "There is no photo in the request");
|
|
}
|
|
return Status::Error(400, "Photo must be uploaded as an InputFile");
|
|
}
|
|
|
|
check_chat(chat_id, AccessRights::Write, std::move(query),
|
|
[this, photo = std::move(photo)](int64 chat_id, PromisedQueryPtr query) mutable {
|
|
send_request(make_object<td_api::setChatPhoto>(
|
|
chat_id, make_object<td_api::inputChatPhotoStatic>(std::move(photo))),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_delete_chat_photo_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
|
|
check_chat(chat_id, AccessRights::Write, std::move(query), [this](int64 chat_id, PromisedQueryPtr query) {
|
|
send_request(make_object<td_api::setChatPhoto>(chat_id, nullptr),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_set_chat_title_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
auto title = query->arg("title");
|
|
|
|
check_chat(chat_id, AccessRights::Write, std::move(query),
|
|
[this, title = title.str()](int64 chat_id, PromisedQueryPtr query) {
|
|
send_request(make_object<td_api::setChatTitle>(chat_id, title),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_set_chat_permissions_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
bool allow_legacy = false;
|
|
TRY_RESULT(permissions, get_chat_permissions(query.get(), allow_legacy));
|
|
CHECK(!allow_legacy);
|
|
|
|
check_chat(chat_id, AccessRights::Write, std::move(query),
|
|
[this, permissions = std::move(permissions)](int64 chat_id, PromisedQueryPtr query) mutable {
|
|
send_request(make_object<td_api::setChatPermissions>(chat_id, std::move(permissions)),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_set_chat_description_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
auto description = query->arg("description");
|
|
|
|
check_chat(chat_id, AccessRights::Write, std::move(query),
|
|
[this, description = description.str()](int64 chat_id, PromisedQueryPtr query) {
|
|
send_request(make_object<td_api::setChatDescription>(chat_id, description),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_pin_chat_message_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
auto message_id = get_message_id(query.get());
|
|
auto disable_notification = to_bool(query->arg("disable_notification"));
|
|
|
|
check_message(chat_id, message_id, false, AccessRights::Write, "message to pin", std::move(query),
|
|
[this, disable_notification](int64 chat_id, int64 message_id, PromisedQueryPtr query) {
|
|
send_request(make_object<td_api::pinChatMessage>(chat_id, message_id, disable_notification, false),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_unpin_chat_message_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
auto message_id = get_message_id(query.get());
|
|
|
|
if (message_id == 0) {
|
|
check_chat(chat_id, AccessRights::Write, std::move(query), [this](int64 chat_id, PromisedQueryPtr query) {
|
|
send_request(make_object<td_api::getChatPinnedMessage>(chat_id),
|
|
std::make_unique<TdOnGetChatPinnedMessageToUnpinCallback>(this, chat_id, std::move(query)));
|
|
});
|
|
} else {
|
|
check_message(chat_id, message_id, false, AccessRights::Write, "message to unpin", std::move(query),
|
|
[this](int64 chat_id, int64 message_id, PromisedQueryPtr query) {
|
|
send_request(make_object<td_api::unpinChatMessage>(chat_id, message_id),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_unpin_all_chat_messages_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
|
|
check_chat(chat_id, AccessRights::Write, std::move(query), [this](int64 chat_id, PromisedQueryPtr query) {
|
|
send_request(make_object<td_api::unpinAllChatMessages>(chat_id),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_set_chat_sticker_set_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
auto sticker_set_name = query->arg("sticker_set_name");
|
|
|
|
check_chat(chat_id, AccessRights::Write, std::move(query),
|
|
[this, sticker_set_name = sticker_set_name.str()](int64 chat_id, PromisedQueryPtr query) {
|
|
if (get_chat_type(chat_id) != ChatType::Supergroup) {
|
|
return fail_query(400, "Bad Request: method is available only for supergroups", std::move(query));
|
|
}
|
|
|
|
resolve_sticker_set(
|
|
sticker_set_name, std::move(query), [this, chat_id](int64 sticker_set_id, PromisedQueryPtr query) {
|
|
auto chat_info = get_chat(chat_id);
|
|
CHECK(chat_info != nullptr);
|
|
CHECK(chat_info->type == ChatInfo::Type::Supergroup);
|
|
send_request(
|
|
make_object<td_api::setSupergroupStickerSet>(chat_info->supergroup_id, sticker_set_id),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_delete_chat_sticker_set_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
|
|
check_chat(chat_id, AccessRights::Write, std::move(query), [this](int64 chat_id, PromisedQueryPtr query) {
|
|
if (get_chat_type(chat_id) != ChatType::Supergroup) {
|
|
return fail_query(400, "Bad Request: method is available only for supergroups", std::move(query));
|
|
}
|
|
|
|
auto chat_info = get_chat(chat_id);
|
|
CHECK(chat_info != nullptr);
|
|
CHECK(chat_info->type == ChatInfo::Type::Supergroup);
|
|
send_request(make_object<td_api::setSupergroupStickerSet>(chat_info->supergroup_id, 0),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_get_chat_member_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
TRY_RESULT(user_id, get_user_id(query.get()));
|
|
|
|
check_chat(chat_id, AccessRights::Read, std::move(query), [this, user_id](int64 chat_id, PromisedQueryPtr query) {
|
|
get_chat_member(chat_id, user_id, std::move(query),
|
|
[this, chat_type = get_chat_type(chat_id)](td_api::object_ptr<td_api::chatMember> &&chat_member,
|
|
PromisedQueryPtr query) {
|
|
answer_query(JsonChatMember(chat_member.get(), chat_type, this), std::move(query));
|
|
});
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_get_chat_administrators_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
|
|
check_chat(chat_id, AccessRights::Read, std::move(query), [this](int64 chat_id, PromisedQueryPtr query) {
|
|
auto chat_info = get_chat(chat_id);
|
|
CHECK(chat_info != nullptr);
|
|
switch (chat_info->type) {
|
|
case ChatInfo::Type::Private:
|
|
return fail_query(400, "Bad Request: there are no administrators in the private chat", std::move(query));
|
|
case ChatInfo::Type::Group: {
|
|
auto group_info = get_group_info(chat_info->group_id);
|
|
CHECK(group_info != nullptr);
|
|
return send_request(make_object<td_api::getBasicGroupFullInfo>(chat_info->group_id),
|
|
std::make_unique<TdOnGetGroupMembersCallback>(this, true, std::move(query)));
|
|
}
|
|
case ChatInfo::Type::Supergroup:
|
|
return send_request(
|
|
make_object<td_api::getSupergroupMembers>(
|
|
chat_info->supergroup_id, make_object<td_api::supergroupMembersFilterAdministrators>(), 0, 100),
|
|
std::make_unique<TdOnGetSupergroupMembersCallback>(this, get_chat_type(chat_id), std::move(query)));
|
|
case ChatInfo::Type::Unknown:
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_get_chat_member_count_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
|
|
check_chat(chat_id, AccessRights::Read, std::move(query), [this](int64 chat_id, PromisedQueryPtr query) {
|
|
auto chat_info = get_chat(chat_id);
|
|
CHECK(chat_info != nullptr);
|
|
switch (chat_info->type) {
|
|
case ChatInfo::Type::Private:
|
|
return answer_query(td::VirtuallyJsonableInt(1 + (chat_info->user_id != my_id_)), std::move(query));
|
|
case ChatInfo::Type::Group: {
|
|
auto group_info = get_group_info(chat_info->group_id);
|
|
CHECK(group_info != nullptr);
|
|
if (group_info->member_count == 0) {
|
|
return fail_query(403, "Forbidden: bot is not a member of the group chat", std::move(query));
|
|
}
|
|
return answer_query(td::VirtuallyJsonableInt(group_info->member_count), std::move(query));
|
|
}
|
|
case ChatInfo::Type::Supergroup:
|
|
return send_request(make_object<td_api::getSupergroupFullInfo>(chat_info->supergroup_id),
|
|
std::make_unique<TdOnGetSupergroupMembersCountCallback>(std::move(query)));
|
|
case ChatInfo::Type::Unknown:
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_leave_chat_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
|
|
check_chat(chat_id, AccessRights::Read, std::move(query), [this](int64 chat_id, PromisedQueryPtr query) {
|
|
send_request(make_object<td_api::leaveChat>(chat_id), std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_promote_chat_member_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
TRY_RESULT(user_id, get_user_id(query.get()));
|
|
auto can_change_info = to_bool(query->arg("can_change_info"));
|
|
auto can_post_messages = to_bool(query->arg("can_post_messages"));
|
|
auto can_edit_messages = to_bool(query->arg("can_edit_messages"));
|
|
auto can_delete_messages = to_bool(query->arg("can_delete_messages"));
|
|
auto can_invite_users = to_bool(query->arg("can_invite_users"));
|
|
auto can_restrict_members = to_bool(query->arg("can_restrict_members"));
|
|
auto can_pin_messages = to_bool(query->arg("can_pin_messages"));
|
|
auto can_promote_members = to_bool(query->arg("can_promote_members"));
|
|
auto is_anonymous = to_bool(query->arg("is_anonymous"));
|
|
auto status = make_object<td_api::chatMemberStatusAdministrator>(
|
|
td::string(), true, can_change_info, can_post_messages, can_edit_messages, can_delete_messages, can_invite_users,
|
|
can_restrict_members, can_pin_messages, can_promote_members, is_anonymous);
|
|
check_chat(chat_id, AccessRights::Write, std::move(query),
|
|
[this, user_id, status = std::move(status)](int64 chat_id, PromisedQueryPtr query) mutable {
|
|
auto chat_info = get_chat(chat_id);
|
|
CHECK(chat_info != nullptr);
|
|
if (chat_info->type != ChatInfo::Type::Supergroup) {
|
|
return fail_query(400, "Bad Request: method is available for supergroup and channel chats only",
|
|
std::move(query));
|
|
}
|
|
|
|
get_chat_member(
|
|
chat_id, user_id, std::move(query),
|
|
[this, chat_id, user_id, status = std::move(status)](
|
|
td_api::object_ptr<td_api::chatMember> &&chat_member, PromisedQueryPtr query) mutable {
|
|
if (chat_member->status_->get_id() == td_api::chatMemberStatusAdministrator::ID) {
|
|
auto administrator =
|
|
static_cast<const td_api::chatMemberStatusAdministrator *>(chat_member->status_.get());
|
|
status->custom_title_ = std::move(administrator->custom_title_);
|
|
}
|
|
|
|
send_request(make_object<td_api::setChatMemberStatus>(chat_id, user_id, std::move(status)),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_set_chat_administrator_custom_title_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
TRY_RESULT(user_id, get_user_id(query.get()));
|
|
|
|
check_chat(chat_id, AccessRights::Write, std::move(query), [this, user_id](int64 chat_id, PromisedQueryPtr query) {
|
|
if (get_chat_type(chat_id) != ChatType::Supergroup) {
|
|
return fail_query(400, "Bad Request: method is available only for supergroups", std::move(query));
|
|
}
|
|
|
|
get_chat_member(
|
|
chat_id, user_id, std::move(query),
|
|
[this, chat_id, user_id](td_api::object_ptr<td_api::chatMember> &&chat_member, PromisedQueryPtr query) {
|
|
if (chat_member->status_->get_id() == td_api::chatMemberStatusCreator::ID) {
|
|
return fail_query(400, "Bad Request: only creator can edit their custom title", std::move(query));
|
|
}
|
|
if (chat_member->status_->get_id() != td_api::chatMemberStatusAdministrator::ID) {
|
|
return fail_query(400, "Bad Request: user is not an administrator", std::move(query));
|
|
}
|
|
auto administrator = td_api::move_object_as<td_api::chatMemberStatusAdministrator>(chat_member->status_);
|
|
if (!administrator->can_be_edited_) {
|
|
return fail_query(400, "Bad Request: not enough rights to change custom title of the user",
|
|
std::move(query));
|
|
}
|
|
administrator->custom_title_ = query->arg("custom_title").str();
|
|
|
|
send_request(make_object<td_api::setChatMemberStatus>(chat_id, user_id, std::move(administrator)),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_ban_chat_member_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
TRY_RESULT(user_id, get_user_id(query.get()));
|
|
int32 until_date = get_integer_arg(query.get(), "until_date", 0);
|
|
|
|
check_chat(chat_id, AccessRights::Write, std::move(query),
|
|
[this, user_id, until_date](int64 chat_id, PromisedQueryPtr query) {
|
|
check_user_no_fail(
|
|
user_id, std::move(query), [this, chat_id, user_id, until_date](PromisedQueryPtr query) {
|
|
send_request(make_object<td_api::setChatMemberStatus>(
|
|
chat_id, user_id, make_object<td_api::chatMemberStatusBanned>(until_date)),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_restrict_chat_member_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
TRY_RESULT(user_id, get_user_id(query.get()));
|
|
int32 until_date = get_integer_arg(query.get(), "until_date", 0);
|
|
bool allow_legacy = true;
|
|
TRY_RESULT(permissions, get_chat_permissions(query.get(), allow_legacy));
|
|
|
|
check_chat(chat_id, AccessRights::Write, std::move(query),
|
|
[this, user_id, until_date, is_legacy = allow_legacy, permissions = std::move(permissions)](
|
|
int64 chat_id, PromisedQueryPtr query) mutable {
|
|
if (get_chat_type(chat_id) != ChatType::Supergroup) {
|
|
return fail_query(400, "Bad Request: method is available only for supergroups", std::move(query));
|
|
}
|
|
|
|
get_chat_member(
|
|
chat_id, user_id, std::move(query),
|
|
[this, chat_id, user_id, until_date, is_legacy, permissions = std::move(permissions)](
|
|
td_api::object_ptr<td_api::chatMember> &&chat_member, PromisedQueryPtr query) mutable {
|
|
if (is_legacy && chat_member->status_->get_id() == td_api::chatMemberStatusRestricted::ID) {
|
|
auto restricted =
|
|
static_cast<const td_api::chatMemberStatusRestricted *>(chat_member->status_.get());
|
|
auto *old_permissions = restricted->permissions_.get();
|
|
permissions->can_send_polls_ = old_permissions->can_send_polls_;
|
|
permissions->can_change_info_ = old_permissions->can_change_info_;
|
|
permissions->can_invite_users_ = old_permissions->can_invite_users_;
|
|
permissions->can_pin_messages_ = old_permissions->can_pin_messages_;
|
|
}
|
|
|
|
send_request(make_object<td_api::setChatMemberStatus>(
|
|
chat_id, user_id,
|
|
make_object<td_api::chatMemberStatusRestricted>(
|
|
is_chat_member(chat_member->status_), until_date, std::move(permissions))),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_unban_chat_member_query(PromisedQueryPtr &query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
TRY_RESULT(user_id, get_user_id(query.get()));
|
|
auto only_if_banned = to_bool(query->arg("only_if_banned"));
|
|
|
|
check_chat(chat_id, AccessRights::Write, std::move(query),
|
|
[this, user_id, only_if_banned](int64 chat_id, PromisedQueryPtr query) {
|
|
auto chat_info = get_chat(chat_id);
|
|
CHECK(chat_info != nullptr);
|
|
if (chat_info->type != ChatInfo::Type::Supergroup) {
|
|
return fail_query(400, "Bad Request: method is available for supergroup and channel chats only",
|
|
std::move(query));
|
|
}
|
|
|
|
if (only_if_banned) {
|
|
get_chat_member(chat_id, user_id, std::move(query),
|
|
[this, chat_id, user_id](td_api::object_ptr<td_api::chatMember> &&chat_member,
|
|
PromisedQueryPtr query) {
|
|
if (chat_member->status_->get_id() != td_api::chatMemberStatusBanned::ID) {
|
|
return answer_query(td::JsonTrue(), std::move(query));
|
|
}
|
|
|
|
send_request(make_object<td_api::setChatMemberStatus>(
|
|
chat_id, user_id, make_object<td_api::chatMemberStatusLeft>()),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
} else {
|
|
check_user_no_fail(user_id, std::move(query), [this, chat_id, user_id](PromisedQueryPtr query) {
|
|
send_request(make_object<td_api::setChatMemberStatus>(chat_id, user_id,
|
|
make_object<td_api::chatMemberStatusLeft>()),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
}
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_get_sticker_set_query(PromisedQueryPtr &query) {
|
|
auto name = query->arg("name");
|
|
if (td::trim(to_lower(name)) == to_lower(GREAT_MINDS_SET_NAME)) {
|
|
send_request(make_object<td_api::getStickerSet>(GREAT_MINDS_SET_ID),
|
|
std::make_unique<TdOnReturnStickerSetCallback>(this, true, std::move(query)));
|
|
} else {
|
|
send_request(make_object<td_api::searchStickerSet>(name.str()),
|
|
std::make_unique<TdOnReturnStickerSetCallback>(this, true, std::move(query)));
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_upload_sticker_file_query(PromisedQueryPtr &query) {
|
|
TRY_RESULT(user_id, get_user_id(query.get()));
|
|
auto png_sticker = get_input_file(query.get(), "png_sticker");
|
|
|
|
check_user(user_id, std::move(query),
|
|
[this, user_id, png_sticker = std::move(png_sticker)](PromisedQueryPtr query) mutable {
|
|
send_request(make_object<td_api::uploadStickerFile>(user_id, std::move(png_sticker)),
|
|
std::make_unique<TdOnReturnFileCallback>(this, std::move(query)));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_create_new_sticker_set_query(PromisedQueryPtr &query) {
|
|
TRY_RESULT(user_id, get_user_id(query.get()));
|
|
auto name = query->arg("name");
|
|
auto title = query->arg("title");
|
|
auto is_masks = to_bool(query->arg("contains_masks"));
|
|
TRY_RESULT(stickers, get_input_stickers(query.get()));
|
|
|
|
check_user(user_id, std::move(query),
|
|
[this, user_id, title, name, is_masks, stickers = std::move(stickers)](PromisedQueryPtr query) mutable {
|
|
send_request(make_object<td_api::createNewStickerSet>(user_id, title.str(), name.str(), is_masks,
|
|
std::move(stickers)),
|
|
std::make_unique<TdOnReturnStickerSetCallback>(this, false, std::move(query)));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_add_sticker_to_set_query(PromisedQueryPtr &query) {
|
|
TRY_RESULT(user_id, get_user_id(query.get()));
|
|
auto name = query->arg("name");
|
|
TRY_RESULT(stickers, get_input_stickers(query.get()));
|
|
CHECK(!stickers.empty());
|
|
|
|
check_user(user_id, std::move(query),
|
|
[this, user_id, name, sticker = std::move(stickers[0])](PromisedQueryPtr query) mutable {
|
|
send_request(make_object<td_api::addStickerToSet>(user_id, name.str(), std::move(sticker)),
|
|
std::make_unique<TdOnReturnStickerSetCallback>(this, false, std::move(query)));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_set_sticker_set_thumb_query(PromisedQueryPtr &query) {
|
|
TRY_RESULT(user_id, get_user_id(query.get()));
|
|
auto name = query->arg("name");
|
|
auto thumbnail = get_input_file(query.get(), "thumb");
|
|
check_user(user_id, std::move(query),
|
|
[this, user_id, name, thumbnail = std::move(thumbnail)](PromisedQueryPtr query) mutable {
|
|
send_request(make_object<td_api::setStickerSetThumbnail>(user_id, name.str(), std::move(thumbnail)),
|
|
std::make_unique<TdOnReturnStickerSetCallback>(this, false, std::move(query)));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_set_sticker_position_in_set_query(PromisedQueryPtr &query) {
|
|
auto file_id = trim(query->arg("sticker"));
|
|
if (file_id.empty()) {
|
|
return Status::Error(400, "Sticker is not specified");
|
|
}
|
|
int32 position = get_integer_arg(query.get(), "position", -1);
|
|
|
|
send_request(
|
|
make_object<td_api::setStickerPositionInSet>(make_object<td_api::inputFileRemote>(file_id.str()), position),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_delete_sticker_from_set_query(PromisedQueryPtr &query) {
|
|
auto file_id = trim(query->arg("sticker"));
|
|
if (file_id.empty()) {
|
|
return Status::Error(400, "Sticker is not specified");
|
|
}
|
|
|
|
send_request(make_object<td_api::removeStickerFromSet>(make_object<td_api::inputFileRemote>(file_id.str())),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_set_passport_data_errors_query(PromisedQueryPtr &query) {
|
|
TRY_RESULT(user_id, get_user_id(query.get()));
|
|
TRY_RESULT(passport_element_errors, get_passport_element_errors(query.get()));
|
|
|
|
check_user(user_id, std::move(query),
|
|
[this, user_id, errors = std::move(passport_element_errors)](PromisedQueryPtr query) mutable {
|
|
send_request(make_object<td_api::setPassportElementErrors>(user_id, std::move(errors)),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_send_custom_request_query(PromisedQueryPtr &query) {
|
|
TRY_RESULT(method, get_required_string_arg(query.get(), "method"));
|
|
auto parameters = query->arg("parameters");
|
|
send_request(make_object<td_api::sendCustomRequest>(method.str(), parameters.str()),
|
|
std::make_unique<TdOnSendCustomRequestCallback>(std::move(query)));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_answer_custom_query_query(PromisedQueryPtr &query) {
|
|
auto custom_query_id = td::to_integer<int64>(query->arg("custom_query_id"));
|
|
auto data = query->arg("data");
|
|
send_request(make_object<td_api::answerCustomQuery>(custom_query_id, data.str()),
|
|
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_get_updates_query(PromisedQueryPtr &query) {
|
|
if (!webhook_url_.empty() || webhook_set_query_) {
|
|
fail_query_conflict(
|
|
"Conflict: can't use getUpdates method while webhook is active; use deleteWebhook to delete the webhook first",
|
|
std::move(query));
|
|
return Status::OK();
|
|
}
|
|
int32 offset = get_integer_arg(query.get(), "offset", 0);
|
|
int32 limit = get_integer_arg(query.get(), "limit", 100, 1, 100);
|
|
int32 timeout = get_integer_arg(query.get(), "timeout", 0, 0, LONG_POLL_MAX_TIMEOUT);
|
|
|
|
update_allowed_update_types(query.get());
|
|
|
|
auto now = td::Time::now_cached();
|
|
if (offset == previous_get_updates_offset_ && timeout < 3 && now < previous_get_updates_start_date_ + 3.0) {
|
|
timeout = 3;
|
|
}
|
|
previous_get_updates_offset_ = offset;
|
|
previous_get_updates_start_date_ = now;
|
|
do_get_updates(offset, limit, timeout, std::move(query));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_set_webhook_query(PromisedQueryPtr &query) {
|
|
Slice new_url;
|
|
if (query->method() == "setwebhook") {
|
|
new_url = query->arg("url");
|
|
}
|
|
|
|
auto now = td::Time::now_cached();
|
|
if (!new_url.empty()) {
|
|
if (now < next_allowed_set_webhook_date_) {
|
|
query->set_retry_after_error(1);
|
|
return Status::OK();
|
|
}
|
|
next_allowed_set_webhook_date_ = now + 1;
|
|
}
|
|
|
|
// do not send warning just after webhook was deleted or set
|
|
next_bot_updates_warning_date_ = td::max(next_bot_updates_warning_date_, now + BOT_UPDATES_WARNING_DELAY);
|
|
|
|
int32 new_max_connections = new_url.empty() ? 0 : get_webhook_max_connections(query.get());
|
|
Slice new_ip_address = new_url.empty() ? Slice() : query->arg("ip_address");
|
|
bool new_fix_ip_address = new_url.empty() ? false : get_webhook_fix_ip_address(query.get());
|
|
bool drop_pending_updates = to_bool(query->arg("drop_pending_updates"));
|
|
if (webhook_set_query_) {
|
|
// already updating webhook. Cancel previous request
|
|
fail_query_conflict("Conflict: terminated by other setWebhook", std::move(webhook_set_query_));
|
|
} else if (webhook_url_ == new_url && !has_webhook_certificate_ && query->file("certificate") == nullptr &&
|
|
query->arg("certificate").empty() && new_max_connections == webhook_max_connections_ &&
|
|
new_fix_ip_address == webhook_fix_ip_address_ &&
|
|
(!new_fix_ip_address || new_ip_address == webhook_ip_address_) && !drop_pending_updates) {
|
|
if (update_allowed_update_types(query.get())) {
|
|
save_webhook();
|
|
} else if (now > next_webhook_is_not_modified_warning_date_) {
|
|
next_webhook_is_not_modified_warning_date_ = now + 300;
|
|
LOG(WARNING) << "Webhook is not modified: \"" << new_url << '"';
|
|
}
|
|
answer_query(td::JsonTrue(), std::move(query),
|
|
new_url.empty() ? Slice("Webhook is already deleted") : Slice("Webhook is already set"));
|
|
return Status::OK();
|
|
}
|
|
|
|
if (now > next_set_webhook_logging_date_ || webhook_url_ != new_url) {
|
|
next_set_webhook_logging_date_ = now + 300;
|
|
LOG(WARNING) << "Set webhook to " << new_url << ", max_connections = " << new_max_connections
|
|
<< ", IP address = " << new_ip_address;
|
|
}
|
|
|
|
if (!new_url.empty()) {
|
|
abort_long_poll(true);
|
|
}
|
|
|
|
webhook_generation_++;
|
|
// need to close old webhook first
|
|
if (!webhook_url_.empty()) {
|
|
if (!webhook_id_.empty()) {
|
|
send_closure_later(std::move(webhook_id_), &WebhookActor::close);
|
|
}
|
|
|
|
// wait for webhook_close callback
|
|
webhook_query_type_ = WebhookQueryType::Cancel;
|
|
webhook_set_query_ = std::move(query);
|
|
return Status::OK();
|
|
}
|
|
do_set_webhook(std::move(query), false);
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_get_webhook_info_query(PromisedQueryPtr &query) {
|
|
answer_query(JsonWebhookInfo(this), std::move(query));
|
|
return Status::OK();
|
|
}
|
|
|
|
td::Status Client::process_get_file_query(PromisedQueryPtr &query) {
|
|
td::string file_id = query->arg("file_id").str();
|
|
check_remote_file_id(file_id, std::move(query), [this](object_ptr<td_api::file> file, PromisedQueryPtr query) {
|
|
do_get_file(std::move(file), std::move(query));
|
|
});
|
|
return Status::OK();
|
|
}
|
|
|
|
void Client::do_get_file(object_ptr<td_api::file> file, PromisedQueryPtr query) {
|
|
if (!parameters_->local_mode_ &&
|
|
td::max(file->expected_size_, file->local_->downloaded_size_) > MAX_DOWNLOAD_FILE_SIZE) { // speculative check
|
|
return fail_query(400, "Bad Request: file is too big", std::move(query));
|
|
}
|
|
|
|
auto file_id = file->id_;
|
|
file_download_listeners_[file_id].push_back(std::move(query));
|
|
if (file->local_->is_downloading_completed_) {
|
|
Slice relative_path = td::PathView::relative(file->local_->path_, absolute_dir_, true);
|
|
if (!relative_path.empty()) {
|
|
auto r_stat = td::stat(file->local_->path_);
|
|
if (r_stat.is_ok() && r_stat.ok().is_reg_ && r_stat.ok().size_ == file->size_) {
|
|
return on_file_download(file_id, std::move(file));
|
|
}
|
|
}
|
|
}
|
|
|
|
send_request(make_object<td_api::downloadFile>(file_id, 1, 0, 0, false),
|
|
std::make_unique<TdOnDownloadFileCallback>(this, file_id));
|
|
}
|
|
|
|
bool Client::is_file_being_downloaded(int32 file_id) const {
|
|
return file_download_listeners_.count(file_id) > 0;
|
|
}
|
|
|
|
void Client::on_file_download(int32 file_id, td::Result<object_ptr<td_api::file>> r_file) {
|
|
auto it = file_download_listeners_.find(file_id);
|
|
if (it == file_download_listeners_.end()) {
|
|
return;
|
|
}
|
|
auto queries = std::move(it->second);
|
|
file_download_listeners_.erase(it);
|
|
download_started_file_ids_.erase(file_id);
|
|
for (auto &query : queries) {
|
|
if (r_file.is_error()) {
|
|
const auto &error = r_file.error();
|
|
fail_query_with_error(std::move(query), error.code(), error.public_message());
|
|
} else {
|
|
answer_query(JsonFile(r_file.ok().get(), this), std::move(query));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::webhook_verified(td::string cached_ip_address) {
|
|
if (get_link_token() != webhook_generation_) {
|
|
return;
|
|
}
|
|
bool need_save = webhook_set_query_ || cached_ip_address != webhook_ip_address_;
|
|
webhook_ip_address_ = cached_ip_address;
|
|
if (webhook_set_query_) {
|
|
LOG(WARNING) << "Webhook verified";
|
|
answer_query(td::JsonTrue(), std::move(webhook_set_query_), "Webhook was set");
|
|
}
|
|
if (need_save) {
|
|
save_webhook();
|
|
}
|
|
}
|
|
|
|
void Client::save_webhook() const {
|
|
td::string value;
|
|
if (has_webhook_certificate_) {
|
|
value += "cert/";
|
|
}
|
|
value += PSTRING() << "#maxc" << webhook_max_connections_ << '/';
|
|
if (!webhook_ip_address_.empty()) {
|
|
value += PSTRING() << "#ip" << webhook_ip_address_ << '/';
|
|
}
|
|
if (webhook_fix_ip_address_) {
|
|
value += "#fix_ip/";
|
|
}
|
|
if (allowed_update_types_ != DEFAULT_ALLOWED_UPDATE_TYPES) {
|
|
value += PSTRING() << "#allow" << allowed_update_types_ << '/';
|
|
}
|
|
value += webhook_url_;
|
|
LOG(INFO) << "Save webhook " << value;
|
|
parameters_->shared_data_->webhook_db_->set(bot_token_with_dc_, value);
|
|
}
|
|
|
|
void Client::webhook_success() {
|
|
next_bot_updates_warning_date_ = td::Time::now() + BOT_UPDATES_WARNING_DELAY;
|
|
if (was_bot_updates_warning_) {
|
|
send_request(make_object<td_api::setBotUpdatesStatus>(0, ""), std::make_unique<TdOnOkCallback>());
|
|
was_bot_updates_warning_ = false;
|
|
}
|
|
}
|
|
|
|
void Client::webhook_error(Status status) {
|
|
CHECK(status.is_error());
|
|
last_webhook_error_date_ = get_unix_time();
|
|
last_webhook_error_ = std::move(status);
|
|
|
|
auto pending_update_count = get_pending_update_count();
|
|
if (pending_update_count >= MIN_PENDING_UPDATES_WARNING && td::Time::now() > next_bot_updates_warning_date_) {
|
|
send_request(make_object<td_api::setBotUpdatesStatus>(td::narrow_cast<int32>(pending_update_count),
|
|
"Webhook error. " + last_webhook_error_.message().str()),
|
|
std::make_unique<TdOnOkCallback>());
|
|
next_bot_updates_warning_date_ = td::Time::now_cached() + BOT_UPDATES_WARNING_DELAY;
|
|
was_bot_updates_warning_ = true;
|
|
}
|
|
}
|
|
|
|
void Client::webhook_closed(Status status) {
|
|
LOG(WARNING) << "Webhook closed: " << status
|
|
<< ", webhook_query_type = " << (webhook_query_type_ == WebhookQueryType::Verify ? "verify" : "change");
|
|
webhook_id_.release();
|
|
webhook_url_ = td::string();
|
|
if (has_webhook_certificate_) {
|
|
td::unlink(get_webhook_certificate_path()).ignore();
|
|
has_webhook_certificate_ = false;
|
|
}
|
|
webhook_max_connections_ = 0;
|
|
webhook_ip_address_ = td::string();
|
|
webhook_fix_ip_address_ = false;
|
|
webhook_set_date_ = td::Time::now();
|
|
last_webhook_error_date_ = 0;
|
|
last_webhook_error_ = Status::OK();
|
|
parameters_->shared_data_->webhook_db_->erase(bot_token_with_dc_);
|
|
|
|
if (webhook_set_query_) {
|
|
if (webhook_query_type_ == WebhookQueryType::Verify) {
|
|
fail_query(400, PSLICE() << "Bad Request: bad webhook: " << status.message(), std::move(webhook_set_query_));
|
|
} else {
|
|
do_set_webhook(std::move(webhook_set_query_), true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::hangup_shared() {
|
|
webhook_closed(Status::Error("Unknown"));
|
|
}
|
|
|
|
td::string Client::get_webhook_certificate_path() const {
|
|
return dir_ + "cert.pem";
|
|
}
|
|
|
|
td::int32 Client::get_webhook_max_connections(const Query *query) const {
|
|
auto default_value = parameters_->default_max_webhook_connections_;
|
|
auto max_value = parameters_->local_mode_ ? 100000 : 100;
|
|
return get_integer_arg(query, "max_connections", default_value, 1, max_value);
|
|
}
|
|
|
|
bool Client::get_webhook_fix_ip_address(const Query *query) {
|
|
if (query->is_internal()) {
|
|
return query->has_arg("fix_ip_address");
|
|
}
|
|
return !query->arg("ip_address").empty();
|
|
}
|
|
|
|
void Client::do_set_webhook(PromisedQueryPtr query, bool was_deleted) {
|
|
CHECK(webhook_url_.empty());
|
|
if (to_bool(query->arg("drop_pending_updates"))) {
|
|
clear_tqueue();
|
|
}
|
|
Slice new_url;
|
|
if (query->method() == "setwebhook") {
|
|
new_url = query->arg("url");
|
|
}
|
|
if (!new_url.empty()) {
|
|
auto url = td::parse_url(new_url, td::HttpUrl::Protocol::Https);
|
|
if (url.is_error()) {
|
|
return fail_query(400, "Bad Request: invalid webhook URL specified", std::move(query));
|
|
}
|
|
auto *cert_file_ptr = query->file("certificate");
|
|
has_webhook_certificate_ = false;
|
|
if (cert_file_ptr != nullptr) {
|
|
auto size = cert_file_ptr->size;
|
|
if (size > MAX_CERTIFICATE_FILE_SIZE) {
|
|
return fail_query(400, PSLICE() << "Bad Request: certificate size is too big (" << size << " bytes)",
|
|
std::move(query));
|
|
}
|
|
auto from_path = cert_file_ptr->temp_file_name;
|
|
auto to_path = get_webhook_certificate_path();
|
|
auto status = td::copy_file(from_path, to_path, size);
|
|
if (status.is_error()) {
|
|
return fail_query(500, "Internal Server Error: failed to save certificate", std::move(query));
|
|
}
|
|
has_webhook_certificate_ = true;
|
|
}
|
|
|
|
if (query->is_internal() && query->arg("certificate") == "previous") {
|
|
has_webhook_certificate_ = true;
|
|
}
|
|
webhook_url_ = new_url.str();
|
|
webhook_set_date_ = td::Time::now();
|
|
webhook_max_connections_ = get_webhook_max_connections(query.get());
|
|
webhook_ip_address_ = query->arg("ip_address").str();
|
|
webhook_fix_ip_address_ = get_webhook_fix_ip_address(query.get());
|
|
last_webhook_error_date_ = 0;
|
|
last_webhook_error_ = Status::OK();
|
|
|
|
update_allowed_update_types(query.get());
|
|
|
|
LOG(WARNING) << "Create " << (has_webhook_certificate_ ? "" : "not ") << "self signed webhook: " << url.ok();
|
|
auto webhook_actor_name = PSTRING() << "Webhook " << url.ok();
|
|
webhook_id_ = td::create_actor<WebhookActor>(
|
|
webhook_actor_name, actor_shared(this, webhook_generation_), tqueue_id_, url.move_as_ok(),
|
|
has_webhook_certificate_ ? get_webhook_certificate_path() : "", webhook_max_connections_, query->is_internal(),
|
|
webhook_ip_address_, webhook_fix_ip_address_, parameters_);
|
|
// wait for webhook verified or webhook callback
|
|
webhook_query_type_ = WebhookQueryType::Verify;
|
|
webhook_set_query_ = std::move(query);
|
|
} else {
|
|
answer_query(td::JsonTrue(), std::move(query),
|
|
was_deleted ? Slice("Webhook was deleted") : Slice("Webhook is already deleted"));
|
|
}
|
|
}
|
|
|
|
void Client::do_send_message(object_ptr<td_api::InputMessageContent> input_message_content, PromisedQueryPtr query) {
|
|
auto chat_id = query->arg("chat_id");
|
|
auto reply_to_message_id = get_message_id(query.get(), "reply_to_message_id");
|
|
auto allow_sending_without_reply = to_bool(query->arg("allow_sending_without_reply"));
|
|
auto disable_notification = to_bool(query->arg("disable_notification"));
|
|
auto r_reply_markup = get_reply_markup(query.get());
|
|
if (r_reply_markup.is_error()) {
|
|
return fail_query_with_error(std::move(query), 400, r_reply_markup.error().message());
|
|
}
|
|
auto reply_markup = r_reply_markup.move_as_ok();
|
|
|
|
resolve_reply_markup_bot_usernames(
|
|
std::move(reply_markup), std::move(query),
|
|
[this, chat_id = chat_id.str(), reply_to_message_id, allow_sending_without_reply, disable_notification,
|
|
input_message_content = std::move(input_message_content)](object_ptr<td_api::ReplyMarkup> reply_markup,
|
|
PromisedQueryPtr query) mutable {
|
|
auto on_success = [this, disable_notification, input_message_content = std::move(input_message_content),
|
|
reply_markup = std::move(reply_markup)](int64 chat_id, int64 reply_to_message_id,
|
|
PromisedQueryPtr query) mutable {
|
|
send_request(make_object<td_api::sendMessage>(chat_id, 0, reply_to_message_id,
|
|
get_message_send_options(disable_notification),
|
|
std::move(reply_markup), std::move(input_message_content)),
|
|
std::make_unique<TdOnSendMessageCallback>(this, std::move(query)));
|
|
};
|
|
check_message(chat_id, reply_to_message_id, reply_to_message_id <= 0 || allow_sending_without_reply,
|
|
AccessRights::Write, "reply message", std::move(query), std::move(on_success));
|
|
});
|
|
}
|
|
|
|
td::int64 Client::get_send_message_query_id(PromisedQueryPtr query, bool is_multisend) {
|
|
auto query_id = current_send_message_query_id_++;
|
|
auto &pending_query = pending_send_message_queries_[query_id];
|
|
pending_query.query = std::move(query);
|
|
pending_query.is_multisend = is_multisend;
|
|
return query_id;
|
|
}
|
|
|
|
void Client::on_sent_message(object_ptr<td_api::message> &&message, int64 query_id) {
|
|
CHECK(message != nullptr);
|
|
int64 chat_id = message->chat_id_;
|
|
int64 message_id = message->id_;
|
|
int64 reply_to_message_id = message->reply_to_message_id_;
|
|
if (reply_to_message_id > 0) {
|
|
CHECK(message->reply_in_chat_id_ == chat_id);
|
|
bool is_inserted = yet_unsent_reply_message_ids_[{chat_id, reply_to_message_id}].insert(message_id).second;
|
|
CHECK(is_inserted);
|
|
}
|
|
|
|
FullMessageId yet_unsent_message_id{chat_id, message_id};
|
|
YetUnsentMessage yet_unsent_message;
|
|
yet_unsent_message.reply_to_message_id = reply_to_message_id;
|
|
yet_unsent_message.send_message_query_id = query_id;
|
|
auto emplace_result = yet_unsent_messages_.emplace(yet_unsent_message_id, yet_unsent_message);
|
|
CHECK(emplace_result.second);
|
|
pending_send_message_queries_[query_id].awaited_messages++;
|
|
}
|
|
|
|
void Client::abort_long_poll(bool from_set_webhook) {
|
|
if (long_poll_query_) {
|
|
Slice message;
|
|
if (from_set_webhook) {
|
|
message = Slice("Conflict: terminated by setWebhook request");
|
|
} else {
|
|
message =
|
|
Slice("Conflict: terminated by other getUpdates request; make sure that only one bot instance is running");
|
|
}
|
|
fail_query_conflict(message, std::move(long_poll_query_));
|
|
}
|
|
}
|
|
|
|
void Client::fail_query_conflict(Slice message, PromisedQueryPtr &&query) {
|
|
auto now = td::Time::now_cached();
|
|
if (now >= next_conflict_response_date_) {
|
|
fail_query(409, message, std::move(query));
|
|
next_conflict_response_date_ = now + 3.0;
|
|
} else {
|
|
td::create_actor<td::SleepActor>(
|
|
"FailQueryConflictSleepActor", 3.0,
|
|
td::PromiseCreator::lambda([message = message.str(), query = std::move(query)](td::Result<> result) mutable {
|
|
fail_query(409, message, std::move(query));
|
|
}))
|
|
.release();
|
|
}
|
|
}
|
|
|
|
class Client::JsonUpdates : public Jsonable {
|
|
public:
|
|
explicit JsonUpdates(td::Span<td::TQueue::Event> updates) : updates_(updates) {
|
|
}
|
|
void store(JsonValueScope *scope) const {
|
|
auto array = scope->enter_array();
|
|
int left_len = 1 << 22;
|
|
for (auto &update : updates_) {
|
|
left_len -= 50 + td::narrow_cast<int>(update.data.size());
|
|
if (left_len <= 0) {
|
|
break;
|
|
}
|
|
array << JsonUpdate(update.id.value(), update.data);
|
|
}
|
|
}
|
|
|
|
private:
|
|
td::Span<td::TQueue::Event> updates_;
|
|
};
|
|
|
|
void Client::do_get_updates(int32 offset, int32 limit, int32 timeout, PromisedQueryPtr query) {
|
|
auto &tqueue = parameters_->shared_data_->tqueue_;
|
|
LOG(DEBUG) << "Get updates with offset = " << offset << ", limit = " << limit << " and timeout = " << timeout;
|
|
LOG(DEBUG) << "Queue head = " << tqueue->get_head(tqueue_id_) << ", queue tail = " << tqueue->get_tail(tqueue_id_);
|
|
|
|
if (offset <= 0) {
|
|
auto head = tqueue->get_head(tqueue_id_);
|
|
if (!head.empty() && offset < 0) {
|
|
// negative offset is counted from the end
|
|
auto tail = tqueue->get_tail(tqueue_id_);
|
|
CHECK(!tail.empty());
|
|
offset += tail.value();
|
|
}
|
|
if (offset < head.value()) {
|
|
offset = head.value();
|
|
}
|
|
}
|
|
|
|
auto updates = mutable_span(parameters_->shared_data_->event_buffer_, SharedData::TQUEUE_EVENT_BUFFER_SIZE);
|
|
updates.truncate(limit);
|
|
td::TQueue::EventId from;
|
|
size_t total_size = 0;
|
|
if (offset <= 0) {
|
|
// queue is not created yet
|
|
updates = {};
|
|
} else {
|
|
bool is_ok = false;
|
|
auto r_offset = td::TQueue::EventId::from_int32(offset);
|
|
auto now = get_unix_time();
|
|
if (r_offset.is_ok()) {
|
|
from = r_offset.ok();
|
|
auto r_total_size = tqueue->get(tqueue_id_, from, true, now, updates);
|
|
if (r_total_size.is_ok()) {
|
|
is_ok = true;
|
|
total_size = r_total_size.move_as_ok();
|
|
}
|
|
}
|
|
if (!is_ok) {
|
|
from = tqueue->get_head(tqueue_id_);
|
|
auto r_total_size = tqueue->get(tqueue_id_, from, true, now, updates);
|
|
CHECK(r_total_size.is_ok());
|
|
total_size = r_total_size.move_as_ok();
|
|
}
|
|
}
|
|
CHECK(total_size >= updates.size());
|
|
total_size -= updates.size();
|
|
|
|
bool need_warning = false;
|
|
if (total_size <= MIN_PENDING_UPDATES_WARNING / 2) {
|
|
if (last_pending_update_count_ > MIN_PENDING_UPDATES_WARNING) {
|
|
need_warning = true;
|
|
last_pending_update_count_ = MIN_PENDING_UPDATES_WARNING;
|
|
}
|
|
} else if (total_size >= last_pending_update_count_) {
|
|
need_warning = true;
|
|
while (total_size >= last_pending_update_count_) {
|
|
last_pending_update_count_ *= 2;
|
|
}
|
|
}
|
|
if (need_warning) {
|
|
LOG(WARNING) << "Found " << updates.size() << " updates out of " << total_size << " + " << updates.size()
|
|
<< " after last getUpdates call at " << previous_get_updates_finish_date_;
|
|
} else {
|
|
LOG(DEBUG) << "Found " << updates.size() << " updates out of " << total_size << " from " << from;
|
|
}
|
|
|
|
if (timeout != 0 && updates.size() == 0) {
|
|
abort_long_poll(false);
|
|
long_poll_offset_ = offset;
|
|
long_poll_limit_ = limit;
|
|
long_poll_query_ = std::move(query);
|
|
long_poll_was_wakeup_ = false;
|
|
long_poll_hard_timeout_ = td::Time::now_cached() + timeout;
|
|
long_poll_slot_.set_event(td::EventCreator::raw(actor_id(), static_cast<td::uint64>(0)));
|
|
long_poll_slot_.set_timeout_at(long_poll_hard_timeout_);
|
|
return;
|
|
}
|
|
previous_get_updates_finish_date_ = td::Clocks::system(); // local time to output it to the log
|
|
next_bot_updates_warning_date_ = td::Time::now() + BOT_UPDATES_WARNING_DELAY;
|
|
if (total_size == updates.size() && was_bot_updates_warning_) {
|
|
send_request(make_object<td_api::setBotUpdatesStatus>(0, ""), std::make_unique<TdOnOkCallback>());
|
|
was_bot_updates_warning_ = false;
|
|
}
|
|
answer_query(JsonUpdates(updates), std::move(query));
|
|
}
|
|
|
|
void Client::long_poll_wakeup(bool force_flag) {
|
|
if (!long_poll_query_) {
|
|
auto pending_update_count = get_pending_update_count();
|
|
if (pending_update_count >= MIN_PENDING_UPDATES_WARNING && td::Time::now() > next_bot_updates_warning_date_) {
|
|
send_request(make_object<td_api::setBotUpdatesStatus>(td::narrow_cast<int32>(pending_update_count),
|
|
"The getUpdates method is not called for too long"),
|
|
std::make_unique<TdOnOkCallback>());
|
|
next_bot_updates_warning_date_ =
|
|
td::Time::now_cached() + BOT_UPDATES_WARNING_DELAY; // do not send warnings too often
|
|
was_bot_updates_warning_ = true;
|
|
}
|
|
return;
|
|
}
|
|
if (force_flag) {
|
|
do_get_updates(long_poll_offset_, long_poll_limit_, 0, std::move(long_poll_query_));
|
|
} else {
|
|
double now = td::Time::now();
|
|
if (!long_poll_was_wakeup_) {
|
|
long_poll_hard_timeout_ = td::min(now + LONG_POLL_MAX_DELAY, long_poll_hard_timeout_);
|
|
long_poll_was_wakeup_ = true;
|
|
}
|
|
double timeout = td::min(now + LONG_POLL_WAIT_AFTER, long_poll_hard_timeout_);
|
|
long_poll_slot_.set_event(td::EventCreator::raw(actor_id(), static_cast<td::uint64>(0)));
|
|
long_poll_slot_.set_timeout_at(timeout);
|
|
}
|
|
}
|
|
|
|
void Client::add_user(std::unordered_map<int32, UserInfo> &users, object_ptr<td_api::user> &&user) {
|
|
auto user_info = &users[user->id_];
|
|
user_info->first_name = user->first_name_;
|
|
user_info->last_name = user->last_name_;
|
|
user_info->username = user->username_;
|
|
user_info->language_code = user->language_code_;
|
|
|
|
user_info->have_access = user->have_access_;
|
|
|
|
switch (user->type_->get_id()) {
|
|
case td_api::userTypeRegular::ID:
|
|
user_info->type = UserInfo::Type::Regular;
|
|
break;
|
|
case td_api::userTypeBot::ID: {
|
|
user_info->type = UserInfo::Type::Bot;
|
|
auto *bot = static_cast<const td_api::userTypeBot *>(user->type_.get());
|
|
user_info->can_join_groups = bot->can_join_groups_;
|
|
user_info->can_read_all_group_messages = bot->can_read_all_group_messages_;
|
|
user_info->is_inline_bot = bot->is_inline_;
|
|
break;
|
|
}
|
|
case td_api::userTypeDeleted::ID:
|
|
user_info->type = UserInfo::Type::Deleted;
|
|
break;
|
|
case td_api::userTypeUnknown::ID:
|
|
user_info->type = UserInfo::Type::Unknown;
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
}
|
|
|
|
const Client::UserInfo *Client::get_user_info(int32 user_id) const {
|
|
auto it = users_.find(user_id);
|
|
return it == users_.end() ? nullptr : &it->second;
|
|
}
|
|
|
|
void Client::set_user_bio(int32 user_id, td::string &&bio) {
|
|
auto user_info = &users_[user_id];
|
|
user_info->bio = std::move(bio);
|
|
}
|
|
|
|
void Client::add_group(std::unordered_map<int32, GroupInfo> &groups, object_ptr<td_api::basicGroup> &&group) {
|
|
auto group_info = &groups[group->id_];
|
|
group_info->member_count = group->member_count_;
|
|
group_info->left = group->status_->get_id() == td_api::chatMemberStatusLeft::ID;
|
|
group_info->kicked = group->status_->get_id() == td_api::chatMemberStatusBanned::ID;
|
|
group_info->is_active = group->is_active_;
|
|
group_info->upgraded_to_supergroup_id = group->upgraded_to_supergroup_id_;
|
|
}
|
|
|
|
const Client::GroupInfo *Client::get_group_info(int32 group_id) const {
|
|
auto it = groups_.find(group_id);
|
|
return it == groups_.end() ? nullptr : &it->second;
|
|
}
|
|
|
|
void Client::set_group_description(int32 group_id, td::string &&descripton) {
|
|
auto group_info = &groups_[group_id];
|
|
group_info->description = std::move(descripton);
|
|
}
|
|
|
|
void Client::set_group_invite_link(int32 group_id, td::string &&invite_link) {
|
|
auto group_info = &groups_[group_id];
|
|
group_info->invite_link = std::move(invite_link);
|
|
}
|
|
|
|
void Client::add_supergroup(std::unordered_map<int32, SupergroupInfo> &supergroups,
|
|
object_ptr<td_api::supergroup> &&supergroup) {
|
|
auto supergroup_info = &supergroups[supergroup->id_];
|
|
supergroup_info->username = std::move(supergroup->username_);
|
|
supergroup_info->date = supergroup->date_;
|
|
supergroup_info->status = std::move(supergroup->status_);
|
|
supergroup_info->is_supergroup = !supergroup->is_channel_;
|
|
supergroup_info->has_location = supergroup->has_location_;
|
|
}
|
|
|
|
void Client::set_supergroup_description(int32 supergroup_id, td::string &&descripton) {
|
|
auto supergroup_info = &supergroups_[supergroup_id];
|
|
supergroup_info->description = std::move(descripton);
|
|
}
|
|
|
|
void Client::set_supergroup_invite_link(int32 supergroup_id, td::string &&invite_link) {
|
|
auto supergroup_info = &supergroups_[supergroup_id];
|
|
supergroup_info->invite_link = std::move(invite_link);
|
|
}
|
|
|
|
void Client::set_supergroup_sticker_set_id(int32 supergroup_id, int64 sticker_set_id) {
|
|
auto supergroup_info = &supergroups_[supergroup_id];
|
|
supergroup_info->sticker_set_id = sticker_set_id;
|
|
}
|
|
|
|
void Client::set_supergroup_can_set_sticker_set(int32 supergroup_id, bool can_set_sticker_set) {
|
|
auto supergroup_info = &supergroups_[supergroup_id];
|
|
supergroup_info->can_set_sticker_set = can_set_sticker_set;
|
|
}
|
|
|
|
void Client::set_supergroup_slow_mode_delay(int32 supergroup_id, int32 slow_mode_delay) {
|
|
auto supergroup_info = &supergroups_[supergroup_id];
|
|
supergroup_info->slow_mode_delay = slow_mode_delay;
|
|
}
|
|
|
|
void Client::set_supergroup_linked_chat_id(int32 supergroup_id, int64 linked_chat_id) {
|
|
auto supergroup_info = &supergroups_[supergroup_id];
|
|
supergroup_info->linked_chat_id = linked_chat_id;
|
|
}
|
|
|
|
void Client::set_supergroup_location(int32 supergroup_id, object_ptr<td_api::chatLocation> location) {
|
|
auto supergroup_info = &supergroups_[supergroup_id];
|
|
supergroup_info->location = std::move(location);
|
|
}
|
|
|
|
const Client::SupergroupInfo *Client::get_supergroup_info(int32 supergroup_id) const {
|
|
auto it = supergroups_.find(supergroup_id);
|
|
return it == supergroups_.end() ? nullptr : &it->second;
|
|
}
|
|
|
|
Client::ChatInfo *Client::add_chat(int64 chat_id) {
|
|
LOG(DEBUG) << "Update chat " << chat_id;
|
|
return &chats_[chat_id];
|
|
}
|
|
|
|
const Client::ChatInfo *Client::get_chat(int64 chat_id) const {
|
|
auto it = chats_.find(chat_id);
|
|
if (it == chats_.end()) {
|
|
return nullptr;
|
|
}
|
|
return &it->second;
|
|
}
|
|
|
|
Client::ChatType Client::get_chat_type(int64 chat_id) const {
|
|
auto chat_info = get_chat(chat_id);
|
|
if (chat_info == nullptr) {
|
|
return ChatType::Unknown;
|
|
}
|
|
switch (chat_info->type) {
|
|
case ChatInfo::Type::Private:
|
|
return ChatType::Private;
|
|
case ChatInfo::Type::Group:
|
|
return ChatType::Group;
|
|
case ChatInfo::Type::Supergroup: {
|
|
auto supergroup_info = get_supergroup_info(chat_info->supergroup_id);
|
|
if (supergroup_info == nullptr) {
|
|
return ChatType::Unknown;
|
|
}
|
|
if (supergroup_info->is_supergroup) {
|
|
return ChatType::Supergroup;
|
|
} else {
|
|
return ChatType::Channel;
|
|
}
|
|
}
|
|
case ChatInfo::Type::Unknown:
|
|
return ChatType::Unknown;
|
|
default:
|
|
UNREACHABLE();
|
|
return ChatType::Unknown;
|
|
}
|
|
}
|
|
|
|
td::string Client::get_chat_description(int64 chat_id) const {
|
|
auto chat_info = get_chat(chat_id);
|
|
if (chat_info == nullptr) {
|
|
return PSTRING() << "unknown chat " << chat_id;
|
|
}
|
|
switch (chat_info->type) {
|
|
case ChatInfo::Type::Private: {
|
|
auto user_info = get_user_info(chat_info->user_id);
|
|
return PSTRING() << "private " << (user_info == nullptr || !user_info->have_access ? "un" : "")
|
|
<< "accessible chat " << chat_id;
|
|
}
|
|
case ChatInfo::Type::Group: {
|
|
auto group_info = get_group_info(chat_info->group_id);
|
|
if (group_info == nullptr) {
|
|
return PSTRING() << "unknown group chat " << chat_id;
|
|
}
|
|
return PSTRING() << (group_info->is_active ? "" : "in") << "active group chat " << chat_id << ", chat status = "
|
|
<< (group_info->kicked ? "kicked" : (group_info->left ? "left" : "member"));
|
|
}
|
|
case ChatInfo::Type::Supergroup: {
|
|
auto supergroup_info = get_supergroup_info(chat_info->supergroup_id);
|
|
if (supergroup_info == nullptr) {
|
|
return PSTRING() << "unknown supergroup chat " << chat_id;
|
|
}
|
|
return PSTRING() << (supergroup_info->is_supergroup ? "supergroup" : "channel") << " chat " << chat_id
|
|
<< ", chat status = " << to_string(supergroup_info->status)
|
|
<< ", username = " << supergroup_info->username;
|
|
}
|
|
case ChatInfo::Type::Unknown:
|
|
return PSTRING() << "unknown chat " << chat_id;
|
|
default:
|
|
UNREACHABLE();
|
|
return "";
|
|
}
|
|
}
|
|
|
|
void Client::json_store_file(td::JsonObjectScope &object, const td_api::file *file, bool with_path) const {
|
|
if (file->id_ == 0) {
|
|
return;
|
|
}
|
|
|
|
LOG_IF(ERROR, file->remote_->id_.empty()) << "File remote identifier is empty: " << td::oneline(to_string(*file));
|
|
|
|
object("file_id", file->remote_->id_);
|
|
object("file_unique_id", file->remote_->unique_id_);
|
|
if (file->size_) {
|
|
object("file_size", file->size_);
|
|
}
|
|
if (with_path && file->local_->is_downloading_completed_) {
|
|
if (parameters_->local_mode_) {
|
|
if (td::check_utf8(file->local_->path_)) {
|
|
object("file_path", file->local_->path_);
|
|
} else {
|
|
object("file_path", td::JsonRawString(file->local_->path_));
|
|
}
|
|
} else {
|
|
Slice relative_path = td::PathView::relative(file->local_->path_, absolute_dir_, true);
|
|
if (!relative_path.empty() && file->local_->downloaded_size_ <= MAX_DOWNLOAD_FILE_SIZE) {
|
|
object("file_path", relative_path);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::json_store_thumbnail(td::JsonObjectScope &object, const td_api::thumbnail *thumbnail) const {
|
|
if (thumbnail == nullptr || thumbnail->format_->get_id() == td_api::thumbnailFormatMpeg4::ID) {
|
|
return;
|
|
}
|
|
|
|
CHECK(thumbnail->file_->id_ > 0);
|
|
object("thumb", JsonThumbnail(thumbnail, this));
|
|
}
|
|
|
|
void Client::json_store_callback_query_payload(td::JsonObjectScope &object,
|
|
const td_api::CallbackQueryPayload *payload) {
|
|
CHECK(payload != nullptr);
|
|
switch (payload->get_id()) {
|
|
case td_api::callbackQueryPayloadData::ID: {
|
|
auto data = static_cast<const td_api::callbackQueryPayloadData *>(payload);
|
|
if (!td::check_utf8(data->data_)) {
|
|
LOG(WARNING) << "Receive non-UTF-8 callback query data";
|
|
object("data", td::JsonRawString(data->data_));
|
|
} else {
|
|
object("data", data->data_);
|
|
}
|
|
break;
|
|
}
|
|
case td_api::callbackQueryPayloadGame::ID:
|
|
object("game_short_name", static_cast<const td_api::callbackQueryPayloadGame *>(payload)->game_short_name_);
|
|
break;
|
|
case td_api::callbackQueryPayloadDataWithPassword::ID:
|
|
UNREACHABLE();
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
void Client::json_store_permissions(td::JsonObjectScope &object, const td_api::chatPermissions *permissions) {
|
|
object("can_send_messages", td::JsonBool(permissions->can_send_messages_));
|
|
object("can_send_media_messages", td::JsonBool(permissions->can_send_media_messages_));
|
|
object("can_send_polls", td::JsonBool(permissions->can_send_polls_));
|
|
object("can_send_other_messages", td::JsonBool(permissions->can_send_other_messages_));
|
|
object("can_add_web_page_previews", td::JsonBool(permissions->can_add_web_page_previews_));
|
|
object("can_change_info", td::JsonBool(permissions->can_change_info_));
|
|
object("can_invite_users", td::JsonBool(permissions->can_invite_users_));
|
|
object("can_pin_messages", td::JsonBool(permissions->can_pin_messages_));
|
|
}
|
|
|
|
Client::Slice Client::get_update_type_name(UpdateType update_type) {
|
|
switch (update_type) {
|
|
case UpdateType::Message:
|
|
return Slice("message");
|
|
case UpdateType::EditedMessage:
|
|
return Slice("edited_message");
|
|
case UpdateType::ChannelPost:
|
|
return Slice("channel_post");
|
|
case UpdateType::EditedChannelPost:
|
|
return Slice("edited_channel_post");
|
|
case UpdateType::InlineQuery:
|
|
return Slice("inline_query");
|
|
case UpdateType::ChosenInlineResult:
|
|
return Slice("chosen_inline_result");
|
|
case UpdateType::CallbackQuery:
|
|
return Slice("callback_query");
|
|
case UpdateType::CustomEvent:
|
|
return Slice("custom_event");
|
|
case UpdateType::CustomQuery:
|
|
return Slice("custom_query");
|
|
case UpdateType::ShippingQuery:
|
|
return Slice("shipping_query");
|
|
case UpdateType::PreCheckoutQuery:
|
|
return Slice("pre_checkout_query");
|
|
case UpdateType::Poll:
|
|
return Slice("poll");
|
|
case UpdateType::PollAnswer:
|
|
return Slice("poll_answer");
|
|
default:
|
|
UNREACHABLE();
|
|
return Slice();
|
|
}
|
|
}
|
|
|
|
td::uint32 Client::get_allowed_update_types(td::MutableSlice allowed_updates, bool is_internal) {
|
|
if (allowed_updates.empty()) {
|
|
return 0;
|
|
}
|
|
|
|
LOG(INFO) << "Parsing JSON object: " << allowed_updates;
|
|
auto r_value = json_decode(allowed_updates);
|
|
if (r_value.is_error()) {
|
|
LOG(INFO) << "Can't parse JSON object: " << r_value.error();
|
|
return 0;
|
|
}
|
|
|
|
td::uint32 result = 0;
|
|
auto value = r_value.move_as_ok();
|
|
if (value.type() != JsonValue::Type::Array) {
|
|
if (value.type() == JsonValue::Type::Number && is_internal) {
|
|
auto r_number = td::to_integer_safe<td::uint32>(value.get_number());
|
|
if (r_number.is_ok() && r_number.ok() > 0) {
|
|
return r_number.ok();
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
for (auto &update_type_name : value.get_array()) {
|
|
if (update_type_name.type() != JsonValue::Type::String) {
|
|
return 0;
|
|
}
|
|
auto type_name = update_type_name.get_string();
|
|
to_lower_inplace(type_name);
|
|
for (int32 i = 0; i < static_cast<int32>(UpdateType::Size); i++) {
|
|
if (get_update_type_name(static_cast<UpdateType>(i)) == type_name) {
|
|
result |= (1 << i);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (result == 0) {
|
|
return DEFAULT_ALLOWED_UPDATE_TYPES;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool Client::update_allowed_update_types(const Query *query) {
|
|
auto allowed_update_types = get_allowed_update_types(query->arg("allowed_updates"), query->is_internal());
|
|
if (allowed_update_types != 0 && allowed_update_types != allowed_update_types_) {
|
|
allowed_update_types_ = allowed_update_types;
|
|
object_ptr<td_api::OptionValue> value;
|
|
if (allowed_update_types == DEFAULT_ALLOWED_UPDATE_TYPES) {
|
|
value = make_object<td_api::optionValueEmpty>();
|
|
} else {
|
|
value = make_object<td_api::optionValueInteger>(allowed_update_types);
|
|
}
|
|
send_request(make_object<td_api::setOption>("xallowed_update_types", std::move(value)),
|
|
std::make_unique<TdOnOkCallback>());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <class T>
|
|
class UpdateJsonable : public td::VirtuallyJsonable {
|
|
public:
|
|
explicit UpdateJsonable(const T &update) : update(update) {
|
|
}
|
|
void store(JsonValueScope *scope) const override {
|
|
*scope << update;
|
|
}
|
|
|
|
private:
|
|
const T &update;
|
|
};
|
|
|
|
template <class T>
|
|
void Client::add_update(UpdateType update_type, const T &update, int32 timeout, int64 webhook_queue_id) {
|
|
add_update_impl(update_type, UpdateJsonable<T>(update), timeout, webhook_queue_id);
|
|
}
|
|
|
|
void Client::add_update_impl(UpdateType update_type, const td::VirtuallyJsonable &update, int32 timeout,
|
|
int64 webhook_queue_id) {
|
|
if (((allowed_update_types_ >> static_cast<int32>(update_type)) & 1) == 0) {
|
|
return;
|
|
}
|
|
|
|
send_closure(stat_actor_, &BotStatActor::add_event<ServerBotStat::Update>, ServerBotStat::Update{}, td::Time::now());
|
|
|
|
const size_t BUF_SIZE = 1 << 16;
|
|
auto buf = td::StackAllocator::alloc(BUF_SIZE);
|
|
td::JsonBuilder jb(td::StringBuilder(buf.as_slice(), true));
|
|
jb.enter_value() << get_update_type_name(update_type);
|
|
jb.string_builder() << ":";
|
|
jb.enter_value() << update;
|
|
if (jb.string_builder().is_error()) {
|
|
LOG(ERROR) << "JSON buffer overflow";
|
|
return;
|
|
}
|
|
|
|
auto update_slice = jb.string_builder().as_cslice();
|
|
auto r_id = parameters_->shared_data_->tqueue_->push(tqueue_id_, update_slice.str(), get_unix_time() + timeout,
|
|
webhook_queue_id, td::TQueue::EventId());
|
|
if (r_id.is_ok()) {
|
|
auto id = r_id.move_as_ok();
|
|
LOG(DEBUG) << "Update " << id << " was added for " << timeout << " seconds: " << update_slice;
|
|
if (webhook_url_.empty()) {
|
|
long_poll_wakeup(false);
|
|
} else {
|
|
send_closure(webhook_id_, &WebhookActor::update);
|
|
}
|
|
} else {
|
|
LOG(DEBUG) << "Update failed to be added with error " << r_id.error() << " for " << timeout
|
|
<< " seconds: " << update_slice;
|
|
}
|
|
}
|
|
|
|
void Client::add_new_message(object_ptr<td_api::message> &&message, bool is_edited) {
|
|
CHECK(message != nullptr);
|
|
|
|
if (message->sending_state_ != nullptr) {
|
|
return;
|
|
}
|
|
|
|
auto chat_id = message->chat_id_;
|
|
if (chat_id == 0) {
|
|
LOG(ERROR) << "Receive invalid chat in " << to_string(message);
|
|
return;
|
|
}
|
|
new_message_queues_[chat_id].queue_.emplace(std::move(message), is_edited);
|
|
process_new_message_queue(chat_id);
|
|
}
|
|
|
|
void Client::add_update_poll(object_ptr<td_api::updatePoll> &&update) {
|
|
CHECK(update != nullptr);
|
|
add_update(UpdateType::Poll, JsonPoll(update->poll_.get(), this), 86400, update->poll_->id_);
|
|
}
|
|
|
|
void Client::add_update_poll_answer(object_ptr<td_api::updatePollAnswer> &&update) {
|
|
CHECK(update != nullptr);
|
|
add_update(UpdateType::PollAnswer, JsonPollAnswer(update.get(), this), 86400, update->poll_id_);
|
|
}
|
|
|
|
void Client::add_new_inline_query(int64 inline_query_id, int32 sender_user_id, object_ptr<td_api::location> location,
|
|
const td::string &query, const td::string &offset) {
|
|
add_update(UpdateType::InlineQuery,
|
|
JsonInlineQuery(inline_query_id, sender_user_id, location.get(), query, offset, this), 30,
|
|
sender_user_id + (static_cast<int64>(1) << 33));
|
|
}
|
|
|
|
void Client::add_new_chosen_inline_result(int32 sender_user_id, object_ptr<td_api::location> location,
|
|
const td::string &query, const td::string &result_id,
|
|
const td::string &inline_message_id) {
|
|
add_update(UpdateType::ChosenInlineResult,
|
|
JsonChosenInlineResult(sender_user_id, location.get(), query, result_id, inline_message_id, this), 600,
|
|
sender_user_id + (static_cast<int64>(2) << 33));
|
|
}
|
|
|
|
void Client::add_new_callback_query(object_ptr<td_api::updateNewCallbackQuery> &&query) {
|
|
CHECK(query != nullptr);
|
|
auto user_id = query->sender_user_id_;
|
|
if (user_id == 0) {
|
|
LOG(ERROR) << "Receive invalid sender in " << to_string(query);
|
|
return;
|
|
}
|
|
new_callback_query_queues_[user_id].queue_.push(std::move(query));
|
|
process_new_callback_query_queue(user_id, 0);
|
|
}
|
|
|
|
void Client::process_new_callback_query_queue(int32 user_id, int state) {
|
|
auto &queue = new_callback_query_queues_[user_id];
|
|
if (queue.has_active_request_) {
|
|
CHECK(state == 0);
|
|
return;
|
|
}
|
|
if (logging_out_ || closing_) {
|
|
new_callback_query_queues_.erase(user_id);
|
|
return;
|
|
}
|
|
while (!queue.queue_.empty()) {
|
|
auto &query = queue.queue_.front();
|
|
int64 chat_id = query->chat_id_;
|
|
int64 message_id = query->message_id_;
|
|
auto message_info = get_message(chat_id, message_id);
|
|
// callback message can be already deleted in the bot outbox
|
|
if (state == 0) {
|
|
if (message_info == nullptr) {
|
|
// get the message from the server
|
|
queue.has_active_request_ = true;
|
|
return send_request(make_object<td_api::getCallbackQueryMessage>(chat_id, message_id, query->id_),
|
|
std::make_unique<TdOnGetCallbackQueryMessageCallback>(this, user_id, state));
|
|
}
|
|
state = 1;
|
|
}
|
|
if (state == 1) {
|
|
auto reply_to_message_id =
|
|
message_info == nullptr || message_info->is_reply_to_message_deleted ? 0 : message_info->reply_to_message_id;
|
|
if (reply_to_message_id > 0 && get_message(chat_id, reply_to_message_id) == nullptr) {
|
|
queue.has_active_request_ = true;
|
|
return send_request(make_object<td_api::getRepliedMessage>(chat_id, message_id),
|
|
std::make_unique<TdOnGetCallbackQueryMessageCallback>(this, user_id, state));
|
|
}
|
|
state = 2;
|
|
}
|
|
if (state == 2) {
|
|
auto message_sticker_set_id = message_info == nullptr ? 0 : get_sticker_set_id(message_info->content);
|
|
if (!have_sticker_set_name(message_sticker_set_id)) {
|
|
queue.has_active_request_ = true;
|
|
return send_request(make_object<td_api::getStickerSet>(message_sticker_set_id),
|
|
std::make_unique<TdOnGetStickerSetCallback>(this, message_sticker_set_id, user_id, 0));
|
|
}
|
|
auto reply_to_message_id =
|
|
message_info == nullptr || message_info->is_reply_to_message_deleted ? 0 : message_info->reply_to_message_id;
|
|
if (reply_to_message_id > 0) {
|
|
auto reply_to_message_info = get_message(chat_id, reply_to_message_id);
|
|
auto reply_sticker_set_id =
|
|
reply_to_message_info == nullptr ? 0 : get_sticker_set_id(reply_to_message_info->content);
|
|
if (!have_sticker_set_name(reply_sticker_set_id)) {
|
|
queue.has_active_request_ = true;
|
|
return send_request(make_object<td_api::getStickerSet>(reply_sticker_set_id),
|
|
std::make_unique<TdOnGetStickerSetCallback>(this, reply_sticker_set_id, user_id, 0));
|
|
}
|
|
}
|
|
}
|
|
CHECK(state == 2);
|
|
|
|
CHECK(user_id == query->sender_user_id_);
|
|
add_update(UpdateType::CallbackQuery,
|
|
JsonCallbackQuery(query->id_, user_id, chat_id, message_id, message_info, query->chat_instance_,
|
|
query->payload_.get(), this),
|
|
150, user_id + (static_cast<int64>(3) << 33));
|
|
|
|
queue.queue_.pop();
|
|
}
|
|
new_callback_query_queues_.erase(user_id);
|
|
}
|
|
|
|
void Client::add_new_inline_callback_query(object_ptr<td_api::updateNewInlineCallbackQuery> &&query) {
|
|
CHECK(query != nullptr);
|
|
add_update(UpdateType::CallbackQuery,
|
|
JsonInlineCallbackQuery(query->id_, query->sender_user_id_, query->inline_message_id_,
|
|
query->chat_instance_, query->payload_.get(), this),
|
|
150, query->sender_user_id_ + (static_cast<int64>(3) << 33));
|
|
}
|
|
|
|
void Client::add_new_shipping_query(object_ptr<td_api::updateNewShippingQuery> &&query) {
|
|
CHECK(query != nullptr);
|
|
add_update(UpdateType::ShippingQuery, JsonShippingQuery(query.get(), this), 150,
|
|
query->sender_user_id_ + (static_cast<int64>(4) << 33));
|
|
}
|
|
|
|
void Client::add_new_pre_checkout_query(object_ptr<td_api::updateNewPreCheckoutQuery> &&query) {
|
|
CHECK(query != nullptr);
|
|
add_update(UpdateType::PreCheckoutQuery, JsonPreCheckoutQuery(query.get(), this), 150,
|
|
query->sender_user_id_ + (static_cast<int64>(4) << 33));
|
|
}
|
|
|
|
void Client::add_new_custom_event(object_ptr<td_api::updateNewCustomEvent> &&event) {
|
|
CHECK(event != nullptr);
|
|
add_update(UpdateType::CustomEvent, JsonCustomJson(event->event_), 600, 0);
|
|
}
|
|
|
|
void Client::add_new_custom_query(object_ptr<td_api::updateNewCustomQuery> &&query) {
|
|
CHECK(query != nullptr);
|
|
int32 timeout = query->timeout_ <= 0 ? 86400 : query->timeout_;
|
|
add_update(UpdateType::CustomQuery, JsonCustomJson(query->data_), timeout, 0);
|
|
}
|
|
|
|
td::int32 Client::choose_added_member_id(const td_api::messageChatAddMembers *message_add_members) const {
|
|
CHECK(message_add_members != nullptr);
|
|
for (auto &member_user_id : message_add_members->member_user_ids_) {
|
|
if (member_user_id == my_id_) {
|
|
return my_id_;
|
|
}
|
|
}
|
|
if (message_add_members->member_user_ids_.empty()) {
|
|
return 0;
|
|
}
|
|
return message_add_members->member_user_ids_[0];
|
|
}
|
|
|
|
bool Client::need_skip_update_message(int64 chat_id, const object_ptr<td_api::message> &message, bool is_edited) const {
|
|
auto chat = get_chat(chat_id);
|
|
CHECK(chat != nullptr);
|
|
if (message->is_outgoing_) {
|
|
switch (message->content_->get_id()) {
|
|
case td_api::messageChatChangeTitle::ID:
|
|
case td_api::messageChatChangePhoto::ID:
|
|
case td_api::messageChatDeletePhoto::ID:
|
|
case td_api::messageChatDeleteMember::ID:
|
|
case td_api::messagePinMessage::ID:
|
|
case td_api::messageProximityAlertTriggered::ID:
|
|
// don't skip
|
|
break;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
int32 message_date = message->edit_date_ == 0 ? message->date_ : message->edit_date_;
|
|
if (message_date <= get_unix_time() - 86400) {
|
|
// don't send messages received/edited more than 1 day ago
|
|
return true;
|
|
}
|
|
|
|
if (chat->type == ChatInfo::Type::Supergroup) {
|
|
auto supergroup_info = get_supergroup_info(chat->supergroup_id);
|
|
if (supergroup_info->status->get_id() == td_api::chatMemberStatusLeft::ID ||
|
|
supergroup_info->status->get_id() == td_api::chatMemberStatusBanned::ID) {
|
|
// if we have left the chat, send only update about leaving the supergroup
|
|
if (message->content_->get_id() == td_api::messageChatDeleteMember::ID) {
|
|
auto user_id = static_cast<const td_api::messageChatDeleteMember *>(message->content_.get())->user_id_;
|
|
return user_id != my_id_;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (supergroup_info->date > message->date_ || authorization_date_ > message->date_) {
|
|
// don't send messages received before join or getting authorization
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (message->ttl_ > 0) {
|
|
return true;
|
|
}
|
|
|
|
switch (message->content_->get_id()) {
|
|
case td_api::messagePhoto::ID: {
|
|
auto message_photo = static_cast<const td_api::messagePhoto *>(message->content_.get());
|
|
if (message_photo->photo_ == nullptr) {
|
|
LOG(ERROR) << "Got empty messagePhoto";
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case td_api::messageChatAddMembers::ID: {
|
|
auto message_add_members = static_cast<const td_api::messageChatAddMembers *>(message->content_.get());
|
|
if (message_add_members->member_user_ids_.empty()) {
|
|
LOG(ERROR) << "Got empty messageChatAddMembers";
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case td_api::messageChatChangePhoto::ID: {
|
|
auto message_change_photo = static_cast<const td_api::messageChatChangePhoto *>(message->content_.get());
|
|
if (message_change_photo->photo_ == nullptr) {
|
|
LOG(ERROR) << "Got empty messageChatChangePhoto";
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case td_api::messageSupergroupChatCreate::ID: {
|
|
if (chat->type != ChatInfo::Type::Supergroup) {
|
|
LOG(ERROR) << "Receive messageSupergroupChatCreate in the non-supergroup chat " << chat_id;
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case td_api::messagePinMessage::ID: {
|
|
auto message_pin_message = static_cast<const td_api::messagePinMessage *>(message->content_.get());
|
|
auto pinned_message_id = message_pin_message->message_id_;
|
|
if (pinned_message_id <= 0) {
|
|
return true;
|
|
}
|
|
const MessageInfo *pinned_message = get_message(chat_id, pinned_message_id);
|
|
if (pinned_message == nullptr) {
|
|
LOG(WARNING) << "Pinned unknown, inaccessible or deleted message " << pinned_message_id;
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case td_api::messageProximityAlertTriggered::ID: {
|
|
auto proximity_alert_triggered =
|
|
static_cast<const td_api::messageProximityAlertTriggered *>(message->content_.get());
|
|
return proximity_alert_triggered->traveler_->get_id() != td_api::messageSenderUser::ID ||
|
|
proximity_alert_triggered->watcher_->get_id() != td_api::messageSenderUser::ID;
|
|
}
|
|
case td_api::messageGameScore::ID:
|
|
return true;
|
|
case td_api::messagePaymentSuccessful::ID:
|
|
return true;
|
|
case td_api::messagePassportDataSent::ID:
|
|
return true;
|
|
case td_api::messageCall::ID:
|
|
return true;
|
|
case td_api::messageUnsupported::ID:
|
|
return true;
|
|
case td_api::messageContactRegistered::ID:
|
|
return true;
|
|
case td_api::messageExpiredPhoto::ID:
|
|
return true;
|
|
case td_api::messageExpiredVideo::ID:
|
|
return true;
|
|
case td_api::messageCustomServiceAction::ID:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (is_edited) {
|
|
const MessageInfo *old_message = get_message(chat_id, message->id_);
|
|
if (old_message != nullptr && !old_message->is_content_changed) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
td::int64 &Client::get_reply_to_message_id(object_ptr<td_api::message> &message) {
|
|
if (message->content_->get_id() == td_api::messagePinMessage::ID) {
|
|
CHECK(message->reply_to_message_id_ == 0);
|
|
return static_cast<td_api::messagePinMessage *>(message->content_.get())->message_id_;
|
|
}
|
|
if (message->reply_in_chat_id_ != message->chat_id_ && message->reply_to_message_id_ != 0) {
|
|
LOG(WARNING) << "Drop reply to message " << message->id_ << " in chat " << message->chat_id_
|
|
<< " from another chat " << message->reply_in_chat_id_;
|
|
message->reply_in_chat_id_ = 0;
|
|
message->reply_to_message_id_ = 0;
|
|
}
|
|
return message->reply_to_message_id_;
|
|
}
|
|
|
|
void Client::set_message_reply_to_message_id(MessageInfo *message_info, int64 reply_to_message_id) {
|
|
if (message_info->reply_to_message_id == reply_to_message_id) {
|
|
return;
|
|
}
|
|
|
|
if (message_info->reply_to_message_id > 0) {
|
|
LOG_IF(ERROR, reply_to_message_id > 0)
|
|
<< "Message " << message_info->id << " in chat " << message_info->chat_id
|
|
<< " has changed reply_to_message from " << message_info->reply_to_message_id << " to " << reply_to_message_id;
|
|
auto it = reply_message_ids_.find({message_info->chat_id, message_info->reply_to_message_id});
|
|
if (it != reply_message_ids_.end()) {
|
|
it->second.erase(message_info->id);
|
|
if (it->second.empty()) {
|
|
reply_message_ids_.erase(it);
|
|
}
|
|
}
|
|
}
|
|
if (reply_to_message_id > 0) {
|
|
reply_message_ids_[{message_info->chat_id, reply_to_message_id}].insert(message_info->id);
|
|
}
|
|
|
|
message_info->reply_to_message_id = reply_to_message_id;
|
|
}
|
|
|
|
td::CSlice Client::get_callback_data(const object_ptr<td_api::InlineKeyboardButtonType> &type) {
|
|
CHECK(type != nullptr);
|
|
switch (type->get_id()) {
|
|
case td_api::inlineKeyboardButtonTypeCallback::ID:
|
|
return static_cast<const td_api::inlineKeyboardButtonTypeCallback *>(type.get())->data_;
|
|
case td_api::inlineKeyboardButtonTypeCallbackWithPassword::ID:
|
|
return static_cast<const td_api::inlineKeyboardButtonTypeCallbackWithPassword *>(type.get())->data_;
|
|
default:
|
|
UNREACHABLE();
|
|
return td::CSlice();
|
|
}
|
|
}
|
|
|
|
bool Client::are_equal_inline_keyboard_buttons(const td_api::inlineKeyboardButton *lhs,
|
|
const td_api::inlineKeyboardButton *rhs) {
|
|
CHECK(lhs != nullptr);
|
|
CHECK(rhs != nullptr);
|
|
if (lhs->text_ != rhs->text_) {
|
|
return false;
|
|
}
|
|
if (lhs->type_->get_id() != rhs->type_->get_id()) {
|
|
return false;
|
|
}
|
|
switch (lhs->type_->get_id()) {
|
|
case td_api::inlineKeyboardButtonTypeUrl::ID: {
|
|
auto lhs_type = static_cast<const td_api::inlineKeyboardButtonTypeUrl *>(lhs->type_.get());
|
|
auto rhs_type = static_cast<const td_api::inlineKeyboardButtonTypeUrl *>(rhs->type_.get());
|
|
return lhs_type->url_ == rhs_type->url_;
|
|
}
|
|
case td_api::inlineKeyboardButtonTypeLoginUrl::ID: {
|
|
auto lhs_type = static_cast<const td_api::inlineKeyboardButtonTypeLoginUrl *>(lhs->type_.get());
|
|
auto rhs_type = static_cast<const td_api::inlineKeyboardButtonTypeLoginUrl *>(rhs->type_.get());
|
|
return lhs_type->url_ == rhs_type->url_; // don't compare id_ and forward_text_
|
|
}
|
|
case td_api::inlineKeyboardButtonTypeCallback::ID:
|
|
case td_api::inlineKeyboardButtonTypeCallbackWithPassword::ID:
|
|
return get_callback_data(lhs->type_) == get_callback_data(rhs->type_);
|
|
case td_api::inlineKeyboardButtonTypeCallbackGame::ID:
|
|
return true;
|
|
case td_api::inlineKeyboardButtonTypeSwitchInline::ID: {
|
|
auto lhs_type = static_cast<const td_api::inlineKeyboardButtonTypeSwitchInline *>(lhs->type_.get());
|
|
auto rhs_type = static_cast<const td_api::inlineKeyboardButtonTypeSwitchInline *>(rhs->type_.get());
|
|
return lhs_type->query_ == rhs_type->query_ && lhs_type->in_current_chat_ == rhs_type->in_current_chat_;
|
|
}
|
|
case td_api::inlineKeyboardButtonTypeBuy::ID:
|
|
return true;
|
|
default:
|
|
UNREACHABLE();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool Client::are_equal_inline_keyboards(const td_api::replyMarkupInlineKeyboard *lhs,
|
|
const td_api::replyMarkupInlineKeyboard *rhs) {
|
|
CHECK(lhs != nullptr);
|
|
CHECK(rhs != nullptr);
|
|
auto &old_rows = lhs->rows_;
|
|
auto &new_rows = rhs->rows_;
|
|
if (old_rows.size() != new_rows.size()) {
|
|
return false;
|
|
}
|
|
for (size_t i = 0; i < old_rows.size(); i++) {
|
|
if (old_rows[i].size() != new_rows[i].size()) {
|
|
return false;
|
|
}
|
|
for (size_t j = 0; j < old_rows[i].size(); j++) {
|
|
if (!are_equal_inline_keyboard_buttons(old_rows[i][j].get(), new_rows[i][j].get())) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Client::set_message_reply_markup(MessageInfo *message_info, object_ptr<td_api::ReplyMarkup> &&reply_markup) {
|
|
if (reply_markup != nullptr && reply_markup->get_id() != td_api::replyMarkupInlineKeyboard::ID) {
|
|
reply_markup = nullptr;
|
|
}
|
|
if (reply_markup == nullptr && message_info->reply_markup == nullptr) {
|
|
return;
|
|
}
|
|
if (reply_markup != nullptr && message_info->reply_markup != nullptr) {
|
|
CHECK(message_info->reply_markup->get_id() == td_api::replyMarkupInlineKeyboard::ID);
|
|
if (are_equal_inline_keyboards(
|
|
static_cast<const td_api::replyMarkupInlineKeyboard *>(message_info->reply_markup.get()),
|
|
static_cast<const td_api::replyMarkupInlineKeyboard *>(reply_markup.get()))) {
|
|
return;
|
|
}
|
|
}
|
|
message_info->reply_markup = std::move(reply_markup);
|
|
message_info->is_content_changed = true;
|
|
}
|
|
|
|
td::int64 Client::get_sticker_set_id(const object_ptr<td_api::MessageContent> &content) {
|
|
if (content->get_id() != td_api::messageSticker::ID) {
|
|
return 0;
|
|
}
|
|
|
|
return static_cast<const td_api::messageSticker *>(content.get())->sticker_->set_id_;
|
|
}
|
|
|
|
bool Client::have_sticker_set_name(int64 sticker_set_id) const {
|
|
return sticker_set_id == 0 || sticker_set_names_.count(sticker_set_id) > 0;
|
|
}
|
|
|
|
Client::Slice Client::get_sticker_set_name(int64 sticker_set_id) const {
|
|
auto it = sticker_set_names_.find(sticker_set_id);
|
|
if (it == sticker_set_names_.end()) {
|
|
return Slice();
|
|
}
|
|
return it->second;
|
|
}
|
|
|
|
void Client::process_new_message_queue(int64 chat_id) {
|
|
auto &queue = new_message_queues_[chat_id];
|
|
if (queue.has_active_request_) {
|
|
return;
|
|
}
|
|
if (logging_out_ || closing_) {
|
|
new_message_queues_.erase(chat_id);
|
|
return;
|
|
}
|
|
while (!queue.queue_.empty()) {
|
|
auto &message_ref = queue.queue_.front().message;
|
|
CHECK(chat_id == message_ref->chat_id_);
|
|
int64 message_id = message_ref->id_;
|
|
int64 reply_to_message_id = get_reply_to_message_id(message_ref);
|
|
if (reply_to_message_id > 0 && get_message(chat_id, reply_to_message_id) == nullptr) {
|
|
queue.has_active_request_ = true;
|
|
return send_request(make_object<td_api::getRepliedMessage>(chat_id, message_id),
|
|
std::make_unique<TdOnGetReplyMessageCallback>(this, chat_id));
|
|
}
|
|
auto message_sticker_set_id = get_sticker_set_id(message_ref->content_);
|
|
if (!have_sticker_set_name(message_sticker_set_id)) {
|
|
queue.has_active_request_ = true;
|
|
return send_request(make_object<td_api::getStickerSet>(message_sticker_set_id),
|
|
std::make_unique<TdOnGetStickerSetCallback>(this, message_sticker_set_id, 0, chat_id));
|
|
}
|
|
if (reply_to_message_id > 0) {
|
|
auto reply_to_message_info = get_message(chat_id, reply_to_message_id);
|
|
CHECK(reply_to_message_info != nullptr);
|
|
auto reply_sticker_set_id = get_sticker_set_id(reply_to_message_info->content);
|
|
if (!have_sticker_set_name(reply_sticker_set_id)) {
|
|
queue.has_active_request_ = true;
|
|
return send_request(make_object<td_api::getStickerSet>(reply_sticker_set_id),
|
|
std::make_unique<TdOnGetStickerSetCallback>(this, reply_sticker_set_id, 0, chat_id));
|
|
}
|
|
}
|
|
|
|
auto message = std::move(message_ref);
|
|
auto is_edited = queue.queue_.front().is_edited;
|
|
queue.queue_.pop();
|
|
if (need_skip_update_message(chat_id, message, is_edited)) {
|
|
add_message(std::move(message));
|
|
continue;
|
|
}
|
|
|
|
auto chat = get_chat(chat_id);
|
|
CHECK(chat != nullptr);
|
|
bool is_channel_post =
|
|
(chat->type == ChatInfo::Type::Supergroup && !get_supergroup_info(chat->supergroup_id)->is_supergroup);
|
|
|
|
UpdateType update_type;
|
|
if (is_channel_post) {
|
|
update_type = is_edited ? UpdateType::EditedChannelPost : UpdateType::ChannelPost;
|
|
} else {
|
|
update_type = is_edited ? UpdateType::EditedMessage : UpdateType::Message;
|
|
}
|
|
|
|
int32 message_date = message->edit_date_ == 0 ? message->date_ : message->edit_date_;
|
|
auto now = get_unix_time();
|
|
auto update_delay_time = now - td::max(message_date, parameters_->shared_data_->get_unix_time(webhook_set_date_));
|
|
const auto UPDATE_DELAY_WARNING_TIME = 10 * 60;
|
|
LOG_IF(ERROR, update_delay_time > UPDATE_DELAY_WARNING_TIME)
|
|
<< "Receive very old update " << get_update_type_name(update_type) << " sent at " << message_date << " to chat "
|
|
<< chat_id << " with a delay of " << update_delay_time << " seconds: " << to_string(message);
|
|
auto left_time = message_date + 86400 - now;
|
|
add_message(std::move(message));
|
|
|
|
auto message_info = get_message(chat_id, message_id);
|
|
CHECK(message_info != nullptr);
|
|
|
|
message_info->is_content_changed = false;
|
|
add_update(update_type, JsonMessage(message_info, true, get_update_type_name(update_type).str(), this), left_time,
|
|
chat_id);
|
|
}
|
|
new_message_queues_.erase(chat_id);
|
|
}
|
|
|
|
void Client::remove_replies_to_message(int64 chat_id, int64 reply_to_message_id, bool only_from_cache) {
|
|
if (!only_from_cache) {
|
|
auto yet_unsent_it = yet_unsent_reply_message_ids_.find({chat_id, reply_to_message_id});
|
|
if (yet_unsent_it != yet_unsent_reply_message_ids_.end()) {
|
|
for (auto message_id : yet_unsent_it->second) {
|
|
auto &message = yet_unsent_messages_[{chat_id, message_id}];
|
|
CHECK(message.reply_to_message_id == reply_to_message_id);
|
|
message.is_reply_to_message_deleted = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto it = reply_message_ids_.find({chat_id, reply_to_message_id});
|
|
if (it == reply_message_ids_.end()) {
|
|
return;
|
|
}
|
|
|
|
if (!only_from_cache) {
|
|
for (auto message_id : it->second) {
|
|
auto message_info = get_message_editable(chat_id, message_id);
|
|
CHECK(message_info != nullptr);
|
|
CHECK(message_info->reply_to_message_id == reply_to_message_id);
|
|
message_info->reply_to_message_id = 0;
|
|
}
|
|
}
|
|
reply_message_ids_.erase(it);
|
|
}
|
|
|
|
void Client::delete_message(int64 chat_id, int64 message_id, bool only_from_cache) {
|
|
remove_replies_to_message(chat_id, message_id, only_from_cache);
|
|
|
|
auto it = messages_.find({chat_id, message_id});
|
|
if (it == messages_.end()) {
|
|
if (yet_unsent_messages_.count({chat_id, message_id}) > 0) {
|
|
// yet unsent message is deleted, possible only if we are trying to write to inaccessible supergroup
|
|
auto chat_info = get_chat(chat_id);
|
|
CHECK(chat_info != nullptr);
|
|
|
|
Status error;
|
|
if (chat_info->type != ChatInfo::Type::Supergroup) {
|
|
LOG(ERROR) << "Yet unsent message " << message_id << " is deleted in the chat " << chat_id;
|
|
error = Status::Error(403, "Forbidden: bot is not a member of the chat");
|
|
} else {
|
|
auto supergroup_info = get_supergroup_info(chat_info->supergroup_id);
|
|
CHECK(supergroup_info != nullptr);
|
|
if (supergroup_info->is_supergroup) {
|
|
error = Status::Error(403, "Forbidden: bot is not a member of the supergroup chat");
|
|
} else {
|
|
error = Status::Error(403, "Forbidden: bot is not a member of the channel chat");
|
|
}
|
|
}
|
|
|
|
on_message_send_failed(chat_id, message_id, 0, std::move(error));
|
|
}
|
|
return;
|
|
}
|
|
|
|
auto message_info = it->second.get();
|
|
CHECK(message_info->lru_next != nullptr);
|
|
message_info->lru_next->lru_prev = message_info->lru_prev;
|
|
message_info->lru_prev->lru_next = message_info->lru_next;
|
|
|
|
set_message_reply_to_message_id(message_info, 0);
|
|
|
|
messages_.erase(it);
|
|
}
|
|
|
|
void Client::schedule_next_delete_messages_lru() {
|
|
CHECK(!next_delete_messages_lru_timeout_.has_timeout());
|
|
next_delete_messages_lru_timeout_.set_callback(Client::delete_messages_lru);
|
|
next_delete_messages_lru_timeout_.set_callback_data(static_cast<void *>(this));
|
|
next_delete_messages_lru_timeout_.set_timeout_in(td::Random::fast(MESSAGES_CACHE_TIME, 2 * MESSAGES_CACHE_TIME));
|
|
}
|
|
|
|
void Client::delete_messages_lru(void *client_void) {
|
|
CHECK(client_void != nullptr);
|
|
auto client = static_cast<Client *>(client_void);
|
|
|
|
auto now = td::Time::now();
|
|
int32 deleted_message_count = 0;
|
|
while (client->messages_lru_root_.lru_next->access_time < now - MESSAGES_CACHE_TIME) {
|
|
auto message = client->messages_lru_root_.lru_next;
|
|
if (client->yet_unsent_reply_message_ids_.count({message->chat_id, message->id})) {
|
|
LOG(DEBUG) << "Force usage of message " << message->id << " in " << message->chat_id;
|
|
client->update_message_lru(message);
|
|
} else {
|
|
client->delete_message(message->chat_id, message->id, true);
|
|
deleted_message_count++;
|
|
}
|
|
}
|
|
|
|
if (deleted_message_count != 0) {
|
|
LOG(DEBUG) << "Delete " << deleted_message_count << " messages from cache";
|
|
}
|
|
client->schedule_next_delete_messages_lru();
|
|
}
|
|
|
|
void Client::update_message_lru(const MessageInfo *message_info) const {
|
|
message_info->access_time = td::Time::now();
|
|
if (message_info->lru_next != nullptr) {
|
|
message_info->lru_next->lru_prev = message_info->lru_prev;
|
|
message_info->lru_prev->lru_next = message_info->lru_next;
|
|
}
|
|
auto prev = messages_lru_root_.lru_prev;
|
|
message_info->lru_prev = prev;
|
|
prev->lru_next = message_info;
|
|
message_info->lru_next = &messages_lru_root_;
|
|
messages_lru_root_.lru_prev = message_info;
|
|
}
|
|
|
|
Client::FullMessageId Client::add_message(object_ptr<td_api::message> &&message, bool force_update_content) {
|
|
CHECK(message != nullptr);
|
|
CHECK(message->sending_state_ == nullptr);
|
|
|
|
int64 chat_id = message->chat_id_;
|
|
int64 message_id = message->id_;
|
|
|
|
LOG(DEBUG) << "Add message " << message_id << " to chat " << chat_id;
|
|
std::unique_ptr<MessageInfo> message_info;
|
|
auto it = messages_.find({chat_id, message_id});
|
|
if (it == messages_.end()) {
|
|
message_info = std::make_unique<MessageInfo>();
|
|
} else {
|
|
message_info = std::move(it->second);
|
|
}
|
|
|
|
update_message_lru(message_info.get());
|
|
|
|
message_info->id = message_id;
|
|
message_info->chat_id = chat_id;
|
|
message_info->date = message->date_;
|
|
message_info->edit_date = message->edit_date_;
|
|
message_info->media_album_id = message->media_album_id_;
|
|
message_info->via_bot_user_id = message->via_bot_user_id_;
|
|
|
|
CHECK(message->sender_ != nullptr);
|
|
switch (message->sender_->get_id()) {
|
|
case td_api::messageSenderUser::ID: {
|
|
auto sender = move_object_as<td_api::messageSenderUser>(message->sender_);
|
|
message_info->sender_user_id = sender->user_id_;
|
|
CHECK(message_info->sender_user_id > 0);
|
|
break;
|
|
}
|
|
case td_api::messageSenderChat::ID: {
|
|
auto sender = move_object_as<td_api::messageSenderChat>(message->sender_);
|
|
message_info->sender_chat_id = sender->chat_id_;
|
|
|
|
auto chat_type = get_chat_type(chat_id);
|
|
if (chat_type != ChatType::Channel) {
|
|
if (message_info->sender_chat_id == chat_id) {
|
|
message_info->sender_user_id = group_anonymous_bot_user_id_;
|
|
} else {
|
|
message_info->sender_user_id = service_notifications_user_id_;
|
|
}
|
|
CHECK(message_info->sender_user_id > 0);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
|
|
message_info->initial_chat_id = 0;
|
|
message_info->initial_sender_user_id = 0;
|
|
message_info->initial_sender_chat_id = 0;
|
|
message_info->initial_send_date = 0;
|
|
message_info->initial_message_id = 0;
|
|
message_info->initial_author_signature = td::string();
|
|
message_info->initial_sender_name = td::string();
|
|
if (message->forward_info_ != nullptr) {
|
|
message_info->initial_send_date = message->forward_info_->date_;
|
|
auto origin = std::move(message->forward_info_->origin_);
|
|
switch (origin->get_id()) {
|
|
case td_api::messageForwardOriginUser::ID: {
|
|
auto forward_info = move_object_as<td_api::messageForwardOriginUser>(origin);
|
|
message_info->initial_sender_user_id = forward_info->sender_user_id_;
|
|
break;
|
|
}
|
|
case td_api::messageForwardOriginChat::ID: {
|
|
auto forward_info = move_object_as<td_api::messageForwardOriginChat>(origin);
|
|
message_info->initial_sender_chat_id = forward_info->sender_chat_id_;
|
|
message_info->initial_author_signature = forward_info->author_signature_;
|
|
break;
|
|
}
|
|
case td_api::messageForwardOriginHiddenUser::ID: {
|
|
auto forward_info = move_object_as<td_api::messageForwardOriginHiddenUser>(origin);
|
|
message_info->initial_sender_name = forward_info->sender_name_;
|
|
break;
|
|
}
|
|
case td_api::messageForwardOriginChannel::ID: {
|
|
auto forward_info = move_object_as<td_api::messageForwardOriginChannel>(origin);
|
|
message_info->initial_chat_id = forward_info->chat_id_;
|
|
message_info->initial_message_id = forward_info->message_id_;
|
|
message_info->initial_author_signature = forward_info->author_signature_;
|
|
break;
|
|
}
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
message_info->author_signature = std::move(message->author_signature_);
|
|
|
|
if (message->reply_in_chat_id_ != chat_id && message->reply_to_message_id_ != 0) {
|
|
LOG(WARNING) << "Drop reply to message " << message_id << " in chat " << chat_id << " from another chat "
|
|
<< message->reply_in_chat_id_;
|
|
message->reply_in_chat_id_ = 0;
|
|
message->reply_to_message_id_ = 0;
|
|
}
|
|
set_message_reply_to_message_id(message_info.get(), message->reply_to_message_id_);
|
|
if (message_info->content == nullptr || force_update_content) {
|
|
message_info->content = std::move(message->content_);
|
|
message_info->is_content_changed = true;
|
|
|
|
auto sticker_set_id = get_sticker_set_id(message_info->content);
|
|
if (!have_sticker_set_name(sticker_set_id)) {
|
|
send_request(make_object<td_api::getStickerSet>(sticker_set_id),
|
|
std::make_unique<TdOnGetStickerSetCallback>(this, sticker_set_id, 0, 0));
|
|
}
|
|
}
|
|
set_message_reply_markup(message_info.get(), std::move(message->reply_markup_));
|
|
|
|
messages_[{chat_id, message_id}] = std::move(message_info);
|
|
message = nullptr;
|
|
|
|
return {chat_id, message_id};
|
|
}
|
|
|
|
void Client::update_message_content(int64 chat_id, int64 message_id, object_ptr<td_api::MessageContent> &&content) {
|
|
auto message_info = get_message_editable(chat_id, message_id);
|
|
if (message_info == nullptr) {
|
|
return;
|
|
}
|
|
LOG(DEBUG) << "Update content of the message " << message_id << " from chat " << chat_id;
|
|
|
|
message_info->content = std::move(content);
|
|
message_info->is_content_changed = true;
|
|
}
|
|
|
|
void Client::on_update_message_edited(int64 chat_id, int64 message_id, int32 edit_date,
|
|
object_ptr<td_api::ReplyMarkup> &&reply_markup) {
|
|
auto message_info = get_message_editable(chat_id, message_id);
|
|
if (message_info == nullptr) {
|
|
return;
|
|
}
|
|
message_info->edit_date = edit_date;
|
|
set_message_reply_markup(message_info, std::move(reply_markup));
|
|
}
|
|
|
|
const Client::MessageInfo *Client::get_message(int64 chat_id, int64 message_id) const {
|
|
auto it = messages_.find({chat_id, message_id});
|
|
if (it == messages_.end()) {
|
|
LOG(DEBUG) << "Not found message " << message_id << " from chat " << chat_id;
|
|
return nullptr;
|
|
}
|
|
LOG(DEBUG) << "Found message " << message_id << " from chat " << chat_id;
|
|
|
|
auto result = it->second.get();
|
|
update_message_lru(result);
|
|
return result;
|
|
}
|
|
|
|
Client::MessageInfo *Client::get_message_editable(int64 chat_id, int64 message_id) {
|
|
auto it = messages_.find({chat_id, message_id});
|
|
if (it == messages_.end()) {
|
|
LOG(DEBUG) << "Not found message " << message_id << " from chat " << chat_id;
|
|
return nullptr;
|
|
}
|
|
LOG(DEBUG) << "Found message " << message_id << " from chat " << chat_id;
|
|
|
|
auto result = it->second.get();
|
|
update_message_lru(result);
|
|
return result;
|
|
}
|
|
|
|
td::string Client::get_chat_member_status(const object_ptr<td_api::ChatMemberStatus> &status) {
|
|
CHECK(status != nullptr);
|
|
switch (status->get_id()) {
|
|
case td_api::chatMemberStatusCreator::ID:
|
|
return "creator";
|
|
case td_api::chatMemberStatusAdministrator::ID:
|
|
return "administrator";
|
|
case td_api::chatMemberStatusMember::ID:
|
|
return "member";
|
|
case td_api::chatMemberStatusRestricted::ID:
|
|
return "restricted";
|
|
case td_api::chatMemberStatusLeft::ID:
|
|
return "left";
|
|
case td_api::chatMemberStatusBanned::ID:
|
|
return "kicked";
|
|
default:
|
|
UNREACHABLE();
|
|
return "";
|
|
}
|
|
}
|
|
|
|
td::string Client::get_passport_element_type(int32 id) {
|
|
switch (id) {
|
|
case td_api::passportElementTypePersonalDetails::ID:
|
|
return "personal_details";
|
|
case td_api::passportElementTypePassport::ID:
|
|
return "passport";
|
|
case td_api::passportElementTypeDriverLicense::ID:
|
|
return "driver_license";
|
|
case td_api::passportElementTypeIdentityCard::ID:
|
|
return "identity_card";
|
|
case td_api::passportElementTypeInternalPassport::ID:
|
|
return "internal_passport";
|
|
case td_api::passportElementTypeAddress::ID:
|
|
return "address";
|
|
case td_api::passportElementTypeUtilityBill::ID:
|
|
return "utility_bill";
|
|
case td_api::passportElementTypeBankStatement::ID:
|
|
return "bank_statement";
|
|
case td_api::passportElementTypeRentalAgreement::ID:
|
|
return "rental_agreement";
|
|
case td_api::passportElementTypePassportRegistration::ID:
|
|
return "passport_registration";
|
|
case td_api::passportElementTypeTemporaryRegistration::ID:
|
|
return "temporary_registration";
|
|
case td_api::passportElementTypePhoneNumber::ID:
|
|
return "phone_number";
|
|
case td_api::passportElementTypeEmailAddress::ID:
|
|
return "email";
|
|
default:
|
|
UNREACHABLE();
|
|
return "None";
|
|
}
|
|
}
|
|
|
|
td_api::object_ptr<td_api::PassportElementType> Client::get_passport_element_type(Slice type) {
|
|
if (type == "personal_details") {
|
|
return make_object<td_api::passportElementTypePersonalDetails>();
|
|
}
|
|
if (type == "passport") {
|
|
return make_object<td_api::passportElementTypePassport>();
|
|
}
|
|
if (type == "driver_license") {
|
|
return make_object<td_api::passportElementTypeDriverLicense>();
|
|
}
|
|
if (type == "identity_card") {
|
|
return make_object<td_api::passportElementTypeIdentityCard>();
|
|
}
|
|
if (type == "internal_passport") {
|
|
return make_object<td_api::passportElementTypeInternalPassport>();
|
|
}
|
|
if (type == "address") {
|
|
return make_object<td_api::passportElementTypeAddress>();
|
|
}
|
|
if (type == "utility_bill") {
|
|
return make_object<td_api::passportElementTypeUtilityBill>();
|
|
}
|
|
if (type == "bank_statement") {
|
|
return make_object<td_api::passportElementTypeBankStatement>();
|
|
}
|
|
if (type == "rental_agreement") {
|
|
return make_object<td_api::passportElementTypeRentalAgreement>();
|
|
}
|
|
if (type == "passport_registration") {
|
|
return make_object<td_api::passportElementTypePassportRegistration>();
|
|
}
|
|
if (type == "temporary_registration") {
|
|
return make_object<td_api::passportElementTypeTemporaryRegistration>();
|
|
}
|
|
if (type == "phone_number") {
|
|
return make_object<td_api::passportElementTypePhoneNumber>();
|
|
}
|
|
if (type == "email") {
|
|
return make_object<td_api::passportElementTypeEmailAddress>();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
td::int32 Client::get_unix_time() const {
|
|
CHECK(was_authorized_);
|
|
return parameters_->shared_data_->get_unix_time(td::Time::now());
|
|
}
|
|
|
|
td::int64 Client::as_tdlib_message_id(int32 message_id) {
|
|
return static_cast<int64>(message_id) << 20;
|
|
}
|
|
|
|
td::int32 Client::as_client_message_id(int64 message_id) {
|
|
int32 result = static_cast<int32>(message_id >> 20);
|
|
CHECK(as_tdlib_message_id(result) == message_id);
|
|
return result;
|
|
}
|
|
|
|
td::int64 Client::get_supergroup_chat_id(int32 supergroup_id) {
|
|
return static_cast<td::int64>(-1000000000000ll) - static_cast<int64>(supergroup_id);
|
|
}
|
|
|
|
td::int64 Client::get_basic_group_chat_id(int32 basic_group_id) {
|
|
return -static_cast<int64>(basic_group_id);
|
|
}
|
|
|
|
constexpr Client::int64 Client::GREAT_MINDS_SET_ID;
|
|
constexpr Client::Slice Client::GREAT_MINDS_SET_NAME;
|
|
|
|
constexpr Client::Slice Client::MASK_POINTS[MASK_POINTS_SIZE];
|
|
|
|
constexpr int Client::LOGGING_OUT_ERROR_CODE;
|
|
constexpr Client::Slice Client::LOGGING_OUT_ERROR_DESCRIPTION;
|
|
|
|
constexpr int Client::CLOSING_ERROR_CODE;
|
|
constexpr Client::Slice Client::CLOSING_ERROR_DESCRIPTION;
|
|
|
|
std::unordered_map<td::string, td::Status (Client::*)(PromisedQueryPtr &query)> Client::methods_;
|
|
|
|
} // namespace telegram_bot_api
|