// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2026 // // 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/RichMessage.h" #include "td/telegram/Dependencies.h" #include "td/telegram/Document.h" #include "td/telegram/DocumentsManager.h" #include "td/telegram/files/FileId.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/MessageContent.h" #include "td/telegram/misc.h" #include "td/telegram/Photo.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/common.h" #include "td/utils/FlatHashMap.h" #include "td/utils/logging.h" namespace td { RichMessage::RichMessage(Td *td, telegram_api::object_ptr &&rich_message, DialogId owner_dialog_id) { CHECK(rich_message != nullptr); FlatHashMap> photos; for (auto &photo_ptr : rich_message->photos_) { Photo photo = get_photo(td, std::move(photo_ptr), owner_dialog_id); if (photo.is_empty() || photo.id.get() == 0) { LOG(ERROR) << "Receive empty photo in a rich message in " << owner_dialog_id; } else { auto photo_id = photo.id.get(); photos.emplace(photo_id, make_unique(std::move(photo))); } } FlatHashMap animations; FlatHashMap audios; FlatHashMap documents; FlatHashMap videos; FlatHashMap voice_notes; FlatHashMap others; auto get_map = [&](Document::Type document_type) { switch (document_type) { case Document::Type::Animation: return &animations; case Document::Type::Audio: return &audios; case Document::Type::General: return &documents; case Document::Type::Video: return &videos; case Document::Type::VoiceNote: return &voice_notes; default: return &others; } }; for (auto &document_ptr : rich_message->documents_) { if (document_ptr->get_id() == telegram_api::document::ID) { auto document = telegram_api::move_object_as(document_ptr); auto document_id = document->id_; auto parsed_document = td->documents_manager_->on_get_document(std::move(document), owner_dialog_id, false, false); if (!parsed_document.is_empty() && document_id != 0) { get_map(parsed_document.type)->emplace(document_id, parsed_document.file_id); } } } if (!others.empty()) { auto file_view = td->file_manager_->get_file_view(others.begin()->second); LOG(ERROR) << "Receive document of an unexpected type " << file_view.get_type(); } blocks_ = get_web_page_blocks(td, std::move(rich_message->blocks_), animations, audios, documents, photos, videos, voice_notes); is_rtl_ = rich_message->rtl_; is_full_ = !rich_message->part_; media_ = get_page_blocks_rich_message_media(blocks_); } Result RichMessage::get_rich_message(Td *td, DialogId dialog_id, td_api::object_ptr &&message, bool is_bot) { if (message == nullptr || message->source_ == nullptr) { return Status::Error(400, "Rich message must be non-empty"); } RichMessage rich_message; rich_message.is_rtl_ = message->is_rtl_; rich_message.noautolink_ = !message->detect_automatic_blocks_; rich_message.is_full_ = true; switch (message->source_->get_id()) { case td_api::richMessageSourceBlocks::ID: { auto source = td_api::move_object_as(message->source_); TRY_RESULT(blocks, get_web_page_blocks(td, dialog_id, std::move(source->blocks_))); rich_message.blocks_ = std::move(blocks); rich_message.media_ = get_page_blocks_rich_message_media(rich_message.blocks_); break; } case td_api::richMessageSourceMarkdown::ID: { auto source = td_api::move_object_as(message->source_); if (!clean_input_string(source->text_)) { return Status::Error(400, "Strings must be encoded in UTF-8"); } rich_message.source_ = std::move(source->text_); rich_message.input_type_ = InputType::Markdown; TRY_RESULT(media, RichMessageMedia::get_rich_message_media(td, dialog_id, std::move(source->media_))); rich_message.media_ = std::move(media); break; } case td_api::richMessageSourceHtml::ID: { auto source = td_api::move_object_as(message->source_); rich_message.source_ = std::move(source->text_); if (!clean_input_string(source->text_)) { return Status::Error(400, "Strings must be encoded in UTF-8"); } rich_message.input_type_ = InputType::Html; TRY_RESULT(media, RichMessageMedia::get_rich_message_media(td, dialog_id, std::move(source->media_))); rich_message.media_ = std::move(media); break; } default: UNREACHABLE(); } if (!clean_input_string(rich_message.source_)) { return Status::Error(400, "Strings must be encoded in UTF-8"); } return std::move(rich_message); } Result RichMessage::get_rich_message(Td *td, DialogId dialog_id, td_api::object_ptr &&message, bool is_bot) { if (message == nullptr) { return Status::Error(400, "Rich message must be non-empty"); } RichMessage rich_message; // rich_message.blocks_ = std::move(blocks); rich_message.is_rtl_ = message->is_rtl_; rich_message.is_full_ = true; rich_message.media_ = get_page_blocks_rich_message_media(rich_message.blocks_); return std::move(rich_message); } vector RichMessage::get_any_file_ids() const { return transform(get_individual_message_content_refs(), get_message_content_any_file_id); } vector RichMessage::get_cover_any_file_ids() const { return transform(get_individual_message_content_refs(), get_message_content_cover_any_file_id); } void RichMessage::append_file_ids(const Td *td, vector &file_ids) const { for (const auto &block : blocks_) { block->append_file_ids(td, file_ids); } } void RichMessage::add_dependencies(Dependencies &dependencies) const { for (const auto &block : blocks_) { block->add_dependencies(dependencies); } } void RichMessage::for_each_text(const std::function &callback) const { for (const auto &block : blocks_) { block->for_each_text(callback); } } vector RichMessage::get_user_ids() const { vector user_ids; for (const auto &block : blocks_) { block->append_user_ids(user_ids); } return user_ids; } bool RichMessage::has_bot_commands() const { for (const auto &block : blocks_) { if (block->has_bot_commands()) { return true; } } return false; } vector RichMessage::get_hashtags() const { vector hashtags; for (const auto &block : blocks_) { append(hashtags, block->get_hashtags()); } return hashtags; } vector RichMessage::get_custom_emoji_ids() const { vector custom_emoji_ids; for (const auto &block : blocks_) { append(custom_emoji_ids, block->get_custom_emoji_ids()); } return custom_emoji_ids; } bool RichMessage::can_send(const RestrictedRights &rights) const { if (!rights.can_send_messages()) { return false; } for (const auto &block : blocks_) { if (!block->can_send(rights)) { return false; } } return true; } int32 RichMessage::get_index_mask() const { return get_web_page_blocks_index_mask(blocks_); } vector> RichMessage::get_input_rich_files( const Td *td, bool with_input_media, vector> input_media) const { vector> input_rich_files; for (size_t i = 0; i < media_.size(); i++) { auto input_rich_file = media_[i].get_input_rich_file(td, with_input_media ? std::move(input_media[i]) : nullptr); if (input_rich_file != nullptr) { input_rich_files.push_back(std::move(input_rich_file)); } } return input_rich_files; } telegram_api::object_ptr RichMessage::get_input_rich_message( const Td *td, bool with_input_media, vector> input_media) const { if (with_input_media) { CHECK(media_.size() == input_media.size()); } switch (input_type_) { case InputType::None: { int32 flags = 0; vector> blocks; GetInputPageBlockContext context; context.td_ = td; context.media_ = &media_; for (const auto &block : blocks_) { blocks.push_back(block->get_input_page_block(context)); } CHECK(context.media_pos_ == media_.size()); auto photos = std::move(context.photos_); auto documents = std::move(context.documents_); if (with_input_media) { photos.clear(); documents.clear(); for (auto &media : input_media) { switch (media->get_id()) { case telegram_api::inputMediaPhoto::ID: { auto *photo = static_cast(media.get()); photos.push_back(std::move(photo->id_)); break; } case telegram_api::inputMediaDocument::ID: { auto *document = static_cast(media.get()); documents.push_back(std::move(document->id_)); break; } default: UNREACHABLE(); } } } if (photos.size() + documents.size() < media_.size()) { CHECK(!with_input_media); return nullptr; } if (!photos.empty()) { flags |= telegram_api::inputRichMessage::PHOTOS_MASK; } if (!documents.empty()) { flags |= telegram_api::inputRichMessage::DOCUMENTS_MASK; } auto input_users = td->user_manager_->get_input_users_force(get_user_ids()); if (!input_users.empty()) { flags |= telegram_api::inputRichMessage::USERS_MASK; } return telegram_api::make_object(flags, is_rtl_, noautolink_, std::move(blocks), std::move(photos), std::move(documents), std::move(input_users)); } case InputType::Markdown: { int32 flags = 0; auto files = get_input_rich_files(td, with_input_media, std::move(input_media)); if (files.size() != media_.size()) { CHECK(!with_input_media); return nullptr; } if (!files.empty()) { flags |= telegram_api::inputRichMessageMarkdown::FILES_MASK; } return telegram_api::make_object(flags, is_rtl_, noautolink_, source_, std::move(files)); } case InputType::Html: { int32 flags = 0; auto files = get_input_rich_files(td, with_input_media, std::move(input_media)); if (files.size() != media_.size()) { CHECK(!with_input_media); return nullptr; } if (!files.empty()) { flags |= telegram_api::inputRichMessageHTML::FILES_MASK; } return telegram_api::make_object(flags, is_rtl_, noautolink_, source_, std::move(files)); } default: UNREACHABLE(); return nullptr; } } td_api::object_ptr RichMessage::get_rich_message_object(Td *td, bool skip_bot_commands) const { return td_api::make_object( get_page_blocks_object(blocks_, td, string(), string(), skip_bot_commands), is_rtl_, is_full_); } RichMessage RichMessage::clone(Td *td, DialogId dialog_id, const MessageContentDupType &type) const { RichMessage result; result.blocks_ = clone_web_page_blocks(blocks_); result.is_rtl_ = is_rtl_; result.is_full_ = is_full_; result.noautolink_ = noautolink_; result.input_type_ = input_type_; if (!media_.empty()) { if (td != nullptr) { result.media_ = transform(media_, [&](const RichMessageMedia &media) { return media.clone(td, dialog_id, type); }); } else { LOG(ERROR) << "Have no Td to clone RichMessage media"; result.media_ = get_page_blocks_rich_message_media(result.blocks_); } } result.source_ = source_; return result; } vector> RichMessage::get_individual_message_contents(Td *td) const { return transform(media_, [td](const RichMessageMedia &media) { return media.get_message_content(td); }); } vector RichMessage::get_individual_message_content_refs() { vector message_contents; for (auto &media : media_) { message_contents.push_back(media.get_message_content_ref()); } return message_contents; } vector RichMessage::get_individual_message_content_refs() const { return transform(media_, [](const RichMessageMedia &media) { return media.get_message_content_ref(); }); } unique_ptr &RichMessage::get_individual_message_content(int32 media_pos) { CHECK(static_cast(media_pos) < media_.size()); return media_[media_pos].get_message_content_editable(); } const MessageContent *RichMessage::get_individual_message_content_ref(int32 media_pos) const { CHECK(static_cast(media_pos) < media_.size()); return media_[media_pos].get_message_content_ref(); } void RichMessage::compare(Td *td, const RichMessage &lhs, const RichMessage &rhs, bool &is_changed, bool &need_update) { if (lhs.blocks_ != rhs.blocks_ || lhs.is_rtl_ != rhs.is_rtl_ || lhs.is_full_ != rhs.is_full_) { need_update = true; } else if (lhs.noautolink_ != rhs.noautolink_ || lhs.input_type_ != rhs.input_type_ || lhs.media_.size() != rhs.media_.size() || lhs.source_ != rhs.source_) { is_changed = true; } else { for (size_t i = 0; i < lhs.media_.size(); i++) { RichMessageMedia::compare(td, lhs.media_[i], rhs.media_[i], is_changed, is_changed); } } } bool operator==(const RichMessage &lhs, const RichMessage &rhs) { // compare only publicly-visible fields return lhs.blocks_ == rhs.blocks_ && lhs.is_rtl_ == rhs.is_rtl_ && lhs.is_full_ == rhs.is_full_; } } // namespace td