From a972252a462b3f9ca901418108a84e3a14310f0d Mon Sep 17 00:00:00 2001 From: levlam Date: Tue, 25 Jan 2022 16:56:10 +0300 Subject: [PATCH] Support creation of WEBM sticker packs. --- td/generate/scheme/td_api.tl | 34 +++++++---------- td/telegram/StickersManager.cpp | 65 ++++++++++++++++----------------- td/telegram/StickersManager.h | 19 ++++------ td/telegram/Td.cpp | 20 +++++----- td/telegram/cli.cpp | 40 +++++++++++--------- 5 files changed, 84 insertions(+), 94 deletions(-) diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index afa962f5c..850740164 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -2191,7 +2191,7 @@ stickers stickers:vector = Stickers; emojis emojis:vector = Emojis; //@description Represents a sticker set -//@id Identifier of the sticker set @title Title of the sticker set @name Name of the sticker set @thumbnail Sticker set thumbnail in WEBP or TGS format with width and height 100; may be null. The file can be downloaded only before the thumbnail is changed +//@id Identifier of the sticker set @title Title of the sticker set @name Name of the sticker set @thumbnail Sticker set thumbnail in WEBP, TGS, or WEBM format with width and height 100; may be null. The file can be downloaded only before the thumbnail is changed //@thumbnail_outline Sticker set thumbnail's outline represented as a list of closed vector paths; may be empty. The coordinate system origin is in the upper-left corner //@is_installed True, if the sticker set has been installed by the current user @is_archived True, if the sticker set has been archived. A sticker set can't be installed and archived simultaneously //@is_official True, if the sticker set is official @sticker_format Format of the stickers in the set @is_viewed True for already viewed trending sticker sets @@ -2199,7 +2199,7 @@ emojis emojis:vector = Emojis; stickerSet id:int64 title:string name:string thumbnail:thumbnail thumbnail_outline:vector is_installed:Bool is_archived:Bool is_official:Bool sticker_format:StickerFormat is_viewed:Bool stickers:vector emojis:vector = StickerSet; //@description Represents short information about a sticker set -//@id Identifier of the sticker set @title Title of the sticker set @name Name of the sticker set @thumbnail Sticker set thumbnail in WEBP or TGS format with width and height 100; may be null +//@id Identifier of the sticker set @title Title of the sticker set @name Name of the sticker set @thumbnail Sticker set thumbnail in WEBP, TGS, or WEBM format with width and height 100; may be null //@thumbnail_outline Sticker set thumbnail's outline represented as a list of closed vector paths; may be empty. The coordinate system origin is in the upper-left corner //@is_installed True, if the sticker set has been installed by the current user @is_archived True, if the sticker set has been archived. A sticker set can't be installed and archived simultaneously //@is_official True, if the sticker set is official @sticker_format Format of the stickers in the set @is_viewed True for already viewed trending sticker sets @@ -2485,8 +2485,8 @@ inputInlineQueryResultLocation id:string location:location live_period:int32 tit //@input_message_content The content of the message to be sent. Must be one of the following types: inputMessageText, inputMessagePhoto, inputMessageInvoice, inputMessageLocation, inputMessageVenue or inputMessageContact inputInlineQueryResultPhoto id:string title:string description:string thumbnail_url:string photo_url:string photo_width:int32 photo_height:int32 reply_markup:ReplyMarkup input_message_content:InputMessageContent = InputInlineQueryResult; -//@description Represents a link to a WEBP or TGS sticker @id Unique identifier of the query result @thumbnail_url URL of the sticker thumbnail, if it exists -//@sticker_url The URL of the WEBP or TGS sticker (sticker file size must not exceed 5MB) @sticker_width Width of the sticker @sticker_height Height of the sticker +//@description Represents a link to a WEBP, TGS, or WEBM sticker @id Unique identifier of the query result @thumbnail_url URL of the sticker thumbnail, if it exists +//@sticker_url The URL of the WEBP, TGS, or WEBM sticker (sticker file size must not exceed 5MB) @sticker_width Width of the sticker @sticker_height Height of the sticker //@reply_markup The message reply markup; pass null if none. Must be of type replyMarkupInlineKeyboard or null //@input_message_content The content of the message to be sent. Must be one of the following types: inputMessageText, inputMessageSticker, inputMessageInvoice, inputMessageLocation, inputMessageVenue or inputMessageContact inputInlineQueryResultSticker id:string thumbnail_url:string sticker_url:string sticker_width:int32 sticker_height:int32 reply_markup:ReplyMarkup input_message_content:InputMessageContent = InputInlineQueryResult; @@ -3579,18 +3579,11 @@ proxy id:int32 server:string port:int32 last_used_date:int32 is_enabled:Bool typ proxies proxies:vector = Proxies; -//@class InputSticker @description Describes a sticker that needs to be added to a sticker set - -//@description A static sticker in PNG format, which will be converted to WEBP server-side -//@sticker PNG image with the sticker; must be up to 512 KB in size and fit in a 512x512 square +//@description A sticker to be added to a sticker set +//@sticker File with the sticker; must fit in a 512x512 square. For WEBP stickers and masks the file must be in PNG format, which will be converted to WEBP server-side. Otherwise, the file must be local or uploaded within a week. See https://core.telegram.org/animated_stickers#technical-requirements for technical requirements //@emojis Emojis corresponding to the sticker -//@mask_position For masks, position where the mask is placed; pass null if unspecified -inputStickerStatic sticker:InputFile emojis:string mask_position:maskPosition = InputSticker; - -//@description An animated sticker in TGS format -//@sticker File with the animated sticker. Only local or uploaded within a week files are supported. See https://core.telegram.org/animated_stickers#technical-requirements for technical requirements -//@emojis Emojis corresponding to the sticker -inputStickerAnimated sticker:InputFile emojis:string = InputSticker; +//@format Sticker format +inputSticker sticker:InputFile emojis:string format:StickerFormat = InputSticker; //@description Represents a date range @start_date Point in time (Unix timestamp) at which the date range begins @end_date Point in time (Unix timestamp) at which the date range ends @@ -5783,7 +5776,7 @@ setBotUpdatesStatus pending_update_count:int32 error_message:string = Ok; //@description Uploads a file with a sticker; returns the uploaded file @user_id Sticker file owner; ignored for regular users @sticker Sticker file to upload -uploadStickerFile user_id:int53 sticker:InputSticker = File; +uploadStickerFile user_id:int53 sticker:inputSticker = File; //@description Returns a suggested name for a new sticker set with a given title @title Sticker set title; 1-64 characters getSuggestedStickerSetName title:string = Text; @@ -5795,18 +5788,17 @@ checkStickerSetName name:string = CheckStickerSetNameResult; //@user_id Sticker set owner; ignored for regular users //@title Sticker set title; 1-64 characters //@name Sticker set name. Can contain only English letters, digits and underscores. Must end with *"_by_"* (** is case insensitive) for bots; 1-64 characters -//@is_masks True, if stickers are masks. Animated stickers can't be masks -//@stickers List of stickers to be added to the set; must be non-empty. All stickers must be of the same type. For animated stickers, uploadStickerFile must be used before the sticker is shown +//@stickers List of stickers to be added to the set; must be non-empty. All stickers must have the same format. For TGS stickers, uploadStickerFile must be used before the sticker is shown //@source Source of the sticker set; may be empty if unknown -createNewStickerSet user_id:int53 title:string name:string is_masks:Bool stickers:vector source:string = StickerSet; +createNewStickerSet user_id:int53 title:string name:string stickers:vector source:string = StickerSet; //@description Adds a new sticker to a set; for bots only. Returns the sticker set //@user_id Sticker set owner @name Sticker set name @sticker Sticker to add to the set -addStickerToSet user_id:int53 name:string sticker:InputSticker = StickerSet; +addStickerToSet user_id:int53 name:string sticker:inputSticker = StickerSet; //@description Sets a sticker set thumbnail; for bots only. Returns the sticker set //@user_id Sticker set owner @name Sticker set name -//@thumbnail Thumbnail to set in PNG or TGS format; pass null to remove the sticker set thumbnail. Animated thumbnail must be set for animated sticker sets and only for them +//@thumbnail Thumbnail to set in PNG, TGS, or WEBM format; pass null to remove the sticker set thumbnail. Thumbnail format must match the format of stickers in the set setStickerSetThumbnail user_id:int53 name:string thumbnail:InputFile = StickerSet; //@description Changes the position of a sticker in the set to which it belongs; for bots only. The sticker set must have been created by the bot diff --git a/td/telegram/StickersManager.cpp b/td/telegram/StickersManager.cpp index d66d24bbc..21ae52b08 100644 --- a/td/telegram/StickersManager.cpp +++ b/td/telegram/StickersManager.cpp @@ -5553,33 +5553,29 @@ void StickersManager::reorder_installed_sticker_sets(bool is_masks, const vector promise.set_value(Unit()); } -string &StickersManager::get_input_sticker_emojis(td_api::InputSticker *sticker) { - CHECK(sticker != nullptr); - auto constructor_id = sticker->get_id(); - if (constructor_id == td_api::inputStickerStatic::ID) { - return static_cast(sticker)->emojis_; - } - CHECK(constructor_id == td_api::inputStickerAnimated::ID); - return static_cast(sticker)->emojis_; -} - Result> StickersManager::prepare_input_sticker( - td_api::InputSticker *sticker) { + td_api::inputSticker *sticker) { if (sticker == nullptr) { return Status::Error(400, "Input sticker must be non-empty"); } - if (!clean_input_string(get_input_sticker_emojis(sticker))) { + if (!clean_input_string(sticker->emojis_)) { return Status::Error(400, "Emojis must be encoded in UTF-8"); } - switch (sticker->get_id()) { - case td_api::inputStickerStatic::ID: - return prepare_input_file(static_cast(sticker)->sticker_, StickerFormat::Webp, - false); - case td_api::inputStickerAnimated::ID: - return prepare_input_file(static_cast(sticker)->sticker_, StickerFormat::Tgs, - false); + if (sticker->format_ == nullptr) { + return Status::Error(400, "Sticker format must be non-empty"); + } + + switch (sticker->format_->get_id()) { + case td_api::stickerFormatWebp::ID: + return prepare_input_file(sticker->sticker_, StickerFormat::Webp, false); + case td_api::stickerFormatTgs::ID: + return prepare_input_file(sticker->sticker_, StickerFormat::Tgs, false); + case td_api::stickerFormatWebm::ID: + return prepare_input_file(sticker->sticker_, StickerFormat::Webm, false); + case td_api::stickerFormatWebpMask::ID: + return prepare_input_file(sticker->sticker_, StickerFormat::Webp, false); default: UNREACHABLE(); return {}; @@ -5588,7 +5584,8 @@ Result> StickersManager::prepare_i Result> StickersManager::prepare_input_file( const tl_object_ptr &input_file, StickerFormat format, bool for_thumbnail) { - auto file_type = format == StickerFormat::Tgs ? FileType::Sticker : FileType::Document; + auto file_type = + format == StickerFormat::Tgs || format == StickerFormat::Tgs ? FileType::Sticker : FileType::Document; auto r_file_id = td_->file_manager_->get_input_file_id(file_type, input_file, DialogId(), for_thumbnail, false); if (r_file_id.is_error()) { return Status::Error(400, r_file_id.error().message()); @@ -5632,7 +5629,7 @@ Result> StickersManager::prepare_i return std::make_tuple(file_id, is_url, is_local, format); } -FileId StickersManager::upload_sticker_file(UserId user_id, tl_object_ptr &&sticker, +FileId StickersManager::upload_sticker_file(UserId user_id, tl_object_ptr &&sticker, Promise &&promise) { bool is_bot = td_->auth_manager_->is_bot(); if (!is_bot) { @@ -5665,7 +5662,7 @@ FileId StickersManager::upload_sticker_file(UserId user_id, tl_object_ptr StickersManager::get_input_sticker(td_api::InputSticker *sticker, +tl_object_ptr StickersManager::get_input_sticker(const td_api::inputSticker *sticker, FileId file_id) const { CHECK(sticker != nullptr); FileView file_view = td_->file_manager_->get_file_view(file_id); @@ -5673,11 +5670,12 @@ tl_object_ptr StickersManager::get_input_stic auto input_document = file_view.main_remote_location().as_input_document(); tl_object_ptr mask_coords; - if (sticker->get_id() == td_api::inputStickerStatic::ID) { - auto mask_position = static_cast(sticker)->mask_position_.get(); + if (sticker->format_->get_id() == td_api::stickerFormatWebpMask::ID) { + auto sticker_format = static_cast(sticker->format_.get()); + auto mask_position = sticker_format->mask_position_.get(); if (mask_position != nullptr && mask_position->point_ != nullptr) { - auto point = [mask_point = std::move(mask_position->point_)] { - switch (mask_point->get_id()) { + auto point = [mask_point_id = mask_position->point_->get_id()] { + switch (mask_point_id) { case td_api::maskPointForehead::ID: return 0; case td_api::maskPointEyes::ID: @@ -5701,8 +5699,8 @@ tl_object_ptr StickersManager::get_input_stic flags |= telegram_api::inputStickerSetItem::MASK_COORDS_MASK; } - return make_tl_object(flags, std::move(input_document), - get_input_sticker_emojis(sticker), std::move(mask_coords)); + return make_tl_object(flags, std::move(input_document), sticker->emojis_, + std::move(mask_coords)); } void StickersManager::get_suggested_sticker_set_name(string title, Promise &&promise) { @@ -5752,8 +5750,8 @@ td_api::object_ptr StickersManager::get_check } } -void StickersManager::create_new_sticker_set(UserId user_id, string &title, string &short_name, bool is_masks, - vector> &&stickers, string software, +void StickersManager::create_new_sticker_set(UserId user_id, string &title, string &short_name, + vector> &&stickers, string software, Promise &&promise) { bool is_bot = td_->auth_manager_->is_bot(); if (!is_bot) { @@ -5794,7 +5792,7 @@ void StickersManager::create_new_sticker_set(UserId user_id, string &title, stri if (is_sticker_format_animated(sticker_format) && is_url) { return promise.set_error(Status::Error(400, "Animated stickers can't be uploaded by URL")); } - sticker_formats.insert(static_cast(sticker_format)); + sticker_formats.insert(sticker->format_->get_id()); file_ids.push_back(file_id); if (is_url) { @@ -5811,7 +5809,6 @@ void StickersManager::create_new_sticker_set(UserId user_id, string &title, stri pending_new_sticker_set->user_id = user_id; pending_new_sticker_set->title = std::move(title); pending_new_sticker_set->short_name = short_name; - pending_new_sticker_set->is_masks = is_masks; pending_new_sticker_set->sticker_format = sticker_format; pending_new_sticker_set->file_ids = std::move(file_ids); pending_new_sticker_set->stickers = std::move(stickers); @@ -5979,7 +5976,7 @@ void StickersManager::on_new_stickers_uploaded(int64 random_id, Result res auto &promise = pending_new_sticker_set->promise; TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(pending_new_sticker_set->user_id)); - bool is_masks = pending_new_sticker_set->is_masks; + bool is_masks = pending_new_sticker_set->stickers[0]->format_->get_id() == td_api::stickerFormatWebpMask::ID; StickerFormat sticker_format = pending_new_sticker_set->sticker_format; auto sticker_count = pending_new_sticker_set->stickers.size(); @@ -5996,7 +5993,7 @@ void StickersManager::on_new_stickers_uploaded(int64 random_id, Result res } void StickersManager::add_sticker_to_set(UserId user_id, string &short_name, - tl_object_ptr &&sticker, Promise &&promise) { + tl_object_ptr &&sticker, Promise &&promise) { TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); short_name = strip_empty_characters(short_name, MAX_STICKER_SET_SHORT_NAME_LENGTH); diff --git a/td/telegram/StickersManager.h b/td/telegram/StickersManager.h index f4a51c6aa..4f65be009 100644 --- a/td/telegram/StickersManager.h +++ b/td/telegram/StickersManager.h @@ -193,7 +193,7 @@ class StickersManager final : public Actor { void reorder_installed_sticker_sets(bool is_masks, const vector &sticker_set_ids, Promise &&promise); - FileId upload_sticker_file(UserId user_id, tl_object_ptr &&sticker, Promise &&promise); + FileId upload_sticker_file(UserId user_id, tl_object_ptr &&sticker, Promise &&promise); void get_suggested_sticker_set_name(string title, Promise &&promise); @@ -203,11 +203,11 @@ class StickersManager final : public Actor { static td_api::object_ptr get_check_sticker_set_name_result_object( CheckStickerSetNameResult result); - void create_new_sticker_set(UserId user_id, string &title, string &short_name, bool is_masks, - vector> &&stickers, string software, + void create_new_sticker_set(UserId user_id, string &title, string &short_name, + vector> &&stickers, string software, Promise &&promise); - void add_sticker_to_set(UserId user_id, string &short_name, tl_object_ptr &&sticker, + void add_sticker_to_set(UserId user_id, string &short_name, tl_object_ptr &&sticker, Promise &&promise); void set_sticker_set_thumbnail(UserId user_id, string &short_name, tl_object_ptr &&thumbnail, @@ -398,9 +398,8 @@ class StickersManager final : public Actor { string title; string short_name; StickerFormat sticker_format = StickerFormat::Unknown; - bool is_masks = false; vector file_ids; - vector> stickers; + vector> stickers; string software; Promise<> promise; }; @@ -408,7 +407,7 @@ class StickersManager final : public Actor { struct PendingAddStickerToSet { string short_name; FileId file_id; - tl_object_ptr sticker; + tl_object_ptr sticker; Promise<> promise; }; @@ -619,14 +618,12 @@ class StickersManager final : public Actor { template void parse_sticker_set(StickerSet *sticker_set, ParserT &parser); - static string &get_input_sticker_emojis(td_api::InputSticker *sticker); - Result> prepare_input_file( const tl_object_ptr &input_file, StickerFormat format, bool for_thumbnail); - Result> prepare_input_sticker(td_api::InputSticker *sticker); + Result> prepare_input_sticker(td_api::inputSticker *sticker); - tl_object_ptr get_input_sticker(td_api::InputSticker *sticker, + tl_object_ptr get_input_sticker(const td_api::inputSticker *sticker, FileId file_id) const; void upload_sticker_file(UserId user_id, FileId file_id, Promise &&promise); diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index e44359170..237536454 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -2240,7 +2240,7 @@ class ChangeStickerSetRequest final : public RequestOnceActor { class UploadStickerFileRequest final : public RequestOnceActor { UserId user_id_; - tl_object_ptr sticker_; + tl_object_ptr sticker_; FileId file_id; @@ -2254,7 +2254,7 @@ class UploadStickerFileRequest final : public RequestOnceActor { public: UploadStickerFileRequest(ActorShared td, uint64 request_id, int64 user_id, - tl_object_ptr &&sticker) + tl_object_ptr &&sticker) : RequestOnceActor(std::move(td), request_id), user_id_(user_id), sticker_(std::move(sticker)) { } }; @@ -2263,13 +2263,12 @@ class CreateNewStickerSetRequest final : public RequestOnceActor { UserId user_id_; string title_; string name_; - bool is_masks_; - vector> stickers_; + vector> stickers_; string software_; void do_run(Promise &&promise) final { - td_->stickers_manager_->create_new_sticker_set(user_id_, title_, name_, is_masks_, std::move(stickers_), - std::move(software_), std::move(promise)); + td_->stickers_manager_->create_new_sticker_set(user_id_, title_, name_, std::move(stickers_), std::move(software_), + std::move(promise)); } void do_send_result() final { @@ -2282,12 +2281,11 @@ class CreateNewStickerSetRequest final : public RequestOnceActor { public: CreateNewStickerSetRequest(ActorShared td, uint64 request_id, int64 user_id, string &&title, string &&name, - bool is_masks, vector> &&stickers, string &&software) + vector> &&stickers, string &&software) : RequestOnceActor(std::move(td), request_id) , user_id_(user_id) , title_(std::move(title)) , name_(std::move(name)) - , is_masks_(is_masks) , stickers_(std::move(stickers)) , software_(std::move(software)) { } @@ -2296,7 +2294,7 @@ class CreateNewStickerSetRequest final : public RequestOnceActor { class AddStickerToSetRequest final : public RequestOnceActor { UserId user_id_; string name_; - tl_object_ptr sticker_; + tl_object_ptr sticker_; void do_run(Promise &&promise) final { td_->stickers_manager_->add_sticker_to_set(user_id_, name_, std::move(sticker_), std::move(promise)); @@ -2312,7 +2310,7 @@ class AddStickerToSetRequest final : public RequestOnceActor { public: AddStickerToSetRequest(ActorShared td, uint64 request_id, int64 user_id, string &&name, - tl_object_ptr &&sticker) + tl_object_ptr &&sticker) : RequestOnceActor(std::move(td), request_id) , user_id_(user_id) , name_(std::move(name)) @@ -6833,7 +6831,7 @@ void Td::on_request(uint64 id, td_api::createNewStickerSet &request) { CLEAN_INPUT_STRING(request.name_); CLEAN_INPUT_STRING(request.source_); CREATE_REQUEST(CreateNewStickerSetRequest, request.user_id_, std::move(request.title_), std::move(request.name_), - request.is_masks_, std::move(request.stickers_), std::move(request.source_)); + std::move(request.stickers_), std::move(request.source_)); } void Td::on_request(uint64 id, td_api::addStickerToSet &request) { diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index c4fd4302b..12c49cf14 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -527,6 +527,21 @@ class CliClient final : public Actor { return to_integer(trim(str)); } + static td_api::object_ptr as_sticker_format(string sticker_format) { + if (!sticker_format.empty() && sticker_format.back() == 't') { + return td_api::make_object(); + } + if (!sticker_format.empty() && sticker_format.back() == 'w') { + return td_api::make_object(); + } + if (!sticker_format.empty() && sticker_format.back() == 'm') { + auto position = td_api::make_object(td_api::make_object(), + Random::fast(-5, 5), Random::fast(-5, 5), 1.0); + return td_api::make_object(Random::fast_bool() ? nullptr : std::move(position)); + } + return td_api::make_object(); + } + static int32 as_limit(Slice str, int32 default_limit = 10) { if (str.empty()) { return default_limit; @@ -2558,29 +2573,20 @@ class CliClient final : public Actor { } else if (op == "cssn") { const string &name = args; send_request(td_api::make_object(name)); - } else if (op == "usf" || op == "usfa") { - td_api::object_ptr input_sticker; - if (op == "usfa") { - input_sticker = td_api::make_object(as_input_file(args), "😀"); - } else { - input_sticker = td_api::make_object(as_input_file(args), "😀", nullptr); - } - send_request(td_api::make_object(-1, std::move(input_sticker))); - } else if (op == "cnss" || op == "cnssa") { + } else if (op == "usf" || op == "usft" || op == "usfw" || op == "usfm") { + send_request(td_api::make_object( + -1, td_api::make_object(as_input_file(args), "😀", as_sticker_format(op)))); + } else if (op == "cnss" || op == "cnsst" || op == "cnssw" || op == "cnssm") { string title; string name; string stickers; get_args(args, title, name, stickers); auto input_stickers = - transform(autosplit(stickers), [op](Slice sticker) -> td_api::object_ptr { - if (op == "cnssa") { - return td_api::make_object(as_input_file(sticker), "😀"); - } else { - return td_api::make_object(as_input_file(sticker), "😀", nullptr); - } + transform(autosplit(stickers), [op](Slice sticker) -> td_api::object_ptr { + return td_api::make_object(as_input_file(sticker), "😀", as_sticker_format(op)); }); - send_request(td_api::make_object(my_id_, title, name, false, - std::move(input_stickers), "tg_cli")); + send_request( + td_api::make_object(my_id_, title, name, std::move(input_stickers), "tg_cli")); } else if (op == "sss") { send_request(td_api::make_object(args)); } else if (op == "siss") {