// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/telegram/BotInfoManager.h" #include "td/telegram/AuthManager.h" #include "td/telegram/DialogId.h" #include "td/telegram/FileReferenceManager.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/files/FileType.h" #include "td/telegram/Global.h" #include "td/telegram/misc.h" #include "td/telegram/net/NetQueryCreator.h" #include "td/telegram/StoryContent.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" #include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" #include "td/utils/logging.h" #include "td/utils/Status.h" #include #include namespace td { class SetBotGroupDefaultAdminRightsQuery final : public Td::ResultHandler { Promise promise_; public: explicit SetBotGroupDefaultAdminRightsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(AdministratorRights administrator_rights) { send_query(G()->net_query_creator().create( telegram_api::bots_setBotGroupDefaultAdminRights(administrator_rights.get_chat_admin_rights()), {{"me"}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.move_as_ok(); LOG_IF(WARNING, !result) << "Failed to set group default administrator rights"; td_->user_manager_->invalidate_user_full(td_->user_manager_->get_my_id()); promise_.set_value(Unit()); } void on_error(Status status) final { if (status.message() == "RIGHTS_NOT_MODIFIED") { return promise_.set_value(Unit()); } td_->user_manager_->invalidate_user_full(td_->user_manager_->get_my_id()); promise_.set_error(std::move(status)); } }; class SetBotBroadcastDefaultAdminRightsQuery final : public Td::ResultHandler { Promise promise_; public: explicit SetBotBroadcastDefaultAdminRightsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(AdministratorRights administrator_rights) { send_query(G()->net_query_creator().create( telegram_api::bots_setBotBroadcastDefaultAdminRights(administrator_rights.get_chat_admin_rights()), {{"me"}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.move_as_ok(); LOG_IF(WARNING, !result) << "Failed to set channel default administrator rights"; td_->user_manager_->invalidate_user_full(td_->user_manager_->get_my_id()); promise_.set_value(Unit()); } void on_error(Status status) final { if (status.message() == "RIGHTS_NOT_MODIFIED") { return promise_.set_value(Unit()); } td_->user_manager_->invalidate_user_full(td_->user_manager_->get_my_id()); promise_.set_error(std::move(status)); } }; static td_api::object_ptr convert_bot_media_preview( Td *td, telegram_api::object_ptr media, UserId bot_user_id, vector &file_ids) { auto content = get_story_content(td, std::move(media->media_), DialogId(bot_user_id)); if (content == nullptr) { LOG(ERROR) << "Receive invalid media preview for " << bot_user_id; return nullptr; } append(file_ids, get_story_content_file_ids(td, content.get())); return td_api::make_object(max(media->date_, 0), get_story_content_object(td, content.get())); } class GetPreviewMediasQuery final : public Td::ResultHandler { Promise> promise_; UserId bot_user_id_; public: explicit GetPreviewMediasQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(UserId bot_user_id, telegram_api::object_ptr input_user) { bot_user_id_ = bot_user_id; send_query( G()->net_query_creator().create(telegram_api::bots_getPreviewMedias(std::move(input_user)), {{bot_user_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetPreviewMediasQuery: " << to_string(ptr); vector> previews; vector file_ids; for (auto &media : ptr) { auto preview = convert_bot_media_preview(td_, std::move(media), bot_user_id_, file_ids); if (preview != nullptr) { previews.push_back(std::move(preview)); } } if (!file_ids.empty()) { auto file_source_id = td_->bot_info_manager_->get_bot_media_preview_file_source_id(bot_user_id_); for (auto file_id : file_ids) { td_->file_manager_->add_file_source(file_id, file_source_id); } } td_->user_manager_->on_update_bot_has_preview_medias(bot_user_id_, !previews.empty()); promise_.set_value(td_api::make_object(std::move(previews))); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetPreviewInfoQuery final : public Td::ResultHandler { Promise> promise_; UserId bot_user_id_; string language_code_; public: explicit GetPreviewInfoQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(UserId bot_user_id, telegram_api::object_ptr input_user, const string &language_code) { bot_user_id_ = bot_user_id; language_code_ = language_code; send_query(G()->net_query_creator().create(telegram_api::bots_getPreviewInfo(std::move(input_user), language_code), {{bot_user_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetPreviewInfoQuery: " << to_string(ptr); vector> previews; vector file_ids; for (auto &media : ptr->media_) { auto preview = convert_bot_media_preview(td_, std::move(media), bot_user_id_, file_ids); if (preview != nullptr) { previews.push_back(std::move(preview)); } } if (!file_ids.empty()) { auto file_source_id = td_->bot_info_manager_->get_bot_media_preview_info_file_source_id(bot_user_id_, language_code_); for (auto file_id : file_ids) { td_->file_manager_->add_file_source(file_id, file_source_id); } } promise_.set_value( td_api::make_object(std::move(previews), std::move(ptr->lang_codes_))); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class BotInfoManager::AddPreviewMediaQuery final : public Td::ResultHandler { FileId file_id_; unique_ptr pending_preview_; public: void send(telegram_api::object_ptr input_user, unique_ptr pending_preview, FileId file_id, telegram_api::object_ptr input_file) { file_id_ = file_id; pending_preview_ = std::move(pending_preview); CHECK(pending_preview_ != nullptr); const StoryContent *content = pending_preview_->content_.get(); CHECK(input_file != nullptr); auto input_media = get_story_content_input_media(td_, content, std::move(input_file)); CHECK(input_media != nullptr); if (pending_preview_->edited_file_id_.is_valid()) { auto edited_input_media = td_->bot_info_manager_->get_fake_input_media(pending_preview_->edited_file_id_); if (edited_input_media == nullptr) { return on_error(Status::Error(400, "Wrong media to edit specified")); } send_query(G()->net_query_creator().create( telegram_api::bots_editPreviewMedia(std::move(input_user), pending_preview_->language_code_, std::move(edited_input_media), std::move(input_media)), {{pending_preview_->bot_user_id_}})); } else { send_query(G()->net_query_creator().create( telegram_api::bots_addPreviewMedia(std::move(input_user), pending_preview_->language_code_, std::move(input_media)), {{pending_preview_->bot_user_id_}})); } } void on_result(BufferSlice packet) final { static_assert(std::is_same::value, ""); auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } if (file_id_.is_valid()) { td_->file_manager_->delete_partial_remote_location(file_id_); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for AddPreviewMediaQuery: " << to_string(ptr); auto bot_user_id = pending_preview_->bot_user_id_; vector file_ids; auto preview = convert_bot_media_preview(td_, std::move(ptr), bot_user_id, file_ids); if (preview == nullptr) { LOG(ERROR) << "Receive invalid sent media preview"; return pending_preview_->promise_.set_error(Status::Error(500, "Receive invalid preview")); } if (!file_ids.empty()) { auto file_source_id = td_->bot_info_manager_->get_bot_media_preview_info_file_source_id( bot_user_id, pending_preview_->language_code_); for (auto file_id : file_ids) { td_->file_manager_->add_file_source(file_id, file_source_id); } } if (pending_preview_->language_code_.empty()) { td_->user_manager_->on_update_bot_has_preview_medias(bot_user_id, true); } pending_preview_->promise_.set_value(std::move(preview)); } void on_error(Status status) final { LOG(INFO) << "Receive error for AddPreviewMediaQuery: " << status; auto bad_parts = FileManager::get_missing_file_parts(status); if (!bad_parts.empty()) { td_->bot_info_manager_->on_add_bot_media_preview_file_parts_missing(std::move(pending_preview_), std::move(bad_parts)); return; } if (file_id_.is_valid()) { td_->file_manager_->delete_partial_remote_location(file_id_); } pending_preview_->promise_.set_error(std::move(status)); } }; class ReorderPreviewMediasQuery final : public Td::ResultHandler { Promise promise_; UserId bot_user_id_; public: explicit ReorderPreviewMediasQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(UserId bot_user_id, telegram_api::object_ptr input_user, const string &language_code, vector> input_media) { bot_user_id_ = bot_user_id; send_query(G()->net_query_creator().create( telegram_api::bots_reorderPreviewMedias(std::move(input_user), language_code, std::move(input_media)), {{bot_user_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } td_->user_manager_->on_update_bot_has_preview_medias(bot_user_id_, true); promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class DeletePreviewMediaQuery final : public Td::ResultHandler { Promise promise_; UserId bot_user_id_; public: explicit DeletePreviewMediaQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(UserId bot_user_id, telegram_api::object_ptr input_user, const string &language_code, vector> input_media) { bot_user_id_ = bot_user_id; send_query(G()->net_query_creator().create( telegram_api::bots_deletePreviewMedia(std::move(input_user), language_code, std::move(input_media)), {{bot_user_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } td_->user_manager_->reload_user_full(bot_user_id_, std::move(promise_), "DeletePreviewMediaQuery"); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class CanBotSendMessageQuery final : public Td::ResultHandler { Promise promise_; public: explicit CanBotSendMessageQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(UserId bot_user_id) { auto r_input_user = td_->user_manager_->get_input_user(bot_user_id); if (r_input_user.is_error()) { return on_error(r_input_user.move_as_error()); } send_query( G()->net_query_creator().create(telegram_api::bots_canSendMessage(r_input_user.move_as_ok()), {{bot_user_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } if (result_ptr.ok()) { promise_.set_value(Unit()); } else { promise_.set_error(Status::Error(404, "Not Found")); } } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class AllowBotSendMessageQuery final : public Td::ResultHandler { Promise promise_; public: explicit AllowBotSendMessageQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(UserId bot_user_id) { auto r_input_user = td_->user_manager_->get_input_user(bot_user_id); if (r_input_user.is_error()) { return on_error(r_input_user.move_as_error()); } send_query(G()->net_query_creator().create(telegram_api::bots_allowSendMessage(r_input_user.move_as_ok()), {{bot_user_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for AllowBotSendMessageQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; static Result> get_bot_input_user(const Td *td, UserId bot_user_id) { if (td->auth_manager_->is_bot()) { if (bot_user_id != UserId() && bot_user_id != td->user_manager_->get_my_id()) { return Status::Error(400, "Invalid bot user identifier specified"); } } else { TRY_RESULT(bot_data, td->user_manager_->get_bot_data(bot_user_id)); if (!bot_data.can_be_edited) { return Status::Error(400, "The bot can't be edited"); } return td->user_manager_->get_input_user(bot_user_id); } return nullptr; } class SetBotInfoQuery final : public Td::ResultHandler { Promise promise_; UserId bot_user_id_; bool set_name_ = false; bool set_info_ = false; void invalidate_bot_info() { if (set_info_) { td_->user_manager_->invalidate_user_full(bot_user_id_); } } public: explicit SetBotInfoQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(UserId bot_user_id, const string &language_code, bool set_name, const string &name, bool set_description, const string &description, bool set_about, const string &about) { int32 flags = 0; if (set_name) { flags |= telegram_api::bots_setBotInfo::NAME_MASK; } if (set_about) { flags |= telegram_api::bots_setBotInfo::ABOUT_MASK; } if (set_description) { flags |= telegram_api::bots_setBotInfo::DESCRIPTION_MASK; } auto r_input_user = get_bot_input_user(td_, bot_user_id); if (r_input_user.is_error()) { return on_error(r_input_user.move_as_error()); } if (r_input_user.ok() != nullptr) { flags |= telegram_api::bots_setBotInfo::BOT_MASK; bot_user_id_ = bot_user_id; } else { bot_user_id_ = td_->user_manager_->get_my_id(); } set_name_ = set_name; set_info_ = set_about || set_description; invalidate_bot_info(); send_query(G()->net_query_creator().create( telegram_api::bots_setBotInfo(flags, r_input_user.move_as_ok(), language_code, name, about, description), {{bot_user_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.move_as_ok(); LOG_IF(WARNING, !result) << "Failed to set bot info"; if (set_info_) { invalidate_bot_info(); if (!td_->auth_manager_->is_bot()) { return td_->user_manager_->reload_user_full(bot_user_id_, std::move(promise_), "SetBotInfoQuery"); } } if (set_name_) { return td_->user_manager_->reload_user(bot_user_id_, std::move(promise_), "SetBotInfoQuery"); } // invalidation is enough for bots if name wasn't changed promise_.set_value(Unit()); } void on_error(Status status) final { invalidate_bot_info(); promise_.set_error(std::move(status)); } }; class GetBotInfoQuery final : public Td::ResultHandler { vector> name_promises_; vector> description_promises_; vector> about_promises_; public: GetBotInfoQuery(vector> name_promises, vector> description_promises, vector> about_promises) : name_promises_(std::move(name_promises)) , description_promises_(std::move(description_promises)) , about_promises_(std::move(about_promises)) { } void send(UserId bot_user_id, const string &language_code) { int32 flags = 0; auto r_input_user = get_bot_input_user(td_, bot_user_id); if (r_input_user.is_error()) { return on_error(r_input_user.move_as_error()); } if (r_input_user.ok() != nullptr) { flags |= telegram_api::bots_getBotInfo::BOT_MASK; } send_query(G()->net_query_creator().create( telegram_api::bots_getBotInfo(flags, r_input_user.move_as_ok(), language_code), {{bot_user_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto result = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetBotInfoQuery: " << to_string(result); for (auto &promise : name_promises_) { promise.set_value(string(result->name_)); } for (auto &promise : description_promises_) { promise.set_value(string(result->description_)); } for (auto &promise : about_promises_) { promise.set_value(string(result->about_)); } } void on_error(Status status) final { fail_promises(name_promises_, status.clone()); fail_promises(description_promises_, status.clone()); fail_promises(about_promises_, status.clone()); } }; class BotInfoManager::UploadMediaCallback final : public FileManager::UploadCallback { public: void on_upload_ok(FileId file_id, telegram_api::object_ptr input_file) final { send_closure_later(G()->bot_info_manager(), &BotInfoManager::on_upload_bot_media_preview, file_id, std::move(input_file)); } void on_upload_encrypted_ok(FileId file_id, telegram_api::object_ptr input_file) final { UNREACHABLE(); } void on_upload_secure_ok(FileId file_id, telegram_api::object_ptr input_file) final { UNREACHABLE(); } void on_upload_error(FileId file_id, Status error) final { send_closure_later(G()->bot_info_manager(), &BotInfoManager::on_upload_bot_media_preview_error, file_id, std::move(error)); } }; BotInfoManager::BotInfoManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { upload_media_callback_ = std::make_shared(); } BotInfoManager::~BotInfoManager() { Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), bot_media_preview_file_source_ids_, bot_media_preview_info_file_source_ids_); } void BotInfoManager::tear_down() { parent_.reset(); } void BotInfoManager::hangup() { auto set_queries = std::move(pending_set_bot_info_queries_); auto get_queries = std::move(pending_get_bot_info_queries_); for (auto &query : set_queries) { query.promise_.set_error(Global::request_aborted_error()); } for (auto &query : get_queries) { query.promise_.set_error(Global::request_aborted_error()); } stop(); } void BotInfoManager::timeout_expired() { auto set_queries = std::move(pending_set_bot_info_queries_); reset_to_empty(pending_set_bot_info_queries_); auto get_queries = std::move(pending_get_bot_info_queries_); reset_to_empty(pending_get_bot_info_queries_); std::stable_sort(set_queries.begin(), set_queries.end(), [](const PendingSetBotInfoQuery &lhs, const PendingSetBotInfoQuery &rhs) { return lhs.bot_user_id_.get() < rhs.bot_user_id_.get() || (lhs.bot_user_id_ == rhs.bot_user_id_ && lhs.language_code_ < rhs.language_code_); }); for (size_t i = 0; i < set_queries.size();) { bool has_value[3] = {false, false, false}; string values[3]; vector> promises; size_t j = i; while (j < set_queries.size() && set_queries[i].bot_user_id_ == set_queries[j].bot_user_id_ && set_queries[i].language_code_ == set_queries[j].language_code_) { has_value[set_queries[j].type_] = true; values[set_queries[j].type_] = std::move(set_queries[j].value_); promises.push_back(std::move(set_queries[j].promise_)); j++; } auto promise = PromiseCreator::lambda([promises = std::move(promises)](Result &&result) mutable { if (result.is_error()) { fail_promises(promises, result.move_as_error()); } else { set_promises(promises); } }); td_->create_handler(std::move(promise)) ->send(set_queries[i].bot_user_id_, set_queries[i].language_code_, has_value[0], values[0], has_value[1], values[1], has_value[2], values[2]); i = j; } std::stable_sort(get_queries.begin(), get_queries.end(), [](const PendingGetBotInfoQuery &lhs, const PendingGetBotInfoQuery &rhs) { return lhs.bot_user_id_.get() < rhs.bot_user_id_.get() || (lhs.bot_user_id_ == rhs.bot_user_id_ && lhs.language_code_ < rhs.language_code_); }); for (size_t i = 0; i < get_queries.size();) { vector> promises[3]; size_t j = i; while (j < get_queries.size() && get_queries[i].bot_user_id_ == get_queries[j].bot_user_id_ && get_queries[i].language_code_ == get_queries[j].language_code_) { promises[get_queries[j].type_].push_back(std::move(get_queries[j].promise_)); j++; } td_->create_handler(std::move(promises[0]), std::move(promises[1]), std::move(promises[2])) ->send(get_queries[i].bot_user_id_, get_queries[i].language_code_); i = j; } } void BotInfoManager::set_default_group_administrator_rights(AdministratorRights administrator_rights, Promise &&promise) { td_->user_manager_->invalidate_user_full(td_->user_manager_->get_my_id()); td_->create_handler(std::move(promise))->send(administrator_rights); } void BotInfoManager::set_default_channel_administrator_rights(AdministratorRights administrator_rights, Promise &&promise) { td_->user_manager_->invalidate_user_full(td_->user_manager_->get_my_id()); td_->create_handler(std::move(promise))->send(administrator_rights); } void BotInfoManager::can_bot_send_messages(UserId bot_user_id, Promise &&promise) { td_->create_handler(std::move(promise))->send(bot_user_id); } void BotInfoManager::allow_bot_to_send_messages(UserId bot_user_id, Promise &&promise) { td_->create_handler(std::move(promise))->send(bot_user_id); } FileSourceId BotInfoManager::get_bot_media_preview_file_source_id(UserId bot_user_id) { if (!bot_user_id.is_valid()) { return FileSourceId(); } auto &source_id = bot_media_preview_file_source_ids_[bot_user_id]; if (!source_id.is_valid()) { source_id = td_->file_reference_manager_->create_bot_media_preview_file_source(bot_user_id); } VLOG(file_references) << "Return " << source_id << " for media previews of " << bot_user_id; return source_id; } FileSourceId BotInfoManager::get_bot_media_preview_info_file_source_id(UserId bot_user_id, const string &language_code) { if (!bot_user_id.is_valid()) { return FileSourceId(); } auto &source_id = bot_media_preview_info_file_source_ids_[{bot_user_id, language_code}]; if (!source_id.is_valid()) { source_id = td_->file_reference_manager_->create_bot_media_preview_info_file_source(bot_user_id, language_code); } VLOG(file_references) << "Return " << source_id << " for media preview info of " << bot_user_id << " for " << language_code; return source_id; } Result> BotInfoManager::get_media_preview_bot_input_user( UserId user_id, bool can_be_edited) { TRY_RESULT(bot_data, td_->user_manager_->get_bot_data(user_id)); if (can_be_edited && !bot_data.can_be_edited) { return Status::Error(400, "Bot must be owned"); } if (!bot_data.has_main_app) { return Status::Error(400, "Bot must have the main Mini App"); } return td_->user_manager_->get_input_user(user_id); } Status BotInfoManager::validate_bot_media_preview_language_code(const string &language_code) { if (language_code.empty()) { return Status::OK(); } if (language_code.size() < 2 || language_code[0] == '-' || language_code[1] == '-') { return Status::Error(400, "Invalid language code specified"); } for (auto c : language_code) { if ((c < 'a' || c > 'z') && c != '-') { return Status::Error(400, "Invalid language code specified"); } } return Status::OK(); } void BotInfoManager::get_bot_media_previews(UserId bot_user_id, Promise> &&promise) { TRY_RESULT_PROMISE(promise, input_user, get_media_preview_bot_input_user(bot_user_id)); td_->create_handler(std::move(promise))->send(bot_user_id, std::move(input_user)); } void BotInfoManager::get_bot_media_preview_info(UserId bot_user_id, const string &language_code, Promise> &&promise) { TRY_RESULT_PROMISE(promise, input_user, get_media_preview_bot_input_user(bot_user_id, true)); TRY_STATUS_PROMISE(promise, validate_bot_media_preview_language_code(language_code)); td_->create_handler(std::move(promise))->send(bot_user_id, std::move(input_user), language_code); } void BotInfoManager::reload_bot_media_previews(UserId bot_user_id, Promise &&promise) { get_bot_media_previews( bot_user_id, PromiseCreator::lambda([promise = std::move(promise)]( Result> result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { promise.set_value(Unit()); } })); } void BotInfoManager::reload_bot_media_preview_info(UserId bot_user_id, const string &language_code, Promise &&promise) { get_bot_media_preview_info( bot_user_id, language_code, PromiseCreator::lambda( [promise = std::move(promise)](Result> result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { promise.set_value(Unit()); } })); } void BotInfoManager::add_bot_media_preview(UserId bot_user_id, const string &language_code, td_api::object_ptr &&input_content, Promise> &&promise) { TRY_RESULT_PROMISE(promise, input_user, get_media_preview_bot_input_user(bot_user_id, true)); TRY_STATUS_PROMISE(promise, validate_bot_media_preview_language_code(language_code)); TRY_RESULT_PROMISE(promise, content, get_input_story_content(td_, std::move(input_content), DialogId(bot_user_id))); auto pending_preview = make_unique(); pending_preview->bot_user_id_ = bot_user_id; pending_preview->language_code_ = language_code; pending_preview->content_ = dup_story_content(td_, content.get()); pending_preview->upload_order_ = ++bot_media_preview_upload_order_; pending_preview->promise_ = std::move(promise); do_add_bot_media_preview(std::move(pending_preview), {}); } void BotInfoManager::edit_bot_media_preview(UserId bot_user_id, const string &language_code, FileId file_id, td_api::object_ptr &&input_content, Promise> &&promise) { TRY_RESULT_PROMISE(promise, input_user, get_media_preview_bot_input_user(bot_user_id, true)); TRY_STATUS_PROMISE(promise, validate_bot_media_preview_language_code(language_code)); TRY_RESULT_PROMISE(promise, content, get_input_story_content(td_, std::move(input_content), DialogId(bot_user_id))); auto input_media = get_fake_input_media(file_id); if (input_media == nullptr) { return promise.set_error(Status::Error(400, "Wrong media to edit specified")); } auto pending_preview = make_unique(); pending_preview->edited_file_id_ = file_id; pending_preview->bot_user_id_ = bot_user_id; pending_preview->language_code_ = language_code; pending_preview->content_ = dup_story_content(td_, content.get()); pending_preview->upload_order_ = ++bot_media_preview_upload_order_; pending_preview->promise_ = std::move(promise); do_add_bot_media_preview(std::move(pending_preview), {}); } void BotInfoManager::do_add_bot_media_preview(unique_ptr &&pending_preview, vector bad_parts) { auto content = pending_preview->content_.get(); auto upload_order = pending_preview->upload_order_; FileId file_id = get_story_content_any_file_id(content); CHECK(file_id.is_valid()); LOG(INFO) << "Ask to upload file " << file_id << " with bad parts " << bad_parts; bool is_inserted = being_uploaded_files_.emplace(file_id, std::move(pending_preview)).second; CHECK(is_inserted); // need to call resume_upload synchronously to make upload process consistent with being_uploaded_files_ // and to send is_uploading_active == true in response td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_media_callback_, 1, upload_order); } void BotInfoManager::on_add_bot_media_preview_file_parts_missing(unique_ptr &&pending_preview, vector &&bad_parts) { do_add_bot_media_preview(std::move(pending_preview), std::move(bad_parts)); } void BotInfoManager::on_upload_bot_media_preview(FileId file_id, telegram_api::object_ptr input_file) { if (G()->close_flag()) { return; } LOG(INFO) << "File " << file_id << " has been uploaded"; auto it = being_uploaded_files_.find(file_id); if (it == being_uploaded_files_.end()) { // callback may be called just before the file upload was canceled return; } auto pending_preview = std::move(it->second); being_uploaded_files_.erase(it); FileView file_view = td_->file_manager_->get_file_view(file_id); CHECK(!file_view.is_encrypted()); if (input_file == nullptr && file_view.has_remote_location()) { if (file_view.main_remote_location().is_web()) { return pending_preview->promise_.set_error(Status::Error(400, "Can't use web photo as a preview")); } if (pending_preview->was_reuploaded_) { return pending_preview->promise_.set_error(Status::Error(500, "Failed to reupload preview")); } pending_preview->was_reuploaded_ = true; // delete file reference and forcely reupload the file td_->file_manager_->delete_file_reference(file_id, file_view.main_remote_location().get_file_reference()); return do_add_bot_media_preview(std::move(pending_preview), {-1}); } CHECK(input_file != nullptr); TRY_RESULT_PROMISE(pending_preview->promise_, input_user, get_media_preview_bot_input_user(pending_preview->bot_user_id_, true)); td_->create_handler()->send(std::move(input_user), std::move(pending_preview), file_id, std::move(input_file)); } void BotInfoManager::on_upload_bot_media_preview_error(FileId file_id, Status status) { if (G()->close_flag()) { return; } LOG(INFO) << "File " << file_id << " has upload error " << status; auto it = being_uploaded_files_.find(file_id); if (it == being_uploaded_files_.end()) { // callback may be called just before the file upload was canceled return; } auto pending_preview = std::move(it->second); being_uploaded_files_.erase(it); pending_preview->promise_.set_error(std::move(status)); } telegram_api::object_ptr BotInfoManager::get_fake_input_media(FileId file_id) const { FileView file_view = td_->file_manager_->get_file_view(file_id); if (file_view.empty() || !file_view.has_remote_location() || file_view.remote_location().is_web()) { return nullptr; } switch (file_view.get_type()) { case FileType::VideoStory: return telegram_api::make_object( 0, false /*ignored*/, file_view.remote_location().as_input_document(), 0, string()); case FileType::PhotoStory: return telegram_api::make_object(0, false /*ignored*/, file_view.remote_location().as_input_photo(), 0); default: return nullptr; } } void BotInfoManager::reorder_bot_media_previews(UserId bot_user_id, const string &language_code, const vector &file_ids, Promise &&promise) { TRY_RESULT_PROMISE(promise, input_user, get_media_preview_bot_input_user(bot_user_id, true)); TRY_STATUS_PROMISE(promise, validate_bot_media_preview_language_code(language_code)); vector> input_medias; for (auto file_id : file_ids) { auto input_media = get_fake_input_media(FileId(file_id, 0)); if (input_media == nullptr) { return promise.set_error(Status::Error(400, "Wrong media to delete specified")); } input_medias.push_back(std::move(input_media)); } if (input_medias.empty()) { return promise.set_value(Unit()); } td_->create_handler(std::move(promise)) ->send(bot_user_id, std::move(input_user), language_code, std::move(input_medias)); } void BotInfoManager::delete_bot_media_previews(UserId bot_user_id, const string &language_code, const vector &file_ids, Promise &&promise) { TRY_RESULT_PROMISE(promise, input_user, get_media_preview_bot_input_user(bot_user_id, true)); TRY_STATUS_PROMISE(promise, validate_bot_media_preview_language_code(language_code)); vector> input_medias; for (auto file_id : file_ids) { auto input_media = get_fake_input_media(FileId(file_id, 0)); if (input_media == nullptr) { return promise.set_error(Status::Error(400, "Wrong media to delete specified")); } input_medias.push_back(std::move(input_media)); } td_->create_handler(std::move(promise)) ->send(bot_user_id, std::move(input_user), language_code, std::move(input_medias)); } void BotInfoManager::add_pending_set_query(UserId bot_user_id, const string &language_code, int type, const string &value, Promise &&promise) { pending_set_bot_info_queries_.emplace_back(bot_user_id, language_code, type, value, std::move(promise)); if (!has_timeout()) { set_timeout_in(MAX_QUERY_DELAY); } } void BotInfoManager::add_pending_get_query(UserId bot_user_id, const string &language_code, int type, Promise &&promise) { pending_get_bot_info_queries_.emplace_back(bot_user_id, language_code, type, std::move(promise)); if (!has_timeout()) { set_timeout_in(MAX_QUERY_DELAY); } } void BotInfoManager::set_bot_name(UserId bot_user_id, const string &language_code, const string &name, Promise &&promise) { TRY_STATUS_PROMISE(promise, validate_bot_language_code(language_code)); add_pending_set_query(bot_user_id, language_code, 0, name, std::move(promise)); } void BotInfoManager::get_bot_name(UserId bot_user_id, const string &language_code, Promise &&promise) { TRY_STATUS_PROMISE(promise, validate_bot_language_code(language_code)); add_pending_get_query(bot_user_id, language_code, 0, std::move(promise)); } void BotInfoManager::set_bot_info_description(UserId bot_user_id, const string &language_code, const string &description, Promise &&promise) { TRY_STATUS_PROMISE(promise, validate_bot_language_code(language_code)); add_pending_set_query(bot_user_id, language_code, 1, description, std::move(promise)); } void BotInfoManager::get_bot_info_description(UserId bot_user_id, const string &language_code, Promise &&promise) { TRY_STATUS_PROMISE(promise, validate_bot_language_code(language_code)); add_pending_get_query(bot_user_id, language_code, 1, std::move(promise)); } void BotInfoManager::set_bot_info_about(UserId bot_user_id, const string &language_code, const string &about, Promise &&promise) { TRY_STATUS_PROMISE(promise, validate_bot_language_code(language_code)); add_pending_set_query(bot_user_id, language_code, 2, about, std::move(promise)); } void BotInfoManager::get_bot_info_about(UserId bot_user_id, const string &language_code, Promise &&promise) { TRY_STATUS_PROMISE(promise, validate_bot_language_code(language_code)); add_pending_get_query(bot_user_id, language_code, 2, std::move(promise)); } void BotInfoManager::memory_stats(vector &output) { output.push_back("\"pending_set_bot_info_queries_\":"); output.push_back(std::to_string(this->pending_set_bot_info_queries_.size())); output.push_back(","); output.push_back("\"pending_get_bot_info_queries_\":"); output.push_back(std::to_string(this->pending_get_bot_info_queries_.size())); output.push_back(","); output.push_back("\"bot_media_preview_file_source_ids_\":"); output.push_back(std::to_string(this->bot_media_preview_file_source_ids_.size())); output.push_back(","); output.push_back("\"bot_media_preview_info_file_source_ids_\":"); output.push_back(std::to_string(this->bot_media_preview_info_file_source_ids_.size())); output.push_back(","); output.push_back("\"being_uploaded_files_\":"); output.push_back(std::to_string(this->being_uploaded_files_.size())); } } // namespace td