// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 // // 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/StickersManager.h" #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" #include "td/telegram/ConfigManager.h" #include "td/telegram/ContactsManager.h" #include "td/telegram/DialogId.h" #include "td/telegram/Document.h" #include "td/telegram/DocumentsManager.h" #include "td/telegram/FileReferenceManager.h" #include "td/telegram/files/FileLocation.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/files/FileType.h" #include "td/telegram/Global.h" #include "td/telegram/LanguagePackManager.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/logevent/LogEventHelper.h" #include "td/telegram/MessageReaction.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/misc.h" #include "td/telegram/net/DcId.h" #include "td/telegram/net/MtprotoHeader.h" #include "td/telegram/net/NetQueryDispatcher.h" #include "td/telegram/OptionManager.h" #include "td/telegram/PhotoSizeSource.h" #include "td/telegram/secret_api.h" #include "td/telegram/SecretChatLayer.h" #include "td/telegram/StickerSetId.hpp" #include "td/telegram/StickersManager.hpp" #include "td/telegram/Td.h" #include "td/telegram/td_api.h" #include "td/telegram/TdDb.h" #include "td/telegram/TdParameters.h" #include "td/telegram/telegram_api.h" #include "td/telegram/Version.h" #include "td/db/SqliteKeyValue.h" #include "td/db/SqliteKeyValueAsync.h" #include "td/actor/SleepActor.h" #include "td/utils/algorithm.h" #include "td/utils/base64.h" #include "td/utils/emoji.h" #include "td/utils/format.h" #include "td/utils/JsonBuilder.h" #include "td/utils/logging.h" #include "td/utils/MimeType.h" #include "td/utils/misc.h" #include "td/utils/PathView.h" #include "td/utils/Random.h" #include "td/utils/ScopeGuard.h" #include "td/utils/Slice.h" #include "td/utils/SliceBuilder.h" #include "td/utils/StackAllocator.h" #include "td/utils/StringBuilder.h" #include "td/utils/Time.h" #include "td/utils/tl_helpers.h" #include "td/utils/utf8.h" #include #include #include #include namespace td { class GetAvailableReactionsQuery final : public Td::ResultHandler { public: void send(int32 hash) { send_query(G()->net_query_creator().create(telegram_api::messages_getAvailableReactions(hash))); } 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 GetAvailableReactionsQuery: " << to_string(ptr); td_->stickers_manager_->on_get_available_reactions(std::move(ptr)); } void on_error(Status status) final { LOG(INFO) << "Receive error for GetAvailableReactionsQuery: " << status; td_->stickers_manager_->on_get_available_reactions(nullptr); } }; class GetRecentReactionsQuery final : public Td::ResultHandler { public: void send(int32 limit, int64 hash) { send_query(G()->net_query_creator().create(telegram_api::messages_getRecentReactions(limit, hash))); } 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 GetRecentReactionsQuery: " << to_string(ptr); td_->stickers_manager_->on_get_recent_reactions(std::move(ptr)); } void on_error(Status status) final { LOG(INFO) << "Receive error for GetRecentReactionsQuery: " << status; td_->stickers_manager_->on_get_recent_reactions(nullptr); } }; class GetTopReactionsQuery final : public Td::ResultHandler { public: void send(int64 hash) { send_query(G()->net_query_creator().create(telegram_api::messages_getTopReactions(50, hash))); } 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 GetTopReactionsQuery: " << to_string(ptr); td_->stickers_manager_->on_get_top_reactions(std::move(ptr)); } void on_error(Status status) final { LOG(INFO) << "Receive error for GetTopReactionsQuery: " << status; td_->stickers_manager_->on_get_top_reactions(nullptr); } }; class ClearRecentReactionsQuery final : public Td::ResultHandler { Promise promise_; public: explicit ClearRecentReactionsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send() { send_query(G()->net_query_creator().create(telegram_api::messages_clearRecentReactions())); } 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_->stickers_manager_->reload_recent_reactions(); promise_.set_value(Unit()); } void on_error(Status status) final { if (!G()->is_expected_error(status)) { LOG(ERROR) << "Receive error for clear recent reactions: " << status; } td_->stickers_manager_->reload_recent_reactions(); promise_.set_error(std::move(status)); } }; class GetAllStickersQuery final : public Td::ResultHandler { StickerType sticker_type_; public: void send(StickerType sticker_type, int64 hash) { sticker_type_ = sticker_type; switch (sticker_type) { case StickerType::Regular: return send_query(G()->net_query_creator().create(telegram_api::messages_getAllStickers(hash))); case StickerType::Mask: return send_query(G()->net_query_creator().create(telegram_api::messages_getMaskStickers(hash))); case StickerType::CustomEmoji: return send_query(G()->net_query_creator().create(telegram_api::messages_getEmojiStickers(hash))); default: UNREACHABLE(); } } 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()); } auto ptr = result_ptr.move_as_ok(); LOG(DEBUG) << "Receive result for get all " << sticker_type_ << " stickers: " << to_string(ptr); td_->stickers_manager_->on_get_installed_sticker_sets(sticker_type_, std::move(ptr)); } void on_error(Status status) final { if (!G()->is_expected_error(status)) { LOG(ERROR) << "Receive error for get all stickers: " << status; } td_->stickers_manager_->on_get_installed_sticker_sets_failed(sticker_type_, std::move(status)); } }; class SearchStickersQuery final : public Td::ResultHandler { string emoji_; public: void send(string emoji, int64 hash) { emoji_ = std::move(emoji); send_query(G()->net_query_creator().create(telegram_api::messages_getStickers(emoji_, hash))); } 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 search stickers: " << to_string(ptr); td_->stickers_manager_->on_find_stickers_success(emoji_, std::move(ptr)); } void on_error(Status status) final { if (!G()->is_expected_error(status)) { LOG(ERROR) << "Receive error for search stickers: " << status; } td_->stickers_manager_->on_find_stickers_fail(emoji_, std::move(status)); } }; class GetEmojiKeywordsLanguageQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetEmojiKeywordsLanguageQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(vector &&language_codes) { send_query( G()->net_query_creator().create(telegram_api::messages_getEmojiKeywordsLanguages(std::move(language_codes)))); } 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 = transform(result_ptr.move_as_ok(), [](auto &&emoji_language) { return std::move(emoji_language->lang_code_); }); promise_.set_value(std::move(result)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetEmojiKeywordsQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetEmojiKeywordsQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(const string &language_code) { send_query(G()->net_query_creator().create(telegram_api::messages_getEmojiKeywords(language_code))); } 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()); } promise_.set_value(result_ptr.move_as_ok()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetEmojiKeywordsDifferenceQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetEmojiKeywordsDifferenceQuery( Promise> &&promise) : promise_(std::move(promise)) { } void send(const string &language_code, int32 version) { send_query( G()->net_query_creator().create(telegram_api::messages_getEmojiKeywordsDifference(language_code, version))); } 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()); } promise_.set_value(result_ptr.move_as_ok()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetEmojiUrlQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetEmojiUrlQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(const string &language_code) { send_query(G()->net_query_creator().create(telegram_api::messages_getEmojiURL(language_code))); } 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()); } promise_.set_value(result_ptr.move_as_ok()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetArchivedStickerSetsQuery final : public Td::ResultHandler { Promise promise_; StickerSetId offset_sticker_set_id_; StickerType sticker_type_; public: explicit GetArchivedStickerSetsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(StickerType sticker_type, StickerSetId offset_sticker_set_id, int32 limit) { offset_sticker_set_id_ = offset_sticker_set_id; sticker_type_ = sticker_type; int32 flags = 0; if (sticker_type_ == StickerType::Mask) { flags |= telegram_api::messages_getArchivedStickers::MASKS_MASK; } if (sticker_type_ == StickerType::CustomEmoji) { flags |= telegram_api::messages_getArchivedStickers::EMOJIS_MASK; } send_query(G()->net_query_creator().create(telegram_api::messages_getArchivedStickers( flags, false /*ignored*/, false /*ignored*/, offset_sticker_set_id.get(), limit))); } 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 GetArchivedStickerSetsQuery: " << to_string(ptr); td_->stickers_manager_->on_get_archived_sticker_sets(sticker_type_, offset_sticker_set_id_, std::move(ptr->sets_), ptr->count_); promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetFeaturedStickerSetsQuery final : public Td::ResultHandler { StickerType sticker_type_; public: void send(StickerType sticker_type, int64 hash) { sticker_type_ = sticker_type; switch (sticker_type) { case StickerType::Regular: send_query(G()->net_query_creator().create(telegram_api::messages_getFeaturedStickers(hash))); break; case StickerType::CustomEmoji: send_query(G()->net_query_creator().create(telegram_api::messages_getFeaturedEmojiStickers(hash))); break; default: UNREACHABLE(); } } 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()); } auto ptr = result_ptr.move_as_ok(); LOG(DEBUG) << "Receive result for GetFeaturedStickerSetsQuery: " << to_string(ptr); td_->stickers_manager_->on_get_featured_sticker_sets(sticker_type_, -1, -1, 0, std::move(ptr)); } void on_error(Status status) final { td_->stickers_manager_->on_get_featured_sticker_sets_failed(sticker_type_, -1, -1, 0, std::move(status)); } }; class GetOldFeaturedStickerSetsQuery final : public Td::ResultHandler { int32 offset_; int32 limit_; uint32 generation_; public: void send(StickerType sticker_type, int32 offset, int32 limit, uint32 generation) { CHECK(sticker_type == StickerType::Regular); offset_ = offset; limit_ = limit; generation_ = generation; send_query(G()->net_query_creator().create(telegram_api::messages_getOldFeaturedStickers(offset, limit, 0))); } 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(DEBUG) << "Receive result for GetOldFeaturedStickerSetsQuery: " << to_string(ptr); td_->stickers_manager_->on_get_featured_sticker_sets(StickerType::Regular, offset_, limit_, generation_, std::move(ptr)); } void on_error(Status status) final { td_->stickers_manager_->on_get_featured_sticker_sets_failed(StickerType::Regular, offset_, limit_, generation_, std::move(status)); } }; class GetAttachedStickerSetsQuery final : public Td::ResultHandler { Promise promise_; FileId file_id_; string file_reference_; public: explicit GetAttachedStickerSetsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(FileId file_id, string &&file_reference, tl_object_ptr &&input_stickered_media) { file_id_ = file_id; file_reference_ = std::move(file_reference); send_query( G()->net_query_creator().create(telegram_api::messages_getAttachedStickers(std::move(input_stickered_media)))); } 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_->stickers_manager_->on_get_attached_sticker_sets(file_id_, result_ptr.move_as_ok()); promise_.set_value(Unit()); } void on_error(Status status) final { if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) { VLOG(file_references) << "Receive " << status << " for " << file_id_; td_->file_manager_->delete_file_reference(file_id_, file_reference_); td_->file_reference_manager_->repair_file_reference( file_id_, PromiseCreator::lambda([file_id = file_id_, promise = std::move(promise_)](Result result) mutable { if (result.is_error()) { return promise.set_error(Status::Error(400, "Failed to find the file")); } send_closure(G()->stickers_manager(), &StickersManager::send_get_attached_stickers_query, file_id, std::move(promise)); })); return; } promise_.set_error(std::move(status)); } }; class GetRecentStickersQuery final : public Td::ResultHandler { bool is_repair_ = false; bool is_attached_ = false; public: void send(bool is_repair, bool is_attached, int64 hash) { is_repair_ = is_repair; is_attached_ = is_attached; int32 flags = 0; if (is_attached) { flags |= telegram_api::messages_getRecentStickers::ATTACHED_MASK; } send_query(G()->net_query_creator().create( telegram_api::messages_getRecentStickers(flags, is_attached /*ignored*/, hash))); } 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(DEBUG) << "Receive result for get recent " << (is_attached_ ? "attached " : "") << "stickers: " << to_string(ptr); td_->stickers_manager_->on_get_recent_stickers(is_repair_, is_attached_, std::move(ptr)); } void on_error(Status status) final { if (!G()->is_expected_error(status)) { LOG(ERROR) << "Receive error for get recent " << (is_attached_ ? "attached " : "") << "stickers: " << status; } td_->stickers_manager_->on_get_recent_stickers_failed(is_repair_, is_attached_, std::move(status)); } }; class SaveRecentStickerQuery final : public Td::ResultHandler { Promise promise_; FileId file_id_; string file_reference_; bool unsave_ = false; bool is_attached_; public: explicit SaveRecentStickerQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(bool is_attached, FileId file_id, tl_object_ptr &&input_document, bool unsave) { CHECK(input_document != nullptr); CHECK(file_id.is_valid()); file_id_ = file_id; file_reference_ = input_document->file_reference_.as_slice().str(); unsave_ = unsave; is_attached_ = is_attached; int32 flags = 0; if (is_attached) { flags |= telegram_api::messages_saveRecentSticker::ATTACHED_MASK; } send_query(G()->net_query_creator().create( telegram_api::messages_saveRecentSticker(flags, is_attached /*ignored*/, std::move(input_document), unsave))); } 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(INFO) << "Receive result for save recent " << (is_attached_ ? "attached " : "") << "sticker: " << result; if (!result) { td_->stickers_manager_->reload_recent_stickers(is_attached_, true); } promise_.set_value(Unit()); } void on_error(Status status) final { if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) { VLOG(file_references) << "Receive " << status << " for " << file_id_; td_->file_manager_->delete_file_reference(file_id_, file_reference_); td_->file_reference_manager_->repair_file_reference( file_id_, PromiseCreator::lambda([sticker_id = file_id_, is_attached = is_attached_, unsave = unsave_, promise = std::move(promise_)](Result result) mutable { if (result.is_error()) { return promise.set_error(Status::Error(400, "Failed to find the sticker")); } send_closure(G()->stickers_manager(), &StickersManager::send_save_recent_sticker_query, is_attached, sticker_id, unsave, std::move(promise)); })); return; } if (!G()->is_expected_error(status)) { LOG(ERROR) << "Receive error for save recent " << (is_attached_ ? "attached " : "") << "sticker: " << status; } td_->stickers_manager_->reload_recent_stickers(is_attached_, true); promise_.set_error(std::move(status)); } }; class ClearRecentStickersQuery final : public Td::ResultHandler { Promise promise_; bool is_attached_; public: explicit ClearRecentStickersQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(bool is_attached) { is_attached_ = is_attached; int32 flags = 0; if (is_attached) { flags |= telegram_api::messages_clearRecentStickers::ATTACHED_MASK; } send_query( G()->net_query_creator().create(telegram_api::messages_clearRecentStickers(flags, is_attached /*ignored*/))); } 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(INFO) << "Receive result for clear recent " << (is_attached_ ? "attached " : "") << "stickers: " << result; if (!result) { td_->stickers_manager_->reload_recent_stickers(is_attached_, true); } promise_.set_value(Unit()); } void on_error(Status status) final { if (!G()->is_expected_error(status)) { LOG(ERROR) << "Receive error for clear recent " << (is_attached_ ? "attached " : "") << "stickers: " << status; } td_->stickers_manager_->reload_recent_stickers(is_attached_, true); promise_.set_error(std::move(status)); } }; class GetFavedStickersQuery final : public Td::ResultHandler { bool is_repair_ = false; public: void send(bool is_repair, int64 hash) { is_repair_ = is_repair; send_query(G()->net_query_creator().create(telegram_api::messages_getFavedStickers(hash))); } 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(); td_->stickers_manager_->on_get_favorite_stickers(is_repair_, std::move(ptr)); } void on_error(Status status) final { if (!G()->is_expected_error(status)) { LOG(ERROR) << "Receive error for get favorite stickers: " << status; } td_->stickers_manager_->on_get_favorite_stickers_failed(is_repair_, std::move(status)); } }; class FaveStickerQuery final : public Td::ResultHandler { FileId file_id_; string file_reference_; bool unsave_ = false; Promise promise_; public: explicit FaveStickerQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(FileId file_id, tl_object_ptr &&input_document, bool unsave) { CHECK(input_document != nullptr); CHECK(file_id.is_valid()); file_id_ = file_id; file_reference_ = input_document->file_reference_.as_slice().str(); unsave_ = unsave; send_query(G()->net_query_creator().create(telegram_api::messages_faveSticker(std::move(input_document), unsave))); } 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(INFO) << "Receive result for fave sticker: " << result; if (!result) { td_->stickers_manager_->reload_favorite_stickers(true); } promise_.set_value(Unit()); } void on_error(Status status) final { if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) { VLOG(file_references) << "Receive " << status << " for " << file_id_; td_->file_manager_->delete_file_reference(file_id_, file_reference_); td_->file_reference_manager_->repair_file_reference( file_id_, PromiseCreator::lambda([sticker_id = file_id_, unsave = unsave_, promise = std::move(promise_)](Result result) mutable { if (result.is_error()) { return promise.set_error(Status::Error(400, "Failed to find the sticker")); } send_closure(G()->stickers_manager(), &StickersManager::send_fave_sticker_query, sticker_id, unsave, std::move(promise)); })); return; } if (!G()->is_expected_error(status)) { LOG(ERROR) << "Receive error for fave sticker: " << status; } td_->stickers_manager_->reload_favorite_stickers(true); promise_.set_error(std::move(status)); } }; class ReorderStickerSetsQuery final : public Td::ResultHandler { StickerType sticker_type_; public: void send(StickerType sticker_type, const vector &sticker_set_ids) { sticker_type_ = sticker_type; int32 flags = 0; if (sticker_type == StickerType::Mask) { flags |= telegram_api::messages_reorderStickerSets::MASKS_MASK; } if (sticker_type == StickerType::CustomEmoji) { flags |= telegram_api::messages_reorderStickerSets::EMOJIS_MASK; } send_query(G()->net_query_creator().create(telegram_api::messages_reorderStickerSets( flags, false /*ignored*/, false /*ignored*/, StickersManager::convert_sticker_set_ids(sticker_set_ids)))); } 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(); if (!result) { return on_error(Status::Error(400, "Result is false")); } } void on_error(Status status) final { if (!G()->is_expected_error(status)) { LOG(ERROR) << "Receive error for ReorderStickerSetsQuery: " << status; } td_->stickers_manager_->reload_installed_sticker_sets(sticker_type_, true); } }; class GetStickerSetQuery final : public Td::ResultHandler { Promise promise_; StickerSetId sticker_set_id_; string sticker_set_name_; public: explicit GetStickerSetQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(StickerSetId sticker_set_id, tl_object_ptr &&input_sticker_set, int32 hash) { sticker_set_id_ = sticker_set_id; if (input_sticker_set->get_id() == telegram_api::inputStickerSetShortName::ID) { sticker_set_name_ = static_cast(input_sticker_set.get())->short_name_; } send_query( G()->net_query_creator().create(telegram_api::messages_getStickerSet(std::move(input_sticker_set), hash))); } 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 set_ptr = result_ptr.move_as_ok(); if (set_ptr->get_id() == telegram_api::messages_stickerSet::ID) { auto set = static_cast(set_ptr.get()); constexpr int64 GREAT_MINDS_COLOR_SET_ID = 151353307481243663; if (set->set_->id_ == GREAT_MINDS_COLOR_SET_ID) { string great_minds_name = "TelegramGreatMinds"; if (sticker_set_id_.get() == StickersManager::GREAT_MINDS_SET_ID || trim(to_lower(sticker_set_name_)) == to_lower(great_minds_name)) { set->set_->id_ = StickersManager::GREAT_MINDS_SET_ID; set->set_->short_name_ = std::move(great_minds_name); } } } td_->stickers_manager_->on_get_messages_sticker_set(sticker_set_id_, std::move(set_ptr), true, "GetStickerSetQuery"); promise_.set_value(Unit()); } void on_error(Status status) final { LOG(INFO) << "Receive error for GetStickerSetQuery: " << status; td_->stickers_manager_->on_load_sticker_set_fail(sticker_set_id_, status); promise_.set_error(std::move(status)); } }; class ReloadSpecialStickerSetQuery final : public Td::ResultHandler { StickerSetId sticker_set_id_; SpecialStickerSetType type_; public: void send(StickerSetId sticker_set_id, SpecialStickerSetType type, int32 hash) { sticker_set_id_ = sticker_set_id; type_ = std::move(type); send_query( G()->net_query_creator().create(telegram_api::messages_getStickerSet(type_.get_input_sticker_set(), hash))); } 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 set_ptr = result_ptr.move_as_ok(); if (set_ptr->get_id() == telegram_api::messages_stickerSet::ID) { // sticker_set_id_ must be replaced always, because it could have been changed // we must not pass sticker_set_id_ in order to allow its change sticker_set_id_ = td_->stickers_manager_->on_get_messages_sticker_set(StickerSetId(), std::move(set_ptr), true, "ReloadSpecialStickerSetQuery"); } else { CHECK(set_ptr->get_id() == telegram_api::messages_stickerSetNotModified::ID); // we received telegram_api::messages_stickerSetNotModified, and must pass sticker_set_id_ to handle it // sticker_set_id_ can't be changed by this call td_->stickers_manager_->on_get_messages_sticker_set(sticker_set_id_, std::move(set_ptr), false, "ReloadSpecialStickerSetQuery"); } if (sticker_set_id_.is_valid()) { td_->stickers_manager_->on_get_special_sticker_set(type_, sticker_set_id_); } else { on_error(Status::Error(500, "Failed to add special sticker set")); } } void on_error(Status status) final { LOG(WARNING) << "Receive error for ReloadSpecialStickerSetQuery: " << status; td_->stickers_manager_->on_load_special_sticker_set(type_, std::move(status)); } }; class SearchStickerSetsQuery final : public Td::ResultHandler { string query_; public: void send(string query) { query_ = std::move(query); send_query( G()->net_query_creator().create(telegram_api::messages_searchStickerSets(0, false /*ignored*/, query_, 0))); } 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 search sticker sets: " << to_string(ptr); td_->stickers_manager_->on_find_sticker_sets_success(query_, std::move(ptr)); } void on_error(Status status) final { if (!G()->is_expected_error(status)) { LOG(ERROR) << "Receive error for search sticker sets: " << status; } td_->stickers_manager_->on_find_sticker_sets_fail(query_, std::move(status)); } }; class InstallStickerSetQuery final : public Td::ResultHandler { Promise promise_; StickerSetId set_id_; bool is_archived_; public: explicit InstallStickerSetQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(StickerSetId set_id, tl_object_ptr &&input_set, bool is_archived) { set_id_ = set_id; is_archived_ = is_archived; send_query( G()->net_query_creator().create(telegram_api::messages_installStickerSet(std::move(input_set), is_archived))); } 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_->stickers_manager_->on_install_sticker_set(set_id_, is_archived_, result_ptr.move_as_ok()); promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class UninstallStickerSetQuery final : public Td::ResultHandler { Promise promise_; StickerSetId set_id_; public: explicit UninstallStickerSetQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(StickerSetId set_id, tl_object_ptr &&input_set) { set_id_ = set_id; send_query(G()->net_query_creator().create(telegram_api::messages_uninstallStickerSet(std::move(input_set)))); } 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(); if (!result) { LOG(WARNING) << "Receive false in result to uninstallStickerSet"; } else { td_->stickers_manager_->on_uninstall_sticker_set(set_id_); } promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class ReadFeaturedStickerSetsQuery final : public Td::ResultHandler { public: void send(const vector &sticker_set_ids) { send_query(G()->net_query_creator().create( telegram_api::messages_readFeaturedStickers(StickersManager::convert_sticker_set_ids(sticker_set_ids)))); } 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(); (void)result; } void on_error(Status status) final { if (!G()->is_expected_error(status)) { LOG(ERROR) << "Receive error for ReadFeaturedStickerSetsQuery: " << status; } td_->stickers_manager_->reload_featured_sticker_sets(StickerType::Regular, true); td_->stickers_manager_->reload_featured_sticker_sets(StickerType::CustomEmoji, true); } }; class UploadStickerFileQuery final : public Td::ResultHandler { Promise promise_; FileId file_id_; bool was_uploaded_ = false; public: explicit UploadStickerFileQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(tl_object_ptr &&input_peer, FileId file_id, tl_object_ptr &&input_media) { CHECK(input_peer != nullptr); CHECK(input_media != nullptr); file_id_ = file_id; was_uploaded_ = FileManager::extract_was_uploaded(input_media); send_query(G()->net_query_creator().create( telegram_api::messages_uploadMedia(std::move(input_peer), std::move(input_media)))); } 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_->stickers_manager_->on_uploaded_sticker_file(file_id_, result_ptr.move_as_ok(), std::move(promise_)); } void on_error(Status status) final { if (was_uploaded_) { CHECK(file_id_.is_valid()); if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) { // TODO td_->stickers_manager_->on_upload_sticker_file_part_missing(file_id_, to_integer(status.message().substr(10))); // return; } else { if (status.code() != 429 && status.code() < 500 && !G()->close_flag()) { td_->file_manager_->delete_partial_remote_location(file_id_); } } } else if (FileReferenceManager::is_file_reference_error(status)) { LOG(ERROR) << "Receive file reference error for UploadStickerFileQuery"; } td_->file_manager_->cancel_upload(file_id_); promise_.set_error(std::move(status)); } }; class SuggestStickerSetShortNameQuery final : public Td::ResultHandler { Promise promise_; public: explicit SuggestStickerSetShortNameQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const string &title) { send_query(G()->net_query_creator().create(telegram_api::stickers_suggestShortName(title))); } 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(); promise_.set_value(std::move(ptr->short_name_)); } void on_error(Status status) final { if (status.message() == "TITLE_INVALID") { return promise_.set_value(string()); } promise_.set_error(std::move(status)); } }; class CheckStickerSetShortNameQuery final : public Td::ResultHandler { Promise promise_; public: explicit CheckStickerSetShortNameQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const string &short_name) { send_query(G()->net_query_creator().create(telegram_api::stickers_checkShortName(short_name))); } 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()); } promise_.set_value(result_ptr.move_as_ok()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class CreateNewStickerSetQuery final : public Td::ResultHandler { Promise> promise_; public: explicit CreateNewStickerSetQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(tl_object_ptr &&input_user, const string &title, const string &short_name, StickerType sticker_type, StickerFormat sticker_format, vector> &&input_stickers, const string &software) { CHECK(input_user != nullptr); int32 flags = 0; if (sticker_type == StickerType::Mask) { flags |= telegram_api::stickers_createStickerSet::MASKS_MASK; } if (sticker_type == StickerType::CustomEmoji) { // flags |= telegram_api::stickers_createStickerSet::EMOJIS_MASK; return on_error(Status::Error(400, "Can't create custom emoji sets")); } if (sticker_format == StickerFormat::Tgs) { flags |= telegram_api::stickers_createStickerSet::ANIMATED_MASK; } if (sticker_format == StickerFormat::Webm) { flags |= telegram_api::stickers_createStickerSet::VIDEOS_MASK; } if (!software.empty()) { flags |= telegram_api::stickers_createStickerSet::SOFTWARE_MASK; } send_query(G()->net_query_creator().create( telegram_api::stickers_createStickerSet(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_user), title, short_name, nullptr, std::move(input_stickers), software), {{short_name}})); } 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 sticker_set_id = td_->stickers_manager_->on_get_messages_sticker_set(StickerSetId(), result_ptr.move_as_ok(), true, "CreateNewStickerSetQuery"); if (!sticker_set_id.is_valid()) { return on_error(Status::Error(500, "Created sticker set not found")); } promise_.set_value(td_->stickers_manager_->get_sticker_set_object(sticker_set_id)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class AddStickerToSetQuery final : public Td::ResultHandler { Promise> promise_; public: explicit AddStickerToSetQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(const string &short_name, tl_object_ptr &&input_sticker) { send_query(G()->net_query_creator().create( telegram_api::stickers_addStickerToSet(make_tl_object(short_name), std::move(input_sticker)), {{short_name}})); } 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 sticker_set_id = td_->stickers_manager_->on_get_messages_sticker_set(StickerSetId(), result_ptr.move_as_ok(), true, "AddStickerToSetQuery"); if (!sticker_set_id.is_valid()) { return on_error(Status::Error(500, "Sticker set not found")); } promise_.set_value(td_->stickers_manager_->get_sticker_set_object(sticker_set_id)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class SetStickerSetThumbnailQuery final : public Td::ResultHandler { Promise> promise_; public: explicit SetStickerSetThumbnailQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(const string &short_name, tl_object_ptr &&input_document) { send_query(G()->net_query_creator().create( telegram_api::stickers_setStickerSetThumb(make_tl_object(short_name), std::move(input_document)), {{short_name}})); } 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 sticker_set_id = td_->stickers_manager_->on_get_messages_sticker_set(StickerSetId(), result_ptr.move_as_ok(), true, "SetStickerSetThumbnailQuery"); if (!sticker_set_id.is_valid()) { return on_error(Status::Error(500, "Sticker set not found")); } promise_.set_value(td_->stickers_manager_->get_sticker_set_object(sticker_set_id)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class SetStickerPositionQuery final : public Td::ResultHandler { Promise promise_; public: explicit SetStickerPositionQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const string &short_name, tl_object_ptr &&input_document, int32 position) { vector chain_ids; if (!short_name.empty()) { chain_ids.emplace_back(short_name); } send_query(G()->net_query_creator().create( telegram_api::stickers_changeStickerPosition(std::move(input_document), position), std::move(chain_ids))); } 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_->stickers_manager_->on_get_messages_sticker_set(StickerSetId(), result_ptr.move_as_ok(), true, "SetStickerPositionQuery"); promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class DeleteStickerFromSetQuery final : public Td::ResultHandler { Promise promise_; public: explicit DeleteStickerFromSetQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const string &short_name, tl_object_ptr &&input_document) { vector chain_ids; if (!short_name.empty()) { chain_ids.emplace_back(short_name); } send_query(G()->net_query_creator().create(telegram_api::stickers_removeStickerFromSet(std::move(input_document)), std::move(chain_ids))); } 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_->stickers_manager_->on_get_messages_sticker_set(StickerSetId(), result_ptr.move_as_ok(), true, "DeleteStickerFromSetQuery"); promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetCustomEmojiDocumentsQuery final : public Td::ResultHandler { Promise>> promise_; public: explicit GetCustomEmojiDocumentsQuery(Promise>> &&promise) : promise_(std::move(promise)) { } void send(vector &&custom_emoji_ids) { auto document_ids = transform(custom_emoji_ids, [](CustomEmojiId custom_emoji_id) { return custom_emoji_id.get(); }); send_query( G()->net_query_creator().create(telegram_api::messages_getCustomEmojiDocuments(std::move(document_ids)))); } 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()); } promise_.set_value(result_ptr.move_as_ok()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class SendAnimatedEmojiClicksQuery final : public Td::ResultHandler { DialogId dialog_id_; string emoji_; public: void send(DialogId dialog_id, tl_object_ptr &&input_peer, tl_object_ptr &&action) { dialog_id_ = dialog_id; CHECK(input_peer != nullptr); CHECK(action != nullptr); emoji_ = action->emoticon_; int32 flags = 0; send_query(G()->net_query_creator().create( telegram_api::messages_setTyping(flags, std::move(input_peer), 0, std::move(action)))); } 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()); } // ignore result } void on_error(Status status) final { if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendAnimatedEmojiClicksQuery")) { LOG(INFO) << "Receive error for send animated emoji clicks: " << status; } td_->stickers_manager_->on_send_animated_emoji_clicks(dialog_id_, emoji_); } }; template void StickersManager::FoundStickers::store(StorerT &storer) const { StickersManager *stickers_manager = storer.context()->td().get_actor_unsafe()->stickers_manager_.get(); td::store(narrow_cast(sticker_ids_.size()), storer); for (auto sticker_id : sticker_ids_) { stickers_manager->store_sticker(sticker_id, false, storer, "FoundStickers"); } td::store(cache_time_, storer); store_time(next_reload_time_, storer); } template void StickersManager::FoundStickers::parse(ParserT &parser) { StickersManager *stickers_manager = parser.context()->td().get_actor_unsafe()->stickers_manager_.get(); int32 size = parser.fetch_int(); sticker_ids_.resize(size); for (auto &sticker_id : sticker_ids_) { sticker_id = stickers_manager->parse_sticker(false, parser); } td::parse(cache_time_, parser); parse_time(next_reload_time_, parser); } class StickersManager::StickerListLogEvent { public: vector sticker_ids; StickerListLogEvent() = default; explicit StickerListLogEvent(vector sticker_ids) : sticker_ids(std::move(sticker_ids)) { } template void store(StorerT &storer) const { StickersManager *stickers_manager = storer.context()->td().get_actor_unsafe()->stickers_manager_.get(); td::store(narrow_cast(sticker_ids.size()), storer); for (auto sticker_id : sticker_ids) { stickers_manager->store_sticker(sticker_id, false, storer, "StickerListLogEvent"); } } template void parse(ParserT &parser) { StickersManager *stickers_manager = parser.context()->td().get_actor_unsafe()->stickers_manager_.get(); int32 size = parser.fetch_int(); sticker_ids.resize(size); for (auto &sticker_id : sticker_ids) { sticker_id = stickers_manager->parse_sticker(false, parser); } } }; class StickersManager::StickerSetListLogEvent { public: vector sticker_set_ids_; bool is_premium_ = false; StickerSetListLogEvent() = default; StickerSetListLogEvent(vector sticker_set_ids, bool is_premium) : sticker_set_ids_(std::move(sticker_set_ids)), is_premium_(is_premium) { } template void store(StorerT &storer) const { BEGIN_STORE_FLAGS(); STORE_FLAG(is_premium_); END_STORE_FLAGS(); td::store(sticker_set_ids_, storer); } template void parse(ParserT &parser) { if (parser.version() >= static_cast(Version::AddStickerSetListFlags)) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_premium_); END_PARSE_FLAGS(); } td::parse(sticker_set_ids_, parser); } }; class StickersManager::UploadStickerFileCallback final : public FileManager::UploadCallback { public: void on_upload_ok(FileId file_id, tl_object_ptr input_file) final { send_closure_later(G()->stickers_manager(), &StickersManager::on_upload_sticker_file, file_id, std::move(input_file)); } void on_upload_encrypted_ok(FileId file_id, tl_object_ptr input_file) final { UNREACHABLE(); } void on_upload_secure_ok(FileId file_id, tl_object_ptr input_file) final { UNREACHABLE(); } void on_upload_error(FileId file_id, Status error) final { send_closure_later(G()->stickers_manager(), &StickersManager::on_upload_sticker_file_error, file_id, std::move(error)); } }; StickersManager::StickersManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { upload_sticker_file_callback_ = std::make_shared(); on_update_animated_emoji_zoom(); on_update_recent_stickers_limit(); on_update_favorite_stickers_limit(); next_click_animated_emoji_message_time_ = Time::now(); next_update_animated_emoji_clicked_time_ = Time::now(); } StickersManager::~StickersManager() { Scheduler::instance()->destroy_on_scheduler( G()->get_gc_scheduler_id(), stickers_, sticker_sets_, short_name_to_sticker_set_id_, attached_sticker_sets_, found_stickers_, found_sticker_sets_, emoji_language_codes_, emoji_language_code_versions_, emoji_language_code_last_difference_times_, reloaded_emoji_keywords_, premium_gift_messages_, dice_messages_, emoji_messages_, custom_emoji_messages_, custom_emoji_to_sticker_id_); } void StickersManager::start_up() { init(); } void StickersManager::init() { if (is_inited_ || !td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot() || G()->close_flag()) { return; } LOG(INFO) << "Init StickersManager"; is_inited_ = true; { auto &sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_emoji()); if (G()->is_test_dc()) { init_special_sticker_set(sticker_set, 1258816259751954, 4879754868529595811, "emojies"); } else { init_special_sticker_set(sticker_set, 1258816259751983, 5100237018658464041, "AnimatedEmojies"); } load_special_sticker_set_info_from_binlog(sticker_set); } if (!G()->is_test_dc()) { auto &sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_emoji_click()); load_special_sticker_set_info_from_binlog(sticker_set); } { auto &sticker_set = add_special_sticker_set(SpecialStickerSetType::premium_gifts()); load_special_sticker_set_info_from_binlog(sticker_set); } { auto &sticker_set = add_special_sticker_set(SpecialStickerSetType::generic_animations()); load_special_sticker_set_info_from_binlog(sticker_set); } { auto &sticker_set = add_special_sticker_set(SpecialStickerSetType::default_statuses()); load_special_sticker_set_info_from_binlog(sticker_set); } { auto &sticker_set = add_special_sticker_set(SpecialStickerSetType::default_topic_icons()); load_special_sticker_set_info_from_binlog(sticker_set); } dice_emojis_str_ = td_->option_manager_->get_option_string("dice_emojis", "🎲\x01🎯\x01🏀\x01⚽\x01⚽️\x01🎰\x01🎳"); dice_emojis_ = full_split(dice_emojis_str_, '\x01'); for (auto &dice_emoji : dice_emojis_) { auto &animated_dice_sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_dice(dice_emoji)); load_special_sticker_set_info_from_binlog(animated_dice_sticker_set); } send_closure(G()->td(), &Td::send_update, get_update_dice_emojis_object()); load_active_reactions(); on_update_dice_success_values(); on_update_dice_emojis(); on_update_emoji_sounds(); on_update_disable_animated_emojis(); if (!disable_animated_emojis_) { load_special_sticker_set(add_special_sticker_set(SpecialStickerSetType::animated_emoji())); } load_special_sticker_set(add_special_sticker_set(SpecialStickerSetType::premium_gifts())); if (G()->parameters().use_file_db) { auto old_featured_sticker_set_count_str = G()->td_db()->get_binlog_pmc()->get("old_featured_sticker_set_count"); if (!old_featured_sticker_set_count_str.empty()) { old_featured_sticker_set_count_[static_cast(StickerType::Regular)] = to_integer(old_featured_sticker_set_count_str); } if (!G()->td_db()->get_binlog_pmc()->get("invalidate_old_featured_sticker_sets").empty()) { invalidate_old_featured_sticker_sets(StickerType::Regular); } } else { G()->td_db()->get_binlog_pmc()->erase("old_featured_sticker_set_count"); G()->td_db()->get_binlog_pmc()->erase("invalidate_old_featured_sticker_sets"); } G()->td_db()->get_binlog_pmc()->erase("animated_dice_sticker_set"); // legacy td_->option_manager_->set_option_empty("animated_dice_sticker_set_name"); // legacy td_->option_manager_->set_option_empty("animated_emoji_sticker_set_name"); // legacy } td_api::object_ptr StickersManager::get_emoji_reaction_object(const string &emoji) const { for (auto &reaction : reactions_.reactions_) { if (reaction.reaction_ == emoji) { return td_api::make_object( reaction.reaction_, reaction.title_, reaction.is_active_, get_sticker_object(reaction.static_icon_), get_sticker_object(reaction.appear_animation_), get_sticker_object(reaction.select_animation_), get_sticker_object(reaction.activate_animation_), get_sticker_object(reaction.effect_animation_), get_sticker_object(reaction.around_animation_), get_sticker_object(reaction.center_animation_)); } } return nullptr; } void StickersManager::get_emoji_reaction(const string &emoji, Promise> &&promise) { load_reactions(); if (reactions_.reactions_.empty() && reactions_.are_being_reloaded_) { pending_get_emoji_reaction_queries_.emplace_back(emoji, std::move(promise)); return; } promise.set_value(get_emoji_reaction_object(emoji)); } vector StickersManager::get_recent_reactions() { load_recent_reactions(); return recent_reactions_.reactions_; } vector StickersManager::get_top_reactions() { load_top_reactions(); return top_reactions_.reactions_; } void StickersManager::add_recent_reaction(const string &reaction) { load_recent_reactions(); auto &reactions = recent_reactions_.reactions_; if (!reactions.empty() && reactions[0] == reaction) { return; } auto it = std::find(reactions.begin(), reactions.end(), reaction); if (it == reactions.end()) { if (static_cast(reactions.size()) == MAX_RECENT_REACTIONS) { reactions.back() = reaction; } else { reactions.push_back(reaction); } it = reactions.end() - 1; } std::rotate(reactions.begin(), it, it + 1); recent_reactions_.hash_ = get_reactions_hash(reactions); } void StickersManager::clear_recent_reactions(Promise &&promise) { load_recent_reactions(); if (recent_reactions_.reactions_.empty()) { return promise.set_value(Unit()); } recent_reactions_.hash_ = 0; recent_reactions_.reactions_.clear(); td_->create_handler(std::move(promise))->send(); } void StickersManager::reload_reactions() { if (G()->close_flag() || reactions_.are_being_reloaded_) { return; } CHECK(!td_->auth_manager_->is_bot()); reactions_.are_being_reloaded_ = true; load_reactions(); // must be after are_being_reloaded_ is set to true to avoid recursion td_->create_handler()->send(reactions_.hash_); } void StickersManager::reload_recent_reactions() { if (G()->close_flag() || recent_reactions_.is_being_reloaded_) { return; } CHECK(!td_->auth_manager_->is_bot()); recent_reactions_.is_being_reloaded_ = true; load_recent_reactions(); // must be after is_being_reloaded_ is set to true to avoid recursion td_->create_handler()->send(MAX_RECENT_REACTIONS, recent_reactions_.hash_); } void StickersManager::reload_top_reactions() { if (G()->close_flag() || top_reactions_.is_being_reloaded_) { return; } CHECK(!td_->auth_manager_->is_bot()); top_reactions_.is_being_reloaded_ = true; load_top_reactions(); // must be after is_being_reloaded_ is set to true to avoid recursion td_->create_handler()->send(top_reactions_.hash_); } StickersManager::SpecialStickerSet &StickersManager::add_special_sticker_set(const SpecialStickerSetType &type) { CHECK(!type.is_empty()); auto &result_ptr = special_sticker_sets_[type]; if (result_ptr == nullptr) { result_ptr = make_unique(); } auto &result = *result_ptr; if (result.type_.is_empty()) { result.type_ = type; } else { CHECK(result.type_ == type); } return result; } void StickersManager::init_special_sticker_set(SpecialStickerSet &sticker_set, int64 sticker_set_id, int64 access_hash, string name) { sticker_set.id_ = StickerSetId(sticker_set_id); sticker_set.access_hash_ = access_hash; sticker_set.short_name_ = std::move(name); } void StickersManager::load_special_sticker_set_info_from_binlog(SpecialStickerSet &sticker_set) { if (G()->parameters().use_file_db) { string sticker_set_string = G()->td_db()->get_binlog_pmc()->get(sticker_set.type_.type_); if (!sticker_set_string.empty()) { auto parts = full_split(sticker_set_string); if (parts.size() != 3) { LOG(ERROR) << "Can't parse " << sticker_set_string; } else { auto r_sticker_set_id = to_integer_safe(parts[0]); auto r_sticker_set_access_hash = to_integer_safe(parts[1]); auto sticker_set_name = parts[2]; if (r_sticker_set_id.is_error() || r_sticker_set_access_hash.is_error() || clean_username(sticker_set_name) != sticker_set_name || sticker_set_name.empty()) { LOG(ERROR) << "Can't parse " << sticker_set_string; } else { init_special_sticker_set(sticker_set, r_sticker_set_id.ok(), r_sticker_set_access_hash.ok(), std::move(sticker_set_name)); } } } } else { G()->td_db()->get_binlog_pmc()->erase(sticker_set.type_.type_); } if (!sticker_set.id_.is_valid()) { return; } add_sticker_set(sticker_set.id_, sticker_set.access_hash_); auto cleaned_username = clean_username(sticker_set.short_name_); if (!cleaned_username.empty()) { short_name_to_sticker_set_id_.set(cleaned_username, sticker_set.id_); } } void StickersManager::load_special_sticker_set_by_type(SpecialStickerSetType type) { if (G()->close_flag()) { return; } auto &sticker_set = add_special_sticker_set(type); if (!sticker_set.is_being_loaded_) { return; } sticker_set.is_being_loaded_ = false; load_special_sticker_set(sticker_set); } void StickersManager::load_special_sticker_set(SpecialStickerSet &sticker_set) { CHECK(!td_->auth_manager_->is_bot() || sticker_set.type_ == SpecialStickerSetType::default_topic_icons()); if (sticker_set.is_being_loaded_) { return; } sticker_set.is_being_loaded_ = true; LOG(INFO) << "Load " << sticker_set.type_.type_ << " " << sticker_set.id_; if (sticker_set.id_.is_valid()) { auto s = get_sticker_set(sticker_set.id_); CHECK(s != nullptr); if (s->was_loaded_) { reload_special_sticker_set(sticker_set, s->is_loaded_ ? s->hash_ : 0); return; } auto promise = PromiseCreator::lambda([actor_id = actor_id(this), type = sticker_set.type_](Result &&result) { send_closure(actor_id, &StickersManager::on_load_special_sticker_set, type, result.is_ok() ? Status::OK() : result.move_as_error()); }); load_sticker_sets({sticker_set.id_}, std::move(promise)); } else { reload_special_sticker_set(sticker_set, 0); } } void StickersManager::reload_special_sticker_set_by_type(SpecialStickerSetType type, bool is_recursive) { if (G()->close_flag()) { return; } if (disable_animated_emojis_ && (type == SpecialStickerSetType::animated_emoji() || type == SpecialStickerSetType::animated_emoji_click())) { return; } auto &sticker_set = add_special_sticker_set(type); if (sticker_set.is_being_reloaded_) { return; } if (!sticker_set.id_.is_valid()) { return reload_special_sticker_set(sticker_set, 0); } const auto *s = get_sticker_set(sticker_set.id_); if (s != nullptr && s->is_inited_ && s->was_loaded_) { return reload_special_sticker_set(sticker_set, s->is_loaded_ ? s->hash_ : 0); } if (!is_recursive) { auto promise = PromiseCreator::lambda([actor_id = actor_id(this), type = std::move(type)](Unit result) mutable { send_closure(actor_id, &StickersManager::reload_special_sticker_set_by_type, std::move(type), true); }); return load_sticker_sets({sticker_set.id_}, std::move(promise)); } reload_special_sticker_set(sticker_set, 0); } void StickersManager::reload_special_sticker_set(SpecialStickerSet &sticker_set, int32 hash) { if (sticker_set.is_being_reloaded_) { return; } sticker_set.is_being_reloaded_ = true; td_->create_handler()->send(sticker_set.id_, sticker_set.type_, hash); } void StickersManager::on_load_special_sticker_set(const SpecialStickerSetType &type, Status result) { if (G()->close_flag()) { return; } auto &special_sticker_set = add_special_sticker_set(type); special_sticker_set.is_being_reloaded_ = false; if (!special_sticker_set.is_being_loaded_) { return; } if (result.is_error()) { LOG(INFO) << "Failed to load special sticker set " << type.type_ << ": " << result.error(); // failed to load the special sticker set; repeat after some time create_actor("RetryLoadSpecialStickerSetActor", Random::fast(300, 600), PromiseCreator::lambda([actor_id = actor_id(this), type](Result result) mutable { send_closure(actor_id, &StickersManager::load_special_sticker_set_by_type, std::move(type)); })) .release(); return; } special_sticker_set.is_being_loaded_ = false; if (type == SpecialStickerSetType::animated_emoji()) { set_promises(pending_get_animated_emoji_queries_); try_update_animated_emoji_messages(); return; } if (type == SpecialStickerSetType::premium_gifts()) { set_promises(pending_get_premium_gift_option_sticker_queries_); try_update_premium_gift_messages(); return; } if (type == SpecialStickerSetType::generic_animations()) { set_promises(pending_get_generic_animations_queries_); return; } if (type == SpecialStickerSetType::default_statuses()) { set_promises(pending_get_default_statuses_queries_); return; } if (type == SpecialStickerSetType::default_topic_icons()) { set_promises(pending_get_default_topic_icons_queries_); return; } CHECK(special_sticker_set.id_.is_valid()); auto sticker_set = get_sticker_set(special_sticker_set.id_); CHECK(sticker_set != nullptr); CHECK(sticker_set->was_loaded_); if (type == SpecialStickerSetType::animated_emoji_click()) { auto pending_get_requests = std::move(pending_get_animated_emoji_click_stickers_); reset_to_empty(pending_get_animated_emoji_click_stickers_); for (auto &pending_request : pending_get_requests) { choose_animated_emoji_click_sticker(sticker_set, std::move(pending_request.message_text_), pending_request.full_message_id_, pending_request.start_time_, std::move(pending_request.promise_)); } auto pending_click_requests = std::move(pending_on_animated_emoji_message_clicked_); reset_to_empty(pending_on_animated_emoji_message_clicked_); for (auto &pending_request : pending_click_requests) { schedule_update_animated_emoji_clicked(sticker_set, pending_request.emoji_, pending_request.full_message_id_, std::move(pending_request.clicks_)); } return; } auto emoji = type.get_dice_emoji(); CHECK(!emoji.empty()); auto it = dice_messages_.find(emoji); if (it == dice_messages_.end()) { return; } vector full_message_ids; it->second.foreach([&](const FullMessageId &full_message_id) { full_message_ids.push_back(full_message_id); }); CHECK(!full_message_ids.empty()); for (const auto &full_message_id : full_message_ids) { td_->messages_manager_->on_external_update_message_content(full_message_id); } } void StickersManager::tear_down() { parent_.reset(); } tl_object_ptr StickersManager::get_mask_point_object(int32 point) { switch (point) { case 0: return td_api::make_object(); case 1: return td_api::make_object(); case 2: return td_api::make_object(); case 3: return td_api::make_object(); default: UNREACHABLE(); return nullptr; } } StickerType StickersManager::get_sticker_type(FileId file_id) const { const auto *sticker = get_sticker(file_id); CHECK(sticker != nullptr); return sticker->type_; } bool StickersManager::is_premium_custom_emoji(CustomEmojiId custom_emoji_id, bool default_result) const { auto sticker_id = custom_emoji_to_sticker_id_.get(custom_emoji_id); if (!sticker_id.is_valid()) { return default_result; } const Sticker *s = get_sticker(sticker_id); CHECK(s != nullptr); return s->is_premium_; } CustomEmojiId StickersManager::get_custom_emoji_id(FileId sticker_id) const { auto sticker_file_view = td_->file_manager_->get_file_view(sticker_id); if (sticker_file_view.is_encrypted() || !sticker_file_view.has_remote_location() || !sticker_file_view.remote_location().is_document()) { return CustomEmojiId(); } return CustomEmojiId(sticker_file_view.remote_location().get_id()); } vector> StickersManager::get_sticker_minithumbnail( CSlice path, StickerSetId sticker_set_id, int64 document_id, double zoom) { if (path.empty()) { return {}; } auto buf = StackAllocator::alloc(1 << 9); StringBuilder sb(buf.as_slice(), true); sb << 'M'; for (unsigned char c : path) { if (c >= 128 + 64) { sb << "AACAAAAHAAALMAAAQASTAVAAAZaacaaaahaaalmaaaqastava.az0123456789-,"[c - 128 - 64]; } else { if (c >= 128) { sb << ','; } else if (c >= 64) { sb << '-'; } sb << (c & 63); } } sb << 'z'; CHECK(!sb.is_error()); path = sb.as_cslice(); LOG(DEBUG) << "Transform SVG path " << path; size_t pos = 0; auto skip_commas = [&path, &pos] { while (path[pos] == ',') { pos++; } }; auto get_number = [&] { skip_commas(); int sign = 1; if (path[pos] == '-') { sign = -1; pos++; } double res = 0; while (is_digit(path[pos])) { res = res * 10 + path[pos++] - '0'; } if (path[pos] == '.') { pos++; double mul = 0.1; while (is_digit(path[pos])) { res += (path[pos] - '0') * mul; mul *= 0.1; pos++; } } return sign * res; }; auto make_point = [zoom](double x, double y) { return td_api::make_object(x * zoom, y * zoom); }; vector> result; double x = 0; double y = 0; while (path[pos] != '\0') { skip_commas(); if (path[pos] == '\0') { break; } while (path[pos] == 'm' || path[pos] == 'M') { auto command = path[pos++]; do { if (command == 'm') { x += get_number(); y += get_number(); } else { x = get_number(); y = get_number(); } skip_commas(); } while (path[pos] != '\0' && !is_alpha(path[pos])); } double start_x = x; double start_y = y; vector> commands; bool have_last_end_control_point = false; double last_end_control_point_x = 0; double last_end_control_point_y = 0; bool is_closed = false; char command = '-'; while (!is_closed) { skip_commas(); if (path[pos] == '\0') { LOG(ERROR) << "Receive unclosed path " << path << " in a sticker " << document_id << " from " << sticker_set_id; return {}; } if (is_alpha(path[pos])) { command = path[pos++]; } switch (command) { case 'l': case 'L': case 'h': case 'H': case 'v': case 'V': if (command == 'l' || command == 'h') { x += get_number(); } else if (command == 'L' || command == 'H') { x = get_number(); } if (command == 'l' || command == 'v') { y += get_number(); } else if (command == 'L' || command == 'V') { y = get_number(); } commands.push_back(td_api::make_object(make_point(x, y))); have_last_end_control_point = false; break; case 'C': case 'c': case 'S': case 's': { double start_control_point_x; double start_control_point_y; if (command == 'S' || command == 's') { if (have_last_end_control_point) { start_control_point_x = 2 * x - last_end_control_point_x; start_control_point_y = 2 * y - last_end_control_point_y; } else { start_control_point_x = x; start_control_point_y = y; } } else { start_control_point_x = get_number(); start_control_point_y = get_number(); if (command == 'c') { start_control_point_x += x; start_control_point_y += y; } } last_end_control_point_x = get_number(); last_end_control_point_y = get_number(); if (command == 'c' || command == 's') { last_end_control_point_x += x; last_end_control_point_y += y; } have_last_end_control_point = true; if (command == 'c' || command == 's') { x += get_number(); y += get_number(); } else { x = get_number(); y = get_number(); } commands.push_back(td_api::make_object( make_point(start_control_point_x, start_control_point_y), make_point(last_end_control_point_x, last_end_control_point_y), make_point(x, y))); break; } case 'm': case 'M': pos--; // fallthrough case 'z': case 'Z': if (x != start_x || y != start_y) { x = start_x; y = start_y; commands.push_back(td_api::make_object(make_point(x, y))); } if (!commands.empty()) { result.push_back(td_api::make_object(std::move(commands))); commands.clear(); } is_closed = true; break; default: LOG(ERROR) << "Receive invalid command " << command << " at pos " << pos << " in a sticker " << document_id << " from " << sticker_set_id << ": " << path; return {}; } } } /* string svg; for (const auto &vector_path : result) { CHECK(!vector_path->commands_.empty()); svg += 'M'; auto add_point = [&](const td_api::object_ptr &p) { svg += to_string(static_cast(p->x_)); svg += ','; svg += to_string(static_cast(p->y_)); svg += ','; }; auto last_command = vector_path->commands_.back().get(); switch (last_command->get_id()) { case td_api::vectorPathCommandLine::ID: add_point(static_cast(last_command)->end_point_); break; case td_api::vectorPathCommandCubicBezierCurve::ID: add_point(static_cast(last_command)->end_point_); break; default: UNREACHABLE(); } for (auto &command : vector_path->commands_) { switch (command->get_id()) { case td_api::vectorPathCommandLine::ID: { auto line = static_cast(command.get()); svg += 'L'; add_point(line->end_point_); break; } case td_api::vectorPathCommandCubicBezierCurve::ID: { auto curve = static_cast(command.get()); svg += 'C'; add_point(curve->start_control_point_); add_point(curve->end_control_point_); add_point(curve->end_point_); break; } default: UNREACHABLE(); } } svg += 'z'; } */ return result; } tl_object_ptr StickersManager::get_sticker_object(FileId file_id, bool for_animated_emoji, bool for_clicked_animated_emoji) const { if (!file_id.is_valid()) { return nullptr; } const auto *sticker = get_sticker(file_id); CHECK(sticker != nullptr); const PhotoSize &thumbnail = sticker->m_thumbnail_.file_id.is_valid() ? sticker->m_thumbnail_ : sticker->s_thumbnail_; auto thumbnail_format = PhotoFormat::Webp; int64 document_id = 0; if (!sticker->set_id_.is_valid()) { auto sticker_file_view = td_->file_manager_->get_file_view(sticker->file_id_); if (sticker_file_view.is_encrypted()) { // uploaded to secret chats stickers have JPEG thumbnail instead of server-generated WEBP thumbnail_format = PhotoFormat::Jpeg; } else { if (sticker_file_view.has_remote_location() && sticker_file_view.remote_location().is_document()) { document_id = sticker_file_view.remote_location().get_id(); } if (thumbnail.file_id.is_valid()) { auto thumbnail_file_view = td_->file_manager_->get_file_view(thumbnail.file_id); if (ends_with(thumbnail_file_view.suggested_path(), ".jpg")) { thumbnail_format = PhotoFormat::Jpeg; } } } } auto thumbnail_object = get_thumbnail_object(td_->file_manager_.get(), thumbnail, thumbnail_format); int32 width = sticker->dimensions_.width; int32 height = sticker->dimensions_.height; double zoom = 1.0; if ((is_sticker_format_vector(sticker->format_) || sticker->type_ == StickerType::CustomEmoji) && (for_animated_emoji || for_clicked_animated_emoji)) { if (sticker->type_ == StickerType::CustomEmoji && max(width, height) <= 100) { zoom *= 5.12; } width = static_cast(width * zoom + 0.5); height = static_cast(height * zoom + 0.5); if (for_clicked_animated_emoji) { zoom *= 3; width *= 3; height *= 3; } } auto full_type = [&]() -> td_api::object_ptr { switch (sticker->type_) { case StickerType::Regular: { auto premium_animation_object = sticker->premium_animation_file_id_.is_valid() ? td_->file_manager_->get_file_object(sticker->premium_animation_file_id_) : nullptr; return td_api::make_object(std::move(premium_animation_object)); } case StickerType::Mask: { td_api::object_ptr mask_position; if (sticker->point_ >= 0) { mask_position = td_api::make_object( get_mask_point_object(sticker->point_), sticker->x_shift_, sticker->y_shift_, sticker->scale_); } return td_api::make_object(std::move(mask_position)); } case StickerType::CustomEmoji: return td_api::make_object(get_custom_emoji_id(sticker->file_id_).get(), sticker->has_text_color_); default: UNREACHABLE(); return nullptr; } }(); return td_api::make_object( sticker->set_id_.get(), width, height, sticker->alt_, get_sticker_format_object(sticker->format_), std::move(full_type), get_sticker_minithumbnail(sticker->minithumbnail_, sticker->set_id_, document_id, zoom), std::move(thumbnail_object), td_->file_manager_->get_file_object(file_id)); } tl_object_ptr StickersManager::get_stickers_object(const vector &sticker_ids) const { return td_api::make_object( transform(sticker_ids, [&](FileId sticker_id) { return get_sticker_object(sticker_id); })); } tl_object_ptr StickersManager::get_dice_stickers_object(const string &emoji, int32 value) const { if (td_->auth_manager_->is_bot()) { return nullptr; } if (!td::contains(dice_emojis_, emoji)) { return nullptr; } auto it = special_sticker_sets_.find(SpecialStickerSetType::animated_dice(emoji)); if (it == special_sticker_sets_.end()) { return nullptr; } auto sticker_set_id = it->second->id_; if (!sticker_set_id.is_valid()) { return nullptr; } auto sticker_set = get_sticker_set(sticker_set_id); CHECK(sticker_set != nullptr); if (!sticker_set->was_loaded_) { return nullptr; } auto get_sticker = [&](int32 value) { return get_sticker_object(sticker_set->sticker_ids_[value], true); }; if (emoji == "🎰") { if (sticker_set->sticker_ids_.size() < 21 || value < 0 || value > 64) { return nullptr; } int32 background_id = value == 1 || value == 22 || value == 43 || value == 64 ? 1 : 0; int32 lever_id = 2; int32 left_reel_id = value == 64 ? 3 : 8; int32 center_reel_id = value == 64 ? 9 : 14; int32 right_reel_id = value == 64 ? 15 : 20; if (value != 0 && value != 64) { left_reel_id = 4 + (value % 4); center_reel_id = 10 + ((value + 3) / 4 % 4); right_reel_id = 16 + ((value + 15) / 16 % 4); } return td_api::make_object(get_sticker(background_id), get_sticker(lever_id), get_sticker(left_reel_id), get_sticker(center_reel_id), get_sticker(right_reel_id)); } if (value >= 0 && value < static_cast(sticker_set->sticker_ids_.size())) { return td_api::make_object(get_sticker(value)); } return nullptr; } int32 StickersManager::get_dice_success_animation_frame_number(const string &emoji, int32 value) const { if (td_->auth_manager_->is_bot()) { return std::numeric_limits::max(); } if (value == 0 || !td::contains(dice_emojis_, emoji)) { return std::numeric_limits::max(); } auto pos = static_cast(std::find(dice_emojis_.begin(), dice_emojis_.end(), emoji) - dice_emojis_.begin()); if (pos >= dice_success_values_.size()) { return std::numeric_limits::max(); } auto &result = dice_success_values_[pos]; return result.first == value ? result.second : std::numeric_limits::max(); } PhotoFormat StickersManager::get_sticker_set_thumbnail_format(StickerFormat sticker_format) { switch (sticker_format) { case StickerFormat::Unknown: return PhotoFormat::Webp; case StickerFormat::Webp: return PhotoFormat::Webp; case StickerFormat::Tgs: return PhotoFormat::Tgs; case StickerFormat::Webm: return PhotoFormat::Webm; default: UNREACHABLE(); return PhotoFormat::Webp; } } double StickersManager::get_sticker_set_minithumbnail_zoom(const StickerSet *sticker_set) { if (sticker_set->sticker_format_ == StickerFormat::Tgs) { return 100.0 / 512.0; } return 1.0; } td_api::object_ptr StickersManager::get_sticker_set_thumbnail_object( const StickerSet *sticker_set) const { CHECK(sticker_set != nullptr); if (sticker_set->thumbnail_document_id_ != 0 && sticker_set->sticker_type_ == StickerType::CustomEmoji) { for (auto sticker_id : sticker_set->sticker_ids_) { auto file_view = td_->file_manager_->get_file_view(sticker_id); if (file_view.has_remote_location() && !file_view.remote_location().is_web() && file_view.remote_location().get_id() == sticker_set->thumbnail_document_id_) { const Sticker *s = get_sticker(sticker_id); auto thumbnail_format = get_sticker_set_thumbnail_format(s->format_); PhotoSize thumbnail; thumbnail.type = 't'; thumbnail.size = static_cast(file_view.size()); thumbnail.dimensions = s->dimensions_; thumbnail.file_id = s->file_id_; return get_thumbnail_object(td_->file_manager_.get(), thumbnail, thumbnail_format); } } } auto thumbnail_format = get_sticker_set_thumbnail_format(sticker_set->sticker_format_); return get_thumbnail_object(td_->file_manager_.get(), sticker_set->thumbnail_, thumbnail_format); } tl_object_ptr StickersManager::get_sticker_set_object(StickerSetId sticker_set_id) const { const StickerSet *sticker_set = get_sticker_set(sticker_set_id); CHECK(sticker_set != nullptr); CHECK(sticker_set->was_loaded_); sticker_set->was_update_sent_ = true; std::vector> stickers; std::vector> emojis; for (auto sticker_id : sticker_set->sticker_ids_) { stickers.push_back(get_sticker_object(sticker_id)); vector sticker_emojis; auto it = sticker_set->sticker_emojis_map_.find(sticker_id); if (it != sticker_set->sticker_emojis_map_.end()) { sticker_emojis = it->second; } emojis.push_back(make_tl_object(std::move(sticker_emojis))); } return make_tl_object( sticker_set->id_.get(), sticker_set->title_, sticker_set->short_name_, get_sticker_set_thumbnail_object(sticker_set), get_sticker_minithumbnail(sticker_set->minithumbnail_, sticker_set->id_, -2, get_sticker_set_minithumbnail_zoom(sticker_set)), sticker_set->is_installed_ && !sticker_set->is_archived_, sticker_set->is_archived_, sticker_set->is_official_, get_sticker_format_object(sticker_set->sticker_format_), get_sticker_type_object(sticker_set->sticker_type_), sticker_set->is_viewed_, std::move(stickers), std::move(emojis)); } tl_object_ptr StickersManager::get_sticker_sets_object(int32 total_count, const vector &sticker_set_ids, size_t covers_limit) const { vector> result; result.reserve(sticker_set_ids.size()); for (auto sticker_set_id : sticker_set_ids) { auto sticker_set_info = get_sticker_set_info_object(sticker_set_id, covers_limit, false); if (sticker_set_info->size_ != 0) { result.push_back(std::move(sticker_set_info)); } } auto result_size = narrow_cast(result.size()); if (total_count < result_size) { if (total_count != -1) { LOG(ERROR) << "Have total_count = " << total_count << ", but there are " << result_size << " results"; } total_count = result_size; } return make_tl_object(total_count, std::move(result)); } tl_object_ptr StickersManager::get_sticker_set_info_object(StickerSetId sticker_set_id, size_t covers_limit, bool prefer_premium) const { const StickerSet *sticker_set = get_sticker_set(sticker_set_id); CHECK(sticker_set != nullptr); CHECK(sticker_set->is_inited_); sticker_set->was_update_sent_ = true; vector> stickers; if (prefer_premium) { CHECK(!td_->auth_manager_->is_bot()); vector regular_sticker_ids; vector premium_sticker_ids; std::tie(regular_sticker_ids, premium_sticker_ids) = split_stickers_by_premium(sticker_set); auto is_premium = td_->option_manager_->get_option_boolean("is_premium"); size_t max_premium_stickers = is_premium ? covers_limit : 1; if (premium_sticker_ids.size() > max_premium_stickers) { premium_sticker_ids.resize(max_premium_stickers); } CHECK(premium_sticker_ids.size() <= covers_limit); if (regular_sticker_ids.size() > covers_limit - premium_sticker_ids.size()) { regular_sticker_ids.resize(covers_limit - premium_sticker_ids.size()); } if (!is_premium) { std::swap(premium_sticker_ids, regular_sticker_ids); } append(premium_sticker_ids, regular_sticker_ids); for (auto sticker_id : premium_sticker_ids) { stickers.push_back(get_sticker_object(sticker_id)); if (stickers.size() >= covers_limit) { break; } } } else { for (auto sticker_id : sticker_set->sticker_ids_) { stickers.push_back(get_sticker_object(sticker_id)); if (stickers.size() >= covers_limit) { break; } } } auto actual_count = narrow_cast(sticker_set->sticker_ids_.size()); return make_tl_object( sticker_set->id_.get(), sticker_set->title_, sticker_set->short_name_, get_sticker_set_thumbnail_object(sticker_set), get_sticker_minithumbnail(sticker_set->minithumbnail_, sticker_set->id_, -3, get_sticker_set_minithumbnail_zoom(sticker_set)), sticker_set->is_installed_ && !sticker_set->is_archived_, sticker_set->is_archived_, sticker_set->is_official_, get_sticker_format_object(sticker_set->sticker_format_), get_sticker_type_object(sticker_set->sticker_type_), sticker_set->is_viewed_, sticker_set->was_loaded_ ? actual_count : max(actual_count, sticker_set->sticker_count_), std::move(stickers)); } td_api::object_ptr StickersManager::get_premium_gift_sticker_object(int32 month_count) { auto it = premium_gift_messages_.find(month_count); if (it == premium_gift_messages_.end()) { return get_sticker_object(get_premium_gift_option_sticker_id(month_count)); } else { return get_sticker_object(it->second->sticker_id_); } } const StickersManager::StickerSet *StickersManager::get_premium_gift_sticker_set() { if (td_->auth_manager_->is_bot()) { return nullptr; } auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::premium_gifts()); if (!special_sticker_set.id_.is_valid()) { load_special_sticker_set(special_sticker_set); return nullptr; } auto sticker_set = get_sticker_set(special_sticker_set.id_); CHECK(sticker_set != nullptr); if (!sticker_set->was_loaded_) { load_special_sticker_set(special_sticker_set); return nullptr; } return sticker_set; } FileId StickersManager::get_premium_gift_option_sticker_id(const StickerSet *sticker_set, int32 month_count) { if (sticker_set == nullptr || sticker_set->sticker_ids_.empty() || month_count <= 0) { return {}; } int32 number = [month_count] { switch (month_count) { case 1: return 1; case 3: return 2; case 6: return 3; case 12: return 4; case 24: return 5; default: return -1; } }(); for (auto sticker_id : sticker_set->sticker_ids_) { auto it = sticker_set->sticker_emojis_map_.find(sticker_id); if (it != sticker_set->sticker_emojis_map_.end()) { for (auto &emoji : it->second) { if (get_emoji_number(emoji) == number) { return sticker_id; } } } } // there is no match; return the first sticker return sticker_set->sticker_ids_[0]; } FileId StickersManager::get_premium_gift_option_sticker_id(int32 month_count) { return get_premium_gift_option_sticker_id(get_premium_gift_sticker_set(), month_count); } const StickersManager::StickerSet *StickersManager::get_animated_emoji_sticker_set() { if (td_->auth_manager_->is_bot() || disable_animated_emojis_) { return nullptr; } auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_emoji()); if (!special_sticker_set.id_.is_valid()) { load_special_sticker_set(special_sticker_set); return nullptr; } auto sticker_set = get_sticker_set(special_sticker_set.id_); CHECK(sticker_set != nullptr); if (!sticker_set->was_loaded_) { load_special_sticker_set(special_sticker_set); return nullptr; } return sticker_set; } std::pair StickersManager::get_animated_emoji_sticker(const StickerSet *sticker_set, const string &emoji) { if (sticker_set == nullptr) { return {}; } auto emoji_without_modifiers = remove_emoji_modifiers(emoji); auto it = sticker_set->emoji_stickers_map_.find(emoji_without_modifiers); if (it == sticker_set->emoji_stickers_map_.end()) { return {}; } auto emoji_without_selectors = remove_emoji_selectors(emoji); // trying to find full emoji match for (const auto &sticker_id : it->second) { auto emoji_it = sticker_set->sticker_emojis_map_.find(sticker_id); CHECK(emoji_it != sticker_set->sticker_emojis_map_.end()); for (auto &sticker_emoji : emoji_it->second) { if (remove_emoji_selectors(sticker_emoji) == emoji_without_selectors) { return {sticker_id, 0}; } } } // trying to find match without Fitzpatrick modifiers int modifier_id = get_fitzpatrick_modifier(emoji_without_selectors); if (modifier_id > 0) { for (const auto &sticker_id : it->second) { auto emoji_it = sticker_set->sticker_emojis_map_.find(sticker_id); CHECK(emoji_it != sticker_set->sticker_emojis_map_.end()); for (auto &sticker_emoji : emoji_it->second) { if (remove_emoji_selectors(sticker_emoji) == Slice(emoji_without_selectors).remove_suffix(4)) { return {sticker_id, modifier_id}; } } } } // there is no match return {}; } std::pair StickersManager::get_animated_emoji_sticker(const string &emoji) { return get_animated_emoji_sticker(get_animated_emoji_sticker_set(), emoji); } FileId StickersManager::get_animated_emoji_sound_file_id(const string &emoji) const { auto it = emoji_sounds_.find(remove_fitzpatrick_modifier(emoji).str()); if (it == emoji_sounds_.end()) { return {}; } return it->second; } FileId StickersManager::get_custom_animated_emoji_sticker_id(CustomEmojiId custom_emoji_id) const { if (disable_animated_emojis_) { return {}; } return custom_emoji_to_sticker_id_.get(custom_emoji_id); } td_api::object_ptr StickersManager::get_animated_emoji_object(const string &emoji, CustomEmojiId custom_emoji_id) { if (td_->auth_manager_->is_bot() || disable_animated_emojis_) { return nullptr; } if (custom_emoji_id.is_valid()) { auto it = custom_emoji_messages_.find(custom_emoji_id); auto sticker_id = it == custom_emoji_messages_.end() ? get_custom_animated_emoji_sticker_id(custom_emoji_id) : it->second->sticker_id_; auto sticker = get_sticker_object(sticker_id, true); auto default_custom_emoji_dimension = static_cast(512 * animated_emoji_zoom_ + 0.5); auto sticker_width = sticker == nullptr ? default_custom_emoji_dimension : sticker->width_; auto sticker_height = sticker == nullptr ? default_custom_emoji_dimension : sticker->height_; return td_api::make_object(std::move(sticker), sticker_width, sticker_height, 0, nullptr); } auto it = emoji_messages_.find(emoji); if (it == emoji_messages_.end()) { return get_animated_emoji_object(get_animated_emoji_sticker(emoji), get_animated_emoji_sound_file_id(emoji)); } else { return get_animated_emoji_object(it->second->animated_emoji_sticker_, it->second->sound_file_id_); } } td_api::object_ptr StickersManager::get_animated_emoji_object( std::pair animated_sticker, FileId sound_file_id) const { if (!animated_sticker.first.is_valid()) { return nullptr; } auto sticker = get_sticker_object(animated_sticker.first, true); CHECK(sticker != nullptr); auto sticker_width = sticker->width_; auto sticker_height = sticker->height_; return td_api::make_object( std::move(sticker), sticker_width, sticker_height, animated_sticker.second, sound_file_id.is_valid() ? td_->file_manager_->get_file_object(sound_file_id) : nullptr); } tl_object_ptr StickersManager::get_input_sticker_set(StickerSetId sticker_set_id) const { auto sticker_set = get_sticker_set(sticker_set_id); if (sticker_set == nullptr) { return nullptr; } return get_input_sticker_set(sticker_set); } class StickersManager::CustomEmojiLogEvent { public: FileId sticker_id; CustomEmojiLogEvent() = default; explicit CustomEmojiLogEvent(FileId sticker_id) : sticker_id(sticker_id) { } template void store(StorerT &storer) const { BEGIN_STORE_FLAGS(); END_STORE_FLAGS(); StickersManager *stickers_manager = storer.context()->td().get_actor_unsafe()->stickers_manager_.get(); stickers_manager->store_sticker(sticker_id, false, storer, "CustomEmoji"); } template void parse(ParserT &parser) { BEGIN_PARSE_FLAGS(); END_PARSE_FLAGS(); StickersManager *stickers_manager = parser.context()->td().get_actor_unsafe()->stickers_manager_.get(); sticker_id = stickers_manager->parse_sticker(false, parser); } }; string StickersManager::get_custom_emoji_database_key(CustomEmojiId custom_emoji_id) { return PSTRING() << "emoji" << custom_emoji_id.get(); } FileId StickersManager::on_get_sticker(unique_ptr new_sticker, bool replace) { auto file_id = new_sticker->file_id_; CHECK(file_id.is_valid()); CustomEmojiId updated_custom_emoji_id; auto *s = get_sticker(file_id); if (s == nullptr) { s = new_sticker.get(); stickers_.set(file_id, std::move(new_sticker)); } else if (replace) { CHECK(s->file_id_ == file_id); if (s->type_ == StickerType::CustomEmoji) { auto custom_emoji_id = get_custom_emoji_id(file_id); if (custom_emoji_id.is_valid() && custom_emoji_to_sticker_id_.get(custom_emoji_id) == file_id) { custom_emoji_to_sticker_id_.erase(custom_emoji_id); updated_custom_emoji_id = custom_emoji_id; } } bool is_changed = false; if (s->dimensions_ != new_sticker->dimensions_ && new_sticker->dimensions_.width != 0) { LOG(DEBUG) << "Sticker " << file_id << " dimensions have changed"; s->dimensions_ = new_sticker->dimensions_; is_changed = true; } if (s->set_id_ != new_sticker->set_id_ && new_sticker->set_id_.is_valid()) { LOG_IF(ERROR, s->set_id_.is_valid()) << "Sticker " << file_id << " set_id has changed"; s->set_id_ = new_sticker->set_id_; is_changed = true; } if (s->alt_ != new_sticker->alt_ && !new_sticker->alt_.empty()) { LOG(DEBUG) << "Sticker " << file_id << " emoji has changed"; s->alt_ = std::move(new_sticker->alt_); is_changed = true; } if (s->minithumbnail_ != new_sticker->minithumbnail_) { LOG(DEBUG) << "Sticker " << file_id << " minithumbnail has changed"; s->minithumbnail_ = std::move(new_sticker->minithumbnail_); is_changed = true; } if (s->s_thumbnail_ != new_sticker->s_thumbnail_ && new_sticker->s_thumbnail_.file_id.is_valid()) { LOG_IF(INFO, s->s_thumbnail_.file_id.is_valid()) << "Sticker " << file_id << " s thumbnail has changed from " << s->s_thumbnail_ << " to " << new_sticker->s_thumbnail_; s->s_thumbnail_ = std::move(new_sticker->s_thumbnail_); is_changed = true; } if (s->m_thumbnail_ != new_sticker->m_thumbnail_ && new_sticker->m_thumbnail_.file_id.is_valid()) { LOG_IF(INFO, s->m_thumbnail_.file_id.is_valid()) << "Sticker " << file_id << " m thumbnail has changed from " << s->m_thumbnail_ << " to " << new_sticker->m_thumbnail_; s->m_thumbnail_ = std::move(new_sticker->m_thumbnail_); is_changed = true; } if (s->is_premium_ != new_sticker->is_premium_) { s->is_premium_ = new_sticker->is_premium_; is_changed = true; } if (s->has_text_color_ != new_sticker->has_text_color_) { s->has_text_color_ = new_sticker->has_text_color_; is_changed = true; } if (s->premium_animation_file_id_ != new_sticker->premium_animation_file_id_ && new_sticker->premium_animation_file_id_.is_valid()) { s->premium_animation_file_id_ = new_sticker->premium_animation_file_id_; is_changed = true; } if (s->format_ != new_sticker->format_ && new_sticker->format_ != StickerFormat::Unknown) { s->format_ = new_sticker->format_; is_changed = true; } if (s->type_ != new_sticker->type_ && new_sticker->type_ != StickerType::Regular) { s->type_ = new_sticker->type_; is_changed = true; } if (s->point_ != new_sticker->point_ && new_sticker->point_ != -1) { s->point_ = new_sticker->point_; s->x_shift_ = new_sticker->x_shift_; s->y_shift_ = new_sticker->y_shift_; s->scale_ = new_sticker->scale_; is_changed = true; } if (s->emoji_receive_date_ < new_sticker->emoji_receive_date_) { LOG(DEBUG) << "Update custom emoji file " << file_id << " receive date"; s->emoji_receive_date_ = new_sticker->emoji_receive_date_; is_changed = true; } if (is_changed) { s->is_from_database_ = false; } } if (s->type_ == StickerType::CustomEmoji) { s->is_being_reloaded_ = false; auto custom_emoji_id = get_custom_emoji_id(file_id); if (custom_emoji_id.is_valid()) { custom_emoji_to_sticker_id_.set(custom_emoji_id, file_id); CHECK(updated_custom_emoji_id == custom_emoji_id || !updated_custom_emoji_id.is_valid()); updated_custom_emoji_id = custom_emoji_id; if (!s->is_from_database_ && G()->parameters().use_file_db && !G()->close_flag()) { LOG(INFO) << "Save " << custom_emoji_id << " to database"; s->is_from_database_ = true; CustomEmojiLogEvent log_event(file_id); G()->td_db()->get_sqlite_pmc()->set(get_custom_emoji_database_key(custom_emoji_id), log_event_store(log_event).as_slice().str(), Auto()); } } } if (updated_custom_emoji_id.is_valid()) { try_update_custom_emoji_messages(updated_custom_emoji_id); } return file_id; } bool StickersManager::has_webp_thumbnail(const vector> &thumbnails) { // server tries to always replace user-provided thumbnail with server-side WEBP thumbnail // but there can be some old sticker documents or some big stickers for (auto &size : thumbnails) { switch (size->get_id()) { case telegram_api::photoStrippedSize::ID: case telegram_api::photoSizeProgressive::ID: // WEBP thumbnail can't have stripped size or be progressive return false; default: break; } } return true; } std::pair StickersManager::on_get_sticker_document(tl_object_ptr &&document_ptr, StickerFormat expected_format) { if (document_ptr == nullptr) { return {}; } int32 document_constructor_id = document_ptr->get_id(); if (document_constructor_id == telegram_api::documentEmpty::ID) { LOG(ERROR) << "Empty sticker document received"; return {}; } CHECK(document_constructor_id == telegram_api::document::ID); auto document = move_tl_object_as(document_ptr); if (!DcId::is_valid(document->dc_id_)) { LOG(ERROR) << "Wrong dc_id = " << document->dc_id_ << " in document " << to_string(document); return {}; } auto dc_id = DcId::internal(document->dc_id_); Dimensions dimensions; tl_object_ptr sticker; tl_object_ptr custom_emoji; for (auto &attribute : document->attributes_) { switch (attribute->get_id()) { case telegram_api::documentAttributeVideo::ID: { auto video = move_tl_object_as(attribute); dimensions = get_dimensions(video->w_, video->h_, "sticker documentAttributeVideo"); break; } case telegram_api::documentAttributeImageSize::ID: { auto image_size = move_tl_object_as(attribute); dimensions = get_dimensions(image_size->w_, image_size->h_, "sticker documentAttributeImageSize"); break; } case telegram_api::documentAttributeSticker::ID: sticker = move_tl_object_as(attribute); break; case telegram_api::documentAttributeCustomEmoji::ID: custom_emoji = move_tl_object_as(attribute); break; default: continue; } } if (sticker == nullptr && custom_emoji == nullptr) { if (document->mime_type_ != "application/x-bad-tgsticker") { LOG(ERROR) << "Have no attributeSticker in sticker " << to_string(document); } return {}; } auto format = get_sticker_format_by_mime_type(document->mime_type_); if (format == StickerFormat::Unknown || (expected_format != StickerFormat::Unknown && format != expected_format)) { LOG(ERROR) << "Expected sticker of the type " << expected_format << ", but received of the type " << format; return {}; } int64 document_id = document->id_; FileId sticker_id = td_->file_manager_->register_remote(FullRemoteFileLocation(FileType::Sticker, document_id, document->access_hash_, dc_id, document->file_reference_.as_slice().str()), FileLocationSource::FromServer, DialogId(), document->size_, 0, PSTRING() << document_id << get_sticker_format_extension(format)); PhotoSize thumbnail; string minithumbnail; auto thumbnail_format = has_webp_thumbnail(document->thumbs_) ? PhotoFormat::Webp : PhotoFormat::Jpeg; FileId premium_animation_file_id; for (auto &thumb : document->thumbs_) { auto photo_size = get_photo_size(td_->file_manager_.get(), PhotoSizeSource::thumbnail(FileType::Thumbnail, 0), document_id, document->access_hash_, document->file_reference_.as_slice().str(), dc_id, DialogId(), std::move(thumb), thumbnail_format); if (photo_size.get_offset() == 0) { if (!thumbnail.file_id.is_valid()) { thumbnail = std::move(photo_size.get<0>()); } break; } else { if (thumbnail_format == PhotoFormat::Webp) { minithumbnail = std::move(photo_size.get<1>()); } } } for (auto &thumb : document->video_thumbs_) { if (thumb->type_ == "f") { if (!premium_animation_file_id.is_valid()) { premium_animation_file_id = register_photo_size(td_->file_manager_.get(), PhotoSizeSource::thumbnail(FileType::Thumbnail, 'f'), document_id, document->access_hash_, document->file_reference_.as_slice().str(), DialogId(), thumb->size_, dc_id, get_sticker_format_photo_format(format)); } } } create_sticker(sticker_id, premium_animation_file_id, std::move(minithumbnail), std::move(thumbnail), dimensions, std::move(sticker), std::move(custom_emoji), format, nullptr); return {document_id, sticker_id}; } StickersManager::Sticker *StickersManager::get_sticker(FileId file_id) { return stickers_.get_pointer(file_id); } const StickersManager::Sticker *StickersManager::get_sticker(FileId file_id) const { return stickers_.get_pointer(file_id); } StickersManager::StickerSet *StickersManager::get_sticker_set(StickerSetId sticker_set_id) { return sticker_sets_.get_pointer(sticker_set_id); } const StickersManager::StickerSet *StickersManager::get_sticker_set(StickerSetId sticker_set_id) const { return sticker_sets_.get_pointer(sticker_set_id); } StickerSetId StickersManager::get_sticker_set_id(const tl_object_ptr &set_ptr) { CHECK(set_ptr != nullptr); switch (set_ptr->get_id()) { case telegram_api::inputStickerSetEmpty::ID: return StickerSetId(); case telegram_api::inputStickerSetID::ID: return StickerSetId(static_cast(set_ptr.get())->id_); case telegram_api::inputStickerSetShortName::ID: LOG(ERROR) << "Receive sticker set by its short name"; return search_sticker_set(static_cast(set_ptr.get())->short_name_, Auto()); case telegram_api::inputStickerSetAnimatedEmoji::ID: case telegram_api::inputStickerSetAnimatedEmojiAnimations::ID: case telegram_api::inputStickerSetPremiumGifts::ID: case telegram_api::inputStickerSetEmojiGenericAnimations::ID: case telegram_api::inputStickerSetEmojiDefaultStatuses::ID: case telegram_api::inputStickerSetEmojiDefaultTopicIcons::ID: LOG(ERROR) << "Receive special sticker set " << to_string(set_ptr); return add_special_sticker_set(SpecialStickerSetType(set_ptr)).id_; case telegram_api::inputStickerSetDice::ID: LOG(ERROR) << "Receive special sticker set " << to_string(set_ptr); return StickerSetId(); default: UNREACHABLE(); return StickerSetId(); } } StickerSetId StickersManager::add_sticker_set(tl_object_ptr &&set_ptr) { CHECK(set_ptr != nullptr); switch (set_ptr->get_id()) { case telegram_api::inputStickerSetEmpty::ID: return StickerSetId(); case telegram_api::inputStickerSetID::ID: { auto set = move_tl_object_as(set_ptr); StickerSetId set_id{set->id_}; add_sticker_set(set_id, set->access_hash_); return set_id; } case telegram_api::inputStickerSetShortName::ID: { auto set = move_tl_object_as(set_ptr); LOG(ERROR) << "Receive sticker set by its short name"; return search_sticker_set(set->short_name_, Auto()); } case telegram_api::inputStickerSetAnimatedEmoji::ID: case telegram_api::inputStickerSetAnimatedEmojiAnimations::ID: case telegram_api::inputStickerSetPremiumGifts::ID: case telegram_api::inputStickerSetEmojiGenericAnimations::ID: case telegram_api::inputStickerSetEmojiDefaultStatuses::ID: case telegram_api::inputStickerSetEmojiDefaultTopicIcons::ID: LOG(ERROR) << "Receive special sticker set " << to_string(set_ptr); return add_special_sticker_set(SpecialStickerSetType(set_ptr)).id_; case telegram_api::inputStickerSetDice::ID: LOG(ERROR) << "Receive special sticker set " << to_string(set_ptr); return StickerSetId(); default: UNREACHABLE(); return StickerSetId(); } } StickersManager::StickerSet *StickersManager::add_sticker_set(StickerSetId sticker_set_id, int64 access_hash) { if (!sticker_set_id.is_valid()) { return nullptr; } auto *s = get_sticker_set(sticker_set_id); if (s == nullptr) { auto sticker_set = make_unique(); s = sticker_set.get(); s->id_ = sticker_set_id; s->access_hash_ = access_hash; s->is_changed_ = false; s->need_save_to_database_ = false; sticker_sets_.set(sticker_set_id, std::move(sticker_set)); } else { CHECK(s->id_ == sticker_set_id); if (s->access_hash_ != access_hash) { LOG(INFO) << "Access hash of " << sticker_set_id << " changed"; s->access_hash_ = access_hash; s->need_save_to_database_ = true; } } return s; } FileId StickersManager::get_sticker_thumbnail_file_id(FileId file_id) const { auto *sticker = get_sticker(file_id); CHECK(sticker != nullptr); return sticker->s_thumbnail_.file_id; } void StickersManager::delete_sticker_thumbnail(FileId file_id) { auto *sticker = get_sticker(file_id); CHECK(sticker != nullptr); sticker->s_thumbnail_ = PhotoSize(); } vector StickersManager::get_sticker_file_ids(FileId file_id) const { vector result; auto sticker = get_sticker(file_id); CHECK(sticker != nullptr); result.push_back(file_id); if (sticker->s_thumbnail_.file_id.is_valid()) { result.push_back(sticker->s_thumbnail_.file_id); } if (sticker->m_thumbnail_.file_id.is_valid()) { result.push_back(sticker->m_thumbnail_.file_id); } if (sticker->premium_animation_file_id_.is_valid()) { result.push_back(sticker->premium_animation_file_id_); } return result; } FileId StickersManager::dup_sticker(FileId new_id, FileId old_id) { const Sticker *old_sticker = get_sticker(old_id); CHECK(old_sticker != nullptr); CHECK(get_sticker(new_id) == nullptr); auto new_sticker = make_unique(*old_sticker); new_sticker->file_id_ = new_id; // there is no reason to dup m_thumbnail and premium_animation_file_id new_sticker->s_thumbnail_.file_id = td_->file_manager_->dup_file_id(new_sticker->s_thumbnail_.file_id, "dup_sticker"); stickers_.set(new_id, std::move(new_sticker)); return new_id; } void StickersManager::merge_stickers(FileId new_id, FileId old_id) { CHECK(old_id.is_valid() && new_id.is_valid()); CHECK(new_id != old_id); LOG(INFO) << "Merge stickers " << new_id << " and " << old_id; const Sticker *old_ = get_sticker(old_id); CHECK(old_ != nullptr); const auto *new_ = get_sticker(new_id); if (new_ == nullptr) { dup_sticker(new_id, old_id); } else { if (old_->set_id_ == new_->set_id_ && (old_->alt_ != new_->alt_ || old_->set_id_ != new_->set_id_ || (!is_sticker_format_vector(old_->format_) && !is_sticker_format_vector(new_->format_) && old_->dimensions_.width != 0 && old_->dimensions_.height != 0 && old_->dimensions_ != new_->dimensions_))) { LOG(ERROR) << "Sticker has changed: alt = (" << old_->alt_ << ", " << new_->alt_ << "), set_id = (" << old_->set_id_ << ", " << new_->set_id_ << "), dimensions = (" << old_->dimensions_ << ", " << new_->dimensions_ << ")"; } if (old_->s_thumbnail_ != new_->s_thumbnail_) { // LOG_STATUS(td_->file_manager_->merge(new_->s_thumbnail_.file_id, old_->s_thumbnail_.file_id)); } if (old_->m_thumbnail_ != new_->m_thumbnail_) { // LOG_STATUS(td_->file_manager_->merge(new_->m_thumbnail_.file_id, old_->m_thumbnail_.file_id)); } } LOG_STATUS(td_->file_manager_->merge(new_id, old_id)); } tl_object_ptr StickersManager::get_input_sticker_set(const StickerSet *set) { CHECK(set != nullptr); return make_tl_object(set->id_.get(), set->access_hash_); } void StickersManager::reload_installed_sticker_sets(StickerType sticker_type, bool force) { if (G()->close_flag()) { return; } auto type = static_cast(sticker_type); auto &next_load_time = next_installed_sticker_sets_load_time_[type]; if (!td_->auth_manager_->is_bot() && next_load_time >= 0 && (next_load_time < Time::now() || force)) { LOG_IF(INFO, force) << "Reload sticker sets"; next_load_time = -1; td_->create_handler()->send(sticker_type, installed_sticker_sets_hash_[type]); } } void StickersManager::reload_featured_sticker_sets(StickerType sticker_type, bool force) { if (G()->close_flag()) { return; } auto type = static_cast(sticker_type); auto &next_load_time = next_featured_sticker_sets_load_time_[type]; if (!td_->auth_manager_->is_bot() && next_load_time >= 0 && (next_load_time < Time::now() || force)) { LOG_IF(INFO, force) << "Reload trending sticker sets"; next_load_time = -1; td_->create_handler()->send(sticker_type, featured_sticker_sets_hash_[type]); } } void StickersManager::reload_old_featured_sticker_sets(StickerType sticker_type, uint32 generation) { if (sticker_type != StickerType::Regular) { return; } auto type = static_cast(sticker_type); if (generation != 0 && generation != old_featured_sticker_set_generation_[type]) { return; } td_->create_handler()->send( sticker_type, static_cast(old_featured_sticker_set_ids_[type].size()), OLD_FEATURED_STICKER_SET_SLICE_SIZE, old_featured_sticker_set_generation_[type]); } StickerSetId StickersManager::on_get_input_sticker_set(FileId sticker_file_id, tl_object_ptr &&set_ptr, MultiPromiseActor *load_data_multipromise_ptr) { if (set_ptr == nullptr) { return StickerSetId(); } switch (set_ptr->get_id()) { case telegram_api::inputStickerSetEmpty::ID: return StickerSetId(); case telegram_api::inputStickerSetID::ID: { auto set = move_tl_object_as(set_ptr); StickerSetId set_id{set->id_}; add_sticker_set(set_id, set->access_hash_); return set_id; } case telegram_api::inputStickerSetShortName::ID: { auto set = move_tl_object_as(set_ptr); if (load_data_multipromise_ptr == nullptr) { LOG(ERROR) << "Receive sticker set " << set->short_name_ << " by its short name"; return search_sticker_set(set->short_name_, Auto()); } auto set_id = search_sticker_set(set->short_name_, load_data_multipromise_ptr->get_promise()); if (!set_id.is_valid()) { load_data_multipromise_ptr->add_promise(PromiseCreator::lambda( [actor_id = actor_id(this), sticker_file_id, short_name = set->short_name_](Result result) { if (result.is_ok()) { // just in case send_closure(actor_id, &StickersManager::on_resolve_sticker_set_short_name, sticker_file_id, short_name); } })); } // always return empty StickerSetId, because we can't trust the set_id provided by the peer in the secret chat // the real sticker set id will be set in on_get_sticker if and only if the sticker is really from the set return StickerSetId(); } case telegram_api::inputStickerSetAnimatedEmoji::ID: case telegram_api::inputStickerSetAnimatedEmojiAnimations::ID: case telegram_api::inputStickerSetPremiumGifts::ID: case telegram_api::inputStickerSetEmojiGenericAnimations::ID: case telegram_api::inputStickerSetEmojiDefaultStatuses::ID: case telegram_api::inputStickerSetEmojiDefaultTopicIcons::ID: return add_special_sticker_set(SpecialStickerSetType(set_ptr)).id_; case telegram_api::inputStickerSetDice::ID: return StickerSetId(); default: UNREACHABLE(); return StickerSetId(); } } void StickersManager::on_resolve_sticker_set_short_name(FileId sticker_file_id, const string &short_name) { if (G()->close_flag()) { return; } LOG(INFO) << "Resolve sticker " << sticker_file_id << " set to " << short_name; StickerSetId set_id = search_sticker_set(short_name, Auto()); if (set_id.is_valid()) { auto *s = get_sticker(sticker_file_id); CHECK(s != nullptr); if (s->set_id_ != set_id) { s->set_id_ = set_id; } } } void StickersManager::add_sticker_thumbnail(Sticker *s, PhotoSize thumbnail) { if (!thumbnail.file_id.is_valid()) { return; } if (thumbnail.type == 'm') { s->m_thumbnail_ = std::move(thumbnail); return; } if (thumbnail.type == 's' || thumbnail.type == 't') { s->s_thumbnail_ = std::move(thumbnail); return; } LOG(ERROR) << "Receive sticker thumbnail of unsupported type " << thumbnail.type; } void StickersManager::create_sticker(FileId file_id, FileId premium_animation_file_id, string minithumbnail, PhotoSize thumbnail, Dimensions dimensions, tl_object_ptr sticker, tl_object_ptr custom_emoji, StickerFormat format, MultiPromiseActor *load_data_multipromise_ptr) { if (format == StickerFormat::Unknown && sticker == nullptr) { auto old_sticker = get_sticker(file_id); if (old_sticker != nullptr) { format = old_sticker->format_; } else { // guess format by file extension auto file_view = td_->file_manager_->get_file_view(file_id); auto suggested_path = file_view.suggested_path(); const PathView path_view(suggested_path); format = get_sticker_format_by_extension(path_view.extension()); if (format == StickerFormat::Unknown) { format = StickerFormat::Webp; } } } if (is_sticker_format_vector(format) && dimensions.width == 0) { dimensions.width = custom_emoji != nullptr ? 100 : 512; dimensions.height = custom_emoji != nullptr ? 100 : 512; } auto s = make_unique(); s->file_id_ = file_id; s->dimensions_ = dimensions; if (!td_->auth_manager_->is_bot()) { s->minithumbnail_ = std::move(minithumbnail); } add_sticker_thumbnail(s.get(), std::move(thumbnail)); if (premium_animation_file_id.is_valid()) { s->is_premium_ = true; } s->premium_animation_file_id_ = premium_animation_file_id; if (sticker != nullptr) { s->set_id_ = on_get_input_sticker_set(file_id, std::move(sticker->stickerset_), load_data_multipromise_ptr); s->alt_ = std::move(sticker->alt_); if ((sticker->flags_ & telegram_api::documentAttributeSticker::MASK_MASK) != 0) { s->type_ = StickerType::Mask; } if ((sticker->flags_ & telegram_api::documentAttributeSticker::MASK_COORDS_MASK) != 0) { CHECK(sticker->mask_coords_ != nullptr); int32 point = sticker->mask_coords_->n_; if (0 <= point && point <= 3) { s->point_ = sticker->mask_coords_->n_; s->x_shift_ = sticker->mask_coords_->x_; s->y_shift_ = sticker->mask_coords_->y_; s->scale_ = sticker->mask_coords_->zoom_; } } } else if (custom_emoji != nullptr) { s->set_id_ = on_get_input_sticker_set(file_id, std::move(custom_emoji->stickerset_), load_data_multipromise_ptr); s->alt_ = std::move(custom_emoji->alt_); s->type_ = StickerType::CustomEmoji; s->is_premium_ = !custom_emoji->free_; s->has_text_color_ = custom_emoji->text_color_; s->emoji_receive_date_ = G()->unix_time(); } s->format_ = format; on_get_sticker(std::move(s), (sticker != nullptr || custom_emoji != nullptr) && load_data_multipromise_ptr == nullptr); } bool StickersManager::has_input_media(FileId sticker_file_id, bool is_secret) const { auto file_view = td_->file_manager_->get_file_view(sticker_file_id); if (is_secret) { const Sticker *sticker = get_sticker(sticker_file_id); CHECK(sticker != nullptr); if (file_view.is_encrypted_secret()) { if (!file_view.encryption_key().empty() && file_view.has_remote_location() && !sticker->s_thumbnail_.file_id.is_valid()) { return true; } } else if (!file_view.is_encrypted()) { if (sticker->set_id_.is_valid()) { const auto *sticker_set = get_sticker_set(sticker->set_id_); if (sticker_set != nullptr && td::contains(sticker_set->sticker_ids_, sticker_file_id)) { // stickers within a set can be sent by id and access_hash return true; } } } } else { if (file_view.is_encrypted()) { return false; } if (td_->auth_manager_->is_bot() && file_view.has_remote_location()) { return true; } const Sticker *sticker = get_sticker(sticker_file_id); CHECK(sticker != nullptr); if (sticker->set_id_.is_valid()) { // stickers within a set doesn't need to be duped // but the sticker can be deleted any time from the set, so we are forced to dup it anyway // return true; } // having remote location is not enough to have InputMedia, because the file may not have valid file_reference // also file_id needs to be duped, because upload can be called to repair the file_reference and every upload // request must have unique file_id if (/* file_view.has_remote_location() || */ file_view.has_url()) { return true; } } return false; } SecretInputMedia StickersManager::get_secret_input_media(FileId sticker_file_id, tl_object_ptr input_file, BufferSlice thumbnail, int32 layer) const { const Sticker *sticker = get_sticker(sticker_file_id); CHECK(sticker != nullptr); auto file_view = td_->file_manager_->get_file_view(sticker_file_id); if (file_view.is_encrypted_secret()) { if (file_view.has_remote_location()) { input_file = file_view.main_remote_location().as_input_encrypted_file(); } if (!input_file) { return {}; } if (sticker->s_thumbnail_.file_id.is_valid() && thumbnail.empty()) { return {}; } } else if (!file_view.is_encrypted()) { if (!sticker->set_id_.is_valid()) { // stickers without set can't be sent by id and access_hash return {}; } } else { return {}; } tl_object_ptr input_sticker_set = make_tl_object(); if (sticker->set_id_.is_valid()) { const StickerSet *sticker_set = get_sticker_set(sticker->set_id_); CHECK(sticker_set != nullptr); if (sticker_set->is_inited_ && td::contains(sticker_set->sticker_ids_, sticker_file_id)) { input_sticker_set = make_tl_object(sticker_set->short_name_); } else { // TODO load sticker set } } vector> attributes; attributes.push_back( secret_api::make_object(sticker->alt_, std::move(input_sticker_set))); if (sticker->dimensions_.width != 0 && sticker->dimensions_.height != 0) { attributes.push_back(secret_api::make_object(sticker->dimensions_.width, sticker->dimensions_.height)); } if (file_view.is_encrypted_secret()) { return {std::move(input_file), std::move(thumbnail), sticker->s_thumbnail_.dimensions, get_sticker_format_mime_type(sticker->format_), file_view, std::move(attributes), string(), layer}; } else { CHECK(!file_view.is_encrypted()); auto &remote_location = file_view.remote_location(); if (remote_location.is_web()) { // web stickers shouldn't have set_id LOG(ERROR) << "Have a web sticker in " << sticker->set_id_; return {}; } if (file_view.size() > 1000000000) { LOG(ERROR) << "Have a sticker of size " << file_view.size() << " in " << sticker->set_id_; return {}; } return SecretInputMedia{ nullptr, make_tl_object( remote_location.get_id(), remote_location.get_access_hash(), 0 /*date*/, get_sticker_format_mime_type(sticker->format_), narrow_cast(file_view.size()), make_tl_object("t"), remote_location.get_dc_id().get_raw_id(), std::move(attributes))}; } } tl_object_ptr StickersManager::get_input_media( FileId file_id, tl_object_ptr input_file, tl_object_ptr input_thumbnail, const string &emoji) const { auto file_view = td_->file_manager_->get_file_view(file_id); if (file_view.is_encrypted()) { return nullptr; } if (file_view.has_remote_location() && !file_view.main_remote_location().is_web() && input_file == nullptr) { int32 flags = 0; if (!emoji.empty()) { flags |= telegram_api::inputMediaDocument::QUERY_MASK; } return make_tl_object( flags, false /*ignored*/, file_view.main_remote_location().as_input_document(), 0, emoji); } if (file_view.has_url()) { return make_tl_object(0, false /*ignored*/, file_view.url(), 0); } if (input_file != nullptr) { const Sticker *s = get_sticker(file_id); CHECK(s != nullptr); vector> attributes; if (s->dimensions_.width != 0 && s->dimensions_.height != 0) { attributes.push_back( make_tl_object(s->dimensions_.width, s->dimensions_.height)); } attributes.push_back(make_tl_object( 0, false /*ignored*/, s->alt_, make_tl_object(), nullptr)); int32 flags = 0; if (input_thumbnail != nullptr) { flags |= telegram_api::inputMediaUploadedDocument::THUMB_MASK; } auto mime_type = get_sticker_format_mime_type(s->format_); return make_tl_object( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), mime_type, std::move(attributes), vector>(), 0); } else { CHECK(!file_view.has_remote_location()); } return nullptr; } StickerSetId StickersManager::on_get_sticker_set(tl_object_ptr &&set, bool is_changed, const char *source) { CHECK(set != nullptr); StickerSetId set_id{set->id_}; StickerSet *s = add_sticker_set(set_id, set->access_hash_); if (s == nullptr) { return {}; } bool is_installed = (set->flags_ & telegram_api::stickerSet::INSTALLED_DATE_MASK) != 0; bool is_archived = set->archived_; bool is_official = set->official_; StickerFormat sticker_format = set->videos_ ? StickerFormat::Webm : (set->animated_ ? StickerFormat::Tgs : StickerFormat::Webp); StickerType sticker_type = set->emojis_ ? StickerType::CustomEmoji : (set->masks_ ? StickerType::Mask : StickerType::Regular); PhotoSize thumbnail; string minithumbnail; int64 thumbnail_document_id = 0; for (auto &thumb : set->thumbs_) { auto photo_size = get_photo_size(td_->file_manager_.get(), PhotoSizeSource::sticker_set_thumbnail(set_id.get(), s->access_hash_, set->thumb_version_), 0, 0, "", DcId::create(set->thumb_dc_id_), DialogId(), std::move(thumb), get_sticker_set_thumbnail_format(sticker_format)); if (photo_size.get_offset() == 0) { if (!thumbnail.file_id.is_valid()) { thumbnail = std::move(photo_size.get<0>()); } } else { minithumbnail = std::move(photo_size.get<1>()); } } if ((set->flags_ & telegram_api::stickerSet::THUMB_DOCUMENT_ID_MASK) != 0) { thumbnail_document_id = set->thumb_document_id_; } if (!s->is_inited_) { LOG(INFO) << "Init " << set_id; s->is_inited_ = true; s->title_ = std::move(set->title_); s->short_name_ = std::move(set->short_name_); if (!td_->auth_manager_->is_bot()) { s->minithumbnail_ = std::move(minithumbnail); } s->thumbnail_ = std::move(thumbnail); s->thumbnail_document_id_ = thumbnail_document_id; s->is_thumbnail_reloaded_ = true; s->are_legacy_sticker_thumbnails_reloaded_ = true; s->sticker_count_ = set->count_; s->hash_ = set->hash_; s->is_official_ = is_official; s->sticker_format_ = sticker_format; s->sticker_type_ = sticker_type; s->is_changed_ = true; } else { CHECK(s->id_ == set_id); auto type = static_cast(s->sticker_type_); if (s->access_hash_ != set->access_hash_) { LOG(INFO) << "Access hash of " << set_id << " has changed"; s->access_hash_ = set->access_hash_; s->need_save_to_database_ = true; } if (s->title_ != set->title_) { LOG(INFO) << "Title of " << set_id << " has changed"; s->title_ = std::move(set->title_); s->is_changed_ = true; if (installed_sticker_sets_hints_[type].has_key(set_id.get())) { installed_sticker_sets_hints_[type].add(set_id.get(), PSLICE() << s->title_ << ' ' << s->short_name_); } } if (s->short_name_ != set->short_name_) { LOG(ERROR) << "Short name of " << set_id << " has changed from \"" << s->short_name_ << "\" to \"" << set->short_name_ << "\" from " << source; short_name_to_sticker_set_id_.erase(clean_username(s->short_name_)); s->short_name_ = std::move(set->short_name_); s->is_changed_ = true; if (installed_sticker_sets_hints_[type].has_key(set_id.get())) { installed_sticker_sets_hints_[type].add(set_id.get(), PSLICE() << s->title_ << ' ' << s->short_name_); } } if (s->minithumbnail_ != minithumbnail) { LOG(INFO) << "Minithumbnail of " << set_id << " has changed"; s->minithumbnail_ = std::move(minithumbnail); s->is_changed_ = true; } if (s->thumbnail_ != thumbnail) { LOG(INFO) << "Thumbnail of " << set_id << " has changed from " << s->thumbnail_ << " to " << thumbnail; s->thumbnail_ = std::move(thumbnail); s->is_changed_ = true; } if (s->thumbnail_document_id_ != thumbnail_document_id) { LOG(INFO) << "Thumbnail of " << set_id << " has changed from " << s->thumbnail_document_id_ << " to " << thumbnail_document_id; s->thumbnail_document_id_ = thumbnail_document_id; s->is_changed_ = true; } if (!s->is_thumbnail_reloaded_ || !s->are_legacy_sticker_thumbnails_reloaded_) { LOG(INFO) << "Sticker thumbnails and thumbnail of " << set_id << " was reloaded"; s->is_thumbnail_reloaded_ = true; s->are_legacy_sticker_thumbnails_reloaded_ = true; s->need_save_to_database_ = true; } if (s->sticker_count_ != set->count_ || s->hash_ != set->hash_) { LOG(INFO) << "Number of stickers in " << set_id << " changed from " << s->sticker_count_ << " to " << set->count_; s->is_loaded_ = false; s->sticker_count_ = set->count_; s->hash_ = set->hash_; if (s->was_loaded_) { s->need_save_to_database_ = true; } else { s->is_changed_ = true; } } if (s->is_official_ != is_official) { LOG(INFO) << "Official flag of " << set_id << " changed to " << is_official; s->is_official_ = is_official; s->is_changed_ = true; } if (s->sticker_format_ != sticker_format) { LOG(ERROR) << "Format of stickers in " << set_id << "/" << s->short_name_ << " has changed from " << s->sticker_format_ << " to " << sticker_format << " from " << source; s->sticker_format_ = sticker_format; s->is_changed_ = true; } LOG_IF(ERROR, s->sticker_type_ != sticker_type) << "Type of " << set_id << "/" << s->short_name_ << " has changed from " << s->sticker_type_ << " to " << sticker_type << " from " << source; } auto cleaned_username = clean_username(s->short_name_); if (!cleaned_username.empty()) { short_name_to_sticker_set_id_.set(cleaned_username, set_id); } on_update_sticker_set(s, is_installed, is_archived, is_changed); return set_id; } StickerSetId StickersManager::on_get_sticker_set_covered(tl_object_ptr &&set_ptr, bool is_changed, const char *source) { StickerSetId set_id; switch (set_ptr->get_id()) { case telegram_api::stickerSetCovered::ID: { auto covered_set = move_tl_object_as(set_ptr); set_id = on_get_sticker_set(std::move(covered_set->set_), is_changed, source); if (!set_id.is_valid()) { break; } auto sticker_set = get_sticker_set(set_id); CHECK(sticker_set != nullptr); CHECK(sticker_set->is_inited_); if (sticker_set->was_loaded_) { break; } if (sticker_set->sticker_count_ == 0) { break; } auto &sticker_ids = sticker_set->sticker_ids_; auto sticker_id = on_get_sticker_document(std::move(covered_set->cover_), sticker_set->sticker_format_).second; if (sticker_id.is_valid() && !td::contains(sticker_ids, sticker_id)) { sticker_ids.push_back(sticker_id); sticker_set->is_changed_ = true; } break; } case telegram_api::stickerSetMultiCovered::ID: { auto multicovered_set = move_tl_object_as(set_ptr); set_id = on_get_sticker_set(std::move(multicovered_set->set_), is_changed, source); if (!set_id.is_valid()) { break; } auto sticker_set = get_sticker_set(set_id); CHECK(sticker_set != nullptr); CHECK(sticker_set->is_inited_); if (sticker_set->was_loaded_) { break; } auto &sticker_ids = sticker_set->sticker_ids_; for (auto &cover : multicovered_set->covers_) { auto sticker_id = on_get_sticker_document(std::move(cover), sticker_set->sticker_format_).second; if (sticker_id.is_valid() && !td::contains(sticker_ids, sticker_id)) { sticker_ids.push_back(sticker_id); sticker_set->is_changed_ = true; } } break; } case telegram_api::stickerSetFullCovered::ID: { auto set = move_tl_object_as(set_ptr); auto sticker_set = telegram_api::make_object( std::move(set->set_), std::move(set->packs_), std::move(set->keywords_), std::move(set->documents_)); return on_get_messages_sticker_set(StickerSetId(), std::move(sticker_set), is_changed, source); } case telegram_api::stickerSetNoCovered::ID: { auto covered_set = move_tl_object_as(set_ptr); set_id = on_get_sticker_set(std::move(covered_set->set_), is_changed, source); break; } default: UNREACHABLE(); } return set_id; } StickerSetId StickersManager::on_get_messages_sticker_set(StickerSetId sticker_set_id, tl_object_ptr &&set_ptr, bool is_changed, const char *source) { LOG(INFO) << "Receive sticker set " << to_string(set_ptr); if (set_ptr->get_id() == telegram_api::messages_stickerSetNotModified::ID) { if (!sticker_set_id.is_valid()) { LOG(ERROR) << "Receive unexpected stickerSetNotModified from " << source; } else { auto s = get_sticker_set(sticker_set_id); CHECK(s != nullptr); CHECK(s->is_inited_); CHECK(s->was_loaded_); s->is_loaded_ = true; s->expires_at_ = G()->unix_time() + (td_->auth_manager_->is_bot() ? Random::fast(10 * 60, 15 * 60) : Random::fast(30 * 60, 50 * 60)); } return sticker_set_id; } auto set = move_tl_object_as(set_ptr); auto set_id = on_get_sticker_set(std::move(set->set_), is_changed, source); if (!set_id.is_valid()) { return StickerSetId(); } if (sticker_set_id.is_valid() && sticker_set_id != set_id) { LOG(ERROR) << "Expected " << sticker_set_id << ", but receive " << set_id << " from " << source; on_load_sticker_set_fail(sticker_set_id, Status::Error(500, "Internal Server Error: wrong sticker set received")); return StickerSetId(); } auto s = get_sticker_set(set_id); CHECK(s != nullptr); CHECK(s->is_inited_); s->expires_at_ = G()->unix_time() + (td_->auth_manager_->is_bot() ? Random::fast(10 * 60, 15 * 60) : Random::fast(30 * 60, 50 * 60)); if (s->is_loaded_) { update_sticker_set(s, "on_get_messages_sticker_set"); send_update_installed_sticker_sets(); return set_id; } s->was_loaded_ = true; s->is_loaded_ = true; s->is_changed_ = true; s->are_keywords_loaded_ = true; s->is_sticker_has_text_color_loaded_ = true; FlatHashMap document_id_to_sticker_id; s->sticker_ids_.clear(); s->premium_sticker_positions_.clear(); bool is_bot = td_->auth_manager_->is_bot(); for (auto &document_ptr : set->documents_) { auto sticker_id = on_get_sticker_document(std::move(document_ptr), s->sticker_format_); if (!sticker_id.second.is_valid() || sticker_id.first == 0) { continue; } if (!is_bot && get_sticker(sticker_id.second)->is_premium_) { s->premium_sticker_positions_.push_back(static_cast(s->sticker_ids_.size())); } s->sticker_ids_.push_back(sticker_id.second); if (!is_bot) { document_id_to_sticker_id.emplace(sticker_id.first, sticker_id.second); } } if (static_cast(s->sticker_ids_.size()) != s->sticker_count_) { LOG(ERROR) << "Wrong sticker set size " << s->sticker_count_ << " instead of " << s->sticker_ids_.size() << " specified in " << set_id << "/" << s->short_name_ << " from " << source; s->sticker_count_ = static_cast(s->sticker_ids_.size()); } if (!is_bot) { s->emoji_stickers_map_.clear(); s->sticker_emojis_map_.clear(); s->keyword_stickers_map_.clear(); s->sticker_keywords_map_.clear(); for (auto &pack : set->packs_) { auto cleaned_emoji = remove_emoji_modifiers(pack->emoticon_); if (cleaned_emoji.empty()) { LOG(ERROR) << "Receive empty emoji in " << set_id << "/" << s->short_name_ << " from " << source; continue; } vector stickers; stickers.reserve(pack->documents_.size()); for (int64 document_id : pack->documents_) { auto it = document_id_to_sticker_id.find(document_id); if (it == document_id_to_sticker_id.end()) { LOG(ERROR) << "Can't find document with ID " << document_id << " in " << set_id << "/" << s->short_name_ << " from " << source; continue; } stickers.push_back(it->second); s->sticker_emojis_map_[it->second].push_back(pack->emoticon_); } auto &sticker_ids = s->emoji_stickers_map_[cleaned_emoji]; for (auto sticker_id : stickers) { if (!td::contains(sticker_ids, sticker_id)) { sticker_ids.push_back(sticker_id); } } } for (auto &keywords : set->keywords_) { auto document_id = keywords->document_id_; auto it = document_id_to_sticker_id.find(document_id); if (it == document_id_to_sticker_id.end()) { LOG(ERROR) << "Can't find document with ID " << document_id << " in " << set_id << "/" << s->short_name_ << " from " << source; continue; } bool is_inserted = s->sticker_keywords_map_.emplace(it->second, std::move(keywords->keyword_)).second; if (!is_inserted) { LOG(ERROR) << "Receive twice document with ID " << document_id << " in " << set_id << "/" << s->short_name_ << " from " << source; } } } update_sticker_set(s, "on_get_messages_sticker_set 2"); update_load_requests(s, true, Status::OK()); send_update_installed_sticker_sets(); if (set_id == add_special_sticker_set(SpecialStickerSetType::animated_emoji()).id_) { try_update_animated_emoji_messages(); } if (set_id == add_special_sticker_set(SpecialStickerSetType::premium_gifts()).id_) { try_update_premium_gift_messages(); } return set_id; } void StickersManager::on_load_sticker_set_fail(StickerSetId sticker_set_id, const Status &error) { if (!sticker_set_id.is_valid()) { return; } update_load_requests(get_sticker_set(sticker_set_id), true, error); } void StickersManager::update_load_requests(StickerSet *sticker_set, bool with_stickers, const Status &status) { if (sticker_set == nullptr) { return; } if (with_stickers) { for (auto load_request_id : sticker_set->load_requests_) { update_load_request(load_request_id, status); } sticker_set->load_requests_.clear(); } for (auto load_request_id : sticker_set->load_without_stickers_requests_) { update_load_request(load_request_id, status); } sticker_set->load_without_stickers_requests_.clear(); if (status.message() == "STICKERSET_INVALID") { // the sticker set is likely to be deleted // clear short_name_to_sticker_set_id_ to allow next searchStickerSet request to succeed short_name_to_sticker_set_id_.erase(clean_username(sticker_set->short_name_)); } } void StickersManager::update_load_request(uint32 load_request_id, const Status &status) { auto it = sticker_set_load_requests_.find(load_request_id); CHECK(it != sticker_set_load_requests_.end()); CHECK(it->second.left_queries_ > 0); if (status.is_error() && it->second.error_.is_ok()) { it->second.error_ = status.clone(); } if (--it->second.left_queries_ == 0) { if (it->second.error_.is_ok()) { it->second.promise_.set_value(Unit()); } else { it->second.promise_.set_error(std::move(it->second.error_)); } sticker_set_load_requests_.erase(it); } } void StickersManager::on_get_special_sticker_set(const SpecialStickerSetType &type, StickerSetId sticker_set_id) { auto s = get_sticker_set(sticker_set_id); CHECK(s != nullptr); CHECK(s->is_inited_); CHECK(s->is_loaded_); LOG(INFO) << "Receive special sticker set " << type.type_ << ": " << sticker_set_id << ' ' << s->access_hash_ << ' ' << s->short_name_; auto &sticker_set = add_special_sticker_set(type); if (sticker_set_id == sticker_set.id_ && s->access_hash_ == sticker_set.access_hash_ && s->short_name_ == sticker_set.short_name_ && !s->short_name_.empty()) { on_load_special_sticker_set(type, Status::OK()); return; } sticker_set.id_ = sticker_set_id; sticker_set.access_hash_ = s->access_hash_; sticker_set.short_name_ = clean_username(s->short_name_); sticker_set.type_ = type; if (!td_->auth_manager_->is_bot()) { G()->td_db()->get_binlog_pmc()->set(type.type_, PSTRING() << sticker_set.id_.get() << ' ' << sticker_set.access_hash_ << ' ' << sticker_set.short_name_); } sticker_set.is_being_loaded_ = true; on_load_special_sticker_set(type, Status::OK()); } td_api::object_ptr StickersManager::get_update_active_emoji_reactions_object() const { return td_api::make_object(vector(active_reactions_)); } void StickersManager::save_active_reactions() { LOG(INFO) << "Save " << active_reactions_.size() << " active reactions"; G()->td_db()->get_binlog_pmc()->set("active_reactions", log_event_store(active_reactions_).as_slice().str()); } void StickersManager::save_reactions() { LOG(INFO) << "Save " << reactions_.reactions_.size() << " available reactions"; are_reactions_loaded_from_database_ = true; G()->td_db()->get_binlog_pmc()->set("reactions", log_event_store(reactions_).as_slice().str()); } void StickersManager::save_recent_reactions() { LOG(INFO) << "Save " << recent_reactions_.reactions_.size() << " recent reactions"; are_recent_reactions_loaded_from_database_ = true; G()->td_db()->get_binlog_pmc()->set("recent_reactions", log_event_store(recent_reactions_).as_slice().str()); } void StickersManager::save_top_reactions() { LOG(INFO) << "Save " << top_reactions_.reactions_.size() << " top reactions"; are_top_reactions_loaded_from_database_ = true; G()->td_db()->get_binlog_pmc()->set("top_reactions", log_event_store(top_reactions_).as_slice().str()); } void StickersManager::load_active_reactions() { LOG(INFO) << "Loading active reactions"; string active_reactions = G()->td_db()->get_binlog_pmc()->get("active_reactions"); if (active_reactions.empty()) { return reload_reactions(); } auto status = log_event_parse(active_reactions_, active_reactions); if (status.is_error()) { LOG(ERROR) << "Can't load active reactions: " << status; active_reactions_ = {}; return reload_reactions(); } LOG(INFO) << "Successfully loaded " << active_reactions_.size() << " active reactions"; td_->messages_manager_->set_active_reactions(vector(active_reactions_)); send_closure(G()->td(), &Td::send_update, get_update_active_emoji_reactions_object()); } void StickersManager::load_reactions() { if (are_reactions_loaded_from_database_) { return; } are_reactions_loaded_from_database_ = true; LOG(INFO) << "Loading available reactions"; string reactions = G()->td_db()->get_binlog_pmc()->get("reactions"); if (reactions.empty()) { return reload_reactions(); } auto new_reactions = reactions_; auto status = log_event_parse(new_reactions, reactions); if (status.is_error()) { LOG(ERROR) << "Can't load available reactions: " << status; return reload_reactions(); } for (auto &reaction : new_reactions.reactions_) { if (!reaction.is_valid()) { LOG(ERROR) << "Loaded invalid reaction"; return reload_reactions(); } } reactions_ = std::move(new_reactions); LOG(INFO) << "Successfully loaded " << reactions_.reactions_.size() << " available reactions"; update_active_reactions(); } void StickersManager::load_recent_reactions() { if (are_recent_reactions_loaded_from_database_) { return; } are_recent_reactions_loaded_from_database_ = true; LOG(INFO) << "Loading recent reactions"; string recent_reactions = G()->td_db()->get_binlog_pmc()->get("recent_reactions"); if (recent_reactions.empty()) { return reload_recent_reactions(); } auto status = log_event_parse(recent_reactions_, recent_reactions); if (status.is_error()) { LOG(ERROR) << "Can't load recent reactions: " << status; recent_reactions_ = {}; return reload_recent_reactions(); } LOG(INFO) << "Successfully loaded " << recent_reactions_.reactions_.size() << " recent reactions"; } void StickersManager::load_top_reactions() { if (are_top_reactions_loaded_from_database_) { return; } are_top_reactions_loaded_from_database_ = true; LOG(INFO) << "Loading top reactions"; string top_reactions = G()->td_db()->get_binlog_pmc()->get("top_reactions"); if (top_reactions.empty()) { return reload_top_reactions(); } auto status = log_event_parse(top_reactions_, top_reactions); if (status.is_error()) { LOG(ERROR) << "Can't load top reactions: " << status; top_reactions_ = {}; return reload_top_reactions(); } LOG(INFO) << "Successfully loaded " << top_reactions_.reactions_.size() << " top reactions"; } void StickersManager::update_active_reactions() { vector active_reactions; for (auto &reaction : reactions_.reactions_) { if (reaction.is_active_) { active_reactions.emplace_back(reaction.reaction_); } } if (active_reactions == active_reactions_) { return; } active_reactions_ = active_reactions; save_active_reactions(); send_closure(G()->td(), &Td::send_update, get_update_active_emoji_reactions_object()); td_->messages_manager_->set_active_reactions(std::move(active_reactions)); } void StickersManager::on_get_available_reactions( tl_object_ptr &&available_reactions_ptr) { CHECK(reactions_.are_being_reloaded_); reactions_.are_being_reloaded_ = false; auto get_emoji_reaction_queries = std::move(pending_get_emoji_reaction_queries_); pending_get_emoji_reaction_queries_.clear(); SCOPE_EXIT { for (auto &query : get_emoji_reaction_queries) { query.second.set_value(get_emoji_reaction_object(query.first)); } }; if (available_reactions_ptr == nullptr) { // failed to get available reactions return; } int32 constructor_id = available_reactions_ptr->get_id(); if (constructor_id == telegram_api::messages_availableReactionsNotModified::ID) { LOG(INFO) << "Available reactions are not modified"; return; } CHECK(constructor_id == telegram_api::messages_availableReactions::ID); auto available_reactions = move_tl_object_as(available_reactions_ptr); vector new_reactions; for (auto &available_reaction : available_reactions->reactions_) { Reaction reaction; reaction.is_active_ = !available_reaction->inactive_; reaction.is_premium_ = available_reaction->premium_; reaction.reaction_ = std::move(available_reaction->reaction_); reaction.title_ = std::move(available_reaction->title_); reaction.static_icon_ = on_get_sticker_document(std::move(available_reaction->static_icon_), StickerFormat::Webp).second; reaction.appear_animation_ = on_get_sticker_document(std::move(available_reaction->appear_animation_), StickerFormat::Tgs).second; reaction.select_animation_ = on_get_sticker_document(std::move(available_reaction->select_animation_), StickerFormat::Tgs).second; reaction.activate_animation_ = on_get_sticker_document(std::move(available_reaction->activate_animation_), StickerFormat::Tgs).second; reaction.effect_animation_ = on_get_sticker_document(std::move(available_reaction->effect_animation_), StickerFormat::Tgs).second; reaction.around_animation_ = on_get_sticker_document(std::move(available_reaction->around_animation_), StickerFormat::Tgs).second; reaction.center_animation_ = on_get_sticker_document(std::move(available_reaction->center_icon_), StickerFormat::Tgs).second; if (!reaction.is_valid()) { LOG(ERROR) << "Receive invalid reaction " << reaction.reaction_; continue; } if (reaction.is_premium_) { LOG(ERROR) << "Receive premium reaction " << reaction.reaction_; continue; } new_reactions.push_back(std::move(reaction)); } reactions_.reactions_ = std::move(new_reactions); reactions_.hash_ = available_reactions->hash_; save_reactions(); update_active_reactions(); } void StickersManager::on_get_recent_reactions(tl_object_ptr &&reactions_ptr) { CHECK(recent_reactions_.is_being_reloaded_); recent_reactions_.is_being_reloaded_ = false; if (reactions_ptr == nullptr) { // failed to get recent reactions return; } int32 constructor_id = reactions_ptr->get_id(); if (constructor_id == telegram_api::messages_reactionsNotModified::ID) { LOG(INFO) << "Top reactions are not modified"; return; } CHECK(constructor_id == telegram_api::messages_reactions::ID); auto reactions = move_tl_object_as(reactions_ptr); auto new_reactions = transform(reactions->reactions_, [](const telegram_api::object_ptr &reaction) { return get_message_reaction_string(reaction); }); if (new_reactions == recent_reactions_.reactions_ && recent_reactions_.hash_ == reactions->hash_) { LOG(INFO) << "Top reactions are not modified"; return; } recent_reactions_.reactions_ = std::move(new_reactions); recent_reactions_.hash_ = reactions->hash_; auto expected_hash = get_reactions_hash(recent_reactions_.reactions_); if (recent_reactions_.hash_ != expected_hash) { LOG(ERROR) << "Receive hash " << recent_reactions_.hash_ << " instead of " << expected_hash << " for reactions " << recent_reactions_.reactions_; } save_recent_reactions(); } void StickersManager::on_get_top_reactions(tl_object_ptr &&reactions_ptr) { CHECK(top_reactions_.is_being_reloaded_); top_reactions_.is_being_reloaded_ = false; if (reactions_ptr == nullptr) { // failed to get top reactions return; } int32 constructor_id = reactions_ptr->get_id(); if (constructor_id == telegram_api::messages_reactionsNotModified::ID) { LOG(INFO) << "Top reactions are not modified"; return; } CHECK(constructor_id == telegram_api::messages_reactions::ID); auto reactions = move_tl_object_as(reactions_ptr); auto new_reactions = transform(reactions->reactions_, [](const telegram_api::object_ptr &reaction) { return get_message_reaction_string(reaction); }); if (new_reactions == top_reactions_.reactions_ && top_reactions_.hash_ == reactions->hash_) { LOG(INFO) << "Top reactions are not modified"; return; } top_reactions_.reactions_ = std::move(new_reactions); top_reactions_.hash_ = reactions->hash_; save_top_reactions(); } void StickersManager::on_get_installed_sticker_sets(StickerType sticker_type, tl_object_ptr &&stickers_ptr) { auto type = static_cast(sticker_type); next_installed_sticker_sets_load_time_[type] = Time::now_cached() + Random::fast(30 * 60, 50 * 60); CHECK(stickers_ptr != nullptr); int32 constructor_id = stickers_ptr->get_id(); if (constructor_id == telegram_api::messages_allStickersNotModified::ID) { LOG(INFO) << sticker_type << " stickers are not modified"; return; } CHECK(constructor_id == telegram_api::messages_allStickers::ID); auto stickers = move_tl_object_as(stickers_ptr); FlatHashSet uninstalled_sticker_sets; for (auto &sticker_set_id : installed_sticker_set_ids_[type]) { uninstalled_sticker_sets.insert(sticker_set_id); } vector sets_to_load; vector installed_sticker_set_ids; vector debug_hashes; vector debug_sticker_set_ids; std::reverse(stickers->sets_.begin(), stickers->sets_.end()); // apply installed sticker sets in reverse order for (auto &set : stickers->sets_) { debug_hashes.push_back(set->hash_); debug_sticker_set_ids.push_back(set->id_); StickerSetId set_id = on_get_sticker_set(std::move(set), false, "on_get_installed_sticker_sets"); if (!set_id.is_valid()) { continue; } auto sticker_set = get_sticker_set(set_id); CHECK(sticker_set != nullptr); LOG_IF(ERROR, !sticker_set->is_installed_) << "Receive non-installed sticker set in getAllStickers"; LOG_IF(ERROR, sticker_set->is_archived_) << "Receive archived sticker set in getAllStickers"; LOG_IF(ERROR, sticker_set->sticker_type_ != sticker_type) << "Receive sticker set of a wrong type in getAllStickers"; CHECK(sticker_set->is_inited_); if (sticker_set->is_installed_ && !sticker_set->is_archived_ && sticker_set->sticker_type_ == sticker_type) { installed_sticker_set_ids.push_back(set_id); uninstalled_sticker_sets.erase(set_id); } update_sticker_set(sticker_set, "on_get_installed_sticker_sets"); if (!sticker_set->is_archived_ && !sticker_set->is_loaded_) { sets_to_load.push_back(set_id); } } std::reverse(debug_hashes.begin(), debug_hashes.end()); std::reverse(installed_sticker_set_ids.begin(), installed_sticker_set_ids.end()); std::reverse(debug_sticker_set_ids.begin(), debug_sticker_set_ids.end()); if (!sets_to_load.empty()) { load_sticker_sets(std::move(sets_to_load), Auto()); } for (auto set_id : uninstalled_sticker_sets) { auto sticker_set = get_sticker_set(set_id); CHECK(sticker_set != nullptr); CHECK(sticker_set->is_installed_ && !sticker_set->is_archived_); on_update_sticker_set(sticker_set, false, false, true); update_sticker_set(sticker_set, "on_get_installed_sticker_sets 2"); } on_load_installed_sticker_sets_finished(sticker_type, std::move(installed_sticker_set_ids)); if (installed_sticker_sets_hash_[type] != stickers->hash_) { LOG(ERROR) << "Sticker sets hash mismatch: server hash list = " << debug_hashes << ", client hash list = " << transform(installed_sticker_set_ids_[type], [this](StickerSetId sticker_set_id) { return get_sticker_set(sticker_set_id)->hash_; }) << ", server sticker set list = " << debug_sticker_set_ids << ", client sticker set list = " << installed_sticker_set_ids_[type] << ", server hash = " << stickers->hash_ << ", client hash = " << installed_sticker_sets_hash_[type]; } } void StickersManager::on_get_installed_sticker_sets_failed(StickerType sticker_type, Status error) { CHECK(error.is_error()); auto type = static_cast(sticker_type); next_installed_sticker_sets_load_time_[type] = Time::now_cached() + Random::fast(5, 10); fail_promises(load_installed_sticker_sets_queries_[type], std::move(error)); } const std::map> &StickersManager::get_sticker_set_keywords(const StickerSet *sticker_set) { if (sticker_set->keyword_stickers_map_.empty()) { for (auto &sticker_id_keywords : sticker_set->sticker_keywords_map_) { for (auto &keyword : Hints::fix_words(transform(sticker_id_keywords.second, utf8_prepare_search_string))) { CHECK(!keyword.empty()); sticker_set->keyword_stickers_map_[keyword].push_back(sticker_id_keywords.first); } } } return sticker_set->keyword_stickers_map_; } void StickersManager::find_sticker_set_stickers(const StickerSet *sticker_set, const string &query, const string &prepared_query, vector &result) { auto it = sticker_set->emoji_stickers_map_.find(query); if (it != sticker_set->emoji_stickers_map_.end()) { LOG(INFO) << "Add " << it->second << " stickers from " << sticker_set->id_; append(result, it->second); } if (!prepared_query.empty()) { const auto &keywords_map = get_sticker_set_keywords(sticker_set); auto keywords_it = keywords_map.lower_bound(prepared_query); if (keywords_it != keywords_map.end() && begins_with(keywords_it->first, prepared_query)) { FlatHashSet found_sticker_ids; if (it != sticker_set->emoji_stickers_map_.end()) { for (auto file_id : it->second) { found_sticker_ids.insert(file_id); } } do { for (auto file_id : keywords_it->second) { if (found_sticker_ids.insert(file_id).second) { result.push_back(file_id); } } ++keywords_it; } while (keywords_it != keywords_map.end() && begins_with(keywords_it->first, prepared_query)); } } } bool StickersManager::can_found_sticker_by_query(FileId sticker_id, const string &query, const string &prepared_query) const { const Sticker *s = get_sticker(sticker_id); CHECK(s != nullptr); if (remove_emoji_modifiers(s->alt_) == query) { // fast path return true; } const StickerSet *sticker_set = get_sticker_set(s->set_id_); if (sticker_set == nullptr || !sticker_set->was_loaded_) { return false; } auto it = sticker_set->emoji_stickers_map_.find(query); if (it != sticker_set->emoji_stickers_map_.end()) { if (td::contains(it->second, sticker_id)) { return true; } } if (!prepared_query.empty()) { const auto &keywords_map = get_sticker_set_keywords(sticker_set); auto keywords_it = keywords_map.lower_bound(prepared_query); if (keywords_it != keywords_map.end() && begins_with(keywords_it->first, prepared_query)) { do { if (td::contains(keywords_it->second, sticker_id)) { return true; } ++keywords_it; } while (keywords_it != keywords_map.end() && begins_with(keywords_it->first, prepared_query)); } } return false; } std::pair, vector> StickersManager::split_stickers_by_premium( const vector &sticker_ids) const { CHECK(!td_->auth_manager_->is_bot()); vector regular_sticker_ids; vector premium_sticker_ids; for (const auto &sticker_id : sticker_ids) { if (sticker_id.is_valid()) { const Sticker *s = get_sticker(sticker_id); CHECK(s != nullptr); if (s->is_premium_) { premium_sticker_ids.push_back(sticker_id); } else { regular_sticker_ids.push_back(sticker_id); } } } return {std::move(regular_sticker_ids), std::move(premium_sticker_ids)}; } std::pair, vector> StickersManager::split_stickers_by_premium( const StickerSet *sticker_set) const { CHECK(!td_->auth_manager_->is_bot()); if (!sticker_set->was_loaded_) { return split_stickers_by_premium(sticker_set->sticker_ids_); } if (sticker_set->premium_sticker_positions_.empty()) { return {sticker_set->sticker_ids_, {}}; } vector regular_sticker_ids; vector premium_sticker_ids; size_t premium_pos = 0; for (size_t i = 0; i < sticker_set->sticker_ids_.size(); i++) { if (premium_pos < sticker_set->premium_sticker_positions_.size() && static_cast(sticker_set->premium_sticker_positions_[premium_pos]) == i) { premium_sticker_ids.push_back(sticker_set->sticker_ids_[i]); premium_pos++; } else { regular_sticker_ids.push_back(sticker_set->sticker_ids_[i]); } } CHECK(premium_pos == sticker_set->premium_sticker_positions_.size()); return {std::move(regular_sticker_ids), std::move(premium_sticker_ids)}; } vector StickersManager::get_stickers(StickerType sticker_type, string query, int32 limit, DialogId dialog_id, bool force, Promise &&promise) { if (G()->close_flag()) { promise.set_error(Global::request_aborted_error()); return {}; } if (limit <= 0) { promise.set_error(Status::Error(400, "Parameter limit must be positive")); return {}; } auto type = static_cast(sticker_type); if (!are_installed_sticker_sets_loaded_[type]) { CHECK(force == false); load_installed_sticker_sets( sticker_type, PromiseCreator::lambda([actor_id = actor_id(this), sticker_type, query = std::move(query), limit, dialog_id, force, promise = std::move(promise)](Result result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &StickersManager::get_stickers, sticker_type, std::move(query), limit, dialog_id, force, std::move(promise)); } })); return {}; } remove_emoji_modifiers_in_place(query); if (!query.empty()) { if (sticker_type == StickerType::Regular) { if (!are_recent_stickers_loaded_[0 /*is_attached*/]) { load_recent_stickers(false, std::move(promise)); return {}; } if (!are_favorite_stickers_loaded_) { load_favorite_stickers(std::move(promise)); return {}; } } else if (sticker_type == StickerType::CustomEmoji) { if (!are_featured_sticker_sets_loaded_[type]) { load_featured_sticker_sets(sticker_type, std::move(promise)); return {}; } } } vector examined_sticker_set_ids = installed_sticker_set_ids_[type]; if (!query.empty() && sticker_type == StickerType::CustomEmoji) { append(examined_sticker_set_ids, featured_sticker_set_ids_[type]); } vector sets_to_load; bool need_load = false; for (const auto &sticker_set_id : examined_sticker_set_ids) { const StickerSet *sticker_set = get_sticker_set(sticker_set_id); CHECK(sticker_set != nullptr); CHECK(sticker_set->is_inited_); if (!sticker_set->is_loaded_) { sets_to_load.push_back(sticker_set_id); if (!sticker_set->was_loaded_) { need_load = true; } } } vector prepend_sticker_ids; if (!query.empty() && sticker_type == StickerType::Regular) { prepend_sticker_ids.reserve(favorite_sticker_ids_.size() + recent_sticker_ids_[0].size()); append(prepend_sticker_ids, recent_sticker_ids_[0]); for (auto sticker_id : favorite_sticker_ids_) { if (!td::contains(prepend_sticker_ids, sticker_id)) { prepend_sticker_ids.push_back(sticker_id); } } auto prefer_animated = [this](FileId lhs, FileId rhs) { const Sticker *lhs_s = get_sticker(lhs); const Sticker *rhs_s = get_sticker(rhs); CHECK(lhs_s != nullptr && rhs_s != nullptr); return is_sticker_format_animated(lhs_s->format_) && !is_sticker_format_animated(rhs_s->format_); }; // std::stable_sort(prepend_sticker_ids.begin(), prepend_sticker_ids.begin() + recent_sticker_ids_[0].size(), // prefer_animated); std::stable_sort(prepend_sticker_ids.begin() + recent_sticker_ids_[0].size(), prepend_sticker_ids.end(), prefer_animated); LOG(INFO) << "Have " << recent_sticker_ids_[0] << " recent and " << favorite_sticker_ids_ << " favorite stickers"; for (const auto &sticker_id : prepend_sticker_ids) { const Sticker *s = get_sticker(sticker_id); CHECK(s != nullptr); LOG(INFO) << "Have prepend sticker " << sticker_id << " from " << s->set_id_; if (s->set_id_.is_valid() && !td::contains(sets_to_load, s->set_id_)) { const StickerSet *sticker_set = get_sticker_set(s->set_id_); if (sticker_set == nullptr || !sticker_set->is_loaded_) { sets_to_load.push_back(s->set_id_); if (sticker_set == nullptr || !sticker_set->was_loaded_) { need_load = true; } } } } } if (!sets_to_load.empty()) { if (need_load && !force) { load_sticker_sets(std::move(sets_to_load), PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { if (result.is_error() && result.error().message() != "STICKERSET_INVALID") { LOG(ERROR) << "Failed to load sticker sets: " << result.error(); } promise.set_value(Unit()); })); return {}; } else { load_sticker_sets(std::move(sets_to_load), Auto()); } } bool allow_premium = false; if (sticker_type == StickerType::CustomEmoji) { switch (dialog_id.get_type()) { case DialogType::User: if (dialog_id.get_user_id() == td_->contacts_manager_->get_my_id()) { allow_premium = true; } break; case DialogType::SecretChat: if (td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()) < static_cast(SecretChatLayer::SpoilerAndCustomEmojiEntities)) { promise.set_value(Unit()); return {}; } break; default: break; } } vector result; auto limit_size_t = static_cast(limit); if (query.empty()) { for (const auto &sticker_set_id : examined_sticker_set_ids) { const StickerSet *sticker_set = get_sticker_set(sticker_set_id); if (sticker_set == nullptr || !sticker_set->was_loaded_) { continue; } append(result, sticker_set->sticker_ids_); if (result.size() > limit_size_t) { result.resize(limit_size_t); break; } } } else { auto prepared_query = utf8_prepare_search_string(query); LOG(INFO) << "Search stickers by " << query << " and keyword " << prepared_query; vector examined_sticker_sets; for (const auto &sticker_set_id : examined_sticker_set_ids) { const StickerSet *sticker_set = get_sticker_set(sticker_set_id); if (sticker_set == nullptr || !sticker_set->was_loaded_) { continue; } if (!td::contains(examined_sticker_sets, sticker_set)) { examined_sticker_sets.push_back(sticker_set); } } std::stable_sort( examined_sticker_sets.begin(), examined_sticker_sets.end(), [](const StickerSet *lhs, const StickerSet *rhs) { if (lhs->is_installed_ != rhs->is_installed_) { return lhs->is_installed_; } if (lhs->is_archived_ != rhs->is_archived_) { return lhs->is_archived_; } return is_sticker_format_animated(lhs->sticker_format_) && !is_sticker_format_animated(rhs->sticker_format_); }); for (auto sticker_set : examined_sticker_sets) { find_sticker_set_stickers(sticker_set, query, prepared_query, result); } vector sorted; sorted.reserve(min(limit_size_t, result.size())); auto recent_stickers_size = recent_sticker_ids_[0].size(); const size_t MAX_RECENT_STICKERS = 5; for (size_t i = 0; i < prepend_sticker_ids.size(); i++) { if (sorted.size() == MAX_RECENT_STICKERS && i < recent_stickers_size) { LOG(INFO) << "Skip recent sticker " << prepend_sticker_ids[i]; continue; } auto sticker_id = prepend_sticker_ids[i]; bool is_good = false; auto it = std::find(result.begin(), result.end(), sticker_id); if (it != result.end()) { LOG(INFO) << "Found prepend sticker " << sticker_id << " in installed packs at position " << (it - result.begin()); *it = FileId(); is_good = true; } else if (can_found_sticker_by_query(sticker_id, query, prepared_query)) { LOG(INFO) << "Found prepend sticker " << sticker_id; is_good = true; } if (is_good) { sorted.push_back(sticker_id); if (sorted.size() == limit_size_t) { break; } } } if (sorted.size() != limit_size_t) { vector regular_sticker_ids; vector premium_sticker_ids; std::tie(regular_sticker_ids, premium_sticker_ids) = split_stickers_by_premium(result); if (td_->option_manager_->get_option_boolean("is_premium") || allow_premium) { auto normal_count = td_->option_manager_->get_option_integer("stickers_normal_by_emoji_per_premium_num", 2); if (normal_count < 0) { normal_count = 2; } if (normal_count > 10) { normal_count = 10; } // premium users have normal_count normal stickers per each premium size_t normal_pos = 0; size_t premium_pos = 0; normal_count++; for (size_t pos = 1; normal_pos < regular_sticker_ids.size() || premium_pos < premium_sticker_ids.size(); pos++) { if (pos % normal_count == 0 && premium_pos < premium_sticker_ids.size()) { auto sticker_id = premium_sticker_ids[premium_pos++]; LOG(INFO) << "Add premium sticker " << sticker_id << " from installed sticker set"; sorted.push_back(sticker_id); } else if (normal_pos < regular_sticker_ids.size()) { auto sticker_id = regular_sticker_ids[normal_pos++]; LOG(INFO) << "Add normal sticker " << sticker_id << " from installed sticker set"; sorted.push_back(sticker_id); } if (sorted.size() == limit_size_t) { break; } } } else { for (const auto &sticker_id : regular_sticker_ids) { LOG(INFO) << "Add normal sticker " << sticker_id << " from installed sticker set"; sorted.push_back(sticker_id); if (sorted.size() == limit_size_t) { break; } } if (sorted.size() < limit_size_t) { auto premium_count = td_->option_manager_->get_option_integer("stickers_premium_by_emoji_num", 0); if (premium_count > 0) { for (const auto &sticker_id : premium_sticker_ids) { LOG(INFO) << "Add premium sticker " << sticker_id << " from installed sticker set"; sorted.push_back(sticker_id); if (sorted.size() == limit_size_t || --premium_count == 0) { break; } } } } } } result = std::move(sorted); } promise.set_value(Unit()); return result; } string StickersManager::get_found_stickers_database_key(const string &emoji) { return PSTRING() << "found_stickers" << emoji; } void StickersManager::search_stickers(string emoji, int32 limit, Promise> &&promise) { if (limit == 0) { return promise.set_value(get_stickers_object({})); } if (limit < 0) { return promise.set_error(Status::Error(400, "Parameter limit must be positive")); } if (limit > MAX_FOUND_STICKERS) { limit = MAX_FOUND_STICKERS; } if (emoji.empty()) { return promise.set_error(Status::Error(400, "Emoji must be non-empty")); } remove_emoji_modifiers_in_place(emoji); if (emoji.empty()) { return promise.set_value(get_stickers_object({})); } auto it = found_stickers_.find(emoji); if (it != found_stickers_.end()) { const auto &sticker_ids = it->second.sticker_ids_; auto result_size = min(static_cast(limit), sticker_ids.size()); promise.set_value(get_stickers_object({sticker_ids.begin(), sticker_ids.begin() + result_size})); if (Time::now() < it->second.next_reload_time_) { return; } promise = Promise>(); limit = 0; } auto &promises = search_stickers_queries_[emoji]; promises.emplace_back(limit, std::move(promise)); if (promises.size() == 1u) { int64 hash = 0; if (it != found_stickers_.end()) { hash = get_recent_stickers_hash(it->second.sticker_ids_); td_->create_handler()->send(std::move(emoji), hash); return; } if (G()->parameters().use_file_db) { LOG(INFO) << "Trying to load stickers for " << emoji << " from database"; G()->td_db()->get_sqlite_pmc()->get( get_found_stickers_database_key(emoji), PromiseCreator::lambda([emoji](string value) mutable { send_closure(G()->stickers_manager(), &StickersManager::on_load_found_stickers_from_database, std::move(emoji), std::move(value)); })); } else { td_->create_handler()->send(std::move(emoji), 0); } } } void StickersManager::on_load_found_stickers_from_database(string emoji, string value) { if (G()->close_flag()) { return; } if (value.empty()) { LOG(INFO) << "Stickers for " << emoji << " aren't found in database"; td_->create_handler()->send(std::move(emoji), 0); return; } LOG(INFO) << "Successfully loaded stickers for " << emoji << " from database"; auto &found_stickers = found_stickers_[emoji]; CHECK(found_stickers.next_reload_time_ == 0); auto status = log_event_parse(found_stickers, value); if (status.is_error()) { LOG(ERROR) << "Can't load stickers for emoji: " << status << ' ' << format::as_hex_dump<4>(Slice(value)); found_stickers_.erase(emoji); td_->create_handler()->send(std::move(emoji), 0); return; } on_search_stickers_finished(emoji, found_stickers); } void StickersManager::on_search_stickers_finished(const string &emoji, const FoundStickers &found_stickers) { auto it = search_stickers_queries_.find(emoji); CHECK(it != search_stickers_queries_.end()); CHECK(!it->second.empty()); auto queries = std::move(it->second); search_stickers_queries_.erase(it); const auto &sticker_ids = found_stickers.sticker_ids_; for (auto &query : queries) { auto result_size = min(static_cast(query.first), sticker_ids.size()); query.second.set_value(get_stickers_object({sticker_ids.begin(), sticker_ids.begin() + result_size})); } } void StickersManager::on_find_stickers_success(const string &emoji, tl_object_ptr &&stickers) { CHECK(stickers != nullptr); switch (stickers->get_id()) { case telegram_api::messages_stickersNotModified::ID: { auto it = found_stickers_.find(emoji); if (it == found_stickers_.end()) { return on_find_stickers_fail(emoji, Status::Error(500, "Receive messages.stickerNotModified")); } auto &found_stickers = it->second; found_stickers.next_reload_time_ = Time::now() + found_stickers.cache_time_; return on_search_stickers_finished(emoji, found_stickers); } case telegram_api::messages_stickers::ID: { auto received_stickers = move_tl_object_as(stickers); auto &found_stickers = found_stickers_[emoji]; found_stickers.cache_time_ = 300; found_stickers.next_reload_time_ = Time::now() + found_stickers.cache_time_; found_stickers.sticker_ids_.clear(); for (auto &sticker : received_stickers->stickers_) { FileId sticker_id = on_get_sticker_document(std::move(sticker), StickerFormat::Unknown).second; if (sticker_id.is_valid()) { found_stickers.sticker_ids_.push_back(sticker_id); } } if (G()->parameters().use_file_db && !G()->close_flag()) { LOG(INFO) << "Save stickers for " << emoji << " to database"; G()->td_db()->get_sqlite_pmc()->set(get_found_stickers_database_key(emoji), log_event_store(found_stickers).as_slice().str(), Auto()); } return on_search_stickers_finished(emoji, found_stickers); } default: UNREACHABLE(); } } void StickersManager::on_find_stickers_fail(const string &emoji, Status &&error) { if (found_stickers_.count(emoji) != 0) { found_stickers_[emoji].cache_time_ = Random::fast(40, 80); return on_find_stickers_success(emoji, make_tl_object()); } auto it = search_stickers_queries_.find(emoji); CHECK(it != search_stickers_queries_.end()); CHECK(!it->second.empty()); auto queries = std::move(it->second); search_stickers_queries_.erase(it); for (auto &query : queries) { query.second.set_error(error.clone()); } } void StickersManager::get_premium_stickers(int32 limit, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); if (limit == 0) { return promise.set_value(get_stickers_object({})); } if (limit > MAX_FOUND_STICKERS) { limit = MAX_FOUND_STICKERS; } MultiPromiseActorSafe mpas{"GetPremiumStickersMultiPromiseActor"}; mpas.add_promise(PromiseCreator::lambda( [actor_id = actor_id(this), limit, promise = std::move(promise)](Result result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &StickersManager::do_get_premium_stickers, limit, std::move(promise)); } })); auto lock = mpas.get_promise(); search_stickers("📂⭐️", limit, PromiseCreator::lambda( [promise = mpas.get_promise()](Result> result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { promise.set_value(Unit()); } })); get_stickers(StickerType::Regular, string(), 1, DialogId(), false, mpas.get_promise()); lock.set_value(Unit()); } void StickersManager::do_get_premium_stickers(int32 limit, Promise> &&promise) { auto type = static_cast(StickerType::Regular); CHECK(are_installed_sticker_sets_loaded_[type]); vector sticker_ids; auto limit_size_t = static_cast(limit); for (const auto &sticker_set_id : installed_sticker_set_ids_[type]) { const StickerSet *sticker_set = get_sticker_set(sticker_set_id); if (sticker_set == nullptr || !sticker_set->was_loaded_) { continue; } for (auto premium_sticker_position : sticker_set->premium_sticker_positions_) { sticker_ids.push_back(sticker_set->sticker_ids_[premium_sticker_position]); if (sticker_ids.size() == limit_size_t) { return promise.set_value(get_stickers_object(sticker_ids)); } } } auto it = found_stickers_.find(remove_emoji_modifiers("📂⭐️")); CHECK(it != found_stickers_.end()); for (auto sticker_id : it->second.sticker_ids_) { if (td::contains(sticker_ids, sticker_id)) { continue; } sticker_ids.push_back(sticker_id); if (sticker_ids.size() == limit_size_t) { break; } } promise.set_value(get_stickers_object(sticker_ids)); } vector StickersManager::get_installed_sticker_sets(StickerType sticker_type, Promise &&promise) { auto type = static_cast(sticker_type); if (!are_installed_sticker_sets_loaded_[type]) { load_installed_sticker_sets(sticker_type, std::move(promise)); return {}; } reload_installed_sticker_sets(sticker_type, false); promise.set_value(Unit()); return installed_sticker_set_ids_[type]; } bool StickersManager::update_sticker_set_cache(const StickerSet *sticker_set, Promise &promise) { CHECK(sticker_set != nullptr); auto set_id = sticker_set->id_; if (!sticker_set->is_loaded_) { if (!sticker_set->was_loaded_ || td_->auth_manager_->is_bot()) { load_sticker_sets({set_id}, std::move(promise)); return true; } else { load_sticker_sets({set_id}, Auto()); } } else if (sticker_set->is_installed_) { reload_installed_sticker_sets(sticker_set->sticker_type_, false); } else { if (G()->unix_time() >= sticker_set->expires_at_) { if (td_->auth_manager_->is_bot()) { do_reload_sticker_set(set_id, get_input_sticker_set(sticker_set), sticker_set->hash_, std::move(promise), "update_sticker_set_cache"); return true; } else { do_reload_sticker_set(set_id, get_input_sticker_set(sticker_set), sticker_set->hash_, Auto(), "update_sticker_set_cache"); } } } return false; } StickerSetId StickersManager::get_sticker_set(StickerSetId set_id, Promise &&promise) { const StickerSet *sticker_set = get_sticker_set(set_id); if (sticker_set == nullptr) { if (set_id.get() == GREAT_MINDS_SET_ID) { do_reload_sticker_set(set_id, make_tl_object(set_id.get(), 0), 0, std::move(promise), "get_sticker_set"); return StickerSetId(); } promise.set_error(Status::Error(400, "Sticker set not found")); return StickerSetId(); } if (update_sticker_set_cache(sticker_set, promise)) { return StickerSetId(); } promise.set_value(Unit()); return set_id; } StickerSetId StickersManager::search_sticker_set(const string &short_name_to_search, Promise &&promise) { string short_name = clean_username(short_name_to_search); const StickerSet *sticker_set = get_sticker_set(short_name_to_sticker_set_id_.get(short_name)); if (sticker_set == nullptr) { auto set_to_load = make_tl_object(short_name); do_reload_sticker_set(StickerSetId(), std::move(set_to_load), 0, std::move(promise), "search_sticker_set"); return StickerSetId(); } if (update_sticker_set_cache(sticker_set, promise)) { return StickerSetId(); } promise.set_value(Unit()); return sticker_set->id_; } std::pair> StickersManager::search_installed_sticker_sets(StickerType sticker_type, const string &query, int32 limit, Promise &&promise) { LOG(INFO) << "Search installed " << sticker_type << " sticker sets with query = \"" << query << "\" and limit = " << limit; if (limit < 0) { promise.set_error(Status::Error(400, "Limit must be non-negative")); return {}; } auto type = static_cast(sticker_type); if (!are_installed_sticker_sets_loaded_[type]) { load_installed_sticker_sets(sticker_type, std::move(promise)); return {}; } reload_installed_sticker_sets(sticker_type, false); std::pair> result = installed_sticker_sets_hints_[type].search(query, limit); promise.set_value(Unit()); return {narrow_cast(result.first), convert_sticker_set_ids(result.second)}; } vector StickersManager::search_sticker_sets(const string &query, Promise &&promise) { auto q = clean_name(query, 1000); auto it = found_sticker_sets_.find(q); if (it != found_sticker_sets_.end()) { promise.set_value(Unit()); return it->second; } auto &promises = search_sticker_sets_queries_[q]; promises.push_back(std::move(promise)); if (promises.size() == 1u) { td_->create_handler()->send(std::move(q)); } return {}; } void StickersManager::on_find_sticker_sets_success( const string &query, tl_object_ptr &&sticker_sets) { CHECK(sticker_sets != nullptr); switch (sticker_sets->get_id()) { case telegram_api::messages_foundStickerSetsNotModified::ID: return on_find_sticker_sets_fail(query, Status::Error(500, "Receive messages.foundStickerSetsNotModified")); case telegram_api::messages_foundStickerSets::ID: { auto found_stickers_sets = move_tl_object_as(sticker_sets); vector &sticker_set_ids = found_sticker_sets_[query]; CHECK(sticker_set_ids.empty()); for (auto &sticker_set : found_stickers_sets->sets_) { StickerSetId set_id = on_get_sticker_set_covered(std::move(sticker_set), true, "on_find_sticker_sets_success"); if (!set_id.is_valid()) { continue; } update_sticker_set(get_sticker_set(set_id), "on_find_sticker_sets_success"); sticker_set_ids.push_back(set_id); } send_update_installed_sticker_sets(); break; } default: UNREACHABLE(); } auto it = search_sticker_sets_queries_.find(query); CHECK(it != search_sticker_sets_queries_.end()); CHECK(!it->second.empty()); auto promises = std::move(it->second); search_sticker_sets_queries_.erase(it); set_promises(promises); } void StickersManager::on_find_sticker_sets_fail(const string &query, Status &&error) { CHECK(found_sticker_sets_.count(query) == 0); auto it = search_sticker_sets_queries_.find(query); CHECK(it != search_sticker_sets_queries_.end()); CHECK(!it->second.empty()); auto promises = std::move(it->second); search_sticker_sets_queries_.erase(it); fail_promises(promises, std::move(error)); } void StickersManager::change_sticker_set(StickerSetId set_id, bool is_installed, bool is_archived, Promise &&promise) { if (is_installed && is_archived) { return promise.set_error(Status::Error(400, "Sticker set can't be installed and archived simultaneously")); } const StickerSet *sticker_set = get_sticker_set(set_id); if (sticker_set == nullptr) { return promise.set_error(Status::Error(400, "Sticker set not found")); } if (!sticker_set->is_inited_) { load_sticker_sets({set_id}, std::move(promise)); return; } auto type = static_cast(sticker_set->sticker_type_); if (!are_installed_sticker_sets_loaded_[type]) { load_installed_sticker_sets(sticker_set->sticker_type_, std::move(promise)); return; } if (is_archived) { is_installed = true; } if (is_installed) { if (sticker_set->is_installed_ && is_archived == sticker_set->is_archived_) { return promise.set_value(Unit()); } td_->create_handler(std::move(promise)) ->send(set_id, get_input_sticker_set(sticker_set), is_archived); return; } if (!sticker_set->is_installed_) { return promise.set_value(Unit()); } td_->create_handler(std::move(promise))->send(set_id, get_input_sticker_set(sticker_set)); } void StickersManager::on_update_sticker_set(StickerSet *sticker_set, bool is_installed, bool is_archived, bool is_changed, bool from_database) { LOG(INFO) << "Update " << sticker_set->id_ << ": installed = " << is_installed << ", archived = " << is_archived << ", changed = " << is_changed << ", from_database = " << from_database; CHECK(sticker_set->is_inited_); if (is_archived) { is_installed = true; } if (sticker_set->is_installed_ == is_installed && sticker_set->is_archived_ == is_archived) { return; } bool was_added = sticker_set->is_installed_ && !sticker_set->is_archived_; bool was_archived = sticker_set->is_archived_; sticker_set->is_installed_ = is_installed; sticker_set->is_archived_ = is_archived; if (!from_database) { sticker_set->is_changed_ = true; } bool is_added = sticker_set->is_installed_ && !sticker_set->is_archived_; auto type = static_cast(sticker_set->sticker_type_); if (was_added != is_added) { vector &sticker_set_ids = installed_sticker_set_ids_[type]; need_update_installed_sticker_sets_[type] = true; if (is_added) { installed_sticker_sets_hints_[type].add(sticker_set->id_.get(), PSLICE() << sticker_set->title_ << ' ' << sticker_set->short_name_); sticker_set_ids.insert(sticker_set_ids.begin(), sticker_set->id_); } else { installed_sticker_sets_hints_[type].remove(sticker_set->id_.get()); td::remove(sticker_set_ids, sticker_set->id_); } } if (was_archived != is_archived && is_changed) { int32 &total_count = total_archived_sticker_set_count_[type]; vector &sticker_set_ids = archived_sticker_set_ids_[type]; if (total_count < 0) { return; } if (is_archived) { if (!td::contains(sticker_set_ids, sticker_set->id_)) { total_count++; sticker_set_ids.insert(sticker_set_ids.begin(), sticker_set->id_); } } else { total_count--; if (total_count < 0) { LOG(ERROR) << "Total count of archived sticker sets became negative"; total_count = 0; } td::remove(sticker_set_ids, sticker_set->id_); } } } void StickersManager::load_installed_sticker_sets(StickerType sticker_type, Promise &&promise) { auto type = static_cast(sticker_type); if (td_->auth_manager_->is_bot()) { are_installed_sticker_sets_loaded_[type] = true; } if (are_installed_sticker_sets_loaded_[type]) { promise.set_value(Unit()); return; } load_installed_sticker_sets_queries_[type].push_back(std::move(promise)); if (load_installed_sticker_sets_queries_[type].size() == 1u) { if (G()->parameters().use_file_db) { LOG(INFO) << "Trying to load installed " << sticker_type << " sticker sets from database"; G()->td_db()->get_sqlite_pmc()->get( PSTRING() << "sss" << type, PromiseCreator::lambda([sticker_type](string value) { send_closure(G()->stickers_manager(), &StickersManager::on_load_installed_sticker_sets_from_database, sticker_type, std::move(value)); })); } else { LOG(INFO) << "Trying to load installed " << sticker_type << " sticker sets from server"; reload_installed_sticker_sets(sticker_type, true); } } } void StickersManager::on_load_installed_sticker_sets_from_database(StickerType sticker_type, string value) { if (G()->close_flag()) { return; } if (value.empty()) { LOG(INFO) << "Installed " << sticker_type << " sticker sets aren't found in database"; reload_installed_sticker_sets(sticker_type, true); return; } LOG(INFO) << "Successfully loaded installed " << sticker_type << " sticker set list of size " << value.size() << " from database"; StickerSetListLogEvent log_event; auto status = log_event_parse(log_event, value); if (status.is_error()) { // can't happen unless database is broken LOG(ERROR) << "Can't load installed sticker set list: " << status << ' ' << format::as_hex_dump<4>(Slice(value)); return reload_installed_sticker_sets(sticker_type, true); } CHECK(!log_event.is_premium_); vector sets_to_load; for (auto sticker_set_id : log_event.sticker_set_ids_) { StickerSet *sticker_set = get_sticker_set(sticker_set_id); CHECK(sticker_set != nullptr); if (!sticker_set->is_inited_) { sets_to_load.push_back(sticker_set_id); } } std::reverse(sets_to_load.begin(), sets_to_load.end()); // load installed sticker sets in reverse order load_sticker_sets_without_stickers( std::move(sets_to_load), PromiseCreator::lambda([sticker_type, sticker_set_ids = std::move(log_event.sticker_set_ids_)](Result result) mutable { if (result.is_ok()) { send_closure(G()->stickers_manager(), &StickersManager::on_load_installed_sticker_sets_finished, sticker_type, std::move(sticker_set_ids), true); } else { send_closure(G()->stickers_manager(), &StickersManager::reload_installed_sticker_sets, sticker_type, true); } })); } void StickersManager::on_load_installed_sticker_sets_finished(StickerType sticker_type, vector &&installed_sticker_set_ids, bool from_database) { bool need_reload = false; vector old_installed_sticker_set_ids; auto type = static_cast(sticker_type); if (!are_installed_sticker_sets_loaded_[type] && !installed_sticker_set_ids_[type].empty()) { old_installed_sticker_set_ids = std::move(installed_sticker_set_ids_[type]); } installed_sticker_set_ids_[type].clear(); for (auto set_id : installed_sticker_set_ids) { CHECK(set_id.is_valid()); auto sticker_set = get_sticker_set(set_id); CHECK(sticker_set != nullptr); CHECK(sticker_set->is_inited_); if (sticker_set->is_installed_ && !sticker_set->is_archived_ && sticker_set->sticker_type_ == sticker_type) { installed_sticker_set_ids_[type].push_back(set_id); } else { need_reload = true; } } if (need_reload) { LOG(ERROR) << "Reload installed " << sticker_type << " sticker sets, because only " << installed_sticker_set_ids_[type].size() << " of " << installed_sticker_set_ids.size() << " are really installed after loading from " << (from_database ? "database" : "server"); reload_installed_sticker_sets(sticker_type, true); } else if (!old_installed_sticker_set_ids.empty() && old_installed_sticker_set_ids != installed_sticker_set_ids_[type]) { LOG(ERROR) << "Reload installed " << sticker_type << " sticker sets, because they has changed from " << old_installed_sticker_set_ids << " to " << installed_sticker_set_ids_[type] << " after loading from " << (from_database ? "database" : "server"); reload_installed_sticker_sets(sticker_type, true); } are_installed_sticker_sets_loaded_[type] = true; need_update_installed_sticker_sets_[type] = true; send_update_installed_sticker_sets(from_database); set_promises(load_installed_sticker_sets_queries_[type]); } string StickersManager::get_sticker_set_database_key(StickerSetId set_id) { return PSTRING() << "ss" << set_id.get(); } string StickersManager::get_full_sticker_set_database_key(StickerSetId set_id) { return PSTRING() << "ssf" << set_id.get(); } string StickersManager::get_sticker_set_database_value(const StickerSet *s, bool with_stickers, const char *source) { LogEventStorerCalcLength storer_calc_length; store_sticker_set(s, with_stickers, storer_calc_length, source); BufferSlice value_buffer{storer_calc_length.get_length()}; auto value = value_buffer.as_slice(); LOG(DEBUG) << "Serialized size of " << s->id_ << " is " << value.size(); LogEventStorerUnsafe storer_unsafe(value.ubegin()); store_sticker_set(s, with_stickers, storer_unsafe, source); return value.str(); } void StickersManager::update_sticker_set(StickerSet *sticker_set, const char *source) { CHECK(sticker_set != nullptr); if (sticker_set->is_changed_ || sticker_set->need_save_to_database_) { if (G()->parameters().use_file_db && !G()->close_flag()) { LOG(INFO) << "Save " << sticker_set->id_ << " to database from " << source; if (sticker_set->is_inited_) { G()->td_db()->get_sqlite_pmc()->set(get_sticker_set_database_key(sticker_set->id_), get_sticker_set_database_value(sticker_set, false, source), Auto()); } if (sticker_set->was_loaded_) { G()->td_db()->get_sqlite_pmc()->set(get_full_sticker_set_database_key(sticker_set->id_), get_sticker_set_database_value(sticker_set, true, source), Auto()); } } if (sticker_set->is_changed_ && sticker_set->was_loaded_ && sticker_set->was_update_sent_) { send_closure(G()->td(), &Td::send_update, td_api::make_object(get_sticker_set_object(sticker_set->id_))); } sticker_set->is_changed_ = false; sticker_set->need_save_to_database_ = false; if (sticker_set->is_inited_) { update_load_requests(sticker_set, false, Status::OK()); } } } void StickersManager::load_sticker_sets(vector &&sticker_set_ids, Promise &&promise) { if (sticker_set_ids.empty()) { promise.set_value(Unit()); return; } CHECK(current_sticker_set_load_request_ < std::numeric_limits::max()); auto load_request_id = ++current_sticker_set_load_request_; StickerSetLoadRequest &load_request = sticker_set_load_requests_[load_request_id]; load_request.promise_ = std::move(promise); load_request.left_queries_ = sticker_set_ids.size(); for (auto sticker_set_id : sticker_set_ids) { StickerSet *sticker_set = get_sticker_set(sticker_set_id); CHECK(sticker_set != nullptr); CHECK(!sticker_set->is_loaded_); sticker_set->load_requests_.push_back(load_request_id); if (sticker_set->load_requests_.size() == 1u) { if (G()->parameters().use_file_db && !sticker_set->was_loaded_) { LOG(INFO) << "Trying to load " << sticker_set_id << " with stickers from database"; G()->td_db()->get_sqlite_pmc()->get( get_full_sticker_set_database_key(sticker_set_id), PromiseCreator::lambda([sticker_set_id](string value) { send_closure(G()->stickers_manager(), &StickersManager::on_load_sticker_set_from_database, sticker_set_id, true, std::move(value)); })); } else { LOG(INFO) << "Trying to load " << sticker_set_id << " with stickers from server"; do_reload_sticker_set(sticker_set_id, get_input_sticker_set(sticker_set), 0, Auto(), "load_sticker_sets"); } } } } void StickersManager::load_sticker_sets_without_stickers(vector &&sticker_set_ids, Promise &&promise) { if (sticker_set_ids.empty()) { promise.set_value(Unit()); return; } CHECK(current_sticker_set_load_request_ < std::numeric_limits::max()); auto load_request_id = ++current_sticker_set_load_request_; StickerSetLoadRequest &load_request = sticker_set_load_requests_[load_request_id]; load_request.promise_ = std::move(promise); load_request.left_queries_ = sticker_set_ids.size(); for (auto sticker_set_id : sticker_set_ids) { StickerSet *sticker_set = get_sticker_set(sticker_set_id); CHECK(sticker_set != nullptr); CHECK(!sticker_set->is_inited_); if (!sticker_set->load_requests_.empty()) { sticker_set->load_requests_.push_back(load_request_id); } else { sticker_set->load_without_stickers_requests_.push_back(load_request_id); if (sticker_set->load_without_stickers_requests_.size() == 1u) { if (G()->parameters().use_file_db) { LOG(INFO) << "Trying to load " << sticker_set_id << " from database"; G()->td_db()->get_sqlite_pmc()->get( get_sticker_set_database_key(sticker_set_id), PromiseCreator::lambda([sticker_set_id](string value) { send_closure(G()->stickers_manager(), &StickersManager::on_load_sticker_set_from_database, sticker_set_id, false, std::move(value)); })); } else { LOG(INFO) << "Trying to load " << sticker_set_id << " from server"; do_reload_sticker_set(sticker_set_id, get_input_sticker_set(sticker_set), 0, Auto(), "load_sticker_sets_without_stickers"); } } } } } void StickersManager::on_load_sticker_set_from_database(StickerSetId sticker_set_id, bool with_stickers, string value) { if (G()->close_flag()) { return; } StickerSet *sticker_set = get_sticker_set(sticker_set_id); CHECK(sticker_set != nullptr); if (sticker_set->was_loaded_) { LOG(INFO) << "Receive from database previously loaded " << sticker_set_id; return; } if (!with_stickers && sticker_set->is_inited_) { LOG(INFO) << "Receive from database previously inited " << sticker_set_id; return; } // it is possible that a server reload_sticker_set request has failed and cleared requests list with an error if (with_stickers) { // CHECK(!sticker_set->load_requests_.empty()); } else { // CHECK(!sticker_set->load_without_stickers_requests_.empty()); } if (value.empty()) { LOG(INFO) << "Failed to find in the database " << sticker_set_id; return do_reload_sticker_set(sticker_set_id, get_input_sticker_set(sticker_set), 0, Auto(), "on_load_sticker_set_from_database"); } LOG(INFO) << "Successfully loaded " << sticker_set_id << " with" << (with_stickers ? "" : "out") << " stickers of size " << value.size() << " from database"; auto old_sticker_count = sticker_set->sticker_ids_.size(); { LOG_IF(ERROR, sticker_set->is_changed_) << sticker_set_id << " with" << (with_stickers ? "" : "out") << " stickers was changed before it is loaded from database"; LogEventParser parser(value); parse_sticker_set(sticker_set, parser); LOG_IF(ERROR, sticker_set->is_changed_) << sticker_set_id << " with" << (with_stickers ? "" : "out") << " stickers is changed"; parser.fetch_end(); auto status = parser.get_status(); if (status.is_error()) { G()->td_db()->get_sqlite_sync_pmc()->erase(with_stickers ? get_full_sticker_set_database_key(sticker_set_id) : get_sticker_set_database_key(sticker_set_id)); // need to crash, because the current StickerSet state is spoiled by parse_sticker_set LOG(FATAL) << "Failed to parse " << sticker_set_id << ": " << status << ' ' << format::as_hex_dump<4>(Slice(value)); } } if (!sticker_set->is_sticker_has_text_color_loaded_ || !sticker_set->are_keywords_loaded_ || !sticker_set->is_thumbnail_reloaded_ || !sticker_set->are_legacy_sticker_thumbnails_reloaded_) { do_reload_sticker_set(sticker_set_id, get_input_sticker_set(sticker_set), 0, Auto(), "on_load_sticker_set_from_database 2"); } if (with_stickers && old_sticker_count < get_max_featured_sticker_count(sticker_set->sticker_type_) && old_sticker_count < sticker_set->sticker_ids_.size()) { sticker_set->need_save_to_database_ = true; update_sticker_set(sticker_set, "on_load_sticker_set_from_database"); } update_load_requests(sticker_set, with_stickers, Status::OK()); } void StickersManager::reload_sticker_set(StickerSetId sticker_set_id, int64 access_hash, Promise &&promise) { do_reload_sticker_set(sticker_set_id, make_tl_object(sticker_set_id.get(), access_hash), 0, std::move(promise), "reload_sticker_set"); } void StickersManager::do_reload_sticker_set(StickerSetId sticker_set_id, tl_object_ptr &&input_sticker_set, int32 hash, Promise &&promise, const char *source) { TRY_STATUS_PROMISE(promise, G()->close_status()); CHECK(input_sticker_set != nullptr); LOG(INFO) << "Reload " << sticker_set_id << " from " << source; if (sticker_set_id.is_valid() && input_sticker_set->get_id() == telegram_api::inputStickerSetID::ID) { auto &queries = sticker_set_reload_queries_[sticker_set_id]; if (queries == nullptr) { queries = make_unique(); } if (!queries->sent_promises_.empty()) { // query has already been sent, just wait for the result if (queries->sent_hash_ == 0 || hash == queries->sent_hash_) { LOG(INFO) << "Wait for result of the sent reload query"; queries->sent_promises_.push_back(std::move(promise)); } else { LOG(INFO) << "Postpone reload of " << sticker_set_id << ", because another query was sent"; if (queries->pending_promises_.empty()) { queries->pending_hash_ = hash; } else if (queries->pending_hash_ != hash) { queries->pending_hash_ = 0; } queries->pending_promises_.push_back(std::move(promise)); } return; } CHECK(queries->pending_promises_.empty()); queries->sent_promises_.push_back(std::move(promise)); queries->sent_hash_ = hash; promise = PromiseCreator::lambda([actor_id = actor_id(this), sticker_set_id](Result result) mutable { send_closure(actor_id, &StickersManager::on_reload_sticker_set, sticker_set_id, std::move(result)); }); } td_->create_handler(std::move(promise))->send(sticker_set_id, std::move(input_sticker_set), hash); } void StickersManager::on_reload_sticker_set(StickerSetId sticker_set_id, Result &&result) { if (G()->close_flag()) { result = Global::request_aborted_error(); } LOG(INFO) << "Reloaded " << sticker_set_id; auto it = sticker_set_reload_queries_.find(sticker_set_id); CHECK(it != sticker_set_reload_queries_.end()); auto queries = std::move(it->second); sticker_set_reload_queries_.erase(it); CHECK(queries != nullptr); CHECK(!queries->sent_promises_.empty()); if (result.is_error()) { fail_promises(queries->sent_promises_, result.error().clone()); fail_promises(queries->pending_promises_, result.move_as_error()); return; } set_promises(queries->sent_promises_); if (!queries->pending_promises_.empty()) { auto sticker_set = get_sticker_set(sticker_set_id); auto access_hash = sticker_set == nullptr ? 0 : sticker_set->access_hash_; auto promises = std::move(queries->pending_promises_); for (auto &promise : promises) { do_reload_sticker_set(sticker_set_id, make_tl_object(sticker_set_id.get(), access_hash), queries->pending_hash_, std::move(promise), "on_reload_sticker_set"); } } } void StickersManager::on_install_sticker_set(StickerSetId set_id, bool is_archived, tl_object_ptr &&result) { StickerSet *sticker_set = get_sticker_set(set_id); CHECK(sticker_set != nullptr); on_update_sticker_set(sticker_set, true, is_archived, true); update_sticker_set(sticker_set, "on_install_sticker_set"); switch (result->get_id()) { case telegram_api::messages_stickerSetInstallResultSuccess::ID: break; case telegram_api::messages_stickerSetInstallResultArchive::ID: { auto archived_sets = move_tl_object_as(result); for (auto &archived_set_ptr : archived_sets->sets_) { StickerSetId archived_sticker_set_id = on_get_sticker_set_covered(std::move(archived_set_ptr), true, "on_install_sticker_set"); if (archived_sticker_set_id.is_valid()) { auto archived_sticker_set = get_sticker_set(archived_sticker_set_id); CHECK(archived_sticker_set != nullptr); update_sticker_set(archived_sticker_set, "on_install_sticker_set 2"); } } break; } default: UNREACHABLE(); } send_update_installed_sticker_sets(); } void StickersManager::on_uninstall_sticker_set(StickerSetId set_id) { StickerSet *sticker_set = get_sticker_set(set_id); CHECK(sticker_set != nullptr); on_update_sticker_set(sticker_set, false, false, true); update_sticker_set(sticker_set, "on_uninstall_sticker_set"); send_update_installed_sticker_sets(); } td_api::object_ptr StickersManager::get_update_dice_emojis_object() const { return td_api::make_object(vector(dice_emojis_)); } void StickersManager::on_update_dice_emojis() { if (G()->close_flag()) { return; } if (td_->auth_manager_->is_bot()) { td_->option_manager_->set_option_empty("dice_emojis"); return; } if (!is_inited_) { return; } auto dice_emojis_str = td_->option_manager_->get_option_string("dice_emojis", "🎲\x01🎯\x01🏀\x01⚽\x01⚽️\x01🎰\x01🎳"); if (dice_emojis_str == dice_emojis_str_) { return; } dice_emojis_str_ = std::move(dice_emojis_str); auto new_dice_emojis = full_split(dice_emojis_str_, '\x01'); for (auto &emoji : new_dice_emojis) { if (!td::contains(dice_emojis_, emoji)) { auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_dice(emoji)); if (special_sticker_set.id_.is_valid()) { // drop information about the sticker set to reload it special_sticker_set.id_ = StickerSetId(); special_sticker_set.access_hash_ = 0; special_sticker_set.short_name_.clear(); } if (G()->parameters().use_file_db) { LOG(INFO) << "Load new dice sticker set for emoji " << emoji; load_special_sticker_set(special_sticker_set); } } } dice_emojis_ = std::move(new_dice_emojis); send_closure(G()->td(), &Td::send_update, get_update_dice_emojis_object()); } void StickersManager::on_update_dice_success_values() { if (G()->close_flag()) { return; } if (td_->auth_manager_->is_bot()) { td_->option_manager_->set_option_empty("dice_success_values"); return; } if (!is_inited_) { return; } auto dice_success_values_str = td_->option_manager_->get_option_string("dice_success_values", "0,6:62,5:110,5:110,5:110,64:110,6:110"); if (dice_success_values_str == dice_success_values_str_) { return; } LOG(INFO) << "Change dice success values to " << dice_success_values_str; dice_success_values_str_ = std::move(dice_success_values_str); dice_success_values_ = transform(full_split(dice_success_values_str_, ','), [](Slice value) { auto result = split(value, ':'); return std::make_pair(to_integer(result.first), to_integer(result.second)); }); } void StickersManager::on_update_emoji_sounds() { if (G()->close_flag() || !is_inited_ || td_->auth_manager_->is_bot()) { return; } auto emoji_sounds_str = td_->option_manager_->get_option_string("emoji_sounds"); if (emoji_sounds_str == emoji_sounds_str_) { return; } LOG(INFO) << "Change emoji sounds to " << emoji_sounds_str; emoji_sounds_str_ = std::move(emoji_sounds_str); vector old_file_ids; for (auto &emoji_sound : emoji_sounds_) { old_file_ids.push_back(emoji_sound.second); } emoji_sounds_.clear(); vector new_file_ids; auto sounds = full_split(Slice(emoji_sounds_str_), ','); CHECK(sounds.size() % 2 == 0); for (size_t i = 0; i < sounds.size(); i += 2) { vector parts = full_split(sounds[i + 1], ':'); CHECK(parts.size() == 3); auto id = to_integer(parts[0]); auto access_hash = to_integer(parts[1]); auto dc_id = G()->net_query_dispatcher().get_main_dc_id(); auto file_reference = base64url_decode(parts[2]).move_as_ok(); int32 expected_size = 7000; auto suggested_file_name = PSTRING() << static_cast(id) << '.' << MimeType::to_extension("audio/ogg", "oga"); auto file_id = td_->file_manager_->register_remote( FullRemoteFileLocation(FileType::VoiceNote, id, access_hash, dc_id, std::move(file_reference)), FileLocationSource::FromServer, DialogId(), 0, expected_size, std::move(suggested_file_name)); CHECK(file_id.is_valid()); auto cleaned_emoji = remove_fitzpatrick_modifier(sounds[i]).str(); if (!cleaned_emoji.empty()) { emoji_sounds_.emplace(cleaned_emoji, file_id); new_file_ids.push_back(file_id); } } td_->file_manager_->change_files_source(get_app_config_file_source_id(), old_file_ids, new_file_ids); try_update_animated_emoji_messages(); } void StickersManager::on_update_disable_animated_emojis() { if (G()->close_flag() || !is_inited_ || td_->auth_manager_->is_bot()) { return; } auto disable_animated_emojis = td_->option_manager_->get_option_boolean("disable_animated_emoji"); if (disable_animated_emojis == disable_animated_emojis_) { return; } disable_animated_emojis_ = disable_animated_emojis; if (!disable_animated_emojis_) { reload_special_sticker_set_by_type(SpecialStickerSetType::animated_emoji()); reload_special_sticker_set_by_type(SpecialStickerSetType::animated_emoji_click()); } try_update_animated_emoji_messages(); vector custom_emoji_ids; for (auto &it : custom_emoji_messages_) { custom_emoji_ids.push_back(it.first); } for (auto custom_emoji_id : custom_emoji_ids) { try_update_custom_emoji_messages(custom_emoji_id); } if (!disable_animated_emojis_) { for (size_t i = 0; i < custom_emoji_ids.size(); i += MAX_GET_CUSTOM_EMOJI_STICKERS) { auto end_i = td::min(i + MAX_GET_CUSTOM_EMOJI_STICKERS, custom_emoji_ids.size()); get_custom_emoji_stickers({custom_emoji_ids.begin() + i, custom_emoji_ids.begin() + end_i}, true, Auto()); } } } void StickersManager::on_update_sticker_sets(StickerType sticker_type) { auto type = static_cast(sticker_type); archived_sticker_set_ids_[type].clear(); total_archived_sticker_set_count_[type] = -1; reload_installed_sticker_sets(sticker_type, true); } void StickersManager::try_update_animated_emoji_messages() { auto sticker_set = get_animated_emoji_sticker_set(); vector full_message_ids; for (auto &it : emoji_messages_) { auto new_animated_sticker = get_animated_emoji_sticker(sticker_set, it.first); auto new_sound_file_id = get_animated_emoji_sound_file_id(it.first); if (new_animated_sticker != it.second->animated_emoji_sticker_ || (new_animated_sticker.first.is_valid() && new_sound_file_id != it.second->sound_file_id_)) { it.second->animated_emoji_sticker_ = new_animated_sticker; it.second->sound_file_id_ = new_sound_file_id; it.second->full_message_ids_.foreach( [&](const FullMessageId &full_message_id) { full_message_ids.push_back(full_message_id); }); } } for (const auto &full_message_id : full_message_ids) { td_->messages_manager_->on_external_update_message_content(full_message_id); } } void StickersManager::try_update_custom_emoji_messages(CustomEmojiId custom_emoji_id) { auto it = custom_emoji_messages_.find(custom_emoji_id); if (it == custom_emoji_messages_.end()) { return; } vector full_message_ids; auto new_sticker_id = get_custom_animated_emoji_sticker_id(custom_emoji_id); if (new_sticker_id != it->second->sticker_id_) { it->second->sticker_id_ = new_sticker_id; it->second->full_message_ids_.foreach( [&](const FullMessageId &full_message_id) { full_message_ids.push_back(full_message_id); }); } for (const auto &full_message_id : full_message_ids) { td_->messages_manager_->on_external_update_message_content(full_message_id); } } void StickersManager::try_update_premium_gift_messages() { auto sticker_set = get_premium_gift_sticker_set(); vector full_message_ids; for (auto &it : premium_gift_messages_) { auto new_sticker_id = get_premium_gift_option_sticker_id(sticker_set, it.first); if (new_sticker_id != it.second->sticker_id_) { it.second->sticker_id_ = new_sticker_id; for (const auto &full_message_id : it.second->full_message_ids_) { full_message_ids.push_back(full_message_id); } } } for (const auto &full_message_id : full_message_ids) { td_->messages_manager_->on_external_update_message_content(full_message_id); } } void StickersManager::register_premium_gift(int32 months, FullMessageId full_message_id, const char *source) { if (td_->auth_manager_->is_bot() || months == 0) { return; } LOG(INFO) << "Register premium gift for " << months << " months from " << full_message_id << " from " << source; auto &premium_gift_messages_ptr = premium_gift_messages_[months]; if (premium_gift_messages_ptr == nullptr) { premium_gift_messages_ptr = make_unique(); } auto &premium_gift_messages = *premium_gift_messages_ptr; if (premium_gift_messages.full_message_ids_.empty()) { premium_gift_messages.sticker_id_ = get_premium_gift_option_sticker_id(months); } bool is_inserted = premium_gift_messages.full_message_ids_.insert(full_message_id).second; LOG_CHECK(is_inserted) << source << " " << months << " " << full_message_id; } void StickersManager::unregister_premium_gift(int32 months, FullMessageId full_message_id, const char *source) { if (td_->auth_manager_->is_bot() || months == 0) { return; } LOG(INFO) << "Unregister premium gift for " << months << " months from " << full_message_id << " from " << source; auto it = premium_gift_messages_.find(months); CHECK(it != premium_gift_messages_.end()); auto &message_ids = it->second->full_message_ids_; auto is_deleted = message_ids.erase(full_message_id) > 0; LOG_CHECK(is_deleted) << source << " " << months << " " << full_message_id; if (message_ids.empty()) { premium_gift_messages_.erase(it); } } void StickersManager::register_dice(const string &emoji, int32 value, FullMessageId full_message_id, const char *source) { CHECK(!emoji.empty()); if (td_->auth_manager_->is_bot()) { return; } LOG(INFO) << "Register dice " << emoji << " with value " << value << " from " << full_message_id << " from " << source; dice_messages_[emoji].insert(full_message_id); if (!td::contains(dice_emojis_, emoji)) { if (full_message_id.get_message_id().is_any_server() && full_message_id.get_dialog_id().get_type() != DialogType::SecretChat) { send_closure(G()->config_manager(), &ConfigManager::reget_app_config, Promise()); } return; } auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_dice(emoji)); bool need_load = false; StickerSet *sticker_set = nullptr; if (!special_sticker_set.id_.is_valid()) { need_load = true; } else { sticker_set = get_sticker_set(special_sticker_set.id_); CHECK(sticker_set != nullptr); need_load = !sticker_set->was_loaded_; } if (need_load) { LOG(INFO) << "Waiting for a dice sticker set needed in " << full_message_id; load_special_sticker_set(special_sticker_set); } else { // TODO reload once in a while // reload_special_sticker_set(special_sticker_set, sticker_set->is_loaded_ ? sticker_set->hash_ : 0); } } void StickersManager::unregister_dice(const string &emoji, int32 value, FullMessageId full_message_id, const char *source) { CHECK(!emoji.empty()); if (td_->auth_manager_->is_bot()) { return; } LOG(INFO) << "Unregister dice " << emoji << " with value " << value << " from " << full_message_id << " from " << source; auto &message_ids = dice_messages_[emoji]; auto is_deleted = message_ids.erase(full_message_id) > 0; LOG_CHECK(is_deleted) << source << " " << emoji << " " << value << " " << full_message_id; if (message_ids.empty()) { dice_messages_.erase(emoji); } } void StickersManager::register_emoji(const string &emoji, CustomEmojiId custom_emoji_id, FullMessageId full_message_id, const char *source) { CHECK(!emoji.empty()); if (td_->auth_manager_->is_bot()) { return; } LOG(INFO) << "Register emoji " << emoji << " with " << custom_emoji_id << " from " << full_message_id << " from " << source; if (custom_emoji_id.is_valid()) { auto &emoji_messages_ptr = custom_emoji_messages_[custom_emoji_id]; if (emoji_messages_ptr == nullptr) { emoji_messages_ptr = make_unique(); } auto &emoji_messages = *emoji_messages_ptr; if (emoji_messages.full_message_ids_.empty()) { if (!disable_animated_emojis_ && custom_emoji_to_sticker_id_.count(custom_emoji_id) == 0) { load_custom_emoji_sticker_from_database_force(custom_emoji_id); if (custom_emoji_to_sticker_id_.count(custom_emoji_id) == 0) { get_custom_emoji_stickers({custom_emoji_id}, false, Promise>()); } } emoji_messages.sticker_id_ = get_custom_animated_emoji_sticker_id(custom_emoji_id); } emoji_messages.full_message_ids_.insert(full_message_id); return; } auto &emoji_messages_ptr = emoji_messages_[emoji]; if (emoji_messages_ptr == nullptr) { emoji_messages_ptr = make_unique(); } auto &emoji_messages = *emoji_messages_ptr; if (emoji_messages.full_message_ids_.empty()) { emoji_messages.animated_emoji_sticker_ = get_animated_emoji_sticker(emoji); emoji_messages.sound_file_id_ = get_animated_emoji_sound_file_id(emoji); } emoji_messages.full_message_ids_.insert(full_message_id); } void StickersManager::unregister_emoji(const string &emoji, CustomEmojiId custom_emoji_id, FullMessageId full_message_id, const char *source) { CHECK(!emoji.empty()); if (td_->auth_manager_->is_bot()) { return; } LOG(INFO) << "Unregister emoji " << emoji << " with " << custom_emoji_id << " from " << full_message_id << " from " << source; if (custom_emoji_id.is_valid()) { auto it = custom_emoji_messages_.find(custom_emoji_id); CHECK(it != custom_emoji_messages_.end()); auto &full_message_ids = it->second->full_message_ids_; auto is_deleted = full_message_ids.erase(full_message_id) > 0; LOG_CHECK(is_deleted) << source << ' ' << custom_emoji_id << ' ' << full_message_id; if (full_message_ids.empty()) { custom_emoji_messages_.erase(it); } return; } auto it = emoji_messages_.find(emoji); CHECK(it != emoji_messages_.end()); auto &full_message_ids = it->second->full_message_ids_; auto is_deleted = full_message_ids.erase(full_message_id) > 0; LOG_CHECK(is_deleted) << source << ' ' << emoji << ' ' << full_message_id; if (full_message_ids.empty()) { emoji_messages_.erase(it); } } void StickersManager::get_animated_emoji(string emoji, bool is_recursive, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_emoji()); auto sticker_set = get_sticker_set(special_sticker_set.id_); if (sticker_set == nullptr || !sticker_set->was_loaded_) { if (is_recursive) { return promise.set_value(nullptr); } pending_get_animated_emoji_queries_.push_back( PromiseCreator::lambda([actor_id = actor_id(this), emoji = std::move(emoji), promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &StickersManager::get_animated_emoji, std::move(emoji), true, std::move(promise)); } })); load_special_sticker_set(special_sticker_set); return; } promise.set_value(get_animated_emoji_object(get_animated_emoji_sticker(sticker_set, emoji), get_animated_emoji_sound_file_id(emoji))); } void StickersManager::get_all_animated_emojis(bool is_recursive, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_emoji()); auto sticker_set = get_sticker_set(special_sticker_set.id_); if (sticker_set == nullptr || !sticker_set->was_loaded_) { if (is_recursive) { return promise.set_value(td_api::make_object()); } pending_get_animated_emoji_queries_.push_back(PromiseCreator::lambda( [actor_id = actor_id(this), promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &StickersManager::get_all_animated_emojis, true, std::move(promise)); } })); load_special_sticker_set(special_sticker_set); return; } auto emojis = transform(sticker_set->sticker_ids_, [&](FileId sticker_id) { auto s = get_sticker(sticker_id); CHECK(s != nullptr); return s->alt_; }); promise.set_value(td_api::make_object(std::move(emojis))); } void StickersManager::get_custom_emoji_reaction_generic_animations( bool is_recursive, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::generic_animations()); auto sticker_set = get_sticker_set(special_sticker_set.id_); if (sticker_set == nullptr || !sticker_set->was_loaded_) { if (is_recursive) { return promise.set_value(td_api::make_object()); } pending_get_generic_animations_queries_.push_back(PromiseCreator::lambda( [actor_id = actor_id(this), promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &StickersManager::get_custom_emoji_reaction_generic_animations, true, std::move(promise)); } })); load_special_sticker_set(special_sticker_set); return; } promise.set_value(get_stickers_object(sticker_set->sticker_ids_)); } void StickersManager::get_default_emoji_statuses(bool is_recursive, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::default_statuses()); auto sticker_set = get_sticker_set(special_sticker_set.id_); if (sticker_set == nullptr || !sticker_set->was_loaded_) { if (is_recursive) { return promise.set_value(td_api::make_object()); } pending_get_default_statuses_queries_.push_back(PromiseCreator::lambda( [actor_id = actor_id(this), promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &StickersManager::get_default_emoji_statuses, true, std::move(promise)); } })); load_special_sticker_set(special_sticker_set); return; } vector> statuses; for (auto sticker_id : sticker_set->sticker_ids_) { auto custom_emoji_id = get_custom_emoji_id(sticker_id); if (!custom_emoji_id.is_valid()) { LOG(ERROR) << "Ignore wrong sticker " << sticker_id; continue; } statuses.push_back(td_api::make_object(custom_emoji_id.get())); if (statuses.size() >= 8) { break; } } promise.set_value(td_api::make_object(std::move(statuses))); } bool StickersManager::is_default_emoji_status(CustomEmojiId custom_emoji_id) { auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::default_statuses()); auto sticker_set = get_sticker_set(special_sticker_set.id_); if (sticker_set == nullptr || !sticker_set->was_loaded_) { return false; } for (auto sticker_id : sticker_set->sticker_ids_) { if (get_custom_emoji_id(sticker_id) == custom_emoji_id) { return true; } } return false; } void StickersManager::get_default_topic_icons(bool is_recursive, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::default_topic_icons()); auto sticker_set = get_sticker_set(special_sticker_set.id_); if (sticker_set == nullptr || !sticker_set->was_loaded_) { if (is_recursive) { return promise.set_value(td_api::make_object()); } pending_get_default_topic_icons_queries_.push_back(PromiseCreator::lambda( [actor_id = actor_id(this), promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &StickersManager::get_default_topic_icons, true, std::move(promise)); } })); load_special_sticker_set(special_sticker_set); return; } if (!is_recursive && td_->auth_manager_->is_bot() && G()->unix_time() >= sticker_set->expires_at_) { auto reload_promise = PromiseCreator::lambda( [actor_id = actor_id(this), promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &StickersManager::get_default_topic_icons, true, std::move(promise)); } }); do_reload_sticker_set(sticker_set->id_, get_input_sticker_set(sticker_set), sticker_set->hash_, std::move(reload_promise), "get_default_topic_icons"); return; } promise.set_value(get_stickers_object(sticker_set->sticker_ids_)); } void StickersManager::load_custom_emoji_sticker_from_database_force(CustomEmojiId custom_emoji_id) { if (!G()->parameters().use_file_db) { return; } auto value = G()->td_db()->get_sqlite_sync_pmc()->get(get_custom_emoji_database_key(custom_emoji_id)); if (value.empty()) { LOG(INFO) << "Failed to load " << custom_emoji_id << " from database"; return; } LOG(INFO) << "Synchronously loaded " << custom_emoji_id << " of size " << value.size() << " from database"; CustomEmojiLogEvent log_event; if (log_event_parse(log_event, value).is_error()) { LOG(ERROR) << "Delete invalid " << custom_emoji_id << " value from database"; G()->td_db()->get_sqlite_sync_pmc()->erase(get_custom_emoji_database_key(custom_emoji_id)); } } void StickersManager::load_custom_emoji_sticker_from_database(CustomEmojiId custom_emoji_id, Promise &&promise) { CHECK(custom_emoji_id.is_valid()); auto &queries = custom_emoji_load_queries_[custom_emoji_id]; queries.push_back(std::move(promise)); if (queries.size() == 1) { LOG(INFO) << "Trying to load " << custom_emoji_id << " from database"; G()->td_db()->get_sqlite_pmc()->get( get_custom_emoji_database_key(custom_emoji_id), PromiseCreator::lambda([custom_emoji_id](string value) { send_closure(G()->stickers_manager(), &StickersManager::on_load_custom_emoji_from_database, custom_emoji_id, std::move(value)); })); } } void StickersManager::on_load_custom_emoji_from_database(CustomEmojiId custom_emoji_id, string value) { if (G()->close_flag()) { return; } if (!value.empty()) { LOG(INFO) << "Successfully loaded " << custom_emoji_id << " of size " << value.size() << " from database"; CustomEmojiLogEvent log_event; if (log_event_parse(log_event, value).is_error()) { LOG(ERROR) << "Delete invalid " << custom_emoji_id << " value from database"; G()->td_db()->get_sqlite_pmc()->erase(get_custom_emoji_database_key(custom_emoji_id), Auto()); } } else { LOG(INFO) << "Failed to load " << custom_emoji_id << " from database"; } auto it = custom_emoji_load_queries_.find(custom_emoji_id); CHECK(it != custom_emoji_load_queries_.end()); CHECK(!it->second.empty()); auto promises = std::move(it->second); custom_emoji_load_queries_.erase(it); set_promises(promises); } td_api::object_ptr StickersManager::get_custom_emoji_stickers_object( const vector &custom_emoji_ids) { vector> stickers; auto update_before_date = G()->unix_time() - 86400; vector reload_custom_emoji_ids; for (auto custom_emoji_id : custom_emoji_ids) { auto file_id = custom_emoji_to_sticker_id_.get(custom_emoji_id); if (file_id.is_valid()) { auto s = get_sticker(file_id); CHECK(s != nullptr); CHECK(s->type_ == StickerType::CustomEmoji); if (s->emoji_receive_date_ < update_before_date && !s->is_being_reloaded_) { s->is_being_reloaded_ = true; reload_custom_emoji_ids.push_back(custom_emoji_id); } auto sticker = get_sticker_object(file_id); CHECK(sticker != nullptr); stickers.push_back(std::move(sticker)); } } if (!reload_custom_emoji_ids.empty()) { LOG(INFO) << "Reload " << reload_custom_emoji_ids; auto promise = PromiseCreator::lambda( [actor_id = actor_id(this)](Result>> r_documents) mutable { send_closure(actor_id, &StickersManager::on_get_custom_emoji_documents, std::move(r_documents), vector(), Promise>()); }); td_->create_handler(std::move(promise))->send(std::move(reload_custom_emoji_ids)); } return td_api::make_object(std::move(stickers)); } void StickersManager::get_custom_emoji_stickers(vector &&custom_emoji_ids, bool use_database, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); if (custom_emoji_ids.size() > MAX_GET_CUSTOM_EMOJI_STICKERS) { return promise.set_error(Status::Error(400, "Too many custom emoji identifiers specified")); } FlatHashSet unique_custom_emoji_ids; size_t j = 0; for (size_t i = 0; i < custom_emoji_ids.size(); i++) { auto custom_emoji_id = custom_emoji_ids[i]; if (custom_emoji_id.is_valid() && unique_custom_emoji_ids.insert(custom_emoji_id).second) { custom_emoji_ids[j++] = custom_emoji_id; } } custom_emoji_ids.resize(j); vector unknown_custom_emoji_ids; for (auto custom_emoji_id : custom_emoji_ids) { if (custom_emoji_to_sticker_id_.count(custom_emoji_id) == 0) { unknown_custom_emoji_ids.push_back(custom_emoji_id); } } if (unknown_custom_emoji_ids.empty()) { return promise.set_value(get_custom_emoji_stickers_object(custom_emoji_ids)); } if (use_database && G()->parameters().use_file_db) { MultiPromiseActorSafe mpas{"LoadCustomEmojiMultiPromiseActor"}; mpas.add_promise(PromiseCreator::lambda( [actor_id = actor_id(this), custom_emoji_ids, promise = std::move(promise)](Unit) mutable { send_closure(actor_id, &StickersManager::get_custom_emoji_stickers, std::move(custom_emoji_ids), false, std::move(promise)); })); auto lock = mpas.get_promise(); for (auto custom_emoji_id : unknown_custom_emoji_ids) { load_custom_emoji_sticker_from_database(custom_emoji_id, mpas.get_promise()); } return lock.set_value(Unit()); } auto query_promise = PromiseCreator::lambda( [actor_id = actor_id(this), custom_emoji_ids = std::move(custom_emoji_ids), promise = std::move(promise)]( Result>> r_documents) mutable { send_closure(actor_id, &StickersManager::on_get_custom_emoji_documents, std::move(r_documents), std::move(custom_emoji_ids), std::move(promise)); }); td_->create_handler(std::move(query_promise)) ->send(std::move(unknown_custom_emoji_ids)); } void StickersManager::on_get_custom_emoji_documents( Result>> &&r_documents, vector &&custom_emoji_ids, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); if (r_documents.is_error()) { return promise.set_error(r_documents.move_as_error()); } auto documents = r_documents.move_as_ok(); for (auto &document : documents) { LOG(INFO) << "Receive " << to_string(document); if (document->get_id() == telegram_api::documentEmpty::ID) { continue; } on_get_sticker_document(std::move(document), StickerFormat::Unknown); } promise.set_value(get_custom_emoji_stickers_object(custom_emoji_ids)); } void StickersManager::get_premium_gift_option_sticker(int32 month_count, bool is_recursive, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::premium_gifts()); auto sticker_set = get_sticker_set(special_sticker_set.id_); if (sticker_set == nullptr || !sticker_set->was_loaded_) { if (is_recursive) { return promise.set_value(nullptr); } pending_get_premium_gift_option_sticker_queries_.push_back(PromiseCreator::lambda( [actor_id = actor_id(this), month_count, promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &StickersManager::get_premium_gift_option_sticker, month_count, true, std::move(promise)); } })); load_special_sticker_set(special_sticker_set); return; } promise.set_value(get_sticker_object(get_premium_gift_option_sticker_id(sticker_set, month_count))); } void StickersManager::get_animated_emoji_click_sticker(const string &message_text, FullMessageId full_message_id, Promise> &&promise) { if (disable_animated_emojis_ || td_->auth_manager_->is_bot()) { return promise.set_value(nullptr); } auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_emoji_click()); if (!special_sticker_set.id_.is_valid()) { // don't wait for the first load of the sticker set from the server load_special_sticker_set(special_sticker_set); return promise.set_value(nullptr); } auto sticker_set = get_sticker_set(special_sticker_set.id_); CHECK(sticker_set != nullptr); if (sticker_set->was_loaded_) { return choose_animated_emoji_click_sticker(sticker_set, message_text, full_message_id, Time::now(), std::move(promise)); } LOG(INFO) << "Waiting for an emoji click sticker set needed in " << full_message_id; load_special_sticker_set(special_sticker_set); PendingGetAnimatedEmojiClickSticker pending_request; pending_request.message_text_ = message_text; pending_request.full_message_id_ = full_message_id; pending_request.start_time_ = Time::now(); pending_request.promise_ = std::move(promise); pending_get_animated_emoji_click_stickers_.push_back(std::move(pending_request)); } int StickersManager::get_emoji_number(Slice emoji) { // '0'-'9' + U+20E3 auto data = emoji.ubegin(); if (emoji.size() != 4 || emoji[0] < '0' || emoji[0] > '9' || data[1] != 0xE2 || data[2] != 0x83 || data[3] != 0xA3) { return -1; } return emoji[0] - '0'; } vector StickersManager::get_animated_emoji_click_stickers(const StickerSet *sticker_set, Slice emoji) const { vector result; for (auto sticker_id : sticker_set->sticker_ids_) { auto s = get_sticker(sticker_id); CHECK(s != nullptr); if (remove_emoji_modifiers(s->alt_) == emoji) { result.push_back(sticker_id); } } if (result.empty()) { const static vector heart_emojis{"💛", "💙", "💚", "💜", "🧡", "🖤", "🤎", "🤍"}; if (td::contains(heart_emojis, emoji)) { return get_animated_emoji_click_stickers(sticker_set, Slice("❤")); } } return result; } void StickersManager::choose_animated_emoji_click_sticker(const StickerSet *sticker_set, string message_text, FullMessageId full_message_id, double start_time, Promise> &&promise) { CHECK(sticker_set->was_loaded_); remove_emoji_modifiers_in_place(message_text); if (message_text.empty()) { return promise.set_error(Status::Error(400, "Message is not an animated emoji message")); } if (disable_animated_emojis_ || td_->auth_manager_->is_bot()) { return promise.set_value(nullptr); } auto now = Time::now(); if (last_clicked_animated_emoji_ == message_text && last_clicked_animated_emoji_full_message_id_ == full_message_id && next_click_animated_emoji_message_time_ >= now + 2 * MIN_ANIMATED_EMOJI_CLICK_DELAY) { return promise.set_value(nullptr); } auto all_sticker_ids = get_animated_emoji_click_stickers(sticker_set, message_text); vector> found_stickers; for (auto sticker_id : all_sticker_ids) { auto it = sticker_set->sticker_emojis_map_.find(sticker_id); if (it != sticker_set->sticker_emojis_map_.end()) { for (auto &emoji : it->second) { auto number = get_emoji_number(emoji); if (number > 0) { found_stickers.emplace_back(number, sticker_id); } } } } if (found_stickers.empty()) { LOG(INFO) << "There is no click effect for " << message_text << " from " << full_message_id; return promise.set_value(nullptr); } if (last_clicked_animated_emoji_full_message_id_ != full_message_id) { flush_pending_animated_emoji_clicks(); last_clicked_animated_emoji_full_message_id_ = full_message_id; } if (last_clicked_animated_emoji_ != message_text) { pending_animated_emoji_clicks_.clear(); last_clicked_animated_emoji_ = std::move(message_text); } if (!pending_animated_emoji_clicks_.empty() && found_stickers.size() >= 2) { for (auto it = found_stickers.begin(); it != found_stickers.end(); ++it) { if (it->first == pending_animated_emoji_clicks_.back().first) { found_stickers.erase(it); break; } } } CHECK(!found_stickers.empty()); auto result = found_stickers[Random::fast(0, narrow_cast(found_stickers.size()) - 1)]; pending_animated_emoji_clicks_.emplace_back(result.first, start_time); if (pending_animated_emoji_clicks_.size() == 5) { flush_pending_animated_emoji_clicks(); } else { set_timeout_in(0.5); } if (now >= next_click_animated_emoji_message_time_) { next_click_animated_emoji_message_time_ = now + MIN_ANIMATED_EMOJI_CLICK_DELAY; promise.set_value(get_sticker_object(result.second, false, true)); } else { create_actor("SendClickAnimatedEmojiMessageResponse", next_click_animated_emoji_message_time_ - now, PromiseCreator::lambda([actor_id = actor_id(this), sticker_id = result.second, promise = std::move(promise)](Result result) mutable { send_closure(actor_id, &StickersManager::send_click_animated_emoji_message_response, sticker_id, std::move(promise)); })) .release(); next_click_animated_emoji_message_time_ += MIN_ANIMATED_EMOJI_CLICK_DELAY; } } void StickersManager::send_click_animated_emoji_message_response( FileId sticker_id, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); promise.set_value(get_sticker_object(sticker_id, false, true)); } void StickersManager::timeout_expired() { flush_pending_animated_emoji_clicks(); } void StickersManager::flush_pending_animated_emoji_clicks() { if (G()->close_flag()) { return; } if (pending_animated_emoji_clicks_.empty()) { return; } auto clicks = std::move(pending_animated_emoji_clicks_); pending_animated_emoji_clicks_.clear(); auto full_message_id = last_clicked_animated_emoji_full_message_id_; last_clicked_animated_emoji_full_message_id_ = FullMessageId(); auto emoji = std::move(last_clicked_animated_emoji_); last_clicked_animated_emoji_.clear(); if (td_->messages_manager_->is_message_edited_recently(full_message_id, 1)) { // includes deleted full_message_id return; } auto dialog_id = full_message_id.get_dialog_id(); auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { return; } double start_time = clicks[0].second; auto data = json_encode(json_object([&clicks, start_time](auto &o) { o("v", 1); o("a", json_array(clicks, [start_time](auto &click) { return json_object([&click, start_time](auto &o) { o("i", click.first); auto t = static_cast((click.second - start_time) * 100); o("t", JsonRaw(PSLICE() << (t / 100) << '.' << (t < 10 ? "0" : "") << (t % 100))); }); })); })); td_->create_handler()->send( dialog_id, std::move(input_peer), make_tl_object( emoji, full_message_id.get_message_id().get_server_message_id().get(), make_tl_object(data))); on_send_animated_emoji_clicks(dialog_id, emoji); } void StickersManager::on_send_animated_emoji_clicks(DialogId dialog_id, const string &emoji) { flush_sent_animated_emoji_clicks(); if (!sent_animated_emoji_clicks_.empty() && sent_animated_emoji_clicks_.back().dialog_id_ == dialog_id && sent_animated_emoji_clicks_.back().emoji_ == emoji) { sent_animated_emoji_clicks_.back().send_time_ = Time::now(); return; } SentAnimatedEmojiClicks clicks; clicks.send_time_ = Time::now(); clicks.dialog_id_ = dialog_id; clicks.emoji_ = emoji; sent_animated_emoji_clicks_.push_back(std::move(clicks)); } void StickersManager::flush_sent_animated_emoji_clicks() { if (sent_animated_emoji_clicks_.empty()) { return; } auto min_send_time = Time::now() - 30.0; auto it = sent_animated_emoji_clicks_.begin(); while (it != sent_animated_emoji_clicks_.end() && it->send_time_ <= min_send_time) { ++it; } sent_animated_emoji_clicks_.erase(sent_animated_emoji_clicks_.begin(), it); } bool StickersManager::is_sent_animated_emoji_click(DialogId dialog_id, const string &emoji) { flush_sent_animated_emoji_clicks(); for (const auto &click : sent_animated_emoji_clicks_) { if (click.dialog_id_ == dialog_id && click.emoji_ == emoji) { return true; } } return false; } Status StickersManager::on_animated_emoji_message_clicked(string &&emoji, FullMessageId full_message_id, string data) { if (td_->auth_manager_->is_bot() || disable_animated_emojis_) { return Status::OK(); } TRY_RESULT(value, json_decode(data)); if (value.type() != JsonValue::Type::Object) { return Status::Error("Expected an object"); } auto &object = value.get_object(); TRY_RESULT(version, get_json_object_int_field(object, "v", false)); if (version != 1) { return Status::OK(); } TRY_RESULT(array_value, get_json_object_field(object, "a", JsonValue::Type::Array, false)); auto &array = array_value.get_array(); if (array.size() > 20) { return Status::Error("Click array is too big"); } vector> clicks; double previous_start_time = 0.0; double adjustment = 0.0; for (auto &click : array) { if (click.type() != JsonValue::Type::Object) { return Status::Error("Expected clicks as JSON objects"); } auto &click_object = click.get_object(); TRY_RESULT(index, get_json_object_int_field(click_object, "i", false)); if (index <= 0 || index > 9) { return Status::Error("Wrong index"); } TRY_RESULT(start_time, get_json_object_double_field(click_object, "t", false)); if (!std::isfinite(start_time)) { return Status::Error("Receive invalid start time"); } if (start_time < previous_start_time) { return Status::Error("Non-monotonic start time"); } if (start_time > previous_start_time + 3) { return Status::Error("Too big delay between clicks"); } previous_start_time = start_time; auto adjusted_start_time = clicks.empty() ? 0.0 : max(start_time + adjustment, clicks.back().second + MIN_ANIMATED_EMOJI_CLICK_DELAY); adjustment = adjusted_start_time - start_time; clicks.emplace_back(static_cast(index), adjusted_start_time); } auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_emoji_click()); if (special_sticker_set.id_.is_valid()) { auto sticker_set = get_sticker_set(special_sticker_set.id_); CHECK(sticker_set != nullptr); if (sticker_set->was_loaded_) { schedule_update_animated_emoji_clicked(sticker_set, emoji, full_message_id, std::move(clicks)); return Status::OK(); } } LOG(INFO) << "Waiting for an emoji click sticker set needed in " << full_message_id; load_special_sticker_set(special_sticker_set); PendingOnAnimatedEmojiClicked pending_request; pending_request.emoji_ = std::move(emoji); pending_request.full_message_id_ = full_message_id; pending_request.clicks_ = std::move(clicks); pending_on_animated_emoji_message_clicked_.push_back(std::move(pending_request)); return Status::OK(); } void StickersManager::schedule_update_animated_emoji_clicked(const StickerSet *sticker_set, Slice emoji, FullMessageId full_message_id, vector> clicks) { if (clicks.empty()) { return; } if (td_->messages_manager_->is_message_edited_recently(full_message_id, 2)) { // includes deleted full_message_id return; } auto dialog_id = full_message_id.get_dialog_id(); if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Write)) { return; } auto all_sticker_ids = get_animated_emoji_click_stickers(sticker_set, emoji); FlatHashMap sticker_ids; for (auto sticker_id : all_sticker_ids) { auto it = sticker_set->sticker_emojis_map_.find(sticker_id); if (it != sticker_set->sticker_emojis_map_.end()) { for (auto &sticker_emoji : it->second) { auto number = get_emoji_number(sticker_emoji); if (number > 0) { sticker_ids[number] = sticker_id; } } } } auto now = Time::now(); auto start_time = max(now, next_update_animated_emoji_clicked_time_); for (const auto &click : clicks) { auto index = click.first; if (index <= 0) { return; } auto sticker_id = sticker_ids[index]; if (!sticker_id.is_valid()) { LOG(INFO) << "Failed to find sticker for " << emoji << " with index " << index; return; } create_actor( "SendUpdateAnimatedEmojiClicked", start_time + click.second - now, PromiseCreator::lambda([actor_id = actor_id(this), full_message_id, sticker_id](Result result) { send_closure(actor_id, &StickersManager::send_update_animated_emoji_clicked, full_message_id, sticker_id); })) .release(); } next_update_animated_emoji_clicked_time_ = start_time + clicks.back().second + MIN_ANIMATED_EMOJI_CLICK_DELAY; } void StickersManager::send_update_animated_emoji_clicked(FullMessageId full_message_id, FileId sticker_id) { if (G()->close_flag() || disable_animated_emojis_ || td_->auth_manager_->is_bot()) { return; } if (td_->messages_manager_->is_message_edited_recently(full_message_id, 2)) { // includes deleted full_message_id return; } auto dialog_id = full_message_id.get_dialog_id(); if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Write)) { return; } send_closure( G()->td(), &Td::send_update, td_api::make_object( dialog_id.get(), full_message_id.get_message_id().get(), get_sticker_object(sticker_id, false, true))); } bool StickersManager::is_active_reaction(const string &reaction) const { for (auto &supported_reaction : reactions_.reactions_) { if (supported_reaction.reaction_ == reaction) { return supported_reaction.is_active_; } } return false; } void StickersManager::view_featured_sticker_sets(const vector &sticker_set_ids) { for (auto sticker_set_id : sticker_set_ids) { auto set = get_sticker_set(sticker_set_id); if (set != nullptr && !set->is_viewed_) { auto type = static_cast(set->sticker_type_); if (td::contains(featured_sticker_set_ids_[type], sticker_set_id)) { need_update_featured_sticker_sets_[type] = true; } set->is_viewed_ = true; pending_viewed_featured_sticker_set_ids_.insert(sticker_set_id); update_sticker_set(set, "view_featured_sticker_sets"); } } for (int32 type = 0; type < MAX_STICKER_TYPE; type++) { send_update_featured_sticker_sets(static_cast(type)); } if (!pending_viewed_featured_sticker_set_ids_.empty() && !pending_featured_sticker_set_views_timeout_.has_timeout()) { LOG(INFO) << "Have pending viewed trending sticker sets"; pending_featured_sticker_set_views_timeout_.set_callback(read_featured_sticker_sets); pending_featured_sticker_set_views_timeout_.set_callback_data(static_cast(td_)); pending_featured_sticker_set_views_timeout_.set_timeout_in(MAX_FEATURED_STICKER_SET_VIEW_DELAY); } } void StickersManager::read_featured_sticker_sets(void *td_void) { if (G()->close_flag()) { return; } CHECK(td_void != nullptr); auto td = static_cast(td_void); auto &set_ids = td->stickers_manager_->pending_viewed_featured_sticker_set_ids_; vector sticker_set_ids; for (auto sticker_set_id : set_ids) { sticker_set_ids.push_back(sticker_set_id); } set_ids.clear(); td->create_handler()->send(std::move(sticker_set_ids)); } std::pair> StickersManager::get_archived_sticker_sets(StickerType sticker_type, StickerSetId offset_sticker_set_id, int32 limit, bool force, Promise &&promise) { if (limit <= 0) { promise.set_error(Status::Error(400, "Parameter limit must be positive")); return {}; } auto type = static_cast(sticker_type); vector &sticker_set_ids = archived_sticker_set_ids_[type]; int32 total_count = total_archived_sticker_set_count_[type]; if (total_count >= 0) { auto offset_it = sticker_set_ids.begin(); if (offset_sticker_set_id.is_valid()) { offset_it = std::find(sticker_set_ids.begin(), sticker_set_ids.end(), offset_sticker_set_id); if (offset_it == sticker_set_ids.end()) { offset_it = sticker_set_ids.begin(); } else { ++offset_it; } } vector result; while (result.size() < static_cast(limit)) { if (offset_it == sticker_set_ids.end()) { break; } auto sticker_set_id = *offset_it++; if (!sticker_set_id.is_valid()) { // end of the list promise.set_value(Unit()); return {total_count, std::move(result)}; } result.push_back(sticker_set_id); } if (result.size() == static_cast(limit) || force) { promise.set_value(Unit()); return {total_count, std::move(result)}; } } td_->create_handler(std::move(promise)) ->send(sticker_type, offset_sticker_set_id, limit); return {}; } void StickersManager::on_get_archived_sticker_sets( StickerType sticker_type, StickerSetId offset_sticker_set_id, vector> &&sticker_sets, int32 total_count) { auto type = static_cast(sticker_type); vector &sticker_set_ids = archived_sticker_set_ids_[type]; if (!sticker_set_ids.empty() && sticker_set_ids.back() == StickerSetId()) { return; } if (total_count < 0) { LOG(ERROR) << "Receive " << total_count << " as total count of archived sticker sets"; } // if 0 sticker sets are received, then set offset_sticker_set_id was found and there are no stickers after it // or it wasn't found and there are no archived sets at all bool is_last = sticker_sets.empty() && (!offset_sticker_set_id.is_valid() || (!sticker_set_ids.empty() && offset_sticker_set_id == sticker_set_ids.back())); total_archived_sticker_set_count_[type] = total_count; for (auto &sticker_set_covered : sticker_sets) { auto sticker_set_id = on_get_sticker_set_covered(std::move(sticker_set_covered), false, "on_get_archived_sticker_sets"); if (sticker_set_id.is_valid()) { auto sticker_set = get_sticker_set(sticker_set_id); CHECK(sticker_set != nullptr); update_sticker_set(sticker_set, "on_get_archived_sticker_sets"); if (!td::contains(sticker_set_ids, sticker_set_id)) { sticker_set_ids.push_back(sticker_set_id); } } } if (sticker_set_ids.size() >= static_cast(total_count) || is_last) { if (sticker_set_ids.size() != static_cast(total_count)) { LOG(ERROR) << "Expected total of " << total_count << " archived sticker sets, but " << sticker_set_ids.size() << " found"; total_archived_sticker_set_count_[type] = static_cast(sticker_set_ids.size()); } sticker_set_ids.push_back(StickerSetId()); } send_update_installed_sticker_sets(); } td_api::object_ptr StickersManager::get_featured_sticker_sets(StickerType sticker_type, int32 offset, int32 limit, Promise &&promise) { if (offset < 0) { promise.set_error(Status::Error(400, "Parameter offset must be non-negative")); return {}; } if (limit < 0) { promise.set_error(Status::Error(400, "Parameter limit must be non-negative")); return {}; } if (limit == 0) { offset = 0; } if (sticker_type == StickerType::Mask) { promise.set_value(Unit()); return get_trending_sticker_sets_object(sticker_type, {}); } auto type = static_cast(sticker_type); if (!are_featured_sticker_sets_loaded_[type]) { load_featured_sticker_sets(sticker_type, std::move(promise)); return {}; } reload_featured_sticker_sets(sticker_type, false); auto set_count = static_cast(featured_sticker_set_ids_[type].size()); if (offset < set_count) { if (limit > set_count - offset) { limit = set_count - offset; } promise.set_value(Unit()); auto begin = featured_sticker_set_ids_[type].begin() + offset; return get_trending_sticker_sets_object(sticker_type, {begin, begin + limit}); } if (offset == set_count && are_old_featured_sticker_sets_invalidated_[type]) { invalidate_old_featured_sticker_sets(sticker_type); } auto total_count = set_count + (old_featured_sticker_set_count_[type] == -1 ? 1 : old_featured_sticker_set_count_[type]); if (offset < total_count || old_featured_sticker_set_count_[type] == -1) { offset -= set_count; set_count = static_cast(old_featured_sticker_set_ids_[type].size()); if (offset < set_count) { if (limit > set_count - offset) { limit = set_count - offset; } promise.set_value(Unit()); auto begin = old_featured_sticker_set_ids_[type].begin() + offset; return get_trending_sticker_sets_object(sticker_type, {begin, begin + limit}); } if (offset > set_count) { promise.set_error( Status::Error(400, "Too big offset specified; trending sticker sets can be received only consequently")); return {}; } load_old_featured_sticker_sets(sticker_type, std::move(promise)); return {}; } promise.set_value(Unit()); return get_trending_sticker_sets_object(sticker_type, {}); } void StickersManager::on_old_featured_sticker_sets_invalidated(StickerType sticker_type) { if (sticker_type != StickerType::Regular) { return; } auto type = static_cast(sticker_type); LOG(INFO) << "Invalidate old trending sticker sets"; are_old_featured_sticker_sets_invalidated_[type] = true; if (!G()->parameters().use_file_db) { return; } G()->td_db()->get_binlog_pmc()->set("invalidate_old_featured_sticker_sets", "1"); } void StickersManager::invalidate_old_featured_sticker_sets(StickerType sticker_type) { if (G()->close_flag()) { return; } if (sticker_type != StickerType::Regular) { return; } auto type = static_cast(sticker_type); LOG(INFO) << "Invalidate old featured sticker sets"; if (G()->parameters().use_file_db) { G()->td_db()->get_binlog_pmc()->erase("invalidate_old_featured_sticker_sets"); G()->td_db()->get_sqlite_pmc()->erase_by_prefix("sssoldfeatured", Auto()); } are_old_featured_sticker_sets_invalidated_[type] = false; old_featured_sticker_set_ids_[type].clear(); old_featured_sticker_set_generation_[type]++; fail_promises(load_old_featured_sticker_sets_queries_, Status::Error(400, "Trending sticker sets were updated")); } void StickersManager::set_old_featured_sticker_set_count(StickerType sticker_type, int32 count) { auto type = static_cast(sticker_type); if (old_featured_sticker_set_count_[type] == count) { return; } if (sticker_type != StickerType::Regular) { return; } on_old_featured_sticker_sets_invalidated(sticker_type); old_featured_sticker_set_count_[type] = count; need_update_featured_sticker_sets_[type] = true; if (!G()->parameters().use_file_db) { return; } LOG(INFO) << "Save old trending sticker set count " << count << " to binlog"; G()->td_db()->get_binlog_pmc()->set("old_featured_sticker_set_count", to_string(count)); } void StickersManager::fix_old_featured_sticker_set_count(StickerType sticker_type) { auto type = static_cast(sticker_type); auto known_count = static_cast(old_featured_sticker_set_ids_[type].size()); if (old_featured_sticker_set_count_[type] < known_count) { if (old_featured_sticker_set_count_[type] >= 0) { LOG(ERROR) << "Have old trending sticker set count " << old_featured_sticker_set_count_[type] << ", but have " << known_count << " old trending sticker sets"; } set_old_featured_sticker_set_count(sticker_type, known_count); } if (old_featured_sticker_set_count_[type] > known_count && known_count % OLD_FEATURED_STICKER_SET_SLICE_SIZE != 0) { LOG(ERROR) << "Have " << known_count << " old sticker sets out of " << old_featured_sticker_set_count_[type]; set_old_featured_sticker_set_count(sticker_type, known_count); } } void StickersManager::on_get_featured_sticker_sets( StickerType sticker_type, int32 offset, int32 limit, uint32 generation, tl_object_ptr &&sticker_sets_ptr) { auto type = static_cast(sticker_type); if (offset < 0) { next_featured_sticker_sets_load_time_[type] = Time::now_cached() + Random::fast(30 * 60, 50 * 60); } int32 constructor_id = sticker_sets_ptr->get_id(); if (constructor_id == telegram_api::messages_featuredStickersNotModified::ID) { LOG(INFO) << "Trending sticker sets are not modified"; auto *stickers = static_cast(sticker_sets_ptr.get()); if (offset >= 0 && generation == old_featured_sticker_set_generation_[type]) { set_old_featured_sticker_set_count(sticker_type, stickers->count_); fix_old_featured_sticker_set_count(sticker_type); } send_update_featured_sticker_sets(sticker_type); return; } CHECK(constructor_id == telegram_api::messages_featuredStickers::ID); auto featured_stickers = move_tl_object_as(sticker_sets_ptr); if (featured_stickers->premium_ != are_featured_sticker_sets_premium_[type]) { on_old_featured_sticker_sets_invalidated(sticker_type); if (offset >= 0) { featured_stickers->premium_ = are_featured_sticker_sets_premium_[type]; reload_featured_sticker_sets(sticker_type, true); } } if (offset >= 0 && generation == old_featured_sticker_set_generation_[type]) { set_old_featured_sticker_set_count(sticker_type, featured_stickers->count_); // the count will be fixed in on_load_old_featured_sticker_sets_finished } FlatHashSet unread_sticker_set_ids; for (auto &unread_sticker_set_id : featured_stickers->unread_) { StickerSetId sticker_set_id(unread_sticker_set_id); if (sticker_set_id.is_valid()) { unread_sticker_set_ids.insert(sticker_set_id); } } vector featured_sticker_set_ids; for (auto &sticker_set : featured_stickers->sets_) { StickerSetId set_id = on_get_sticker_set_covered(std::move(sticker_set), true, "on_get_featured_sticker_sets"); if (!set_id.is_valid()) { continue; } auto set = get_sticker_set(set_id); CHECK(set != nullptr); bool is_viewed = unread_sticker_set_ids.count(set_id) == 0; if (is_viewed != set->is_viewed_) { set->is_viewed_ = is_viewed; set->is_changed_ = true; } update_sticker_set(set, "on_get_featured_sticker_sets 2"); featured_sticker_set_ids.push_back(set_id); } send_update_installed_sticker_sets(); if (offset >= 0) { if (generation == old_featured_sticker_set_generation_[type] && sticker_type == StickerType::Regular) { if (G()->parameters().use_file_db && !G()->close_flag()) { LOG(INFO) << "Save old trending sticker sets to database with offset " << old_featured_sticker_set_ids_[type].size(); CHECK(old_featured_sticker_set_ids_[type].size() % OLD_FEATURED_STICKER_SET_SLICE_SIZE == 0); StickerSetListLogEvent log_event(featured_sticker_set_ids, false); G()->td_db()->get_sqlite_pmc()->set(PSTRING() << "sssoldfeatured" << old_featured_sticker_set_ids_[type].size(), log_event_store(log_event).as_slice().str(), Auto()); } on_load_old_featured_sticker_sets_finished(sticker_type, generation, std::move(featured_sticker_set_ids)); } send_update_featured_sticker_sets(sticker_type); // because of changed count return; } on_load_featured_sticker_sets_finished(sticker_type, std::move(featured_sticker_set_ids), featured_stickers->premium_); LOG_IF(ERROR, featured_sticker_sets_hash_[type] != featured_stickers->hash_) << "Trending sticker sets hash mismatch"; if (G()->parameters().use_file_db && !G()->close_flag()) { LOG(INFO) << "Save trending sticker sets to database"; StickerSetListLogEvent log_event(featured_sticker_set_ids_[type], are_featured_sticker_sets_premium_[type]); G()->td_db()->get_sqlite_pmc()->set(PSTRING() << "sssfeatured" << get_featured_sticker_suffix(sticker_type), log_event_store(log_event).as_slice().str(), Auto()); } } void StickersManager::on_get_featured_sticker_sets_failed(StickerType sticker_type, int32 offset, int32 limit, uint32 generation, Status error) { auto type = static_cast(sticker_type); CHECK(error.is_error()); if (offset >= 0) { if (generation != old_featured_sticker_set_generation_[type] || sticker_type != StickerType::Regular) { return; } fail_promises(load_old_featured_sticker_sets_queries_, std::move(error)); } else { next_featured_sticker_sets_load_time_[type] = Time::now_cached() + Random::fast(5, 10); fail_promises(load_featured_sticker_sets_queries_[type], std::move(error)); } } void StickersManager::load_featured_sticker_sets(StickerType sticker_type, Promise &&promise) { CHECK(sticker_type != StickerType::Mask); auto type = static_cast(sticker_type); if (td_->auth_manager_->is_bot()) { are_featured_sticker_sets_loaded_[type] = true; old_featured_sticker_set_count_[type] = 0; } if (are_featured_sticker_sets_loaded_[type]) { promise.set_value(Unit()); return; } load_featured_sticker_sets_queries_[type].push_back(std::move(promise)); if (load_featured_sticker_sets_queries_[type].size() == 1u) { if (G()->parameters().use_file_db) { LOG(INFO) << "Trying to load trending sticker sets from database"; G()->td_db()->get_sqlite_pmc()->get(PSTRING() << "sssfeatured" << get_featured_sticker_suffix(sticker_type), PromiseCreator::lambda([sticker_type](string value) { send_closure(G()->stickers_manager(), &StickersManager::on_load_featured_sticker_sets_from_database, sticker_type, std::move(value)); })); } else { LOG(INFO) << "Trying to load trending sticker sets from server"; reload_featured_sticker_sets(sticker_type, true); } } } void StickersManager::on_load_featured_sticker_sets_from_database(StickerType sticker_type, string value) { if (G()->close_flag()) { return; } if (value.empty()) { LOG(INFO) << "Trending " << sticker_type << " sticker sets aren't found in database"; reload_featured_sticker_sets(sticker_type, true); return; } LOG(INFO) << "Successfully loaded trending " << sticker_type << " sticker set list of size " << value.size() << " from database"; StickerSetListLogEvent log_event; auto status = log_event_parse(log_event, value); if (status.is_error()) { // can't happen unless database is broken LOG(ERROR) << "Can't load trending sticker set list: " << status << ' ' << format::as_hex_dump<4>(Slice(value)); return reload_featured_sticker_sets(sticker_type, true); } vector sets_to_load; for (auto sticker_set_id : log_event.sticker_set_ids_) { StickerSet *sticker_set = get_sticker_set(sticker_set_id); CHECK(sticker_set != nullptr); if (!sticker_set->is_inited_) { sets_to_load.push_back(sticker_set_id); } } load_sticker_sets_without_stickers( std::move(sets_to_load), PromiseCreator::lambda([sticker_type, sticker_set_ids = std::move(log_event.sticker_set_ids_), is_premium = log_event.is_premium_](Result<> result) mutable { if (result.is_ok()) { send_closure(G()->stickers_manager(), &StickersManager::on_load_featured_sticker_sets_finished, sticker_type, std::move(sticker_set_ids), is_premium); } else { send_closure(G()->stickers_manager(), &StickersManager::reload_featured_sticker_sets, sticker_type, true); } })); } void StickersManager::on_load_featured_sticker_sets_finished(StickerType sticker_type, vector &&featured_sticker_set_ids, bool is_premium) { auto type = static_cast(sticker_type); if (!featured_sticker_set_ids_[type].empty() && featured_sticker_set_ids != featured_sticker_set_ids_[type]) { // always invalidate old featured sticker sets when current featured sticker sets change on_old_featured_sticker_sets_invalidated(sticker_type); } featured_sticker_set_ids_[type] = std::move(featured_sticker_set_ids); are_featured_sticker_sets_premium_[type] = is_premium; are_featured_sticker_sets_loaded_[type] = true; need_update_featured_sticker_sets_[type] = true; send_update_featured_sticker_sets(sticker_type); set_promises(load_featured_sticker_sets_queries_[type]); } void StickersManager::load_old_featured_sticker_sets(StickerType sticker_type, Promise &&promise) { CHECK(sticker_type == StickerType::Regular); CHECK(!td_->auth_manager_->is_bot()); auto type = static_cast(sticker_type); CHECK(old_featured_sticker_set_ids_[type].size() % OLD_FEATURED_STICKER_SET_SLICE_SIZE == 0); load_old_featured_sticker_sets_queries_.push_back(std::move(promise)); if (load_old_featured_sticker_sets_queries_.size() == 1u) { if (G()->parameters().use_file_db) { LOG(INFO) << "Trying to load old trending sticker sets from database with offset " << old_featured_sticker_set_ids_[type].size(); G()->td_db()->get_sqlite_pmc()->get( PSTRING() << "sssoldfeatured" << old_featured_sticker_set_ids_[type].size(), PromiseCreator::lambda([sticker_type, generation = old_featured_sticker_set_generation_[type]](string value) { send_closure(G()->stickers_manager(), &StickersManager::on_load_old_featured_sticker_sets_from_database, sticker_type, generation, std::move(value)); })); } else { LOG(INFO) << "Trying to load old trending sticker sets from server with offset " << old_featured_sticker_set_ids_[type].size(); reload_old_featured_sticker_sets(sticker_type); } } } void StickersManager::on_load_old_featured_sticker_sets_from_database(StickerType sticker_type, uint32 generation, string value) { if (G()->close_flag()) { return; } CHECK(sticker_type == StickerType::Regular); auto type = static_cast(sticker_type); if (generation != old_featured_sticker_set_generation_[type]) { return; } if (value.empty()) { LOG(INFO) << "Old trending sticker sets aren't found in database"; return reload_old_featured_sticker_sets(sticker_type); } LOG(INFO) << "Successfully loaded old trending sticker set list of size " << value.size() << " from database with offset " << old_featured_sticker_set_ids_[type].size(); StickerSetListLogEvent log_event; auto status = log_event_parse(log_event, value); if (status.is_error()) { // can't happen unless database is broken LOG(ERROR) << "Can't load old trending sticker set list: " << status << ' ' << format::as_hex_dump<4>(Slice(value)); return reload_old_featured_sticker_sets(sticker_type); } CHECK(!log_event.is_premium_); vector sets_to_load; for (auto sticker_set_id : log_event.sticker_set_ids_) { StickerSet *sticker_set = get_sticker_set(sticker_set_id); CHECK(sticker_set != nullptr); if (!sticker_set->is_inited_) { sets_to_load.push_back(sticker_set_id); } } load_sticker_sets_without_stickers( std::move(sets_to_load), PromiseCreator::lambda( [sticker_type, generation, sticker_set_ids = std::move(log_event.sticker_set_ids_)](Result<> result) mutable { if (result.is_ok()) { send_closure(G()->stickers_manager(), &StickersManager::on_load_old_featured_sticker_sets_finished, sticker_type, generation, std::move(sticker_set_ids)); } else { send_closure(G()->stickers_manager(), &StickersManager::reload_old_featured_sticker_sets, sticker_type, generation); } })); } void StickersManager::on_load_old_featured_sticker_sets_finished(StickerType sticker_type, uint32 generation, vector &&featured_sticker_set_ids) { auto type = static_cast(sticker_type); if (generation != old_featured_sticker_set_generation_[type]) { fix_old_featured_sticker_set_count(sticker_type); // must never be needed return; } CHECK(sticker_type == StickerType::Regular); append(old_featured_sticker_set_ids_[type], std::move(featured_sticker_set_ids)); fix_old_featured_sticker_set_count(sticker_type); set_promises(load_old_featured_sticker_sets_queries_); } vector StickersManager::get_attached_sticker_sets(FileId file_id, Promise &&promise) { if (!file_id.is_valid()) { promise.set_error(Status::Error(400, "Wrong file_id specified")); return {}; } auto it = attached_sticker_sets_.find(file_id); if (it != attached_sticker_sets_.end()) { promise.set_value(Unit()); return it->second; } send_get_attached_stickers_query(file_id, std::move(promise)); return {}; } void StickersManager::send_get_attached_stickers_query(FileId file_id, Promise &&promise) { auto file_view = td_->file_manager_->get_file_view(file_id); if (file_view.empty()) { return promise.set_error(Status::Error(400, "File not found")); } if (!file_view.has_remote_location() || (!file_view.remote_location().is_document() && !file_view.remote_location().is_photo()) || file_view.remote_location().is_web()) { return promise.set_value(Unit()); } tl_object_ptr input_stickered_media; string file_reference; if (file_view.main_remote_location().is_photo()) { auto input_photo = file_view.main_remote_location().as_input_photo(); file_reference = input_photo->file_reference_.as_slice().str(); input_stickered_media = make_tl_object(std::move(input_photo)); } else { auto input_document = file_view.main_remote_location().as_input_document(); file_reference = input_document->file_reference_.as_slice().str(); input_stickered_media = make_tl_object(std::move(input_document)); } td_->create_handler(std::move(promise)) ->send(file_id, std::move(file_reference), std::move(input_stickered_media)); } void StickersManager::on_get_attached_sticker_sets( FileId file_id, vector> &&sticker_sets) { CHECK(file_id.is_valid()); vector &sticker_set_ids = attached_sticker_sets_[file_id]; sticker_set_ids.clear(); for (auto &sticker_set_covered : sticker_sets) { auto sticker_set_id = on_get_sticker_set_covered(std::move(sticker_set_covered), true, "on_get_attached_sticker_sets"); if (sticker_set_id.is_valid()) { auto sticker_set = get_sticker_set(sticker_set_id); CHECK(sticker_set != nullptr); update_sticker_set(sticker_set, "on_get_attached_sticker_sets"); sticker_set_ids.push_back(sticker_set_id); } } send_update_installed_sticker_sets(); } // -1 - order can't be applied, because some sticker sets aren't loaded or aren't installed, // 0 - order wasn't changed, 1 - order was partly replaced by the new order, 2 - order was replaced by the new order int StickersManager::apply_installed_sticker_sets_order(StickerType sticker_type, const vector &sticker_set_ids) { auto type = static_cast(sticker_type); if (!are_installed_sticker_sets_loaded_[type]) { return -1; } vector ¤t_sticker_set_ids = installed_sticker_set_ids_[type]; if (sticker_set_ids == current_sticker_set_ids) { return 0; } FlatHashSet valid_set_ids; for (auto sticker_set_id : current_sticker_set_ids) { valid_set_ids.insert(sticker_set_id); } vector new_sticker_set_ids; for (auto sticker_set_id : sticker_set_ids) { auto it = valid_set_ids.find(sticker_set_id); if (it != valid_set_ids.end()) { new_sticker_set_ids.push_back(sticker_set_id); valid_set_ids.erase(it); } else { return -1; } } if (new_sticker_set_ids.empty()) { return 0; } if (!valid_set_ids.empty()) { vector missed_sticker_set_ids; for (auto sticker_set_id : current_sticker_set_ids) { auto it = valid_set_ids.find(sticker_set_id); if (it != valid_set_ids.end()) { missed_sticker_set_ids.push_back(sticker_set_id); valid_set_ids.erase(it); } } append(missed_sticker_set_ids, new_sticker_set_ids); new_sticker_set_ids = std::move(missed_sticker_set_ids); } CHECK(valid_set_ids.empty()); if (new_sticker_set_ids == current_sticker_set_ids) { return 0; } current_sticker_set_ids = std::move(new_sticker_set_ids); need_update_installed_sticker_sets_[type] = true; if (sticker_set_ids != current_sticker_set_ids) { return 1; } return 2; } void StickersManager::on_update_sticker_sets_order(StickerType sticker_type, const vector &sticker_set_ids) { int result = apply_installed_sticker_sets_order(sticker_type, sticker_set_ids); if (result < 0) { return reload_installed_sticker_sets(sticker_type, true); } if (result > 0) { send_update_installed_sticker_sets(); } } // -1 - sticker set can't be moved to top, 0 - order wasn't changed, 1 - sticker set was moved to top int StickersManager::move_installed_sticker_set_to_top(StickerType sticker_type, StickerSetId sticker_set_id) { LOG(INFO) << "Move " << sticker_set_id << " to top of " << sticker_type; auto type = static_cast(sticker_type); if (!are_installed_sticker_sets_loaded_[type]) { return -1; } vector ¤t_sticker_set_ids = installed_sticker_set_ids_[type]; auto it = std::find(current_sticker_set_ids.begin(), current_sticker_set_ids.end(), sticker_set_id); if (it == current_sticker_set_ids.end()) { return -1; } if (sticker_set_id == current_sticker_set_ids[0]) { CHECK(it == current_sticker_set_ids.begin()); return 0; } std::rotate(current_sticker_set_ids.begin(), it, it + 1); need_update_installed_sticker_sets_[type] = true; return 1; } void StickersManager::on_update_move_sticker_set_to_top(StickerType sticker_type, StickerSetId sticker_set_id) { int result = move_installed_sticker_set_to_top(sticker_type, sticker_set_id); if (result < 0) { return reload_installed_sticker_sets(sticker_type, true); } if (result > 0) { send_update_installed_sticker_sets(); } } void StickersManager::reorder_installed_sticker_sets(StickerType sticker_type, const vector &sticker_set_ids, Promise &&promise) { auto result = apply_installed_sticker_sets_order(sticker_type, sticker_set_ids); if (result < 0) { return promise.set_error(Status::Error(400, "Wrong sticker set list")); } if (result > 0) { auto type = static_cast(sticker_type); td_->create_handler()->send(sticker_type, installed_sticker_set_ids_[type]); send_update_installed_sticker_sets(); } promise.set_value(Unit()); } void StickersManager::move_sticker_set_to_top_by_sticker_id(FileId sticker_id) { LOG(INFO) << "Move to top sticker set of " << sticker_id; const auto *s = get_sticker(sticker_id); if (s == nullptr || !s->set_id_.is_valid()) { return; } if (s->type_ == StickerType::CustomEmoji) { // just in case return; } if (move_installed_sticker_set_to_top(s->type_, s->set_id_) > 0) { send_update_installed_sticker_sets(); } } void StickersManager::move_sticker_set_to_top_by_custom_emoji_ids(const vector &custom_emoji_ids) { LOG(INFO) << "Move to top sticker set of " << custom_emoji_ids; StickerSetId sticker_set_id; for (auto custom_emoji_id : custom_emoji_ids) { auto sticker_id = custom_emoji_to_sticker_id_.get(custom_emoji_id); if (!sticker_id.is_valid()) { return; } const auto *s = get_sticker(sticker_id); CHECK(s != nullptr); CHECK(s->type_ == StickerType::CustomEmoji); if (!s->set_id_.is_valid()) { return; } if (s->set_id_ != sticker_set_id) { if (sticker_set_id.is_valid()) { return; } sticker_set_id = s->set_id_; } } CHECK(sticker_set_id.is_valid()); if (move_installed_sticker_set_to_top(StickerType::CustomEmoji, sticker_set_id) > 0) { send_update_installed_sticker_sets(); } } Result> StickersManager::prepare_input_sticker( td_api::inputSticker *sticker, StickerType sticker_type) { if (sticker == nullptr) { return Status::Error(400, "Input sticker must be non-empty"); } if (!clean_input_string(sticker->emojis_)) { return Status::Error(400, "Emojis must be encoded in UTF-8"); } if (sticker->format_ == nullptr) { return Status::Error(400, "Sticker format must be non-empty"); } return prepare_input_file(sticker->sticker_, get_sticker_format(sticker->format_), sticker_type, false); } Result> StickersManager::prepare_input_file( const tl_object_ptr &input_file, StickerFormat format, StickerType type, bool for_thumbnail) { auto file_type = 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()); } auto file_id = r_file_id.move_as_ok(); if (file_id.empty()) { return std::make_tuple(FileId(), false, false, StickerFormat::Unknown); } if (format == StickerFormat::Tgs) { int32 width = for_thumbnail ? 100 : 512; create_sticker(file_id, FileId(), string(), PhotoSize(), get_dimensions(width, width, "prepare_input_file"), nullptr, nullptr, format, nullptr); } else if (format == StickerFormat::Webm) { td_->documents_manager_->create_document(file_id, string(), PhotoSize(), "sticker.webm", "video/webm", false); } else { td_->documents_manager_->create_document(file_id, string(), PhotoSize(), "sticker.png", "image/png", false); } FileView file_view = td_->file_manager_->get_file_view(file_id); if (file_view.is_encrypted()) { return Status::Error(400, "Can't use encrypted file"); } if (file_view.has_remote_location() && file_view.main_remote_location().is_web()) { return Status::Error(400, "Can't use web file to create a sticker"); } bool is_url = false; bool is_local = false; if (file_view.has_remote_location()) { CHECK(file_view.main_remote_location().is_document()); } else { if (file_view.has_url()) { is_url = true; } else { if (file_view.has_local_location() && file_view.expected_size() > get_max_sticker_file_size(format, type, for_thumbnail)) { return Status::Error(400, "File is too big"); } is_local = true; } } return std::make_tuple(file_id, is_url, is_local, format); } 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) { user_id = td_->contacts_manager_->get_my_id(); } auto r_input_user = td_->contacts_manager_->get_input_user(user_id); if (r_input_user.is_error()) { promise.set_error(r_input_user.move_as_error()); return FileId(); } // StickerType::Regular has less restrictions auto r_file_id = prepare_input_sticker(sticker.get(), StickerType::Regular); if (r_file_id.is_error()) { promise.set_error(r_file_id.move_as_error()); return FileId(); } auto file_id = std::get<0>(r_file_id.ok()); auto is_url = std::get<1>(r_file_id.ok()); auto is_local = std::get<2>(r_file_id.ok()); if (is_url) { do_upload_sticker_file(user_id, file_id, nullptr, std::move(promise)); } else if (is_local) { upload_sticker_file(user_id, file_id, std::move(promise)); } else { promise.set_value(Unit()); } return file_id; } 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); CHECK(file_view.has_remote_location()); auto input_document = file_view.main_remote_location().as_input_document(); tl_object_ptr mask_coords; auto mask_position = sticker->mask_position_.get(); if (mask_position != nullptr && mask_position->point_ != nullptr) { 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: return 1; case td_api::maskPointMouth::ID: return 2; case td_api::maskPointChin::ID: return 3; default: UNREACHABLE(); return -1; } }(); mask_coords = make_tl_object(point, mask_position->x_shift_, mask_position->y_shift_, mask_position->scale_); } int32 flags = 0; if (mask_coords != nullptr) { flags |= telegram_api::inputStickerSetItem::MASK_COORDS_MASK; } 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) { title = strip_empty_characters(title, MAX_STICKER_SET_TITLE_LENGTH); if (title.empty()) { return promise.set_error(Status::Error(400, "Sticker set title must be non-empty")); } td_->create_handler(std::move(promise))->send(title); } void StickersManager::check_sticker_set_name(const string &name, Promise &&promise) { if (name.empty()) { return promise.set_value(CheckStickerSetNameResult::Invalid); } auto request_promise = PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { if (result.is_error()) { auto error = result.move_as_error(); if (error.message() == "SHORT_NAME_INVALID") { return promise.set_value(CheckStickerSetNameResult::Invalid); } if (error.message() == "SHORT_NAME_OCCUPIED") { return promise.set_value(CheckStickerSetNameResult::Occupied); } return promise.set_error(std::move(error)); } promise.set_value(CheckStickerSetNameResult::Ok); }); return td_->create_handler(std::move(request_promise))->send(name); } td_api::object_ptr StickersManager::get_check_sticker_set_name_result_object( CheckStickerSetNameResult result) { switch (result) { case CheckStickerSetNameResult::Ok: return td_api::make_object(); case CheckStickerSetNameResult::Invalid: return td_api::make_object(); case CheckStickerSetNameResult::Occupied: return td_api::make_object(); default: UNREACHABLE(); return nullptr; } } void StickersManager::create_new_sticker_set(UserId user_id, string title, string short_name, StickerType sticker_type, vector> &&stickers, string software, Promise> &&promise) { bool is_bot = td_->auth_manager_->is_bot(); if (!is_bot) { user_id = td_->contacts_manager_->get_my_id(); } TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); title = strip_empty_characters(title, MAX_STICKER_SET_TITLE_LENGTH); if (title.empty()) { return promise.set_error(Status::Error(400, "Sticker set title must be non-empty")); } short_name = strip_empty_characters(short_name, MAX_STICKER_SET_SHORT_NAME_LENGTH); if (short_name.empty()) { return promise.set_error(Status::Error(400, "Sticker set name must be non-empty")); } if (stickers.empty()) { return promise.set_error(Status::Error(400, "At least 1 sticker must be specified")); } vector file_ids; file_ids.reserve(stickers.size()); vector local_file_ids; vector url_file_ids; FlatHashSet sticker_formats; StickerFormat sticker_format = StickerFormat::Unknown; for (auto &sticker : stickers) { auto r_file_id = prepare_input_sticker(sticker.get(), sticker_type); if (r_file_id.is_error()) { return promise.set_error(r_file_id.move_as_error()); } auto file_id = std::get<0>(r_file_id.ok()); auto is_url = std::get<1>(r_file_id.ok()); auto is_local = std::get<2>(r_file_id.ok()); sticker_format = std::get<3>(r_file_id.ok()); 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(get_sticker_format(sticker->format_)) + 1); file_ids.push_back(file_id); if (is_url) { url_file_ids.push_back(file_id); } else if (is_local) { local_file_ids.push_back(file_id); } } if (sticker_formats.size() != 1) { return promise.set_error(Status::Error(400, "All stickers must be of the same format")); } auto pending_new_sticker_set = make_unique(); 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->sticker_format_ = sticker_format; pending_new_sticker_set->sticker_type_ = sticker_type; pending_new_sticker_set->file_ids_ = std::move(file_ids); pending_new_sticker_set->stickers_ = std::move(stickers); pending_new_sticker_set->software_ = std::move(software); pending_new_sticker_set->promise_ = std::move(promise); auto &multipromise = pending_new_sticker_set->upload_files_multipromise_; int64 random_id; do { random_id = Random::secure_int64(); } while (random_id == 0 || pending_new_sticker_sets_.count(random_id) > 0); pending_new_sticker_sets_[random_id] = std::move(pending_new_sticker_set); multipromise.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), random_id](Result result) { send_closure_later(actor_id, &StickersManager::on_new_stickers_uploaded, random_id, std::move(result)); })); auto lock_promise = multipromise.get_promise(); for (auto file_id : url_file_ids) { do_upload_sticker_file(user_id, file_id, nullptr, multipromise.get_promise()); } for (auto file_id : local_file_ids) { upload_sticker_file(user_id, file_id, multipromise.get_promise()); } lock_promise.set_value(Unit()); } void StickersManager::upload_sticker_file(UserId user_id, FileId file_id, Promise &&promise) { FileId upload_file_id; if (td_->file_manager_->get_file_view(file_id).get_type() == FileType::Sticker) { CHECK(get_input_media(file_id, nullptr, nullptr, string()) == nullptr); upload_file_id = dup_sticker(td_->file_manager_->dup_file_id(file_id, "upload_sticker_file"), file_id); } else { CHECK(td_->documents_manager_->get_input_media(file_id, nullptr, nullptr) == nullptr); upload_file_id = td_->documents_manager_->dup_document(td_->file_manager_->dup_file_id(file_id, "upload_sticker_file"), file_id); } CHECK(upload_file_id.is_valid()); being_uploaded_files_[upload_file_id] = {user_id, std::move(promise)}; LOG(INFO) << "Ask to upload sticker file " << upload_file_id; td_->file_manager_->upload(upload_file_id, upload_sticker_file_callback_, 2, 0); } void StickersManager::on_upload_sticker_file(FileId file_id, tl_object_ptr input_file) { LOG(INFO) << "Sticker file " << file_id << " has been uploaded"; auto it = being_uploaded_files_.find(file_id); CHECK(it != being_uploaded_files_.end()); auto user_id = it->second.first; auto promise = std::move(it->second.second); being_uploaded_files_.erase(it); do_upload_sticker_file(user_id, file_id, std::move(input_file), std::move(promise)); } void StickersManager::on_upload_sticker_file_error(FileId file_id, Status status) { if (G()->close_flag()) { // do not fail upload if closing return; } LOG(WARNING) << "Sticker file " << file_id << " has upload error " << status; CHECK(status.is_error()); auto it = being_uploaded_files_.find(file_id); CHECK(it != being_uploaded_files_.end()); auto promise = std::move(it->second.second); being_uploaded_files_.erase(it); // TODO FILE_PART_X_MISSING support promise.set_error(Status::Error(status.code() > 0 ? status.code() : 500, status.message())); // TODO CHECK that status has always a code } void StickersManager::do_upload_sticker_file(UserId user_id, FileId file_id, tl_object_ptr &&input_file, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); DialogId dialog_id(user_id); auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { if (input_file != nullptr) { td_->file_manager_->cancel_upload(file_id); } return promise.set_error(Status::Error(400, "Have no access to the user")); } FileView file_view = td_->file_manager_->get_file_view(file_id); FileType file_type = file_view.get_type(); bool had_input_file = input_file != nullptr; auto input_media = file_type == FileType::Sticker ? get_input_media(file_id, std::move(input_file), nullptr, string()) : td_->documents_manager_->get_input_media(file_id, std::move(input_file), nullptr); CHECK(input_media != nullptr); if (had_input_file && !FileManager::extract_was_uploaded(input_media)) { // if we had InputFile, but has failed to use it for input_media, then we need to immediately cancel file upload // so the next upload with the same file can succeed td_->file_manager_->cancel_upload(file_id); } td_->create_handler(std::move(promise)) ->send(std::move(input_peer), file_id, std::move(input_media)); } void StickersManager::on_uploaded_sticker_file(FileId file_id, tl_object_ptr media, Promise &&promise) { CHECK(media != nullptr); LOG(INFO) << "Receive uploaded sticker file " << to_string(media); if (media->get_id() != telegram_api::messageMediaDocument::ID) { return promise.set_error(Status::Error(400, "Can't upload sticker file: wrong file type")); } auto message_document = move_tl_object_as(media); auto document_ptr = std::move(message_document->document_); int32 document_id = document_ptr->get_id(); if (document_id == telegram_api::documentEmpty::ID) { return promise.set_error(Status::Error(400, "Can't upload sticker file: empty file")); } CHECK(document_id == telegram_api::document::ID); FileView file_view = td_->file_manager_->get_file_view(file_id); FileType file_type = file_view.get_type(); auto expected_document_type = file_type == FileType::Sticker ? Document::Type::Sticker : Document::Type::General; auto parsed_document = td_->documents_manager_->on_get_document( move_tl_object_as(document_ptr), DialogId(), nullptr); if (parsed_document.type != expected_document_type) { return promise.set_error(Status::Error(400, "Wrong file type")); } if (parsed_document.file_id != file_id) { if (file_type == FileType::Sticker) { merge_stickers(parsed_document.file_id, file_id); } else { // must not delete the old document, because the file_id could be used for simultaneous URL uploads td_->documents_manager_->merge_documents(parsed_document.file_id, file_id); } } promise.set_value(Unit()); } void StickersManager::on_new_stickers_uploaded(int64 random_id, Result result) { if (G()->close_flag()) { result = Global::request_aborted_error(); } auto it = pending_new_sticker_sets_.find(random_id); CHECK(it != pending_new_sticker_sets_.end()); auto pending_new_sticker_set = std::move(it->second); CHECK(pending_new_sticker_set != nullptr); pending_new_sticker_sets_.erase(it); if (result.is_error()) { pending_new_sticker_set->promise_.set_error(result.move_as_error()); return; } CHECK(pending_new_sticker_set->upload_files_multipromise_.promise_count() == 0); 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_)); StickerFormat sticker_format = pending_new_sticker_set->sticker_format_; StickerType sticker_type = pending_new_sticker_set->sticker_type_; auto sticker_count = pending_new_sticker_set->stickers_.size(); vector> input_stickers; input_stickers.reserve(sticker_count); for (size_t i = 0; i < sticker_count; i++) { input_stickers.push_back( get_input_sticker(pending_new_sticker_set->stickers_[i].get(), pending_new_sticker_set->file_ids_[i])); } td_->create_handler(std::move(promise)) ->send(std::move(input_user), pending_new_sticker_set->title_, pending_new_sticker_set->short_name_, sticker_type, sticker_format, std::move(input_stickers), pending_new_sticker_set->software_); } void StickersManager::add_sticker_to_set(UserId user_id, string short_name, tl_object_ptr &&sticker, Promise> &&promise) { TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); short_name = clean_username(strip_empty_characters(short_name, MAX_STICKER_SET_SHORT_NAME_LENGTH)); if (short_name.empty()) { return promise.set_error(Status::Error(400, "Sticker set name must be non-empty")); } const StickerSet *sticker_set = get_sticker_set(short_name_to_sticker_set_id_.get(short_name)); if (sticker_set != nullptr && sticker_set->was_loaded_) { return do_add_sticker_to_set(user_id, short_name, std::move(sticker), std::move(promise)); } do_reload_sticker_set( StickerSetId(), make_tl_object(short_name), 0, PromiseCreator::lambda([actor_id = actor_id(this), user_id, short_name, sticker = std::move(sticker), promise = std::move(promise)](Result result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &StickersManager::do_add_sticker_to_set, user_id, std::move(short_name), std::move(sticker), std::move(promise)); } }), "add_sticker_to_set"); } void StickersManager::do_add_sticker_to_set(UserId user_id, string short_name, tl_object_ptr &&sticker, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); const StickerSet *sticker_set = get_sticker_set(short_name_to_sticker_set_id_.get(short_name)); if (sticker_set == nullptr || !sticker_set->was_loaded_) { return promise.set_error(Status::Error(400, "Sticker set not found")); } auto r_file_id = prepare_input_sticker(sticker.get(), sticker_set->sticker_type_); if (r_file_id.is_error()) { return promise.set_error(r_file_id.move_as_error()); } auto file_id = std::get<0>(r_file_id.ok()); auto is_url = std::get<1>(r_file_id.ok()); auto is_local = std::get<2>(r_file_id.ok()); auto pending_add_sticker_to_set = make_unique(); pending_add_sticker_to_set->short_name_ = short_name; pending_add_sticker_to_set->file_id_ = file_id; pending_add_sticker_to_set->sticker_ = std::move(sticker); pending_add_sticker_to_set->promise_ = std::move(promise); int64 random_id; do { random_id = Random::secure_int64(); } while (random_id == 0 || pending_add_sticker_to_sets_.count(random_id) > 0); pending_add_sticker_to_sets_[random_id] = std::move(pending_add_sticker_to_set); auto on_upload_promise = PromiseCreator::lambda([random_id](Result result) { send_closure(G()->stickers_manager(), &StickersManager::on_added_sticker_uploaded, random_id, std::move(result)); }); if (is_url) { do_upload_sticker_file(user_id, file_id, nullptr, std::move(on_upload_promise)); } else if (is_local) { upload_sticker_file(user_id, file_id, std::move(on_upload_promise)); } else { on_upload_promise.set_value(Unit()); } } void StickersManager::on_added_sticker_uploaded(int64 random_id, Result result) { if (G()->close_flag()) { result = Global::request_aborted_error(); } auto it = pending_add_sticker_to_sets_.find(random_id); CHECK(it != pending_add_sticker_to_sets_.end()); auto pending_add_sticker_to_set = std::move(it->second); CHECK(pending_add_sticker_to_set != nullptr); pending_add_sticker_to_sets_.erase(it); if (result.is_error()) { pending_add_sticker_to_set->promise_.set_error(result.move_as_error()); return; } td_->create_handler(std::move(pending_add_sticker_to_set->promise_)) ->send(pending_add_sticker_to_set->short_name_, get_input_sticker(pending_add_sticker_to_set->sticker_.get(), pending_add_sticker_to_set->file_id_)); } void StickersManager::set_sticker_set_thumbnail(UserId user_id, string short_name, tl_object_ptr &&thumbnail, Promise> &&promise) { TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); short_name = clean_username(strip_empty_characters(short_name, MAX_STICKER_SET_SHORT_NAME_LENGTH)); if (short_name.empty()) { return promise.set_error(Status::Error(400, "Sticker set name must be non-empty")); } const StickerSet *sticker_set = get_sticker_set(short_name_to_sticker_set_id_.get(short_name)); if (sticker_set != nullptr && sticker_set->was_loaded_) { return do_set_sticker_set_thumbnail(user_id, short_name, std::move(thumbnail), std::move(promise)); } do_reload_sticker_set( StickerSetId(), make_tl_object(short_name), 0, PromiseCreator::lambda([actor_id = actor_id(this), user_id, short_name, thumbnail = std::move(thumbnail), promise = std::move(promise)](Result result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &StickersManager::do_set_sticker_set_thumbnail, user_id, std::move(short_name), std::move(thumbnail), std::move(promise)); } }), "set_sticker_set_thumbnail"); } void StickersManager::do_set_sticker_set_thumbnail(UserId user_id, string short_name, tl_object_ptr &&thumbnail, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); const StickerSet *sticker_set = get_sticker_set(short_name_to_sticker_set_id_.get(short_name)); if (sticker_set == nullptr || !sticker_set->was_loaded_) { return promise.set_error(Status::Error(400, "Sticker set not found")); } auto r_file_id = prepare_input_file(thumbnail, sticker_set->sticker_format_, sticker_set->sticker_type_, true); if (r_file_id.is_error()) { return promise.set_error(r_file_id.move_as_error()); } auto file_id = std::get<0>(r_file_id.ok()); auto is_url = std::get<1>(r_file_id.ok()); auto is_local = std::get<2>(r_file_id.ok()); if (!file_id.is_valid()) { td_->create_handler(std::move(promise)) ->send(short_name, telegram_api::make_object()); return; } auto pending_set_sticker_set_thumbnail = make_unique(); pending_set_sticker_set_thumbnail->short_name_ = short_name; pending_set_sticker_set_thumbnail->file_id_ = file_id; pending_set_sticker_set_thumbnail->promise_ = std::move(promise); int64 random_id; do { random_id = Random::secure_int64(); } while (random_id == 0 || pending_set_sticker_set_thumbnails_.count(random_id) > 0); pending_set_sticker_set_thumbnails_[random_id] = std::move(pending_set_sticker_set_thumbnail); auto on_upload_promise = PromiseCreator::lambda([random_id](Result result) { send_closure(G()->stickers_manager(), &StickersManager::on_sticker_set_thumbnail_uploaded, random_id, std::move(result)); }); if (is_url) { do_upload_sticker_file(user_id, file_id, nullptr, std::move(on_upload_promise)); } else if (is_local) { upload_sticker_file(user_id, file_id, std::move(on_upload_promise)); } else { on_upload_promise.set_value(Unit()); } } void StickersManager::on_sticker_set_thumbnail_uploaded(int64 random_id, Result result) { if (G()->close_flag()) { result = Global::request_aborted_error(); } auto it = pending_set_sticker_set_thumbnails_.find(random_id); CHECK(it != pending_set_sticker_set_thumbnails_.end()); auto pending_set_sticker_set_thumbnail = std::move(it->second); CHECK(pending_set_sticker_set_thumbnail != nullptr); pending_set_sticker_set_thumbnails_.erase(it); if (result.is_error()) { pending_set_sticker_set_thumbnail->promise_.set_error(result.move_as_error()); return; } FileView file_view = td_->file_manager_->get_file_view(pending_set_sticker_set_thumbnail->file_id_); CHECK(file_view.has_remote_location()); td_->create_handler(std::move(pending_set_sticker_set_thumbnail->promise_)) ->send(pending_set_sticker_set_thumbnail->short_name_, file_view.main_remote_location().as_input_document()); } string StickersManager::get_sticker_set_short_name(FileId sticker_id) const { string sticker_set_short_name; const Sticker *s = get_sticker(sticker_id); if (s != nullptr && s->set_id_.is_valid()) { const StickerSet *sticker_set = get_sticker_set(s->set_id_); if (sticker_set != nullptr) { return sticker_set->short_name_; } else { return to_string(s->set_id_.get()); } } return string(); } void StickersManager::set_sticker_position_in_set(const tl_object_ptr &sticker, int32 position, Promise &&promise) { if (position < 0) { return promise.set_error(Status::Error(400, "Wrong sticker position specified")); } auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Sticker, sticker, DialogId(), false, false); if (r_file_id.is_error()) { return promise.set_error(Status::Error(400, r_file_id.error().message())); // TODO do not drop error code } auto file_id = r_file_id.move_as_ok(); auto file_view = td_->file_manager_->get_file_view(file_id); if (!file_view.has_remote_location() || !file_view.main_remote_location().is_document() || file_view.main_remote_location().is_web()) { return promise.set_error(Status::Error(400, "Wrong sticker file specified")); } td_->create_handler(std::move(promise)) ->send(get_sticker_set_short_name(file_id), file_view.main_remote_location().as_input_document(), position); } void StickersManager::remove_sticker_from_set(const tl_object_ptr &sticker, Promise &&promise) { auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Sticker, sticker, DialogId(), false, false); if (r_file_id.is_error()) { return promise.set_error(Status::Error(400, r_file_id.error().message())); // TODO do not drop error code } auto file_id = r_file_id.move_as_ok(); auto file_view = td_->file_manager_->get_file_view(file_id); if (!file_view.has_remote_location() || !file_view.main_remote_location().is_document() || file_view.main_remote_location().is_web()) { return promise.set_error(Status::Error(400, "Wrong sticker file specified")); } td_->create_handler(std::move(promise)) ->send(get_sticker_set_short_name(file_id), file_view.main_remote_location().as_input_document()); } vector StickersManager::get_attached_sticker_file_ids(const vector &int_file_ids) { vector result; result.reserve(int_file_ids.size()); for (auto int_file_id : int_file_ids) { FileId file_id(int_file_id, 0); const Sticker *s = get_sticker(file_id); if (s == nullptr) { LOG(WARNING) << "Can't find sticker " << file_id; continue; } if (!s->set_id_.is_valid()) { // only stickers from sticker sets can be attached to files continue; } auto file_view = td_->file_manager_->get_file_view(file_id); CHECK(!file_view.empty()); if (!file_view.has_remote_location()) { LOG(ERROR) << "Sticker " << file_id << " has no remote location"; continue; } if (file_view.remote_location().is_web()) { LOG(ERROR) << "Sticker " << file_id << " is web"; continue; } if (!file_view.remote_location().is_document()) { LOG(ERROR) << "Sticker " << file_id << " is encrypted"; continue; } result.push_back(file_id); if (!td_->auth_manager_->is_bot() && s->type_ != StickerType::CustomEmoji) { add_recent_sticker_by_id(true, file_id); } } return result; } int64 StickersManager::get_sticker_sets_hash(const vector &sticker_set_ids) const { vector numbers; numbers.reserve(sticker_set_ids.size()); for (auto sticker_set_id : sticker_set_ids) { const StickerSet *sticker_set = get_sticker_set(sticker_set_id); CHECK(sticker_set != nullptr); CHECK(sticker_set->is_inited_); numbers.push_back(sticker_set->hash_); } return get_vector_hash(numbers); } int64 StickersManager::get_featured_sticker_sets_hash(StickerType sticker_type) const { auto type = static_cast(sticker_type); vector numbers; numbers.reserve(featured_sticker_set_ids_[type].size() * 2); for (auto sticker_set_id : featured_sticker_set_ids_[type]) { const StickerSet *sticker_set = get_sticker_set(sticker_set_id); CHECK(sticker_set != nullptr); CHECK(sticker_set->is_inited_); numbers.push_back(sticker_set_id.get()); if (!sticker_set->is_viewed_) { numbers.push_back(1); } } return get_vector_hash(numbers); } vector StickersManager::convert_sticker_set_ids(const vector &sticker_set_ids) { return transform(sticker_set_ids, [](StickerSetId sticker_set_id) { return sticker_set_id.get(); }); } vector StickersManager::convert_sticker_set_ids(const vector &sticker_set_ids) { return transform(sticker_set_ids, [](int64 sticker_set_id) { return StickerSetId(sticker_set_id); }); } td_api::object_ptr StickersManager::get_update_installed_sticker_sets_object( StickerType sticker_type) const { auto type = static_cast(sticker_type); return td_api::make_object( get_sticker_type_object(sticker_type), convert_sticker_set_ids(installed_sticker_set_ids_[type])); } void StickersManager::send_update_installed_sticker_sets(bool from_database) { for (int32 type = 0; type < MAX_STICKER_TYPE; type++) { auto sticker_type = static_cast(type); if (need_update_installed_sticker_sets_[type]) { need_update_installed_sticker_sets_[type] = false; if (are_installed_sticker_sets_loaded_[type]) { installed_sticker_sets_hash_[type] = get_sticker_sets_hash(installed_sticker_set_ids_[type]); send_closure(G()->td(), &Td::send_update, get_update_installed_sticker_sets_object(sticker_type)); if (G()->parameters().use_file_db && !from_database && !G()->close_flag()) { LOG(INFO) << "Save installed " << sticker_type << " sticker sets to database"; StickerSetListLogEvent log_event(installed_sticker_set_ids_[type], false); G()->td_db()->get_sqlite_pmc()->set(PSTRING() << "sss" << type, log_event_store(log_event).as_slice().str(), Auto()); } } } } } size_t StickersManager::get_max_featured_sticker_count(StickerType sticker_type) { switch (sticker_type) { case StickerType::Regular: return 5; case StickerType::Mask: return 5; case StickerType::CustomEmoji: return 16; default: UNREACHABLE(); return 0; } } Slice StickersManager::get_featured_sticker_suffix(StickerType sticker_type) { switch (sticker_type) { case StickerType::Regular: return Slice(); case StickerType::Mask: return Slice("1"); case StickerType::CustomEmoji: return Slice("2"); default: UNREACHABLE(); return Slice(); } } td_api::object_ptr StickersManager::get_trending_sticker_sets_object( StickerType sticker_type, const vector &sticker_set_ids) const { auto type = static_cast(sticker_type); auto total_count = static_cast(featured_sticker_set_ids_[type].size()) + (old_featured_sticker_set_count_[type] == -1 ? 1 : old_featured_sticker_set_count_[type]); vector> result; result.reserve(sticker_set_ids.size()); for (auto sticker_set_id : sticker_set_ids) { auto sticker_set_info = get_sticker_set_info_object(sticker_set_id, get_max_featured_sticker_count(sticker_type), are_featured_sticker_sets_premium_[type]); if (sticker_set_info->size_ != 0) { result.push_back(std::move(sticker_set_info)); } } auto result_size = narrow_cast(result.size()); CHECK(total_count >= result_size); return td_api::make_object(total_count, std::move(result), are_featured_sticker_sets_premium_[type]); } td_api::object_ptr StickersManager::get_update_trending_sticker_sets_object( StickerType sticker_type) const { auto type = static_cast(sticker_type); return td_api::make_object( get_sticker_type_object(sticker_type), get_trending_sticker_sets_object(sticker_type, featured_sticker_set_ids_[type])); } void StickersManager::send_update_featured_sticker_sets(StickerType sticker_type) { auto type = static_cast(sticker_type); if (need_update_featured_sticker_sets_[type]) { need_update_featured_sticker_sets_[type] = false; featured_sticker_sets_hash_[type] = get_featured_sticker_sets_hash(sticker_type); send_closure(G()->td(), &Td::send_update, get_update_trending_sticker_sets_object(sticker_type)); } } void StickersManager::reload_recent_stickers(bool is_attached, bool force) { if (G()->close_flag()) { return; } auto &next_load_time = next_recent_stickers_load_time_[is_attached]; if (!td_->auth_manager_->is_bot() && next_load_time >= 0 && (next_load_time < Time::now() || force)) { LOG_IF(INFO, force) << "Reload recent " << (is_attached ? "attached " : "") << "stickers"; next_load_time = -1; td_->create_handler()->send(false, is_attached, recent_stickers_hash_[is_attached]); } } void StickersManager::repair_recent_stickers(bool is_attached, Promise &&promise) { if (td_->auth_manager_->is_bot()) { return promise.set_error(Status::Error(400, "Bots have no recent stickers")); } repair_recent_stickers_queries_[is_attached].push_back(std::move(promise)); if (repair_recent_stickers_queries_[is_attached].size() == 1u) { td_->create_handler()->send(true, is_attached, 0); } } vector StickersManager::get_recent_stickers(bool is_attached, Promise &&promise) { if (!are_recent_stickers_loaded_[is_attached]) { load_recent_stickers(is_attached, std::move(promise)); return {}; } reload_recent_stickers(is_attached, false); promise.set_value(Unit()); return recent_sticker_ids_[is_attached]; } void StickersManager::load_recent_stickers(bool is_attached, Promise &&promise) { if (td_->auth_manager_->is_bot()) { are_recent_stickers_loaded_[is_attached] = true; } if (are_recent_stickers_loaded_[is_attached]) { promise.set_value(Unit()); return; } load_recent_stickers_queries_[is_attached].push_back(std::move(promise)); if (load_recent_stickers_queries_[is_attached].size() == 1u) { if (G()->parameters().use_file_db) { LOG(INFO) << "Trying to load recent " << (is_attached ? "attached " : "") << "stickers from database"; G()->td_db()->get_sqlite_pmc()->get( is_attached ? "ssr1" : "ssr0", PromiseCreator::lambda([is_attached](string value) { send_closure(G()->stickers_manager(), &StickersManager::on_load_recent_stickers_from_database, is_attached, std::move(value)); })); } else { LOG(INFO) << "Trying to load recent " << (is_attached ? "attached " : "") << "stickers from server"; reload_recent_stickers(is_attached, true); } } } void StickersManager::on_load_recent_stickers_from_database(bool is_attached, string value) { if (G()->close_flag()) { return; } if (value.empty()) { LOG(INFO) << "Recent " << (is_attached ? "attached " : "") << "stickers aren't found in database"; reload_recent_stickers(is_attached, true); return; } LOG(INFO) << "Successfully loaded recent " << (is_attached ? "attached " : "") << "stickers list of size " << value.size() << " from database"; StickerListLogEvent log_event; auto status = log_event_parse(log_event, value); if (status.is_error()) { // can't happen unless database is broken, but has been seen in the wild LOG(ERROR) << "Can't load recent stickers: " << status << ' ' << format::as_hex_dump<4>(Slice(value)); return reload_recent_stickers(is_attached, true); } on_load_recent_stickers_finished(is_attached, std::move(log_event.sticker_ids), true); } void StickersManager::on_load_recent_stickers_finished(bool is_attached, vector &&recent_sticker_ids, bool from_database) { if (static_cast(recent_sticker_ids.size()) > recent_stickers_limit_) { recent_sticker_ids.resize(recent_stickers_limit_); } recent_sticker_ids_[is_attached] = std::move(recent_sticker_ids); are_recent_stickers_loaded_[is_attached] = true; send_update_recent_stickers(is_attached, from_database); set_promises(load_recent_stickers_queries_[is_attached]); } void StickersManager::on_get_recent_stickers(bool is_repair, bool is_attached, tl_object_ptr &&stickers_ptr) { CHECK(!td_->auth_manager_->is_bot()); if (!is_repair) { next_recent_stickers_load_time_[is_attached] = Time::now_cached() + Random::fast(30 * 60, 50 * 60); } CHECK(stickers_ptr != nullptr); int32 constructor_id = stickers_ptr->get_id(); if (constructor_id == telegram_api::messages_recentStickersNotModified::ID) { if (is_repair) { return on_get_recent_stickers_failed(true, is_attached, Status::Error(500, "Failed to reload recent stickers")); } LOG(INFO) << (is_attached ? "Attached r" : "R") << "ecent stickers are not modified"; return; } CHECK(constructor_id == telegram_api::messages_recentStickers::ID); auto stickers = move_tl_object_as(stickers_ptr); vector recent_sticker_ids; recent_sticker_ids.reserve(stickers->stickers_.size()); for (auto &document_ptr : stickers->stickers_) { auto sticker_id = on_get_sticker_document(std::move(document_ptr), StickerFormat::Unknown).second; if (!sticker_id.is_valid()) { continue; } recent_sticker_ids.push_back(sticker_id); } if (is_repair) { set_promises(repair_recent_stickers_queries_[is_attached]); } else { on_load_recent_stickers_finished(is_attached, std::move(recent_sticker_ids)); LOG_IF(ERROR, recent_stickers_hash_[is_attached] != stickers->hash_) << "Stickers hash mismatch"; } } void StickersManager::on_get_recent_stickers_failed(bool is_repair, bool is_attached, Status error) { CHECK(error.is_error()); if (!is_repair) { next_recent_stickers_load_time_[is_attached] = Time::now_cached() + Random::fast(5, 10); } fail_promises(is_repair ? repair_recent_stickers_queries_[is_attached] : load_recent_stickers_queries_[is_attached], std::move(error)); } int64 StickersManager::get_recent_stickers_hash(const vector &sticker_ids) const { vector numbers; numbers.reserve(sticker_ids.size()); for (auto sticker_id : sticker_ids) { auto sticker = get_sticker(sticker_id); CHECK(sticker != nullptr); auto file_view = td_->file_manager_->get_file_view(sticker_id); CHECK(file_view.has_remote_location()); if (!file_view.remote_location().is_document()) { LOG(ERROR) << "Recent sticker remote location is not document: " << file_view.remote_location(); continue; } numbers.push_back(file_view.remote_location().get_id()); } return get_vector_hash(numbers); } FileSourceId StickersManager::get_recent_stickers_file_source_id(int is_attached) { if (!recent_stickers_file_source_id_[is_attached].is_valid()) { recent_stickers_file_source_id_[is_attached] = td_->file_reference_manager_->create_recent_stickers_file_source(is_attached != 0); } return recent_stickers_file_source_id_[is_attached]; } void StickersManager::add_recent_sticker(bool is_attached, const tl_object_ptr &input_file, Promise &&promise) { if (!are_recent_stickers_loaded_[is_attached]) { load_recent_stickers(is_attached, std::move(promise)); return; } auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Sticker, input_file, DialogId(), false, false); if (r_file_id.is_error()) { return promise.set_error(Status::Error(400, r_file_id.error().message())); // TODO do not drop error code } add_recent_sticker_impl(is_attached, r_file_id.ok(), true, std::move(promise)); } void StickersManager::send_save_recent_sticker_query(bool is_attached, FileId sticker_id, bool unsave, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); // TODO invokeAfter and log event auto file_view = td_->file_manager_->get_file_view(sticker_id); CHECK(file_view.has_remote_location()); CHECK(file_view.remote_location().is_document()); CHECK(!file_view.remote_location().is_web()); td_->create_handler(std::move(promise)) ->send(is_attached, sticker_id, file_view.remote_location().as_input_document(), unsave); } void StickersManager::add_recent_sticker_by_id(bool is_attached, FileId sticker_id) { // TODO log event add_recent_sticker_impl(is_attached, sticker_id, false, Auto()); } void StickersManager::add_recent_sticker_impl(bool is_attached, FileId sticker_id, bool add_on_server, Promise &&promise) { CHECK(!td_->auth_manager_->is_bot()); LOG(INFO) << "Add recent " << (is_attached ? "attached " : "") << "sticker " << sticker_id; if (!are_recent_stickers_loaded_[is_attached]) { load_recent_stickers(is_attached, PromiseCreator::lambda([is_attached, sticker_id, add_on_server, promise = std::move(promise)](Result<> result) mutable { if (result.is_ok()) { send_closure(G()->stickers_manager(), &StickersManager::add_recent_sticker_impl, is_attached, sticker_id, add_on_server, std::move(promise)); } else { promise.set_error(result.move_as_error()); } })); return; } auto is_equal = [sticker_id](FileId file_id) { return file_id == sticker_id || (file_id.get_remote() == sticker_id.get_remote() && sticker_id.get_remote() != 0); }; vector &sticker_ids = recent_sticker_ids_[is_attached]; if (!sticker_ids.empty() && is_equal(sticker_ids[0])) { if (sticker_ids[0].get_remote() == 0 && sticker_id.get_remote() != 0) { sticker_ids[0] = sticker_id; save_recent_stickers_to_database(is_attached); } return promise.set_value(Unit()); } auto sticker = get_sticker(sticker_id); if (sticker == nullptr) { return promise.set_error(Status::Error(400, "Sticker not found")); } if (!sticker->set_id_.is_valid()) { return promise.set_error(Status::Error(400, "Stickers without sticker set can't be added to recent")); } if (sticker->type_ == StickerType::CustomEmoji) { return promise.set_error(Status::Error(400, "Custom emoji stickers can't be added to recent")); } auto file_view = td_->file_manager_->get_file_view(sticker_id); if (!file_view.has_remote_location()) { return promise.set_error(Status::Error(400, "Can save only sent stickers")); } if (file_view.remote_location().is_web()) { return promise.set_error(Status::Error(400, "Can't save web stickers")); } if (!file_view.remote_location().is_document()) { return promise.set_error(Status::Error(400, "Can't save encrypted stickers")); } auto it = std::find_if(sticker_ids.begin(), sticker_ids.end(), is_equal); if (it == sticker_ids.end()) { if (static_cast(sticker_ids.size()) == recent_stickers_limit_) { sticker_ids.back() = sticker_id; } else { sticker_ids.push_back(sticker_id); } it = sticker_ids.end() - 1; } std::rotate(sticker_ids.begin(), it, it + 1); if (sticker_ids[0].get_remote() == 0 && sticker_id.get_remote() != 0) { sticker_ids[0] = sticker_id; } send_update_recent_stickers(is_attached); if (add_on_server) { send_save_recent_sticker_query(is_attached, sticker_id, false, std::move(promise)); } } void StickersManager::remove_recent_sticker(bool is_attached, const tl_object_ptr &input_file, Promise &&promise) { if (!are_recent_stickers_loaded_[is_attached]) { load_recent_stickers(is_attached, std::move(promise)); return; } auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Sticker, input_file, DialogId(), false, false); if (r_file_id.is_error()) { return promise.set_error(Status::Error(400, r_file_id.error().message())); // TODO do not drop error code } vector &sticker_ids = recent_sticker_ids_[is_attached]; FileId file_id = r_file_id.ok(); if (!td::remove(sticker_ids, file_id)) { return promise.set_value(Unit()); } auto sticker = get_sticker(file_id); if (sticker == nullptr) { return promise.set_error(Status::Error(400, "Sticker not found")); } send_save_recent_sticker_query(is_attached, file_id, true, std::move(promise)); send_update_recent_stickers(is_attached); } void StickersManager::clear_recent_stickers(bool is_attached, Promise &&promise) { if (!are_recent_stickers_loaded_[is_attached]) { load_recent_stickers(is_attached, std::move(promise)); return; } vector &sticker_ids = recent_sticker_ids_[is_attached]; if (sticker_ids.empty()) { return promise.set_value(Unit()); } // TODO invokeAfter td_->create_handler(std::move(promise))->send(is_attached); sticker_ids.clear(); send_update_recent_stickers(is_attached); } td_api::object_ptr StickersManager::get_update_recent_stickers_object( int is_attached) const { return td_api::make_object( is_attached != 0, td_->file_manager_->get_file_ids_object(recent_sticker_ids_[is_attached])); } void StickersManager::send_update_recent_stickers(bool is_attached, bool from_database) { if (!are_recent_stickers_loaded_[is_attached]) { return; } vector new_recent_sticker_file_ids; for (auto &sticker_id : recent_sticker_ids_[is_attached]) { append(new_recent_sticker_file_ids, get_sticker_file_ids(sticker_id)); } std::sort(new_recent_sticker_file_ids.begin(), new_recent_sticker_file_ids.end()); if (new_recent_sticker_file_ids != recent_sticker_file_ids_[is_attached]) { td_->file_manager_->change_files_source(get_recent_stickers_file_source_id(is_attached), recent_sticker_file_ids_[is_attached], new_recent_sticker_file_ids); recent_sticker_file_ids_[is_attached] = std::move(new_recent_sticker_file_ids); } recent_stickers_hash_[is_attached] = get_recent_stickers_hash(recent_sticker_ids_[is_attached]); send_closure(G()->td(), &Td::send_update, get_update_recent_stickers_object(is_attached)); if (!from_database) { save_recent_stickers_to_database(is_attached != 0); } } void StickersManager::save_recent_stickers_to_database(bool is_attached) { if (G()->parameters().use_file_db && !G()->close_flag()) { LOG(INFO) << "Save recent " << (is_attached ? "attached " : "") << "stickers to database"; StickerListLogEvent log_event(recent_sticker_ids_[is_attached]); G()->td_db()->get_sqlite_pmc()->set(is_attached ? "ssr1" : "ssr0", log_event_store(log_event).as_slice().str(), Auto()); } } void StickersManager::on_update_animated_emoji_zoom() { animated_emoji_zoom_ = static_cast(td_->option_manager_->get_option_integer("animated_emoji_zoom", 625000000)) * 1e-9; } void StickersManager::on_update_recent_stickers_limit() { auto recent_stickers_limit = narrow_cast(td_->option_manager_->get_option_integer("recent_stickers_limit", 200)); if (recent_stickers_limit != recent_stickers_limit_) { if (recent_stickers_limit > 0) { LOG(INFO) << "Update recent stickers limit to " << recent_stickers_limit; recent_stickers_limit_ = recent_stickers_limit; for (int is_attached = 0; is_attached < 2; is_attached++) { if (static_cast(recent_sticker_ids_[is_attached].size()) > recent_stickers_limit) { recent_sticker_ids_[is_attached].resize(recent_stickers_limit); send_update_recent_stickers(is_attached != 0); } } } else { LOG(ERROR) << "Receive wrong recent stickers limit = " << recent_stickers_limit; } } } void StickersManager::on_update_favorite_stickers_limit() { auto favorite_stickers_limit = narrow_cast(td_->option_manager_->get_option_integer("favorite_stickers_limit", 5)); if (favorite_stickers_limit != favorite_stickers_limit_) { if (favorite_stickers_limit > 0) { LOG(INFO) << "Update favorite stickers limit to " << favorite_stickers_limit; favorite_stickers_limit_ = favorite_stickers_limit; if (static_cast(favorite_sticker_ids_.size()) > favorite_stickers_limit) { favorite_sticker_ids_.resize(favorite_stickers_limit); send_update_favorite_stickers(); } } else { LOG(ERROR) << "Receive wrong favorite stickers limit = " << favorite_stickers_limit; } } } void StickersManager::reload_favorite_stickers(bool force) { if (G()->close_flag()) { return; } auto &next_load_time = next_favorite_stickers_load_time_; if (!td_->auth_manager_->is_bot() && next_load_time >= 0 && (next_load_time < Time::now() || force)) { LOG_IF(INFO, force) << "Reload favorite stickers"; next_load_time = -1; td_->create_handler()->send(false, get_favorite_stickers_hash()); } } void StickersManager::repair_favorite_stickers(Promise &&promise) { if (td_->auth_manager_->is_bot()) { return promise.set_error(Status::Error(400, "Bots have no favorite stickers")); } repair_favorite_stickers_queries_.push_back(std::move(promise)); if (repair_favorite_stickers_queries_.size() == 1u) { td_->create_handler()->send(true, 0); } } vector StickersManager::get_favorite_stickers(Promise &&promise) { if (!are_favorite_stickers_loaded_) { load_favorite_stickers(std::move(promise)); return {}; } reload_favorite_stickers(false); promise.set_value(Unit()); return favorite_sticker_ids_; } void StickersManager::load_favorite_stickers(Promise &&promise) { if (td_->auth_manager_->is_bot()) { are_favorite_stickers_loaded_ = true; } if (are_favorite_stickers_loaded_) { promise.set_value(Unit()); return; } load_favorite_stickers_queries_.push_back(std::move(promise)); if (load_favorite_stickers_queries_.size() == 1u) { if (G()->parameters().use_file_db) { LOG(INFO) << "Trying to load favorite stickers from database"; G()->td_db()->get_sqlite_pmc()->get("ssfav", PromiseCreator::lambda([](string value) { send_closure(G()->stickers_manager(), &StickersManager::on_load_favorite_stickers_from_database, std::move(value)); })); } else { LOG(INFO) << "Trying to load favorite stickers from server"; reload_favorite_stickers(true); } } } void StickersManager::on_load_favorite_stickers_from_database(const string &value) { if (G()->close_flag()) { return; } if (value.empty()) { LOG(INFO) << "Favorite stickers aren't found in database"; reload_favorite_stickers(true); return; } LOG(INFO) << "Successfully loaded favorite stickers list of size " << value.size() << " from database"; StickerListLogEvent log_event; auto status = log_event_parse(log_event, value); if (status.is_error()) { // can't happen unless database is broken, but has been seen in the wild LOG(ERROR) << "Can't load favorite stickers: " << status << ' ' << format::as_hex_dump<4>(Slice(value)); return reload_favorite_stickers(true); } on_load_favorite_stickers_finished(std::move(log_event.sticker_ids), true); } void StickersManager::on_load_favorite_stickers_finished(vector &&favorite_sticker_ids, bool from_database) { if (static_cast(favorite_sticker_ids.size()) > favorite_stickers_limit_) { favorite_sticker_ids.resize(favorite_stickers_limit_); } favorite_sticker_ids_ = std::move(favorite_sticker_ids); are_favorite_stickers_loaded_ = true; send_update_favorite_stickers(from_database); set_promises(load_favorite_stickers_queries_); } void StickersManager::on_get_favorite_stickers( bool is_repair, tl_object_ptr &&favorite_stickers_ptr) { CHECK(!td_->auth_manager_->is_bot()); if (!is_repair) { next_favorite_stickers_load_time_ = Time::now_cached() + Random::fast(30 * 60, 50 * 60); } CHECK(favorite_stickers_ptr != nullptr); int32 constructor_id = favorite_stickers_ptr->get_id(); if (constructor_id == telegram_api::messages_favedStickersNotModified::ID) { if (is_repair) { return on_get_favorite_stickers_failed(true, Status::Error(500, "Failed to reload favorite stickers")); } LOG(INFO) << "Favorite stickers are not modified"; return; } CHECK(constructor_id == telegram_api::messages_favedStickers::ID); auto favorite_stickers = move_tl_object_as(favorite_stickers_ptr); // TODO use favorite_stickers->packs_ vector favorite_sticker_ids; favorite_sticker_ids.reserve(favorite_stickers->stickers_.size()); for (auto &document_ptr : favorite_stickers->stickers_) { auto sticker_id = on_get_sticker_document(std::move(document_ptr), StickerFormat::Unknown).second; if (!sticker_id.is_valid()) { continue; } favorite_sticker_ids.push_back(sticker_id); } if (is_repair) { set_promises(repair_favorite_stickers_queries_); } else { on_load_favorite_stickers_finished(std::move(favorite_sticker_ids)); LOG_IF(ERROR, get_favorite_stickers_hash() != favorite_stickers->hash_) << "Favorite stickers hash mismatch"; } } void StickersManager::on_get_favorite_stickers_failed(bool is_repair, Status error) { CHECK(error.is_error()); if (!is_repair) { next_favorite_stickers_load_time_ = Time::now_cached() + Random::fast(5, 10); } fail_promises(is_repair ? repair_favorite_stickers_queries_ : load_favorite_stickers_queries_, std::move(error)); } int64 StickersManager::get_favorite_stickers_hash() const { return get_recent_stickers_hash(favorite_sticker_ids_); } FileSourceId StickersManager::get_app_config_file_source_id() { if (!app_config_file_source_id_.is_valid()) { app_config_file_source_id_ = td_->file_reference_manager_->create_app_config_file_source(); } return app_config_file_source_id_; } FileSourceId StickersManager::get_favorite_stickers_file_source_id() { if (!favorite_stickers_file_source_id_.is_valid()) { favorite_stickers_file_source_id_ = td_->file_reference_manager_->create_favorite_stickers_file_source(); } return favorite_stickers_file_source_id_; } void StickersManager::add_favorite_sticker(const tl_object_ptr &input_file, Promise &&promise) { if (!are_favorite_stickers_loaded_) { load_favorite_stickers(std::move(promise)); return; } auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Sticker, input_file, DialogId(), false, false); if (r_file_id.is_error()) { return promise.set_error(Status::Error(400, r_file_id.error().message())); // TODO do not drop error code } add_favorite_sticker_impl(r_file_id.ok(), true, std::move(promise)); } void StickersManager::send_fave_sticker_query(FileId sticker_id, bool unsave, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); // TODO invokeAfter and log event auto file_view = td_->file_manager_->get_file_view(sticker_id); CHECK(file_view.has_remote_location()); CHECK(file_view.remote_location().is_document()); CHECK(!file_view.remote_location().is_web()); td_->create_handler(std::move(promise)) ->send(sticker_id, file_view.remote_location().as_input_document(), unsave); } void StickersManager::add_favorite_sticker_by_id(FileId sticker_id) { // TODO log event add_favorite_sticker_impl(sticker_id, false, Auto()); } void StickersManager::add_favorite_sticker_impl(FileId sticker_id, bool add_on_server, Promise &&promise) { CHECK(!td_->auth_manager_->is_bot()); if (!are_favorite_stickers_loaded_) { load_favorite_stickers( PromiseCreator::lambda([sticker_id, add_on_server, promise = std::move(promise)](Result<> result) mutable { if (result.is_ok()) { send_closure(G()->stickers_manager(), &StickersManager::add_favorite_sticker_impl, sticker_id, add_on_server, std::move(promise)); } else { promise.set_error(result.move_as_error()); } })); return; } auto is_equal = [sticker_id](FileId file_id) { return file_id == sticker_id || (file_id.get_remote() == sticker_id.get_remote() && sticker_id.get_remote() != 0); }; if (!favorite_sticker_ids_.empty() && is_equal(favorite_sticker_ids_[0])) { if (favorite_sticker_ids_[0].get_remote() == 0 && sticker_id.get_remote() != 0) { favorite_sticker_ids_[0] = sticker_id; save_favorite_stickers_to_database(); } return promise.set_value(Unit()); } auto sticker = get_sticker(sticker_id); if (sticker == nullptr) { return promise.set_error(Status::Error(400, "Sticker not found")); } if (!sticker->set_id_.is_valid()) { return promise.set_error(Status::Error(400, "Stickers without sticker set can't be added to favorite")); } if (sticker->type_ == StickerType::CustomEmoji) { return promise.set_error(Status::Error(400, "Custom emoji stickers can't be added to favorite")); } auto file_view = td_->file_manager_->get_file_view(sticker_id); if (!file_view.has_remote_location()) { return promise.set_error(Status::Error(400, "Can add to favorites only sent stickers")); } if (file_view.remote_location().is_web()) { return promise.set_error(Status::Error(400, "Can't add to favorites web stickers")); } if (!file_view.remote_location().is_document()) { return promise.set_error(Status::Error(400, "Can't add to favorites encrypted stickers")); } auto it = std::find_if(favorite_sticker_ids_.begin(), favorite_sticker_ids_.end(), is_equal); if (it == favorite_sticker_ids_.end()) { if (static_cast(favorite_sticker_ids_.size()) == favorite_stickers_limit_) { favorite_sticker_ids_.back() = sticker_id; } else { favorite_sticker_ids_.push_back(sticker_id); } it = favorite_sticker_ids_.end() - 1; } std::rotate(favorite_sticker_ids_.begin(), it, it + 1); if (favorite_sticker_ids_[0].get_remote() == 0 && sticker_id.get_remote() != 0) { favorite_sticker_ids_[0] = sticker_id; } send_update_favorite_stickers(); if (add_on_server) { send_fave_sticker_query(sticker_id, false, std::move(promise)); } } void StickersManager::remove_favorite_sticker(const tl_object_ptr &input_file, Promise &&promise) { if (!are_favorite_stickers_loaded_) { load_favorite_stickers(std::move(promise)); return; } auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Sticker, input_file, DialogId(), false, false); if (r_file_id.is_error()) { return promise.set_error(Status::Error(400, r_file_id.error().message())); // TODO do not drop error code } FileId file_id = r_file_id.ok(); auto is_equal = [sticker_id = file_id](FileId file_id) { return file_id == sticker_id || (file_id.get_remote() == sticker_id.get_remote() && sticker_id.get_remote() != 0); }; if (!td::remove_if(favorite_sticker_ids_, is_equal)) { return promise.set_value(Unit()); } auto sticker = get_sticker(file_id); if (sticker == nullptr) { return promise.set_error(Status::Error(400, "Sticker not found")); } send_fave_sticker_query(file_id, true, std::move(promise)); send_update_favorite_stickers(); } td_api::object_ptr StickersManager::get_update_favorite_stickers_object() const { return td_api::make_object( td_->file_manager_->get_file_ids_object(favorite_sticker_ids_)); } void StickersManager::send_update_favorite_stickers(bool from_database) { if (are_favorite_stickers_loaded_) { vector new_favorite_sticker_file_ids; for (auto &sticker_id : favorite_sticker_ids_) { append(new_favorite_sticker_file_ids, get_sticker_file_ids(sticker_id)); } std::sort(new_favorite_sticker_file_ids.begin(), new_favorite_sticker_file_ids.end()); if (new_favorite_sticker_file_ids != favorite_sticker_file_ids_) { td_->file_manager_->change_files_source(get_favorite_stickers_file_source_id(), favorite_sticker_file_ids_, new_favorite_sticker_file_ids); favorite_sticker_file_ids_ = std::move(new_favorite_sticker_file_ids); } send_closure(G()->td(), &Td::send_update, get_update_favorite_stickers_object()); if (!from_database) { save_favorite_stickers_to_database(); } } } void StickersManager::save_favorite_stickers_to_database() { if (G()->parameters().use_file_db && !G()->close_flag()) { LOG(INFO) << "Save favorite stickers to database"; StickerListLogEvent log_event(favorite_sticker_ids_); G()->td_db()->get_sqlite_pmc()->set("ssfav", log_event_store(log_event).as_slice().str(), Auto()); } } vector StickersManager::get_sticker_emojis(const tl_object_ptr &input_file, Promise &&promise) { auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Sticker, input_file, DialogId(), false, false); if (r_file_id.is_error()) { promise.set_error(Status::Error(400, r_file_id.error().message())); // TODO do not drop error code return {}; } FileId file_id = r_file_id.ok(); auto sticker = get_sticker(file_id); if (sticker == nullptr) { promise.set_value(Unit()); return {}; } if (!sticker->set_id_.is_valid()) { promise.set_value(Unit()); return {}; } auto file_view = td_->file_manager_->get_file_view(file_id); if (!file_view.has_remote_location()) { promise.set_value(Unit()); return {}; } if (!file_view.remote_location().is_document()) { promise.set_value(Unit()); return {}; } if (file_view.remote_location().is_web()) { promise.set_value(Unit()); return {}; } const StickerSet *sticker_set = get_sticker_set(sticker->set_id_); if (update_sticker_set_cache(sticker_set, promise)) { return {}; } promise.set_value(Unit()); auto it = sticker_set->sticker_emojis_map_.find(file_id); if (it == sticker_set->sticker_emojis_map_.end()) { return {}; } return it->second; } string StickersManager::get_emoji_language_code_version_database_key(const string &language_code) { return PSTRING() << "emojiv$" << language_code; } int32 StickersManager::get_emoji_language_code_version(const string &language_code) { auto it = emoji_language_code_versions_.find(language_code); if (it != emoji_language_code_versions_.end()) { return it->second; } if (language_code.empty()) { return 0; } auto &result = emoji_language_code_versions_[language_code]; result = to_integer( G()->td_db()->get_sqlite_sync_pmc()->get(get_emoji_language_code_version_database_key(language_code))); return result; } string StickersManager::get_emoji_language_code_last_difference_time_database_key(const string &language_code) { return PSTRING() << "emojid$" << language_code; } double StickersManager::get_emoji_language_code_last_difference_time(const string &language_code) { auto it = emoji_language_code_last_difference_times_.find(language_code); if (it != emoji_language_code_last_difference_times_.end()) { return it->second; } if (language_code.empty()) { return Time::now_cached() - G()->unix_time(); } auto &result = emoji_language_code_last_difference_times_[language_code]; auto old_unix_time = to_integer(G()->td_db()->get_sqlite_sync_pmc()->get( get_emoji_language_code_last_difference_time_database_key(language_code))); int32 passed_time = max(static_cast(0), G()->unix_time() - old_unix_time); result = Time::now_cached() - passed_time; return result; } string StickersManager::get_language_emojis_database_key(const string &language_code, const string &text) { return PSTRING() << "emoji$" << language_code << '$' << text; } vector StickersManager::search_language_emojis(const string &language_code, const string &text, bool exact_match) { LOG(INFO) << "Search for \"" << text << "\" in language " << language_code; auto key = get_language_emojis_database_key(language_code, text); if (exact_match) { string emojis = G()->td_db()->get_sqlite_sync_pmc()->get(key); return full_split(emojis, '$'); } else { vector result; G()->td_db()->get_sqlite_sync_pmc()->get_by_prefix(key, [&result](Slice key, Slice value) { for (auto &emoji : full_split(value, '$')) { result.push_back(emoji.str()); } return true; }); return result; } } string StickersManager::get_emoji_language_codes_database_key(const vector &language_codes) { return PSTRING() << "emojilc$" << implode(language_codes, '$'); } void StickersManager::load_language_codes(vector language_codes, string key, Promise &&promise) { auto &promises = load_language_codes_queries_[key]; promises.push_back(std::move(promise)); if (promises.size() != 1) { // query has already been sent, just wait for the result return; } auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), key = std::move(key)](Result> &&result) { send_closure(actor_id, &StickersManager::on_get_language_codes, key, std::move(result)); }); td_->create_handler(std::move(query_promise))->send(std::move(language_codes)); } void StickersManager::on_get_language_codes(const string &key, Result> &&result) { auto queries_it = load_language_codes_queries_.find(key); CHECK(queries_it != load_language_codes_queries_.end()); CHECK(!queries_it->second.empty()); auto promises = std::move(queries_it->second); load_language_codes_queries_.erase(queries_it); if (result.is_error()) { if (!G()->is_expected_error(result.error())) { LOG(ERROR) << "Receive " << result.error() << " from GetEmojiKeywordsLanguageQuery"; } fail_promises(promises, result.move_as_error()); return; } auto language_codes = result.move_as_ok(); LOG(INFO) << "Receive language codes " << language_codes << " for emojis search with key " << key; td::remove_if(language_codes, [](const string &language_code) { if (language_code.empty() || language_code.find('$') != string::npos) { LOG(ERROR) << "Receive language_code \"" << language_code << '"'; return true; } return false; }); if (language_codes.empty()) { LOG(ERROR) << "Language codes list is empty"; language_codes.emplace_back("en"); } td::unique(language_codes); auto it = emoji_language_codes_.find(key); CHECK(it != emoji_language_codes_.end()); if (it->second != language_codes) { LOG(INFO) << "Update emoji language codes for " << key << " to " << language_codes; if (!G()->close_flag()) { CHECK(G()->parameters().use_file_db); G()->td_db()->get_sqlite_pmc()->set(key, implode(language_codes, '$'), Auto()); } it->second = std::move(language_codes); } set_promises(promises); } vector StickersManager::get_emoji_language_codes(const vector &input_language_codes, Slice text, Promise &promise) { vector language_codes = td_->language_pack_manager_.get_actor_unsafe()->get_used_language_codes(); auto system_language_code = G()->mtproto_header().get_system_language_code(); if (system_language_code.size() >= 2 && system_language_code.find('$') == string::npos && (system_language_code.size() == 2 || system_language_code[2] == '-')) { language_codes.push_back(system_language_code.substr(0, 2)); } for (auto &input_language_code : input_language_codes) { if (input_language_code.size() >= 2 && input_language_code.find('$') == string::npos && (input_language_code.size() == 2 || input_language_code[2] == '-')) { language_codes.push_back(input_language_code.substr(0, 2)); } } if (!text.empty()) { uint32 code = 0; next_utf8_unsafe(text.ubegin(), &code); if ((0x410 <= code && code <= 0x44F) || code == 0x401 || code == 0x451) { // the first letter is cyrillic if (!td::contains(language_codes, "ru") && !td::contains(language_codes, "uk") && !td::contains(language_codes, "bg") && !td::contains(language_codes, "be") && !td::contains(language_codes, "mk") && !td::contains(language_codes, "sr") && !td::contains(language_codes, "mn") && !td::contains(language_codes, "ky") && !td::contains(language_codes, "kk") && !td::contains(language_codes, "uz") && !td::contains(language_codes, "tk")) { language_codes.push_back("ru"); } } } if (language_codes.empty()) { LOG(INFO) << "List of language codes is empty"; language_codes.push_back("en"); } td::unique(language_codes); LOG(DEBUG) << "Have language codes " << language_codes; auto key = get_emoji_language_codes_database_key(language_codes); auto it = emoji_language_codes_.find(key); if (it == emoji_language_codes_.end()) { it = emoji_language_codes_.emplace(key, full_split(G()->td_db()->get_sqlite_sync_pmc()->get(key), '$')).first; td::remove_if(it->second, [](const string &language_code) { if (language_code.empty() || language_code.find('$') != string::npos) { LOG(ERROR) << "Loaded language_code \"" << language_code << '"'; return true; } return false; }); } if (it->second.empty()) { load_language_codes(std::move(language_codes), std::move(key), std::move(promise)); } else { LOG(DEBUG) << "Have emoji language codes " << it->second; double now = Time::now_cached(); for (auto &language_code : it->second) { double last_difference_time = get_emoji_language_code_last_difference_time(language_code); if (last_difference_time < now - EMOJI_KEYWORDS_UPDATE_DELAY && get_emoji_language_code_version(language_code) != 0) { load_emoji_keywords_difference(language_code); } } if (reloaded_emoji_keywords_.insert(key).second) { load_language_codes(std::move(language_codes), std::move(key), Auto()); } } return it->second; } void StickersManager::load_emoji_keywords(const string &language_code, Promise &&promise) { auto &promises = load_emoji_keywords_queries_[language_code]; promises.push_back(std::move(promise)); if (promises.size() != 1) { // query has already been sent, just wait for the result return; } auto query_promise = PromiseCreator::lambda( [actor_id = actor_id(this), language_code](Result> &&result) mutable { send_closure(actor_id, &StickersManager::on_get_emoji_keywords, language_code, std::move(result)); }); td_->create_handler(std::move(query_promise))->send(language_code); } void StickersManager::on_get_emoji_keywords( const string &language_code, Result> &&result) { auto it = load_emoji_keywords_queries_.find(language_code); CHECK(it != load_emoji_keywords_queries_.end()); auto promises = std::move(it->second); CHECK(!promises.empty()); load_emoji_keywords_queries_.erase(it); if (result.is_error()) { if (!G()->is_expected_error(result.error())) { LOG(ERROR) << "Receive " << result.error() << " from GetEmojiKeywordsQuery"; } fail_promises(promises, result.move_as_error()); return; } auto version = get_emoji_language_code_version(language_code); CHECK(version == 0); MultiPromiseActorSafe mpas{"SaveEmojiKeywordsMultiPromiseActor"}; for (auto &promise : promises) { mpas.add_promise(std::move(promise)); } auto lock = mpas.get_promise(); auto keywords = result.move_as_ok(); LOG(INFO) << "Receive " << keywords->keywords_.size() << " emoji keywords for language " << language_code; LOG_IF(ERROR, language_code != keywords->lang_code_) << "Receive keywords for " << keywords->lang_code_ << " instead of " << language_code; LOG_IF(ERROR, keywords->from_version_ != 0) << "Receive keywords from version " << keywords->from_version_; version = keywords->version_; if (version <= 0) { LOG(ERROR) << "Receive keywords of version " << version; version = 1; } for (auto &keyword_ptr : keywords->keywords_) { switch (keyword_ptr->get_id()) { case telegram_api::emojiKeyword::ID: { auto keyword = telegram_api::move_object_as(keyword_ptr); auto text = utf8_to_lower(keyword->keyword_); bool is_good = true; for (auto &emoji : keyword->emoticons_) { if (emoji.find('$') != string::npos) { LOG(ERROR) << "Receive emoji \"" << emoji << "\" from server for " << text; is_good = false; } } if (is_good && !G()->close_flag()) { CHECK(G()->parameters().use_file_db); G()->td_db()->get_sqlite_pmc()->set(get_language_emojis_database_key(language_code, text), implode(keyword->emoticons_, '$'), mpas.get_promise()); } break; } case telegram_api::emojiKeywordDeleted::ID: LOG(ERROR) << "Receive emojiKeywordDeleted in keywords for " << language_code; break; default: UNREACHABLE(); } } if (!G()->close_flag()) { CHECK(G()->parameters().use_file_db); G()->td_db()->get_sqlite_pmc()->set(get_emoji_language_code_version_database_key(language_code), to_string(version), mpas.get_promise()); G()->td_db()->get_sqlite_pmc()->set(get_emoji_language_code_last_difference_time_database_key(language_code), to_string(G()->unix_time()), mpas.get_promise()); } emoji_language_code_versions_[language_code] = version; emoji_language_code_last_difference_times_[language_code] = static_cast(Time::now_cached()); lock.set_value(Unit()); } void StickersManager::load_emoji_keywords_difference(const string &language_code) { LOG(INFO) << "Load emoji keywords difference for language " << language_code; CHECK(!language_code.empty()); emoji_language_code_last_difference_times_[language_code] = Time::now_cached() + 1e9; // prevent simultaneous requests int32 from_version = get_emoji_language_code_version(language_code); auto query_promise = PromiseCreator::lambda( [actor_id = actor_id(this), language_code, from_version](Result> &&result) mutable { send_closure(actor_id, &StickersManager::on_get_emoji_keywords_difference, language_code, from_version, std::move(result)); }); td_->create_handler(std::move(query_promise))->send(language_code, from_version); } void StickersManager::on_get_emoji_keywords_difference( const string &language_code, int32 from_version, Result> &&result) { if (G()->close_flag()) { result = Global::request_aborted_error(); } if (result.is_error()) { if (!G()->is_expected_error(result.error())) { LOG(ERROR) << "Receive " << result.error() << " from GetEmojiKeywordsDifferenceQuery"; } emoji_language_code_last_difference_times_[language_code] = Time::now_cached() - EMOJI_KEYWORDS_UPDATE_DELAY - 2; return; } auto version = get_emoji_language_code_version(language_code); CHECK(version == from_version); auto keywords = result.move_as_ok(); LOG(INFO) << "Receive " << keywords->keywords_.size() << " emoji keywords difference for language " << language_code; LOG_IF(ERROR, language_code != keywords->lang_code_) << "Receive keywords for " << keywords->lang_code_ << " instead of " << language_code; LOG_IF(ERROR, keywords->from_version_ != from_version) << "Receive keywords from version " << keywords->from_version_ << " instead of " << from_version; if (keywords->version_ < version) { LOG(ERROR) << "Receive keywords of version " << keywords->version_ << ", but have of version " << version; keywords->version_ = version; } version = keywords->version_; FlatHashMap key_values; key_values.emplace(get_emoji_language_code_version_database_key(language_code), to_string(version)); key_values.emplace(get_emoji_language_code_last_difference_time_database_key(language_code), to_string(G()->unix_time())); for (auto &keyword_ptr : keywords->keywords_) { switch (keyword_ptr->get_id()) { case telegram_api::emojiKeyword::ID: { auto keyword = telegram_api::move_object_as(keyword_ptr); auto text = utf8_to_lower(keyword->keyword_); bool is_good = true; for (auto &emoji : keyword->emoticons_) { if (emoji.find('$') != string::npos) { LOG(ERROR) << "Receive emoji \"" << emoji << "\" from server for " << text; is_good = false; } } if (is_good) { vector emojis = search_language_emojis(language_code, text, true); bool is_changed = false; for (auto &emoji : keyword->emoticons_) { if (!td::contains(emojis, emoji)) { emojis.push_back(emoji); is_changed = true; } } if (is_changed) { key_values.emplace(get_language_emojis_database_key(language_code, text), implode(emojis, '$')); } else { LOG(INFO) << "Emoji keywords not changed for \"" << text << "\" from version " << from_version << " to version " << version; } } break; } case telegram_api::emojiKeywordDeleted::ID: { auto keyword = telegram_api::move_object_as(keyword_ptr); auto text = utf8_to_lower(keyword->keyword_); vector emojis = search_language_emojis(language_code, text, true); bool is_changed = false; for (auto &emoji : keyword->emoticons_) { if (td::remove(emojis, emoji)) { is_changed = true; } } if (is_changed) { key_values.emplace(get_language_emojis_database_key(language_code, text), implode(emojis, '$')); } else { LOG(INFO) << "Emoji keywords not changed for \"" << text << "\" from version " << from_version << " to version " << version; } break; } default: UNREACHABLE(); } } G()->td_db()->get_sqlite_pmc()->set_all( std::move(key_values), PromiseCreator::lambda([actor_id = actor_id(this), language_code, version](Unit) mutable { send_closure(actor_id, &StickersManager::finish_get_emoji_keywords_difference, std::move(language_code), version); })); } void StickersManager::finish_get_emoji_keywords_difference(string language_code, int32 version) { if (G()->close_flag()) { return; } LOG(INFO) << "Finished to get emoji keywords difference for language " << language_code; emoji_language_code_versions_[language_code] = version; emoji_language_code_last_difference_times_[language_code] = static_cast(Time::now_cached()); } vector StickersManager::search_emojis(const string &text, bool exact_match, const vector &input_language_codes, bool force, Promise &&promise) { if (text.empty() || !G()->parameters().use_file_db /* have SQLite PMC */) { promise.set_value(Unit()); return {}; } auto language_codes = get_emoji_language_codes(input_language_codes, text, promise); if (language_codes.empty()) { // promise was consumed return {}; } vector languages_to_load; for (auto &language_code : language_codes) { CHECK(!language_code.empty()); auto version = get_emoji_language_code_version(language_code); if (version == 0) { languages_to_load.push_back(language_code); } else { LOG(DEBUG) << "Found language " << language_code << " with version " << version; } } if (!languages_to_load.empty()) { if (!force) { MultiPromiseActorSafe mpas{"LoadEmojiLanguagesMultiPromiseActor"}; mpas.add_promise(std::move(promise)); auto lock = mpas.get_promise(); for (auto &language_code : languages_to_load) { load_emoji_keywords(language_code, mpas.get_promise()); } lock.set_value(Unit()); return {}; } else { LOG(ERROR) << "Have no " << languages_to_load << " emoji keywords"; } } auto text_lowered = utf8_to_lower(text); vector result; for (auto &language_code : language_codes) { combine(result, search_language_emojis(language_code, text_lowered, exact_match)); } td::unique(result); promise.set_value(Unit()); return result; } int64 StickersManager::get_emoji_suggestions_url(const string &language_code, Promise &&promise) { int64 random_id = 0; do { random_id = Random::secure_int64(); } while (random_id == 0 || emoji_suggestions_urls_.count(random_id) > 0); emoji_suggestions_urls_[random_id]; // reserve place for result auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), random_id, promise = std::move(promise)]( Result> &&result) mutable { send_closure(actor_id, &StickersManager::on_get_emoji_suggestions_url, random_id, std::move(promise), std::move(result)); }); td_->create_handler(std::move(query_promise))->send(language_code); return random_id; } void StickersManager::on_get_emoji_suggestions_url( int64 random_id, Promise &&promise, Result> &&r_emoji_url) { auto it = emoji_suggestions_urls_.find(random_id); CHECK(it != emoji_suggestions_urls_.end()); auto &result = it->second; CHECK(result.empty()); if (r_emoji_url.is_error()) { emoji_suggestions_urls_.erase(it); return promise.set_error(r_emoji_url.move_as_error()); } auto emoji_url = r_emoji_url.move_as_ok(); result = std::move(emoji_url->url_); promise.set_value(Unit()); } td_api::object_ptr StickersManager::get_emoji_suggestions_url_result(int64 random_id) { auto it = emoji_suggestions_urls_.find(random_id); CHECK(it != emoji_suggestions_urls_.end()); auto result = td_api::make_object(it->second); emoji_suggestions_urls_.erase(it); return result; } void StickersManager::get_current_state(vector> &updates) const { if (td_->auth_manager_->is_bot()) { return; } if (!active_reactions_.empty()) { updates.push_back(get_update_active_emoji_reactions_object()); } for (int32 type = 0; type < MAX_STICKER_TYPE; type++) { if (are_installed_sticker_sets_loaded_[type]) { updates.push_back(get_update_installed_sticker_sets_object(static_cast(type))); } if (are_featured_sticker_sets_loaded_[type]) { updates.push_back(get_update_trending_sticker_sets_object(static_cast(type))); } } for (int is_attached = 0; is_attached < 2; is_attached++) { if (are_recent_stickers_loaded_[is_attached]) { updates.push_back(get_update_recent_stickers_object(is_attached)); } } if (are_favorite_stickers_loaded_) { updates.push_back(get_update_favorite_stickers_object()); } if (!dice_emojis_.empty()) { updates.push_back(get_update_dice_emojis_object()); } } } // namespace td