From 80b71e4410c149dd1dc476f0364f5fe3c5ed0255 Mon Sep 17 00:00:00 2001 From: levlam Date: Thu, 18 Jul 2024 23:28:08 +0300 Subject: [PATCH] Add td_api::addBotMediaPreview. --- td/generate/scheme/td_api.tl | 10 +- td/telegram/BotInfoManager.cpp | 189 ++++++++++++++++++++++++++++++++- td/telegram/BotInfoManager.h | 41 +++++++ td/telegram/Td.cpp | 6 ++ td/telegram/Td.h | 2 + td/telegram/cli.cpp | 16 +++ 6 files changed, 261 insertions(+), 3 deletions(-) diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index f2a29cc9e..fd9e5e02f 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -10620,10 +10620,16 @@ allowBotToSendMessages bot_user_id:int53 = Ok; sendWebAppCustomRequest bot_user_id:int53 method:string parameters:string = CustomRequestResult; -//@description Returns the list of media previews of a bot -//@bot_user_id Identifier of the target bot +//@description Returns the list of media previews of a bot @bot_user_id Identifier of the target bot getBotMediaPreviews bot_user_id:int53 = BotMediaPreviews; +//@description Adds a new media preview to the beginning of the list of bot's media previews. Returns the added preview. The total number of previews must not exceed getOption("bot_media_preview_count_max") for the given language +//@bot_user_id Identifier of the target bot +//@language_code A two-letter ISO 639-1 language code for which preview is added. If empty, then the preview will be shown to all users for whose languages there are no dedicated previews. +//-If non-empty, then there must be an official language pack with the same name as returned by getLocalizationTargetInfo +//@content Content of the added preview +addBotMediaPreview bot_user_id:int53 language_code:string content:InputStoryContent = StoryContent; + //@description Sets the name of a bot. Can be called only if userTypeBot.can_be_edited == true //@bot_user_id Identifier of the target bot diff --git a/td/telegram/BotInfoManager.cpp b/td/telegram/BotInfoManager.cpp index b7c838f74..cd3292627 100644 --- a/td/telegram/BotInfoManager.cpp +++ b/td/telegram/BotInfoManager.cpp @@ -121,7 +121,7 @@ class GetPreviewMediasQuery final : public Td::ResultHandler { for (auto &media_ptr : ptr) { auto content = get_story_content(td_, std::move(media_ptr->media_), DialogId(bot_user_id_)); if (content == nullptr) { - LOG(ERROR) << "Receive invalid preview media for " << bot_user_id_; + LOG(ERROR) << "Receive invalid media preview for " << bot_user_id_; } else { append(file_ids, get_story_content_file_ids(td_, content.get())); contents.push_back(get_story_content_object(td_, content.get())); @@ -141,6 +141,71 @@ class GetPreviewMediasQuery final : public Td::ResultHandler { } }; +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); + 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 { + 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_; + auto content = get_story_content(td_, std::move(ptr->media_), DialogId(bot_user_id)); + if (content == nullptr) { + LOG(ERROR) << "Receive invalid media preview"; + return pending_preview_->promise_.set_error(Status::Error(500, "Receive invalid preview")); + } + auto file_ids = get_story_content_file_ids(td_, content.get()); + 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); + } + } + pending_preview_->promise_.set_value(get_story_content_object(td_, content.get())); + } + + 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 CanBotSendMessageQuery final : public Td::ResultHandler { Promise promise_; @@ -347,7 +412,31 @@ class GetBotInfoQuery final : public Td::ResultHandler { } }; +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_); } void BotInfoManager::tear_down() { @@ -483,6 +572,104 @@ void BotInfoManager::reload_bot_media_previews(UserId bot_user_id, Promise })); } +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_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::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(td_, 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)); +} + 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)); diff --git a/td/telegram/BotInfoManager.h b/td/telegram/BotInfoManager.h index c0e04943f..7186f706d 100644 --- a/td/telegram/BotInfoManager.h +++ b/td/telegram/BotInfoManager.h @@ -7,6 +7,7 @@ #pragma once #include "td/telegram/DialogParticipant.h" +#include "td/telegram/files/FileId.h" #include "td/telegram/files/FileSourceId.h" #include "td/telegram/UserId.h" @@ -15,14 +16,22 @@ #include "td/utils/common.h" #include "td/utils/FlatHashMap.h" #include "td/utils/Promise.h" +#include "td/utils/Status.h" namespace td { +class StoryContent; + class Td; class BotInfoManager final : public Actor { public: BotInfoManager(Td *td, ActorShared<> parent); + BotInfoManager(const BotInfoManager &) = delete; + BotInfoManager &operator=(const BotInfoManager &) = delete; + BotInfoManager(BotInfoManager &&) = delete; + BotInfoManager &operator=(BotInfoManager &&) = delete; + ~BotInfoManager() final; void set_default_group_administrator_rights(AdministratorRights administrator_rights, Promise &&promise); @@ -38,6 +47,10 @@ class BotInfoManager final : public Actor { void reload_bot_media_previews(UserId bot_user_id, Promise &&promise); + void add_bot_media_preview(UserId bot_user_id, const string &language_code, + td_api::object_ptr &&input_content, + Promise> &&promise); + void set_bot_name(UserId bot_user_id, const string &language_code, const string &name, Promise &&promise); void get_bot_name(UserId bot_user_id, const string &language_code, Promise &&promise); @@ -55,6 +68,19 @@ class BotInfoManager final : public Actor { private: static constexpr double MAX_QUERY_DELAY = 0.01; + class UploadMediaCallback; + + class AddPreviewMediaQuery; + + struct PendingBotMediaPreview { + UserId bot_user_id_; + string language_code_; + unique_ptr content_; + uint32 upload_order_ = 0; + bool was_reuploaded_ = false; + Promise> promise_; + }; + struct PendingSetBotInfoQuery { UserId bot_user_id_; string language_code_; @@ -92,6 +118,15 @@ class BotInfoManager final : public Actor { Result> get_media_preview_bot_input_user( UserId user_id, bool can_be_edited = false); + void do_add_bot_media_preview(unique_ptr &&pending_preview, vector bad_parts); + + void on_add_bot_media_preview_file_parts_missing(unique_ptr &&pending_preview, + vector &&bad_parts); + + void on_upload_bot_media_preview(FileId file_id, telegram_api::object_ptr input_file); + + void on_upload_bot_media_preview_error(FileId file_id, Status status); + void add_pending_set_query(UserId bot_user_id, const string &language_code, int type, const string &value, Promise &&promise); @@ -103,6 +138,12 @@ class BotInfoManager final : public Actor { FlatHashMap bot_media_preview_file_source_ids_; + FlatHashMap, FileIdHash> being_uploaded_files_; + + std::shared_ptr upload_media_callback_; + + uint32 bot_media_preview_upload_order_ = 0; + Td *td_; ActorShared<> parent_; }; diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index e5cd19421..ea0651960 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -7901,6 +7901,12 @@ void Td::on_request(uint64 id, const td_api::getBotMediaPreviews &request) { bot_info_manager_->get_bot_media_previews(UserId(request.bot_user_id_), std::move(promise)); } +void Td::on_request(uint64 id, td_api::addBotMediaPreview &request) { + CREATE_REQUEST_PROMISE(); + bot_info_manager_->add_bot_media_preview(UserId(request.bot_user_id_), request.language_code_, + std::move(request.content_), std::move(promise)); +} + void Td::on_request(uint64 id, td_api::setBotName &request) { CLEAN_INPUT_STRING(request.name_); CREATE_OK_REQUEST_PROMISE(); diff --git a/td/telegram/Td.h b/td/telegram/Td.h index 565ec7843..4963ccfae 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -1459,6 +1459,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::getBotMediaPreviews &request); + void on_request(uint64 id, td_api::addBotMediaPreview &request); + void on_request(uint64 id, td_api::setBotName &request); void on_request(uint64 id, const td_api::getBotName &request); diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 443a4ee0c..93ed815d0 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -6562,6 +6562,22 @@ class CliClient final : public Actor { UserId bot_user_id; get_args(args, bot_user_id); send_request(td_api::make_object(bot_user_id)); + } else if (op == "abmpp") { + UserId bot_user_id; + string language_code; + string photo; + get_args(args, bot_user_id, language_code, photo); + send_request(td_api::make_object( + bot_user_id, language_code, + td_api::make_object(as_input_file(photo), Auto()))); + } else if (op == "abmpv") { + UserId bot_user_id; + string language_code; + string video; + get_args(args, bot_user_id, language_code, video); + send_request(td_api::make_object( + bot_user_id, language_code, + td_api::make_object(as_input_file(video), Auto(), 0.0, 1.5, true))); } else if (op == "gbi") { UserId bot_user_id; string language_code;