diff --git a/CMakeLists.txt b/CMakeLists.txt index fadd00d..b52e30d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if (POLICY CMP0065) cmake_policy(SET CMP0065 NEW) endif() -project(TelegramBotApi VERSION 6.7.1 LANGUAGES CXX) +project(TelegramBotApi VERSION 6.9.2 LANGUAGES CXX) if (POLICY CMP0069) option(TELEGRAM_BOT_API_ENABLE_LTO "Use \"ON\" to enable Link Time Optimization.") diff --git a/td b/td index 328b864..9184b3e 160000 --- a/td +++ b/td @@ -1 +1 @@ -Subproject commit 328b8649d859c5ed4088a875cbb059db6029dc0d +Subproject commit 9184b3e62de59663a59d3500528aee7e5f0d83fa diff --git a/telegram-bot-api/Client.cpp b/telegram-bot-api/Client.cpp index 2a71ca3..291458b 100644 --- a/telegram-bot-api/Client.cpp +++ b/telegram-bot-api/Client.cpp @@ -214,8 +214,7 @@ Client::Client(td::ActorShared<> parent, const td::string &bot_token, bool is_us Client::~Client() { td::Scheduler::instance()->destroy_on_scheduler(SharedData::get_file_gc_scheduler_id(), messages_, users_, groups_, - supergroups_, chats_, reply_message_ids_, - yet_unsent_reply_message_ids_, sticker_set_names_); + supergroups_, chats_, sticker_set_names_); } bool Client::init_methods() { @@ -297,6 +296,7 @@ bool Client::init_methods() { methods_.emplace("reopengeneralforumtopic", &Client::process_reopen_general_forum_topic_query); methods_.emplace("hidegeneralforumtopic", &Client::process_hide_general_forum_topic_query); methods_.emplace("unhidegeneralforumtopic", &Client::process_unhide_general_forum_topic_query); + methods_.emplace("unpinallgeneralforumtopicmessages", &Client::process_unpin_all_general_forum_topic_messages_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); @@ -600,7 +600,8 @@ class Client::JsonVectorEntities final : public td::Jsonable { for (auto &entity : entities_) { auto entity_type = entity->type_->get_id(); if (entity_type != td_api::textEntityTypeBankCardNumber::ID && - entity_type != td_api::textEntityTypeMediaTimestamp::ID) { + entity_type != td_api::textEntityTypeMediaTimestamp::ID && + entity_type != td_api::textEntityTypeBlockQuote::ID) { array << JsonEntity(entity.get(), client_); } } @@ -798,6 +799,9 @@ class Client::JsonChat final : public td::Jsonable { } if (user_info->emoji_status_custom_emoji_id != 0) { object("emoji_status_custom_emoji_id", td::to_string(user_info->emoji_status_custom_emoji_id)); + if (user_info->emoji_status_expiration_date != 0) { + object("emoji_status_expiration_date", user_info->emoji_status_expiration_date); + } } if (!user_info->bio.empty()) { object("bio", user_info->bio); @@ -956,7 +960,7 @@ class Client::JsonChat final : public td::Jsonable { 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_; + LOG(INFO) << "Pinned unknown, inaccessible or deleted message " << pinned_message_id_; } } if (chat_info->message_auto_delete_time != 0) { @@ -1511,7 +1515,21 @@ class Client::JsonPollAnswer final : public td::Jsonable { void store(td::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_)); + switch (poll_answer_->voter_id_->get_id()) { + case td_api::messageSenderUser::ID: { + auto user_id = static_cast(poll_answer_->voter_id_.get())->user_id_; + object("user", JsonUser(user_id, client_)); + break; + } + case td_api::messageSenderChat::ID: { + auto voter_chat_id = static_cast(poll_answer_->voter_id_.get())->chat_id_; + object("user", JsonUser(client_->channel_bot_user_id_, client_)); + object("voter_chat", JsonChat(voter_chat_id, false, client_)); + break; + } + default: + UNREACHABLE(); + } object("option_ids", td::json_array(poll_answer_->option_ids_, [](int32 option_id) { return option_id; })); } @@ -1847,8 +1865,20 @@ class Client::JsonWriteAccessAllowed final : public td::Jsonable { } void store(td::JsonValueScope *scope) const { auto object = scope->enter_object(); - if (write_access_allowed_->web_app_ != nullptr) { - object("web_app_name", write_access_allowed_->web_app_->short_name_); + switch (write_access_allowed_->reason_->get_id()) { + case td_api::botWriteAccessAllowReasonLaunchedWebApp::ID: + object("web_app_name", static_cast( + write_access_allowed_->reason_.get()) + ->web_app_->short_name_); + break; + case td_api::botWriteAccessAllowReasonAcceptedRequest::ID: + object("from_request", td::JsonTrue()); + break; + case td_api::botWriteAccessAllowReasonAddedToAttachmentMenu::ID: + object("from_attachment_menu", td::JsonTrue()); + break; + default: + UNREACHABLE(); } } @@ -2070,15 +2100,23 @@ void Client::JsonMessage::store(td::JsonValueScope *scope) const { } 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, true); - 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 (need_reply_) { + auto reply_to_message_id = + get_same_chat_reply_to_message_id(message_->reply_to_message.get(), message_->message_thread_id); + if (reply_to_message_id > 0) { + // internal reply + const MessageInfo *reply_to_message = client_->get_message(message_->chat_id, reply_to_message_id, true); + if (reply_to_message != nullptr) { + object("reply_to_message", JsonMessage(reply_to_message, false, "reply in " + source_, client_)); + } else { + LOG(INFO) << "Replied to unknown or deleted message " << reply_to_message_id << " in chat " << message_->chat_id + << " while storing " << source_ << ' ' << message_->id; + } } } + if (message_->reply_to_message != nullptr && message_->reply_to_message->origin_ != nullptr) { + // external reply + } if (message_->media_album_id != 0) { object("media_group_id", td::to_string(message_->media_album_id)); } @@ -2293,8 +2331,8 @@ void Client::JsonMessage::store(td::JsonValueScope *scope) const { const MessageInfo *pinned_message = client_->get_message(message_->chat_id, message_id, true); 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; + } else if (need_reply_) { + LOG(INFO) << "Pinned unknown, inaccessible or deleted message " << message_id; } } break; @@ -2332,18 +2370,6 @@ void Client::JsonMessage::store(td::JsonValueScope *scope) const { case td_api::messageAnimatedEmoji::ID: UNREACHABLE(); break; - case td_api::messageWebsiteConnected::ID: { - auto chat = client_->get_chat(message_->chat_id); - if (chat->type != ChatInfo::Type::Private) { - break; - } - - auto content = static_cast(message_->content.get()); - if (!content->domain_name_.empty()) { - object("connected_website", content->domain_name_); - } - break; - } case td_api::messagePassportDataSent::ID: break; case td_api::messagePassportDataReceived::ID: { @@ -2390,8 +2416,20 @@ void Client::JsonMessage::store(td::JsonValueScope *scope) const { case td_api::messageSuggestProfilePhoto::ID: break; case td_api::messageBotWriteAccessAllowed::ID: { + auto chat = client_->get_chat(message_->chat_id); + if (chat->type != ChatInfo::Type::Private) { + break; + } + auto content = static_cast(message_->content.get()); - object("write_access_allowed", JsonWriteAccessAllowed(content)); + if (content->reason_->get_id() == td_api::botWriteAccessAllowReasonConnectedWebsite::ID) { + auto reason = static_cast(content->reason_.get()); + if (!reason->domain_name_.empty()) { + object("connected_website", reason->domain_name_); + } + } else { + object("write_access_allowed", JsonWriteAccessAllowed(content)); + } break; } case td_api::messageUserShared::ID: { @@ -2406,6 +2444,15 @@ void Client::JsonMessage::store(td::JsonValueScope *scope) const { } case td_api::messageChatSetBackground::ID: break; + case td_api::messagePremiumGiftCode::ID: + break; + case td_api::messagePremiumGiveawayCreated::ID: + break; + case td_api::messagePremiumGiveaway::ID: + break; + case td_api::messageStory::ID: + object("story", JsonEmptyObject()); + break; default: UNREACHABLE(); } @@ -5039,16 +5086,17 @@ void Client::on_get_reply_message(int64 chat_id, object_ptr rep CHECK(!queue.queue_.empty()); object_ptr &message = queue.queue_.front().message; CHECK(chat_id == message->chat_id_); - int64 &reply_to_message_id = get_reply_to_message_id(message); + int64 reply_to_message_id = get_same_chat_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; + if (chat_id != reply_to_message->chat_id_ || reply_to_message_id != reply_to_message->id_) { + LOG(ERROR) << "Expect to get replied message " << reply_to_message_id << " in " << chat_id << ", but receive " + << reply_to_message->id_ << " in " << reply_to_message->chat_id_; + } + LOG(INFO) << "Receive reply to message " << reply_to_message->id_ << " in chat " << reply_to_message->chat_id_; add_message(std::move(reply_to_message)); } @@ -5078,16 +5126,17 @@ void Client::on_get_callback_query_message(object_ptr message, << ". It may be already deleted"; } else { CHECK(state == 1); - auto message_info = get_message_editable(chat_id, message_id); + auto message_info = get_message(chat_id, message_id, true); 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; + auto reply_to_message_id = + get_same_chat_reply_to_message_id(message_info->reply_to_message.get(), message_info->message_thread_id); + LOG(INFO) << "Can't find callback query reply to message " << reply_to_message_id << " in chat " << chat_id + << ". It may be already deleted"; } } else { LOG(INFO) << "Receive callback query " << (state == 1 ? "reply to " : "") << "message " << message_id << " in chat " @@ -5451,13 +5500,13 @@ void Client::fix_inline_query_results_bot_user_ids( } void Client::resolve_bot_usernames(PromisedQueryPtr query, td::Promise on_success) { - CHECK(!unresolved_bot_usernames_.empty()); + CHECK(!bot_user_ids_.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.pending_resolve_count = bot_user_ids_.unresolved_bot_usernames_.size(); pending_query.query = std::move(query); pending_query.on_success = std::move(on_success); - for (auto &username : unresolved_bot_usernames_) { + for (auto &username : bot_user_ids_.unresolved_bot_usernames_) { auto &query_ids = awaiting_bot_resolve_queries_[username]; query_ids.push_back(query_id); if (query_ids.size() == 1) { @@ -5465,13 +5514,13 @@ void Client::resolve_bot_usernames(PromisedQueryPtr query, td::Promise(this, username)); } } - unresolved_bot_usernames_.clear(); + bot_user_ids_.unresolved_bot_usernames_.clear(); } template void Client::resolve_reply_markup_bot_usernames(object_ptr reply_markup, PromisedQueryPtr query, OnSuccess on_success) { - if (!unresolved_bot_usernames_.empty()) { + if (!bot_user_ids_.unresolved_bot_usernames_.empty()) { CHECK(reply_markup != nullptr); CHECK(reply_markup->get_id() == td_api::replyMarkupInlineKeyboard::ID); return resolve_bot_usernames( @@ -5490,7 +5539,7 @@ void Client::resolve_reply_markup_bot_usernames(object_ptr template void Client::resolve_inline_query_results_bot_usernames(td::vector> results, PromisedQueryPtr query, OnSuccess on_success) { - if (!unresolved_bot_usernames_.empty()) { + if (!bot_user_ids_.unresolved_bot_usernames_.empty()) { return resolve_bot_usernames( std::move(query), td::PromiseCreator::lambda([this, results = std::move(results), @@ -5512,9 +5561,9 @@ void Client::on_resolve_bot_username(const td::string &username, int64 user_id) awaiting_bot_resolve_queries_.erase(query_ids_it); if (user_id == 0) { - bot_user_ids_.erase(username); + bot_user_ids_.bot_user_ids_.erase(username); } else { - auto &temp_bot_user_id = bot_user_ids_[username]; + auto &temp_bot_user_id = bot_user_ids_.bot_user_ids_[username]; temp_to_real_bot_user_id_[temp_bot_user_id] = user_id; temp_bot_user_id = user_id; } @@ -5695,6 +5744,7 @@ void Client::on_update_authorization_state() { td::reset_to_empty(pending_updates_); } last_update_creation_time_ = td::Time::now(); + log_in_date_ = get_unix_time(); } return loop(); } @@ -5776,7 +5826,7 @@ void Client::on_update(object_ptr result) { case td_api::updateMessageSendFailed::ID: { auto update = move_object_as(result); on_message_send_failed(update->message_->chat_id_, update->old_message_id_, update->message_->id_, - td::Status::Error(update->error_code_, update->error_message_)); + std::move(update->error_)); break; } case td_api::updateMessageContent::ID: { @@ -5822,14 +5872,12 @@ void Client::on_update(object_ptr result) { auto update = move_object_as(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 type = move_object_as(chat->type_); chat_info->type = ChatInfo::Type::Private; auto user_id = type->user_id_; chat_info->user_id = user_id; - need_warning = get_user_info(user_id) == nullptr; break; } case td_api::chatTypeBasicGroup::ID: { @@ -5837,7 +5885,6 @@ void Client::on_update(object_ptr result) { chat_info->type = ChatInfo::Type::Group; auto group_id = type->basic_group_id_; chat_info->group_id = group_id; - need_warning = get_group_info(group_id) == nullptr; break; } case td_api::chatTypeSupergroup::ID: { @@ -5845,7 +5892,6 @@ void Client::on_update(object_ptr result) { chat_info->type = ChatInfo::Type::Supergroup; auto supergroup_id = type->supergroup_id_; chat_info->supergroup_id = supergroup_id; - need_warning = get_supergroup_info(supergroup_id) == nullptr; break; } case td_api::chatTypeSecret::ID: @@ -5854,9 +5900,6 @@ void Client::on_update(object_ptr result) { 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_info = std::move(chat->photo_); @@ -5977,6 +6020,7 @@ void Client::on_update(object_ptr result) { } else { CHECK(update->value_->get_id() == td_api::optionValueInteger::ID); my_id_ = move_object_as(update->value_)->value_; + bot_user_ids_.default_bot_user_id_ = my_id_; } } if (name == "group_anonymous_bot_user_id" && update->value_->get_id() == td_api::optionValueInteger::ID) { @@ -6203,30 +6247,37 @@ bool Client::to_bool(td::MutableSlice value) { return value == "true" || value == "yes" || value == "1"; } +td_api::object_ptr Client::get_input_message_reply_to(int64 reply_to_message_id) { + if (reply_to_message_id > 0) { + return make_object(0, reply_to_message_id, nullptr); + } + return nullptr; +} + td::Result> Client::get_keyboard_button(td::JsonValue &button) { if (button.type() == td::JsonValue::Type::Object) { auto &object = button.get_object(); - TRY_RESULT(text, get_json_object_string_field(object, "text", false)); + TRY_RESULT(text, object.get_required_string_field("text")); - 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")); + TRY_RESULT(request_phone_number, object.get_optional_bool_field("request_phone_number")); + TRY_RESULT(request_contact, object.get_optional_bool_field("request_contact")); if (request_phone_number || request_contact) { return make_object(text, make_object()); } - TRY_RESULT(request_location, get_json_object_bool_field(object, "request_location")); + TRY_RESULT(request_location, object.get_optional_bool_field("request_location")); if (request_location) { return make_object(text, make_object()); } - if (has_json_object_field(object, "request_poll")) { + if (object.has_field("request_poll")) { bool force_regular = false; bool force_quiz = false; - TRY_RESULT(request_poll, get_json_object_field(object, "request_poll", td::JsonValue::Type::Object, false)); + TRY_RESULT(request_poll, object.extract_required_field("request_poll", td::JsonValue::Type::Object)); 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 (request_poll_object.has_field("type")) { + TRY_RESULT(type, request_poll_object.get_optional_string_field("type")); if (type == "quiz") { force_quiz = true; } else if (type == "regular") { @@ -6237,47 +6288,48 @@ td::Result> Client::get_keyboard_butt text, make_object(force_regular, force_quiz)); } - if (has_json_object_field(object, "web_app")) { - TRY_RESULT(web_app, get_json_object_field(object, "web_app", td::JsonValue::Type::Object, false)); + if (object.has_field("web_app")) { + TRY_RESULT(web_app, object.extract_required_field("web_app", td::JsonValue::Type::Object)); auto &web_app_object = web_app.get_object(); - TRY_RESULT(url, get_json_object_string_field(web_app_object, "url", false)); + TRY_RESULT(url, web_app_object.get_required_string_field("url")); return make_object(text, make_object(url)); } - if (has_json_object_field(object, "request_user")) { - TRY_RESULT(request_user, get_json_object_field(object, "request_user", td::JsonValue::Type::Object, false)); + if (object.has_field("request_user")) { + TRY_RESULT(request_user, object.extract_required_field("request_user", td::JsonValue::Type::Object)); auto &request_user_object = request_user.get_object(); - TRY_RESULT(id, get_json_object_int_field(request_user_object, "request_id", false)); - auto restrict_user_is_bot = has_json_object_field(request_user_object, "user_is_bot"); - TRY_RESULT(user_is_bot, get_json_object_bool_field(request_user_object, "user_is_bot")); - auto restrict_user_is_premium = has_json_object_field(request_user_object, "user_is_premium"); - TRY_RESULT(user_is_premium, get_json_object_bool_field(request_user_object, "user_is_premium")); + TRY_RESULT(id, request_user_object.get_required_int_field("request_id")); + auto restrict_user_is_bot = request_user_object.has_field("user_is_bot"); + TRY_RESULT(user_is_bot, request_user_object.get_optional_bool_field("user_is_bot")); + auto restrict_user_is_premium = request_user_object.has_field("user_is_premium"); + TRY_RESULT(user_is_premium, request_user_object.get_optional_bool_field("user_is_premium")); return make_object( text, make_object(id, restrict_user_is_bot, user_is_bot, restrict_user_is_premium, user_is_premium)); } - if (has_json_object_field(object, "request_chat")) { - TRY_RESULT(request_chat, get_json_object_field(object, "request_chat", td::JsonValue::Type::Object, false)); + if (object.has_field("request_chat")) { + TRY_RESULT(request_chat, object.extract_required_field("request_chat", td::JsonValue::Type::Object)); auto &request_chat_object = request_chat.get_object(); - TRY_RESULT(id, get_json_object_int_field(request_chat_object, "request_id", false)); - TRY_RESULT(chat_is_channel, get_json_object_bool_field(request_chat_object, "chat_is_channel")); - auto restrict_chat_is_forum = has_json_object_field(request_chat_object, "chat_is_forum"); - TRY_RESULT(chat_is_forum, get_json_object_bool_field(request_chat_object, "chat_is_forum")); - auto restrict_chat_has_username = has_json_object_field(request_chat_object, "chat_has_username"); - TRY_RESULT(chat_has_username, get_json_object_bool_field(request_chat_object, "chat_has_username")); - TRY_RESULT(chat_is_created, get_json_object_bool_field(request_chat_object, "chat_is_created")); + TRY_RESULT(id, request_chat_object.get_required_int_field("request_id")); + TRY_RESULT(chat_is_channel, request_chat_object.get_optional_bool_field("chat_is_channel")); + auto restrict_chat_is_forum = request_chat_object.has_field("chat_is_forum"); + TRY_RESULT(chat_is_forum, request_chat_object.get_optional_bool_field("chat_is_forum")); + auto restrict_chat_has_username = request_chat_object.has_field("chat_has_username"); + TRY_RESULT(chat_has_username, request_chat_object.get_optional_bool_field("chat_has_username")); + TRY_RESULT(chat_is_created, request_chat_object.get_optional_bool_field("chat_is_created")); object_ptr user_administrator_rights; - if (has_json_object_field(request_chat_object, "user_administrator_rights")) { - TRY_RESULT_ASSIGN(user_administrator_rights, get_chat_administrator_rights(get_json_object_field_force( - request_chat_object, "user_administrator_rights"))); + if (request_chat_object.has_field("user_administrator_rights")) { + TRY_RESULT_ASSIGN( + user_administrator_rights, + get_chat_administrator_rights(request_chat_object.extract_field("user_administrator_rights"))); } object_ptr bot_administrator_rights; - if (has_json_object_field(request_chat_object, "bot_administrator_rights")) { - TRY_RESULT_ASSIGN(bot_administrator_rights, get_chat_administrator_rights(get_json_object_field_force( - request_chat_object, "bot_administrator_rights"))); + if (request_chat_object.has_field("bot_administrator_rights")) { + TRY_RESULT_ASSIGN(bot_administrator_rights, + get_chat_administrator_rights(request_chat_object.extract_field("bot_administrator_rights"))); } - TRY_RESULT(bot_is_member, get_json_object_bool_field(request_chat_object, "bot_is_member")); + TRY_RESULT(bot_is_member, request_chat_object.get_optional_bool_field("bot_is_member")); return make_object( text, make_object( id, chat_is_channel, restrict_chat_is_forum, chat_is_forum, restrict_chat_has_username, @@ -6294,79 +6346,80 @@ td::Result> Client::get_keyboard_butt return td::Status::Error(400, "KeyboardButton must be a String or an Object"); } -td::Result> Client::get_inline_keyboard_button(td::JsonValue &button) { +td::Result> Client::get_inline_keyboard_button( + td::JsonValue &button, BotUserIds &bot_user_ids) { if (button.type() != td::JsonValue::Type::Object) { return td::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(text, object.get_required_string_field("text")); { - TRY_RESULT(url, get_json_object_string_field(object, "url")); + TRY_RESULT(url, object.get_optional_string_field("url")); if (!url.empty()) { return make_object(text, make_object(url)); } } { - TRY_RESULT(callback_data, get_json_object_string_field(object, "callback_data")); + TRY_RESULT(callback_data, object.get_optional_string_field("callback_data")); if (!callback_data.empty()) { return make_object( text, make_object(callback_data)); } } - if (has_json_object_field(object, "callback_game")) { + if (object.has_field("callback_game")) { return make_object(text, make_object()); } - if (has_json_object_field(object, "pay")) { + if (object.has_field("pay")) { return make_object(text, make_object()); } - if (has_json_object_field(object, "switch_inline_query")) { - TRY_RESULT(switch_inline_query, get_json_object_string_field(object, "switch_inline_query", false)); + if (object.has_field("switch_inline_query")) { + TRY_RESULT(switch_inline_query, object.get_required_string_field("switch_inline_query")); return make_object( text, make_object( switch_inline_query, td_api::make_object(true, true, true, true))); } - if (has_json_object_field(object, "switch_inline_query_chosen_chat")) { + if (object.has_field("switch_inline_query_chosen_chat")) { TRY_RESULT(switch_inline_query, - get_json_object_field(object, "switch_inline_query_chosen_chat", td::JsonValue::Type::Object, false)); + object.extract_required_field("switch_inline_query_chosen_chat", td::JsonValue::Type::Object)); CHECK(switch_inline_query.type() == td::JsonValue::Type::Object); auto &switch_inline_query_object = switch_inline_query.get_object(); - TRY_RESULT(query, get_json_object_string_field(switch_inline_query_object, "query")); - TRY_RESULT(allow_user_chats, get_json_object_bool_field(switch_inline_query_object, "allow_user_chats")); - TRY_RESULT(allow_bot_chats, get_json_object_bool_field(switch_inline_query_object, "allow_bot_chats")); - TRY_RESULT(allow_group_chats, get_json_object_bool_field(switch_inline_query_object, "allow_group_chats")); - TRY_RESULT(allow_channel_chats, get_json_object_bool_field(switch_inline_query_object, "allow_channel_chats")); + TRY_RESULT(query, switch_inline_query_object.get_optional_string_field("query")); + TRY_RESULT(allow_user_chats, switch_inline_query_object.get_optional_bool_field("allow_user_chats")); + TRY_RESULT(allow_bot_chats, switch_inline_query_object.get_optional_bool_field("allow_bot_chats")); + TRY_RESULT(allow_group_chats, switch_inline_query_object.get_optional_bool_field("allow_group_chats")); + TRY_RESULT(allow_channel_chats, switch_inline_query_object.get_optional_bool_field("allow_channel_chats")); return make_object( text, make_object( query, td_api::make_object(allow_user_chats, allow_bot_chats, allow_group_chats, allow_channel_chats))); } - 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)); + if (object.has_field("switch_inline_query_current_chat")) { + TRY_RESULT(switch_inline_query, object.get_required_string_field("switch_inline_query_current_chat")); return make_object( text, make_object( switch_inline_query, td_api::make_object())); } - if (has_json_object_field(object, "login_url")) { - TRY_RESULT(login_url, get_json_object_field(object, "login_url", td::JsonValue::Type::Object, false)); + if (object.has_field("login_url")) { + TRY_RESULT(login_url, object.extract_required_field("login_url", td::JsonValue::Type::Object)); CHECK(login_url.type() == td::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")); + TRY_RESULT(url, login_url_object.get_required_string_field("url")); + TRY_RESULT(bot_username, login_url_object.get_optional_string_field("bot_username")); + TRY_RESULT(request_write_access, login_url_object.get_optional_bool_field("request_write_access")); + TRY_RESULT(forward_text, login_url_object.get_optional_string_field("forward_text")); int64 bot_user_id = 0; if (bot_username.empty()) { - bot_user_id = my_id_; + bot_user_id = bot_user_ids.default_bot_user_id_; } else { if (bot_username[0] == '@') { bot_username = bot_username.substr(1); @@ -6379,16 +6432,13 @@ td::Result> Client::get_inline_ return td::Status::Error(400, "LoginUrl bot username is invalid"); } } - if (cur_temp_bot_user_id_ >= 100000) { - return td::Status::Error(400, "Too many different LoginUrl bot usernames"); - } - auto &user_id = bot_user_ids_[bot_username]; + auto &user_id = bot_user_ids.bot_user_ids_[bot_username]; if (user_id == 0) { - user_id = cur_temp_bot_user_id_++; + user_id = bot_user_ids.cur_temp_bot_user_id_++; user_id *= 1000; } if (user_id % 1000 == 0) { - unresolved_bot_usernames_.insert(bot_username); + bot_user_ids.unresolved_bot_usernames_.insert(bot_username); } bot_user_id = user_id; } @@ -6399,17 +6449,18 @@ td::Result> Client::get_inline_ text, make_object(url, bot_user_id, forward_text)); } - if (has_json_object_field(object, "web_app")) { - TRY_RESULT(web_app, get_json_object_field(object, "web_app", td::JsonValue::Type::Object, false)); + if (object.has_field("web_app")) { + TRY_RESULT(web_app, object.extract_required_field("web_app", td::JsonValue::Type::Object)); auto &web_app_object = web_app.get_object(); - TRY_RESULT(url, get_json_object_string_field(web_app_object, "url", false)); + TRY_RESULT(url, web_app_object.get_required_string_field("url")); return make_object(text, make_object(url)); } return td::Status::Error(400, "Text buttons are unallowed in the inline keyboard"); } -td::Result> Client::get_reply_markup(const Query *query) { +td::Result> Client::get_reply_markup(const Query *query, + BotUserIds &bot_user_ids) { auto reply_markup = query->arg("reply_markup"); if (reply_markup.empty()) { return nullptr; @@ -6422,121 +6473,83 @@ td::Result> Client::get_reply_markup(con return td::Status::Error(400, "Can't parse reply keyboard markup JSON object"); } - return get_reply_markup(r_value.move_as_ok()); + return get_reply_markup(r_value.move_as_ok(), bot_user_ids); } -td::Result> Client::get_reply_markup(td::JsonValue &&value) { - td::vector>> rows; - td::vector>> inline_rows; - td::Slice input_field_placeholder; - bool resize_keyboard = false; - bool is_persistent = false; - bool one_time = false; - bool remove = false; - bool is_personal = false; - bool force_reply = false; - +td::Result> Client::get_reply_markup(td::JsonValue &&value, + BotUserIds &bot_user_ids) { if (value.type() != td::JsonValue::Type::Object) { return td::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() != td::JsonValue::Type::Array) { - return td::Status::Error(400, "Field \"keyboard\" of the ReplyKeyboardMarkup must be an Array"); - } - for (auto &row : keyboard.get_array()) { - td::vector> new_row; - if (row.type() != td::JsonValue::Type::Array) { - return td::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 td::Status::Error(400, PSLICE() << "Can't parse keyboard button: " << r_button.error().message()); - } - new_row.push_back(r_button.move_as_ok()); - } + auto &object = value.get_object(); - rows.push_back(std::move(new_row)); + td::vector>> rows; + TRY_RESULT(keyboard, object.extract_optional_field("keyboard", td::JsonValue::Type::Array)); + if (keyboard.type() == td::JsonValue::Type::Array) { + for (auto &row : keyboard.get_array()) { + td::vector> new_row; + if (row.type() != td::JsonValue::Type::Array) { + return td::Status::Error(400, "Field \"keyboard\" must be an Array of Arrays"); } - } else if (field_value.first == "inline_keyboard") { - auto inline_keyboard = std::move(field_value.second); - if (inline_keyboard.type() != td::JsonValue::Type::Array) { - return td::Status::Error(400, "Field \"inline_keyboard\" of the InlineKeyboardMarkup must be an Array"); + for (auto &button : row.get_array()) { + auto r_button = get_keyboard_button(button); + if (r_button.is_error()) { + return td::Status::Error(400, PSLICE() << "Can't parse keyboard button: " << r_button.error().message()); + } + new_row.push_back(r_button.move_as_ok()); } - for (auto &inline_row : inline_keyboard.get_array()) { - td::vector> new_inline_row; - if (inline_row.type() != td::JsonValue::Type::Array) { - return td::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 td::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() != td::JsonValue::Type::Boolean) { - return td::Status::Error(400, - "Field \"resize_keyboard\" of the ReplyKeyboardMarkup must be of the type Boolean"); - } - resize_keyboard = field_value.second.get_boolean(); - } else if (field_value.first == "is_persistent") { - if (field_value.second.type() != td::JsonValue::Type::Boolean) { - return td::Status::Error(400, "Field \"is_persistent\" of the ReplyKeyboardMarkup must be of the type Boolean"); - } - is_persistent = field_value.second.get_boolean(); - } else if (field_value.first == "one_time_keyboard") { - if (field_value.second.type() != td::JsonValue::Type::Boolean) { - return td::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() != td::JsonValue::Type::Boolean) { - return td::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() != td::JsonValue::Type::Boolean) { - return td::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() != td::JsonValue::Type::Boolean) { - return td::Status::Error(400, "Field \"force_reply\" of the reply markup must be of the type Boolean"); - } - force_reply = field_value.second.get_boolean(); - } else if (field_value.first == "input_field_placeholder") { - if (field_value.second.type() != td::JsonValue::Type::String) { - return td::Status::Error(400, - "Field \"input_field_placeholder\" of the reply markup must be of the type String"); - } - input_field_placeholder = field_value.second.get_string(); + rows.push_back(std::move(new_row)); } } + td::vector>> inline_rows; + TRY_RESULT(inline_keyboard, object.extract_optional_field("inline_keyboard", td::JsonValue::Type::Array)); + if (inline_keyboard.type() == td::JsonValue::Type::Array) { + for (auto &inline_row : inline_keyboard.get_array()) { + td::vector> new_inline_row; + if (inline_row.type() != td::JsonValue::Type::Array) { + return td::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, bot_user_ids); + if (r_button.is_error()) { + return td::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)); + } + } + + TRY_RESULT(hide_keyboard, object.get_optional_bool_field("hide_keyboard")); + TRY_RESULT(remove_keyboard, object.get_optional_bool_field("remove_keyboard")); + TRY_RESULT(personal_keyboard, object.get_optional_bool_field("personal_keyboard")); + TRY_RESULT(selective, object.get_optional_bool_field("selective")); + TRY_RESULT(force_reply_keyboard, object.get_optional_bool_field("force_reply_keyboard")); + TRY_RESULT(force_reply, object.get_optional_bool_field("force_reply")); + TRY_RESULT(input_field_placeholder, object.get_optional_string_field("input_field_placeholder")); + bool is_personal = personal_keyboard || selective; + object_ptr result; if (!rows.empty()) { - result = make_object(std::move(rows), is_persistent, resize_keyboard, one_time, - is_personal, input_field_placeholder.str()); + TRY_RESULT(resize_keyboard, object.get_optional_bool_field("resize_keyboard")); + TRY_RESULT(one_time_keyboard, object.get_optional_bool_field("one_time_keyboard")); + TRY_RESULT(is_persistent, object.get_optional_bool_field("is_persistent")); + result = make_object(std::move(rows), is_persistent, resize_keyboard, + one_time_keyboard, is_personal, input_field_placeholder); } else if (!inline_rows.empty()) { result = make_object(std::move(inline_rows)); - } else if (remove) { + } else if (hide_keyboard || remove_keyboard) { result = make_object(is_personal); - } else if (force_reply) { - result = make_object(is_personal, input_field_placeholder.str()); + } else if (force_reply || force_reply_keyboard) { + result = make_object(is_personal, input_field_placeholder); } if (result == nullptr || result->get_id() != td_api::replyMarkupInlineKeyboard::ID) { - unresolved_bot_usernames_.clear(); + bot_user_ids.unresolved_bot_usernames_.clear(); } return std::move(result); @@ -6549,25 +6562,13 @@ td::Result> Client::get_labeled_pri auto &object = value.get_object(); - TRY_RESULT(label, get_json_object_string_field(object, "label", false)); + TRY_RESULT(label, object.get_required_string_field("label")); if (label.empty()) { return td::Status::Error(400, "LabeledPrice label must be non-empty"); } - TRY_RESULT(amount, get_json_object_field(object, "amount", td::JsonValue::Type::Null, false)); - td::Slice number; - if (amount.type() == td::JsonValue::Type::Number) { - number = amount.get_number(); - } else if (amount.type() == td::JsonValue::Type::String) { - number = amount.get_string(); - } else { - return td::Status::Error(400, "Field \"amount\" must be of type Number or String"); - } - auto parsed_amount = td::to_integer_safe(number); - if (parsed_amount.is_error()) { - return td::Status::Error(400, "Can't parse \"amount\" as Number"); - } - return make_object(label, parsed_amount.ok()); + TRY_RESULT(amount, object.get_required_long_field("amount")); + return make_object(label, amount); } td::Result>> Client::get_labeled_price_parts( @@ -6622,17 +6623,17 @@ td::Result> Client::get_shipping_opti auto &object = option.get_object(); - TRY_RESULT(id, get_json_object_string_field(object, "id", false)); + TRY_RESULT(id, object.get_required_string_field("id")); if (id.empty()) { return td::Status::Error(400, "ShippingOption identifier must be non-empty"); } - TRY_RESULT(title, get_json_object_string_field(object, "title", false)); + TRY_RESULT(title, object.get_required_string_field("title")); if (title.empty()) { return td::Status::Error(400, "ShippingOption title must be non-empty"); } - TRY_RESULT(prices_json, get_json_object_field(object, "prices", td::JsonValue::Type::Array, false)); + TRY_RESULT(prices_json, object.extract_required_field("prices", td::JsonValue::Type::Array)); auto r_prices = get_labeled_price_parts(prices_json); if (r_prices.is_error()) { @@ -6779,42 +6780,42 @@ td::Result> Client::get_input_me CHECK(input_message_content.type() == td::JsonValue::Type::Object); auto &object = input_message_content.get_object(); - TRY_RESULT(message_text, get_json_object_string_field(object, "message_text")); + TRY_RESULT(message_text, object.get_optional_string_field("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(disable_web_page_preview, object.get_optional_bool_field("disable_web_page_preview")); + TRY_RESULT(parse_mode, object.get_optional_string_field("parse_mode")); + auto entities = object.extract_field("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")); + if (object.has_field("latitude") && object.has_field("longitude")) { + TRY_RESULT(latitude, object.get_required_double_field("latitude")); + TRY_RESULT(longitude, object.get_required_double_field("longitude")); + TRY_RESULT(horizontal_accuracy, object.get_optional_double_field("horizontal_accuracy")); + TRY_RESULT(live_period, object.get_optional_int_field("live_period")); + TRY_RESULT(heading, object.get_optional_int_field("heading")); + TRY_RESULT(proximity_alert_radius, object.get_optional_int_field("proximity_alert_radius")); auto location = make_object(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)); + if (object.has_field("title") && object.has_field("address")) { + TRY_RESULT(title, object.get_required_string_field("title")); + TRY_RESULT(address, object.get_required_string_field("address")); 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")); + TRY_RESULT(google_place_id, object.get_optional_string_field("google_place_id")); + TRY_RESULT(google_place_type, object.get_optional_string_field("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")); + TRY_RESULT(foursquare_id, object.get_optional_string_field("foursquare_id")); + TRY_RESULT(foursquare_type, object.get_optional_string_field("foursquare_type")); if (!foursquare_id.empty() || !foursquare_type.empty()) { provider = "foursquare"; venue_id = std::move(foursquare_id); @@ -6828,51 +6829,51 @@ td::Result> Client::get_input_me return make_object(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")); + if (object.has_field("phone_number")) { + TRY_RESULT(phone_number, object.get_required_string_field("phone_number")); + TRY_RESULT(first_name, object.get_required_string_field("first_name")); + TRY_RESULT(last_name, object.get_optional_string_field("last_name")); + TRY_RESULT(vcard, object.get_optional_string_field("vcard")); return make_object( make_object(phone_number, first_name, last_name, vcard, 0)); } - if (has_json_object_field(object, "payload")) { - TRY_RESULT(title, get_json_object_string_field(object, "title", false)); - TRY_RESULT(description, get_json_object_string_field(object, "description", false)); - TRY_RESULT(payload, get_json_object_string_field(object, "payload", false)); + if (object.has_field("payload")) { + TRY_RESULT(title, object.get_required_string_field("title")); + TRY_RESULT(description, object.get_required_string_field("description")); + TRY_RESULT(payload, object.get_required_string_field("payload")); if (!td::check_utf8(payload)) { return td::Status::Error(400, "InputInvoiceMessageContent payload must be encoded in UTF-8"); } - TRY_RESULT(provider_token, get_json_object_string_field(object, "provider_token", false)); - TRY_RESULT(currency, get_json_object_string_field(object, "currency", false)); - TRY_RESULT(prices_object, get_json_object_field(object, "prices", td::JsonValue::Type::Array, false)); + TRY_RESULT(provider_token, object.get_required_string_field("provider_token")); + TRY_RESULT(currency, object.get_required_string_field("currency")); + TRY_RESULT(prices_object, object.extract_required_field("prices", td::JsonValue::Type::Array)); TRY_RESULT(prices, get_labeled_price_parts(prices_object)); - TRY_RESULT(provider_data, get_json_object_string_field(object, "provider_data")); - TRY_RESULT(max_tip_amount, get_json_object_long_field(object, "max_tip_amount")); + TRY_RESULT(provider_data, object.get_optional_string_field("provider_data")); + TRY_RESULT(max_tip_amount, object.get_optional_long_field("max_tip_amount")); td::vector suggested_tip_amounts; TRY_RESULT(suggested_tip_amounts_array, - get_json_object_field(object, "suggested_tip_amounts", td::JsonValue::Type::Array)); + object.extract_optional_field("suggested_tip_amounts", td::JsonValue::Type::Array)); if (suggested_tip_amounts_array.type() == td::JsonValue::Type::Array) { TRY_RESULT_ASSIGN(suggested_tip_amounts, get_suggested_tip_amounts(suggested_tip_amounts_array)); } - TRY_RESULT(photo_url, get_json_object_string_field(object, "photo_url")); - TRY_RESULT(photo_size, get_json_object_int_field(object, "photo_size")); - TRY_RESULT(photo_width, get_json_object_int_field(object, "photo_width")); - TRY_RESULT(photo_height, get_json_object_int_field(object, "photo_height")); - TRY_RESULT(need_name, get_json_object_bool_field(object, "need_name")); - TRY_RESULT(need_phone_number, get_json_object_bool_field(object, "need_phone_number")); - TRY_RESULT(need_email_address, get_json_object_bool_field(object, "need_email")); - TRY_RESULT(need_shipping_address, get_json_object_bool_field(object, "need_shipping_address")); - TRY_RESULT(send_phone_number_to_provider, get_json_object_bool_field(object, "send_phone_number_to_provider")); - TRY_RESULT(send_email_address_to_provider, get_json_object_bool_field(object, "send_email_to_provider")); - TRY_RESULT(is_flexible, get_json_object_bool_field(object, "is_flexible")); + TRY_RESULT(photo_url, object.get_optional_string_field("photo_url")); + TRY_RESULT(photo_size, object.get_optional_int_field("photo_size")); + TRY_RESULT(photo_width, object.get_optional_int_field("photo_width")); + TRY_RESULT(photo_height, object.get_optional_int_field("photo_height")); + TRY_RESULT(need_name, object.get_optional_bool_field("need_name")); + TRY_RESULT(need_phone_number, object.get_optional_bool_field("need_phone_number")); + TRY_RESULT(need_email_address, object.get_optional_bool_field("need_email")); + TRY_RESULT(need_shipping_address, object.get_optional_bool_field("need_shipping_address")); + TRY_RESULT(send_phone_number_to_provider, object.get_optional_bool_field("send_phone_number_to_provider")); + TRY_RESULT(send_email_address_to_provider, object.get_optional_bool_field("send_email_to_provider")); + TRY_RESULT(is_flexible, object.get_optional_bool_field("is_flexible")); return make_object( make_object(currency, std::move(prices), max_tip_amount, std::move(suggested_tip_amounts), - td::string(), false, need_name, need_phone_number, need_email_address, - need_shipping_address, send_phone_number_to_provider, + td::string(), td::string(), 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, description, photo_url, photo_size, photo_width, photo_height, payload, provider_token, provider_data, td::string(), nullptr); @@ -6887,7 +6888,7 @@ td::Result> Client::get_input_me td_api::object_ptr Client::get_message_send_options(bool disable_notification, bool protect_content, td_api::object_ptr &&scheduling_state) { - return make_object(disable_notification, false, protect_content, false, std::move(scheduling_state), 0); + return make_object(disable_notification, false, protect_content, false, std::move(scheduling_state), 0, false); } td::Result> Client::get_inline_query_results_button( @@ -6898,18 +6899,18 @@ td::Result> Client::get_inl auto &object = value.get_object(); - TRY_RESULT(text, get_json_object_string_field(object, "text", false)); + TRY_RESULT(text, object.get_required_string_field("text")); - if (has_json_object_field(object, "start_parameter")) { - TRY_RESULT(start_parameter, get_json_object_string_field(object, "start_parameter", false)); + if (object.has_field("start_parameter")) { + TRY_RESULT(start_parameter, object.get_required_string_field("start_parameter")); return make_object( text, make_object(start_parameter)); } - if (has_json_object_field(object, "web_app")) { - TRY_RESULT(web_app, get_json_object_field(object, "web_app", td::JsonValue::Type::Object, false)); + if (object.has_field("web_app")) { + TRY_RESULT(web_app, object.extract_required_field("web_app", td::JsonValue::Type::Object)); auto &web_app_object = web_app.get_object(); - TRY_RESULT(url, get_json_object_string_field(web_app_object, "url", false)); + TRY_RESULT(url, web_app_object.get_required_string_field("url")); return make_object(text, make_object(url)); } @@ -6939,7 +6940,7 @@ td::Result> Client::get_inl } td::Result>> Client::get_inline_query_results( - const Query *query) { + const Query *query, BotUserIds &bot_user_ids) { auto results_encoded = query->arg("results"); if (results_encoded.empty()) { return td::vector>(); @@ -6952,21 +6953,25 @@ td::Result>> Clien 400, PSLICE() << "Can't parse JSON encoded inline query results: " << r_values.error().message()); } - return get_inline_query_results(r_values.move_as_ok()); + return get_inline_query_results(r_values.move_as_ok(), bot_user_ids); } td::Result>> Client::get_inline_query_results( - td::JsonValue &&values) { + td::JsonValue &&values, BotUserIds &bot_user_ids) { if (values.type() == td::JsonValue::Type::Null) { return td::vector>(); } if (values.type() != td::JsonValue::Type::Array) { return td::Status::Error(400, "Expected an Array of inline query results"); } + constexpr std::size_t MAX_INLINE_QUERY_RESULT_COUNT = 50; + if (values.get_array().size() > MAX_INLINE_QUERY_RESULT_COUNT) { + return td::Status::Error(400, "Too many inline query results specified"); + } td::vector> inline_query_results; for (auto &value : values.get_array()) { - auto r_inline_query_result = get_inline_query_result(std::move(value)); + auto r_inline_query_result = get_inline_query_result(std::move(value), bot_user_ids); if (r_inline_query_result.is_error()) { return td::Status::Error( 400, PSLICE() << "Can't parse inline query result: " << r_inline_query_result.error().message()); @@ -6977,7 +6982,8 @@ td::Result>> Clien return std::move(inline_query_results); } -td::Result> Client::get_inline_query_result(const Query *query) { +td::Result> Client::get_inline_query_result( + const Query *query, BotUserIds &bot_user_ids) { auto result_encoded = query->arg("result"); if (result_encoded.empty()) { return td::Status::Error(400, "Result isn't specified"); @@ -6990,31 +6996,33 @@ td::Result> Client::get_inlin 400, PSLICE() << "Can't parse JSON encoded web view query results " << r_value.error().message()); } - return get_inline_query_result(r_value.move_as_ok()); + return get_inline_query_result(r_value.move_as_ok(), bot_user_ids); } -td::Result> Client::get_inline_query_result(td::JsonValue &&value) { +td::Result> Client::get_inline_query_result( + td::JsonValue &&value, BotUserIds &bot_user_ids) { if (value.type() != td::JsonValue::Type::Object) { return td::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)); + TRY_RESULT(type, object.get_required_string_field("type")); td::to_lower_inplace(type); - TRY_RESULT(id, get_json_object_string_field(object, "id", false)); + TRY_RESULT(id, object.get_required_string_field("id")); bool is_input_message_content_required = (type == "article"); object_ptr input_message_content; TRY_RESULT(input_message_content_obj, - get_json_object_field(object, "input_message_content", td::JsonValue::Type::Object)); + object.extract_optional_field("input_message_content", td::JsonValue::Type::Object)); if (input_message_content_obj.type() == td::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"); + TRY_RESULT(message_text, is_input_message_content_required ? object.get_required_string_field("message_text") + : object.get_optional_string_field("message_text")); + TRY_RESULT(disable_web_page_preview, object.get_optional_bool_field("disable_web_page_preview")); + TRY_RESULT(parse_mode, object.get_optional_string_field("parse_mode")); + auto entities = object.extract_field("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, @@ -7026,37 +7034,36 @@ td::Result> Client::get_inlin 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(input_caption, object.get_optional_string_field("caption")); + TRY_RESULT(parse_mode, object.get_optional_string_field("parse_mode")); + auto entities = object.extract_field("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", td::JsonValue::Type::Object)); + TRY_RESULT(reply_markup_object, object.extract_optional_field("reply_markup", td::JsonValue::Type::Object)); object_ptr reply_markup; if (reply_markup_object.type() != td::JsonValue::Type::Null) { - TRY_RESULT_ASSIGN(reply_markup, get_reply_markup(std::move(reply_markup_object))); + TRY_RESULT_ASSIGN(reply_markup, get_reply_markup(std::move(reply_markup_object), bot_user_ids)); } auto thumbnail_url_field_name = td::Slice("thumbnail_url"); auto thumbnail_width_field_name = td::Slice("thumbnail_width"); auto thumbnail_height_field_name = td::Slice("thumbnail_height"); - if (!has_json_object_field(object, thumbnail_url_field_name) && - !has_json_object_field(object, thumbnail_width_field_name) && - !has_json_object_field(object, thumbnail_height_field_name)) { + if (!object.has_field(thumbnail_url_field_name) && !object.has_field(thumbnail_width_field_name) && + !object.has_field(thumbnail_height_field_name)) { thumbnail_url_field_name = td::Slice("thumb_url"); thumbnail_width_field_name = td::Slice("thumb_width"); thumbnail_height_field_name = td::Slice("thumb_height"); } - TRY_RESULT(thumbnail_url, get_json_object_string_field(object, thumbnail_url_field_name)); - TRY_RESULT(thumbnail_width, get_json_object_int_field(object, thumbnail_width_field_name)); - TRY_RESULT(thumbnail_height, get_json_object_int_field(object, thumbnail_height_field_name)); + TRY_RESULT(thumbnail_url, object.get_optional_string_field(thumbnail_url_field_name)); + TRY_RESULT(thumbnail_width, object.get_optional_int_field(thumbnail_width_field_name)); + TRY_RESULT(thumbnail_height, object.get_optional_int_field(thumbnail_height_field_name)); object_ptr 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(url, object.get_optional_string_field("url")); + TRY_RESULT(hide_url, object.get_optional_bool_field("hide_url")); + TRY_RESULT(title, object.get_required_string_field("title")); + TRY_RESULT(description, object.get_optional_string_field("description")); CHECK(input_message_content != nullptr); return make_object( @@ -7064,12 +7071,13 @@ td::Result> Client::get_inlin 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")); + TRY_RESULT(audio_url, object.get_optional_string_field("audio_url")); + TRY_RESULT(audio_duration, object.get_optional_int_field("audio_duration")); + TRY_RESULT(title, audio_url.empty() ? object.get_optional_string_field("title") + : object.get_required_string_field("title")); + TRY_RESULT(performer, object.get_optional_string_field("performer")); if (audio_url.empty()) { - TRY_RESULT_ASSIGN(audio_url, get_json_object_string_field(object, "audio_file_id", false)); + TRY_RESULT_ASSIGN(audio_url, object.get_required_string_field("audio_file_id")); } if (input_message_content == nullptr) { @@ -7080,10 +7088,10 @@ td::Result> Client::get_inlin 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(phone_number, object.get_required_string_field("phone_number")); + TRY_RESULT(first_name, object.get_required_string_field("first_name")); + TRY_RESULT(last_name, object.get_optional_string_field("last_name")); + TRY_RESULT(vcard, object.get_optional_string_field("vcard")); if (input_message_content == nullptr) { input_message_content = make_object( @@ -7094,12 +7102,13 @@ td::Result> Client::get_inlin 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(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(title, object.get_required_string_field("title")); + TRY_RESULT(description, object.get_optional_string_field("description")); + TRY_RESULT(document_url, object.get_optional_string_field("document_url")); + TRY_RESULT(mime_type, document_url.empty() ? object.get_optional_string_field("mime_type") + : object.get_required_string_field("mime_type")); if (document_url.empty()) { - TRY_RESULT_ASSIGN(document_url, get_json_object_string_field(object, "document_file_id", false)); + TRY_RESULT_ASSIGN(document_url, object.get_required_string_field("document_file_id")); } if (input_message_content == nullptr) { @@ -7110,22 +7119,22 @@ td::Result> Client::get_inlin 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)); + TRY_RESULT(game_short_name, object.get_required_string_field("game_short_name")); return make_object(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(title, object.get_optional_string_field("title")); + TRY_RESULT(gif_url, object.get_optional_string_field("gif_url")); auto thumbnail_mime_type_field_name = td::Slice("thumbnail_mime_type"); - if (!has_json_object_field(object, thumbnail_mime_type_field_name)) { + if (!object.has_field(thumbnail_mime_type_field_name)) { thumbnail_mime_type_field_name = td::Slice("thumb_mime_type"); } - TRY_RESULT(thumbnail_mime_type, get_json_object_string_field(object, thumbnail_mime_type_field_name)); - 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")); + TRY_RESULT(thumbnail_mime_type, object.get_optional_string_field(thumbnail_mime_type_field_name)); + TRY_RESULT(gif_duration, object.get_optional_int_field("gif_duration")); + TRY_RESULT(gif_width, object.get_optional_int_field("gif_width")); + TRY_RESULT(gif_height, object.get_optional_int_field("gif_height")); if (gif_url.empty()) { - TRY_RESULT_ASSIGN(gif_url, get_json_object_string_field(object, "gif_file_id", false)); + TRY_RESULT_ASSIGN(gif_url, object.get_required_string_field("gif_file_id")); } if (input_message_content == nullptr) { @@ -7137,13 +7146,13 @@ td::Result> Client::get_inlin 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(latitude, object.get_required_double_field("latitude")); + TRY_RESULT(longitude, object.get_required_double_field("longitude")); + TRY_RESULT(horizontal_accuracy, object.get_optional_double_field("horizontal_accuracy")); + TRY_RESULT(live_period, object.get_optional_int_field("live_period")); + TRY_RESULT(heading, object.get_optional_int_field("heading")); + TRY_RESULT(proximity_alert_radius, object.get_optional_int_field("proximity_alert_radius")); + TRY_RESULT(title, object.get_required_string_field("title")); if (input_message_content == nullptr) { auto location = make_object(latitude, longitude, horizontal_accuracy); @@ -7155,18 +7164,18 @@ td::Result> Client::get_inlin 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(title, object.get_optional_string_field("title")); + TRY_RESULT(mpeg4_url, object.get_optional_string_field("mpeg4_url")); auto thumbnail_mime_type_field_name = td::Slice("thumbnail_mime_type"); - if (!has_json_object_field(object, thumbnail_mime_type_field_name)) { + if (!object.has_field(thumbnail_mime_type_field_name)) { thumbnail_mime_type_field_name = td::Slice("thumb_mime_type"); } - TRY_RESULT(thumbnail_mime_type, get_json_object_string_field(object, thumbnail_mime_type_field_name)); - 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")); + TRY_RESULT(thumbnail_mime_type, object.get_optional_string_field(thumbnail_mime_type_field_name)); + TRY_RESULT(mpeg4_duration, object.get_optional_int_field("mpeg4_duration")); + TRY_RESULT(mpeg4_width, object.get_optional_int_field("mpeg4_width")); + TRY_RESULT(mpeg4_height, object.get_optional_int_field("mpeg4_height")); if (mpeg4_url.empty()) { - TRY_RESULT_ASSIGN(mpeg4_url, get_json_object_string_field(object, "mpeg4_file_id", false)); + TRY_RESULT_ASSIGN(mpeg4_url, object.get_required_string_field("mpeg4_file_id")); } if (input_message_content == nullptr) { @@ -7178,25 +7187,25 @@ td::Result> Client::get_inlin 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(photo_width, get_json_object_int_field(object, "photo_width")); - TRY_RESULT(photo_height, get_json_object_int_field(object, "photo_height")); + TRY_RESULT(title, object.get_optional_string_field("title")); + TRY_RESULT(description, object.get_optional_string_field("description")); + TRY_RESULT(photo_url, object.get_optional_string_field("photo_url")); + TRY_RESULT(photo_width, object.get_optional_int_field("photo_width")); + TRY_RESULT(photo_height, object.get_optional_int_field("photo_height")); if (photo_url.empty()) { - TRY_RESULT_ASSIGN(photo_url, get_json_object_string_field(object, "photo_file_id", false)); + TRY_RESULT_ASSIGN(photo_url, object.get_required_string_field("photo_file_id")); } if (input_message_content == nullptr) { input_message_content = make_object(nullptr, nullptr, td::vector(), 0, 0, - std::move(caption), 0, false); + std::move(caption), nullptr, false); } return make_object(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)); + TRY_RESULT(sticker_file_id, object.get_required_string_field("sticker_file_id")); if (input_message_content == nullptr) { input_message_content = make_object(nullptr, nullptr, 0, 0, td::string()); @@ -7205,15 +7214,15 @@ td::Result> Client::get_inlin 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(latitude, object.get_required_double_field("latitude")); + TRY_RESULT(longitude, object.get_required_double_field("longitude")); + TRY_RESULT(horizontal_accuracy, object.get_optional_double_field("horizontal_accuracy")); + TRY_RESULT(title, object.get_required_string_field("title")); + TRY_RESULT(address, object.get_required_string_field("address")); + TRY_RESULT(foursquare_id, object.get_optional_string_field("foursquare_id")); + TRY_RESULT(foursquare_type, object.get_optional_string_field("foursquare_type")); + TRY_RESULT(google_place_id, object.get_optional_string_field("google_place_id")); + TRY_RESULT(google_place_type, object.get_optional_string_field("google_place_type")); td::string provider; td::string venue_id; @@ -7241,32 +7250,33 @@ td::Result> Client::get_inlin 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(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")); + TRY_RESULT(title, object.get_required_string_field("title")); + TRY_RESULT(description, object.get_optional_string_field("description")); + TRY_RESULT(video_url, object.get_optional_string_field("video_url")); + TRY_RESULT(mime_type, video_url.empty() ? object.get_optional_string_field("mime_type") + : object.get_required_string_field("mime_type")); + TRY_RESULT(video_width, object.get_optional_int_field("video_width")); + TRY_RESULT(video_height, object.get_optional_int_field("video_height")); + TRY_RESULT(video_duration, object.get_optional_int_field("video_duration")); if (video_url.empty()) { - TRY_RESULT_ASSIGN(video_url, get_json_object_string_field(object, "video_file_id", false)); + TRY_RESULT_ASSIGN(video_url, object.get_required_string_field("video_file_id")); } if (input_message_content == nullptr) { input_message_content = make_object(nullptr, nullptr, td::vector(), video_duration, video_width, - video_height, false, std::move(caption), 0, false); + video_height, false, std::move(caption), nullptr, false); } return make_object(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")); + TRY_RESULT(title, object.get_required_string_field("title")); + TRY_RESULT(voice_note_url, object.get_optional_string_field("voice_url")); + TRY_RESULT(voice_note_duration, object.get_optional_int_field("voice_duration")); if (voice_note_url.empty()) { - TRY_RESULT_ASSIGN(voice_note_url, get_json_object_string_field(object, "voice_file_id", false)); + TRY_RESULT_ASSIGN(voice_note_url, object.get_required_string_field("voice_file_id")); } if (input_message_content == nullptr) { @@ -7287,7 +7297,7 @@ td::Result Client::get_bot_command_scope(td::JsonValue auto &object = value.get_object(); - TRY_RESULT(type, get_json_object_string_field(object, "type", false)); + TRY_RESULT(type, object.get_required_string_field("type")); if (type == "default") { return BotCommandScope(make_object()); } @@ -7304,7 +7314,7 @@ td::Result Client::get_bot_command_scope(td::JsonValue return td::Status::Error(400, "Unsupported type specified"); } - TRY_RESULT(chat_id, get_json_object_string_field(object, "chat_id", false)); + TRY_RESULT(chat_id, object.get_required_string_field("chat_id")); if (chat_id.empty()) { return td::Status::Error(400, "Empty chat_id specified"); } @@ -7315,7 +7325,7 @@ td::Result Client::get_bot_command_scope(td::JsonValue return BotCommandScope(make_object(0), std::move(chat_id)); } - TRY_RESULT(user_id, get_json_object_long_field(object, "user_id", false)); + TRY_RESULT(user_id, object.get_required_long_field("user_id")); if (user_id <= 0) { return td::Status::Error(400, "Invalid user_id specified"); } @@ -7350,8 +7360,8 @@ td::Result> Client::get_bot_command(td::J 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)); + TRY_RESULT(command, object.get_required_string_field("command")); + TRY_RESULT(description, object.get_required_string_field("description")); return make_object(command, description); } @@ -7391,7 +7401,7 @@ td::Result> Client::get_bot_menu_butto auto &object = value.get_object(); - TRY_RESULT(type, get_json_object_string_field(object, "type", false)); + TRY_RESULT(type, object.get_required_string_field("type")); if (type == "default") { return make_object("", "default"); } @@ -7399,10 +7409,10 @@ td::Result> Client::get_bot_menu_butto return nullptr; } if (type == "web_app") { - TRY_RESULT(text, get_json_object_string_field(object, "text", false)); - TRY_RESULT(web_app, get_json_object_field(object, "web_app", td::JsonValue::Type::Object, false)); + TRY_RESULT(text, object.get_required_string_field("text")); + TRY_RESULT(web_app, object.extract_required_field("web_app", td::JsonValue::Type::Object)); auto &web_app_object = web_app.get_object(); - TRY_RESULT(url, get_json_object_string_field(web_app_object, "url", false)); + TRY_RESULT(url, web_app_object.get_required_string_field("url")); return make_object(text, url); } @@ -7436,22 +7446,25 @@ td::Result> Client::get_chat } auto &object = value.get_object(); - TRY_RESULT(can_manage_chat, get_json_object_bool_field(object, "can_manage_chat")); - TRY_RESULT(can_change_info, get_json_object_bool_field(object, "can_change_info")); - TRY_RESULT(can_post_messages, get_json_object_bool_field(object, "can_post_messages")); - TRY_RESULT(can_edit_messages, get_json_object_bool_field(object, "can_edit_messages")); - TRY_RESULT(can_delete_messages, get_json_object_bool_field(object, "can_delete_messages")); - TRY_RESULT(can_invite_users, get_json_object_bool_field(object, "can_invite_users")); - TRY_RESULT(can_restrict_members, get_json_object_bool_field(object, "can_restrict_members")); - TRY_RESULT(can_pin_messages, get_json_object_bool_field(object, "can_pin_messages")); - TRY_RESULT(can_manage_topics, get_json_object_bool_field(object, "can_manage_topics")); - TRY_RESULT(can_promote_members, get_json_object_bool_field(object, "can_promote_members")); - TRY_RESULT(can_manage_video_chats, get_json_object_bool_field(object, "can_manage_video_chats")); - TRY_RESULT(is_anonymous, get_json_object_bool_field(object, "is_anonymous")); - return make_object(can_manage_chat, can_change_info, can_post_messages, - can_edit_messages, can_delete_messages, can_invite_users, - can_restrict_members, can_pin_messages, can_manage_topics, - can_promote_members, can_manage_video_chats, is_anonymous); + TRY_RESULT(can_manage_chat, object.get_optional_bool_field("can_manage_chat")); + TRY_RESULT(can_change_info, object.get_optional_bool_field("can_change_info")); + TRY_RESULT(can_post_messages, object.get_optional_bool_field("can_post_messages")); + TRY_RESULT(can_edit_messages, object.get_optional_bool_field("can_edit_messages")); + TRY_RESULT(can_delete_messages, object.get_optional_bool_field("can_delete_messages")); + TRY_RESULT(can_invite_users, object.get_optional_bool_field("can_invite_users")); + TRY_RESULT(can_restrict_members, object.get_optional_bool_field("can_restrict_members")); + TRY_RESULT(can_pin_messages, object.get_optional_bool_field("can_pin_messages")); + TRY_RESULT(can_manage_topics, object.get_optional_bool_field("can_manage_topics")); + TRY_RESULT(can_promote_members, object.get_optional_bool_field("can_promote_members")); + TRY_RESULT(can_manage_video_chats, object.get_optional_bool_field("can_manage_video_chats")); + TRY_RESULT(can_post_stories, object.get_optional_bool_field("can_post_stories")); + TRY_RESULT(can_edit_stories, object.get_optional_bool_field("can_edit_stories")); + TRY_RESULT(can_delete_stories, object.get_optional_bool_field("can_delete_stories")); + TRY_RESULT(is_anonymous, object.get_optional_bool_field("is_anonymous")); + return make_object( + can_manage_chat, can_change_info, can_post_messages, can_edit_messages, can_delete_messages, can_invite_users, + can_restrict_members, can_pin_messages, can_manage_topics, can_promote_members, can_manage_video_chats, + can_post_stories, can_edit_stories, can_delete_stories, is_anonymous); } td::Result> Client::get_chat_administrator_rights( @@ -7485,7 +7498,7 @@ td::Result> Client::get_mask_position(t auto &object = value.get_object(); - TRY_RESULT(point_str, get_json_object_string_field(object, "point", false)); + TRY_RESULT(point_str, object.get_required_string_field("point")); point_str = td::trim(td::to_lower(point_str)); int32 point; for (point = 0; point < MASK_POINTS_SIZE; point++) { @@ -7497,9 +7510,9 @@ td::Result> Client::get_mask_position(t return td::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)); + TRY_RESULT(x_shift, object.get_required_double_field("x_shift")); + TRY_RESULT(y_shift, object.get_required_double_field("y_shift")); + TRY_RESULT(scale, object.get_required_double_field("scale")); return make_object(mask_index_to_point(point), x_shift, y_shift, scale); } @@ -7619,17 +7632,17 @@ td::Result> Client::get_input_sticker(c auto &object = value.get_object(); - TRY_RESULT(sticker, get_json_object_string_field(object, "sticker")); + TRY_RESULT(sticker, object.get_optional_string_field("sticker")); auto input_file = get_input_file(query, td::Slice(), sticker, false); if (input_file == nullptr) { return td::Status::Error(400, "sticker not found"); } - TRY_RESULT(emoji_list, get_json_object_field(object, "emoji_list", td::JsonValue::Type::Array, false)); + TRY_RESULT(emoji_list, object.extract_required_field("emoji_list", td::JsonValue::Type::Array)); TRY_RESULT(emojis, get_sticker_emojis(std::move(emoji_list))); - TRY_RESULT(mask_position, get_mask_position(get_json_object_field_force(object, "mask_position"))); + TRY_RESULT(mask_position, get_mask_position(object.extract_field("mask_position"))); td::vector input_keywords; - if (has_json_object_field(object, "keywords")) { - TRY_RESULT(keywords, get_json_object_field(object, "keywords", td::JsonValue::Type::Array, false)); + if (object.has_field("keywords")) { + TRY_RESULT(keywords, object.extract_required_field("keywords", td::JsonValue::Type::Array)); for (auto &keyword : keywords.get_array()) { if (keyword.type() != td::JsonValue::Type::String) { return td::Status::Error(400, "keyword must be a string"); @@ -7767,24 +7780,24 @@ td::Result Client::get_passport_element_hash(td::Slice encoded_hash) td::Result> Client::get_passport_element_error_source( td::JsonObject &object) { - TRY_RESULT(source, get_json_object_string_field(object, "source")); + TRY_RESULT(source, object.get_optional_string_field("source")); if (source.empty() || source == "unspecified") { - TRY_RESULT(element_hash, get_json_object_string_field(object, "element_hash", false)); + TRY_RESULT(element_hash, object.get_required_string_field("element_hash")); TRY_RESULT(hash, get_passport_element_hash(element_hash)); return make_object(hash); } if (source == "data") { - TRY_RESULT(data_hash, get_json_object_string_field(object, "data_hash", false)); + TRY_RESULT(data_hash, object.get_required_string_field("data_hash")); TRY_RESULT(hash, get_passport_element_hash(data_hash)); - TRY_RESULT(field_name, get_json_object_string_field(object, "field_name", false)); + TRY_RESULT(field_name, object.get_required_string_field("field_name")); return make_object(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(file_hash, object.get_required_string_field("file_hash")); TRY_RESULT(hash, get_passport_element_hash(file_hash)); if (source == "front_side") { @@ -7806,7 +7819,7 @@ td::Result> Client:: } if (source == "files" || source == "translation_files") { td::vector input_hashes; - TRY_RESULT(file_hashes, get_json_object_field(object, "file_hashes", td::JsonValue::Type::Array, false)); + TRY_RESULT(file_hashes, object.extract_required_field("file_hashes", td::JsonValue::Type::Array)); for (auto &input_hash : file_hashes.get_array()) { if (input_hash.type() != td::JsonValue::Type::String) { return td::Status::Error(400, "hash must be a string"); @@ -7833,12 +7846,12 @@ td::Result> Client::get_pa auto &object = value.get_object(); - TRY_RESULT(input_type, get_json_object_string_field(object, "type", false)); + TRY_RESULT(input_type, object.get_required_string_field("type")); auto type = get_passport_element_type(input_type); if (type == nullptr) { return td::Status::Error(400, "wrong Telegram Passport element type specified"); } - TRY_RESULT(message, get_json_object_string_field(object, "message", false)); + TRY_RESULT(message, object.get_required_string_field("message")); TRY_RESULT(source, get_passport_element_error_source(object)); return make_object(std::move(type), message, std::move(source)); @@ -7890,7 +7903,7 @@ td::Result> Client::get_caption(const } td::Result> Client::get_text_entity_type(td::JsonObject &object) { - TRY_RESULT(type, get_json_object_string_field(object, "type", false)); + TRY_RESULT(type, object.get_required_string_field("type")); if (type.empty()) { return td::Status::Error("Type is not specified"); } @@ -7914,24 +7927,25 @@ td::Result> Client::get_text_entity_t return make_object(); } if (type == "pre") { - TRY_RESULT(language, get_json_object_string_field(object, "language")); + TRY_RESULT(language, object.get_optional_string_field("language")); if (language.empty()) { return make_object(); } return make_object(language); } if (type == "text_link") { - TRY_RESULT(url, get_json_object_string_field(object, "url", false)); + TRY_RESULT(url, object.get_required_string_field("url")); return make_object(url); } if (type == "text_mention") { - TRY_RESULT(user, get_json_object_field(object, "user", td::JsonValue::Type::Object, false)); + TRY_RESULT(user, object.extract_required_field("user", td::JsonValue::Type::Object)); CHECK(user.type() == td::JsonValue::Type::Object); - TRY_RESULT(user_id, get_json_object_long_field(user.get_object(), "id", false)); + const auto &user_object = user.get_object(); + TRY_RESULT(user_id, user_object.get_required_long_field("id")); return make_object(user_id); } if (type == "custom_emoji") { - TRY_RESULT(custom_emoji_id, get_json_object_long_field(object, "custom_emoji_id", false)); + TRY_RESULT(custom_emoji_id, object.get_required_long_field("custom_emoji_id")); return make_object(custom_emoji_id); } if (type == "mention" || type == "hashtag" || type == "cashtag" || type == "bot_command" || type == "url" || @@ -7948,8 +7962,8 @@ td::Result> Client::get_text_entity(td::J } 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(offset, object.get_required_int_field("offset")); + TRY_RESULT(length, object.get_required_int_field("length")); TRY_RESULT(type, get_text_entity_type(object)); if (type == nullptr) { @@ -7961,6 +7975,10 @@ td::Result> Client::get_text_entity(td::J td::Result> Client::get_formatted_text(td::string text, td::string parse_mode, td::JsonValue &&input_entities) { + if (text.size() > (1 << 15)) { + return td::Status::Error(400, "Text is too long"); + } + td::to_lower_inplace(parse_mode); if (!text.empty() && !parse_mode.empty() && parse_mode != "none") { object_ptr text_parse_mode; @@ -8014,7 +8032,9 @@ td::Result> Client::get_input_messa TRY_RESULT(formatted_text, get_formatted_text(std::move(text), std::move(parse_mode), std::move(input_entities))); - return make_object(std::move(formatted_text), disable_web_page_preview, false); + return make_object( + std::move(formatted_text), + make_object(disable_web_page_preview, td::string(), false, false, false), false); } td::Result> Client::get_location(const Query *query) { @@ -8065,30 +8085,29 @@ td::Result> Client::get_chat_permiss 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_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")); - if (has_json_object_field(object, "can_manage_topics")) { - TRY_RESULT_ASSIGN(can_manage_topics, get_json_object_bool_field(object, "can_manage_topics")); + TRY_RESULT_ASSIGN(can_send_messages, object.get_optional_bool_field("can_send_messages")); + TRY_RESULT_ASSIGN(can_send_polls, object.get_optional_bool_field("can_send_polls")); + TRY_RESULT_ASSIGN(can_send_other_messages, object.get_optional_bool_field("can_send_other_messages")); + TRY_RESULT_ASSIGN(can_add_web_page_previews, object.get_optional_bool_field("can_add_web_page_previews")); + TRY_RESULT_ASSIGN(can_change_info, object.get_optional_bool_field("can_change_info")); + TRY_RESULT_ASSIGN(can_invite_users, object.get_optional_bool_field("can_invite_users")); + TRY_RESULT_ASSIGN(can_pin_messages, object.get_optional_bool_field("can_pin_messages")); + if (object.has_field("can_manage_topics")) { + TRY_RESULT_ASSIGN(can_manage_topics, object.get_optional_bool_field("can_manage_topics")); } else { can_manage_topics = can_pin_messages; } - if (has_json_object_field(object, "can_send_audios") || has_json_object_field(object, "can_send_documents") || - has_json_object_field(object, "can_send_photos") || has_json_object_field(object, "can_send_videos") || - has_json_object_field(object, "can_send_video_notes") || - has_json_object_field(object, "can_send_voice_notes")) { - TRY_RESULT_ASSIGN(can_send_audios, get_json_object_bool_field(object, "can_send_audios")); - TRY_RESULT_ASSIGN(can_send_documents, get_json_object_bool_field(object, "can_send_documents")); - TRY_RESULT_ASSIGN(can_send_photos, get_json_object_bool_field(object, "can_send_photos")); - TRY_RESULT_ASSIGN(can_send_videos, get_json_object_bool_field(object, "can_send_videos")); - TRY_RESULT_ASSIGN(can_send_video_notes, get_json_object_bool_field(object, "can_send_video_notes")); - TRY_RESULT_ASSIGN(can_send_voice_notes, get_json_object_bool_field(object, "can_send_voice_notes")); + if (object.has_field("can_send_audios") || object.has_field("can_send_documents") || + object.has_field("can_send_photos") || object.has_field("can_send_videos") || + object.has_field("can_send_video_notes") || object.has_field("can_send_voice_notes")) { + TRY_RESULT_ASSIGN(can_send_audios, object.get_optional_bool_field("can_send_audios")); + TRY_RESULT_ASSIGN(can_send_documents, object.get_optional_bool_field("can_send_documents")); + TRY_RESULT_ASSIGN(can_send_photos, object.get_optional_bool_field("can_send_photos")); + TRY_RESULT_ASSIGN(can_send_videos, object.get_optional_bool_field("can_send_videos")); + TRY_RESULT_ASSIGN(can_send_video_notes, object.get_optional_bool_field("can_send_video_notes")); + TRY_RESULT_ASSIGN(can_send_voice_notes, object.get_optional_bool_field("can_send_voice_notes")); } else { - TRY_RESULT(can_send_media_messages, get_json_object_bool_field(object, "can_send_media_messages")); + TRY_RESULT(can_send_media_messages, object.get_optional_bool_field("can_send_media_messages")); can_send_audios = can_send_media_messages; can_send_documents = can_send_media_messages; can_send_photos = can_send_media_messages; @@ -8167,23 +8186,21 @@ td::Result> Client::get_input_me 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(input_caption, object.get_optional_string_field("caption")); + TRY_RESULT(parse_mode, object.get_optional_string_field("parse_mode")); + auto entities = object.extract_field("caption_entities"); TRY_RESULT(caption, get_formatted_text(std::move(input_caption), std::move(parse_mode), std::move(entities))); - // TRY_RESULT(self_destruct_time, get_json_object_int_field(object, "self_destruct_time")); - int32 self_destruct_time = 0; - TRY_RESULT(has_spoiler, get_json_object_bool_field(object, "has_spoiler")); - TRY_RESULT(media, get_json_object_string_field(object, "media")); + TRY_RESULT(has_spoiler, object.get_optional_bool_field("has_spoiler")); + TRY_RESULT(media, object.get_optional_string_field("media")); auto input_file = get_input_file(query, td::Slice(), media, false); if (input_file == nullptr) { return td::Status::Error("media not found"); } - TRY_RESULT(thumbnail, get_json_object_string_field(object, "thumbnail")); + TRY_RESULT(thumbnail, object.get_optional_string_field("thumbnail")); if (thumbnail.empty()) { - TRY_RESULT_ASSIGN(thumbnail, get_json_object_string_field(object, "thumb")); + TRY_RESULT_ASSIGN(thumbnail, object.get_optional_string_field("thumb")); } auto thumbnail_input_file = get_input_file(query, td::Slice(), thumbnail, true); if (thumbnail_input_file == nullptr) { @@ -8197,31 +8214,31 @@ td::Result> Client::get_input_me input_thumbnail = make_object(std::move(thumbnail_input_file), 0, 0); } - TRY_RESULT(type, get_json_object_string_field(object, "type", false)); + TRY_RESULT(type, object.get_required_string_field("type")); if (type == "photo") { return make_object(std::move(input_file), nullptr, td::vector(), 0, 0, - std::move(caption), self_destruct_time, has_spoiler); + std::move(caption), nullptr, has_spoiler); } 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")); + TRY_RESULT(width, object.get_optional_int_field("width")); + TRY_RESULT(height, object.get_optional_int_field("height")); + TRY_RESULT(duration, object.get_optional_int_field("duration")); + TRY_RESULT(supports_streaming, object.get_optional_bool_field("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(std::move(input_file), std::move(input_thumbnail), td::vector(), duration, width, height, supports_streaming, - std::move(caption), self_destruct_time, has_spoiler); + std::move(caption), nullptr, has_spoiler); } if (for_album && type == "animation") { return td::Status::Error(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")); + TRY_RESULT(width, object.get_optional_int_field("width")); + TRY_RESULT(height, object.get_optional_int_field("height")); + TRY_RESULT(duration, object.get_optional_int_field("duration")); width = td::clamp(width, 0, MAX_LENGTH); height = td::clamp(height, 0, MAX_LENGTH); duration = td::clamp(duration, 0, MAX_DURATION); @@ -8230,15 +8247,15 @@ td::Result> Client::get_input_me has_spoiler); } 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")); + TRY_RESULT(duration, object.get_optional_int_field("duration")); + TRY_RESULT(title, object.get_optional_string_field("title")); + TRY_RESULT(performer, object.get_optional_string_field("performer")); duration = td::clamp(duration, 0, MAX_DURATION); return make_object(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")); + TRY_RESULT(disable_content_type_detection, object.get_optional_bool_field("disable_content_type_detection")); return make_object(std::move(input_file), std::move(input_thumbnail), disable_content_type_detection || for_album, std::move(caption)); } @@ -8360,7 +8377,7 @@ td::Result> Client::get_input_me return make_object( make_object(currency.str(), std::move(prices), max_tip_amount, std::move(suggested_tip_amounts), - td::string(), false, need_name, need_phone_number, need_email_address, + td::string(), td::string(), 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(), @@ -8446,31 +8463,16 @@ void Client::decrease_yet_unsent_message_count(int64 chat_id, int32 count) { } } -td::int64 Client::extract_yet_unsent_message_query_id(int64 chat_id, int64 message_id, - bool *is_reply_to_message_deleted) { +td::int64 Client::extract_yet_unsent_message_query_id(int64 chat_id, int64 message_id) { 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); decrease_yet_unsent_message_count(chat_id, 1); - 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; } @@ -8528,26 +8530,26 @@ td::int64 Client::get_int64_arg(const Query *query, td::Slice field_name, int64 return td::clamp(td::to_integer(s_arg), min_value, max_value); } -td::Result> Client::get_report_reason(const Query *query, +td::Result> Client::get_report_reason(const Query *query, td::Slice field_name) { auto reason = query->arg(field_name); - object_ptr result; + object_ptr result; if (reason.empty()) { return td::Status::Error(400, "reason is not specified"); } else if (reason == "child_abuse") { - result = make_object(); + result = make_object(); } else if (reason == "copyright") { - result = make_object(); + result = make_object(); } else if (reason == "pornography") { - result = make_object(); + result = make_object(); } else if (reason == "spam") { - result = make_object(); + result = make_object(); } else if (reason == "unrelated_location") { - result = make_object(); + result = make_object(); } else if (reason == "violence") { - result = make_object(); + result = make_object(); } else { - result = make_object(); + result = make_object(); } return std::move(result); } @@ -8611,8 +8613,7 @@ void Client::on_message_send_succeeded(object_ptr &&message, in 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_id = extract_yet_unsent_message_query_id(chat_id, old_message_id); auto &query = *pending_send_message_queries_[query_id]; if (query.is_multisend) { query.messages.push_back(td::json_encode(JsonMessage(message_info, true, "sent message", this))); @@ -8637,10 +8638,9 @@ void Client::on_message_send_succeeded(object_ptr &&message, in } } -void Client::on_message_send_failed(int64 chat_id, int64 old_message_id, int64 new_message_id, td::Status result) { - auto error = make_object(result.code(), result.message().str()); - - auto query_id = extract_yet_unsent_message_query_id(chat_id, old_message_id, nullptr); +void Client::on_message_send_failed(int64 chat_id, int64 old_message_id, int64 new_message_id, + object_ptr &&error) { + auto query_id = extract_yet_unsent_message_query_id(chat_id, old_message_id); auto &query = *pending_send_message_queries_[query_id]; if (query.is_multisend) { if (query.error == nullptr || query.error->message_ == "Group send failed") { @@ -8671,7 +8671,7 @@ void Client::on_message_send_failed(int64 chat_id, int64 old_message_id, int64 n } } -void Client::on_cmd(PromisedQueryPtr query) { +void Client::on_cmd(PromisedQueryPtr query, bool force) { LOG(DEBUG) << "Process query " << *query; if (!td_client_.empty() && was_authorized_) { if (query->method() == "close") { @@ -8706,13 +8706,49 @@ void Client::on_cmd(PromisedQueryPtr query) { } CHECK(was_authorized_); - unresolved_bot_usernames_.clear(); + bot_user_ids_.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)); } + if (!query->files().empty() && !parameters_->local_mode_ && !force) { + auto file_size = query->files_size(); + if (file_size > 100000) { + auto &last_send_message_time = last_send_message_time_[file_size]; + auto now = td::Time::now(); + auto min_delay = td::clamp(static_cast(file_size) * 1e-7, 0.2, 0.9); + auto max_bucket_volume = 1.0; + if (last_send_message_time > now + 5.0) { + return fail_query_flood_limit_exceeded(std::move(query)); + } + + last_send_message_time = td::max(last_send_message_time + min_delay, now - max_bucket_volume); + LOG(DEBUG) << "Query with files of size " << file_size << " can be processed in " << last_send_message_time - now + << " seconds"; + + td::create_actor( + "DeleteLastSendMessageTimeSleepActor", last_send_message_time + min_delay - (now - max_bucket_volume), + td::PromiseCreator::lambda([actor_id = actor_id(this), file_size, + max_delay = max_bucket_volume + min_delay](td::Result) mutable { + send_closure(actor_id, &Client::delete_last_send_message_time, file_size, max_delay); + })) + .release(); + + if (last_send_message_time > now) { + td::create_actor( + "DoSendMessageSleepActor", last_send_message_time - now, + td::PromiseCreator::lambda( + [actor_id = actor_id(this), query = std::move(query)](td::Result) mutable { + send_closure(actor_id, &Client::on_cmd, std::move(query), true); + })) + .release(); + return; + } + } + } + auto result = (this->*(method_it->second))(query); if (result.is_error()) { fail_query_with_error(std::move(query), result.code(), result.message()); @@ -8947,10 +8983,9 @@ td::Status Client::process_send_photo_query(PromisedQueryPtr &query) { return td::Status::Error(400, "There is no photo in the request"); } TRY_RESULT(caption, get_caption(query.get())); - auto self_destruct_time = 0; auto has_spoiler = to_bool(query->arg("has_spoiler")); do_send_message(make_object(std::move(photo), nullptr, td::vector(), 0, 0, - std::move(caption), self_destruct_time, has_spoiler), + std::move(caption), nullptr, has_spoiler), std::move(query)); return td::Status::OK(); } @@ -8977,11 +9012,10 @@ td::Status Client::process_send_video_query(PromisedQueryPtr &query) { 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 self_destruct_time = 0; auto has_spoiler = to_bool(query->arg("has_spoiler")); do_send_message(make_object(std::move(video), std::move(thumbnail), td::vector(), duration, width, height, supports_streaming, - std::move(caption), self_destruct_time, has_spoiler), + std::move(caption), nullptr, has_spoiler), std::move(query)); return td::Status::OK(); } @@ -9113,7 +9147,7 @@ td::Status Client::process_send_poll_query(PromisedQueryPtr &query) { 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())); + TRY_RESULT(reply_markup, get_reply_markup(query.get(), bot_user_ids_)); CHECK_USER_REPLY_MARKUP(); resolve_reply_markup_bot_usernames( @@ -9169,7 +9203,7 @@ td::Status Client::process_send_media_group_query(PromisedQueryPtr &query) { auto allow_sending_without_reply = to_bool(query->arg("allow_sending_without_reply")); auto disable_notification = to_bool(query->arg("disable_notification")); auto protect_content = to_bool(query->arg("protect_content")); - // TRY_RESULT(reply_markup, get_reply_markup(query.get())); + // TRY_RESULT(reply_markup, get_reply_markup(query.get(), bot_user_ids_)); auto reply_markup = nullptr; TRY_RESULT(input_message_contents, get_input_message_contents(query.get(), "media")); TRY_RESULT(send_at, get_message_scheduling_state(query.get())); @@ -9196,16 +9230,16 @@ td::Status Client::process_send_media_group_query(PromisedQueryPtr &query) { count += static_cast(message_count); send_request( - make_object(chat_id, message_thread_id, reply_to_message_id, - get_message_send_options(disable_notification, protect_content, std::move(send_at)), - std::move(input_message_contents), false), + make_object( + chat_id, message_thread_id, get_input_message_reply_to(reply_to_message_id), + get_message_send_options(disable_notification, protect_content, std::move(send_at)), std::move(input_message_contents)), td::make_unique(this, chat_id, message_count, std::move(query))); }; check_message_thread(chat_id, message_thread_id, reply_to_message_id, std::move(query), std::move(on_message_thread_checked)); }; check_message(chat_id, reply_to_message_id, reply_to_message_id <= 0 || allow_sending_without_reply, - AccessRights::Write, "replied message", std::move(query), std::move(on_success)); + AccessRights::Write, "message to reply", std::move(query), std::move(on_success)); }); return td::Status::OK(); } @@ -9230,7 +9264,7 @@ 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())); + TRY_RESULT(reply_markup, get_reply_markup(query.get(), bot_user_ids_)); CHECK_USER_REPLY_MARKUP(); if (chat_id.empty() && message_id == 0) { @@ -9270,7 +9304,7 @@ td::Status Client::process_edit_message_live_location_query(PromisedQueryPtr &qu } 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(reply_markup, get_reply_markup(query.get(), bot_user_ids_)); CHECK_USER_REPLY_MARKUP(); if (chat_id.empty() && message_id == 0) { @@ -9306,7 +9340,7 @@ td::Status Client::process_edit_message_live_location_query(PromisedQueryPtr &qu 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(reply_markup, get_reply_markup(query.get(), bot_user_ids_)); CHECK_USER_REPLY_MARKUP(); TRY_RESULT(input_media, get_input_media(query.get(), "media")); @@ -9341,7 +9375,7 @@ td::Status Client::process_edit_message_media_query(PromisedQueryPtr &query) { 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(reply_markup, get_reply_markup(query.get(), bot_user_ids_)); CHECK_USER_REPLY_MARKUP(); TRY_RESULT(caption, get_caption(query.get())); @@ -9376,7 +9410,7 @@ td::Status Client::process_edit_message_reply_markup_query(PromisedQueryPtr &que CHECK_IS_BOT(); 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(reply_markup, get_reply_markup(query.get(), bot_user_ids_)); CHECK_USER_REPLY_MARKUP(); if (chat_id.empty() && message_id == 0) { @@ -9499,7 +9533,7 @@ td::Status Client::process_get_game_high_scores_query(PromisedQueryPtr &query) { td::Status Client::process_answer_web_app_query_query(PromisedQueryPtr &query) { auto web_app_query_id = query->arg("web_app_query_id"); - TRY_RESULT(result, get_inline_query_result(query.get())); + TRY_RESULT(result, get_inline_query_result(query.get(), bot_user_ids_)); td::vector> results; results.push_back(std::move(result)); @@ -9529,7 +9563,7 @@ td::Status Client::process_answer_inline_query_query(PromisedQueryPtr &query) { make_object(query->arg("switch_pm_parameter").str())); } } - TRY_RESULT(results, get_inline_query_results(query.get())); + TRY_RESULT(results, get_inline_query_results(query.get(), bot_user_ids_)); resolve_inline_query_results_bot_usernames( std::move(results), std::move(query), @@ -9955,6 +9989,16 @@ td::Status Client::process_unhide_general_forum_topic_query(PromisedQueryPtr &qu return td::Status::OK(); } +td::Status Client::process_unpin_all_general_forum_topic_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(chat_id, GENERAL_MESSAGE_THREAD_ID), + td::make_unique(std::move(query))); + }); + return td::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())); @@ -10044,13 +10088,16 @@ td::Status Client::process_promote_chat_member_query(PromisedQueryPtr &query) { auto can_promote_members = to_bool(query->arg("can_promote_members")); auto can_manage_video_chats = to_bool(query->arg("can_manage_voice_chats")) || to_bool(query->arg("can_manage_video_chats")); + auto can_post_stories = to_bool(query->arg("can_post_stories")); + auto can_edit_stories = to_bool(query->arg("can_edit_stories")); + auto can_delete_stories = to_bool(query->arg("can_delete_stories")); auto is_anonymous = to_bool(query->arg("is_anonymous")); auto status = make_object( td::string(), true, - make_object(can_manage_chat, can_change_info, can_post_messages, - can_edit_messages, can_delete_messages, can_invite_users, - can_restrict_members, can_pin_messages, can_manage_topics, - can_promote_members, can_manage_video_chats, is_anonymous)); + make_object( + can_manage_chat, can_change_info, can_post_messages, can_edit_messages, can_delete_messages, can_invite_users, + can_restrict_members, can_pin_messages, can_manage_topics, can_promote_members, can_manage_video_chats, + can_post_stories, can_edit_stories, can_delete_stories, 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); @@ -10925,7 +10972,7 @@ td::Status Client::process_report_chat_query(PromisedQueryPtr &query) { [this, reason = std::move(reason), message_ids = std::move(message_ids)](int64 chat_id, PromisedQueryPtr query) mutable { - send_request(make_object(chat_id, std::move(message_ids), std::move(reason), reason->get_id() == td_api::chatReportReasonCustom::ID ? query->arg("reason").str() : td::string()), + send_request(make_object(chat_id, std::move(message_ids), std::move(reason), reason->get_id() == td_api::reportReasonCustom::ID ? query->arg("reason").str() : td::string()), td::make_unique(std::move(query))); }); return td::Status::OK(); @@ -11199,7 +11246,7 @@ void Client::webhook_error(td::Status status) { void Client::webhook_closed(td::Status status) { if (has_webhook_certificate_) { - td::Scheduler::instance()->run_on_scheduler(SharedData::get_database_scheduler_id(), + td::Scheduler::instance()->run_on_scheduler(SharedData::get_webhook_certificate_scheduler_id(), [actor_id = actor_id(this), path = get_webhook_certificate_path(), status = std::move(status)](td::Unit) mutable { LOG(INFO) << "Unlink certificate " << path; @@ -11312,7 +11359,7 @@ void Client::do_set_webhook(PromisedQueryPtr query, bool was_deleted) { CHECK(!webhook_set_query_); active_webhook_set_query_ = std::move(query); td::Scheduler::instance()->run_on_scheduler( - SharedData::get_database_scheduler_id(), + SharedData::get_webhook_certificate_scheduler_id(), [actor_id = actor_id(this), from_path = cert_file_ptr->temp_file_name, to_path = get_webhook_certificate_path(), size](td::Unit) mutable { LOG(INFO) << "Copy certificate to " << to_path; @@ -11373,14 +11420,26 @@ void Client::finish_set_webhook(PromisedQueryPtr query) { webhook_set_query_ = std::move(query); } -void Client::do_send_message(object_ptr input_message_content, PromisedQueryPtr query) { +void Client::delete_last_send_message_time(td::int64 file_size, double max_delay) { + auto last_send_message_time = last_send_message_time_.get(file_size); + if (last_send_message_time == 0.0) { + return; + } + if (last_send_message_time < td::Time::now() - max_delay) { + LOG(DEBUG) << "Clear last send message time for size " << file_size; + last_send_message_time_.erase(file_size); + } +} + +void Client::do_send_message(object_ptr input_message_content, PromisedQueryPtr query, + bool force) { auto chat_id = query->arg("chat_id"); auto message_thread_id = get_message_id(query.get(), "message_thread_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 protect_content = to_bool(query->arg("protect_content")); - auto r_reply_markup = get_reply_markup(query.get()); + auto r_reply_markup = get_reply_markup(query.get(), bot_user_ids_); if (r_reply_markup.is_error()) { return fail_query_with_error(std::move(query), 400, r_reply_markup.error().message()); } @@ -11415,7 +11474,7 @@ void Client::do_send_message(object_ptr input_messa count++; send_request( - make_object(chat_id, message_thread_id, reply_to_message_id, + make_object(chat_id, message_thread_id, get_input_message_reply_to(reply_to_message_id), get_message_send_options(disable_notification, protect_content, std::move(send_at)), std::move(reply_markup), std::move(input_message_content)), td::make_unique(this, chat_id, std::move(query))); @@ -11424,7 +11483,7 @@ void Client::do_send_message(object_ptr input_messa std::move(on_message_thread_checked)); }; check_message(chat_id, reply_to_message_id, reply_to_message_id <= 0 || allow_sending_without_reply, - AccessRights::Write, "replied message", std::move(query), std::move(on_success)); + AccessRights::Write, "message to reply", std::move(query), std::move(on_success)); }); } @@ -11442,16 +11501,9 @@ void Client::on_sent_message(object_ptr &&message, int64 query_ 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); @@ -11680,6 +11732,7 @@ void Client::add_user(UserInfo *user_info, object_ptr &&user) { } user_info->language_code = std::move(user->language_code_); user_info->emoji_status_custom_emoji_id = user->emoji_status_ != nullptr ? user->emoji_status_->custom_emoji_id_ : 0; + user_info->emoji_status_expiration_date = user->emoji_status_ != nullptr ? user->emoji_status_->expiration_date_ : 0; // start custom properties user_info->is_verified = user->is_verified_; @@ -11775,8 +11828,8 @@ void Client::set_group_photo(int64 group_id, object_ptr &&pho add_group_info(group_id)->photo = std::move(photo); } -void Client::set_group_description(int64 group_id, td::string &&descripton) { - add_group_info(group_id)->description = std::move(descripton); +void Client::set_group_description(int64 group_id, td::string &&description) { + add_group_info(group_id)->description = std::move(description); } void Client::set_group_invite_link(int64 group_id, td::string &&invite_link) { @@ -11809,8 +11862,8 @@ void Client::set_supergroup_photo(int64 supergroup_id, object_ptrphoto = std::move(photo); } -void Client::set_supergroup_description(int64 supergroup_id, td::string &&descripton) { - add_supergroup_info(supergroup_id)->description = std::move(descripton); +void Client::set_supergroup_description(int64 supergroup_id, td::string &&description) { + add_supergroup_info(supergroup_id)->description = std::move(description); } void Client::set_supergroup_invite_link(int64 supergroup_id, td::string &&invite_link) { @@ -12017,6 +12070,11 @@ void Client::json_store_administrator_rights(td::JsonObjectScope &object, const } object("can_promote_members", td::JsonBool(rights->can_promote_members_)); object("can_manage_video_chats", td::JsonBool(rights->can_manage_video_chats_)); + if (chat_type == ChatType::Channel) { + object("can_post_stories", td::JsonBool(rights->can_post_stories_)); + object("can_edit_stories", td::JsonBool(rights->can_edit_stories_)); + object("can_delete_stories", td::JsonBool(rights->can_delete_stories_)); + } object("is_anonymous", td::JsonBool(rights->is_anonymous_)); } @@ -12188,6 +12246,8 @@ void Client::add_update_impl(UpdateType update_type, const td::VirtuallyJsonable last_update_creation_time_ = td::Time::now(); if (((allowed_update_types_ >> static_cast(update_type)) & 1) == 0) { + LOG(DEBUG) << "Skip unallowed update of the type " << static_cast(update_type) << ", allowed update mask is " + << allowed_update_types_; return; } @@ -12300,8 +12360,10 @@ void Client::process_new_callback_query_queue(int64 user_id, int 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; + auto reply_to_message_id = message_info == nullptr + ? 0 + : get_same_chat_reply_to_message_id(message_info->reply_to_message.get(), + message_info->message_thread_id); if (reply_to_message_id > 0 && get_message(chat_id, reply_to_message_id, false) == nullptr) { queue.has_active_request_ = true; return send_request(make_object(chat_id, message_id), @@ -12316,8 +12378,10 @@ void Client::process_new_callback_query_queue(int64 user_id, int state) { return send_request(make_object(message_sticker_set_id), td::make_unique(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; + auto reply_to_message_id = message_info == nullptr + ? 0 + : get_same_chat_reply_to_message_id(message_info->reply_to_message.get(), + message_info->message_thread_id); if (reply_to_message_id > 0) { auto reply_to_message_info = get_message(chat_id, reply_to_message_id, true); auto reply_sticker_set_id = @@ -12338,6 +12402,7 @@ void Client::process_new_callback_query_queue(int64 user_id, int state) { 150, user_id + (static_cast(3) << 33)); queue.queue_.pop(); + state = 0; } new_callback_query_queues_.erase(user_id); } @@ -12387,6 +12452,9 @@ void Client::add_update_chat_member(object_ptr &&updat auto webhook_queue_id = update->chat_id_ + (static_cast(is_my ? 5 : 6) << 33); auto update_type = is_my ? UpdateType::MyChatMember : UpdateType::ChatMember; add_update(update_type, JsonChatMemberUpdated(update.get(), this), left_time, webhook_queue_id); + } else { + LOG(DEBUG) << "Skip updateChatMember with date " << update->date_ << ", because current date is " + << get_unix_time(); } } @@ -12397,6 +12465,9 @@ void Client::add_update_chat_join_request(object_ptr 0) { auto webhook_queue_id = update->chat_id_ + (static_cast(6) << 33); add_update(UpdateType::ChatJoinRequest, JsonChatJoinRequest(update.get(), this), left_time, webhook_queue_id); + } else { + LOG(DEBUG) << "Skip updateNewChatJoinRequest with date " << update->request_->date_ << ", because current date is " + << get_unix_time(); } } @@ -12443,6 +12514,8 @@ bool Client::need_skip_update_message(int64 chat_id, const object_ptredit_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 + LOG(DEBUG) << "Skip update about message with date " << message_date << ", because current date is " + << get_unix_time(); return true; } @@ -12462,14 +12535,18 @@ bool Client::need_skip_update_message(int64 chat_id, const object_ptris_supergroup && message->content_->get_id() == td_api::messageSupergroupChatCreate::ID) { + // don't send message about channel creation, even the bot was added at exactly the same time + return true; + } } - if (message->self_destruct_time_ > 0) { + if (message->self_destruct_type_ != nullptr) { return true; } - if (message->forward_info_ != nullptr && - message->forward_info_->origin_->get_id() == td_api::messageForwardOriginMessageImport::ID) { + if (message->import_info_ != nullptr) { return true; } @@ -12497,7 +12574,7 @@ bool Client::need_skip_update_message(int64 chat_id, const object_ptr &message) { - if (message->content_->get_id() == td_api::messagePinMessage::ID) { - CHECK(message->reply_to_message_id_ == 0); - return static_cast(message->content_.get())->message_id_; +td::int64 Client::get_same_chat_reply_to_message_id(const td_api::messageReplyToMessage *reply_to, + int64 message_thread_id) { + if (reply_to != nullptr && reply_to->origin_ == nullptr) { + CHECK(reply_to->message_id_ > 0); + return reply_to->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_; + return message_thread_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); - } +td::int64 Client::get_same_chat_reply_to_message_id(const object_ptr &reply_to, + int64 message_thread_id) { + if (reply_to != nullptr) { + switch (reply_to->get_id()) { + case td_api::messageReplyToMessage::ID: + return get_same_chat_reply_to_message_id(static_cast(reply_to.get()), + message_thread_id); + case td_api::messageReplyToStory::ID: + break; + default: + UNREACHABLE(); + break; } } - if (reply_to_message_id > 0) { - reply_message_ids_[{message_info->chat_id, reply_to_message_id}].insert(message_info->id); - } + return message_thread_id; +} - message_info->reply_to_message_id = reply_to_message_id; +td::int64 Client::get_same_chat_reply_to_message_id(const object_ptr &message) { + auto content_message_id = [&] { + switch (message->content_->get_id()) { + case td_api::messagePinMessage::ID: + return static_cast(message->content_.get())->message_id_; + case td_api::messageGameScore::ID: + return static_cast(message->content_.get())->game_message_id_; + case td_api::messageChatSetBackground::ID: + return static_cast(message->content_.get()) + ->old_background_message_id_; + case td_api::messagePaymentSuccessful::ID: + UNREACHABLE(); + return static_cast(0); + default: + return static_cast(0); + } + }(); + if (content_message_id != 0) { + CHECK(message->reply_to_ == nullptr); + return content_message_id; + } + return get_same_chat_reply_to_message_id(message->reply_to_, message->message_thread_id_); +} + +void Client::drop_internal_reply_to_message_in_another_chat(object_ptr &message) { + if (message->reply_to_ != nullptr && message->reply_to_->get_id() == td_api::messageReplyToMessage::ID) { + auto *reply_to = static_cast(message->reply_to_.get()); + auto reply_in_chat_id = reply_to->chat_id_; + if (reply_in_chat_id != message->chat_id_ && reply_to->origin_ == nullptr) { + LOG(ERROR) << "Drop reply to message " << message->id_ << " in chat " << message->chat_id_ + << " from another chat " << reply_in_chat_id; + message->reply_to_ = nullptr; + } + } } td::Slice Client::get_sticker_type(const object_ptr &type) { @@ -12748,11 +12855,17 @@ void Client::process_new_message_queue(int64 chat_id, int state) { 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, state > 0) == nullptr) { - queue.has_active_request_ = true; - return send_request(make_object(chat_id, message_id), - td::make_unique(this, chat_id)); + + drop_internal_reply_to_message_in_another_chat(message_ref); + + int64 reply_to_message_id = get_same_chat_reply_to_message_id(message_ref); + if (state == 0) { + if (reply_to_message_id > 0 && get_message(chat_id, reply_to_message_id, false) == nullptr) { + queue.has_active_request_ = true; + return send_request(make_object(chat_id, message_id), + td::make_unique(this, chat_id)); + } + state = 1; } auto message_sticker_set_id = get_sticker_set_id(message_ref->content_); if (!have_sticker_set_name(message_sticker_set_id)) { @@ -12762,18 +12875,20 @@ void Client::process_new_message_queue(int64 chat_id, int state) { } if (reply_to_message_id > 0) { auto reply_to_message_info = get_message(chat_id, reply_to_message_id, true); - 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(reply_sticker_set_id), - td::make_unique(this, reply_sticker_set_id, 0, chat_id)); + if (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(reply_sticker_set_id), + td::make_unique(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(); + state = 0; if (need_skip_update_message(chat_id, message, is_edited)) { add_message(std::move(message)); continue; @@ -12808,7 +12923,8 @@ void Client::process_new_message_queue(int64 chat_id, int state) { auto now = get_unix_time(); auto update_delay_time = now - td::max(message_date, parameters_->shared_data_->get_unix_time(webhook_set_time_)); const auto UPDATE_DELAY_WARNING_TIME = 10 * 60; - if (update_delay_time > UPDATE_DELAY_WARNING_TIME && message_date > last_synchronization_error_date_ + 60) { + if (message_date > log_in_date_ && update_delay_time > UPDATE_DELAY_WARNING_TIME && + message_date > last_synchronization_error_date_ + 60) { if (delayed_update_count_ == 0) { delayed_update_type_ = update_type; delayed_chat_id_ = chat_id; @@ -12835,37 +12951,7 @@ void Client::process_new_message_queue(int64 chat_id, int state) { 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); -} - td::unique_ptr 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 message_info = std::move(messages_[{chat_id, message_id}]); if (message_info == nullptr) { if (yet_unsent_messages_.count({chat_id, message_id}) > 0) { @@ -12874,17 +12960,17 @@ td::unique_ptr Client::delete_message(int64 chat_id, int64 auto chat_info = get_chat(chat_id); CHECK(chat_info != nullptr); - td::Status error = - td::Status::Error(500, "Internal Server Error: sent message was immediately deleted and can't be returned"); + auto error = make_object( + 500, "Internal Server Error: sent message was immediately deleted and can't be returned"); if (chat_info->type == ChatInfo::Type::Supergroup) { auto supergroup_info = get_supergroup_info(chat_info->supergroup_id); CHECK(supergroup_info != nullptr); if (supergroup_info->status->get_id() == td_api::chatMemberStatusBanned::ID || supergroup_info->status->get_id() == td_api::chatMemberStatusLeft::ID) { if (supergroup_info->is_supergroup) { - error = td::Status::Error(403, "Forbidden: bot is not a member of the supergroup chat"); + error = make_object(403, "Forbidden: bot is not a member of the supergroup chat"); } else { - error = td::Status::Error(403, "Forbidden: bot is not a member of the channel chat"); + error = make_object(403, "Forbidden: bot is not a member of the channel chat"); } } } @@ -12892,7 +12978,6 @@ td::unique_ptr Client::delete_message(int64 chat_id, int64 on_message_send_failed(chat_id, message_id, 0, std::move(error)); } } else { - set_message_reply_to_message_id(message_info.get(), 0); messages_.erase({chat_id, message_id}); } return message_info; @@ -12927,38 +13012,34 @@ Client::FullMessageId Client::add_message(object_ptr &&message, message_info->initial_message_id = 0; message_info->initial_author_signature = td::string(); message_info->initial_sender_name = td::string(); + message_info->is_automatic_forward = false; 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(origin); + case td_api::messageOriginUser::ID: { + auto forward_info = move_object_as(origin); message_info->initial_sender_user_id = forward_info->sender_user_id_; break; } - case td_api::messageForwardOriginChat::ID: { - auto forward_info = move_object_as(origin); + case td_api::messageOriginChat::ID: { + auto forward_info = move_object_as(origin); message_info->initial_sender_chat_id = forward_info->sender_chat_id_; message_info->initial_author_signature = std::move(forward_info->author_signature_); break; } - case td_api::messageForwardOriginHiddenUser::ID: { - auto forward_info = move_object_as(origin); + case td_api::messageOriginHiddenUser::ID: { + auto forward_info = move_object_as(origin); message_info->initial_sender_name = std::move(forward_info->sender_name_); break; } - case td_api::messageForwardOriginChannel::ID: { - auto forward_info = move_object_as(origin); + case td_api::messageOriginChannel::ID: { + auto forward_info = move_object_as(origin); message_info->initial_chat_id = forward_info->chat_id_; message_info->initial_message_id = forward_info->message_id_; message_info->initial_author_signature = std::move(forward_info->author_signature_); break; } - case td_api::messageForwardOriginMessageImport::ID: { - auto forward_info = move_object_as(origin); - message_info->initial_sender_name = std::move(forward_info->sender_name_); - break; - } default: UNREACHABLE(); } @@ -12966,6 +13047,9 @@ Client::FullMessageId Client::add_message(object_ptr &&message, message_info->is_automatic_forward = from_chat_id != 0 && from_chat_id != chat_id && message->forward_info_->from_message_id_ != 0 && get_chat_type(chat_id) == ChatType::Supergroup && get_chat_type(from_chat_id) == ChatType::Channel; + } else if (message->import_info_ != nullptr) { + message_info->initial_send_date = message->import_info_->date_; + message_info->initial_sender_name = std::move(message->import_info_->sender_name_); } CHECK(message->sender_id_ != nullptr); @@ -13011,13 +13095,14 @@ Client::FullMessageId Client::add_message(object_ptr &&message, message_info->is_topic_message = message->is_topic_message_; 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; + drop_internal_reply_to_message_in_another_chat(message); + + if (message->reply_to_ != nullptr && message->reply_to_->get_id() == td_api::messageReplyToMessage::ID) { + message_info->reply_to_message = move_object_as(message->reply_to_); + } else { + message_info->reply_to_message = nullptr; } - 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; diff --git a/telegram-bot-api/Client.h b/telegram-bot-api/Client.h index f059a0a..0ce31ab 100644 --- a/telegram-bot-api/Client.h +++ b/telegram-bot-api/Client.h @@ -277,6 +277,7 @@ class Client final : public WebhookActor::Callback { struct UserInfo; struct ChatInfo; struct BotCommandScope; + struct BotUserIds; enum class AccessRights { Read, ReadMembers, Edit, Write }; @@ -375,13 +376,16 @@ class Client final : public WebhookActor::Callback { static bool to_bool(td::MutableSlice value); + static object_ptr get_input_message_reply_to(int64 reply_to_message_id); + static td::Result> get_keyboard_button(td::JsonValue &button); - td::Result> get_inline_keyboard_button(td::JsonValue &button); + static td::Result> get_inline_keyboard_button(td::JsonValue &button, + BotUserIds &bot_user_ids); - td::Result> get_reply_markup(const Query *query); + static td::Result> get_reply_markup(const Query *query, BotUserIds &bot_user_ids); - td::Result> get_reply_markup(td::JsonValue &&value); + static td::Result> get_reply_markup(td::JsonValue &&value, BotUserIds &bot_user_ids); static td::Result> get_labeled_price_part(td::JsonValue &value); @@ -415,13 +419,17 @@ class Client final : public WebhookActor::Callback { static td::Result> get_inline_query_results_button( td::MutableSlice value); - td::Result> get_inline_query_result(const Query *query); + static td::Result> get_inline_query_result(const Query *query, + BotUserIds &bot_user_ids); - td::Result> get_inline_query_result(td::JsonValue &&value); + static td::Result> get_inline_query_result(td::JsonValue &&value, + BotUserIds &bot_user_ids); - td::Result>> get_inline_query_results(const Query *query); + static td::Result>> get_inline_query_results( + const Query *query, BotUserIds &bot_user_ids); - td::Result>> get_inline_query_results(td::JsonValue &&value); + static td::Result>> get_inline_query_results( + td::JsonValue &&value, BotUserIds &bot_user_ids); struct BotCommandScope { object_ptr scope_; @@ -540,7 +548,7 @@ class Client final : public WebhookActor::Callback { void decrease_yet_unsent_message_count(int64 chat_id, int32 count); - int64 extract_yet_unsent_message_query_id(int64 chat_id, int64 message_id, bool *is_reply_to_message_deleted); + int64 extract_yet_unsent_message_query_id(int64 chat_id, int64 message_id); // start custom helper methods @@ -552,7 +560,7 @@ class Client final : public WebhookActor::Callback { static int64 get_int64_arg(const Query *query, td::Slice field_name, int64 default_value, int64 min_value = std::numeric_limits::min(), int64 max_value = std::numeric_limits::max()); - static td::Result> get_report_reason(const Query *query, + static td::Result> get_report_reason(const Query *query, td::Slice field_name = td::Slice("reason")); static td::Result> get_search_messages_filter( @@ -561,13 +569,15 @@ class Client final : public WebhookActor::Callback { // end custom helper methods void on_message_send_succeeded(object_ptr &&message, int64 old_message_id); - void on_message_send_failed(int64 chat_id, int64 old_message_id, int64 new_message_id, td::Status result); + + void on_message_send_failed(int64 chat_id, int64 old_message_id, int64 new_message_id, + object_ptr &&error); static bool init_methods(); static bool is_local_method(td::Slice method); - void on_cmd(PromisedQueryPtr query); + void on_cmd(PromisedQueryPtr query, bool force = false); td::Status process_get_me_query(PromisedQueryPtr &query); td::Status process_get_my_commands_query(PromisedQueryPtr &query); @@ -646,6 +656,7 @@ class Client final : public WebhookActor::Callback { td::Status process_reopen_general_forum_topic_query(PromisedQueryPtr &query); td::Status process_hide_general_forum_topic_query(PromisedQueryPtr &query); td::Status process_unhide_general_forum_topic_query(PromisedQueryPtr &query); + td::Status process_unpin_all_general_forum_topic_messages_query(PromisedQueryPtr &query); td::Status process_get_chat_member_query(PromisedQueryPtr &query); td::Status process_get_chat_administrators_query(PromisedQueryPtr &query); td::Status process_get_chat_member_count_query(PromisedQueryPtr &query); @@ -735,7 +746,10 @@ class Client final : public WebhookActor::Callback { void on_webhook_closed(td::Status status); - void do_send_message(object_ptr input_message_content, PromisedQueryPtr query); + void delete_last_send_message_time(td::int64 file_size, double max_delay); + + void do_send_message(object_ptr input_message_content, PromisedQueryPtr query, + bool force = false); int64 get_send_message_query_id(PromisedQueryPtr query, bool is_multisend); @@ -800,6 +814,7 @@ class Client final : public WebhookActor::Callback { td::string editable_username; td::string language_code; int64 emoji_status_custom_emoji_id; + int32 emoji_status_expiration_date; object_ptr photo; td::string bio; @@ -842,7 +857,7 @@ class Client final : public WebhookActor::Callback { }; static void add_group(GroupInfo *group_info, object_ptr &&group); void set_group_photo(int64 group_id, object_ptr &&photo); - void set_group_description(int64 group_id, td::string &&descripton); + void set_group_description(int64 group_id, td::string &&description); void set_group_invite_link(int64 group_id, td::string &&invite_link); GroupInfo *add_group_info(int64 group_id); const GroupInfo *get_group_info(int64 group_id) const; @@ -875,7 +890,7 @@ class Client final : public WebhookActor::Callback { }; static void add_supergroup(SupergroupInfo *supergroup_info, object_ptr &&supergroup); void set_supergroup_photo(int64 supergroup_id, object_ptr &&photo); - void set_supergroup_description(int64 supergroup_id, td::string &&descripton); + void set_supergroup_description(int64 supergroup_id, td::string &&description); void set_supergroup_invite_link(int64 supergroup_id, td::string &&invite_link); void set_supergroup_sticker_set_id(int64 supergroup_id, int64 sticker_set_id); void set_supergroup_can_set_sticker_set(int64 supergroup_id, bool can_set_sticker_set); @@ -926,7 +941,7 @@ class Client final : public WebhookActor::Callback { td::string initial_author_signature; td::string initial_sender_name; td::string author_signature; - int64 reply_to_message_id = 0; + object_ptr reply_to_message; int64 media_album_id = 0; int64 via_bot_user_id = 0; object_ptr content; @@ -943,13 +958,18 @@ class Client final : public WebhookActor::Callback { bool can_be_saved = false; bool is_automatic_forward = false; bool is_topic_message = false; - mutable bool is_reply_to_message_deleted = false; mutable bool is_content_changed = false; }; - static int64 &get_reply_to_message_id(object_ptr &message); + static int64 get_same_chat_reply_to_message_id(const td_api::messageReplyToMessage *reply_to, + int64 message_thread_id); - void set_message_reply_to_message_id(MessageInfo *message_info, int64 reply_to_message_id); + static int64 get_same_chat_reply_to_message_id(const object_ptr &reply_to, + int64 message_thread_id); + + static int64 get_same_chat_reply_to_message_id(const object_ptr &message); + + static void drop_internal_reply_to_message_in_another_chat(object_ptr &message); static td::Slice get_sticker_type(const object_ptr &type); @@ -1155,17 +1175,10 @@ class Client final : public WebhookActor::Callback { td::WaitFreeHashMap> supergroups_; td::WaitFreeHashMap> chats_; - td::FlatHashMap, FullMessageIdHash> - reply_message_ids_; // message -> replies to it - td::FlatHashMap, FullMessageIdHash> - yet_unsent_reply_message_ids_; // message -> replies to it - td::FlatHashMap> file_download_listeners_; td::FlatHashSet download_started_file_ids_; struct YetUnsentMessage { - int64 reply_to_message_id = 0; - bool is_reply_to_message_deleted = false; int64 send_message_query_id = 0; }; td::FlatHashMap yet_unsent_messages_; @@ -1206,11 +1219,15 @@ class Client final : public WebhookActor::Callback { td::WaitFreeHashMap sticker_set_names_; - int64 cur_temp_bot_user_id_ = 1; - td::FlatHashMap bot_user_ids_; - td::FlatHashSet unresolved_bot_usernames_; - td::FlatHashMap temp_to_real_bot_user_id_; - td::FlatHashMap> awaiting_bot_resolve_queries_; + td::WaitFreeHashMap last_send_message_time_; + + struct BotUserIds { + int64 default_bot_user_id_ = 0; + int64 cur_temp_bot_user_id_ = 1; + td::FlatHashMap bot_user_ids_; + td::FlatHashSet unresolved_bot_usernames_; + }; + BotUserIds bot_user_ids_; struct PendingBotResolveQuery { std::size_t pending_resolve_count = 0; @@ -1220,6 +1237,9 @@ class Client final : public WebhookActor::Callback { td::FlatHashMap pending_bot_resolve_queries_; int64 current_bot_resolve_query_id_ = 1; + td::FlatHashMap> awaiting_bot_resolve_queries_; + td::FlatHashMap temp_to_real_bot_user_id_; + td::string dir_; td::ActorOwn td_client_; td::ActorContext context_; @@ -1273,6 +1293,8 @@ class Client final : public WebhookActor::Callback { double previous_get_updates_finish_time_ = 0; double next_get_updates_conflict_time_ = 0; + int32 log_in_date_ = 0; + int32 flood_limited_query_count_ = 0; double next_flood_limit_warning_time_ = 0; diff --git a/telegram-bot-api/ClientManager.cpp b/telegram-bot-api/ClientManager.cpp index 5f351c2..f8aba7c 100644 --- a/telegram-bot-api/ClientManager.cpp +++ b/telegram-bot-api/ClientManager.cpp @@ -296,7 +296,7 @@ void ClientManager::get_stats(td::Promise promise, auto top_clients = get_top_clients(50, id_filter); if(!as_json) { - sb << stat_.get_description() << '\n'; + sb << BotStatActor::get_description() << '\n'; } if (id_filter.empty()) { if(as_json) { @@ -448,9 +448,6 @@ td::int64 ClientManager::get_tqueue_id(td::int64 user_id, bool is_test_dc) { } void ClientManager::start_up() { - //NB: the same scheduler as for database in Td - auto scheduler_id = 1; - // init tqueue { auto load_start_time = td::Time::now(); @@ -478,7 +475,8 @@ void ClientManager::start_up() { } } - auto concurrent_binlog = std::make_shared(std::move(binlog), scheduler_id); + auto concurrent_binlog = + std::make_shared(std::move(binlog), SharedData::get_binlog_scheduler_id()); auto concurrent_tqueue_binlog = td::make_unique>(); concurrent_tqueue_binlog->set_binlog(std::move(concurrent_binlog)); tqueue->set_callback(std::move(concurrent_tqueue_binlog)); @@ -493,18 +491,18 @@ void ClientManager::start_up() { // init webhook_db and user_db auto concurrent_webhook_db = td::make_unique>(); auto status = concurrent_webhook_db->init(parameters_->working_directory_ + "webhooks_db.binlog", td::DbKey::empty(), - scheduler_id); + SharedData::get_binlog_scheduler_id()); LOG_IF(FATAL, status.is_error()) << "Can't open webhooks_db.binlog " << status; parameters_->shared_data_->webhook_db_ = std::move(concurrent_webhook_db); auto concurrent_user_db = td::make_unique>(); - status = concurrent_user_db->init(parameters_->working_directory_ + "user_db.binlog", td::DbKey::empty(), scheduler_id); + status = concurrent_user_db->init(parameters_->working_directory_ + "user_db.binlog", td::DbKey::empty(), SharedData::get_binlog_scheduler_id()); LOG_IF(FATAL, status.is_error()) << "Can't open user_db.binlog " << status.error(); parameters_->shared_data_->user_db_ = std::move(concurrent_user_db); auto &webhook_db = *parameters_->shared_data_->webhook_db_; auto &user_db = *parameters_->shared_data_->user_db_; - for (auto key_value : webhook_db.get_all()) { + for (const auto &key_value : webhook_db.get_all()) { if (!token_range_(td::to_integer(key_value.first))) { LOG(WARNING) << "DROP WEBHOOK: " << key_value.first << " ---> " << key_value.second; webhook_db.erase(key_value.first); @@ -516,8 +514,8 @@ void ClientManager::start_up() { } // launch watchdog - watchdog_id_ = td::create_actor_on_scheduler( - "ManagerWatchdog", td::Scheduler::instance()->sched_count() - 3, td::this_thread::get_id(), WATCHDOG_TIMEOUT); + watchdog_id_ = td::create_actor_on_scheduler("ManagerWatchdog", SharedData::get_watchdog_scheduler_id(), + td::this_thread::get_id(), WATCHDOG_TIMEOUT); set_timeout_in(600.0); } @@ -684,7 +682,7 @@ void ClientManager::raw_event(const td::Event::Raw &event) { void ClientManager::timeout_expired() { send_closure(watchdog_id_, &Watchdog::kick); - set_timeout_in(WATCHDOG_TIMEOUT / 2); + set_timeout_in(WATCHDOG_TIMEOUT / 10); double now = td::Time::now(); if (now > next_tqueue_gc_time_) { diff --git a/telegram-bot-api/ClientParameters.h b/telegram-bot-api/ClientParameters.h index a0db963..e41ad50 100644 --- a/telegram-bot-api/ClientParameters.h +++ b/telegram-bot-api/ClientParameters.h @@ -54,15 +54,54 @@ struct SharedData { return static_cast(result); } - static td::int32 get_database_scheduler_id() { - // the same scheduler as for database in Td - return 1; - } - static td::int32 get_file_gc_scheduler_id() { // the same scheduler as for file GC in Td return 2; } + + static td::int32 get_client_scheduler_id() { + // the thread for ClientManager and all Clients + return 4; + } + + static td::int32 get_watchdog_scheduler_id() { + // the thread for watchdogs + return 5; + } + + static td::int32 get_slow_incoming_http_scheduler_id() { + // the thread for slow incoming HTTP connections + return 6; + } + + static td::int32 get_slow_outgoing_http_scheduler_id() { + // the thread for slow outgoing HTTP connections + return 7; + } + + static td::int32 get_dns_resolver_scheduler_id() { + // the thread for DNS resolving + return 8; + } + + static td::int32 get_binlog_scheduler_id() { + // the thread for TQueue and webhook binlogs + return 9; + } + + static td::int32 get_webhook_certificate_scheduler_id() { + // the thread for webhook certificate processing + return 10; + } + + static td::int32 get_statistics_thread_id() { + // the thread for CPU usage updating + return 11; + } + + static td::int32 get_thread_count() { + return 12; + } }; struct ClientParameters { diff --git a/telegram-bot-api/HttpServer.h b/telegram-bot-api/HttpServer.h index 8686a3f..c2398ee 100644 --- a/telegram-bot-api/HttpServer.h +++ b/telegram-bot-api/HttpServer.h @@ -6,6 +6,8 @@ // #pragma once +#include "telegram-bot-api/ClientParameters.h" + #include "td/net/HttpInboundConnection.h" #include "td/net/TcpListener.h" @@ -61,13 +63,8 @@ class HttpServer final : public td::TcpListener::Callback { } void accept(td::SocketFd fd) final { - auto scheduler_count = td::Scheduler::instance()->sched_count(); - auto scheduler_id = scheduler_count - 1; - if (scheduler_id > 0) { - scheduler_id--; - } td::create_actor("HttpInboundConnection", td::BufferedFd(std::move(fd)), 0, - 50, 500, creator_(), scheduler_id) + 50, 500, creator_(), SharedData::get_slow_incoming_http_scheduler_id()) .release(); } diff --git a/telegram-bot-api/Query.cpp b/telegram-bot-api/Query.cpp index 6ef2ae1..d2b6a60 100644 --- a/telegram-bot-api/Query.cpp +++ b/telegram-bot-api/Query.cpp @@ -44,7 +44,7 @@ Query::Query(td::vector &&container, td::Slice token, bool is_u } td::to_lower_inplace(method_); start_timestamp_ = td::Time::now(); - LOG(INFO) << "QUERY: create " << td::tag("ptr", this) << *this; + LOG(INFO) << "Query " << this << ": " << *this; if (shared_data_) { shared_data_->query_count_.fetch_add(1, std::memory_order_relaxed); if (method_ != "getupdates") { @@ -86,7 +86,7 @@ void Query::set_stat_actor(td::ActorId stat_actor) { void Query::set_ok(td::BufferSlice result) { CHECK(state_ == State::Query); - LOG(INFO) << "QUERY: got ok " << td::tag("ptr", this) << td::tag("text", result.as_slice()); + LOG(INFO) << "Query " << this << ": " << td::tag("method", method_) << td::tag("text", result.as_slice()); answer_ = std::move(result); state_ = State::OK; http_status_code_ = 200; @@ -94,7 +94,7 @@ void Query::set_ok(td::BufferSlice result) { } void Query::set_error(int http_status_code, td::BufferSlice result) { - LOG(INFO) << "QUERY: got error " << td::tag("ptr", this) << td::tag("code", http_status_code) + LOG(INFO) << "Query " << this << ": " << td::tag("method", method_) << td::tag("code", http_status_code) << td::tag("text", result.as_slice()); CHECK(state_ == State::Query); answer_ = std::move(result); @@ -116,9 +116,25 @@ td::StringBuilder &operator<<(td::StringBuilder &sb, const Query &query) { auto padded_time = td::lpad(PSTRING() << td::format::as_time(td::Time::now_cached() - query.start_timestamp()), 10, ' '); sb << "[bot" << td::rpad(query.token().str(), 46, ' ') << "][time:" << padded_time << ']' - << td::tag("method", td::lpad(query.method().str(), 20, ' ')); + << td::tag("method", td::lpad(query.method().str(), 25, ' ')); if (!query.args().empty()) { - sb << td::oneline(PSLICE() << query.args()); + sb << '{'; + for (const auto &arg : query.args()) { + sb << '['; + if (arg.first.size() > 128) { + sb << '<' << arg.first.size() << '>' << td::oneline(arg.first.substr(0, 128)) << "..."; + } else { + sb << td::oneline(arg.first); + } + sb << ':'; + if (arg.second.size() > 4096) { + sb << '<' << arg.second.size() << '>' << td::oneline(arg.second.substr(0, 4096)) << "..."; + } else { + sb << td::oneline(arg.second); + } + sb << ']'; + } + sb << '}'; } if (!query.files().empty()) { sb << query.files(); diff --git a/telegram-bot-api/Query.h b/telegram-bot-api/Query.h index b956e14..9096e93 100644 --- a/telegram-bot-api/Query.h +++ b/telegram-bot-api/Query.h @@ -38,41 +38,52 @@ class Query final : public td::ListNode { td::Slice token() const { return token_; } + bool is_user() const { return is_user_; } + bool is_test_dc() const { return is_test_dc_; } + td::Slice method() const { return method_; } + bool has_arg(td::Slice key) const { auto it = std::find_if(args_.begin(), args_.end(), [&key](const std::pair &s) { return s.first == key; }); return it != args_.end(); } + td::MutableSlice arg(td::Slice key) const { auto it = std::find_if(args_.begin(), args_.end(), [&key](const std::pair &s) { return s.first == key; }); return it == args_.end() ? td::MutableSlice() : it->second; } + const td::vector> &args() const { return args_; } + td::Slice get_header(td::Slice key) const { auto it = std::find_if(headers_.begin(), headers_.end(), [&key](const std::pair &s) { return s.first == key; }); return it == headers_.end() ? td::Slice() : it->second; } + const td::HttpFile *file(td::Slice key) const { auto it = std::find_if(files_.begin(), files_.end(), [&key](const td::HttpFile &f) { return f.field_name == key; }); return it == files_.end() ? nullptr : &*it; } + const td::vector &files() const { return files_; } + td::int64 files_size() const; + td::string get_peer_ip_address() const; td::BufferSlice &answer() { @@ -156,8 +167,6 @@ class Query final : public td::ListNode { td::int64 query_size() const; - td::int64 files_size() const; - td::int64 files_max_size() const; void send_request_stat() const; diff --git a/telegram-bot-api/Stats.cpp b/telegram-bot-api/Stats.cpp index 5dc08dd..31d2300 100644 --- a/telegram-bot-api/Stats.cpp +++ b/telegram-bot-api/Stats.cpp @@ -7,6 +7,7 @@ #include "telegram-bot-api/Stats.h" #include "td/utils/common.h" +#include "td/utils/logging.h" #include "td/utils/port/thread.h" #include "td/utils/SliceBuilder.h" #include "td/utils/StringBuilder.h" @@ -19,17 +20,24 @@ ServerCpuStat::ServerCpuStat() { } } -void ServerCpuStat::add_event(const td::CpuStat &cpu_stat, double now) { - std::lock_guard guard(mutex_); - for (auto &stat : stat_) { - stat.add_event(cpu_stat, now); +void ServerCpuStat::update(double now) { + auto r_cpu_stat = td::cpu_stat(); + if (r_cpu_stat.is_error()) { + return; } + + auto &cpu_stat = instance(); + std::lock_guard guard(cpu_stat.mutex_); + for (auto &stat : cpu_stat.stat_) { + stat.add_event(r_cpu_stat.ok(), now); + } + LOG(WARNING) << "CPU usage: " << cpu_stat.stat_[1].get_stat(now).as_vector()[0].value_; } -td::string ServerCpuStat::get_description() const { +td::string ServerCpuStat::get_description() { td::string res = "DURATION"; for (auto &descr : DESCR) { - res += "\t"; + res += '\t'; res += descr; } return res; @@ -37,7 +45,7 @@ td::string ServerCpuStat::get_description() const { static td::string to_percentage(td::uint64 ticks, td::uint64 total_ticks) { static double multiplier = 100.0 * (td::thread::hardware_concurrency() ? td::thread::hardware_concurrency() : 1); - return PSTRING() << (static_cast(ticks) / static_cast(total_ticks) * multiplier) << "%"; + return PSTRING() << (static_cast(ticks) / static_cast(total_ticks) * multiplier) << '%'; } td::vector CpuStat::as_vector() const { @@ -167,7 +175,7 @@ td::vector BotStatActor::as_json_ready_vector(double now) { return res; } -td::string BotStatActor::get_description() const { +td::string BotStatActor::get_description() { td::string res = "DURATION"; for (auto &descr : DESCR) { res += "\t"; diff --git a/telegram-bot-api/Stats.h b/telegram-bot-api/Stats.h index f3ed265..1b979d7 100644 --- a/telegram-bot-api/Stats.h +++ b/telegram-bot-api/Stats.h @@ -9,7 +9,6 @@ #include "td/actor/actor.h" #include "td/utils/common.h" -#include "td/utils/logging.h" #include "td/utils/port/Stat.h" #include "td/utils/Time.h" #include "td/utils/TimedStat.h" @@ -49,16 +48,10 @@ class ServerCpuStat { static ServerCpuStat stat; return stat; } - static void update(double now) { - auto r_event = td::cpu_stat(); - if (r_event.is_error()) { - return; - } - instance().add_event(r_event.ok(), now); - LOG(WARNING) << "CPU usage: " << instance().stat_[1].get_stat(now).as_vector()[0].value_; - } - td::string get_description() const; + static void update(double now); + + static td::string get_description(); td::vector as_vector(double now); td::vector> as_json_ready_vector(double now); @@ -72,8 +65,6 @@ class ServerCpuStat { td::TimedStat stat_[SIZE]; ServerCpuStat(); - - void add_event(const td::CpuStat &stat, double now); }; class ServerBotInfo { @@ -157,7 +148,7 @@ class BotStatActor final : public td::Actor { } BotStatActor(const BotStatActor &) = delete; - BotStatActor &operator=(const BotStatActor &other) = delete; + BotStatActor &operator=(const BotStatActor &) = delete; BotStatActor(BotStatActor &&) = default; BotStatActor &operator=(BotStatActor &&other) noexcept { if (!empty()) { @@ -185,8 +176,8 @@ class BotStatActor final : public td::Actor { td::vector as_vector(double now); td::vector as_json_ready_vector(double now); - td::string get_description() const; td::vector get_jsonable_description() const; + static td::string get_description(); double get_score(double now); diff --git a/telegram-bot-api/WebhookActor.cpp b/telegram-bot-api/WebhookActor.cpp index 548398d..3a243db 100644 --- a/telegram-bot-api/WebhookActor.cpp +++ b/telegram-bot-api/WebhookActor.cpp @@ -47,10 +47,8 @@ WebhookActor::WebhookActor(td::ActorShared callback, td::int64 tqueue_ , fix_ip_address_(fix_ip_address) , from_db_flag_(from_db_flag) , max_connections_(max_connections) - , secret_token_(std::move(secret_token)) - , slow_scheduler_id_(td::Scheduler::instance()->sched_count() - 2) { + , secret_token_(std::move(secret_token)) { CHECK(max_connections_ > 0); - CHECK(slow_scheduler_id_ > 0); if (!cached_ip_address.empty()) { auto r_ip_address = td::IPAddress::get_ip_address(cached_ip_address); @@ -73,7 +71,7 @@ WebhookActor::WebhookActor(td::ActorShared callback, td::int64 tqueue_ WebhookActor::~WebhookActor() { td::Scheduler::instance()->destroy_on_scheduler(SharedData::get_file_gc_scheduler_id(), update_map_, queue_updates_, - queues_); + queues_, ssl_ctx_); } void WebhookActor::relax_wakeup_at(double wakeup_at, const char *source) { @@ -230,7 +228,8 @@ td::Status WebhookActor::create_connection(td::BufferedFd fd) { auto *conn = connections_.get(id); conn->actor_id_ = td::create_actor( PSLICE() << "Connect:" << id, std::move(fd), std::move(ssl_stream), 0, 50, 60, - td::ActorShared(actor_id(this), id), slow_scheduler_id_); + td::ActorShared(actor_id(this), id), + SharedData::get_slow_outgoing_http_scheduler_id()); conn->ip_generation_ = ip_generation_; conn->event_id_ = {}; conn->id_ = id; @@ -623,10 +622,12 @@ void WebhookActor::handle(td::unique_ptr response) { if (!method.empty() && method != "deletewebhook" && method != "setwebhook" && method != "close" && method != "logout" && !td::begins_with(method, "get")) { VLOG(webhook) << "Receive request " << method << " in response to webhook"; - auto query = td::make_unique(std::move(response->container_), td::MutableSlice(), false, false, - td::MutableSlice(), std::move(response->args_), - std::move(response->headers_), std::move(response->files_), - parameters_->shared_data_, response->peer_address_, false); + response->container_.emplace_back(PSLICE() << (tqueue_id_ & ((static_cast(1) << 54) - 1))); + auto token = response->container_.back().as_slice(); + auto query = td::make_unique( + std::move(response->container_), token, tqueue_id_ >= (static_cast(1) << 54), false, + td::MutableSlice(), std::move(response->args_), std::move(response->headers_), + std::move(response->files_), parameters_->shared_data_, response->peer_address_, false); auto promised_query = PromisedQueryPtr(query.release(), PromiseDeleter(td::Promise>())); send_closure(callback_, &Callback::send, std::move(promised_query)); } @@ -690,7 +691,12 @@ void WebhookActor::handle(td::unique_ptr response) { void WebhookActor::start_up() { max_loaded_updates_ = max_connections_ * 2; - next_ip_address_resolve_time_ = last_success_time_ = td::Time::now() - 3600; + last_success_time_ = td::Time::now() - 2 * IP_ADDRESS_CACHE_TIME; + if (from_db_flag_) { + next_ip_address_resolve_time_ = td::Time::now() + td::Random::fast(0, IP_ADDRESS_CACHE_TIME); + } else { + next_ip_address_resolve_time_ = last_success_time_; + } active_new_connection_flood_.add_limit(0.5, 10); @@ -723,11 +729,12 @@ void WebhookActor::start_up() { if (url_.protocol_ != td::HttpUrl::Protocol::Http && !stop_flag_) { // asynchronously create SSL context - td::Scheduler::instance()->run_on_scheduler( - SharedData::get_database_scheduler_id(), [actor_id = actor_id(this), cert_path = cert_path_](td::Unit) mutable { - send_closure(actor_id, &WebhookActor::on_ssl_context_created, - td::SslCtx::create(cert_path, td::SslCtx::VerifyPeer::On)); - }); + td::Scheduler::instance()->run_on_scheduler(SharedData::get_webhook_certificate_scheduler_id(), + [actor_id = actor_id(this), cert_path = cert_path_](td::Unit) mutable { + send_closure( + actor_id, &WebhookActor::on_ssl_context_created, + td::SslCtx::create(cert_path, td::SslCtx::VerifyPeer::On)); + }); } yield(); diff --git a/telegram-bot-api/WebhookActor.h b/telegram-bot-api/WebhookActor.h index 926a914..4c101a3 100644 --- a/telegram-bot-api/WebhookActor.h +++ b/telegram-bot-api/WebhookActor.h @@ -177,7 +177,6 @@ class WebhookActor final : public td::HttpOutboundConnection::Callback { double last_success_time_ = 0; double wakeup_at_ = 0; bool last_update_was_successful_ = true; - td::int32 slow_scheduler_id_ = -1; void relax_wakeup_at(double wakeup_at, const char *source); diff --git a/telegram-bot-api/telegram-bot-api.cpp b/telegram-bot-api/telegram-bot-api.cpp index cd4b3b6..04755f9 100644 --- a/telegram-bot-api/telegram-bot-api.cpp +++ b/telegram-bot-api/telegram-bot-api.cpp @@ -165,7 +165,7 @@ int main(int argc, char *argv[]) { auto start_time = td::Time::now(); auto shared_data = std::make_shared(); auto parameters = std::make_unique(); - parameters->version_ = "6.7.1"; + parameters->version_ = "6.9.2"; parameters->shared_data_ = shared_data; parameters->start_time_ = start_time; auto net_query_stats = td::create_net_query_stats(); @@ -476,27 +476,22 @@ int main(int argc, char *argv[]) { // << (td::GitInfo::is_dirty() ? "(dirty)" : "") << " started"; LOG(WARNING) << "TDLight Bot API " << parameters->version_ << " server started"; - // +3 threads for Td - // one thread for ClientManager and all Clients - // one thread for watchdogs - // one thread for slow HTTP connections - // one thread for DNS resolving - const int thread_count = 7; - td::ConcurrentScheduler sched(thread_count, cpu_affinity); + td::ConcurrentScheduler sched(SharedData::get_thread_count() - 1, cpu_affinity); td::GetHostByNameActor::Options get_host_by_name_options; - get_host_by_name_options.scheduler_id = thread_count; + get_host_by_name_options.scheduler_id = SharedData::get_dns_resolver_scheduler_id(); parameters->get_host_by_name_actor_id_ = sched.create_actor_unsafe(0, "GetHostByName", std::move(get_host_by_name_options)) .release(); - auto client_manager = - sched.create_actor_unsafe(thread_count - 3, "ClientManager", std::move(parameters), token_range) - .release(); + auto client_manager = sched + .create_actor_unsafe(SharedData::get_client_scheduler_id(), "ClientManager", + std::move(parameters), token_range) + .release(); sched .create_actor_unsafe( - thread_count - 3, "HttpServer", http_ip_address, http_port, + SharedData::get_client_scheduler_id(), "HttpServer", http_ip_address, http_port, [client_manager, shared_data] { return td::ActorOwn( td::create_actor("HttpConnection", client_manager, shared_data)); @@ -506,7 +501,7 @@ int main(int argc, char *argv[]) { if (http_stat_port != 0) { sched .create_actor_unsafe( - thread_count - 3, "HttpStatsServer", http_stat_ip_address, http_stat_port, + SharedData::get_client_scheduler_id(), "HttpStatsServer", http_stat_ip_address, http_stat_port, [client_manager] { return td::ActorOwn( td::create_actor("HttpStatConnection", client_manager)); @@ -515,8 +510,8 @@ int main(int argc, char *argv[]) { } constexpr double WATCHDOG_TIMEOUT = 0.25; - auto watchdog_id = - sched.create_actor_unsafe(thread_count - 2, "Watchdog", td::this_thread::get_id(), WATCHDOG_TIMEOUT); + auto watchdog_id = sched.create_actor_unsafe(SharedData::get_watchdog_scheduler_id(), "Watchdog", + td::this_thread::get_id(), WATCHDOG_TIMEOUT); sched.start(); @@ -578,13 +573,15 @@ int main(int argc, char *argv[]) { next_cron_time = now; } next_cron_time += 1.0; - ServerCpuStat::update(now); + auto guard = sched.get_main_guard(); + td::Scheduler::instance()->run_on_scheduler(SharedData::get_statistics_thread_id(), + [](td::Unit) { ServerCpuStat::update(td::Time::now()); }); } if (now >= start_time + 600) { auto guard = sched.get_main_guard(); send_closure(watchdog_id, &Watchdog::kick); - next_watchdog_kick_time = now + WATCHDOG_TIMEOUT / 2; + next_watchdog_kick_time = now + WATCHDOG_TIMEOUT / 10; } if (!need_dump_statistics.test_and_set() || now > last_dump_time + 300.0) {