// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2026 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/telegram/MessageQueryManager.h" #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" #include "td/telegram/ChannelParticipantFilter.h" #include "td/telegram/ChatManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogId.h" #include "td/telegram/DialogManager.h" #include "td/telegram/DialogParticipant.h" #include "td/telegram/DialogParticipantManager.h" #include "td/telegram/FactCheck.h" #include "td/telegram/FileReferenceManager.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/FolderId.h" #include "td/telegram/ForumTopicManager.h" #include "td/telegram/Global.h" #include "td/telegram/HashtagHints.h" #include "td/telegram/InlineQueriesManager.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/logevent/LogEventHelper.h" #include "td/telegram/MessageContent.h" #include "td/telegram/MessageContentType.h" #include "td/telegram/MessageEntity.h" #include "td/telegram/MessageReaction.h" #include "td/telegram/MessageSearchOffset.h" #include "td/telegram/MessageSender.h" #include "td/telegram/MessagesInfo.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/RequestedDialogType.h" #include "td/telegram/RichMessage.h" #include "td/telegram/SearchPostsFlood.h" #include "td/telegram/SecretChatsManager.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/StarManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/ToDoItem.h" #include "td/telegram/TranslationManager.h" #include "td/telegram/UpdatesManager.h" #include "td/telegram/UserId.h" #include "td/telegram/UserManager.h" #include "td/telegram/Version.h" #include "td/db/binlog/BinlogEvent.h" #include "td/db/binlog/BinlogHelper.h" #include "td/actor/MultiPromise.h" #include "td/actor/SleepActor.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/Random.h" #include "td/utils/SliceBuilder.h" #include "td/utils/Status.h" #include "td/utils/Time.h" #include "td/utils/tl_helpers.h" #include #include #include namespace td { class GetRichMessageQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; MessageId message_id_; public: explicit GetRichMessageQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(MessageFullId message_full_id) { dialog_id_ = message_full_id.get_dialog_id(); auto input_peer = td_->dialog_manager_->get_input_peer(message_full_id.get_dialog_id(), AccessRights::Read); if (input_peer == nullptr) { return on_error(Status::Error(400, "Chat not found")); } message_id_ = message_full_id.get_message_id(); send_query(G()->net_query_creator().create( telegram_api::messages_getRichMessage(std::move(input_peer), message_id_.get_server_message_id().get()))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetRichMessageQuery: " << to_string(ptr); auto info = get_messages_info(td_, dialog_id_, std::move(ptr), "GetRichMessageQuery"); if (info.messages.size() != 1u || info.messages[0]->get_id() != telegram_api::message::ID) { return promise_.set_error(500, "Receive invalid response"); } auto rich_message = std::move(static_cast(info.messages[0].get())->rich_message_); if (rich_message == nullptr) { return promise_.set_error(500, "Receive invalid response"); } promise_.set_value(RichMessage(td_, std::move(rich_message), dialog_id_)); } void on_error(Status status) final { td_->messages_manager_->on_get_message_error(dialog_id_, message_id_, status, "GetRichMessageQuery"); promise_.set_error(std::move(status)); } }; class UploadCoverQuery final : public Td::ResultHandler { Promise promise_; BusinessConnectionId business_connection_id_; DialogId dialog_id_; MessageCover cover_; FileUploadId file_upload_id_; bool was_uploaded_ = false; public: explicit UploadCoverQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(BusinessConnectionId business_connection_id, DialogId dialog_id, MessageCover &&cover, FileUploadId file_upload_id, telegram_api::object_ptr &&input_media) { CHECK(input_media != nullptr); business_connection_id_ = business_connection_id; dialog_id_ = dialog_id; cover_ = std::move(cover); file_upload_id_ = file_upload_id; was_uploaded_ = FileManager::extract_was_uploaded(input_media); if (was_uploaded_ && false) { return on_error(Status::Error(400, "FILE_PART_1_MISSING")); } auto input_peer = td_->dialog_manager_->get_input_peer( dialog_id, business_connection_id_.is_valid() ? AccessRights::Know : AccessRights::Write); if (input_peer == nullptr) { return on_error(Status::Error(400, "Have no access to the chat")); } int32 flags = 0; if (business_connection_id_.is_valid()) { flags |= telegram_api::messages_uploadMedia::BUSINESS_CONNECTION_ID_MASK; } send_query(G()->net_query_creator().create(telegram_api::messages_uploadMedia( flags, business_connection_id_.get(), std::move(input_peer), std::move(input_media)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for UploadCoverQuery: " << to_string(ptr); td_->message_query_manager_->complete_upload_message_cover(business_connection_id_, dialog_id_, std::move(cover_), file_upload_id_, std::move(ptr), std::move(promise_)); } void on_error(Status status) final { LOG(INFO) << "Receive error for UploadCoverQuery: " << status; if (was_uploaded_) { auto bad_parts = FileManager::get_missing_file_parts(status); if (!bad_parts.empty()) { td_->message_query_manager_->upload_message_cover(business_connection_id_, dialog_id_, std::move(cover_), file_upload_id_, std::move(promise_), std::move(bad_parts)); return; } else { td_->file_manager_->delete_partial_remote_location_if_needed(file_upload_id_, status); } } promise_.set_error(std::move(status)); } }; class ReportMessageDeliveryQuery final : public Td::ResultHandler { DialogId dialog_id_; MessageId message_id_; public: void send(MessageFullId message_full_id, bool from_push) { dialog_id_ = message_full_id.get_dialog_id(); auto input_peer = td_->dialog_manager_->get_input_peer(message_full_id.get_dialog_id(), AccessRights::Read); if (input_peer == nullptr) { return; } message_id_ = message_full_id.get_message_id(); CHECK(message_id_.is_server()); send_query(G()->net_query_creator().create(telegram_api::messages_reportMessagesDelivery( 0, from_push, std::move(input_peer), {message_id_.get_server_message_id().get()}))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } // ok } void on_error(Status status) final { td_->messages_manager_->on_get_message_error(dialog_id_, message_id_, status, "ReportMessageDeliveryQuery"); } }; class SendBotRequestedPeerQuery final : public Td::ResultHandler { Promise promise_; public: explicit SendBotRequestedPeerQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(MessageFullId message_full_id, UserId bot_user_id, const string &request_id, int32 button_id, vector &&requested_dialog_ids) { auto dialog_id = message_full_id.get_dialog_id(); int32 flags = 0; telegram_api::object_ptr input_peer; if (bot_user_id == UserId() && request_id.empty()) { input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); flags |= telegram_api::messages_sendBotRequestedPeer::MSG_ID_MASK; } else { input_peer = td_->dialog_manager_->get_input_peer(DialogId(bot_user_id), AccessRights::Write); flags |= telegram_api::messages_sendBotRequestedPeer::WEBAPP_REQ_ID_MASK; } if (input_peer == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } vector> requested_peers; for (auto requested_dialog_id : requested_dialog_ids) { auto requested_peer = td_->dialog_manager_->get_input_peer(requested_dialog_id, AccessRights::Read); if (requested_peer == nullptr) { return on_error(Status::Error(400, "Can't access the chosen chat")); } requested_peers.push_back(std::move(requested_peer)); } send_query(G()->net_query_creator().create( telegram_api::messages_sendBotRequestedPeer(flags, std::move(input_peer), message_full_id.get_message_id().get_server_message_id().get(), request_id, button_id, std::move(requested_peers)), {{dialog_id, MessageContentType::Text}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for SendBotRequestedPeerQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetExtendedMediaQuery final : public Td::ResultHandler { DialogId dialog_id_; vector message_ids_; public: void send(DialogId dialog_id, vector &&message_ids) { dialog_id_ = dialog_id; message_ids_ = std::move(message_ids); auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } send_query(G()->net_query_creator().create(telegram_api::messages_getExtendedMedia( std::move(input_peer), MessageId::get_server_message_ids(message_ids_)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetExtendedMediaQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), Promise()); td_->message_query_manager_->finish_get_message_extended_media(dialog_id_, message_ids_); } void on_error(Status status) final { td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "GetExtendedMediaQuery"); td_->message_query_manager_->finish_get_message_extended_media(dialog_id_, message_ids_); } }; class GetFactCheckQuery final : public Td::ResultHandler { Promise>> promise_; DialogId dialog_id_; public: explicit GetFactCheckQuery(Promise>> &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, const vector &message_ids) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return promise_.set_error(400, "Can't access the chat"); } send_query(G()->net_query_creator().create( telegram_api::messages_getFactCheck(std::move(input_peer), MessageId::get_server_message_ids(message_ids)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetFactCheckQuery: " << to_string(ptr); promise_.set_value(std::move(ptr)); } void on_error(Status status) final { td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "GetFactCheckQuery"); promise_.set_error(std::move(status)); } }; class EditMessageFactCheckQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; MessageId message_id_; public: explicit EditMessageFactCheckQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId message_id, const FormattedText &text) { dialog_id_ = dialog_id; message_id_ = message_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); CHECK(message_id.is_server()); auto server_message_id = message_id.get_server_message_id().get(); if (text.text.empty()) { send_query(G()->net_query_creator().create( telegram_api::messages_deleteFactCheck(std::move(input_peer), server_message_id))); } else { send_query(G()->net_query_creator().create(telegram_api::messages_editFactCheck( std::move(input_peer), server_message_id, get_input_text_with_entities(td_->user_manager_.get(), text, "messages_editFactCheck")))); } } void on_result(BufferSlice packet) final { static_assert(std::is_same::value); auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for EditMessageFactCheckQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { td_->messages_manager_->on_get_message_error(dialog_id_, message_id_, status, "EditMessageFactCheckQuery"); promise_.set_error(std::move(status)); } }; class ToggleSuggestedPostApprovalQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; MessageId message_id_; public: explicit ToggleSuggestedPostApprovalQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId message_id, bool is_rejected, int32 schedule_date, const string &comment) { dialog_id_ = dialog_id; message_id_ = message_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); CHECK(message_id.is_server()); auto server_message_id = message_id.get_server_message_id().get(); int32 flags = 0; if (schedule_date) { flags |= telegram_api::messages_toggleSuggestedPostApproval::SCHEDULE_DATE_MASK; } if (!comment.empty()) { flags |= telegram_api::messages_toggleSuggestedPostApproval::REJECT_COMMENT_MASK; } send_query(G()->net_query_creator().create(telegram_api::messages_toggleSuggestedPostApproval( flags, is_rejected, std::move(input_peer), server_message_id, schedule_date, comment))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for ToggleSuggestedPostApprovalQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { td_->messages_manager_->on_get_message_error(dialog_id_, message_id_, status, "ToggleSuggestedPostApprovalQuery"); promise_.set_error(std::move(status)); } }; class SearchMessagesGlobalQuery final : public Td::ResultHandler { Promise> promise_; string query_; int32 offset_date_; DialogId offset_dialog_id_; MessageId offset_message_id_; int32 limit_; MessageSearchFilter filter_; int32 min_date_; int32 max_date_; public: explicit SearchMessagesGlobalQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(FolderId folder_id, bool ignore_folder_id, const string &query, int32 offset_date, DialogId offset_dialog_id, MessageId offset_message_id, int32 limit, MessageSearchFilter filter, const td_api::object_ptr &dialog_type_filter, int32 min_date, int32 max_date) { query_ = query; offset_date_ = offset_date; offset_dialog_id_ = offset_dialog_id; offset_message_id_ = offset_message_id; limit_ = limit; filter_ = filter; min_date_ = min_date; max_date_ = max_date; auto input_peer = DialogManager::get_input_peer_force(offset_dialog_id); CHECK(input_peer != nullptr); int32 flags = 0; if (!ignore_folder_id) { flags |= telegram_api::messages_searchGlobal::FOLDER_ID_MASK; } bool users_only = false; bool groups_only = false; bool broadcasts_only = false; if (dialog_type_filter != nullptr) { switch (dialog_type_filter->get_id()) { case td_api::searchMessagesChatTypeFilterPrivate::ID: users_only = true; break; case td_api::searchMessagesChatTypeFilterGroup::ID: groups_only = true; break; case td_api::searchMessagesChatTypeFilterChannel::ID: broadcasts_only = true; break; default: UNREACHABLE(); } } send_query(G()->net_query_creator().create(telegram_api::messages_searchGlobal( flags, broadcasts_only, groups_only, users_only, folder_id.get(), nullptr, query, get_input_messages_filter(filter), min_date_, max_date_, offset_date_, std::move(input_peer), offset_message_id.get_server_message_id().get(), limit))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto info = get_messages_info(td_, DialogId(), result_ptr.move_as_ok(), "SearchMessagesGlobalQuery"); td_->messages_manager_->get_channel_differences_if_needed( std::move(info), PromiseCreator::lambda([actor_id = td_->message_query_manager_actor_.get(), query = std::move(query_), offset_date = offset_date_, offset_dialog_id = offset_dialog_id_, offset_message_id = offset_message_id_, limit = limit_, filter = std::move(filter_), min_date = min_date_, max_date = max_date_, promise = std::move(promise_)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { auto info = result.move_as_ok(); send_closure(actor_id, &MessageQueryManager::on_get_messages_search_result, query, offset_date, offset_dialog_id, offset_message_id, limit, filter, min_date, max_date, info.total_count, std::move(info.messages), info.next_rate, std::move(promise)); } }), "SearchMessagesGlobalQuery"); } void on_error(Status status) final { if (status.message() == "SEARCH_QUERY_EMPTY") { return promise_.set_value(td_->messages_manager_->get_found_messages_object({}, "SearchMessagesGlobalQuery")); } promise_.set_error(std::move(status)); } }; class SearchSentMediaQuery final : public Td::ResultHandler { Promise> promise_; public: explicit SearchSentMediaQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(const string &query, int32 limit) { send_query(G()->net_query_creator().create(telegram_api::messages_searchSentMedia( query, telegram_api::make_object(), limit))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto info = get_messages_info(td_, DialogId(), result_ptr.move_as_ok(), "SearchSentMediaQuery"); td_->messages_manager_->get_channel_differences_if_needed( std::move(info), PromiseCreator::lambda([actor_id = td_->message_query_manager_actor_.get(), promise = std::move(promise_)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { auto info = result.move_as_ok(); send_closure(actor_id, &MessageQueryManager::on_get_outgoing_document_messages, std::move(info.messages), std::move(promise)); } }), "SearchSentMediaQuery"); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class CheckSearchPostsFloodQuery final : public Td::ResultHandler { Promise> promise_; public: explicit CheckSearchPostsFloodQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(const string &query) { int32 flags = 0; if (!query.empty()) { flags |= telegram_api::channels_checkSearchPostsFlood::QUERY_MASK; } send_query(G()->net_query_creator().create(telegram_api::channels_checkSearchPostsFlood(flags, query))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } promise_.set_value(SearchPostsFlood(result_ptr.move_as_ok()).get_public_post_search_limits_object()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class SearchPublicPostsQuery final : public Td::ResultHandler { Promise> promise_; string query_; MessageSearchOffset offset_; int32 limit_; int64 star_count_; public: explicit SearchPublicPostsQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(const string &query, MessageSearchOffset offset, int32 limit, int64 star_count) { query_ = query; offset_ = offset; limit_ = limit; star_count_ = star_count; auto input_peer = DialogManager::get_input_peer_force(offset.dialog_id_); CHECK(input_peer != nullptr); int32 flags = telegram_api::channels_searchPosts::QUERY_MASK; if (star_count > 0) { td_->star_manager_->add_pending_owned_star_count(-star_count, false); flags |= telegram_api::channels_searchPosts::ALLOW_PAID_STARS_MASK; } send_query(G()->net_query_creator().create( telegram_api::channels_searchPosts(flags, string(), query, offset.date_, std::move(input_peer), offset.message_id_.get_server_message_id().get(), limit, star_count))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); telegram_api::object_ptr flood; if (ptr->get_id() == telegram_api::messages_messagesSlice::ID) { flood = std::move(static_cast(ptr.get())->search_flood_); LOG(INFO) << "Receive " << to_string(flood); } if (flood == nullptr) { LOG(ERROR) << "Receive " << to_string(ptr); return on_error(Status::Error(500, "Failed to receive search limits")); } auto info = get_messages_info(td_, DialogId(), std::move(ptr), "SearchPublicPostsQuery"); td_->messages_manager_->get_channel_differences_if_needed( std::move(info), PromiseCreator::lambda([actor_id = td_->message_query_manager_actor_.get(), query = std::move(query_), offset = offset_, limit = limit_, star_count = star_count_, flood = std::move(flood), promise = std::move(promise_)](Result &&result) mutable { if (result.is_error()) { send_closure(G()->star_manager(), &StarManager::add_pending_owned_star_count, star_count, false); promise.set_error(result.move_as_error()); } else { auto info = result.move_as_ok(); send_closure(actor_id, &MessageQueryManager::on_get_public_post_search_result, query, offset, limit, star_count, std::move(flood), std::move(info.messages), info.next_rate, std::move(promise)); } }), "SearchPostsQuery"); } void on_error(Status status) final { td_->star_manager_->add_pending_owned_star_count(star_count_, false); auto message = status.message(); if (begins_with(message, "FLOOD_WAIT_")) { auto second_part_pos = message.str().find("_OR_STARS_"); if (second_part_pos != string::npos) { auto wait_time = max(1, to_integer(message.substr(11))); auto star_count = max(static_cast(1), StarManager::get_star_count(to_integer(message.substr(second_part_pos + 10)))); return promise_.set_value(td_api::make_object( vector>(), string(), td_api::make_object(0, 0, wait_time, star_count, true), true)); } } if (message == "QUERY_EMPTY") { return promise_.set_value(td_api::make_object( vector>(), string(), nullptr, false)); } promise_.set_error(std::move(status)); } }; class SearchPostsQuery final : public Td::ResultHandler { Promise> promise_; string hashtag_; MessageSearchOffset offset_; int32 limit_; public: explicit SearchPostsQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(const string &hashtag, MessageSearchOffset offset, int32 limit) { hashtag_ = hashtag; offset_ = offset; limit_ = limit; auto input_peer = DialogManager::get_input_peer_force(offset.dialog_id_); CHECK(input_peer != nullptr); int32 flags = telegram_api::channels_searchPosts::HASHTAG_MASK; send_query(G()->net_query_creator().create( telegram_api::channels_searchPosts(flags, hashtag, string(), offset.date_, std::move(input_peer), offset.message_id_.get_server_message_id().get(), limit, 0))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto info = get_messages_info(td_, DialogId(), result_ptr.move_as_ok(), "SearchPostsQuery"); td_->messages_manager_->get_channel_differences_if_needed( std::move(info), PromiseCreator::lambda([actor_id = td_->message_query_manager_actor_.get(), hashtag = std::move(hashtag_), offset = offset_, limit = limit_, promise = std::move(promise_)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { auto info = result.move_as_ok(); send_closure(actor_id, &MessageQueryManager::on_get_hashtag_search_result, hashtag, offset, limit, info.total_count, std::move(info.messages), info.next_rate, std::move(promise)); } }), "SearchPostsQuery"); } void on_error(Status status) final { if (status.message() == "SEARCH_QUERY_EMPTY") { return promise_.set_value(td_->messages_manager_->get_found_messages_object({}, "SearchPostsQuery")); } promise_.set_error(std::move(status)); } }; class GetRecentLocationsQuery final : public Td::ResultHandler { Promise> promise_; DialogId dialog_id_; int32 limit_; public: explicit GetRecentLocationsQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, int32 limit) { dialog_id_ = dialog_id; limit_ = limit; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); send_query( G()->net_query_creator().create(telegram_api::messages_getRecentLocations(std::move(input_peer), limit, 0))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto info = get_messages_info(td_, dialog_id_, result_ptr.move_as_ok(), "GetRecentLocationsQuery"); td_->messages_manager_->get_channel_difference_if_needed( dialog_id_, std::move(info), PromiseCreator::lambda([actor_id = td_->message_query_manager_actor_.get(), dialog_id = dialog_id_, limit = limit_, promise = std::move(promise_)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { auto info = result.move_as_ok(); send_closure(actor_id, &MessageQueryManager::on_get_recent_locations, dialog_id, limit, info.total_count, std::move(info.messages), std::move(promise)); } }), "GetRecentLocationsQuery"); } void on_error(Status status) final { td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "GetRecentLocationsQuery"); promise_.set_error(std::move(status)); } }; class GetMessagePositionQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; MessageId message_id_; MessageSearchFilter filter_; MessageTopic message_topic_; public: explicit GetMessagePositionQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId message_id, MessageSearchFilter filter, MessageTopic message_topic) { auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); dialog_id_ = dialog_id; message_id_ = message_id; message_topic_ = message_topic; filter_ = filter; auto saved_input_peer = message_topic.get_saved_input_peer(td_); auto top_msg_id = message_topic.get_input_top_msg_id(); if (filter == MessageSearchFilter::Empty && top_msg_id == 0) { if (saved_input_peer != nullptr) { int32 flags = 0; if (message_topic_.is_monoforum()) { flags |= telegram_api::messages_getSavedHistory::PARENT_PEER_MASK; } else { input_peer = nullptr; } send_query(G()->net_query_creator().create( telegram_api::messages_getSavedHistory(flags, std::move(input_peer), std::move(saved_input_peer), message_id.get_server_message_id().get(), 0, -1, 1, 0, 0, 0))); } else { send_query(G()->net_query_creator().create(telegram_api::messages_getHistory( std::move(input_peer), message_id.get_server_message_id().get(), 0, -1, 1, 0, 0, 0))); } } else { int32 flags = 0; if (saved_input_peer != nullptr) { flags |= telegram_api::messages_search::SAVED_PEER_ID_MASK; } if (top_msg_id != 0) { flags |= telegram_api::messages_search::TOP_MSG_ID_MASK; } send_query(G()->net_query_creator().create(telegram_api::messages_search( flags, std::move(input_peer), string(), nullptr, std::move(saved_input_peer), Auto(), top_msg_id, get_input_messages_filter(filter), 0, std::numeric_limits::max(), message_id.get_server_message_id().get(), -1, 1, std::numeric_limits::max(), 0, 0))); } } void on_result(BufferSlice packet) final { static_assert(std::is_same::value); auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto messages_ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetMessagePositionQuery: " << to_string(messages_ptr); switch (messages_ptr->get_id()) { case telegram_api::messages_messages::ID: { auto messages = telegram_api::move_object_as(messages_ptr); if (messages->messages_.size() != 1 || MessageId::get_message_id(messages->messages_[0], false) != message_id_) { return promise_.set_error(400, "Message not found by the filter"); } return promise_.set_value(narrow_cast(messages->messages_.size())); } case telegram_api::messages_messagesSlice::ID: { auto messages = telegram_api::move_object_as(messages_ptr); if (messages->messages_.size() != 1 || MessageId::get_message_id(messages->messages_[0], false) != message_id_) { return promise_.set_error(400, "Message not found by the filter"); } if (messages->offset_id_offset_ <= 0) { LOG(ERROR) << "Failed to receive position for " << message_id_ << " in " << message_topic_ << " in " << dialog_id_ << " by " << filter_; return promise_.set_error(400, "Message position is unknown"); } return promise_.set_value(std::move(messages->offset_id_offset_)); } case telegram_api::messages_channelMessages::ID: { auto messages = telegram_api::move_object_as(messages_ptr); if (messages->messages_.size() != 1 || MessageId::get_message_id(messages->messages_[0], false) != message_id_) { return promise_.set_error(400, "Message not found by the filter"); } if (messages->offset_id_offset_ <= 0) { LOG(ERROR) << "Failed to receive position for " << message_id_ << " in " << dialog_id_ << " by " << filter_; return promise_.set_error(500, "Message position is unknown"); } return promise_.set_value(std::move(messages->offset_id_offset_)); } case telegram_api::messages_messagesNotModified::ID: LOG(ERROR) << "Server returned messagesNotModified in response to GetMessagePositionQuery"; return promise_.set_error(500, "Receive invalid response"); default: UNREACHABLE(); break; } } void on_error(Status status) final { td_->messages_manager_->on_get_message_error(dialog_id_, message_id_, status, "GetMessagePositionQuery"); promise_.set_error(std::move(status)); } }; class ReportMusicListenQuery final : public Td::ResultHandler { Promise promise_; public: explicit ReportMusicListenQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(telegram_api::object_ptr input_document, int32 duration) { send_query( G()->net_query_creator().create(telegram_api::messages_reportMusicListen(std::move(input_document), duration))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class ReportReadMetricsQuery final : public Td::ResultHandler { public: void send(DialogId dialog_id, vector> &&input_metrics) { auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return; } send_query(G()->net_query_creator().create( telegram_api::messages_reportReadMetrics(std::move(input_peer), std::move(input_metrics)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } } void on_error(Status status) final { } }; class GetOutboxReadDateQuery final : public Td::ResultHandler { Promise> promise_; DialogId dialog_id_; MessageId message_id_; public: explicit GetOutboxReadDateQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId message_id) { dialog_id_ = dialog_id; message_id_ = message_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create( telegram_api::messages_getOutboxReadDate(std::move(input_peer), message_id.get_server_message_id().get()))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); promise_.set_value(td_api::make_object(ptr->date_)); } void on_error(Status status) final { if (status.message() == "USER_PRIVACY_RESTRICTED") { return promise_.set_value(td_api::make_object()); } if (status.message() == "YOUR_PRIVACY_RESTRICTED") { return promise_.set_value(td_api::make_object()); } if (status.message() == "MESSAGE_TOO_OLD") { return promise_.set_value(td_api::make_object()); } td_->messages_manager_->on_get_message_error(dialog_id_, message_id_, status, "GetOutboxReadDateQuery"); promise_.set_error(std::move(status)); } }; class GetMessageReadParticipantsQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; MessageId message_id_; public: explicit GetMessageReadParticipantsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId message_id) { dialog_id_ = dialog_id; message_id_ = message_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create(telegram_api::messages_getMessageReadParticipants( std::move(input_peer), message_id.get_server_message_id().get()))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } promise_.set_value(MessageViewers(result_ptr.move_as_ok())); } void on_error(Status status) final { td_->messages_manager_->on_get_message_error(dialog_id_, message_id_, status, "GetMessageReadParticipantsQuery"); promise_.set_error(std::move(status)); } }; class GetMessagesViewsQuery final : public Td::ResultHandler { DialogId dialog_id_; vector message_ids_; public: void send(DialogId dialog_id, vector &&message_ids, bool increment_view_counter) { dialog_id_ = dialog_id; message_ids_ = std::move(message_ids); auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } send_query(G()->net_query_creator().create(telegram_api::messages_getMessagesViews( std::move(input_peer), MessageId::get_server_message_ids(message_ids_), increment_view_counter))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto result = result_ptr.move_as_ok(); auto interaction_infos = std::move(result->views_); if (message_ids_.size() != interaction_infos.size()) { return on_error(Status::Error(500, "Wrong number of message views returned")); } td_->user_manager_->on_get_users(std::move(result->users_), "GetMessagesViewsQuery"); td_->chat_manager_->on_get_chats(std::move(result->chats_), "GetMessagesViewsQuery"); for (size_t i = 0; i < message_ids_.size(); i++) { MessageFullId message_full_id{dialog_id_, message_ids_[i]}; auto *info = interaction_infos[i].get(); td_->messages_manager_->on_update_message_interaction_info(message_full_id, info->views_, info->forwards_, true, std::move(info->replies_)); } td_->message_query_manager_->finish_get_message_views(dialog_id_, message_ids_); } void on_error(Status status) final { if (!td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "GetMessagesViewsQuery")) { LOG(ERROR) << "Receive error for GetMessagesViewsQuery: " << status; } td_->message_query_manager_->finish_get_message_views(dialog_id_, message_ids_); } }; class GetMessagesReactionsQuery final : public Td::ResultHandler { DialogId dialog_id_; vector message_ids_; public: void send(DialogId dialog_id, vector &&message_ids) { dialog_id_ = dialog_id; message_ids_ = std::move(message_ids); auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id_, AccessRights::Read); CHECK(input_peer != nullptr); send_query( G()->net_query_creator().create(telegram_api::messages_getMessagesReactions( std::move(input_peer), MessageId::get_server_message_ids(message_ids_)), {{dialog_id_}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetMessagesReactionsQuery: " << to_string(ptr); if (ptr->get_id() == telegram_api::updates::ID) { auto &updates = static_cast(ptr.get())->updates_; FlatHashSet skipped_message_ids; for (auto message_id : message_ids_) { skipped_message_ids.insert(message_id); } for (const auto &update : updates) { if (update->get_id() == telegram_api::updateMessageReactions::ID) { auto update_message_reactions = static_cast(update.get()); if (DialogId(update_message_reactions->peer_) == dialog_id_) { skipped_message_ids.erase(MessageId(ServerMessageId(update_message_reactions->msg_id_))); } } } for (auto message_id : skipped_message_ids) { td_->messages_manager_->update_message_reactions({dialog_id_, message_id}, nullptr); } } td_->updates_manager_->on_get_updates(std::move(ptr), Promise()); td_->message_query_manager_->try_reload_message_reactions(dialog_id_, true); } void on_error(Status status) final { td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "GetMessagesReactionsQuery"); td_->message_query_manager_->try_reload_message_reactions(dialog_id_, true); } }; class SummarizeTextQuery final : public Td::ResultHandler { Promise> promise_; public: explicit SummarizeTextQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId message_id, const string &to_language_code, const string &tone) { int32 flags = 0; if (!to_language_code.empty()) { flags |= telegram_api::messages_summarizeText::TO_LANG_MASK; } if (!tone.empty()) { flags |= telegram_api::messages_summarizeText::TONE_MASK; } auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return promise_.set_error(400, "Chat is not accessible"); } send_query(G()->net_query_creator().create(telegram_api::messages_summarizeText( flags, std::move(input_peer), message_id.get_server_message_id().get(), to_language_code, tone))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for SummarizeTextQuery: " << to_string(ptr); auto formatted_text = get_formatted_text(td_->user_manager_.get(), std::move(ptr), true, true, "SummarizeTextQuery"); promise_.set_value(get_formatted_text_object(td_->user_manager_.get(), formatted_text, true, -1)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class AppendToDoListQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; MessageId message_id_; public: explicit AppendToDoListQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId message_id, const vector &items) { dialog_id_ = dialog_id; message_id_ = message_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create(telegram_api::messages_appendTodoList( std::move(input_peer), message_id.get_server_message_id().get(), transform(items, [user_manager = td_->user_manager_.get()](const auto &item) { return item.get_input_todo_item(user_manager); })))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for AppendToDoListQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { td_->messages_manager_->on_get_message_error(dialog_id_, message_id_, status, "AppendToDoListQuery"); promise_.set_error(std::move(status)); } }; class ToggleToDoCompletedQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; MessageId message_id_; public: explicit ToggleToDoCompletedQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId message_id, vector &&done_task_ids, vector &¬_done_task_ids) { dialog_id_ = dialog_id; message_id_ = message_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create( telegram_api::messages_toggleTodoCompleted(std::move(input_peer), message_id.get_server_message_id().get(), std::move(done_task_ids), std::move(not_done_task_ids)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for ToggleToDoCompletedQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { td_->messages_manager_->on_get_message_error(dialog_id_, message_id_, status, "ToggleToDoCompletedQuery"); promise_.set_error(std::move(status)); } }; class GetDiscussionMessageQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; MessageId message_id_; DialogId expected_dialog_id_; MessageId expected_message_id_; public: explicit GetDiscussionMessageQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId message_id, DialogId expected_dialog_id, MessageId expected_message_id) { dialog_id_ = dialog_id; message_id_ = message_id; expected_dialog_id_ = expected_dialog_id; expected_message_id_ = expected_message_id; CHECK(expected_dialog_id_.is_valid()); auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create( telegram_api::messages_getDiscussionMessage(std::move(input_peer), message_id.get_server_message_id().get()))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } td_->message_query_manager_->process_discussion_message(result_ptr.move_as_ok(), dialog_id_, message_id_, expected_dialog_id_, expected_message_id_, std::move(promise_)); } void on_error(Status status) final { td_->messages_manager_->on_get_message_error(dialog_id_, message_id_, status, "GetDiscussionMessageQuery"); promise_.set_error(std::move(status)); } }; class GetEmojiGameInfoQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetEmojiGameInfoQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send() { send_query(G()->net_query_creator().create(telegram_api::messages_getEmojiGameInfo())); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetEmojiGameInfoQuery: " << to_string(ptr); promise_.set_value(std::move(ptr)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class BlockFromRepliesQuery final : public Td::ResultHandler { Promise promise_; public: explicit BlockFromRepliesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(MessageId message_id, bool need_delete_message, bool need_delete_all_messages, bool report_spam) { send_query(G()->net_query_creator().create(telegram_api::contacts_blockFromReplies( 0, need_delete_message, need_delete_all_messages, report_spam, message_id.get_server_message_id().get()))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for BlockFromRepliesQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class DeletePhoneCallHistoryQuery final : public Td::ResultHandler { Promise promise_; public: explicit DeletePhoneCallHistoryQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(bool revoke) { send_query(G()->net_query_creator().create(telegram_api::messages_deletePhoneCallHistory(0, revoke))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto affected_messages = result_ptr.move_as_ok(); if (!affected_messages->messages_.empty()) { td_->messages_manager_->process_pts_update( make_tl_object(std::move(affected_messages->messages_), 0, 0)); } promise_.set_value(AffectedHistory(std::move(affected_messages))); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class DeleteParticipantHistoryQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; DialogId sender_dialog_id_; public: explicit DeleteParticipantHistoryQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, DialogId sender_dialog_id) { channel_id_ = channel_id; sender_dialog_id_ = sender_dialog_id; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return promise_.set_error(400, "Chat is not accessible"); } auto input_peer = td_->dialog_manager_->get_input_peer(sender_dialog_id, AccessRights::Know); if (input_peer == nullptr) { return promise_.set_error(400, "Message sender is not accessible"); } send_query(G()->net_query_creator().create( telegram_api::channels_deleteParticipantHistory(std::move(input_channel), std::move(input_peer)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } promise_.set_value(AffectedHistory(result_ptr.move_as_ok())); } void on_error(Status status) final { if (sender_dialog_id_.get_type() != DialogType::Channel) { td_->chat_manager_->on_get_channel_error(channel_id_, status, "DeleteParticipantHistoryQuery"); } promise_.set_error(std::move(status)); } }; class DeleteHistoryQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit DeleteHistoryQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId max_message_id, bool remove_from_dialog_list, bool revoke) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id_, AccessRights::Read); if (input_peer == nullptr) { return promise_.set_error(400, "Chat is not accessible"); } send_query(G()->net_query_creator().create( telegram_api::messages_deleteHistory(0, !remove_from_dialog_list, revoke, std::move(input_peer), max_message_id.get_server_message_id().get(), 0, 0))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } promise_.set_value(AffectedHistory(result_ptr.move_as_ok())); } void on_error(Status status) final { td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "DeleteHistoryQuery"); promise_.set_error(std::move(status)); } }; class DeleteChannelHistoryQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; MessageId max_message_id_; bool allow_error_; public: explicit DeleteChannelHistoryQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, MessageId max_message_id, bool allow_error, bool revoke) { channel_id_ = channel_id; max_message_id_ = max_message_id; allow_error_ = allow_error; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } send_query(G()->net_query_creator().create(telegram_api::channels_deleteHistory( 0, revoke, std::move(input_channel), max_message_id.get_server_message_id().get()))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for DeleteChannelHistoryQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { if (!td_->chat_manager_->on_get_channel_error(channel_id_, status, "DeleteChannelHistoryQuery")) { LOG(ERROR) << "Receive error for DeleteChannelHistoryQuery: " << status; } promise_.set_error(std::move(status)); } }; class DeleteMessagesByDateQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit DeleteMessagesByDateQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, int32 min_date, int32 max_date, bool revoke) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id_, AccessRights::Read); if (input_peer == nullptr) { return promise_.set_error(400, "Chat is not accessible"); } int32 flags = telegram_api::messages_deleteHistory::MIN_DATE_MASK | telegram_api::messages_deleteHistory::MAX_DATE_MASK; bool just_clear = true; send_query(G()->net_query_creator().create( telegram_api::messages_deleteHistory(flags, just_clear, revoke, std::move(input_peer), 0, min_date, max_date))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } promise_.set_value(AffectedHistory(result_ptr.move_as_ok())); } void on_error(Status status) final { td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "DeleteMessagesByDateQuery"); promise_.set_error(std::move(status)); } }; class DeleteMessagesQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; vector server_message_ids_; public: explicit DeleteMessagesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, vector &&server_message_ids, bool revoke) { dialog_id_ = dialog_id; server_message_ids_ = server_message_ids; send_query(G()->net_query_creator().create( telegram_api::messages_deleteMessages(0, revoke, std::move(server_message_ids)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto affected_messages = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for DeleteMessagesQuery: " << to_string(affected_messages); td_->updates_manager_->add_pending_pts_update(make_tl_object(), affected_messages->pts_, affected_messages->pts_count_, Time::now(), std::move(promise_), "delete messages query"); } void on_error(Status status) final { if (!G()->is_expected_error(status)) { // MESSAGE_DELETE_FORBIDDEN can be returned in group chats when administrator rights were removed // MESSAGE_DELETE_FORBIDDEN can be returned in private chats for bots when revoke time limit exceeded if (status.message() != "MESSAGE_DELETE_FORBIDDEN" || (dialog_id_.get_type() == DialogType::User && !td_->auth_manager_->is_bot())) { LOG(ERROR) << "Receive error for delete messages: " << status; } } td_->messages_manager_->on_failed_message_deletion(dialog_id_, server_message_ids_); promise_.set_error(std::move(status)); } }; class DeleteChannelMessagesQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; vector server_message_ids_; public: explicit DeleteChannelMessagesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, vector &&server_message_ids) { channel_id_ = channel_id; server_message_ids_ = server_message_ids; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } send_query(G()->net_query_creator().create( telegram_api::channels_deleteMessages(std::move(input_channel), std::move(server_message_ids)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto affected_messages = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for DeleteChannelMessagesQuery: " << to_string(affected_messages); td_->messages_manager_->add_pending_channel_update(DialogId(channel_id_), make_tl_object(), affected_messages->pts_, affected_messages->pts_count_, std::move(promise_), "DeleteChannelMessagesQuery"); } void on_error(Status status) final { if (!td_->chat_manager_->on_get_channel_error(channel_id_, status, "DeleteChannelMessagesQuery")) { if (status.message() != "MESSAGE_DELETE_FORBIDDEN") { LOG(ERROR) << "Receive error for delete channel messages: " << status; } } td_->messages_manager_->on_failed_message_deletion(DialogId(channel_id_), server_message_ids_); promise_.set_error(std::move(status)); } }; class DeleteScheduledMessagesQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; vector message_ids_; public: explicit DeleteScheduledMessagesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, vector &&message_ids) { dialog_id_ = dialog_id; message_ids_ = std::move(message_ids); auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } send_query(G()->net_query_creator().create(telegram_api::messages_deleteScheduledMessages( std::move(input_peer), MessageId::get_scheduled_server_message_ids(message_ids_)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for DeleteScheduledMessagesQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { if (!td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "DeleteScheduledMessagesQuery")) { LOG(ERROR) << "Receive error for delete scheduled messages: " << status; } td_->messages_manager_->on_failed_scheduled_message_deletion(dialog_id_, message_ids_); promise_.set_error(std::move(status)); } }; class DeleteEphemeralMessageQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit DeleteEphemeralMessageQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, DialogId receiver_dialog_id, EphemeralMessageId ephemeral_message_id) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr || receiver_dialog_id.get_type() != DialogType::User) { return on_error(Status::Error(400, "Can't access the chat")); } auto r_input_user = td_->user_manager_->get_input_user(receiver_dialog_id.get_user_id()); if (r_input_user.is_error()) { return on_error(Status::Error(400, "Can't access the user")); } send_query(G()->net_query_creator().create(telegram_api::ephemeral_deleteMessage( std::move(input_peer), r_input_user.move_as_ok(), ephemeral_message_id.get()))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } promise_.set_value(Unit()); } void on_error(Status status) final { if (!td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "DeleteEphemeralMessageQuery")) { LOG(ERROR) << "Receive error for delete ephemeral messages: " << status; } promise_.set_error(std::move(status)); } }; class DeleteTopicHistoryQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; ForumTopicId forum_topic_id_; public: explicit DeleteTopicHistoryQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, ForumTopicId forum_topic_id) { dialog_id_ = dialog_id; forum_topic_id_ = forum_topic_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } send_query(G()->net_query_creator().create( telegram_api::messages_deleteTopicHistory(std::move(input_peer), forum_topic_id.get()))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } promise_.set_value(AffectedHistory(result_ptr.move_as_ok())); } void on_error(Status status) final { td_->forum_topic_manager_->on_get_forum_topic_error(dialog_id_, forum_topic_id_, status, "DeleteTopicHistoryQuery"); promise_.set_error(std::move(status)); } }; class DeleteParticipantReactionsQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; DialogId sender_dialog_id_; public: explicit DeleteParticipantReactionsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, DialogId sender_dialog_id) { dialog_id_ = dialog_id; sender_dialog_id_ = sender_dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); CHECK(input_peer != nullptr); auto sender_input_peer = td_->dialog_manager_->get_input_peer(sender_dialog_id, AccessRights::Know); CHECK(sender_input_peer != nullptr); send_query(G()->net_query_creator().create( telegram_api::messages_deleteParticipantReactions(std::move(input_peer), std::move(sender_input_peer)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } promise_.set_value(Unit()); } void on_error(Status status) final { if (sender_dialog_id_.get_type() != DialogType::Channel) { td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "DeleteParticipantReactionsQuery"); } promise_.set_error(std::move(status)); } }; class DeleteParticipantReactionQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; DialogId sender_dialog_id_; public: explicit DeleteParticipantReactionQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId message_id, DialogId sender_dialog_id) { dialog_id_ = dialog_id; sender_dialog_id_ = sender_dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); CHECK(input_peer != nullptr); auto sender_input_peer = td_->dialog_manager_->get_input_peer(sender_dialog_id, AccessRights::Know); CHECK(sender_input_peer != nullptr); send_query(G()->net_query_creator().create(telegram_api::messages_deleteParticipantReaction( std::move(input_peer), message_id.get_server_message_id().get(), std::move(sender_input_peer)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } promise_.set_value(Unit()); } void on_error(Status status) final { if (sender_dialog_id_.get_type() != DialogType::Channel) { td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "DeleteParticipantReactionQuery"); } if (status.message() == "MESSAGE_NOT_MODIFIED") { return promise_.set_value(Unit()); } promise_.set_error(std::move(status)); } }; class GetPersonalChannelHistoryQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetPersonalChannelHistoryQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(telegram_api::object_ptr &&input_user, int32 limit) { send_query(G()->net_query_creator().create(telegram_api::messages_getPersonalChannelHistory( std::move(input_user), limit, std::numeric_limits::max(), 0, 0))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto messages_info = get_messages_info(td_, DialogId(), result_ptr.move_as_ok(), "GetPersonalChannelHistoryQuery"); auto messages = td_api::make_object(); for (auto &message : messages_info.messages) { auto message_object = td_->messages_manager_->get_guest_message_object(std::move(message), false); if (message_object != nullptr) { messages->messages_.push_back(std::move(message_object)); } } promise_.set_value(std::move(messages)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class ReadMentionsQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; ForumTopicId forum_topic_id_; public: explicit ReadMentionsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, ForumTopicId forum_topic_id) { dialog_id_ = dialog_id; forum_topic_id_ = forum_topic_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id_, AccessRights::Read); if (input_peer == nullptr) { return promise_.set_error(400, "Chat is not accessible"); } int32 flags = 0; if (forum_topic_id.is_valid()) { flags |= telegram_api::messages_readMentions::TOP_MSG_ID_MASK; } send_query(G()->net_query_creator().create( telegram_api::messages_readMentions(flags, std::move(input_peer), forum_topic_id.get()), {{dialog_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } promise_.set_value(AffectedHistory(result_ptr.move_as_ok())); } void on_error(Status status) final { td_->forum_topic_manager_->on_get_forum_topic_error(dialog_id_, forum_topic_id_, status, "ReadMentionsQuery"); promise_.set_error(std::move(status)); } }; class ReadReactionsQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; ForumTopicId forum_topic_id_; public: explicit ReadReactionsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, ForumTopicId forum_topic_id, SavedMessagesTopicId saved_messages_topic_id) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id_, AccessRights::Read); if (input_peer == nullptr) { return promise_.set_error(400, "Chat is not accessible"); } int32 flags = 0; if (forum_topic_id.is_valid()) { flags |= telegram_api::messages_readReactions::TOP_MSG_ID_MASK; } telegram_api::object_ptr saved_input_peer; if (saved_messages_topic_id.is_valid()) { flags |= telegram_api::messages_readReactions::SAVED_PEER_ID_MASK; saved_input_peer = saved_messages_topic_id.get_input_peer(td_); CHECK(saved_input_peer != nullptr); } send_query(G()->net_query_creator().create( telegram_api::messages_readReactions(flags, std::move(input_peer), forum_topic_id.get(), std::move(saved_input_peer)), {{dialog_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } promise_.set_value(AffectedHistory(result_ptr.move_as_ok())); } void on_error(Status status) final { td_->forum_topic_manager_->on_get_forum_topic_error(dialog_id_, forum_topic_id_, status, "ReadReactionsQuery"); promise_.set_error(std::move(status)); } }; class ReadPollVotesQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; ForumTopicId forum_topic_id_; public: explicit ReadPollVotesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, ForumTopicId forum_topic_id) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id_, AccessRights::Read); if (input_peer == nullptr) { return promise_.set_error(400, "Chat is not accessible"); } int32 flags = 0; if (forum_topic_id.is_valid()) { flags |= telegram_api::messages_readPollVotes::TOP_MSG_ID_MASK; } send_query(G()->net_query_creator().create( telegram_api::messages_readPollVotes(flags, std::move(input_peer), forum_topic_id.get()), {{dialog_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } promise_.set_value(AffectedHistory(result_ptr.move_as_ok())); } void on_error(Status status) final { td_->forum_topic_manager_->on_get_forum_topic_error(dialog_id_, forum_topic_id_, status, "ReadPollVotesQuery"); promise_.set_error(std::move(status)); } }; class ReadMessagesContentsQuery final : public Td::ResultHandler { Promise promise_; public: explicit ReadMessagesContentsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(vector &&message_ids) { send_query(G()->net_query_creator().create( telegram_api::messages_readMessageContents(MessageId::get_server_message_ids(message_ids)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto affected_messages = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for ReadMessagesContentsQuery: " << to_string(affected_messages); if (affected_messages->pts_count_ > 0) { td_->updates_manager_->add_pending_pts_update(make_tl_object(), affected_messages->pts_, affected_messages->pts_count_, Time::now(), Promise(), "read messages content query"); } promise_.set_value(Unit()); } void on_error(Status status) final { if (!G()->is_expected_error(status)) { LOG(ERROR) << "Receive error for read message contents: " << status; } promise_.set_error(std::move(status)); } }; class ReadChannelMessagesContentsQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; public: explicit ReadChannelMessagesContentsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, vector &&message_ids) { channel_id_ = channel_id; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { LOG(ERROR) << "Have no input channel for " << channel_id; return on_error(Status::Error(400, "Can't access the chat")); } send_query( G()->net_query_creator().create(telegram_api::channels_readMessageContents( std::move(input_channel), MessageId::get_server_message_ids(message_ids)), {{channel_id_}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.ok(); LOG_IF(ERROR, !result) << "Read channel messages contents failed"; promise_.set_value(Unit()); } void on_error(Status status) final { if (!td_->chat_manager_->on_get_channel_error(channel_id_, status, "ReadChannelMessagesContentsQuery")) { LOG(ERROR) << "Receive error for read messages contents in " << channel_id_ << ": " << status; } promise_.set_error(std::move(status)); } }; class UnpinAllMessagesQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; ForumTopicId forum_topic_id_; public: explicit UnpinAllMessagesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, ForumTopicId forum_topic_id, SavedMessagesTopicId saved_messages_topic_id) { dialog_id_ = dialog_id; forum_topic_id_ = forum_topic_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id_, AccessRights::Write); if (input_peer == nullptr) { LOG(INFO) << "Can't unpin all messages in " << dialog_id_; return on_error(Status::Error(400, "Can't unpin all messages")); } int32 flags = 0; if (forum_topic_id.is_valid()) { flags |= telegram_api::messages_unpinAllMessages::TOP_MSG_ID_MASK; } telegram_api::object_ptr saved_input_peer; if (saved_messages_topic_id.is_valid()) { flags |= telegram_api::messages_unpinAllMessages::SAVED_PEER_ID_MASK; saved_input_peer = saved_messages_topic_id.get_input_peer(td_); CHECK(saved_input_peer != nullptr); } send_query(G()->net_query_creator().create(telegram_api::messages_unpinAllMessages( flags, std::move(input_peer), forum_topic_id.get(), std::move(saved_input_peer)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } promise_.set_value(AffectedHistory(result_ptr.move_as_ok())); } void on_error(Status status) final { td_->forum_topic_manager_->on_get_forum_topic_error(dialog_id_, forum_topic_id_, status, "UnpinAllMessagesQuery"); promise_.set_error(std::move(status)); } }; class MessageQueryManager::UploadCoverCallback final : public FileManager::UploadCallback { public: void on_upload_ok(FileUploadId file_upload_id, telegram_api::object_ptr input_file) final { send_closure_later(G()->message_query_manager(), &MessageQueryManager::on_upload_cover, file_upload_id, std::move(input_file)); } void on_upload_error(FileUploadId file_upload_id, Status error) final { send_closure_later(G()->message_query_manager(), &MessageQueryManager::on_upload_cover_error, file_upload_id, std::move(error)); } }; MessageQueryManager::MessageQueryManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { upload_cover_callback_ = std::make_shared(); send_message_view_metrics_timeout_.set_callback(on_send_message_view_metrics_timeout_callback); send_message_view_metrics_timeout_.set_callback_data(static_cast(this)); } void MessageQueryManager::tear_down() { parent_.reset(); } void MessageQueryManager::run_affected_history_query_until_complete(DialogId dialog_id, AffectedHistoryQuery query, bool get_affected_messages, Promise &&promise) { CHECK(!G()->close_flag()); auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, query, get_affected_messages, promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { return promise.set_error(result.move_as_error()); } send_closure(actor_id, &MessageQueryManager::on_get_affected_history, dialog_id, query, get_affected_messages, result.move_as_ok(), std::move(promise)); }); query(dialog_id, std::move(query_promise)); } void MessageQueryManager::on_get_affected_history(DialogId dialog_id, AffectedHistoryQuery query, bool get_affected_messages, AffectedHistory affected_history, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); LOG(INFO) << "Receive " << (affected_history.is_final_ ? "final " : "partial ") << "affected history with PTS = " << affected_history.pts_ << " and pts_count = " << affected_history.pts_count_; if (affected_history.pts_count_ > 0) { if (get_affected_messages) { affected_history.pts_count_ = 0; } auto update_promise = affected_history.is_final_ ? std::move(promise) : Promise(); if (dialog_id.get_type() == DialogType::Channel) { td_->messages_manager_->add_pending_channel_update(dialog_id, telegram_api::make_object(), affected_history.pts_, affected_history.pts_count_, std::move(update_promise), "on_get_affected_history"); } else { td_->updates_manager_->add_pending_pts_update( telegram_api::make_object(), affected_history.pts_, affected_history.pts_count_, Time::now() - (get_affected_messages ? 10.0 : 0.0), std::move(update_promise), "on_get_affected_history"); } } else if (affected_history.is_final_) { promise.set_value(Unit()); } if (!affected_history.is_final_) { run_affected_history_query_until_complete(dialog_id, std::move(query), get_affected_messages, std::move(promise)); } } void MessageQueryManager::get_full_rich_message(MessageFullId message_full_id, Promise> &&promise) { if (!message_full_id.get_message_id().is_server()) { return promise.set_error(400, "Invalid message specified"); } auto &queries = get_full_rich_message_queries_[message_full_id]; queries.push_back(std::move(promise)); if (queries.size() != 1u) { return; } auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), message_full_id](Result r_rich_message) { send_closure(actor_id, &MessageQueryManager::on_get_full_rich_message, message_full_id, std::move(r_rich_message)); }); td_->create_handler(std::move(query_promise))->send(message_full_id); } void MessageQueryManager::on_get_full_rich_message(MessageFullId message_full_id, Result &&r_rich_message) { auto it = get_full_rich_message_queries_.find(message_full_id); CHECK(it != get_full_rich_message_queries_.end()); auto promises = std::move(it->second); get_full_rich_message_queries_.erase(it); if (r_rich_message.is_error()) { return fail_promises(promises, r_rich_message.move_as_error()); } auto rich_message = r_rich_message.move_as_ok(); auto file_source_id = get_rich_message_file_source_id(message_full_id); if (file_source_id.is_valid()) { vector file_ids; rich_message.append_file_ids(td_, file_ids); auto &old_file_ids = rich_message_file_ids_[message_full_id]; td_->file_manager_->change_files_source(file_source_id, old_file_ids, file_ids, "on_get_full_rich_message"); if (file_ids.empty()) { rich_message_file_ids_.erase(message_full_id); } else { old_file_ids = std::move(file_ids); } } for (auto &promise : promises) { promise.set_value(rich_message.get_rich_message_object(td_, true)); } } void MessageQueryManager::reload_full_rich_message(MessageFullId message_full_id, Promise &&promise) { return get_full_rich_message( message_full_id, PromiseCreator::lambda([promise = std::move(promise)]( Result> result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { promise.set_value(Unit()); } })); } FileSourceId MessageQueryManager::get_rich_message_file_source_id(MessageFullId message_full_id) { if (td_->auth_manager_->is_bot()) { return FileSourceId(); } auto dialog_id = message_full_id.get_dialog_id(); auto message_id = message_full_id.get_message_id(); if (!dialog_id.is_valid() || dialog_id.get_type() == DialogType::SecretChat || !message_id.is_server()) { return FileSourceId(); } auto &file_source_id = rich_message_full_id_to_file_source_id_[message_full_id]; if (!file_source_id.is_valid()) { file_source_id = td_->file_reference_manager_->create_rich_message_file_source(message_full_id); } return file_source_id; } void MessageQueryManager::upload_message_covers(BusinessConnectionId business_connection_id, DialogId dialog_id, vector covers, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); CHECK(!covers.empty()); MultiPromiseActorSafe mpas{"UploadMessageCoversMultiPromiseActor"}; mpas.add_promise(std::move(promise)); auto lock = mpas.get_promise(); for (const MessageCover &cover : covers) { CHECK(!cover.is_empty()); auto file_upload_id = FileUploadId(cover.get_any_file_id(), FileManager::get_internal_upload_id()); upload_message_cover(business_connection_id, dialog_id, cover, file_upload_id, mpas.get_promise()); } lock.set_value(Unit()); } void MessageQueryManager::upload_message_cover(BusinessConnectionId business_connection_id, DialogId dialog_id, MessageCover message_cover, FileUploadId file_upload_id, Promise &&promise, vector bad_parts) { TRY_STATUS_PROMISE(promise, G()->close_status()); BeingUploadedCover cover; cover.business_connection_id_ = business_connection_id; cover.dialog_id_ = dialog_id; cover.cover_ = std::move(message_cover); cover.promise_ = std::move(promise); auto input_media = cover.cover_.get_cover_input_media(td_, td_->auth_manager_->is_bot() && bad_parts.empty(), true); if (input_media != nullptr && bad_parts.empty()) { return do_upload_cover(file_upload_id, std::move(cover)); } LOG(INFO) << "Ask to upload cover " << file_upload_id << " with bad parts " << bad_parts; CHECK(file_upload_id.is_valid()); bool is_inserted = being_uploaded_covers_.emplace(file_upload_id, std::move(cover)).second; CHECK(is_inserted); // need to call resume_upload synchronously to make upload process consistent with being_uploaded_covers_ td_->file_manager_->resume_upload(file_upload_id, std::move(bad_parts), upload_cover_callback_, 1, 0); } void MessageQueryManager::on_upload_cover(FileUploadId file_upload_id, telegram_api::object_ptr input_file) { if (G()->close_flag()) { return; } LOG(INFO) << "Cover " << file_upload_id << " has been uploaded"; auto it = being_uploaded_covers_.find(file_upload_id); CHECK(it != being_uploaded_covers_.end()); auto cover = std::move(it->second); being_uploaded_covers_.erase(it); cover.input_file_ = std::move(input_file); do_upload_cover(file_upload_id, std::move(cover)); } void MessageQueryManager::on_upload_cover_error(FileUploadId file_upload_id, Status status) { if (G()->close_flag()) { return; } CHECK(status.is_error()); auto it = being_uploaded_covers_.find(file_upload_id); CHECK(it != being_uploaded_covers_.end()); auto cover = std::move(it->second); being_uploaded_covers_.erase(it); cover.promise_.set_error(std::move(status)); } void MessageQueryManager::do_upload_cover(FileUploadId file_upload_id, BeingUploadedCover &&being_uploaded_cover) { auto input_file = std::move(being_uploaded_cover.input_file_); bool have_input_file = input_file != nullptr; LOG(INFO) << "Do upload cover " << file_upload_id << ", have_input_file = " << have_input_file; auto input_media = being_uploaded_cover.cover_.get_input_media(td_, std::move(input_file)); CHECK(input_media != nullptr); if (is_uploaded_input_media(input_media)) { return being_uploaded_cover.promise_.set_value(Unit()); } else { td_->create_handler(std::move(being_uploaded_cover.promise_)) ->send(being_uploaded_cover.business_connection_id_, being_uploaded_cover.dialog_id_, std::move(being_uploaded_cover.cover_), file_upload_id, std::move(input_media)); } } void MessageQueryManager::complete_upload_message_cover( BusinessConnectionId business_connection_id, DialogId dialog_id, MessageCover cover, FileUploadId file_upload_id, telegram_api::object_ptr &&media_ptr, Promise &&promise) { send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_upload_id); TRY_STATUS_PROMISE(promise, cover.merge_with_media(td_, dialog_id, std::move(media_ptr))); auto input_media = cover.get_cover_input_media(td_, true, true); if (input_media == nullptr) { return promise.set_error(500, "Failed to upload file"); } promise.set_value(Unit()); } void MessageQueryManager::report_message_delivery(MessageFullId message_full_id, int32 until_date, bool from_push) { if (G()->unix_time() > until_date) { return; } td_->create_handler()->send(message_full_id, from_push); } void MessageQueryManager::share_dialogs_with_bot(const td_api::object_ptr &source_ptr, int32 button_id, vector shared_dialog_ids, bool expect_user, bool only_check, Promise &&promise) { if (source_ptr == nullptr) { return promise.set_error(400, "Source must be non-empty"); } const RequestedDialogType *requested_dialog_type = nullptr; MessageFullId message_full_id; UserId bot_user_id; string request_id; switch (source_ptr->get_id()) { case td_api::keyboardButtonSourceMessage::ID: { const auto *source = static_cast(source_ptr.get()); message_full_id = {DialogId(source->chat_id_), MessageId(source->message_id_)}; TRY_RESULT_PROMISE_ASSIGN(promise, requested_dialog_type, td_->messages_manager_->get_message_requested_dialog_type(message_full_id, button_id)); break; } case td_api::keyboardButtonSourceWebApp::ID: { const auto *source = static_cast(source_ptr.get()); bot_user_id = UserId(source->bot_user_id_); request_id = source->prepared_button_id_; requested_dialog_type = td_->inline_queries_manager_->get_requested_dialog_type(bot_user_id, request_id); break; } default: UNREACHABLE(); } if (requested_dialog_type == nullptr) { return promise.set_error(400, "Button not found"); } TRY_STATUS_PROMISE(promise, requested_dialog_type->check_shared_dialog_count(shared_dialog_ids.size())); for (auto shared_dialog_id : shared_dialog_ids) { if (shared_dialog_id.get_type() != DialogType::User) { if (!td_->dialog_manager_->have_dialog_force(shared_dialog_id, "share_dialogs_with_bot")) { return promise.set_error(400, "Shared chat not found"); } } else { if (!expect_user) { return promise.set_error(400, "Wrong chat type"); } if (!td_->user_manager_->have_accessible_user(shared_dialog_id.get_user_id())) { return promise.set_error(400, "Shared user not found"); } } TRY_STATUS_PROMISE(promise, requested_dialog_type->check_shared_dialog(td_, shared_dialog_id)); } if (only_check) { return promise.set_value(Unit()); } td_->create_handler(std::move(promise)) ->send(message_full_id, bot_user_id, request_id, button_id, std::move(shared_dialog_ids)); } void MessageQueryManager::reload_message_extended_media(DialogId dialog_id, vector message_ids) { CHECK(dialog_id.get_type() != DialogType::SecretChat); td::remove_if(message_ids, [&](MessageId message_id) { return !being_reloaded_fact_checks_.insert({dialog_id, message_id}).second; }); if (message_ids.empty()) { return; } td_->create_handler()->send(dialog_id, std::move(message_ids)); } void MessageQueryManager::finish_get_message_extended_media(DialogId dialog_id, const vector &message_ids) { for (auto message_id : message_ids) { being_reloaded_extended_media_message_full_ids_.erase({dialog_id, message_id}); } } void MessageQueryManager::reload_message_fact_checks(DialogId dialog_id, vector message_ids) { CHECK(dialog_id.get_type() != DialogType::SecretChat); td::remove_if(message_ids, [&](MessageId message_id) { return !being_reloaded_fact_checks_.insert({dialog_id, message_id}).second; }); if (message_ids.empty()) { return; } auto promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, message_ids]( Result>> r_fact_checks) { send_closure(actor_id, &MessageQueryManager::on_reload_message_fact_checks, dialog_id, message_ids, std::move(r_fact_checks)); }); td_->create_handler(std::move(promise))->send(dialog_id, message_ids); } void MessageQueryManager::on_reload_message_fact_checks( DialogId dialog_id, const vector &message_ids, Result>> r_fact_checks) { G()->ignore_result_if_closing(r_fact_checks); for (auto message_id : message_ids) { auto erased_count = being_reloaded_fact_checks_.erase({dialog_id, message_id}); CHECK(erased_count > 0); } if (r_fact_checks.is_error() || !td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { return; } auto fact_checks = r_fact_checks.move_as_ok(); if (fact_checks.size() != message_ids.size()) { LOG(ERROR) << "Receive " << fact_checks.size() << " fact checks instead of " << message_ids.size(); return; } for (size_t i = 0; i < message_ids.size(); i++) { td_->messages_manager_->on_update_message_fact_check( {dialog_id, message_ids[i]}, FactCheck::get_fact_check(td_->user_manager_.get(), std::move(fact_checks[i]), false)); } } void MessageQueryManager::set_message_fact_check(MessageFullId message_full_id, const FormattedText &fact_check_text, Promise &&promise) { td_->create_handler(std::move(promise)) ->send(message_full_id.get_dialog_id(), message_full_id.get_message_id(), fact_check_text); } void MessageQueryManager::toggle_suggested_post_approval(MessageFullId message_full_id, bool is_rejected, int32 schedule_date, const string &comment, Promise &&promise) { td_->create_handler(std::move(promise)) ->send(message_full_id.get_dialog_id(), message_full_id.get_message_id(), is_rejected, schedule_date, comment); } void MessageQueryManager::search_messages(DialogListId dialog_list_id, bool ignore_folder_id, const string &query, const string &offset_str, int32 limit, MessageSearchFilter filter, td_api::object_ptr &&dialog_type_filter, int32 min_date, int32 max_date, Promise> &&promise) { if (!dialog_list_id.is_folder()) { return promise.set_error(400, "Wrong chat list specified"); } if (limit <= 0) { return promise.set_error(400, "Parameter limit must be positive"); } if (limit > MAX_SEARCH_MESSAGES) { limit = MAX_SEARCH_MESSAGES; } TRY_RESULT_PROMISE(promise, offset, MessageSearchOffset::from_string(offset_str)); CHECK(filter != MessageSearchFilter::Call && filter != MessageSearchFilter::MissedCall); if (filter == MessageSearchFilter::Mention || filter == MessageSearchFilter::UnreadMention || filter == MessageSearchFilter::UnreadReaction || filter == MessageSearchFilter::UnreadPollVote || filter == MessageSearchFilter::FailedToSend || filter == MessageSearchFilter::Pinned) { return promise.set_error(400, "The filter is not supported"); } if (query.empty() && filter == MessageSearchFilter::Empty) { return promise.set_value(td_->messages_manager_->get_found_messages_object({}, "search_messages")); } td_->create_handler(std::move(promise)) ->send(dialog_list_id.get_folder_id(), ignore_folder_id, query, offset.date_, offset.dialog_id_, offset.message_id_, limit, filter, dialog_type_filter, min_date, max_date); } void MessageQueryManager::on_get_messages_search_result( const string &query, int32 offset_date, DialogId offset_dialog_id, MessageId offset_message_id, int32 limit, MessageSearchFilter filter, int32 min_date, int32 max_date, int32 total_count, vector> &&messages, int32 next_rate, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); LOG(INFO) << "Receive " << messages.size() << " found messages"; MessagesManager::FoundMessages found_messages; auto &result = found_messages.message_full_ids; MessageSearchOffset next_offset; for (auto &message : messages) { next_offset.update_from_message(message); auto new_message_full_id = td_->messages_manager_->on_get_message(DialogId(), std::move(message), false, false, "search messages"); if (new_message_full_id != MessageFullId()) { result.push_back(new_message_full_id); } else { total_count--; } } if (total_count < static_cast(result.size())) { LOG(ERROR) << "Receive " << result.size() << " valid messages out of " << total_count << " in " << messages.size() << " messages"; total_count = static_cast(result.size()); } found_messages.total_count = total_count; if (!result.empty()) { if (next_rate > 0) { next_offset.date_ = next_rate; } found_messages.next_offset = next_offset.to_string(); } promise.set_value(td_->messages_manager_->get_found_messages_object(found_messages, "on_get_messages_search_result")); } void MessageQueryManager::search_outgoing_document_messages( const string &query, int32 limit, Promise> &&promise) { if (limit <= 0) { return promise.set_error(400, "Parameter limit must be positive"); } if (limit > MAX_SEARCH_MESSAGES) { limit = MAX_SEARCH_MESSAGES; } td_->create_handler(std::move(promise))->send(query, limit); } void MessageQueryManager::on_get_outgoing_document_messages( vector> &&messages, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); MessagesManager::FoundMessages found_messages; for (auto &message : messages) { auto dialog_id = DialogId::get_message_dialog_id(message); auto message_full_id = td_->messages_manager_->on_get_message(dialog_id, std::move(message), false, false, "on_get_outgoing_document_messages"); if (message_full_id != MessageFullId()) { found_messages.message_full_ids.push_back(message_full_id); } } auto result = td_->messages_manager_->get_found_messages_object(found_messages, "on_get_outgoing_document_messages"); td::remove_if(result->messages_, [](const auto &message) { return message->content_->get_id() != td_api::messageDocument::ID; }); result->total_count_ = narrow_cast(result->messages_.size()); promise.set_value(std::move(result)); } void MessageQueryManager::check_search_posts_flood( const string &query, Promise> promise) { td_->create_handler(std::move(promise))->send(query); } void MessageQueryManager::search_public_posts(const string &query, const string &offset_str, int32 limit, int64 star_count, Promise> &&promise) { if (limit <= 0) { return promise.set_error(400, "Parameter limit must be positive"); } if (limit > MAX_SEARCH_MESSAGES) { limit = MAX_SEARCH_MESSAGES; } if (star_count < 0) { return promise.set_error(400, "Invalid number of Telegram Stars specified"); } if (!td_->star_manager_->has_owned_star_count(star_count)) { return promise.set_error(400, "BALANCE_TOO_LOW"); } TRY_RESULT_PROMISE(promise, offset, MessageSearchOffset::from_string(offset_str)); td_->create_handler(std::move(promise))->send(query, offset, limit, star_count); } void MessageQueryManager::on_get_public_post_search_result( const string &hashtag, const MessageSearchOffset &old_offset, int32 limit, int64 star_count, telegram_api::object_ptr flood, vector> &&messages, int32 next_rate, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); SearchPostsFlood posts_flood(std::move(flood)); vector> result; MessageSearchOffset next_offset; for (auto &message : messages) { next_offset.update_from_message(message); auto message_full_id = td_->messages_manager_->on_get_message(DialogId(), std::move(message), false, false, "on_get_public_post_search_result"); auto message_object = td_->messages_manager_->get_message_object(message_full_id, "on_get_public_post_search_result"); if (message_object != nullptr) { result.push_back(std::move(message_object)); } } string next_offset_str; if (!result.empty()) { if (next_rate > 0) { next_offset.date_ = next_rate; } next_offset_str = next_offset.to_string(); } td_->star_manager_->add_pending_owned_star_count(star_count, !posts_flood.is_free()); promise.set_value(td_api::make_object( std::move(result), next_offset_str, posts_flood.get_public_post_search_limits_object(), false)); } void MessageQueryManager::search_hashtag_posts(string hashtag, string offset_str, int32 limit, Promise> &&promise) { if (limit <= 0) { return promise.set_error(400, "Parameter limit must be positive"); } if (limit > MAX_SEARCH_MESSAGES) { limit = MAX_SEARCH_MESSAGES; } TRY_RESULT_PROMISE(promise, offset, MessageSearchOffset::from_string(offset_str)); bool is_cashtag = false; if (hashtag[0] == '#' || hashtag[0] == '$') { is_cashtag = (hashtag[0] == '$'); hashtag = hashtag.substr(1); } if (hashtag.empty()) { return promise.set_value(td_->messages_manager_->get_found_messages_object({}, "search_hashtag_posts")); } send_closure(is_cashtag ? td_->cashtag_search_hints_ : td_->hashtag_search_hints_, &HashtagHints::hashtag_used, hashtag); td_->create_handler(std::move(promise)) ->send(PSTRING() << (is_cashtag ? '$' : '#') << hashtag, offset, limit); } void MessageQueryManager::on_get_hashtag_search_result( const string &hashtag, const MessageSearchOffset &old_offset, int32 limit, int32 total_count, vector> &&messages, int32 next_rate, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); MessagesManager::FoundMessages found_messages; auto &result = found_messages.message_full_ids; MessageSearchOffset next_offset; for (auto &message : messages) { next_offset.update_from_message(message); auto new_message_full_id = td_->messages_manager_->on_get_message(DialogId(), std::move(message), false, false, "search hashtag"); if (new_message_full_id != MessageFullId()) { result.push_back(new_message_full_id); } else { total_count--; } } if (total_count < static_cast(result.size())) { LOG(ERROR) << "Receive " << result.size() << " valid messages out of " << total_count << " in " << messages.size() << " messages"; total_count = static_cast(result.size()); } found_messages.total_count = total_count; if (!result.empty()) { if (next_rate > 0) { next_offset.date_ = next_rate; } found_messages.next_offset = next_offset.to_string(); } promise.set_value(td_->messages_manager_->get_found_messages_object(found_messages, "on_get_hashtag_search_result")); } void MessageQueryManager::search_dialog_recent_location_messages( DialogId dialog_id, int32 limit, Promise> &&promise) { LOG(INFO) << "Search recent location messages in " << dialog_id << " with limit " << limit; if (limit <= 0) { return promise.set_error(400, "Parameter limit must be positive"); } if (limit > MAX_SEARCH_MESSAGES) { limit = MAX_SEARCH_MESSAGES; } TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(dialog_id, true, AccessRights::Read, "search_dialog_recent_location_messages")); switch (dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: case DialogType::Channel: return td_->create_handler(std::move(promise))->send(dialog_id, limit); case DialogType::SecretChat: return promise.set_value(td_->messages_manager_->get_messages_object(0, dialog_id, {}, false, "search_dialog_recent_location_messages")); default: UNREACHABLE(); promise.set_error(500, "Message search is not supported"); } } void MessageQueryManager::on_get_recent_locations(DialogId dialog_id, int32 limit, int32 total_count, vector> &&messages, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); LOG(INFO) << "Receive " << messages.size() << " recent locations in " << dialog_id; vector message_ids; for (auto &message : messages) { auto new_message_full_id = td_->messages_manager_->on_get_message(dialog_id, std::move(message), false, false, "on_get_recent_locations"); if (new_message_full_id != MessageFullId()) { message_ids.push_back(new_message_full_id.get_message_id()); } else { total_count--; } } if (total_count < static_cast(message_ids.size())) { LOG(ERROR) << "Receive " << message_ids.size() << " valid messages out of " << total_count << " in " << messages.size() << " messages"; total_count = static_cast(message_ids.size()); } auto result = td_->messages_manager_->get_messages_object(total_count, dialog_id, message_ids, true, "on_get_recent_locations"); td::remove_if(result->messages_, [&](const auto &message) { if (message->content_->get_id() != td_api::messageLiveLocation::ID) { result->total_count_--; return true; } return false; }); promise.set_value(std::move(result)); } void MessageQueryManager::get_dialog_message_position_from_server(DialogId dialog_id, MessageTopic message_topic, MessageSearchFilter filter, MessageId message_id, Promise &&promise) { if (filter == MessageSearchFilter::UnreadMention || filter == MessageSearchFilter::UnreadReaction || filter == MessageSearchFilter::UnreadPollVote || filter == MessageSearchFilter::FailedToSend) { return promise.set_error(400, "The filter is not supported"); } td_->create_handler(std::move(promise))->send(dialog_id, message_id, filter, message_topic); } void MessageQueryManager::report_music_listen(FileId file_id, int32 duration, Promise &&promise) { if (duration < 0) { return promise.set_error(400, "Invalid duration specified"); } if (!file_id.is_valid()) { return promise.set_error(400, "Invalid file identifier specified"); } auto file_view = td_->file_manager_->get_file_view(file_id); if (file_view.is_encrypted()) { return promise.set_value(Unit()); } const auto *main_remote_location = file_view.get_main_remote_location(); if (main_remote_location == nullptr || main_remote_location->is_web() || !main_remote_location->is_document()) { return promise.set_value(Unit()); } if (duration < 3) { return promise.set_value(Unit()); } td_->create_handler(std::move(promise)) ->send(main_remote_location->as_input_document(), duration); } void MessageQueryManager::send_message_view_metrics(DialogId dialog_id, MessageId message_id, int32 time_in_view_ms, int32 active_time_in_view_ms, int32 height_to_viewport_ratio_per_mille, int32 seen_range_ratio_per_mille, Promise &&promise) { TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(dialog_id, true, AccessRights::Read, "send_message_view_metrics")); if (time_in_view_ms < 0 || time_in_view_ms > 1000000000 || active_time_in_view_ms < 0 || active_time_in_view_ms > 1000000000 || height_to_viewport_ratio_per_mille < 0 || seen_range_ratio_per_mille < 0 || seen_range_ratio_per_mille > 1000) { return promise.set_error(400, "Invalid metrics specified"); } if (dialog_id.get_type() == DialogType::SecretChat || !message_id.is_server() || time_in_view_ms < 300 || !td_->messages_manager_->have_message_force({dialog_id, message_id}, "send_message_view_metrics")) { return promise.set_value(Unit()); } MessageViewMetrics metrics; metrics.message_id_ = message_id; metrics.time_in_view_ms_ = time_in_view_ms; metrics.active_time_in_view_ms_ = active_time_in_view_ms; metrics.height_to_viewport_ratio_per_mille_ = height_to_viewport_ratio_per_mille; metrics.seen_range_ratio_per_mille_ = seen_range_ratio_per_mille; pending_message_view_metrics_[dialog_id].push_back(std::move(metrics)); send_message_view_metrics_timeout_.add_timeout_in(dialog_id.get(), 5.0); promise.set_value(Unit()); } void MessageQueryManager::on_send_message_view_metrics_timeout_callback(void *message_query_manager_ptr, int64 dialog_id_int) { if (G()->close_flag()) { return; } auto message_query_manager = static_cast(message_query_manager_ptr); send_closure_later(message_query_manager->actor_id(message_query_manager), &MessageQueryManager::send_message_view_metrics_timeout, DialogId(dialog_id_int)); } void MessageQueryManager::send_message_view_metrics_timeout(DialogId dialog_id) { if (G()->close_flag()) { return; } auto it = pending_message_view_metrics_.find(dialog_id); if (it == pending_message_view_metrics_.end()) { return; } FlatHashMap metrics; for (const auto &input_metric : it->second) { CHECK(input_metric.message_id_.is_server()); auto &metric = metrics[input_metric.message_id_]; metric.message_id_ = input_metric.message_id_; metric.time_in_view_ms_ += input_metric.time_in_view_ms_; metric.active_time_in_view_ms_ += input_metric.active_time_in_view_ms_; metric.height_to_viewport_ratio_per_mille_ = max(metric.height_to_viewport_ratio_per_mille_, input_metric.height_to_viewport_ratio_per_mille_); metric.seen_range_ratio_per_mille_ = max(metric.seen_range_ratio_per_mille_, input_metric.seen_range_ratio_per_mille_); } pending_message_view_metrics_.erase(it); vector> input_metrics; for (const auto &metric_it : metrics) { const auto &metric = metric_it.second; input_metrics.push_back(telegram_api::make_object( metric.message_id_.get_server_message_id().get(), Random::secure_int64(), metric.time_in_view_ms_, metric.active_time_in_view_ms_, metric.height_to_viewport_ratio_per_mille_, metric.seen_range_ratio_per_mille_)); } td_->create_handler()->send(dialog_id, std::move(input_metrics)); } void MessageQueryManager::get_message_read_date_from_server( MessageFullId message_full_id, Promise> &&promise) { td_->create_handler(std::move(promise)) ->send(message_full_id.get_dialog_id(), message_full_id.get_message_id()); } void MessageQueryManager::get_message_viewers(MessageFullId message_full_id, Promise> &&promise) { TRY_STATUS_PROMISE(promise, td_->messages_manager_->can_get_message_viewers(message_full_id)); auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id = message_full_id.get_dialog_id(), promise = std::move(promise)](Result result) mutable { if (result.is_error()) { return promise.set_error(result.move_as_error()); } send_closure(actor_id, &MessageQueryManager::on_get_message_viewers, dialog_id, result.move_as_ok(), false, std::move(promise)); }); td_->create_handler(std::move(query_promise)) ->send(message_full_id.get_dialog_id(), message_full_id.get_message_id()); } void MessageQueryManager::on_get_message_viewers(DialogId dialog_id, MessageViewers message_viewers, bool is_recursive, Promise> &&promise) { if (!is_recursive) { bool need_participant_list = false; for (auto user_id : message_viewers.get_user_ids()) { if (!td_->user_manager_->have_user_force(user_id, "on_get_message_viewers")) { need_participant_list = true; } } if (need_participant_list) { auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, message_viewers = std::move(message_viewers), promise = std::move(promise)](Unit result) mutable { send_closure(actor_id, &MessageQueryManager::on_get_message_viewers, dialog_id, std::move(message_viewers), true, std::move(promise)); }); switch (dialog_id.get_type()) { case DialogType::Chat: return td_->chat_manager_->reload_chat_full(dialog_id.get_chat_id(), std::move(query_promise), "on_get_message_viewers"); case DialogType::Channel: return td_->dialog_participant_manager_->get_channel_participants( dialog_id.get_channel_id(), ChannelParticipantFilter::recent(), string(), 0, 200, 200, PromiseCreator::lambda([query_promise = std::move(query_promise)](DialogParticipants) mutable { query_promise.set_value(Unit()); })); default: UNREACHABLE(); return; } } } promise.set_value(message_viewers.get_message_viewers_object(td_->user_manager_.get())); } void MessageQueryManager::view_messages(DialogId dialog_id, const vector &message_ids, bool increment_view_counter) { const size_t MAX_MESSAGE_VIEWS = 100; // server-side limit vector viewed_message_ids; viewed_message_ids.reserve(min(message_ids.size(), MAX_MESSAGE_VIEWS)); for (auto message_id : message_ids) { MessageFullId message_full_id{dialog_id, message_id}; if (!being_reloaded_views_message_full_ids_.insert(message_full_id).second) { if (!increment_view_counter || !need_view_counter_increment_message_full_ids_.insert(message_full_id).second) { continue; } } else if (increment_view_counter) { need_view_counter_increment_message_full_ids_.insert(message_full_id); } viewed_message_ids.push_back(message_id); if (viewed_message_ids.size() >= MAX_MESSAGE_VIEWS) { td_->create_handler()->send(dialog_id, std::move(viewed_message_ids), increment_view_counter); viewed_message_ids.clear(); } } if (!viewed_message_ids.empty()) { td_->create_handler()->send(dialog_id, std::move(viewed_message_ids), increment_view_counter); } } void MessageQueryManager::finish_get_message_views(DialogId dialog_id, const vector &message_ids) { for (auto message_id : message_ids) { MessageFullId message_full_id{dialog_id, message_id}; being_reloaded_views_message_full_ids_.erase(message_full_id); need_view_counter_increment_message_full_ids_.erase(message_full_id); } } void MessageQueryManager::queue_message_reactions_reload(MessageFullId message_full_id) { auto dialog_id = message_full_id.get_dialog_id(); CHECK(dialog_id.is_valid()); auto message_id = message_full_id.get_message_id(); CHECK(message_id.is_valid()); being_reloaded_reactions_[dialog_id].message_ids.insert(message_id); try_reload_message_reactions(dialog_id, false); } void MessageQueryManager::queue_message_reactions_reload(DialogId dialog_id, const vector &message_ids) { LOG(INFO) << "Queue reload of reactions in " << message_ids << " in " << dialog_id; auto &message_ids_to_reload = being_reloaded_reactions_[dialog_id].message_ids; for (auto &message_id : message_ids) { CHECK(message_id.is_valid()); message_ids_to_reload.insert(message_id); } try_reload_message_reactions(dialog_id, false); } void MessageQueryManager::try_reload_message_reactions(DialogId dialog_id, bool is_finished) { if (G()->close_flag()) { return; } auto it = being_reloaded_reactions_.find(dialog_id); if (it == being_reloaded_reactions_.end()) { return; } if (is_finished) { CHECK(it->second.is_request_sent); it->second.is_request_sent = false; if (it->second.message_ids.empty()) { being_reloaded_reactions_.erase(it); return; } } else if (it->second.is_request_sent) { return; } CHECK(!it->second.message_ids.empty()); CHECK(!it->second.is_request_sent); it->second.is_request_sent = true; static constexpr size_t MAX_MESSAGE_IDS = 100; // server-side limit vector message_ids; for (auto message_id_it = it->second.message_ids.begin(); message_id_it != it->second.message_ids.end() && message_ids.size() < MAX_MESSAGE_IDS; ++message_id_it) { auto message_id = *message_id_it; if (!has_message_pending_read_reactions({dialog_id, message_id})) { message_ids.push_back(message_id); } } for (auto message_id : message_ids) { it->second.message_ids.erase(message_id); } if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read) || message_ids.empty()) { create_actor("RetryReloadMessageReactionsActor", 0.2, PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](Unit) mutable { send_closure(actor_id, &MessageQueryManager::try_reload_message_reactions, dialog_id, true); })) .release(); return; } for (const auto &message_id : message_ids) { CHECK(message_id.is_server()); } td_->create_handler()->send(dialog_id, std::move(message_ids)); } void MessageQueryManager::get_paid_message_reaction_senders( DialogId dialog_id, Promise> &&promise) { MultiPromiseActorSafe mpas{"GetPaidMessageReactionSendersMultiPromiseActor"}; mpas.add_promise(PromiseCreator::lambda( [actor_id = actor_id(this), dialog_id, promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure_later(actor_id, &MessageQueryManager::do_get_paid_message_reaction_senders, dialog_id, std::move(promise)); } })); auto lock = mpas.get_promise(); td_->chat_manager_->load_created_public_broadcasts(mpas.get_promise()); td_->user_manager_->get_me(mpas.get_promise()); lock.set_value(Unit()); } void MessageQueryManager::do_get_paid_message_reaction_senders( DialogId dialog_id, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, "do_get_paid_message_reaction_senders")); if (!td_->dialog_manager_->is_broadcast_channel(dialog_id)) { return promise.set_value(td_api::make_object()); } CHECK(td_->chat_manager_->are_created_public_broadcasts_inited()); auto senders = td_api::make_object(); const auto &created_public_broadcasts = td_->chat_manager_->get_created_public_broadcasts(); auto add_sender = [&senders, td = td_](DialogId dialog_id) { senders->senders_.push_back(get_message_sender_object(td, dialog_id, "add_sender")); senders->total_count_++; }; add_sender(td_->dialog_manager_->get_my_dialog_id()); std::multimap sorted_channel_ids; for (auto channel_id : created_public_broadcasts) { int64 score = td_->chat_manager_->get_channel_participant_count(channel_id); sorted_channel_ids.emplace(-score, channel_id); }; for (auto &channel_id : sorted_channel_ids) { add_sender(DialogId(channel_id.second)); } return promise.set_value(std::move(senders)); } void MessageQueryManager::summarize_message_text(MessageFullId message_full_id, const string &to_language_code, string tone, Promise> &&promise) { auto dialog_id = message_full_id.get_dialog_id(); TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, "summarize_message_text")); if (!td_->messages_manager_->have_message_force(message_full_id, "summarize_message_text")) { return promise.set_error(400, "Message not found"); } TRY_STATUS_PROMISE(promise, TranslationManager::check_tone(tone)); auto message_id = message_full_id.get_message_id(); if (!message_id.is_server()) { return promise.set_error(400, "Message can't be summarized"); } td_->create_handler(std::move(promise))->send(dialog_id, message_id, to_language_code, tone); } void MessageQueryManager::add_to_do_list_tasks(MessageFullId message_full_id, vector> &&tasks, Promise &&promise) { auto dialog_id = message_full_id.get_dialog_id(); if (!td_->messages_manager_->can_add_message_tasks(message_full_id, static_cast(tasks.size()))) { return promise.set_error(400, "Can't add the checklist tasks to the message"); } vector items; for (auto &task : tasks) { TRY_RESULT_PROMISE(promise, item, ToDoItem::get_to_do_item(td_, dialog_id, std::move(task))); items.push_back(std::move(item)); } if (items.empty()) { return promise.set_error(400, "The list of new tasks must be non-empty"); } td_->create_handler(std::move(promise)) ->send(dialog_id, message_full_id.get_message_id(), items); } void MessageQueryManager::mark_to_do_list_tasks_as_done(MessageFullId message_full_id, vector done_task_ids, vector not_done_task_ids, Promise &&promise) { auto dialog_id = message_full_id.get_dialog_id(); if (!td_->messages_manager_->can_mark_message_tasks_as_done(message_full_id)) { return promise.set_error(400, "Can't mark tasks as done in the message"); } td_->create_handler(std::move(promise)) ->send(dialog_id, message_full_id.get_message_id(), std::move(done_task_ids), std::move(not_done_task_ids)); } void MessageQueryManager::get_discussion_message(DialogId dialog_id, MessageId message_id, DialogId expected_dialog_id, MessageId expected_message_id, Promise &&promise) { td_->create_handler(std::move(promise)) ->send(dialog_id, message_id, expected_dialog_id, expected_message_id); } void MessageQueryManager::process_discussion_message( telegram_api::object_ptr &&result, DialogId dialog_id, MessageId message_id, DialogId expected_dialog_id, MessageId expected_message_id, Promise promise) { LOG(INFO) << "Receive discussion message for " << message_id << " in " << dialog_id << " with expected " << expected_message_id << " in " << expected_dialog_id << ": " << to_string(result); td_->user_manager_->on_get_users(std::move(result->users_), "process_discussion_message"); td_->chat_manager_->on_get_chats(std::move(result->chats_), "process_discussion_message"); for (auto &message : result->messages_) { if (td_->messages_manager_->need_channel_difference_to_add_message(expected_dialog_id, message)) { auto max_message_id = MessageId::get_max_message_id(result->messages_); return td_->messages_manager_->run_after_channel_difference( expected_dialog_id, max_message_id, PromiseCreator::lambda([actor_id = actor_id(this), result = std::move(result), dialog_id, message_id, expected_dialog_id, expected_message_id, promise = std::move(promise)](Unit ignored) mutable { send_closure(actor_id, &MessageQueryManager::process_discussion_message_impl, std::move(result), dialog_id, message_id, expected_dialog_id, expected_message_id, std::move(promise)); }), "process_discussion_message"); } } process_discussion_message_impl(std::move(result), dialog_id, message_id, expected_dialog_id, expected_message_id, std::move(promise)); } void MessageQueryManager::process_discussion_message_impl( telegram_api::object_ptr &&result, DialogId dialog_id, MessageId message_id, DialogId expected_dialog_id, MessageId expected_message_id, Promise promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); MessageThreadInfo message_thread_info; message_thread_info.dialog_id = expected_dialog_id; message_thread_info.unread_message_count = max(0, result->unread_count_); MessageId top_message_id; for (auto &message : result->messages_) { auto message_full_id = td_->messages_manager_->on_get_message(expected_dialog_id, std::move(message), false, false, "process_discussion_message_impl"); if (message_full_id.get_message_id().is_valid()) { message_thread_info.message_ids.push_back(message_full_id.get_message_id()); if (message_full_id.get_message_id() == expected_message_id) { top_message_id = expected_message_id; } } } if (!message_thread_info.message_ids.empty() && !top_message_id.is_valid()) { top_message_id = message_thread_info.message_ids.back(); } auto max_message_id = MessageId(ServerMessageId(result->max_id_)); auto last_read_inbox_message_id = MessageId(ServerMessageId(result->read_inbox_max_id_)); auto last_read_outbox_message_id = MessageId(ServerMessageId(result->read_outbox_max_id_)); if (top_message_id.is_valid()) { td_->messages_manager_->on_update_read_message_comments(expected_dialog_id, top_message_id, max_message_id, last_read_inbox_message_id, last_read_outbox_message_id, message_thread_info.unread_message_count); } if (expected_dialog_id != dialog_id) { td_->messages_manager_->on_update_read_message_comments(dialog_id, message_id, max_message_id, last_read_inbox_message_id, last_read_outbox_message_id, message_thread_info.unread_message_count); } promise.set_value(std::move(message_thread_info)); } void MessageQueryManager::get_emoji_game_info(Promise> &&promise) { auto query_promise = PromiseCreator::lambda( [actor_id = actor_id(this), promise = std::move(promise)]( Result> result) mutable { if (result.is_error()) { return promise.set_error(result.move_as_error()); } send_closure(actor_id, &MessageQueryManager::on_get_emoji_game_info, result.move_as_ok(), std::move(promise)); }); td_->create_handler(std::move(query_promise))->send(); } void MessageQueryManager::on_get_emoji_game_info( telegram_api::object_ptr &&result, Promise> &&promise) { on_update_emoji_game_info(std::move(result)); CHECK(is_emoji_game_info_inited_); promise.set_value(emoji_game_info_.get_stake_dice_state_object(td_)); } class MessageQueryManager::BlockMessageSenderFromRepliesOnServerLogEvent { public: MessageId message_id_; bool delete_message_; bool delete_all_messages_; bool report_spam_; template void store(StorerT &storer) const { BEGIN_STORE_FLAGS(); STORE_FLAG(delete_message_); STORE_FLAG(delete_all_messages_); STORE_FLAG(report_spam_); END_STORE_FLAGS(); td::store(message_id_, storer); } template void parse(ParserT &parser) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(delete_message_); PARSE_FLAG(delete_all_messages_); PARSE_FLAG(report_spam_); END_PARSE_FLAGS(); td::parse(message_id_, parser); } }; uint64 MessageQueryManager::save_block_message_sender_from_replies_on_server_log_event(MessageId message_id, bool need_delete_message, bool need_delete_all_messages, bool report_spam) { BlockMessageSenderFromRepliesOnServerLogEvent log_event{message_id, need_delete_message, need_delete_all_messages, report_spam}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::BlockMessageSenderFromRepliesOnServer, get_log_event_storer(log_event)); } void MessageQueryManager::block_message_sender_from_replies_on_server(MessageId message_id, bool need_delete_message, bool need_delete_all_messages, bool report_spam, uint64 log_event_id, Promise &&promise) { if (log_event_id == 0) { log_event_id = save_block_message_sender_from_replies_on_server_log_event(message_id, need_delete_message, need_delete_all_messages, report_spam); } td_->create_handler(get_erase_log_event_promise(log_event_id, std::move(promise))) ->send(message_id, need_delete_message, need_delete_all_messages, report_spam); } void MessageQueryManager::delete_dialog_messages_by_sender(DialogId dialog_id, DialogId sender_dialog_id, Promise &&promise) { CHECK(!td_->auth_manager_->is_bot()); TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(dialog_id, true, AccessRights::Write, "delete_dialog_messages_by_sender")); TRY_STATUS_PROMISE(promise, td_->dialog_manager_->can_delete_all_dialog_messages_by_sender(dialog_id)); if (!td_->dialog_manager_->have_input_peer(sender_dialog_id, false, AccessRights::Know)) { return promise.set_error(400, "Message sender not found"); } td_->messages_manager_->delete_local_dialog_messages_by_sender(dialog_id, sender_dialog_id); CHECK(dialog_id.get_type() == DialogType::Channel); delete_all_channel_messages_by_sender_on_server(dialog_id.get_channel_id(), sender_dialog_id, 0, std::move(promise)); } void MessageQueryManager::delete_dialog_messages_by_date(DialogId dialog_id, int32 min_date, int32 max_date, bool revoke, Promise &&promise) { CHECK(!td_->auth_manager_->is_bot()); TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, "delete_dialog_messages_by_date")); TRY_STATUS_PROMISE(promise, fix_delete_message_min_max_dates(min_date, max_date)); if (max_date == 0) { return promise.set_value(Unit()); } switch (dialog_id.get_type()) { case DialogType::User: break; case DialogType::Chat: if (revoke) { return promise.set_error(400, "Bulk message revocation is unsupported in basic group chats"); } break; case DialogType::Channel: return promise.set_error(400, "Bulk message deletion is unsupported in supergroup chats"); case DialogType::SecretChat: case DialogType::None: default: UNREACHABLE(); break; } td_->messages_manager_->delete_local_dialog_messages_by_date(dialog_id, min_date, max_date); delete_dialog_messages_by_date_on_server(dialog_id, min_date, max_date, revoke, 0, std::move(promise)); } void MessageQueryManager::delete_all_call_messages(bool revoke, Promise &&promise) { delete_all_call_messages_on_server(revoke, 0, std::move(promise)); } class MessageQueryManager::DeleteAllCallMessagesOnServerLogEvent { public: bool revoke_; template void store(StorerT &storer) const { BEGIN_STORE_FLAGS(); STORE_FLAG(revoke_); END_STORE_FLAGS(); } template void parse(ParserT &parser) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(revoke_); END_PARSE_FLAGS(); } }; uint64 MessageQueryManager::save_delete_all_call_messages_on_server_log_event(bool revoke) { DeleteAllCallMessagesOnServerLogEvent log_event{revoke}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteAllCallMessagesOnServer, get_log_event_storer(log_event)); } void MessageQueryManager::delete_all_call_messages_on_server(bool revoke, uint64 log_event_id, Promise &&promise) { if (log_event_id == 0) { log_event_id = save_delete_all_call_messages_on_server_log_event(revoke); } AffectedHistoryQuery query = [td = td_, revoke](DialogId /*dialog_id*/, Promise &&query_promise) { td->create_handler(std::move(query_promise))->send(revoke); }; run_affected_history_query_until_complete(DialogId(), std::move(query), false, get_erase_log_event_promise(log_event_id, std::move(promise))); } class MessageQueryManager::DeleteAllChannelMessagesFromSenderOnServerLogEvent { public: ChannelId channel_id_; DialogId sender_dialog_id_; template void store(StorerT &storer) const { td::store(channel_id_, storer); td::store(sender_dialog_id_, storer); } template void parse(ParserT &parser) { td::parse(channel_id_, parser); if (parser.version() >= static_cast(Version::AddKeyboardButtonFlags)) { td::parse(sender_dialog_id_, parser); } else { UserId user_id; td::parse(user_id, parser); sender_dialog_id_ = DialogId(user_id); } } }; uint64 MessageQueryManager::save_delete_all_channel_messages_by_sender_on_server_log_event(ChannelId channel_id, DialogId sender_dialog_id) { DeleteAllChannelMessagesFromSenderOnServerLogEvent log_event{channel_id, sender_dialog_id}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteAllChannelMessagesFromSenderOnServer, get_log_event_storer(log_event)); } void MessageQueryManager::delete_all_channel_messages_by_sender_on_server(ChannelId channel_id, DialogId sender_dialog_id, uint64 log_event_id, Promise &&promise) { if (log_event_id == 0 && G()->use_chat_info_database()) { log_event_id = save_delete_all_channel_messages_by_sender_on_server_log_event(channel_id, sender_dialog_id); } AffectedHistoryQuery query = [td = td_, sender_dialog_id](DialogId dialog_id, Promise &&query_promise) { td->create_handler(std::move(query_promise)) ->send(dialog_id.get_channel_id(), sender_dialog_id); }; run_affected_history_query_until_complete(DialogId(channel_id), std::move(query), sender_dialog_id.get_type() != DialogType::User, get_erase_log_event_promise(log_event_id, std::move(promise))); } class MessageQueryManager::DeleteDialogHistoryOnServerLogEvent { public: DialogId dialog_id_; MessageId max_message_id_; bool remove_from_dialog_list_; bool revoke_; template void store(StorerT &storer) const { BEGIN_STORE_FLAGS(); STORE_FLAG(remove_from_dialog_list_); STORE_FLAG(revoke_); END_STORE_FLAGS(); td::store(dialog_id_, storer); td::store(max_message_id_, storer); } template void parse(ParserT &parser) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(remove_from_dialog_list_); PARSE_FLAG(revoke_); END_PARSE_FLAGS(); td::parse(dialog_id_, parser); td::parse(max_message_id_, parser); } }; uint64 MessageQueryManager::save_delete_dialog_history_on_server_log_event(DialogId dialog_id, MessageId max_message_id, bool remove_from_dialog_list, bool revoke) { DeleteDialogHistoryOnServerLogEvent log_event{dialog_id, max_message_id, remove_from_dialog_list, revoke}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteDialogHistoryOnServer, get_log_event_storer(log_event)); } void MessageQueryManager::delete_dialog_history_on_server(DialogId dialog_id, MessageId max_message_id, bool remove_from_dialog_list, bool revoke, bool allow_error, uint64 log_event_id, Promise &&promise) { LOG(INFO) << "Delete history in " << dialog_id << " up to " << max_message_id << " from server"; if (log_event_id == 0 && G()->use_message_database()) { log_event_id = save_delete_dialog_history_on_server_log_event(dialog_id, max_message_id, remove_from_dialog_list, revoke); } auto new_promise = get_erase_log_event_promise(log_event_id, std::move(promise)); promise = std::move(new_promise); // to prevent self-move switch (dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: { AffectedHistoryQuery query = [td = td_, max_message_id, remove_from_dialog_list, revoke]( DialogId dialog_id, Promise &&query_promise) { td->create_handler(std::move(query_promise)) ->send(dialog_id, max_message_id, remove_from_dialog_list, revoke); }; run_affected_history_query_until_complete(dialog_id, std::move(query), false, std::move(promise)); break; } case DialogType::Channel: td_->create_handler(std::move(promise)) ->send(dialog_id.get_channel_id(), max_message_id, allow_error, revoke); break; case DialogType::SecretChat: send_closure(G()->secret_chats_manager(), &SecretChatsManager::delete_all_messages, dialog_id.get_secret_chat_id(), std::move(promise)); break; case DialogType::None: default: UNREACHABLE(); break; } } class MessageQueryManager::DeleteDialogMessagesByDateOnServerLogEvent { public: DialogId dialog_id_; int32 min_date_; int32 max_date_; bool revoke_; template void store(StorerT &storer) const { BEGIN_STORE_FLAGS(); STORE_FLAG(revoke_); END_STORE_FLAGS(); td::store(dialog_id_, storer); td::store(min_date_, storer); td::store(max_date_, storer); } template void parse(ParserT &parser) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(revoke_); END_PARSE_FLAGS(); td::parse(dialog_id_, parser); td::parse(min_date_, parser); td::parse(max_date_, parser); } }; uint64 MessageQueryManager::save_delete_dialog_messages_by_date_on_server_log_event(DialogId dialog_id, int32 min_date, int32 max_date, bool revoke) { DeleteDialogMessagesByDateOnServerLogEvent log_event{dialog_id, min_date, max_date, revoke}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteDialogMessagesByDateOnServer, get_log_event_storer(log_event)); } Status MessageQueryManager::fix_delete_message_min_max_dates(int32 &min_date, int32 &max_date) { if (min_date > max_date) { return Status::Error(400, "Wrong date interval specified"); } const int32 telegram_launch_date = 1376438400; if (max_date < telegram_launch_date) { max_date = 0; min_date = 0; return Status::OK(); } if (min_date < telegram_launch_date) { min_date = telegram_launch_date; } auto current_date = max(G()->unix_time(), 1635000000); if (min_date >= current_date - 30) { max_date = 0; min_date = 0; return Status::OK(); } if (max_date >= current_date - 30) { max_date = current_date - 31; } CHECK(min_date <= max_date); return Status::OK(); } void MessageQueryManager::delete_dialog_messages_by_date_on_server(DialogId dialog_id, int32 min_date, int32 max_date, bool revoke, uint64 log_event_id, Promise &&promise) { if (log_event_id == 0 && G()->use_chat_info_database()) { log_event_id = save_delete_dialog_messages_by_date_on_server_log_event(dialog_id, min_date, max_date, revoke); } AffectedHistoryQuery query = [td = td_, min_date, max_date, revoke](DialogId dialog_id, Promise &&query_promise) { td->create_handler(std::move(query_promise)) ->send(dialog_id, min_date, max_date, revoke); }; run_affected_history_query_until_complete(dialog_id, std::move(query), true, get_erase_log_event_promise(log_event_id, std::move(promise))); } class MessageQueryManager::DeleteMessagesOnServerLogEvent { public: DialogId dialog_id_; vector message_ids_; bool revoke_; template void store(StorerT &storer) const { BEGIN_STORE_FLAGS(); STORE_FLAG(revoke_); END_STORE_FLAGS(); td::store(dialog_id_, storer); td::store(message_ids_, storer); } template void parse(ParserT &parser) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(revoke_); END_PARSE_FLAGS(); td::parse(dialog_id_, parser); td::parse(message_ids_, parser); } }; uint64 MessageQueryManager::save_delete_messages_on_server_log_event(DialogId dialog_id, const vector &message_ids, bool revoke) { DeleteMessagesOnServerLogEvent log_event{dialog_id, message_ids, revoke}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteMessagesOnServer, get_log_event_storer(log_event)); } void MessageQueryManager::erase_delete_messages_log_event(uint64 log_event_id) { if (!G()->close_flag()) { binlog_erase(G()->td_db()->get_binlog(), log_event_id); } } void MessageQueryManager::delete_messages_on_server(DialogId dialog_id, vector message_ids, bool revoke, uint64 log_event_id, Promise &&promise) { if (message_ids.empty()) { return promise.set_value(Unit()); } LOG(INFO) << (revoke ? "Revoke " : "Delete ") << message_ids << " in " << dialog_id << " from server"; if (log_event_id == 0 && G()->use_message_database()) { log_event_id = save_delete_messages_on_server_log_event(dialog_id, message_ids, revoke); } MultiPromiseActorSafe mpas{"DeleteMessagesOnServerMultiPromiseActor"}; mpas.add_promise(std::move(promise)); if (log_event_id != 0) { mpas.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), log_event_id](Unit) { send_closure(actor_id, &MessageQueryManager::erase_delete_messages_log_event, log_event_id); })); } auto lock = mpas.get_promise(); auto dialog_type = dialog_id.get_type(); switch (dialog_type) { case DialogType::User: case DialogType::Chat: case DialogType::Channel: { auto server_message_ids = MessageId::get_server_message_ids(message_ids); const size_t MAX_SLICE_SIZE = 100; // server-side limit for (auto &slice_server_message_ids : vector_split(std::move(server_message_ids), MAX_SLICE_SIZE)) { if (dialog_type != DialogType::Channel) { td_->create_handler(mpas.get_promise()) ->send(dialog_id, std::move(slice_server_message_ids), revoke); } else { td_->create_handler(mpas.get_promise()) ->send(dialog_id.get_channel_id(), std::move(slice_server_message_ids)); } } break; } case DialogType::SecretChat: { vector random_ids; for (auto &message_id : message_ids) { auto random_id = td_->messages_manager_->get_message_random_id({dialog_id, message_id}); if (random_id != 0) { random_ids.push_back(random_id); } } if (!random_ids.empty()) { send_closure(G()->secret_chats_manager(), &SecretChatsManager::delete_messages, dialog_id.get_secret_chat_id(), std::move(random_ids), mpas.get_promise()); } break; } case DialogType::None: default: UNREACHABLE(); } lock.set_value(Unit()); } class MessageQueryManager::DeleteScheduledMessagesOnServerLogEvent { public: DialogId dialog_id_; vector message_ids_; template void store(StorerT &storer) const { td::store(dialog_id_, storer); td::store(message_ids_, storer); } template void parse(ParserT &parser) { td::parse(dialog_id_, parser); td::parse(message_ids_, parser); } }; uint64 MessageQueryManager::save_delete_scheduled_messages_on_server_log_event(DialogId dialog_id, const vector &message_ids) { DeleteScheduledMessagesOnServerLogEvent log_event{dialog_id, message_ids}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteScheduledMessagesOnServer, get_log_event_storer(log_event)); } void MessageQueryManager::delete_scheduled_messages_on_server(DialogId dialog_id, vector message_ids, uint64 log_event_id, Promise &&promise) { if (message_ids.empty()) { return promise.set_value(Unit()); } LOG(INFO) << "Delete " << message_ids << " in " << dialog_id << " from server"; if (log_event_id == 0 && G()->use_message_database()) { log_event_id = save_delete_scheduled_messages_on_server_log_event(dialog_id, message_ids); } auto new_promise = get_erase_log_event_promise(log_event_id, std::move(promise)); promise = std::move(new_promise); // to prevent self-move td_->create_handler(std::move(promise))->send(dialog_id, std::move(message_ids)); } class MessageQueryManager::DeleteEphemeralMessageOnServerLogEvent { public: DialogId dialog_id_; DialogId receiver_dialog_id_; EphemeralMessageId ephemeral_message_id_; template void store(StorerT &storer) const { td::store(dialog_id_, storer); td::store(receiver_dialog_id_, storer); td::store(ephemeral_message_id_, storer); } template void parse(ParserT &parser) { td::parse(dialog_id_, parser); td::parse(receiver_dialog_id_, parser); td::parse(ephemeral_message_id_, parser); } }; uint64 MessageQueryManager::save_delete_ephemeral_message_on_server_log_event(DialogId dialog_id, DialogId receiver_dialog_id, EphemeralMessageId ephemeral_message_id) { DeleteEphemeralMessageOnServerLogEvent log_event{dialog_id, receiver_dialog_id, ephemeral_message_id}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteEphemeralMessageOnServer, get_log_event_storer(log_event)); } void MessageQueryManager::delete_ephemeral_message_on_server(DialogId dialog_id, DialogId receiver_dialog_id, EphemeralMessageId ephemeral_message_id, uint64 log_event_id, Promise &&promise) { LOG(INFO) << "Delete " << ephemeral_message_id << " from " << receiver_dialog_id << " in " << dialog_id << " from server"; td_->messages_manager_->on_delete_ephemeral_messages(dialog_id, {ephemeral_message_id}); if (log_event_id == 0 && G()->use_message_database()) { log_event_id = save_delete_ephemeral_message_on_server_log_event(dialog_id, receiver_dialog_id, ephemeral_message_id); } auto new_promise = get_erase_log_event_promise(log_event_id, std::move(promise)); promise = std::move(new_promise); // to prevent self-move td_->create_handler(std::move(promise)) ->send(dialog_id, receiver_dialog_id, ephemeral_message_id); } void MessageQueryManager::delete_topic_history(DialogId dialog_id, ForumTopicId forum_topic_id, Promise &&promise) { TRY_STATUS_PROMISE( promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, "delete_topic_history")); // TODO check rights and delete topic history locally delete_topic_history_on_server(dialog_id, forum_topic_id, 0, std::move(promise)); } class MessageQueryManager::DeleteTopicHistoryOnServerLogEvent { public: DialogId dialog_id_; ForumTopicId forum_topic_id_; template void store(StorerT &storer) const { BEGIN_STORE_FLAGS(); END_STORE_FLAGS(); td::store(dialog_id_, storer); td::store(forum_topic_id_, storer); } template void parse(ParserT &parser) { BEGIN_PARSE_FLAGS(); END_PARSE_FLAGS(); td::parse(dialog_id_, parser); td::parse(forum_topic_id_, parser); } }; uint64 MessageQueryManager::save_delete_topic_history_on_server_log_event(DialogId dialog_id, ForumTopicId forum_topic_id) { DeleteTopicHistoryOnServerLogEvent log_event{dialog_id, forum_topic_id}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteTopicHistoryOnServer, get_log_event_storer(log_event)); } void MessageQueryManager::delete_topic_history_on_server(DialogId dialog_id, ForumTopicId forum_topic_id, uint64 log_event_id, Promise &&promise) { if (log_event_id == 0 && G()->use_message_database()) { log_event_id = save_delete_topic_history_on_server_log_event(dialog_id, forum_topic_id); } auto new_promise = get_erase_log_event_promise(log_event_id, std::move(promise)); promise = std::move(new_promise); // to prevent self-move AffectedHistoryQuery query = [td = td_, forum_topic_id](DialogId dialog_id, Promise &&query_promise) { td->create_handler(std::move(query_promise))->send(dialog_id, forum_topic_id); }; run_affected_history_query_until_complete(dialog_id, std::move(query), true, std::move(promise)); } void MessageQueryManager::delete_reactions_by_sender(DialogId dialog_id, DialogId sender_dialog_id, Promise &&promise) { TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(dialog_id, true, AccessRights::Write, "delete_reactions_by_sender")); if (!td_->dialog_manager_->have_input_peer(sender_dialog_id, false, AccessRights::Know)) { return promise.set_error(400, "Reaction sender not found"); } // td_->messages_manager_->delete_local_reactions_by_sender(dialog_id, sender_dialog_id); td_->create_handler(std::move(promise))->send(dialog_id, sender_dialog_id); } void MessageQueryManager::delete_reaction_by_sender(DialogId dialog_id, MessageId message_id, DialogId sender_dialog_id, Promise &&promise) { TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(dialog_id, true, AccessRights::Write, "delete_reactions_by_sender")); if (!td_->dialog_manager_->have_input_peer(sender_dialog_id, false, AccessRights::Know)) { return promise.set_error(400, "Reaction sender not found"); } if (!message_id.is_server()) { return promise.set_error(400, "Invalid message identifier specified"); } // td_->messages_manager_->delete_reaction_by_sender(dialog_id, message_id, sender_dialog_id); td_->create_handler(std::move(promise)) ->send(dialog_id, message_id, sender_dialog_id); } void MessageQueryManager::get_personal_chat_history(UserId user_id, int32 limit, Promise> &&promise) { TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); if (limit <= 0) { return promise.set_error(400, "Limit must be positive"); } td_->create_handler(std::move(promise))->send(std::move(input_user), limit); } void MessageQueryManager::read_all_topic_mentions_on_server(DialogId dialog_id, ForumTopicId forum_topic_id, uint64 log_event_id, Promise &&promise) { AffectedHistoryQuery query = [td = td_, forum_topic_id](DialogId dialog_id, Promise &&query_promise) { td->create_handler(std::move(query_promise))->send(dialog_id, forum_topic_id); }; run_affected_history_query_until_complete(dialog_id, std::move(query), true, std::move(promise)); } class MessageQueryManager::ReadAllDialogMentionsOnServerLogEvent { public: DialogId dialog_id_; template void store(StorerT &storer) const { td::store(dialog_id_, storer); } template void parse(ParserT &parser) { td::parse(dialog_id_, parser); } }; uint64 MessageQueryManager::save_read_all_dialog_mentions_on_server_log_event(DialogId dialog_id) { ReadAllDialogMentionsOnServerLogEvent log_event{dialog_id}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ReadAllDialogMentionsOnServer, get_log_event_storer(log_event)); } void MessageQueryManager::read_all_dialog_mentions_on_server(DialogId dialog_id, uint64 log_event_id, Promise &&promise) { if (log_event_id == 0 && G()->use_message_database()) { log_event_id = save_read_all_dialog_mentions_on_server_log_event(dialog_id); } AffectedHistoryQuery query = [td = td_](DialogId dialog_id, Promise &&query_promise) { td->create_handler(std::move(query_promise))->send(dialog_id, ForumTopicId()); }; run_affected_history_query_until_complete(dialog_id, std::move(query), false, get_erase_log_event_promise(log_event_id, std::move(promise))); } void MessageQueryManager::read_all_topic_reactions_on_server(DialogId dialog_id, ForumTopicId forum_topic_id, SavedMessagesTopicId saved_messages_topic_id, uint64 log_event_id, Promise &&promise) { AffectedHistoryQuery query = [td = td_, forum_topic_id, saved_messages_topic_id]( DialogId dialog_id, Promise &&query_promise) { td->create_handler(std::move(query_promise)) ->send(dialog_id, forum_topic_id, saved_messages_topic_id); }; run_affected_history_query_until_complete(dialog_id, std::move(query), true, std::move(promise)); } class MessageQueryManager::ReadAllDialogReactionsOnServerLogEvent { public: DialogId dialog_id_; template void store(StorerT &storer) const { td::store(dialog_id_, storer); } template void parse(ParserT &parser) { td::parse(dialog_id_, parser); } }; uint64 MessageQueryManager::save_read_all_dialog_reactions_on_server_log_event(DialogId dialog_id) { ReadAllDialogReactionsOnServerLogEvent log_event{dialog_id}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ReadAllDialogReactionsOnServer, get_log_event_storer(log_event)); } void MessageQueryManager::read_all_dialog_reactions_on_server(DialogId dialog_id, uint64 log_event_id, Promise &&promise) { if (log_event_id == 0 && G()->use_message_database()) { log_event_id = save_read_all_dialog_reactions_on_server_log_event(dialog_id); } AffectedHistoryQuery query = [td = td_](DialogId dialog_id, Promise &&query_promise) { td->create_handler(std::move(query_promise)) ->send(dialog_id, ForumTopicId(), SavedMessagesTopicId()); }; run_affected_history_query_until_complete(dialog_id, std::move(query), false, get_erase_log_event_promise(log_event_id, std::move(promise))); } class MessageQueryManager::ReadAllPollVotesOnServerLogEvent { public: DialogId dialog_id_; ForumTopicId forum_topic_id_; template void store(StorerT &storer) const { bool has_forum_topic_id = forum_topic_id_.is_valid(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_forum_topic_id); END_STORE_FLAGS(); td::store(dialog_id_, storer); if (has_forum_topic_id) { td::store(forum_topic_id_, storer); } } template void parse(ParserT &parser) { bool has_forum_topic_id; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_forum_topic_id); END_PARSE_FLAGS(); td::parse(dialog_id_, parser); if (has_forum_topic_id) { td::parse(forum_topic_id_, parser); } } }; uint64 MessageQueryManager::save_read_all_dialog_poll_votes_on_server_log_event(DialogId dialog_id, ForumTopicId forum_topic_id) { ReadAllPollVotesOnServerLogEvent log_event{dialog_id, forum_topic_id}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ReadAllPollVotesOnServer, get_log_event_storer(log_event)); } void MessageQueryManager::read_all_dialog_poll_votes_on_server(DialogId dialog_id, ForumTopicId forum_topic_id, uint64 log_event_id, Promise &&promise) { if (log_event_id == 0 && G()->use_message_database()) { log_event_id = save_read_all_dialog_poll_votes_on_server_log_event(dialog_id, forum_topic_id); } AffectedHistoryQuery query = [td = td_, forum_topic_id](DialogId dialog_id, Promise &&query_promise) { td->create_handler(std::move(query_promise))->send(dialog_id, forum_topic_id); }; run_affected_history_query_until_complete(dialog_id, std::move(query), false, get_erase_log_event_promise(log_event_id, std::move(promise))); } void MessageQueryManager::unpin_all_topic_messages_on_server(DialogId dialog_id, ForumTopicId forum_topic_id, SavedMessagesTopicId saved_messages_topic_id, uint64 log_event_id, Promise &&promise) { AffectedHistoryQuery query = [td = td_, forum_topic_id, saved_messages_topic_id]( DialogId dialog_id, Promise &&query_promise) { td->create_handler(std::move(query_promise)) ->send(dialog_id, forum_topic_id, saved_messages_topic_id); }; run_affected_history_query_until_complete(dialog_id, std::move(query), true, std::move(promise)); } class MessageQueryManager::ReadMessageContentsOnServerLogEvent { public: DialogId dialog_id_; vector message_ids_; template void store(StorerT &storer) const { td::store(dialog_id_, storer); td::store(message_ids_, storer); } template void parse(ParserT &parser) { td::parse(dialog_id_, parser); td::parse(message_ids_, parser); } }; uint64 MessageQueryManager::save_read_message_contents_on_server_log_event(DialogId dialog_id, const vector &message_ids) { ReadMessageContentsOnServerLogEvent log_event{dialog_id, message_ids}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ReadMessageContentsOnServer, get_log_event_storer(log_event)); } void MessageQueryManager::read_message_contents_on_server(DialogId dialog_id, vector message_ids, uint64 log_event_id, Promise &&promise, bool skip_log_event) { CHECK(!message_ids.empty()); LOG(INFO) << "Read contents of " << message_ids << " in " << dialog_id << " on server"; if (log_event_id == 0 && G()->use_message_database() && !skip_log_event) { log_event_id = save_read_message_contents_on_server_log_event(dialog_id, message_ids); } auto new_promise = get_erase_log_event_promise(log_event_id, std::move(promise)); promise = std::move(new_promise); // to prevent self-move switch (dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: td_->create_handler(std::move(promise))->send(std::move(message_ids)); break; case DialogType::Channel: td_->create_handler(std::move(promise)) ->send(dialog_id.get_channel_id(), std::move(message_ids)); break; case DialogType::SecretChat: { CHECK(message_ids.size() == 1); auto random_id = td_->messages_manager_->get_message_random_id({dialog_id, message_ids[0]}); if (random_id != 0) { send_closure(G()->secret_chats_manager(), &SecretChatsManager::send_open_message, dialog_id.get_secret_chat_id(), random_id, std::move(promise)); } else { promise.set_error(400, "Message not found"); } break; } case DialogType::None: default: UNREACHABLE(); } } bool MessageQueryManager::has_message_pending_read_reactions(MessageFullId message_full_id) const { return pending_read_reactions_.count(message_full_id) > 0; } void MessageQueryManager::read_message_reactions_on_server(DialogId dialog_id, vector message_ids) { for (auto message_id : message_ids) { pending_read_reactions_[{dialog_id, message_id}]++; } auto promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, message_ids](Result &&result) mutable { send_closure(actor_id, &MessageQueryManager::on_read_message_reactions, dialog_id, std::move(message_ids), std::move(result)); }); read_message_contents_on_server(dialog_id, std::move(message_ids), 0, std::move(promise)); } void MessageQueryManager::on_read_message_reactions(DialogId dialog_id, vector &&message_ids, Result &&result) { for (auto message_id : message_ids) { MessageFullId message_full_id{dialog_id, message_id}; auto it = pending_read_reactions_.find(message_full_id); CHECK(it != pending_read_reactions_.end()); if (--it->second == 0) { pending_read_reactions_.erase(it); } if (!td_->messages_manager_->have_message_force(message_full_id, "on_read_message_reactions")) { continue; } if (result.is_error()) { queue_message_reactions_reload(message_full_id); } } } bool MessageQueryManager::has_message_pending_read_poll_votes(MessageFullId message_full_id) const { return pending_read_poll_votes_.count(message_full_id) > 0; } void MessageQueryManager::read_message_poll_votes_on_server(DialogId dialog_id, vector message_ids) { CHECK(dialog_id.is_valid()); for (auto message_id : message_ids) { pending_read_poll_votes_[{dialog_id, message_id}]++; } auto promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, message_ids](Result &&result) mutable { send_closure(actor_id, &MessageQueryManager::on_read_message_poll_votes, dialog_id, std::move(message_ids), std::move(result)); }); read_message_contents_on_server(dialog_id, std::move(message_ids), 0, std::move(promise)); } void MessageQueryManager::on_read_message_poll_votes(DialogId dialog_id, vector &&message_ids, Result &&result) { vector message_full_ids; for (auto message_id : message_ids) { MessageFullId message_full_id{dialog_id, message_id}; auto it = pending_read_poll_votes_.find(message_full_id); CHECK(it != pending_read_poll_votes_.end()); if (--it->second == 0) { pending_read_poll_votes_.erase(it); } if (!td_->messages_manager_->have_message_force(message_full_id, "on_read_message_poll_votes")) { continue; } if (result.is_error()) { message_full_ids.push_back(message_full_id); } } if (!message_full_ids.empty()) { td_->messages_manager_->get_messages_from_server(std::move(message_full_ids), Promise(), "on_read_message_poll_votes"); } } class MessageQueryManager::UnpinAllDialogMessagesOnServerLogEvent { public: DialogId dialog_id_; template void store(StorerT &storer) const { td::store(dialog_id_, storer); } template void parse(ParserT &parser) { td::parse(dialog_id_, parser); } }; uint64 MessageQueryManager::save_unpin_all_dialog_messages_on_server_log_event(DialogId dialog_id) { UnpinAllDialogMessagesOnServerLogEvent log_event{dialog_id}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::UnpinAllDialogMessagesOnServer, get_log_event_storer(log_event)); } void MessageQueryManager::unpin_all_dialog_messages_on_server(DialogId dialog_id, uint64 log_event_id, Promise &&promise) { if (log_event_id == 0 && G()->use_message_database()) { log_event_id = save_unpin_all_dialog_messages_on_server_log_event(dialog_id); } AffectedHistoryQuery query = [td = td_](DialogId dialog_id, Promise &&query_promise) { td->create_handler(std::move(query_promise)) ->send(dialog_id, ForumTopicId(), SavedMessagesTopicId()); }; run_affected_history_query_until_complete(dialog_id, std::move(query), true, get_erase_log_event_promise(log_event_id, std::move(promise))); } void MessageQueryManager::on_update_emoji_game_info( telegram_api::object_ptr &&game_info) { EmojiGameInfo emoji_game_info(std::move(game_info)); if (td_->auth_manager_->is_bot()) { return; } if (is_emoji_game_info_inited_ && emoji_game_info == emoji_game_info_) { return; } emoji_game_info_ = std::move(emoji_game_info); emoji_game_info_receive_time_ = Time::now(); is_emoji_game_info_inited_ = true; send_closure(G()->td(), &Td::send_update, emoji_game_info_.get_update_stake_dice_state_object(td_)); } void MessageQueryManager::get_current_state(vector> &updates) const { if (td_->auth_manager_->is_bot()) { return; } if (is_emoji_game_info_inited_ && emoji_game_info_receive_time_ > Time::now() - 60) { updates.push_back(emoji_game_info_.get_update_stake_dice_state_object(td_)); } } void MessageQueryManager::on_binlog_events(vector &&events) { if (G()->close_flag()) { return; } bool have_old_message_database = G()->use_message_database() && !G()->td_db()->was_dialog_db_created(); for (auto &event : events) { CHECK(event.id_ != 0); switch (event.type_) { case LogEvent::HandlerType::BlockMessageSenderFromRepliesOnServer: { BlockMessageSenderFromRepliesOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); block_message_sender_from_replies_on_server(log_event.message_id_, log_event.delete_message_, log_event.delete_all_messages_, log_event.report_spam_, event.id_, Auto()); break; } case LogEvent::HandlerType::DeleteAllCallMessagesOnServer: { DeleteAllCallMessagesOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); delete_all_call_messages_on_server(log_event.revoke_, event.id_, Auto()); break; } case LogEvent::HandlerType::DeleteAllChannelMessagesFromSenderOnServer: { if (!G()->use_chat_info_database()) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } DeleteAllChannelMessagesFromSenderOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto channel_id = log_event.channel_id_; auto sender_dialog_id = log_event.sender_dialog_id_; Dependencies dependencies; dependencies.add(channel_id); dependencies.add_dialog_dependencies(sender_dialog_id); if (!dependencies.resolve_force(td_, "DeleteAllChannelMessagesFromSenderOnServer") || !td_->dialog_manager_->have_input_peer(sender_dialog_id, false, AccessRights::Know)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); continue; } delete_all_channel_messages_by_sender_on_server(channel_id, sender_dialog_id, event.id_, Auto()); break; } case LogEvent::HandlerType::DeleteDialogHistoryOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } DeleteDialogHistoryOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; if (!td_->dialog_manager_->have_dialog_force(dialog_id, "DeleteDialogHistoryOnServerLogEvent") || !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } delete_dialog_history_on_server(dialog_id, log_event.max_message_id_, log_event.remove_from_dialog_list_, log_event.revoke_, true, event.id_, Auto()); break; } case LogEvent::HandlerType::DeleteDialogMessagesByDateOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } DeleteDialogMessagesByDateOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; if (!td_->dialog_manager_->have_dialog_force(dialog_id, "DeleteDialogMessagesByDateOnServerLogEvent") || !td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } delete_dialog_messages_by_date_on_server(dialog_id, log_event.min_date_, log_event.max_date_, log_event.revoke_, event.id_, Auto()); break; } case LogEvent::HandlerType::DeleteMessagesOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } DeleteMessagesOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; if (!td_->dialog_manager_->have_dialog_force(dialog_id, "DeleteMessagesOnServerLogEvent") || !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } td_->messages_manager_->on_messages_deleted(dialog_id, log_event.message_ids_); delete_messages_on_server(dialog_id, std::move(log_event.message_ids_), log_event.revoke_, event.id_, Auto()); break; } case LogEvent::HandlerType::DeleteScheduledMessagesOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } DeleteScheduledMessagesOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; if (!td_->dialog_manager_->have_dialog_force(dialog_id, "DeleteScheduledMessagesOnServerLogEvent") || !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } td_->messages_manager_->on_scheduled_messages_deleted(dialog_id, log_event.message_ids_); delete_scheduled_messages_on_server(dialog_id, std::move(log_event.message_ids_), event.id_, Auto()); break; } case LogEvent::HandlerType::DeleteEphemeralMessageOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } DeleteEphemeralMessageOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; if (!td_->dialog_manager_->have_dialog_force(dialog_id, "DeleteEphemeralMessageOnServerLogEvent") || !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } delete_ephemeral_message_on_server(dialog_id, log_event.receiver_dialog_id_, log_event.ephemeral_message_id_, event.id_, Auto()); break; } case LogEvent::HandlerType::DeleteTopicHistoryOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } DeleteTopicHistoryOnServerLogEvent log_event; if (log_event_parse(log_event, event.get_data()).is_error()) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } auto dialog_id = log_event.dialog_id_; if (!td_->dialog_manager_->have_dialog_force(dialog_id, "DeleteTopicHistoryOnServerLogEvent") || !td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } delete_topic_history_on_server(dialog_id, log_event.forum_topic_id_, event.id_, Auto()); break; } case LogEvent::HandlerType::ReadAllDialogMentionsOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } ReadAllDialogMentionsOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; if (!td_->dialog_manager_->have_dialog_force(dialog_id, "ReadAllDialogMentionsOnServerLogEvent") || !td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } read_all_dialog_mentions_on_server(dialog_id, event.id_, Promise()); break; } case LogEvent::HandlerType::ReadAllDialogReactionsOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } ReadAllDialogReactionsOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; if (!td_->dialog_manager_->have_dialog_force(dialog_id, "ReadAllDialogReactionsOnServerLogEvent") || !td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } read_all_dialog_reactions_on_server(dialog_id, event.id_, Promise()); break; } case LogEvent::HandlerType::ReadAllPollVotesOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } ReadAllPollVotesOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; if (!td_->dialog_manager_->have_dialog_force(dialog_id, "ReadAllPollVotesOnServerLogEvent") || !td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } read_all_dialog_poll_votes_on_server(dialog_id, log_event.forum_topic_id_, event.id_, Promise()); break; } case LogEvent::HandlerType::ReadMessageContentsOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } ReadMessageContentsOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; if (!td_->dialog_manager_->have_dialog_force(dialog_id, "ReadMessageContentsOnServerLogEvent") || !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } read_message_contents_on_server(dialog_id, std::move(log_event.message_ids_), event.id_, Auto()); break; } case LogEvent::HandlerType::UnpinAllDialogMessagesOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } UnpinAllDialogMessagesOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); unpin_all_dialog_messages_on_server(log_event.dialog_id_, event.id_, Auto()); break; } default: LOG(FATAL) << "Unsupported log event type " << event.type_; } } } } // namespace td