// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once #include "td/telegram/StickersManager.h" #include "td/telegram/files/FileId.hpp" #include "td/telegram/misc.h" #include "td/telegram/PhotoSize.hpp" #include "td/telegram/StickerFormat.h" #include "td/telegram/StickerMaskPosition.hpp" #include "td/utils/emoji.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/Slice.h" #include "td/utils/tl_helpers.h" #include "td/utils/utf8.h" namespace td { template void StickersManager::store_sticker(FileId file_id, bool in_sticker_set, StorerT &storer, const char *source) const { const Sticker *sticker = get_sticker(file_id); LOG_CHECK(sticker != nullptr) << file_id << ' ' << in_sticker_set << ' ' << source; bool has_sticker_set_access_hash = sticker->set_id_.is_valid() && !in_sticker_set; bool has_minithumbnail = !sticker->minithumbnail_.empty(); bool is_tgs = sticker->format_ == StickerFormat::Tgs; bool is_webm = sticker->format_ == StickerFormat::Webm; bool has_premium_animation = sticker->premium_animation_file_id_.is_valid(); bool is_mask = sticker->type_ == StickerType::Mask; bool is_emoji = sticker->type_ == StickerType::CustomEmoji; bool has_emoji_receive_date = is_emoji && sticker->emoji_receive_date_ != 0; BEGIN_STORE_FLAGS(); STORE_FLAG(is_mask); STORE_FLAG(has_sticker_set_access_hash); STORE_FLAG(in_sticker_set); STORE_FLAG(is_tgs); STORE_FLAG(has_minithumbnail); STORE_FLAG(is_webm); STORE_FLAG(has_premium_animation); STORE_FLAG(is_emoji); STORE_FLAG(sticker->is_premium_); STORE_FLAG(has_emoji_receive_date); STORE_FLAG(sticker->has_text_color_); END_STORE_FLAGS(); if (!in_sticker_set) { store(sticker->set_id_.get(), storer); if (has_sticker_set_access_hash) { auto sticker_set = get_sticker_set(sticker->set_id_); CHECK(sticker_set != nullptr); store(sticker_set->access_hash_, storer); } } store(sticker->alt_, storer); store(sticker->dimensions_, storer); store(sticker->s_thumbnail_, storer); store(sticker->m_thumbnail_, storer); store(file_id, storer); if (is_mask) { store(sticker->mask_position_, storer); } if (has_minithumbnail) { store(sticker->minithumbnail_, storer); } if (has_premium_animation) { store(sticker->premium_animation_file_id_, storer); } if (has_emoji_receive_date) { store(sticker->emoji_receive_date_, storer); } } template FileId StickersManager::parse_sticker(bool in_sticker_set, ParserT &parser) { if (parser.get_error() != nullptr) { return FileId(); } auto sticker = make_unique(); bool has_sticker_set_access_hash; bool in_sticker_set_stored; bool has_minithumbnail; bool is_tgs; bool is_webm; bool has_premium_animation; bool is_mask; bool is_emoji; bool has_emoji_receive_date; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_mask); PARSE_FLAG(has_sticker_set_access_hash); PARSE_FLAG(in_sticker_set_stored); PARSE_FLAG(is_tgs); PARSE_FLAG(has_minithumbnail); PARSE_FLAG(is_webm); PARSE_FLAG(has_premium_animation); PARSE_FLAG(is_emoji); PARSE_FLAG(sticker->is_premium_); PARSE_FLAG(has_emoji_receive_date); PARSE_FLAG(sticker->has_text_color_); END_PARSE_FLAGS(); if (is_webm) { sticker->format_ = StickerFormat::Webm; } else if (is_tgs) { sticker->format_ = StickerFormat::Tgs; } else { sticker->format_ = StickerFormat::Webp; } sticker->type_ = ::td::get_sticker_type(is_mask, is_emoji); if (in_sticker_set_stored != in_sticker_set) { Slice data = parser.template fetch_string_raw(parser.get_left_len()); for (auto c : data) { if (c != '\0') { parser.set_error("Invalid sticker set is stored in the database"); break; } } parser.set_error("Zero sticker set is stored in the database"); return FileId(); } if (!in_sticker_set) { int64 set_id; parse(set_id, parser); sticker->set_id_ = StickerSetId(set_id); if (has_sticker_set_access_hash) { int64 sticker_set_access_hash; parse(sticker_set_access_hash, parser); add_sticker_set(sticker->set_id_, sticker_set_access_hash); } else { // backward compatibility sticker->set_id_ = StickerSetId(); } } parse(sticker->alt_, parser); parse(sticker->dimensions_, parser); PhotoSize thumbnail; parse(thumbnail, parser); add_sticker_thumbnail(sticker.get(), thumbnail); parse(thumbnail, parser); add_sticker_thumbnail(sticker.get(), thumbnail); parse(sticker->file_id_, parser); if (is_mask) { parse(sticker->mask_position_, parser); } if (has_minithumbnail) { parse(sticker->minithumbnail_, parser); } if (has_premium_animation) { sticker->is_premium_ = true; parse(sticker->premium_animation_file_id_, parser); } if (has_emoji_receive_date) { parse(sticker->emoji_receive_date_, parser); } if (parser.get_error() != nullptr || !sticker->file_id_.is_valid()) { return FileId(); } sticker->is_from_database_ = true; return on_get_sticker(std::move(sticker), false); // data in the database is always outdated } template void StickersManager::store_sticker_set(const StickerSet *sticker_set, bool with_stickers, StorerT &storer, const char *source) const { size_t stickers_limit = with_stickers ? sticker_set->sticker_ids_.size() : get_max_featured_sticker_count(sticker_set->sticker_type_); bool is_full = sticker_set->sticker_ids_.size() <= stickers_limit; bool was_loaded = sticker_set->was_loaded_ && is_full; bool is_loaded = sticker_set->is_loaded_ && is_full; bool has_expires_at = !sticker_set->is_installed_ && sticker_set->expires_at_ != 0; bool has_thumbnail = sticker_set->thumbnail_.file_id.is_valid(); bool has_minithumbnail = !sticker_set->minithumbnail_.empty(); bool is_masks = sticker_set->sticker_type_ == StickerType::Mask; bool is_emojis = sticker_set->sticker_type_ == StickerType::CustomEmoji; bool has_thumbnail_document_id = sticker_set->thumbnail_document_id_ != 0; bool is_mixed_format = true; BEGIN_STORE_FLAGS(); STORE_FLAG(sticker_set->is_inited_); STORE_FLAG(was_loaded); STORE_FLAG(is_loaded); STORE_FLAG(sticker_set->is_installed_); STORE_FLAG(sticker_set->is_archived_); STORE_FLAG(sticker_set->is_official_); STORE_FLAG(is_masks); STORE_FLAG(sticker_set->is_viewed_); STORE_FLAG(has_expires_at); STORE_FLAG(has_thumbnail); STORE_FLAG(sticker_set->is_thumbnail_reloaded_); STORE_FLAG(false); STORE_FLAG(sticker_set->are_legacy_sticker_thumbnails_reloaded_); STORE_FLAG(has_minithumbnail); STORE_FLAG(false); STORE_FLAG(is_emojis); STORE_FLAG(has_thumbnail_document_id); STORE_FLAG(sticker_set->are_keywords_loaded_); STORE_FLAG(sticker_set->is_sticker_has_text_color_loaded_); STORE_FLAG(sticker_set->has_text_color_); STORE_FLAG(sticker_set->is_sticker_channel_emoji_status_loaded_); STORE_FLAG(sticker_set->channel_emoji_status_); STORE_FLAG(is_mixed_format); END_STORE_FLAGS(); store(sticker_set->id_.get(), storer); store(sticker_set->access_hash_, storer); if (sticker_set->is_inited_) { store(sticker_set->title_, storer); store(sticker_set->short_name_, storer); store(sticker_set->sticker_count_, storer); store(sticker_set->hash_, storer); if (has_expires_at) { store(sticker_set->expires_at_, storer); } if (has_thumbnail) { store(sticker_set->thumbnail_, storer); } if (has_minithumbnail) { store(sticker_set->minithumbnail_, storer); } if (has_thumbnail_document_id) { store(sticker_set->thumbnail_document_id_, storer); } auto stored_sticker_count = narrow_cast(is_full ? sticker_set->sticker_ids_.size() : stickers_limit); store(stored_sticker_count, storer); for (uint32 i = 0; i < stored_sticker_count; i++) { auto sticker_id = sticker_set->sticker_ids_[i]; store_sticker(sticker_id, true, storer, source); if (was_loaded) { auto it = sticker_set->sticker_emojis_map_.find(sticker_id); if (it != sticker_set->sticker_emojis_map_.end()) { store(it->second, storer); } else { store(vector(), storer); } } if (sticker_set->are_keywords_loaded_) { auto it = sticker_set->sticker_keywords_map_.find(sticker_id); if (it != sticker_set->sticker_keywords_map_.end()) { store(it->second, storer); } else { store(vector(), storer); } } } } } template void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser) { CHECK(sticker_set != nullptr); CHECK(!sticker_set->was_loaded_); bool was_inited = sticker_set->is_inited_; bool is_installed; bool is_archived; bool is_official; bool is_masks; bool has_expires_at; bool has_thumbnail; bool legacy_is_tgs; bool has_minithumbnail; bool legacy_is_webm; bool is_emojis; bool has_thumbnail_document_id; bool has_text_color; bool channel_emoji_status; bool is_mixed_format; BEGIN_PARSE_FLAGS(); PARSE_FLAG(sticker_set->is_inited_); PARSE_FLAG(sticker_set->was_loaded_); PARSE_FLAG(sticker_set->is_loaded_); PARSE_FLAG(is_installed); PARSE_FLAG(is_archived); PARSE_FLAG(is_official); PARSE_FLAG(is_masks); PARSE_FLAG(sticker_set->is_viewed_); PARSE_FLAG(has_expires_at); PARSE_FLAG(has_thumbnail); PARSE_FLAG(sticker_set->is_thumbnail_reloaded_); PARSE_FLAG(legacy_is_tgs); PARSE_FLAG(sticker_set->are_legacy_sticker_thumbnails_reloaded_); PARSE_FLAG(has_minithumbnail); PARSE_FLAG(legacy_is_webm); PARSE_FLAG(is_emojis); PARSE_FLAG(has_thumbnail_document_id); PARSE_FLAG(sticker_set->are_keywords_loaded_); PARSE_FLAG(sticker_set->is_sticker_has_text_color_loaded_); PARSE_FLAG(has_text_color); PARSE_FLAG(sticker_set->is_sticker_channel_emoji_status_loaded_); PARSE_FLAG(channel_emoji_status); PARSE_FLAG(is_mixed_format); END_PARSE_FLAGS(); int64 sticker_set_id; int64 access_hash; parse(sticker_set_id, parser); parse(access_hash, parser); if (sticker_set->id_.get() != sticker_set_id) { return parser.set_error("Invalid sticker set data stored in the database"); } (void)access_hash; // unused, because only known sticker sets with access hash can be loaded from database auto sticker_type = ::td::get_sticker_type(is_masks, is_emojis); if (!is_emojis) { sticker_set->is_sticker_has_text_color_loaded_ = true; sticker_set->is_sticker_channel_emoji_status_loaded_ = true; } if (sticker_set->is_inited_) { string title; string short_name; string minithumbnail; PhotoSize thumbnail; int64 thumbnail_document_id = 0; int32 sticker_count; int32 hash; int32 expires_at = 0; parse(title, parser); parse(short_name, parser); parse(sticker_count, parser); parse(hash, parser); if (has_expires_at) { parse(expires_at, parser); } if (has_thumbnail) { parse(thumbnail, parser); } if (has_minithumbnail) { parse(minithumbnail, parser); } if (has_thumbnail_document_id) { parse(thumbnail_document_id, parser); } if (!is_mixed_format && thumbnail.file_id.is_valid()) { if (legacy_is_webm) { thumbnail.type = 'v'; } else if (legacy_is_tgs) { thumbnail.type = 'a'; } else { thumbnail.type = 's'; } } if (!was_inited) { sticker_set->title_ = std::move(title); sticker_set->short_name_ = std::move(short_name); sticker_set->minithumbnail_ = std::move(minithumbnail); sticker_set->thumbnail_ = std::move(thumbnail); sticker_set->thumbnail_document_id_ = thumbnail_document_id; sticker_set->sticker_count_ = sticker_count; sticker_set->hash_ = hash; sticker_set->expires_at_ = expires_at; sticker_set->is_official_ = is_official; sticker_set->sticker_type_ = sticker_type; sticker_set->has_text_color_ = has_text_color; sticker_set->channel_emoji_status_ = channel_emoji_status; 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_); } on_update_sticker_set(sticker_set, is_installed, is_archived, false, true); } else { if (sticker_set->title_ != title || sticker_set->minithumbnail_ != minithumbnail || sticker_set->thumbnail_ != thumbnail || sticker_set->thumbnail_document_id_ != thumbnail_document_id || sticker_set->is_official_ != is_official || sticker_set->has_text_color_ != has_text_color || sticker_set->channel_emoji_status_ != channel_emoji_status) { sticker_set->is_changed_ = true; } if (sticker_set->short_name_ != short_name) { LOG(INFO) << "Short name of " << sticker_set->id_ << " has changed from \"" << short_name << "\" to \"" << sticker_set->short_name_ << "\""; sticker_set->is_changed_ = true; } if (sticker_set->is_loaded_ && (sticker_set->sticker_count_ != sticker_count || sticker_set->hash_ != hash)) { sticker_set->is_loaded_ = false; sticker_set->is_changed_ = true; } if (sticker_set->sticker_type_ != sticker_type) { LOG(ERROR) << "Type of " << sticker_set->id_ << " has changed from \"" << sticker_type << "\" to \"" << sticker_set->sticker_type_ << "\""; } } uint32 stored_sticker_count; parse(stored_sticker_count, parser); sticker_set->sticker_ids_.clear(); sticker_set->premium_sticker_positions_.clear(); if (sticker_set->was_loaded_) { sticker_set->emoji_stickers_map_.clear(); sticker_set->sticker_emojis_map_.clear(); sticker_set->keyword_stickers_map_.clear(); sticker_set->sticker_keywords_map_.clear(); } for (uint32 i = 0; i < stored_sticker_count; i++) { auto sticker_id = parse_sticker(true, parser); if (parser.get_error() != nullptr) { return; } if (!sticker_id.is_valid()) { return parser.set_error("Receive invalid sticker in a sticker set"); } sticker_set->sticker_ids_.push_back(sticker_id); Sticker *sticker = get_sticker(sticker_id); CHECK(sticker != nullptr); if (sticker->set_id_ != sticker_set->id_) { LOG_IF(ERROR, sticker->set_id_.is_valid()) << "Sticker " << sticker_id << " set_id has changed"; sticker->set_id_ = sticker_set->id_; if (sticker->has_text_color_) { sticker_set->has_text_color_ = true; } } if (sticker->is_premium_) { sticker_set->premium_sticker_positions_.push_back(static_cast(sticker_set->sticker_ids_.size() - 1)); } if (sticker_set->was_loaded_) { vector emojis; parse(emojis, parser); for (auto &emoji : emojis) { auto cleaned_emoji = remove_emoji_modifiers(emoji); if (!cleaned_emoji.empty()) { auto &sticker_ids = sticker_set->emoji_stickers_map_[cleaned_emoji]; if (sticker_ids.empty() || sticker_ids.back() != sticker_id) { sticker_ids.push_back(sticker_id); } } else { LOG(INFO) << "Sticker " << sticker_id << " in " << sticker_set_id << '/' << sticker_set->short_name_ << " has an empty emoji"; } } sticker_set->sticker_emojis_map_[sticker_id] = std::move(emojis); } if (sticker_set->are_keywords_loaded_) { vector keywords; parse(keywords, parser); if (!keywords.empty()) { sticker_set->sticker_keywords_map_.emplace(sticker_id, std::move(keywords)); } } } if (expires_at > sticker_set->expires_at_) { sticker_set->expires_at_ = expires_at; } if (!check_utf8(sticker_set->title_)) { return parser.set_error("Have invalid sticker set title"); } if (!check_utf8(sticker_set->short_name_)) { return parser.set_error("Have invalid sticker set name"); } } } template void StickersManager::store_sticker_set_id(StickerSetId sticker_set_id, StorerT &storer) const { CHECK(sticker_set_id.is_valid()); const StickerSet *sticker_set = get_sticker_set(sticker_set_id); CHECK(sticker_set != nullptr); store(sticker_set_id.get(), storer); store(sticker_set->access_hash_, storer); } template void StickersManager::parse_sticker_set_id(StickerSetId &sticker_set_id, ParserT &parser) { int64 set_id; parse(set_id, parser); sticker_set_id = StickerSetId(set_id); int64 sticker_set_access_hash; parse(sticker_set_access_hash, parser); add_sticker_set(sticker_set_id, sticker_set_access_hash); } } // namespace td