// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019 // // 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/WebPagesManager.h" #include "td/telegram/secret_api.h" #include "td/telegram/telegram_api.hpp" #include "td/telegram/AnimationsManager.h" #include "td/telegram/AnimationsManager.hpp" #include "td/telegram/AudiosManager.h" #include "td/telegram/AudiosManager.hpp" #include "td/telegram/ChannelId.h" #include "td/telegram/ContactsManager.h" #include "td/telegram/DocumentsManager.h" #include "td/telegram/DocumentsManager.hpp" #include "td/telegram/FileReferenceManager.h" #include "td/telegram/files/FileId.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/files/FileSourceId.h" #include "td/telegram/Global.h" #include "td/telegram/Location.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/MessageEntity.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/Photo.h" #include "td/telegram/Photo.hpp" #include "td/telegram/StickersManager.h" #include "td/telegram/StickersManager.hpp" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/Version.h" #include "td/telegram/VideoNotesManager.h" #include "td/telegram/VideoNotesManager.hpp" #include "td/telegram/VideosManager.h" #include "td/telegram/VideosManager.hpp" #include "td/telegram/VoiceNotesManager.h" #include "td/telegram/VoiceNotesManager.hpp" #include "td/actor/PromiseFuture.h" #include "td/db/binlog/BinlogEvent.h" #include "td/db/binlog/BinlogHelper.h" #include "td/db/SqliteKeyValue.h" #include "td/db/SqliteKeyValueAsync.h" #include "td/utils/buffer.h" #include "td/utils/common.h" #include "td/utils/format.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/Slice.h" #include "td/utils/StringBuilder.h" #include "td/utils/tl_helpers.h" #include namespace td { class GetWebPagePreviewQuery : public Td::ResultHandler { Promise promise_; int64 request_id_; string url_; public: explicit GetWebPagePreviewQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const string &text, vector> &&entities, int64 request_id, string url) { request_id_ = request_id; url_ = std::move(url); int32 flags = 0; if (!entities.empty()) { flags |= telegram_api::messages_getWebPagePreview::ENTITIES_MASK; } send_query(G()->net_query_creator().create( create_storer(telegram_api::messages_getWebPagePreview(flags, text, std::move(entities))))); } void on_result(uint64 id, BufferSlice packet) override { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(id, result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetWebPagePreviewQuery " << to_string(ptr); td->web_pages_manager_->on_get_web_page_preview_success(request_id_, url_, std::move(ptr), std::move(promise_)); } void on_error(uint64 id, Status status) override { td->web_pages_manager_->on_get_web_page_preview_fail(request_id_, url_, std::move(status), std::move(promise_)); } }; class GetWebPageQuery : public Td::ResultHandler { Promise promise_; string url_; public: explicit GetWebPageQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const string &url, int32 hash) { url_ = url; send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_getWebPage(url, hash)))); } void on_result(uint64 id, BufferSlice packet) override { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(id, result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetWebPageQuery " << to_string(ptr); if (ptr->get_id() != telegram_api::webPageNotModified::ID) { auto web_page_id = td->web_pages_manager_->on_get_web_page(std::move(ptr), DialogId()); td->web_pages_manager_->on_get_web_page_by_url(url_, web_page_id, false); } promise_.set_value(Unit()); } void on_error(uint64 id, Status status) override { promise_.set_error(std::move(status)); } }; class WebPagesManager::WebPageInstantView { public: vector> page_blocks; string url; int32 hash = 0; bool is_v2 = false; bool is_rtl = false; bool is_empty = true; bool is_full = false; bool is_loaded = false; bool was_loaded_from_database = false; template void store(StorerT &storer) const { using ::td::store; bool has_url = !url.empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(is_full); STORE_FLAG(is_loaded); STORE_FLAG(is_rtl); STORE_FLAG(is_v2); STORE_FLAG(has_url); END_STORE_FLAGS(); store(page_blocks, storer); store(hash, storer); if (has_url) { store(url, storer); } CHECK(!is_empty); } template void parse(ParserT &parser) { using ::td::parse; bool has_url; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_full); PARSE_FLAG(is_loaded); PARSE_FLAG(is_rtl); PARSE_FLAG(is_v2); PARSE_FLAG(has_url); END_PARSE_FLAGS(); parse(page_blocks, parser); parse(hash, parser); if (has_url) { parse(url, parser); } is_empty = false; } friend StringBuilder &operator<<(StringBuilder &string_builder, const WebPagesManager::WebPageInstantView &instant_view) { return string_builder << "InstantView(url = " << instant_view.url << ", size = " << instant_view.page_blocks.size() << ", hash = " << instant_view.hash << ", is_empty = " << instant_view.is_empty << ", is_v2 = " << instant_view.is_v2 << ", is_rtl = " << instant_view.is_rtl << ", is_full = " << instant_view.is_full << ", is_loaded = " << instant_view.is_loaded << ", was_loaded_from_database = " << instant_view.was_loaded_from_database << ")"; } }; class WebPagesManager::WebPage { public: string url; string display_url; string type; string site_name; string title; string description; Photo photo; string embed_url; string embed_type; Dimensions embed_dimensions; int32 duration = 0; string author; DocumentsManager::DocumentType document_type = DocumentsManager::DocumentType::Unknown; FileId document_file_id; WebPageInstantView instant_view; FileSourceId file_source_id; mutable uint64 logevent_id = 0; template void store(StorerT &storer) const { using ::td::store; bool has_type = !type.empty(); bool has_site_name = !site_name.empty(); bool has_title = !title.empty(); bool has_description = !description.empty(); bool has_photo = photo.id != -2; bool has_embed = !embed_url.empty(); bool has_embed_dimensions = has_embed && embed_dimensions != Dimensions(); bool has_duration = duration > 0; bool has_author = !author.empty(); bool has_document = document_type != DocumentsManager::DocumentType::Unknown; bool has_instant_view = !instant_view.is_empty; bool is_instant_view_v2 = instant_view.is_v2; bool has_no_hash = true; BEGIN_STORE_FLAGS(); STORE_FLAG(has_type); STORE_FLAG(has_site_name); STORE_FLAG(has_title); STORE_FLAG(has_description); STORE_FLAG(has_photo); STORE_FLAG(has_embed); STORE_FLAG(has_embed_dimensions); STORE_FLAG(has_duration); STORE_FLAG(has_author); STORE_FLAG(has_document); STORE_FLAG(has_instant_view); STORE_FLAG(has_no_hash); STORE_FLAG(is_instant_view_v2); END_STORE_FLAGS(); store(url, storer); store(display_url, storer); if (has_type) { store(type, storer); } if (has_site_name) { store(site_name, storer); } if (has_title) { store(title, storer); } if (has_description) { store(description, storer); } if (has_photo) { store(photo, storer); } if (has_embed) { store(embed_url, storer); store(embed_type, storer); } if (has_embed_dimensions) { store(embed_dimensions, storer); } if (has_duration) { store(duration, storer); } if (has_author) { store(author, storer); } if (has_document) { Td *td = storer.context()->td().get_actor_unsafe(); CHECK(td != nullptr); store(document_type, storer); switch (document_type) { case DocumentsManager::DocumentType::Animation: td->animations_manager_->store_animation(document_file_id, storer); break; case DocumentsManager::DocumentType::Audio: td->audios_manager_->store_audio(document_file_id, storer); break; case DocumentsManager::DocumentType::General: td->documents_manager_->store_document(document_file_id, storer); break; case DocumentsManager::DocumentType::Sticker: td->stickers_manager_->store_sticker(document_file_id, false, storer); break; case DocumentsManager::DocumentType::Video: td->videos_manager_->store_video(document_file_id, storer); break; case DocumentsManager::DocumentType::VideoNote: td->video_notes_manager_->store_video_note(document_file_id, storer); break; case DocumentsManager::DocumentType::VoiceNote: td->voice_notes_manager_->store_voice_note(document_file_id, storer); break; case DocumentsManager::DocumentType::Unknown: default: UNREACHABLE(); } } } template void parse(ParserT &parser) { using ::td::parse; bool has_type; bool has_site_name; bool has_title; bool has_description; bool has_photo; bool has_embed; bool has_embed_dimensions; bool has_duration; bool has_author; bool has_document; bool has_instant_view; bool is_instant_view_v2; bool has_no_hash; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_type); PARSE_FLAG(has_site_name); PARSE_FLAG(has_title); PARSE_FLAG(has_description); PARSE_FLAG(has_photo); PARSE_FLAG(has_embed); PARSE_FLAG(has_embed_dimensions); PARSE_FLAG(has_duration); PARSE_FLAG(has_author); PARSE_FLAG(has_document); PARSE_FLAG(has_instant_view); PARSE_FLAG(has_no_hash); PARSE_FLAG(is_instant_view_v2); END_PARSE_FLAGS(); parse(url, parser); parse(display_url, parser); if (!has_no_hash) { int32 hash; parse(hash, parser); } if (has_type) { parse(type, parser); } if (has_site_name) { parse(site_name, parser); } if (has_title) { parse(title, parser); } if (has_description) { parse(description, parser); } if (has_photo) { parse(photo, parser); } else { photo.id = -2; } if (has_embed) { parse(embed_url, parser); parse(embed_type, parser); } if (has_embed_dimensions) { parse(embed_dimensions, parser); } if (has_duration) { parse(duration, parser); } if (has_author) { parse(author, parser); } if (has_document) { Td *td = parser.context()->td().get_actor_unsafe(); CHECK(td != nullptr); parse(document_type, parser); switch (document_type) { case DocumentsManager::DocumentType::Animation: document_file_id = td->animations_manager_->parse_animation(parser); break; case DocumentsManager::DocumentType::Audio: document_file_id = td->audios_manager_->parse_audio(parser); break; case DocumentsManager::DocumentType::General: document_file_id = td->documents_manager_->parse_document(parser); break; case DocumentsManager::DocumentType::Sticker: document_file_id = td->stickers_manager_->parse_sticker(false, parser); break; case DocumentsManager::DocumentType::Video: document_file_id = td->videos_manager_->parse_video(parser); break; case DocumentsManager::DocumentType::VideoNote: document_file_id = td->video_notes_manager_->parse_video_note(parser); break; case DocumentsManager::DocumentType::VoiceNote: document_file_id = td->voice_notes_manager_->parse_voice_note(parser); break; case DocumentsManager::DocumentType::Unknown: default: UNREACHABLE(); } if (!document_file_id.is_valid()) { LOG(ERROR) << "Parse invalid document_file_id"; document_type = DocumentsManager::DocumentType::Unknown; } } if (has_instant_view) { instant_view.is_empty = false; } if (is_instant_view_v2) { instant_view.is_v2 = true; } } }; class WebPagesManager::RichText { public: enum class Type : int32 { Plain, Bold, Italic, Underline, Strikethrough, Fixed, Url, EmailAddress, Concatenation, Subscript, Superscript, Marked, PhoneNumber, Icon, Anchor }; Type type = Type::Plain; string content; vector texts; FileId document_file_id; WebPageId web_page_id; bool empty() const { return type == Type::Plain && content.empty(); } template void store(StorerT &storer) const { using ::td::store; store(type, storer); store(content, storer); store(texts, storer); if (type == Type::Icon) { storer.context()->td().get_actor_unsafe()->documents_manager_->store_document(document_file_id, storer); } if (type == Type::Url) { store(web_page_id, storer); } } template void parse(ParserT &parser) { using ::td::parse; parse(type, parser); parse(content, parser); parse(texts, parser); if (type == Type::Icon) { document_file_id = parser.context()->td().get_actor_unsafe()->documents_manager_->parse_document(parser); if (!document_file_id.is_valid()) { LOG(ERROR) << "Failed to load document from database"; *this = RichText(); } } else { document_file_id = FileId(); } if (type == Type::Url && parser.version() >= static_cast(Version::SupportInstantView2_0)) { parse(web_page_id, parser); } else { web_page_id = WebPageId(); } } }; class WebPagesManager::PageBlockCaption { public: RichText text; RichText credit; template void store(StorerT &storer) const { using ::td::store; store(text, storer); store(credit, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(text, parser); if (parser.version() >= static_cast(Version::SupportInstantView2_0)) { parse(credit, parser); } else { credit = RichText(); } } }; class WebPagesManager::PageBlockTableCell { public: RichText text; bool is_header = false; bool align_left = false; bool align_center = false; bool align_right = false; bool valign_top = false; bool valign_middle = false; bool valign_bottom = false; int32 colspan = 1; int32 rowspan = 1; template void store(StorerT &storer) const { using ::td::store; bool has_text = !text.empty(); bool has_colspan = colspan != 1; bool has_rowspan = rowspan != 1; BEGIN_STORE_FLAGS(); STORE_FLAG(is_header); STORE_FLAG(align_left); STORE_FLAG(align_center); STORE_FLAG(align_right); STORE_FLAG(valign_top); STORE_FLAG(valign_middle); STORE_FLAG(valign_bottom); STORE_FLAG(has_text); STORE_FLAG(has_colspan); STORE_FLAG(has_rowspan); END_STORE_FLAGS(); if (has_text) { store(text, storer); } if (has_colspan) { store(colspan, storer); } if (has_rowspan) { store(rowspan, storer); } } template void parse(ParserT &parser) { using ::td::parse; bool has_text; bool has_colspan; bool has_rowspan; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_header); PARSE_FLAG(align_left); PARSE_FLAG(align_center); PARSE_FLAG(align_right); PARSE_FLAG(valign_top); PARSE_FLAG(valign_middle); PARSE_FLAG(valign_bottom); PARSE_FLAG(has_text); PARSE_FLAG(has_colspan); PARSE_FLAG(has_rowspan); END_PARSE_FLAGS(); if (has_text) { parse(text, parser); } if (has_colspan) { parse(colspan, parser); } if (has_rowspan) { parse(rowspan, parser); } } }; class WebPagesManager::RelatedArticle { public: string url; WebPageId web_page_id; string title; string description; Photo photo; string author; int32 published_date = 0; template void store(StorerT &storer) const { using ::td::store; bool has_title = !title.empty(); bool has_description = !description.empty(); bool has_photo = photo.id != -2; bool has_author = !author.empty(); bool has_date = published_date != 0; BEGIN_STORE_FLAGS(); STORE_FLAG(has_title); STORE_FLAG(has_description); STORE_FLAG(has_photo); STORE_FLAG(has_author); STORE_FLAG(has_date); END_STORE_FLAGS(); store(url, storer); store(web_page_id, storer); if (has_title) { store(title, storer); } if (has_description) { store(description, storer); } if (has_photo) { store(photo, storer); } if (has_author) { store(author, storer); } if (has_date) { store(published_date, storer); } } template void parse(ParserT &parser) { using ::td::parse; bool has_title; bool has_description; bool has_photo; bool has_author; bool has_date; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_title); PARSE_FLAG(has_description); PARSE_FLAG(has_photo); PARSE_FLAG(has_author); PARSE_FLAG(has_date); END_PARSE_FLAGS(); parse(url, parser); parse(web_page_id, parser); if (has_title) { parse(title, parser); } if (has_description) { parse(description, parser); } if (has_photo) { parse(photo, parser); } else { photo.id = -2; } if (has_author) { parse(author, parser); } if (has_date) { parse(published_date, parser); } } }; class WebPagesManager::PageBlock { public: enum class Type : int32 { Title, Subtitle, AuthorDate, Header, Subheader, Paragraph, Preformatted, Footer, Divider, Anchor, List, BlockQuote, PullQuote, Animation, Photo, Video, Cover, Embedded, EmbeddedPost, Collage, Slideshow, ChatLink, Audio, Kicker, Table, Details, RelatedArticles, Map }; virtual Type get_type() const = 0; virtual void append_file_ids(vector &file_ids) const = 0; virtual tl_object_ptr get_page_block_object() const = 0; PageBlock() = default; PageBlock(const PageBlock &) = delete; PageBlock &operator=(const PageBlock &) = delete; PageBlock(PageBlock &&) = delete; PageBlock &operator=(PageBlock &&) = delete; virtual ~PageBlock() = default; template void store(StorerT &storer) const { using ::td::store; Type type = get_type(); store(type, storer); call_impl(type, this, [&](const auto *object) { store(*object, storer); }); } template static unique_ptr parse(ParserT &parser) { using ::td::parse; Type type; parse(type, parser); unique_ptr res; call_impl(type, nullptr, [&](const auto *ptr) { using ObjT = std::decay_t; auto object = make_unique(); parse(*object, parser); res = std::move(object); }); return res; } private: template static void call_impl(Type type, const PageBlock *ptr, F &&f); }; template void store(const unique_ptr &block, StorerT &storer) { block->store(storer); } template void parse(unique_ptr &block, ParserT &parser) { block = WebPagesManager::PageBlock::parse(parser); } class WebPagesManager::PageBlockTitle : public PageBlock { RichText title; public: PageBlockTitle() = default; explicit PageBlockTitle(RichText &&title) : title(std::move(title)) { } Type get_type() const override { return Type::Title; } void append_file_ids(vector &file_ids) const override { append_rich_text_file_ids(title, file_ids); } tl_object_ptr get_page_block_object() const override { return make_tl_object(get_rich_text_object(title)); } template void store(StorerT &storer) const { using ::td::store; store(title, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(title, parser); } }; class WebPagesManager::PageBlockSubtitle : public PageBlock { RichText subtitle; public: PageBlockSubtitle() = default; explicit PageBlockSubtitle(RichText &&subtitle) : subtitle(std::move(subtitle)) { } Type get_type() const override { return Type::Subtitle; } void append_file_ids(vector &file_ids) const override { append_rich_text_file_ids(subtitle, file_ids); } tl_object_ptr get_page_block_object() const override { return make_tl_object(get_rich_text_object(subtitle)); } template void store(StorerT &storer) const { using ::td::store; store(subtitle, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(subtitle, parser); } }; class WebPagesManager::PageBlockAuthorDate : public PageBlock { RichText author; int32 date = 0; public: PageBlockAuthorDate() = default; PageBlockAuthorDate(RichText &&author, int32 date) : author(std::move(author)), date(max(date, 0)) { } Type get_type() const override { return Type::AuthorDate; } void append_file_ids(vector &file_ids) const override { append_rich_text_file_ids(author, file_ids); } tl_object_ptr get_page_block_object() const override { return make_tl_object(get_rich_text_object(author), date); } template void store(StorerT &storer) const { using ::td::store; store(author, storer); store(date, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(author, parser); parse(date, parser); } }; class WebPagesManager::PageBlockHeader : public PageBlock { RichText header; public: PageBlockHeader() = default; explicit PageBlockHeader(RichText &&header) : header(std::move(header)) { } Type get_type() const override { return Type::Header; } void append_file_ids(vector &file_ids) const override { append_rich_text_file_ids(header, file_ids); } tl_object_ptr get_page_block_object() const override { return make_tl_object(get_rich_text_object(header)); } template void store(StorerT &storer) const { using ::td::store; store(header, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(header, parser); } }; class WebPagesManager::PageBlockSubheader : public PageBlock { RichText subheader; public: PageBlockSubheader() = default; explicit PageBlockSubheader(RichText &&subheader) : subheader(std::move(subheader)) { } Type get_type() const override { return Type::Subheader; } void append_file_ids(vector &file_ids) const override { append_rich_text_file_ids(subheader, file_ids); } tl_object_ptr get_page_block_object() const override { return make_tl_object(get_rich_text_object(subheader)); } template void store(StorerT &storer) const { using ::td::store; store(subheader, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(subheader, parser); } }; class WebPagesManager::PageBlockKicker : public PageBlock { RichText kicker; public: PageBlockKicker() = default; explicit PageBlockKicker(RichText &&kicker) : kicker(std::move(kicker)) { } Type get_type() const override { return Type::Kicker; } void append_file_ids(vector &file_ids) const override { append_rich_text_file_ids(kicker, file_ids); } tl_object_ptr get_page_block_object() const override { return make_tl_object(get_rich_text_object(kicker)); } template void store(StorerT &storer) const { using ::td::store; store(kicker, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(kicker, parser); } }; class WebPagesManager::PageBlockParagraph : public PageBlock { RichText text; public: PageBlockParagraph() = default; explicit PageBlockParagraph(RichText &&text) : text(std::move(text)) { } Type get_type() const override { return Type::Paragraph; } void append_file_ids(vector &file_ids) const override { append_rich_text_file_ids(text, file_ids); } tl_object_ptr get_page_block_object() const override { return make_tl_object(get_rich_text_object(text)); } template void store(StorerT &storer) const { using ::td::store; store(text, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(text, parser); } }; class WebPagesManager::PageBlockPreformatted : public PageBlock { RichText text; string language; public: PageBlockPreformatted() = default; PageBlockPreformatted(RichText &&text, string language) : text(std::move(text)), language(std::move(language)) { } Type get_type() const override { return Type::Preformatted; } void append_file_ids(vector &file_ids) const override { append_rich_text_file_ids(text, file_ids); } tl_object_ptr get_page_block_object() const override { return make_tl_object(get_rich_text_object(text), language); } template void store(StorerT &storer) const { using ::td::store; store(text, storer); store(language, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(text, parser); parse(language, parser); } }; class WebPagesManager::PageBlockFooter : public PageBlock { RichText footer; public: PageBlockFooter() = default; explicit PageBlockFooter(RichText &&footer) : footer(std::move(footer)) { } Type get_type() const override { return Type::Footer; } void append_file_ids(vector &file_ids) const override { append_rich_text_file_ids(footer, file_ids); } tl_object_ptr get_page_block_object() const override { return make_tl_object(get_rich_text_object(footer)); } template void store(StorerT &storer) const { using ::td::store; store(footer, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(footer, parser); } }; class WebPagesManager::PageBlockDivider : public PageBlock { public: Type get_type() const override { return Type::Divider; } void append_file_ids(vector &file_ids) const override { } tl_object_ptr get_page_block_object() const override { return make_tl_object(); } template void store(StorerT &storer) const { } template void parse(ParserT &parser) { } }; class WebPagesManager::PageBlockAnchor : public PageBlock { string name; public: PageBlockAnchor() = default; explicit PageBlockAnchor(string name) : name(std::move(name)) { } Type get_type() const override { return Type::Anchor; } void append_file_ids(vector &file_ids) const override { } tl_object_ptr get_page_block_object() const override { return make_tl_object(name); } template void store(StorerT &storer) const { using ::td::store; store(name, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(name, parser); } }; class WebPagesManager::PageBlockList : public PageBlock { public: struct Item { string label; vector> page_blocks; template void store(StorerT &storer) const { using ::td::store; store(label, storer); store(page_blocks, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(label, parser); parse(page_blocks, parser); } }; private: vector items; static td_api::object_ptr get_page_block_list_item_object(const Item &item) { // if label is empty, then Bullet U+2022 is used as a label return td_api::make_object(item.label.empty() ? "\xE2\x80\xA2" : item.label, get_page_block_objects(item.page_blocks)); } public: PageBlockList() = default; explicit PageBlockList(vector &&items) : items(std::move(items)) { } Type get_type() const override { return Type::List; } void append_file_ids(vector &file_ids) const override { for (auto &item : items) { for (auto &page_block : item.page_blocks) { page_block->append_file_ids(file_ids); } } } tl_object_ptr get_page_block_object() const override { return td_api::make_object( transform(items, [](const Item &item) { return get_page_block_list_item_object(item); })); } template void store(StorerT &storer) const { using ::td::store; store(items, storer); } template void parse(ParserT &parser) { using ::td::parse; if (parser.version() >= static_cast(Version::SupportInstantView2_0)) { parse(items, parser); } else { vector text_items; bool is_ordered; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_ordered); END_PARSE_FLAGS(); parse(text_items, parser); int pos = 0; items.reserve(text_items.size()); for (auto &text_item : text_items) { Item item; if (is_ordered) { pos++; item.label = (PSTRING() << pos << '.'); } item.page_blocks.push_back(make_unique(std::move(text_item))); items.push_back(std::move(item)); } } } }; class WebPagesManager::PageBlockBlockQuote : public PageBlock { RichText text; RichText credit; public: PageBlockBlockQuote() = default; PageBlockBlockQuote(RichText &&text, RichText &&credit) : text(std::move(text)), credit(std::move(credit)) { } Type get_type() const override { return Type::BlockQuote; } void append_file_ids(vector &file_ids) const override { append_rich_text_file_ids(text, file_ids); append_rich_text_file_ids(credit, file_ids); } tl_object_ptr get_page_block_object() const override { return make_tl_object(get_rich_text_object(text), get_rich_text_object(credit)); } template void store(StorerT &storer) const { using ::td::store; store(text, storer); store(credit, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(text, parser); parse(credit, parser); } }; class WebPagesManager::PageBlockPullQuote : public PageBlock { RichText text; RichText credit; public: PageBlockPullQuote() = default; PageBlockPullQuote(RichText &&text, RichText &&credit) : text(std::move(text)), credit(std::move(credit)) { } Type get_type() const override { return Type::PullQuote; } void append_file_ids(vector &file_ids) const override { append_rich_text_file_ids(text, file_ids); append_rich_text_file_ids(credit, file_ids); } tl_object_ptr get_page_block_object() const override { return make_tl_object(get_rich_text_object(text), get_rich_text_object(credit)); } template void store(StorerT &storer) const { using ::td::store; store(text, storer); store(credit, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(text, parser); parse(credit, parser); } }; class WebPagesManager::PageBlockAnimation : public PageBlock { FileId animation_file_id; PageBlockCaption caption; bool need_autoplay = false; public: PageBlockAnimation() = default; PageBlockAnimation(FileId animation_file_id, PageBlockCaption &&caption, bool need_autoplay) : animation_file_id(animation_file_id), caption(std::move(caption)), need_autoplay(need_autoplay) { } Type get_type() const override { return Type::Animation; } void append_file_ids(vector &file_ids) const override { append_page_block_caption_file_ids(caption, file_ids); if (animation_file_id.is_valid()) { file_ids.push_back(animation_file_id); auto thumbnail_file_id = G()->td().get_actor_unsafe()->animations_manager_->get_animation_thumbnail_file_id(animation_file_id); if (thumbnail_file_id.is_valid()) { file_ids.push_back(thumbnail_file_id); } } } tl_object_ptr get_page_block_object() const override { return make_tl_object( G()->td().get_actor_unsafe()->animations_manager_->get_animation_object(animation_file_id, "get_page_block_object"), get_page_block_caption_object(caption), need_autoplay); } template void store(StorerT &storer) const { using ::td::store; bool has_empty_animation = !animation_file_id.is_valid(); BEGIN_STORE_FLAGS(); STORE_FLAG(need_autoplay); STORE_FLAG(has_empty_animation); END_STORE_FLAGS(); if (!has_empty_animation) { storer.context()->td().get_actor_unsafe()->animations_manager_->store_animation(animation_file_id, storer); } store(caption, storer); } template void parse(ParserT &parser) { using ::td::parse; bool has_empty_animation; BEGIN_PARSE_FLAGS(); PARSE_FLAG(need_autoplay); PARSE_FLAG(has_empty_animation); END_PARSE_FLAGS(); if (parser.version() >= static_cast(Version::FixWebPageInstantViewDatabase)) { if (!has_empty_animation) { animation_file_id = parser.context()->td().get_actor_unsafe()->animations_manager_->parse_animation(parser); } else { animation_file_id = FileId(); } } else { animation_file_id = FileId(); parser.set_error("Wrong stored object"); } parse(caption, parser); } }; class WebPagesManager::PageBlockPhoto : public PageBlock { Photo photo; PageBlockCaption caption; string url; WebPageId web_page_id; public: PageBlockPhoto() = default; PageBlockPhoto(Photo photo, PageBlockCaption &&caption, string &&url, WebPageId web_page_id) : photo(std::move(photo)), caption(std::move(caption)), url(std::move(url)), web_page_id(web_page_id) { } Type get_type() const override { return Type::Photo; } void append_file_ids(vector &file_ids) const override { append_page_block_caption_file_ids(caption, file_ids); } tl_object_ptr get_page_block_object() const override { return make_tl_object( get_photo_object(G()->td().get_actor_unsafe()->file_manager_.get(), &photo), get_page_block_caption_object(caption), url); } template void store(StorerT &storer) const { using ::td::store; store(photo, storer); store(caption, storer); store(url, storer); store(web_page_id, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(photo, parser); parse(caption, parser); if (parser.version() >= static_cast(Version::SupportInstantView2_0)) { parse(url, parser); parse(web_page_id, parser); } else { url.clear(); web_page_id = WebPageId(); } } }; class WebPagesManager::PageBlockVideo : public PageBlock { FileId video_file_id; PageBlockCaption caption; bool need_autoplay = false; bool is_looped = false; public: PageBlockVideo() = default; PageBlockVideo(FileId video_file_id, PageBlockCaption &&caption, bool need_autoplay, bool is_looped) : video_file_id(video_file_id), caption(std::move(caption)), need_autoplay(need_autoplay), is_looped(is_looped) { } Type get_type() const override { return Type::Video; } void append_file_ids(vector &file_ids) const override { append_page_block_caption_file_ids(caption, file_ids); if (video_file_id.is_valid()) { file_ids.push_back(video_file_id); auto thumbnail_file_id = G()->td().get_actor_unsafe()->videos_manager_->get_video_thumbnail_file_id(video_file_id); if (thumbnail_file_id.is_valid()) { file_ids.push_back(thumbnail_file_id); } } } tl_object_ptr get_page_block_object() const override { return make_tl_object( G()->td().get_actor_unsafe()->videos_manager_->get_video_object(video_file_id), get_page_block_caption_object(caption), need_autoplay, is_looped); } template void store(StorerT &storer) const { using ::td::store; bool has_empty_video = !video_file_id.is_valid(); BEGIN_STORE_FLAGS(); STORE_FLAG(need_autoplay); STORE_FLAG(is_looped); STORE_FLAG(has_empty_video); END_STORE_FLAGS(); if (!has_empty_video) { storer.context()->td().get_actor_unsafe()->videos_manager_->store_video(video_file_id, storer); } store(caption, storer); } template void parse(ParserT &parser) { using ::td::parse; bool has_empty_video; BEGIN_PARSE_FLAGS(); PARSE_FLAG(need_autoplay); PARSE_FLAG(is_looped); PARSE_FLAG(has_empty_video); END_PARSE_FLAGS(); if (parser.version() >= static_cast(Version::FixWebPageInstantViewDatabase)) { if (!has_empty_video) { video_file_id = parser.context()->td().get_actor_unsafe()->videos_manager_->parse_video(parser); } else { video_file_id = FileId(); } } else { video_file_id = FileId(); parser.set_error("Wrong stored object"); } parse(caption, parser); } }; class WebPagesManager::PageBlockCover : public PageBlock { unique_ptr cover; public: PageBlockCover() = default; explicit PageBlockCover(unique_ptr &&cover) : cover(std::move(cover)) { } Type get_type() const override { return Type::Cover; } void append_file_ids(vector &file_ids) const override { cover->append_file_ids(file_ids); } tl_object_ptr get_page_block_object() const override { return make_tl_object(cover->get_page_block_object()); } template void store(StorerT &storer) const { using ::td::store; store(cover, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(cover, parser); } }; class WebPagesManager::PageBlockEmbedded : public PageBlock { string url; string html; Photo poster_photo; Dimensions dimensions; PageBlockCaption caption; bool is_full_width; bool allow_scrolling; public: PageBlockEmbedded() = default; PageBlockEmbedded(string url, string html, Photo poster_photo, Dimensions dimensions, PageBlockCaption &&caption, bool is_full_width, bool allow_scrolling) : url(std::move(url)) , html(std::move(html)) , poster_photo(std::move(poster_photo)) , dimensions(dimensions) , caption(std::move(caption)) , is_full_width(is_full_width) , allow_scrolling(allow_scrolling) { } Type get_type() const override { return Type::Embedded; } void append_file_ids(vector &file_ids) const override { append(file_ids, photo_get_file_ids(poster_photo)); append_page_block_caption_file_ids(caption, file_ids); } tl_object_ptr get_page_block_object() const override { return make_tl_object( url, html, get_photo_object(G()->td().get_actor_unsafe()->file_manager_.get(), &poster_photo), dimensions.width, dimensions.height, get_page_block_caption_object(caption), is_full_width, allow_scrolling); } template void store(StorerT &storer) const { using ::td::store; BEGIN_STORE_FLAGS(); STORE_FLAG(is_full_width); STORE_FLAG(allow_scrolling); END_STORE_FLAGS(); store(url, storer); store(html, storer); store(poster_photo, storer); store(dimensions, storer); store(caption, storer); } template void parse(ParserT &parser) { using ::td::parse; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_full_width); PARSE_FLAG(allow_scrolling); END_PARSE_FLAGS(); parse(url, parser); parse(html, parser); parse(poster_photo, parser); parse(dimensions, parser); parse(caption, parser); } }; class WebPagesManager::PageBlockEmbeddedPost : public PageBlock { string url; string author; Photo author_photo; int32 date; vector> page_blocks; PageBlockCaption caption; public: PageBlockEmbeddedPost() = default; PageBlockEmbeddedPost(string url, string author, Photo author_photo, int32 date, vector> &&page_blocks, PageBlockCaption &&caption) : url(std::move(url)) , author(std::move(author)) , author_photo(std::move(author_photo)) , date(max(date, 0)) , page_blocks(std::move(page_blocks)) , caption(std::move(caption)) { } Type get_type() const override { return Type::EmbeddedPost; } void append_file_ids(vector &file_ids) const override { append(file_ids, photo_get_file_ids(author_photo)); for (auto &page_block : page_blocks) { page_block->append_file_ids(file_ids); } append_page_block_caption_file_ids(caption, file_ids); } tl_object_ptr get_page_block_object() const override { return make_tl_object( url, author, get_photo_object(G()->td().get_actor_unsafe()->file_manager_.get(), &author_photo), date, get_page_block_objects(page_blocks), get_page_block_caption_object(caption)); } template void store(StorerT &storer) const { using ::td::store; store(url, storer); store(author, storer); store(author_photo, storer); store(date, storer); store(page_blocks, storer); store(caption, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(url, parser); parse(author, parser); parse(author_photo, parser); parse(date, parser); parse(page_blocks, parser); parse(caption, parser); } }; class WebPagesManager::PageBlockCollage : public PageBlock { vector> page_blocks; PageBlockCaption caption; public: PageBlockCollage() = default; PageBlockCollage(vector> &&page_blocks, PageBlockCaption &&caption) : page_blocks(std::move(page_blocks)), caption(std::move(caption)) { } Type get_type() const override { return Type::Collage; } void append_file_ids(vector &file_ids) const override { for (auto &page_block : page_blocks) { page_block->append_file_ids(file_ids); } append_page_block_caption_file_ids(caption, file_ids); } tl_object_ptr get_page_block_object() const override { return make_tl_object(get_page_block_objects(page_blocks), get_page_block_caption_object(caption)); } template void store(StorerT &storer) const { using ::td::store; store(page_blocks, storer); store(caption, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(page_blocks, parser); parse(caption, parser); } }; class WebPagesManager::PageBlockSlideshow : public PageBlock { vector> page_blocks; PageBlockCaption caption; public: PageBlockSlideshow() = default; PageBlockSlideshow(vector> &&page_blocks, PageBlockCaption &&caption) : page_blocks(std::move(page_blocks)), caption(std::move(caption)) { } Type get_type() const override { return Type::Slideshow; } void append_file_ids(vector &file_ids) const override { for (auto &page_block : page_blocks) { page_block->append_file_ids(file_ids); } append_page_block_caption_file_ids(caption, file_ids); } tl_object_ptr get_page_block_object() const override { return make_tl_object(get_page_block_objects(page_blocks), get_page_block_caption_object(caption)); } template void store(StorerT &storer) const { using ::td::store; store(page_blocks, storer); store(caption, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(page_blocks, parser); parse(caption, parser); } }; class WebPagesManager::PageBlockChatLink : public PageBlock { string title; DialogPhoto photo; string username; public: PageBlockChatLink() = default; PageBlockChatLink(string title, DialogPhoto photo, string username) : title(std::move(title)), photo(std::move(photo)), username(std::move(username)) { } Type get_type() const override { return Type::ChatLink; } void append_file_ids(vector &file_ids) const override { append(file_ids, dialog_photo_get_file_ids(photo)); } tl_object_ptr get_page_block_object() const override { return make_tl_object( title, get_chat_photo_object(G()->td().get_actor_unsafe()->file_manager_.get(), &photo), username); } template void store(StorerT &storer) const { using ::td::store; store(title, storer); store(photo, storer); store(username, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(title, parser); parse(photo, parser); parse(username, parser); } }; class WebPagesManager::PageBlockAudio : public PageBlock { FileId audio_file_id; PageBlockCaption caption; public: PageBlockAudio() = default; PageBlockAudio(FileId audio_file_id, PageBlockCaption &&caption) : audio_file_id(audio_file_id), caption(std::move(caption)) { } Type get_type() const override { return Type::Audio; } void append_file_ids(vector &file_ids) const override { if (audio_file_id.is_valid()) { file_ids.push_back(audio_file_id); auto thumbnail_file_id = G()->td().get_actor_unsafe()->audios_manager_->get_audio_thumbnail_file_id(audio_file_id); if (thumbnail_file_id.is_valid()) { file_ids.push_back(thumbnail_file_id); } } append_page_block_caption_file_ids(caption, file_ids); } tl_object_ptr get_page_block_object() const override { return make_tl_object( G()->td().get_actor_unsafe()->audios_manager_->get_audio_object(audio_file_id), get_page_block_caption_object(caption)); } template void store(StorerT &storer) const { using ::td::store; bool has_empty_audio = !audio_file_id.is_valid(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_empty_audio); END_STORE_FLAGS(); if (!has_empty_audio) { storer.context()->td().get_actor_unsafe()->audios_manager_->store_audio(audio_file_id, storer); } store(caption, storer); } template void parse(ParserT &parser) { using ::td::parse; bool has_empty_audio; if (parser.version() >= static_cast(Version::FixPageBlockAudioEmptyFile)) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_empty_audio); END_PARSE_FLAGS(); } else { has_empty_audio = false; } if (!has_empty_audio) { audio_file_id = parser.context()->td().get_actor_unsafe()->audios_manager_->parse_audio(parser); } else { audio_file_id = FileId(); } parse(caption, parser); } }; class WebPagesManager::PageBlockTable : public PageBlock { RichText title; vector> cells; bool is_bordered = false; bool is_striped = false; public: PageBlockTable() = default; PageBlockTable(RichText &&title, vector> &&cells, bool is_bordered, bool is_striped) : title(std::move(title)), cells(std::move(cells)), is_bordered(is_bordered), is_striped(is_striped) { } Type get_type() const override { return Type::Table; } void append_file_ids(vector &file_ids) const override { append_rich_text_file_ids(title, file_ids); for (auto &row : cells) { for (auto &cell : row) { append_rich_text_file_ids(cell.text, file_ids); } } } tl_object_ptr get_page_block_object() const override { auto cell_objects = transform(cells, [&](const vector &row) { return transform(row, [&](const PageBlockTableCell &cell) { return get_page_block_table_cell_object(cell); }); }); return make_tl_object(get_rich_text_object(title), std::move(cell_objects), is_bordered, is_striped); } template void store(StorerT &storer) const { using ::td::store; BEGIN_STORE_FLAGS(); STORE_FLAG(is_bordered); STORE_FLAG(is_striped); END_STORE_FLAGS(); store(title, storer); store(cells, storer); } template void parse(ParserT &parser) { using ::td::parse; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_bordered); PARSE_FLAG(is_striped); END_PARSE_FLAGS(); parse(title, parser); parse(cells, parser); } }; class WebPagesManager::PageBlockDetails : public PageBlock { RichText header; vector> page_blocks; bool is_open; public: PageBlockDetails() = default; PageBlockDetails(RichText &&header, vector> &&page_blocks, bool is_open) : header(std::move(header)), page_blocks(std::move(page_blocks)), is_open(is_open) { } Type get_type() const override { return Type::Details; } void append_file_ids(vector &file_ids) const override { append_rich_text_file_ids(header, file_ids); for (auto &page_block : page_blocks) { page_block->append_file_ids(file_ids); } } tl_object_ptr get_page_block_object() const override { return make_tl_object(get_rich_text_object(header), get_page_block_objects(page_blocks), is_open); } template void store(StorerT &storer) const { using ::td::store; BEGIN_STORE_FLAGS(); STORE_FLAG(is_open); END_STORE_FLAGS(); store(header, storer); store(page_blocks, storer); } template void parse(ParserT &parser) { using ::td::parse; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_open); END_PARSE_FLAGS(); parse(header, parser); parse(page_blocks, parser); } }; class WebPagesManager::PageBlockRelatedArticles : public PageBlock { RichText header; vector related_articles; public: PageBlockRelatedArticles() = default; PageBlockRelatedArticles(RichText &&header, vector &&related_articles) : header(std::move(header)), related_articles(std::move(related_articles)) { } Type get_type() const override { return Type::RelatedArticles; } void append_file_ids(vector &file_ids) const override { append_rich_text_file_ids(header, file_ids); for (auto &article : related_articles) { if (article.photo.id != -2) { append(file_ids, photo_get_file_ids(article.photo)); } } } tl_object_ptr get_page_block_object() const override { auto related_article_objects = transform(related_articles, [](const RelatedArticle &article) { return td_api::make_object( article.url, article.title, article.description, get_photo_object(G()->td().get_actor_unsafe()->file_manager_.get(), &article.photo), article.author, article.published_date); }); return make_tl_object(get_rich_text_object(header), std::move(related_article_objects)); } template void store(StorerT &storer) const { using ::td::store; store(header, storer); store(related_articles, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(header, parser); parse(related_articles, parser); } }; class WebPagesManager::PageBlockMap : public PageBlock { Location location; int32 zoom = 0; Dimensions dimensions; PageBlockCaption caption; public: PageBlockMap() = default; PageBlockMap(Location location, int32 zoom, Dimensions dimensions, PageBlockCaption &&caption) : location(std::move(location)), zoom(zoom), dimensions(dimensions), caption(std::move(caption)) { } Type get_type() const override { return Type::Map; } void append_file_ids(vector &file_ids) const override { append_page_block_caption_file_ids(caption, file_ids); } tl_object_ptr get_page_block_object() const override { return make_tl_object(location.get_location_object(), zoom, dimensions.width, dimensions.height, get_page_block_caption_object(caption)); } template void store(StorerT &storer) const { using ::td::store; store(location, storer); store(zoom, storer); store(dimensions, storer); store(caption, storer); } template void parse(ParserT &parser) { using ::td::parse; parse(location, parser); parse(zoom, parser); parse(dimensions, parser); parse(caption, parser); } }; template void WebPagesManager::PageBlock::call_impl(Type type, const PageBlock *ptr, F &&f) { switch (type) { case Type::Title: return f(static_cast(ptr)); case Type::Subtitle: return f(static_cast(ptr)); case Type::AuthorDate: return f(static_cast(ptr)); case Type::Header: return f(static_cast(ptr)); case Type::Subheader: return f(static_cast(ptr)); case Type::Kicker: return f(static_cast(ptr)); case Type::Paragraph: return f(static_cast(ptr)); case Type::Preformatted: return f(static_cast(ptr)); case Type::Footer: return f(static_cast(ptr)); case Type::Divider: return f(static_cast(ptr)); case Type::Anchor: return f(static_cast(ptr)); case Type::List: return f(static_cast(ptr)); case Type::BlockQuote: return f(static_cast(ptr)); case Type::PullQuote: return f(static_cast(ptr)); case Type::Animation: return f(static_cast(ptr)); case Type::Photo: return f(static_cast(ptr)); case Type::Video: return f(static_cast(ptr)); case Type::Cover: return f(static_cast(ptr)); case Type::Embedded: return f(static_cast(ptr)); case Type::EmbeddedPost: return f(static_cast(ptr)); case Type::Collage: return f(static_cast(ptr)); case Type::Slideshow: return f(static_cast(ptr)); case Type::ChatLink: return f(static_cast(ptr)); case Type::Audio: return f(static_cast(ptr)); case Type::Table: return f(static_cast(ptr)); case Type::Details: return f(static_cast(ptr)); case Type::RelatedArticles: return f(static_cast(ptr)); case Type::Map: return f(static_cast(ptr)); } UNREACHABLE(); } WebPagesManager::WebPagesManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { pending_web_pages_timeout_.set_callback(on_pending_web_page_timeout_callback); pending_web_pages_timeout_.set_callback_data(static_cast(this)); } void WebPagesManager::tear_down() { parent_.reset(); } WebPagesManager::~WebPagesManager() = default; WebPageId WebPagesManager::on_get_web_page(tl_object_ptr &&web_page_ptr, DialogId owner_dialog_id) { CHECK(web_page_ptr != nullptr); LOG(DEBUG) << "Got " << to_string(web_page_ptr); switch (web_page_ptr->get_id()) { case telegram_api::webPageEmpty::ID: { auto web_page = move_tl_object_as(web_page_ptr); WebPageId web_page_id(web_page->id_); if (!web_page_id.is_valid()) { LOG_IF(ERROR, web_page_id != WebPageId()) << "Receive invalid " << web_page_id; return WebPageId(); } LOG(INFO) << "Got empty " << web_page_id; auto web_page_to_delete = get_web_page(web_page_id); if (web_page_to_delete != nullptr) { if (web_page_to_delete->logevent_id != 0) { LOG(INFO) << "Erase " << web_page_id << " from binlog"; binlog_erase(G()->td_db()->get_binlog(), web_page_to_delete->logevent_id); web_page_to_delete->logevent_id = 0; } if (web_page_to_delete->file_source_id.is_valid()) { td_->file_manager_->change_files_source(web_page_to_delete->file_source_id, get_web_page_file_ids(web_page_to_delete), vector()); } web_pages_.erase(web_page_id); } update_messages_content(web_page_id, false); if (!G()->parameters().use_message_db) { // update_messages_content(web_page_id, false); } else { LOG(INFO) << "Delete " << web_page_id << " from database"; G()->td_db()->get_sqlite_pmc()->erase(get_web_page_database_key(web_page_id), Auto() /* PromiseCreator::lambda([web_page_id](Result<> result) { if (result.is_ok()) { send_closure(G()->web_pages_manager(), &WebPagesManager::update_messages_content, web_page_id, false); } }) */ ); G()->td_db()->get_sqlite_pmc()->erase(get_web_page_instant_view_database_key(web_page_id), Auto()); } return WebPageId(); } case telegram_api::webPagePending::ID: { auto web_page = move_tl_object_as(web_page_ptr); WebPageId web_page_id(web_page->id_); if (!web_page_id.is_valid()) { LOG(ERROR) << "Receive invalid " << web_page_id; return WebPageId(); } auto web_page_date = web_page->date_; LOG(INFO) << "Got pending " << web_page_id << ", date = " << web_page_date << ", now = " << G()->server_time(); pending_web_pages_timeout_.add_timeout_in(web_page_id.get(), max(web_page_date - G()->server_time(), 1.0)); return web_page_id; } case telegram_api::webPage::ID: { auto web_page = move_tl_object_as(web_page_ptr); WebPageId web_page_id(web_page->id_); if (!web_page_id.is_valid()) { LOG(ERROR) << "Receive invalid " << web_page_id; return WebPageId(); } LOG(INFO) << "Got " << web_page_id; auto page = make_unique(); page->url = std::move(web_page->url_); page->display_url = std::move(web_page->display_url_); page->type = std::move(web_page->type_); page->site_name = std::move(web_page->site_name_); page->title = std::move(web_page->title_); page->description = std::move(web_page->description_); if ((web_page->flags_ & WEBPAGE_FLAG_HAS_PHOTO) && web_page->photo_->get_id() == telegram_api::photo::ID) { page->photo = get_photo(td_->file_manager_.get(), move_tl_object_as(web_page->photo_), owner_dialog_id); } else { page->photo.id = -2; } if (web_page->flags_ & WEBPAGE_FLAG_HAS_EMBEDDED_PREVIEW) { page->embed_url = std::move(web_page->embed_url_); page->embed_type = std::move(web_page->embed_type_); } if (web_page->flags_ & WEBPAGE_FLAG_HAS_EMBEDDED_PREVIEW_SIZE) { page->embed_dimensions = get_dimensions(web_page->embed_width_, web_page->embed_height_); } if (web_page->flags_ & WEBPAGE_FLAG_HAS_DURATION) { page->duration = web_page->duration_; if (page->duration < 0) { LOG(ERROR) << "Receive wrong web page duration " << page->duration; page->duration = 0; } } if (web_page->flags_ & WEBPAGE_FLAG_HAS_AUTHOR) { page->author = std::move(web_page->author_); } if (web_page->flags_ & WEBPAGE_FLAG_HAS_DOCUMENT) { int32 document_id = web_page->document_->get_id(); if (document_id == telegram_api::document::ID) { auto parsed_document = td_->documents_manager_->on_get_document( move_tl_object_as(web_page->document_), owner_dialog_id); page->document_type = parsed_document.first; page->document_file_id = parsed_document.second; } } if (web_page->flags_ & WEBPAGE_FLAG_HAS_INSTANT_VIEW) { on_get_web_page_instant_view(page.get(), std::move(web_page->cached_page_), web_page->hash_, owner_dialog_id); } update_web_page(std::move(page), web_page_id, false, false); return web_page_id; } case telegram_api::webPageNotModified::ID: { LOG(ERROR) << "Receive webPageNotModified"; return WebPageId(); } default: UNREACHABLE(); return WebPageId(); } } void WebPagesManager::update_web_page(unique_ptr web_page, WebPageId web_page_id, bool from_binlog, bool from_database) { LOG(INFO) << "Update " << web_page_id; CHECK(web_page != nullptr); auto &page = web_pages_[web_page_id]; auto old_file_ids = get_web_page_file_ids(page.get()); WebPageInstantView old_instant_view; if (page != nullptr) { old_instant_view = std::move(page->instant_view); web_page->logevent_id = page->logevent_id; } else { auto it = url_to_file_source_id_.find(web_page->url); if (it != url_to_file_source_id_.end()) { VLOG(file_references) << "Move " << it->second << " inside of " << web_page_id; web_page->file_source_id = it->second; url_to_file_source_id_.erase(it); } } page = std::move(web_page); update_web_page_instant_view(web_page_id, page->instant_view, std::move(old_instant_view)); auto new_file_ids = get_web_page_file_ids(page.get()); if (old_file_ids != new_file_ids) { td_->file_manager_->change_files_source(get_web_page_file_source_id(page.get()), old_file_ids, new_file_ids); } on_get_web_page_by_url(page->url, web_page_id, from_database); update_messages_content(web_page_id, true); if (!from_database) { save_web_page(page.get(), web_page_id, from_binlog); } } void WebPagesManager::update_web_page_instant_view(WebPageId web_page_id, WebPageInstantView &new_instant_view, WebPageInstantView &&old_instant_view) { LOG(INFO) << "Merge new " << new_instant_view << " and old " << old_instant_view; bool new_from_database = new_instant_view.was_loaded_from_database; bool old_from_database = old_instant_view.was_loaded_from_database; if (new_instant_view.is_empty && !new_from_database) { // new_instant_view is from server and is empty, need to delete the instant view if (G()->parameters().use_message_db && (!old_instant_view.is_empty || !old_from_database)) { // we have no instant view and probably want it to be deleted from database LOG(INFO) << "Erase instant view of " << web_page_id << " from database"; new_instant_view.was_loaded_from_database = true; G()->td_db()->get_sqlite_pmc()->erase(get_web_page_instant_view_database_key(web_page_id), Auto()); } return; } if (need_use_old_instant_view(new_instant_view, old_instant_view)) { new_instant_view = std::move(old_instant_view); } if (G()->parameters().use_message_db && !new_instant_view.is_empty && new_instant_view.is_loaded) { // we have instant view and probably want it to be saved if (!new_from_database && !old_from_database) { // if it wasn't loaded from the database, load it first auto &load_web_page_instant_view_queries = load_web_page_instant_view_queries_[web_page_id]; auto previous_queries = load_web_page_instant_view_queries.partial.size() + load_web_page_instant_view_queries.full.size(); if (previous_queries == 0) { // try to load it only if there is no pending load queries load_web_page_instant_view(web_page_id, false, Auto()); return; } } if (!new_instant_view.was_loaded_from_database) { LOG(INFO) << "Save instant view of " << web_page_id << " to database"; /* if (web_page_id.get() == 0) { auto blocks = std::move(new_instant_view.page_blocks); new_instant_view.page_blocks.clear(); for (size_t i = 0; i < blocks.size(); i++) { LOG(ERROR) << to_string(blocks[i]->get_page_block_object()); new_instant_view.page_blocks.push_back(std::move(blocks[i])); log_event_store(new_instant_view); } UNREACHABLE(); } */ new_instant_view.was_loaded_from_database = true; G()->td_db()->get_sqlite_pmc()->set(get_web_page_instant_view_database_key(web_page_id), log_event_store(new_instant_view).as_slice().str(), Auto()); } } } bool WebPagesManager::need_use_old_instant_view(const WebPageInstantView &new_instant_view, const WebPageInstantView &old_instant_view) { if (old_instant_view.is_empty || !old_instant_view.is_loaded) { return false; } if (new_instant_view.is_empty || !new_instant_view.is_loaded) { return true; } if (new_instant_view.is_full != old_instant_view.is_full) { return old_instant_view.is_full; } if (new_instant_view.hash == old_instant_view.hash) { // the same instant view return !new_instant_view.is_full || old_instant_view.is_full; } // data in database is always outdated return new_instant_view.was_loaded_from_database; } void WebPagesManager::on_get_web_page_by_url(const string &url, WebPageId web_page_id, bool from_database) { if (!from_database && G()->parameters().use_message_db) { if (web_page_id.is_valid()) { G()->td_db()->get_sqlite_pmc()->set(get_web_page_url_database_key(url), to_string(web_page_id.get()), Auto()); } else { G()->td_db()->get_sqlite_pmc()->erase(get_web_page_url_database_key(url), Auto()); } } auto &cached_web_page_id = url_to_web_page_id_[url]; if (cached_web_page_id.is_valid() && web_page_id.is_valid() && web_page_id != cached_web_page_id) { LOG(ERROR) << "Url \"" << url << "\" preview is changed from " << cached_web_page_id << " to " << web_page_id; } cached_web_page_id = web_page_id; } void WebPagesManager::wait_for_pending_web_page(DialogId dialog_id, MessageId message_id, WebPageId web_page_id) { LOG(INFO) << "Waiting for " << web_page_id << " needed in " << message_id << " in " << dialog_id; pending_web_pages_[web_page_id].emplace(dialog_id, message_id); pending_web_pages_timeout_.add_timeout_in(web_page_id.get(), 1.0); } void WebPagesManager::on_get_web_page_preview_success(int64 request_id, const string &url, tl_object_ptr &&message_media_ptr, Promise &&promise) { CHECK(message_media_ptr != nullptr); int32 constructor_id = message_media_ptr->get_id(); if (constructor_id != telegram_api::messageMediaWebPage::ID) { if (constructor_id == telegram_api::messageMediaEmpty::ID) { on_get_web_page_preview_success(request_id, url, WebPageId(), std::move(promise)); return; } LOG(ERROR) << "Receive " << to_string(message_media_ptr) << " instead of web page"; on_get_web_page_preview_fail(request_id, url, Status::Error(500, "Receive not web page in GetWebPagePreview"), std::move(promise)); return; } auto message_media_web_page = move_tl_object_as(message_media_ptr); CHECK(message_media_web_page->webpage_ != nullptr); auto web_page_id = on_get_web_page(std::move(message_media_web_page->webpage_), DialogId()); if (web_page_id.is_valid() && !have_web_page(web_page_id)) { pending_get_web_pages_[web_page_id].emplace(request_id, std::make_pair(url, std::move(promise))); // TODO MultiPromise ? pending_web_pages_timeout_.add_timeout_in(web_page_id.get(), 1.0); return; } on_get_web_page_preview_success(request_id, url, web_page_id, std::move(promise)); } void WebPagesManager::on_get_web_page_preview_success(int64 request_id, const string &url, WebPageId web_page_id, Promise &&promise) { CHECK(web_page_id == WebPageId() || have_web_page(web_page_id)); CHECK(got_web_page_previews_.find(request_id) == got_web_page_previews_.end()); got_web_page_previews_[request_id] = web_page_id; if (web_page_id.is_valid() && !url.empty()) { on_get_web_page_by_url(url, web_page_id, true); } promise.set_value(Unit()); } void WebPagesManager::on_get_web_page_preview_fail(int64 request_id, const string &url, Status error, Promise &&promise) { LOG(INFO) << "Clean up getting of web page preview with url \"" << url << '"'; CHECK(error.is_error()); promise.set_error(std::move(error)); } int64 WebPagesManager::get_web_page_preview(td_api::object_ptr &&text, Promise &&promise) { if (text == nullptr) { promise.set_value(Unit()); return 0; } auto r_entities = get_message_entities(td_->contacts_manager_.get(), std::move(text->entities_)); if (r_entities.is_error()) { promise.set_error(r_entities.move_as_error()); return 0; } auto entities = r_entities.move_as_ok(); auto result = fix_formatted_text(text->text_, entities, true, false, true, false); if (text->text_.empty()) { promise.set_value(Unit()); return 0; } auto url = get_first_url(text->text_, entities); if (url.empty()) { promise.set_value(Unit()); return 0; } LOG(INFO) << "Trying to get web page preview for message \"" << text->text_ << '"'; int64 request_id = get_web_page_preview_request_id_++; auto web_page_id = get_web_page_by_url(url); if (web_page_id.is_valid()) { got_web_page_previews_[request_id] = web_page_id; promise.set_value(Unit()); } else { td_->create_handler(std::move(promise)) ->send(text->text_, get_input_message_entities(td_->contacts_manager_.get(), entities, "get_web_page_preview"), request_id, std::move(url)); } return request_id; } tl_object_ptr WebPagesManager::get_web_page_preview_result(int64 request_id) { if (request_id == 0) { return nullptr; } auto it = got_web_page_previews_.find(request_id); CHECK(it != got_web_page_previews_.end()); auto web_page_id = it->second; got_web_page_previews_.erase(it); return get_web_page_object(web_page_id); } WebPageId WebPagesManager::get_web_page_instant_view(const string &url, bool force_full, bool force, Promise &&promise) { LOG(INFO) << "Trying to get web page instant view for the url \"" << url << '"'; auto it = url_to_web_page_id_.find(url); if (it != url_to_web_page_id_.end()) { if (it->second == WebPageId() && !force) { // ignore negative caching reload_web_page_by_url(url, std::move(promise)); return WebPageId(); } return get_web_page_instant_view(it->second, force_full, std::move(promise)); } load_web_page_by_url(url, std::move(promise)); return WebPageId(); } WebPageId WebPagesManager::get_web_page_instant_view(WebPageId web_page_id, bool force_full, Promise &&promise) { LOG(INFO) << "Trying to get web page instant view for " << web_page_id; auto web_page_instant_view = get_web_page_instant_view(web_page_id); if (web_page_instant_view == nullptr) { promise.set_value(Unit()); return WebPageId(); } if (!web_page_instant_view->is_loaded || (force_full && !web_page_instant_view->is_full)) { load_web_page_instant_view(web_page_id, force_full, std::move(promise)); return WebPageId(); } if (force_full) { reload_web_page_instant_view(web_page_id); } promise.set_value(Unit()); return web_page_id; } string WebPagesManager::get_web_page_instant_view_database_key(WebPageId web_page_id) { return PSTRING() << "wpiv" << web_page_id.get(); } void WebPagesManager::load_web_page_instant_view(WebPageId web_page_id, bool force_full, Promise &&promise) { auto &load_web_page_instant_view_queries = load_web_page_instant_view_queries_[web_page_id]; auto previous_queries = load_web_page_instant_view_queries.partial.size() + load_web_page_instant_view_queries.full.size(); if (force_full) { load_web_page_instant_view_queries.full.push_back(std::move(promise)); } else { load_web_page_instant_view_queries.partial.push_back(std::move(promise)); } LOG(INFO) << "Load " << web_page_id << " instant view, have " << previous_queries << " previous queries"; if (previous_queries == 0) { auto web_page_instant_view = get_web_page_instant_view(web_page_id); CHECK(web_page_instant_view != nullptr); if (G()->parameters().use_message_db && !web_page_instant_view->was_loaded_from_database) { LOG(INFO) << "Trying to load " << web_page_id << " instant view from database"; G()->td_db()->get_sqlite_pmc()->get( get_web_page_instant_view_database_key(web_page_id), PromiseCreator::lambda([web_page_id](string value) { send_closure(G()->web_pages_manager(), &WebPagesManager::on_load_web_page_instant_view_from_database, web_page_id, std::move(value)); })); } else { reload_web_page_instant_view(web_page_id); } } } void WebPagesManager::reload_web_page_instant_view(WebPageId web_page_id) { LOG(INFO) << "Reload " << web_page_id << " instant view"; auto web_page = get_web_page(web_page_id); CHECK(web_page != nullptr && !web_page->instant_view.is_empty); auto promise = PromiseCreator::lambda([web_page_id](Result<> result) { send_closure(G()->web_pages_manager(), &WebPagesManager::update_web_page_instant_view_load_requests, web_page_id, true, std::move(result)); }); td_->create_handler(std::move(promise)) ->send(web_page->url, web_page->instant_view.is_full ? web_page->instant_view.hash : 0); } void WebPagesManager::on_load_web_page_instant_view_from_database(WebPageId web_page_id, string value) { CHECK(G()->parameters().use_message_db); LOG(INFO) << "Successfully loaded " << web_page_id << " instant view of size " << value.size() << " from database"; // G()->td_db()->get_sqlite_pmc()->erase(get_web_page_instant_view_database_key(web_page_id), Auto()); // return; auto web_page_it = web_pages_.find(web_page_id); if (web_page_it == web_pages_.end() || web_page_it->second->instant_view.is_empty) { // possible if web page loses preview/instant view LOG(WARNING) << "There is no instant view in " << web_page_id; if (!value.empty()) { G()->td_db()->get_sqlite_pmc()->erase(get_web_page_instant_view_database_key(web_page_id), Auto()); } update_web_page_instant_view_load_requests(web_page_id, true, Unit()); return; } WebPage *web_page = web_page_it->second.get(); auto &web_page_instant_view = web_page->instant_view; if (web_page_instant_view.was_loaded_from_database) { return; } WebPageInstantView result; if (!value.empty()) { if (log_event_parse(result, value).is_error()) { result = WebPageInstantView(); LOG(ERROR) << "Erase instant view in " << web_page_id << " from database"; G()->td_db()->get_sqlite_pmc()->erase(get_web_page_instant_view_database_key(web_page_id), Auto()); } } result.was_loaded_from_database = true; auto old_file_ids = get_web_page_file_ids(web_page); update_web_page_instant_view(web_page_id, web_page_instant_view, std::move(result)); auto new_file_ids = get_web_page_file_ids(web_page); if (old_file_ids != new_file_ids) { td_->file_manager_->change_files_source(get_web_page_file_source_id(web_page), old_file_ids, new_file_ids); } update_web_page_instant_view_load_requests(web_page_id, false, Unit()); } void WebPagesManager::update_web_page_instant_view_load_requests(WebPageId web_page_id, bool force_update, Result<> result) { // TODO [Error : 0 : Lost promise] on closing LOG(INFO) << "Update load requests for " << web_page_id; auto it = load_web_page_instant_view_queries_.find(web_page_id); if (it == load_web_page_instant_view_queries_.end()) { return; } vector> promises[2]; promises[0] = std::move(it->second.partial); promises[1] = std::move(it->second.full); reset_to_empty(it->second.partial); reset_to_empty(it->second.full); load_web_page_instant_view_queries_.erase(it); if (result.is_error()) { LOG(INFO) << "Receive error " << result.error() << " for load " << web_page_id; combine(promises[0], std::move(promises[1])); for (auto &promise : promises[0]) { promise.set_error(result.error().clone()); } return; } LOG(INFO) << "Successfully loaded web page " << web_page_id; auto web_page_instant_view = get_web_page_instant_view(web_page_id); if (web_page_instant_view == nullptr) { combine(promises[0], std::move(promises[1])); for (auto &promise : promises[0]) { promise.set_value(Unit()); } return; } if (web_page_instant_view->is_loaded) { if (web_page_instant_view->is_full) { combine(promises[0], std::move(promises[1])); } for (auto &promise : promises[0]) { promise.set_value(Unit()); } reset_to_empty(promises[0]); } if (!promises[0].empty() || !promises[1].empty()) { if (force_update) { // protection from cycles LOG(ERROR) << "Expected to receive " << web_page_id << " from the server, but didn't receive it"; combine(promises[0], std::move(promises[1])); for (auto &promise : promises[0]) { promise.set_value(Unit()); } return; } auto &load_queries = load_web_page_instant_view_queries_[web_page_id]; auto old_size = load_queries.partial.size() + load_queries.full.size(); combine(load_queries.partial, std::move(promises[0])); combine(load_queries.full, std::move(promises[1])); if (old_size == 0) { reload_web_page_instant_view(web_page_id); } } } WebPageId WebPagesManager::get_web_page_by_url(const string &url) const { if (url.empty()) { return WebPageId(); } LOG(INFO) << "Get web page id for the url \"" << url << '"'; auto it = url_to_web_page_id_.find(url); if (it != url_to_web_page_id_.end()) { return it->second; } return WebPageId(); } WebPageId WebPagesManager::get_web_page_by_url(const string &url, Promise &&promise) { LOG(INFO) << "Trying to get web page id for the url \"" << url << '"'; auto it = url_to_web_page_id_.find(url); if (it != url_to_web_page_id_.end()) { promise.set_value(Unit()); return it->second; } load_web_page_by_url(url, std::move(promise)); return WebPageId(); } void WebPagesManager::load_web_page_by_url(const string &url, Promise &&promise) { if (!G()->parameters().use_message_db) { reload_web_page_by_url(url, std::move(promise)); return; } LOG(INFO) << "Load \"" << url << '"'; G()->td_db()->get_sqlite_pmc()->get(get_web_page_url_database_key(url), PromiseCreator::lambda([url, promise = std::move(promise)](string value) mutable { send_closure(G()->web_pages_manager(), &WebPagesManager::on_load_web_page_id_by_url_from_database, url, value, std::move(promise)); })); } void WebPagesManager::on_load_web_page_id_by_url_from_database(const string &url, string value, Promise &&promise) { LOG(INFO) << "Successfully loaded url \"" << url << "\" of size " << value.size() << " from database"; // G()->td_db()->get_sqlite_pmc()->erase(get_web_page_url_database_key(web_page_id), Auto()); // return; auto it = url_to_web_page_id_.find(url); if (it != url_to_web_page_id_.end()) { // URL web page has already been loaded promise.set_value(Unit()); return; } if (!value.empty()) { auto web_page_id = WebPageId(to_integer(value)); if (web_page_id.is_valid()) { if (have_web_page(web_page_id)) { // URL web page has already been loaded on_get_web_page_by_url(url, web_page_id, true); promise.set_value(Unit()); return; } load_web_page_from_database( web_page_id, PromiseCreator::lambda([web_page_id, url, promise = std::move(promise)](Result<> result) mutable { send_closure(G()->web_pages_manager(), &WebPagesManager::on_load_web_page_by_url_from_database, web_page_id, url, std::move(promise), std::move(result)); })); return; } else { LOG(ERROR) << "Receive invalid " << web_page_id; } } reload_web_page_by_url(url, std::move(promise)); } void WebPagesManager::on_load_web_page_by_url_from_database(WebPageId web_page_id, const string &url, Promise &&promise, Result<> result) { if (result.is_error()) { CHECK(G()->close_flag()); promise.set_error(Status::Error(500, "Request aborted")); return; } auto web_page = get_web_page(web_page_id); if (web_page == nullptr) { reload_web_page_by_url(url, std::move(promise)); return; } if (web_page->url != url) { on_get_web_page_by_url(url, web_page_id, true); } promise.set_value(Unit()); } void WebPagesManager::reload_web_page_by_url(const string &url, Promise &&promise) { LOG(INFO) << "Reload url \"" << url << '"'; td_->create_handler(std::move(promise))->send(url, 0); } SecretInputMedia WebPagesManager::get_secret_input_media(WebPageId web_page_id) const { if (!web_page_id.is_valid()) { return SecretInputMedia{}; } auto web_page = get_web_page(web_page_id); if (web_page == nullptr) { return SecretInputMedia{}; } return SecretInputMedia{nullptr, make_tl_object(web_page->url)}; } bool WebPagesManager::have_web_page(WebPageId web_page_id) const { if (!web_page_id.is_valid()) { return false; } return get_web_page(web_page_id) != nullptr; } tl_object_ptr WebPagesManager::get_web_page_object(WebPageId web_page_id) const { if (!web_page_id.is_valid()) { return nullptr; } auto web_page = get_web_page(web_page_id); if (web_page == nullptr) { return nullptr; } int32 instant_view_version = [web_page] { if (web_page->instant_view.is_empty) { return 0; } if (web_page->instant_view.is_v2) { return 2; } return 1; }(); return make_tl_object( web_page->url, web_page->display_url, web_page->type, web_page->site_name, web_page->title, web_page->description, get_photo_object(td_->file_manager_.get(), &web_page->photo), web_page->embed_url, web_page->embed_type, web_page->embed_dimensions.width, web_page->embed_dimensions.height, web_page->duration, web_page->author, web_page->document_type == DocumentsManager::DocumentType::Animation ? td_->animations_manager_->get_animation_object(web_page->document_file_id, "get_web_page_object") : nullptr, web_page->document_type == DocumentsManager::DocumentType::Audio ? td_->audios_manager_->get_audio_object(web_page->document_file_id) : nullptr, web_page->document_type == DocumentsManager::DocumentType::General ? td_->documents_manager_->get_document_object(web_page->document_file_id) : nullptr, web_page->document_type == DocumentsManager::DocumentType::Sticker ? td_->stickers_manager_->get_sticker_object(web_page->document_file_id) : nullptr, web_page->document_type == DocumentsManager::DocumentType::Video ? td_->videos_manager_->get_video_object(web_page->document_file_id) : nullptr, web_page->document_type == DocumentsManager::DocumentType::VideoNote ? td_->video_notes_manager_->get_video_note_object(web_page->document_file_id) : nullptr, web_page->document_type == DocumentsManager::DocumentType::VoiceNote ? td_->voice_notes_manager_->get_voice_note_object(web_page->document_file_id) : nullptr, instant_view_version); } tl_object_ptr WebPagesManager::get_web_page_instant_view_object( WebPageId web_page_id) const { return get_web_page_instant_view_object(get_web_page_instant_view(web_page_id)); } tl_object_ptr WebPagesManager::get_web_page_instant_view_object( const WebPageInstantView *web_page_instant_view) const { if (web_page_instant_view == nullptr) { return nullptr; } if (!web_page_instant_view->is_loaded) { LOG(ERROR) << "Trying to get not loaded web page instant view"; return nullptr; } return make_tl_object(get_page_block_objects(web_page_instant_view->page_blocks), web_page_instant_view->url, web_page_instant_view->is_rtl, web_page_instant_view->is_full); } void WebPagesManager::update_messages_content(WebPageId web_page_id, bool have_web_page) { LOG(INFO) << "Update messages awaiting " << web_page_id; auto it = pending_web_pages_.find(web_page_id); if (it != pending_web_pages_.end()) { auto full_message_ids = std::move(it->second); pending_web_pages_.erase(it); for (auto full_message_id : full_message_ids) { send_closure_later(G()->messages_manager(), &MessagesManager::on_update_message_web_page, full_message_id, have_web_page); } } auto get_it = pending_get_web_pages_.find(web_page_id); if (get_it != pending_get_web_pages_.end()) { auto requests = std::move(get_it->second); pending_get_web_pages_.erase(get_it); for (auto &request : requests) { on_get_web_page_preview_success(request.first, request.second.first, have_web_page ? web_page_id : WebPageId(), std::move(request.second.second)); } } pending_web_pages_timeout_.cancel_timeout(web_page_id.get()); } const WebPagesManager::WebPage *WebPagesManager::get_web_page(WebPageId web_page_id) const { auto p = web_pages_.find(web_page_id); if (p == web_pages_.end()) { return nullptr; } else { return p->second.get(); } } const WebPagesManager::WebPageInstantView *WebPagesManager::get_web_page_instant_view(WebPageId web_page_id) const { auto web_page = get_web_page(web_page_id); if (web_page == nullptr || web_page->instant_view.is_empty) { return nullptr; } return &web_page->instant_view; } void WebPagesManager::on_pending_web_page_timeout_callback(void *web_pages_manager_ptr, int64 web_page_id) { static_cast(web_pages_manager_ptr)->on_pending_web_page_timeout(WebPageId(web_page_id)); } void WebPagesManager::on_pending_web_page_timeout(WebPageId web_page_id) { int32 count = 0; auto it = pending_web_pages_.find(web_page_id); if (it != pending_web_pages_.end()) { vector full_message_ids; for (auto full_message_id : it->second) { full_message_ids.push_back(full_message_id); count++; } send_closure_later(G()->messages_manager(), &MessagesManager::get_messages_from_server, std::move(full_message_ids), Promise(), nullptr); } auto get_it = pending_get_web_pages_.find(web_page_id); if (get_it != pending_get_web_pages_.end()) { auto requests = std::move(get_it->second); pending_get_web_pages_.erase(get_it); for (auto &request : requests) { on_get_web_page_preview_fail(request.first, request.second.first, Status::Error(500, "Request timeout exceeded"), std::move(request.second.second)); count++; } } if (count == 0) { LOG(WARNING) << "Have no messages waiting for " << web_page_id; } } WebPagesManager::RichText WebPagesManager::get_rich_text(tl_object_ptr &&rich_text_ptr, const std::unordered_map &documents) { CHECK(rich_text_ptr != nullptr); RichText result; switch (rich_text_ptr->get_id()) { case telegram_api::textEmpty::ID: break; case telegram_api::textPlain::ID: { auto rich_text = move_tl_object_as(rich_text_ptr); result.content = std::move(rich_text->text_); break; } case telegram_api::textBold::ID: { auto rich_text = move_tl_object_as(rich_text_ptr); result.type = RichText::Type::Bold; result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents)); break; } case telegram_api::textItalic::ID: { auto rich_text = move_tl_object_as(rich_text_ptr); result.type = RichText::Type::Italic; result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents)); break; } case telegram_api::textUnderline::ID: { auto rich_text = move_tl_object_as(rich_text_ptr); result.type = RichText::Type::Underline; result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents)); break; } case telegram_api::textStrike::ID: { auto rich_text = move_tl_object_as(rich_text_ptr); result.type = RichText::Type::Strikethrough; result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents)); break; } case telegram_api::textFixed::ID: { auto rich_text = move_tl_object_as(rich_text_ptr); result.type = RichText::Type::Fixed; result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents)); break; } case telegram_api::textUrl::ID: { auto rich_text = move_tl_object_as(rich_text_ptr); result.type = RichText::Type::Url; result.content = std::move(rich_text->url_); result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents)); result.web_page_id = WebPageId(rich_text->webpage_id_); break; } case telegram_api::textEmail::ID: { auto rich_text = move_tl_object_as(rich_text_ptr); result.type = RichText::Type::EmailAddress; result.content = std::move(rich_text->email_); result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents)); break; } case telegram_api::textConcat::ID: { auto rich_text = move_tl_object_as(rich_text_ptr); result.type = RichText::Type::Concatenation; result.texts = get_rich_texts(std::move(rich_text->texts_), documents); break; } case telegram_api::textSubscript::ID: { auto rich_text = move_tl_object_as(rich_text_ptr); result.type = RichText::Type::Subscript; result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents)); break; } case telegram_api::textSuperscript::ID: { auto rich_text = move_tl_object_as(rich_text_ptr); result.type = RichText::Type::Superscript; result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents)); break; } case telegram_api::textMarked::ID: { auto rich_text = move_tl_object_as(rich_text_ptr); result.type = RichText::Type::Marked; result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents)); break; } case telegram_api::textPhone::ID: { auto rich_text = move_tl_object_as(rich_text_ptr); result.type = RichText::Type::PhoneNumber; result.content = std::move(rich_text->phone_); result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents)); break; } case telegram_api::textImage::ID: { auto rich_text = move_tl_object_as(rich_text_ptr); auto it = documents.find(rich_text->document_id_); if (it != documents.end()) { result.type = RichText::Type::Icon; result.document_file_id = it->second; Dimensions dimensions = get_dimensions(rich_text->w_, rich_text->h_); result.content = PSTRING() << (dimensions.width * static_cast(65536) + dimensions.height); } else { LOG(ERROR) << "Can't find document " << rich_text->document_id_; } break; } case telegram_api::textAnchor::ID: { auto rich_text = move_tl_object_as(rich_text_ptr); result.type = RichText::Type::Anchor; result.content = std::move(rich_text->name_); result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents)); break; } default: UNREACHABLE(); } return result; } vector WebPagesManager::get_rich_texts( vector> &&rich_text_ptrs, const std::unordered_map &documents) { return transform(std::move(rich_text_ptrs), [&documents](tl_object_ptr &&rich_text) { return get_rich_text(std::move(rich_text), documents); }); } tl_object_ptr WebPagesManager::get_rich_text_object(const RichText &rich_text) { switch (rich_text.type) { case RichText::Type::Plain: return make_tl_object(rich_text.content); case RichText::Type::Bold: return make_tl_object(get_rich_text_object(rich_text.texts[0])); case RichText::Type::Italic: return make_tl_object(get_rich_text_object(rich_text.texts[0])); case RichText::Type::Underline: return make_tl_object(get_rich_text_object(rich_text.texts[0])); case RichText::Type::Strikethrough: return make_tl_object(get_rich_text_object(rich_text.texts[0])); case RichText::Type::Fixed: return make_tl_object(get_rich_text_object(rich_text.texts[0])); case RichText::Type::Url: return make_tl_object(get_rich_text_object(rich_text.texts[0]), rich_text.content); case RichText::Type::EmailAddress: return make_tl_object(get_rich_text_object(rich_text.texts[0]), rich_text.content); case RichText::Type::Concatenation: return make_tl_object(get_rich_text_objects(rich_text.texts)); case RichText::Type::Subscript: return make_tl_object(get_rich_text_object(rich_text.texts[0])); case RichText::Type::Superscript: return make_tl_object(get_rich_text_object(rich_text.texts[0])); case RichText::Type::Marked: return make_tl_object(get_rich_text_object(rich_text.texts[0])); case RichText::Type::PhoneNumber: return make_tl_object(get_rich_text_object(rich_text.texts[0]), rich_text.content); case RichText::Type::Icon: { auto dimensions = to_integer(rich_text.content); auto width = static_cast(dimensions / 65536); auto height = static_cast(dimensions % 65536); return make_tl_object( G()->td().get_actor_unsafe()->documents_manager_->get_document_object(rich_text.document_file_id), width, height); } case RichText::Type::Anchor: return make_tl_object(get_rich_text_object(rich_text.texts[0]), rich_text.content); } UNREACHABLE(); return nullptr; } vector> WebPagesManager::get_rich_text_objects(const vector &rich_texts) { return transform(rich_texts, [](const RichText &rich_text) { return get_rich_text_object(rich_text); }); } WebPagesManager::PageBlockCaption WebPagesManager::get_page_block_caption( tl_object_ptr &&page_caption, const std::unordered_map &documents) { CHECK(page_caption != nullptr); PageBlockCaption result; result.text = get_rich_text(std::move(page_caption->text_), documents); result.credit = get_rich_text(std::move(page_caption->credit_), documents); return result; } td_api::object_ptr WebPagesManager::get_page_block_caption_object( const PageBlockCaption &caption) { return td_api::make_object(get_rich_text_object(caption.text), get_rich_text_object(caption.credit)); } td_api::object_ptr WebPagesManager::get_page_block_table_cell_object( const PageBlockTableCell &cell) { auto align = [&]() -> td_api::object_ptr { if (cell.align_left) { return td_api::make_object(); } if (cell.align_center) { return td_api::make_object(); } if (cell.align_right) { return td_api::make_object(); } UNREACHABLE(); return nullptr; }(); auto valign = [&]() -> td_api::object_ptr { if (cell.valign_top) { return td_api::make_object(); } if (cell.valign_middle) { return td_api::make_object(); } if (cell.valign_bottom) { return td_api::make_object(); } UNREACHABLE(); return nullptr; }(); return td_api::make_object(get_rich_text_object(cell.text), cell.is_header, cell.colspan, cell.rowspan, std::move(align), std::move(valign)); } vector> WebPagesManager::get_page_block_objects( const vector> &page_blocks) { return transform(page_blocks, [](const unique_ptr &page_block) { return page_block->get_page_block_object(); }); } unique_ptr WebPagesManager::get_page_block( tl_object_ptr page_block_ptr, const std::unordered_map &animations, const std::unordered_map &audios, const std::unordered_map &documents, const std::unordered_map &photos, const std::unordered_map &videos) const { CHECK(page_block_ptr != nullptr); switch (page_block_ptr->get_id()) { case telegram_api::pageBlockUnsupported::ID: return nullptr; case telegram_api::pageBlockTitle::ID: { auto page_block = move_tl_object_as(page_block_ptr); return make_unique(get_rich_text(std::move(page_block->text_), documents)); } case telegram_api::pageBlockSubtitle::ID: { auto page_block = move_tl_object_as(page_block_ptr); return make_unique(get_rich_text(std::move(page_block->text_), documents)); } case telegram_api::pageBlockAuthorDate::ID: { auto page_block = move_tl_object_as(page_block_ptr); return make_unique(get_rich_text(std::move(page_block->author_), documents), page_block->published_date_); } case telegram_api::pageBlockHeader::ID: { auto page_block = move_tl_object_as(page_block_ptr); return make_unique(get_rich_text(std::move(page_block->text_), documents)); } case telegram_api::pageBlockSubheader::ID: { auto page_block = move_tl_object_as(page_block_ptr); return make_unique(get_rich_text(std::move(page_block->text_), documents)); } case telegram_api::pageBlockKicker::ID: { auto page_block = move_tl_object_as(page_block_ptr); return make_unique(get_rich_text(std::move(page_block->text_), documents)); } case telegram_api::pageBlockParagraph::ID: { auto page_block = move_tl_object_as(page_block_ptr); return make_unique(get_rich_text(std::move(page_block->text_), documents)); } case telegram_api::pageBlockPreformatted::ID: { auto page_block = move_tl_object_as(page_block_ptr); return td::make_unique(get_rich_text(std::move(page_block->text_), documents), std::move(page_block->language_)); } case telegram_api::pageBlockFooter::ID: { auto page_block = move_tl_object_as(page_block_ptr); return make_unique(get_rich_text(std::move(page_block->text_), documents)); } case telegram_api::pageBlockDivider::ID: return make_unique(); case telegram_api::pageBlockAnchor::ID: { auto page_block = move_tl_object_as(page_block_ptr); return td::make_unique(std::move(page_block->name_)); } case telegram_api::pageBlockList::ID: { auto page_block = move_tl_object_as(page_block_ptr); return td::make_unique(transform(std::move(page_block->items_), [&](auto &&list_item_ptr) { PageBlockList::Item item; CHECK(list_item_ptr != nullptr); switch (list_item_ptr->get_id()) { case telegram_api::pageListItemText::ID: { auto list_item = telegram_api::move_object_as(list_item_ptr); item.page_blocks.push_back( make_unique(get_rich_text(std::move(list_item->text_), documents))); break; } case telegram_api::pageListItemBlocks::ID: { auto list_item = telegram_api::move_object_as(list_item_ptr); item.page_blocks = this->get_page_blocks(std::move(list_item->blocks_), animations, audios, documents, photos, videos); break; } } if (item.page_blocks.empty()) { item.page_blocks.push_back(make_unique(RichText())); } return item; })); } case telegram_api::pageBlockOrderedList::ID: { auto page_block = move_tl_object_as(page_block_ptr); int32 current_label = 0; return td::make_unique(transform(std::move(page_block->items_), [&](auto &&list_item_ptr) { PageBlockList::Item item; CHECK(list_item_ptr != nullptr); switch (list_item_ptr->get_id()) { case telegram_api::pageListOrderedItemText::ID: { auto list_item = telegram_api::move_object_as(list_item_ptr); item.label = std::move(list_item->num_); item.page_blocks.push_back( make_unique(get_rich_text(std::move(list_item->text_), documents))); break; } case telegram_api::pageListOrderedItemBlocks::ID: { auto list_item = telegram_api::move_object_as(list_item_ptr); item.label = std::move(list_item->num_); item.page_blocks = this->get_page_blocks(std::move(list_item->blocks_), animations, audios, documents, photos, videos); break; } } if (item.page_blocks.empty()) { item.page_blocks.push_back(make_unique(RichText())); } ++current_label; if (item.label.empty()) { item.label = PSTRING() << current_label << '.'; } else { item.label += '.'; } return item; })); } case telegram_api::pageBlockBlockquote::ID: { auto page_block = move_tl_object_as(page_block_ptr); return make_unique(get_rich_text(std::move(page_block->text_), documents), get_rich_text(std::move(page_block->caption_), documents)); } case telegram_api::pageBlockPullquote::ID: { auto page_block = move_tl_object_as(page_block_ptr); return make_unique(get_rich_text(std::move(page_block->text_), documents), get_rich_text(std::move(page_block->caption_), documents)); } case telegram_api::pageBlockPhoto::ID: { auto page_block = move_tl_object_as(page_block_ptr); auto it = photos.find(page_block->photo_id_); Photo photo; if (it == photos.end()) { photo.id = -2; } else { photo = it->second; } string url; WebPageId web_page_id; if ((page_block->flags_ & telegram_api::pageBlockPhoto::URL_MASK) != 0) { url = std::move(page_block->url_); web_page_id = WebPageId(page_block->webpage_id_); } return td::make_unique(std::move(photo), get_page_block_caption(std::move(page_block->caption_), documents), std::move(url), web_page_id); } case telegram_api::pageBlockVideo::ID: { auto page_block = move_tl_object_as(page_block_ptr); bool need_autoplay = (page_block->flags_ & telegram_api::pageBlockVideo::AUTOPLAY_MASK) != 0; bool is_looped = (page_block->flags_ & telegram_api::pageBlockVideo::LOOP_MASK) != 0; auto animations_it = animations.find(page_block->video_id_); if (animations_it != animations.end()) { LOG_IF(ERROR, !is_looped) << "Receive non-looped animation"; return make_unique( animations_it->second, get_page_block_caption(std::move(page_block->caption_), documents), need_autoplay); } auto it = videos.find(page_block->video_id_); FileId video_file_id; if (it != videos.end()) { video_file_id = it->second; } return make_unique( video_file_id, get_page_block_caption(std::move(page_block->caption_), documents), need_autoplay, is_looped); } case telegram_api::pageBlockCover::ID: { auto page_block = move_tl_object_as(page_block_ptr); auto cover = get_page_block(std::move(page_block->cover_), animations, audios, documents, photos, videos); if (cover == nullptr) { return nullptr; } return make_unique(std::move(cover)); } case telegram_api::pageBlockEmbed::ID: { auto page_block = move_tl_object_as(page_block_ptr); bool is_full_width = (page_block->flags_ & telegram_api::pageBlockEmbed::FULL_WIDTH_MASK) != 0; bool allow_scrolling = (page_block->flags_ & telegram_api::pageBlockEmbed::ALLOW_SCROLLING_MASK) != 0; bool has_dimensions = (page_block->flags_ & telegram_api::pageBlockEmbed::W_MASK) != 0; auto it = (page_block->flags_ & telegram_api::pageBlockEmbed::POSTER_PHOTO_ID_MASK) != 0 ? photos.find(page_block->poster_photo_id_) : photos.end(); Photo poster_photo; if (it == photos.end()) { poster_photo.id = -2; } else { poster_photo = it->second; } Dimensions dimensions; if (has_dimensions) { dimensions = get_dimensions(page_block->w_, page_block->h_); } return td::make_unique( std::move(page_block->url_), std::move(page_block->html_), std::move(poster_photo), dimensions, get_page_block_caption(std::move(page_block->caption_), documents), is_full_width, allow_scrolling); } case telegram_api::pageBlockEmbedPost::ID: { auto page_block = move_tl_object_as(page_block_ptr); auto it = photos.find(page_block->author_photo_id_); Photo author_photo; if (it == photos.end()) { author_photo.id = -2; } else { author_photo = it->second; } return td::make_unique( std::move(page_block->url_), std::move(page_block->author_), std::move(author_photo), page_block->date_, get_page_blocks(std::move(page_block->blocks_), animations, audios, documents, photos, videos), get_page_block_caption(std::move(page_block->caption_), documents)); } case telegram_api::pageBlockCollage::ID: { auto page_block = move_tl_object_as(page_block_ptr); return td::make_unique( get_page_blocks(std::move(page_block->items_), animations, audios, documents, photos, videos), get_page_block_caption(std::move(page_block->caption_), documents)); } case telegram_api::pageBlockSlideshow::ID: { auto page_block = move_tl_object_as(page_block_ptr); return td::make_unique( get_page_blocks(std::move(page_block->items_), animations, audios, documents, photos, videos), get_page_block_caption(std::move(page_block->caption_), documents)); } case telegram_api::pageBlockChannel::ID: { auto page_block = move_tl_object_as(page_block_ptr); CHECK(page_block->channel_ != nullptr); if (page_block->channel_->get_id() == telegram_api::channel::ID) { auto channel = static_cast(page_block->channel_.get()); ChannelId channel_id(channel->id_); if (!channel_id.is_valid()) { LOG(ERROR) << "Receive invalid " << channel_id; return nullptr; } if (td_->contacts_manager_->have_channel_force(channel_id)) { td_->contacts_manager_->on_get_chat(std::move(page_block->channel_), "pageBlockChannel"); LOG(INFO) << "Receive known min " << channel_id; return td::make_unique(td_->contacts_manager_->get_channel_title(channel_id), *td_->contacts_manager_->get_channel_dialog_photo(channel_id), td_->contacts_manager_->get_channel_username(channel_id)); } else { return td::make_unique( std::move(channel->title_), get_dialog_photo(td_->file_manager_.get(), std::move(channel->photo_)), std::move(channel->username_)); } } else { LOG(ERROR) << "Receive wrong channel " << to_string(page_block->channel_); return nullptr; } } case telegram_api::pageBlockAudio::ID: { auto page_block = move_tl_object_as(page_block_ptr); auto it = audios.find(page_block->audio_id_); FileId audio_file_id; if (it != audios.end()) { audio_file_id = it->second; } return make_unique(audio_file_id, get_page_block_caption(std::move(page_block->caption_), documents)); } case telegram_api::pageBlockTable::ID: { auto page_block = move_tl_object_as(page_block_ptr); auto is_bordered = (page_block->flags_ & telegram_api::pageBlockTable::BORDERED_MASK) != 0; auto is_striped = (page_block->flags_ & telegram_api::pageBlockTable::STRIPED_MASK) != 0; auto cells = transform(std::move(page_block->rows_), [&](tl_object_ptr &&row) { return transform(std::move(row->cells_), [&](tl_object_ptr &&table_cell) { PageBlockTableCell cell; auto flags = table_cell->flags_; cell.is_header = (flags & telegram_api::pageTableCell::HEADER_MASK) != 0; cell.align_center = (flags & telegram_api::pageTableCell::ALIGN_CENTER_MASK) != 0; if (!cell.align_center) { cell.align_right = (flags & telegram_api::pageTableCell::ALIGN_RIGHT_MASK) != 0; if (!cell.align_right) { cell.align_left = true; } } cell.valign_middle = (flags & telegram_api::pageTableCell::VALIGN_MIDDLE_MASK) != 0; if (!cell.valign_middle) { cell.valign_bottom = (flags & telegram_api::pageTableCell::VALIGN_BOTTOM_MASK) != 0; if (!cell.valign_bottom) { cell.valign_top = true; } } if (table_cell->text_ != nullptr) { cell.text = get_rich_text(std::move(table_cell->text_), documents); } if ((flags & telegram_api::pageTableCell::COLSPAN_MASK) != 0) { cell.colspan = table_cell->colspan_; } if ((flags & telegram_api::pageTableCell::ROWSPAN_MASK) != 0) { cell.rowspan = table_cell->rowspan_; } return cell; }); }); return td::make_unique(get_rich_text(std::move(page_block->title_), documents), std::move(cells), is_bordered, is_striped); } case telegram_api::pageBlockDetails::ID: { auto page_block = move_tl_object_as(page_block_ptr); auto is_open = (page_block->flags_ & telegram_api::pageBlockDetails::OPEN_MASK) != 0; return td::make_unique( get_rich_text(std::move(page_block->title_), documents), get_page_blocks(std::move(page_block->blocks_), animations, audios, documents, photos, videos), is_open); } case telegram_api::pageBlockRelatedArticles::ID: { auto page_block = move_tl_object_as(page_block_ptr); auto articles = transform( std::move(page_block->articles_), [&](tl_object_ptr &&related_article) { RelatedArticle article; article.url = std::move(related_article->url_); article.web_page_id = WebPageId(related_article->webpage_id_); article.title = std::move(related_article->title_); article.description = std::move(related_article->description_); auto it = (related_article->flags_ & telegram_api::pageRelatedArticle::PHOTO_ID_MASK) != 0 ? photos.find(related_article->photo_id_) : photos.end(); if (it == photos.end()) { article.photo.id = -2; } else { article.photo = it->second; } article.author = std::move(related_article->author_); if ((related_article->flags_ & telegram_api::pageRelatedArticle::PUBLISHED_DATE_MASK) != 0) { article.published_date = related_article->published_date_; } return article; }); return td::make_unique(get_rich_text(std::move(page_block->title_), documents), std::move(articles)); } case telegram_api::pageBlockMap::ID: { auto page_block = move_tl_object_as(page_block_ptr); Location location(std::move(page_block->geo_)); auto zoom = page_block->zoom_; Dimensions dimensions = get_dimensions(page_block->w_, page_block->h_); if (location.empty()) { LOG(ERROR) << "Receive invalid map location"; break; } if (zoom <= 0 || zoom > 30) { LOG(ERROR) << "Receive invalid map zoom " << zoom; break; } if (dimensions.width == 0) { LOG(ERROR) << "Receive invalid map dimensions " << page_block->w_ << " " << page_block->h_; break; } return make_unique(std::move(location), zoom, dimensions, get_page_block_caption(std::move(page_block->caption_), documents)); } default: UNREACHABLE(); } return nullptr; } vector> WebPagesManager::get_page_blocks( vector> page_block_ptrs, const std::unordered_map &animations, const std::unordered_map &audios, const std::unordered_map &documents, const std::unordered_map &photos, const std::unordered_map &videos) const { vector> result; result.reserve(page_block_ptrs.size()); for (auto &page_block_ptr : page_block_ptrs) { auto page_block = get_page_block(std::move(page_block_ptr), animations, audios, documents, photos, videos); if (page_block != nullptr) { result.push_back(std::move(page_block)); } } return result; } void WebPagesManager::on_get_web_page_instant_view(WebPage *web_page, tl_object_ptr &&page, int32 hash, DialogId owner_dialog_id) { CHECK(page != nullptr); std::unordered_map photos; for (auto &photo_ptr : page->photos_) { if (photo_ptr->get_id() == telegram_api::photo::ID) { Photo photo = get_photo(td_->file_manager_.get(), move_tl_object_as(photo_ptr), owner_dialog_id); int64 photo_id = photo.id; photos.emplace(photo_id, std::move(photo)); } } if (web_page->photo.id != -2 && web_page->photo.id != 0) { photos.emplace(web_page->photo.id, web_page->photo); } std::unordered_map animations; std::unordered_map audios; std::unordered_map documents; std::unordered_map videos; for (auto &document_ptr : page->documents_) { if (document_ptr->get_id() == telegram_api::document::ID) { auto document = move_tl_object_as(document_ptr); auto document_id = document->id_; auto parsed_document = td_->documents_manager_->on_get_document(std::move(document), owner_dialog_id); if (parsed_document.first == DocumentsManager::DocumentType::Animation) { animations.emplace(document_id, parsed_document.second); } else if (parsed_document.first == DocumentsManager::DocumentType::Audio) { audios.emplace(document_id, parsed_document.second); } else if (parsed_document.first == DocumentsManager::DocumentType::General) { documents.emplace(document_id, parsed_document.second); } else if (parsed_document.first == DocumentsManager::DocumentType::Video) { videos.emplace(document_id, parsed_document.second); } else { LOG(ERROR) << "Receive document of the wrong type " << static_cast(parsed_document.first); } } } if (web_page->document_type == DocumentsManager::DocumentType::Animation) { auto file_view = td_->file_manager_->get_file_view(web_page->document_file_id); if (file_view.has_remote_location()) { animations.emplace(file_view.remote_location().get_id(), web_page->document_file_id); } else { LOG(ERROR) << "Animation has no remote location"; } } if (web_page->document_type == DocumentsManager::DocumentType::Audio) { auto file_view = td_->file_manager_->get_file_view(web_page->document_file_id); if (file_view.has_remote_location()) { audios.emplace(file_view.remote_location().get_id(), web_page->document_file_id); } else { LOG(ERROR) << "Audio has no remote location"; } } if (web_page->document_type == DocumentsManager::DocumentType::General) { auto file_view = td_->file_manager_->get_file_view(web_page->document_file_id); if (file_view.has_remote_location()) { documents.emplace(file_view.remote_location().get_id(), web_page->document_file_id); } else { LOG(ERROR) << "Document has no remote location"; } } if (web_page->document_type == DocumentsManager::DocumentType::Video) { auto file_view = td_->file_manager_->get_file_view(web_page->document_file_id); if (file_view.has_remote_location()) { videos.emplace(file_view.remote_location().get_id(), web_page->document_file_id); } else { LOG(ERROR) << "Video has no remote location"; } } LOG(INFO) << "Receive a web page instant view with " << page->blocks_.size() << " blocks, " << animations.size() << " animations, " << audios.size() << " audios, " << documents.size() << " documents, " << photos.size() << " photos and " << videos.size() << " videos"; web_page->instant_view.page_blocks = get_page_blocks(std::move(page->blocks_), animations, audios, documents, photos, videos); web_page->instant_view.is_v2 = (page->flags_ & telegram_api::page::V2_MASK) != 0; web_page->instant_view.is_rtl = (page->flags_ & telegram_api::page::RTL_MASK) != 0; web_page->instant_view.hash = hash; web_page->instant_view.url = std::move(page->url_); web_page->instant_view.is_empty = false; web_page->instant_view.is_full = (page->flags_ & telegram_api::page::PART_MASK) == 0; web_page->instant_view.is_loaded = true; LOG(DEBUG) << "Receive web page instant view: " << to_string(get_web_page_instant_view_object(&web_page->instant_view)); } class WebPagesManager::WebPageLogEvent { public: WebPageId web_page_id; const WebPage *web_page_in; unique_ptr web_page_out; WebPageLogEvent() = default; WebPageLogEvent(WebPageId web_page_id, const WebPage *web_page) : web_page_id(web_page_id), web_page_in(web_page) { } template void store(StorerT &storer) const { td::store(web_page_id, storer); td::store(*web_page_in, storer); } template void parse(ParserT &parser) { td::parse(web_page_id, parser); CHECK(web_page_out == nullptr); web_page_out = make_unique(); td::parse(*web_page_out, parser); } }; void WebPagesManager::save_web_page(const WebPage *web_page, WebPageId web_page_id, bool from_binlog) { if (!G()->parameters().use_message_db) { return; } CHECK(web_page != nullptr); if (!from_binlog) { WebPageLogEvent logevent(web_page_id, web_page); LogEventStorerImpl storer(logevent); if (web_page->logevent_id == 0) { web_page->logevent_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::WebPages, storer); } else { binlog_rewrite(G()->td_db()->get_binlog(), web_page->logevent_id, LogEvent::HandlerType::WebPages, storer); } } LOG(INFO) << "Save " << web_page_id << " to database"; G()->td_db()->get_sqlite_pmc()->set( get_web_page_database_key(web_page_id), log_event_store(*web_page).as_slice().str(), PromiseCreator::lambda([web_page_id](Result<> result) { send_closure(G()->web_pages_manager(), &WebPagesManager::on_save_web_page_to_database, web_page_id, result.is_ok()); })); } string WebPagesManager::get_web_page_url_database_key(const string &url) { return "wpurl" + url; } void WebPagesManager::on_binlog_web_page_event(BinlogEvent &&event) { if (!G()->parameters().use_message_db) { binlog_erase(G()->td_db()->get_binlog(), event.id_); return; } WebPageLogEvent log_event; log_event_parse(log_event, event.data_).ensure(); auto web_page_id = log_event.web_page_id; LOG(INFO) << "Add " << web_page_id << " from binlog"; auto web_page = std::move(log_event.web_page_out); CHECK(web_page != nullptr); web_page->logevent_id = event.id_; update_web_page(std::move(web_page), web_page_id, true, false); } string WebPagesManager::get_web_page_database_key(WebPageId web_page_id) { return PSTRING() << "wp" << web_page_id.get(); } void WebPagesManager::on_save_web_page_to_database(WebPageId web_page_id, bool success) { auto web_page = get_web_page(web_page_id); if (web_page == nullptr) { LOG(ERROR) << "Can't find " << (success ? "saved " : "failed to save ") << web_page_id; return; } if (!success) { LOG(ERROR) << "Failed to save " << web_page_id << " to database"; save_web_page(web_page, web_page_id, web_page->logevent_id != 0); } else { LOG(INFO) << "Successfully saved " << web_page_id << " to database"; if (web_page->logevent_id != 0) { LOG(INFO) << "Erase " << web_page_id << " from binlog"; binlog_erase(G()->td_db()->get_binlog(), web_page->logevent_id); web_page->logevent_id = 0; } } } void WebPagesManager::load_web_page_from_database(WebPageId web_page_id, Promise promise) { if (!G()->parameters().use_message_db || loaded_from_database_web_pages_.count(web_page_id)) { promise.set_value(Unit()); return; } LOG(INFO) << "Load " << web_page_id << " from database"; auto &load_web_page_queries = load_web_page_from_database_queries_[web_page_id]; load_web_page_queries.push_back(std::move(promise)); if (load_web_page_queries.size() == 1u) { G()->td_db()->get_sqlite_pmc()->get( get_web_page_database_key(web_page_id), PromiseCreator::lambda([web_page_id](string value) { send_closure(G()->web_pages_manager(), &WebPagesManager::on_load_web_page_from_database, web_page_id, std::move(value)); })); } } void WebPagesManager::on_load_web_page_from_database(WebPageId web_page_id, string value) { if (!loaded_from_database_web_pages_.insert(web_page_id).second) { return; } auto it = load_web_page_from_database_queries_.find(web_page_id); vector> promises; if (it != load_web_page_from_database_queries_.end()) { promises = std::move(it->second); CHECK(!promises.empty()); load_web_page_from_database_queries_.erase(it); } LOG(INFO) << "Successfully loaded " << web_page_id << " of size " << value.size() << " from database"; // G()->td_db()->get_sqlite_pmc()->erase(get_web_page_database_key(web_page_id), Auto()); // return; if (!have_web_page(web_page_id)) { if (!value.empty()) { auto result = make_unique(); auto status = log_event_parse(*result, value); if (status.is_error()) { LOG(FATAL) << status << ": " << format::as_hex_dump<4>(Slice(value)); } update_web_page(std::move(result), web_page_id, true, true); } } else { // web page has already been loaded from the server } for (auto &promise : promises) { promise.set_value(Unit()); } } bool WebPagesManager::have_web_page_force(WebPageId web_page_id) { return get_web_page_force(web_page_id) != nullptr; } const WebPagesManager::WebPage *WebPagesManager::get_web_page_force(WebPageId web_page_id) { auto web_page = get_web_page(web_page_id); if (web_page != nullptr) { return web_page; } if (!G()->parameters().use_message_db) { return nullptr; } if (loaded_from_database_web_pages_.count(web_page_id)) { return nullptr; } LOG(INFO) << "Trying to load " << web_page_id << " from database"; on_load_web_page_from_database(web_page_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_web_page_database_key(web_page_id))); return get_web_page(web_page_id); } FileSourceId WebPagesManager::get_web_page_file_source_id(WebPage *web_page) { if (!web_page->file_source_id.is_valid()) { web_page->file_source_id = td_->file_reference_manager_->create_web_page_file_source(web_page->url); } return web_page->file_source_id; } FileSourceId WebPagesManager::get_url_file_source_id(const string &url) { auto web_page_id = get_web_page_by_url(url); if (web_page_id.is_valid()) { auto web_page = get_web_page(web_page_id); if (web_page != nullptr) { if (!web_page->file_source_id.is_valid()) { web_pages_[web_page_id]->file_source_id = td_->file_reference_manager_->create_web_page_file_source(web_page->url); } return web_page->file_source_id; } } return url_to_file_source_id_[url] = td_->file_reference_manager_->create_web_page_file_source(url); } string WebPagesManager::get_web_page_search_text(WebPageId web_page_id) const { auto web_page = get_web_page(web_page_id); if (web_page == nullptr) { return ""; } return PSTRING() << web_page->title + " " + web_page->description; } void WebPagesManager::append_rich_text_file_ids(const RichText &rich_text, vector &file_ids) { if (rich_text.type == RichText::Type::Icon) { CHECK(rich_text.document_file_id.is_valid()); file_ids.push_back(rich_text.document_file_id); auto thumbnail_file_id = G()->td().get_actor_unsafe()->documents_manager_->get_document_thumbnail_file_id(rich_text.document_file_id); if (thumbnail_file_id.is_valid()) { file_ids.push_back(thumbnail_file_id); } } else { for (auto &text : rich_text.texts) { append_rich_text_file_ids(text, file_ids); } } } void WebPagesManager::append_page_block_caption_file_ids(const PageBlockCaption &caption, vector &file_ids) { append_rich_text_file_ids(caption.text, file_ids); append_rich_text_file_ids(caption.credit, file_ids); } vector WebPagesManager::get_web_page_file_ids(const WebPage *web_page) const { if (web_page == nullptr) { return vector(); } vector result = photo_get_file_ids(web_page->photo); if (web_page->document_file_id.is_valid()) { result.push_back(web_page->document_file_id); FileId thumbnail_file_id; switch (web_page->document_type) { case DocumentsManager::DocumentType::Animation: thumbnail_file_id = td_->animations_manager_->get_animation_thumbnail_file_id(web_page->document_file_id); break; case DocumentsManager::DocumentType::Audio: thumbnail_file_id = td_->audios_manager_->get_audio_thumbnail_file_id(web_page->document_file_id); break; case DocumentsManager::DocumentType::General: thumbnail_file_id = td_->documents_manager_->get_document_thumbnail_file_id(web_page->document_file_id); break; case DocumentsManager::DocumentType::Sticker: thumbnail_file_id = td_->stickers_manager_->get_sticker_thumbnail_file_id(web_page->document_file_id); break; case DocumentsManager::DocumentType::Video: thumbnail_file_id = td_->videos_manager_->get_video_thumbnail_file_id(web_page->document_file_id); break; case DocumentsManager::DocumentType::VideoNote: thumbnail_file_id = td_->video_notes_manager_->get_video_note_thumbnail_file_id(web_page->document_file_id); break; default: break; } if (thumbnail_file_id.is_valid()) { result.push_back(thumbnail_file_id); } } if (!web_page->instant_view.is_empty) { for (auto &page_block : web_page->instant_view.page_blocks) { page_block->append_file_ids(result); } } return result; } } // namespace td