// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/telegram/MessagesManager.h" #include "td/telegram/AccountManager.h" #include "td/telegram/AuthManager.h" #include "td/telegram/BackgroundInfo.hpp" #include "td/telegram/BlockListId.h" #include "td/telegram/ChainId.h" #include "td/telegram/ChannelType.h" #include "td/telegram/ChatId.h" #include "td/telegram/ChatReactions.hpp" #include "td/telegram/ContactsManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogActionBar.h" #include "td/telegram/DialogDb.h" #include "td/telegram/DialogFilter.h" #include "td/telegram/DialogFilterManager.h" #include "td/telegram/DialogLocation.h" #include "td/telegram/DialogNotificationSettings.hpp" #include "td/telegram/DownloadManager.h" #include "td/telegram/DraftMessage.h" #include "td/telegram/DraftMessage.hpp" #include "td/telegram/FileReferenceManager.h" #include "td/telegram/files/FileId.hpp" #include "td/telegram/files/FileLocation.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/files/FileType.h" #include "td/telegram/ForumTopicManager.h" #include "td/telegram/Global.h" #include "td/telegram/GroupCallManager.h" #include "td/telegram/InlineQueriesManager.h" #include "td/telegram/InputMessageText.h" #include "td/telegram/LinkManager.h" #include "td/telegram/Location.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/MessageContent.h" #include "td/telegram/MessageDb.h" #include "td/telegram/MessageEntity.h" #include "td/telegram/MessageEntity.hpp" #include "td/telegram/MessageOrigin.hpp" #include "td/telegram/MessageReaction.h" #include "td/telegram/MessageReaction.hpp" #include "td/telegram/MessageReplyInfo.hpp" #include "td/telegram/MessageSender.h" #include "td/telegram/misc.h" #include "td/telegram/net/DcId.h" #include "td/telegram/net/NetQuery.h" #include "td/telegram/NotificationGroupInfo.hpp" #include "td/telegram/NotificationGroupType.h" #include "td/telegram/NotificationManager.h" #include "td/telegram/NotificationObjectId.h" #include "td/telegram/NotificationSettingsManager.h" #include "td/telegram/NotificationSound.h" #include "td/telegram/NotificationType.h" #include "td/telegram/OptionManager.h" #include "td/telegram/PollId.h" #include "td/telegram/PublicDialogType.h" #include "td/telegram/ReactionManager.h" #include "td/telegram/RepliedMessageInfo.hpp" #include "td/telegram/ReplyMarkup.h" #include "td/telegram/ReplyMarkup.hpp" #include "td/telegram/ReportReason.h" #include "td/telegram/SecretChatsManager.h" #include "td/telegram/SponsoredMessageManager.h" #include "td/telegram/StickerPhotoSize.h" #include "td/telegram/StickerType.h" #include "td/telegram/StoryId.h" #include "td/telegram/StoryManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/TopDialogCategory.h" #include "td/telegram/TopDialogManager.h" #include "td/telegram/TranslationManager.h" #include "td/telegram/UpdatesManager.h" #include "td/telegram/Version.h" #include "td/telegram/WebPageId.h" #include "td/db/binlog/BinlogEvent.h" #include "td/db/binlog/BinlogHelper.h" #include "td/db/SqliteKeyValue.h" #include "td/db/SqliteKeyValueAsync.h" #include "td/actor/SleepActor.h" #include "td/utils/algorithm.h" #include "td/utils/format.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/PathView.h" #include "td/utils/Random.h" #include "td/utils/Slice.h" #include "td/utils/SliceBuilder.h" #include "td/utils/Time.h" #include "td/utils/tl_helpers.h" #include "td/utils/utf8.h" #include #include #include #include #include #include namespace td { class GetOnlinesQuery final : public Td::ResultHandler { DialogId dialog_id_; public: void send(DialogId dialog_id) { dialog_id_ = dialog_id; CHECK(dialog_id.get_type() == DialogType::Channel); auto input_peer = td_->messages_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_getOnlines(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()); } auto result = result_ptr.move_as_ok(); td_->messages_manager_->on_update_dialog_online_member_count(dialog_id_, result->onlines_, true); } void on_error(Status status) final { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetOnlinesQuery"); td_->messages_manager_->on_update_dialog_online_member_count(dialog_id_, 0, true); } }; class GetDialogQuery final : public Td::ResultHandler { DialogId dialog_id_; public: void send(DialogId dialog_id) { dialog_id_ = dialog_id; send_query(G()->net_query_creator().create( telegram_api::messages_getPeerDialogs( td_->messages_manager_->get_input_dialog_peers({dialog_id}, AccessRights::Read)), {{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 result = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetDialogQuery: " << to_string(result); td_->contacts_manager_->on_get_users(std::move(result->users_), "GetDialogQuery"); td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetDialogQuery"); td_->messages_manager_->on_get_dialogs(FolderId(), std::move(result->dialogs_), -1, std::move(result->messages_), PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), dialog_id = dialog_id_](Result<> result) { send_closure(actor_id, &MessagesManager::on_get_dialog_query_finished, dialog_id, result.is_error() ? result.move_as_error() : Status::OK()); })); } void on_error(Status status) final { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetDialogQuery"); td_->messages_manager_->on_get_dialog_query_finished(dialog_id_, std::move(status)); } }; class GetPinnedDialogsQuery final : public Td::ResultHandler { FolderId folder_id_; Promise promise_; public: explicit GetPinnedDialogsQuery(Promise &&promise) : promise_(std::move(promise)) { } NetQueryRef send(FolderId folder_id) { folder_id_ = folder_id; auto query = G()->net_query_creator().create(telegram_api::messages_getPinnedDialogs(folder_id.get()), {{folder_id}}); auto result = query.get_weak(); send_query(std::move(query)); return result; } 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(); LOG(INFO) << "Receive pinned chats in " << folder_id_ << ": " << to_string(result); td_->contacts_manager_->on_get_users(std::move(result->users_), "GetPinnedDialogsQuery"); td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetPinnedDialogsQuery"); td_->messages_manager_->on_get_dialogs(folder_id_, std::move(result->dialogs_), -2, std::move(result->messages_), std::move(promise_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetDialogUnreadMarksQuery final : public Td::ResultHandler { public: void send() { send_query(G()->net_query_creator().create(telegram_api::messages_getDialogUnreadMarks())); } 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 results = result_ptr.move_as_ok(); for (auto &result : results) { td_->messages_manager_->on_update_dialog_is_marked_as_unread(DialogId(result), true); } G()->td_db()->get_binlog_pmc()->set("fetched_marks_as_unread", "1"); } void on_error(Status status) final { if (!G()->is_expected_error(status)) { LOG(ERROR) << "Receive error for GetDialogUnreadMarksQuery: " << status; } status.ignore(); } }; 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_->messages_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_->messages_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 GetMessagesQuery final : public Td::ResultHandler { Promise promise_; public: explicit GetMessagesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(vector> &&message_ids) { send_query(G()->net_query_creator().create(telegram_api::messages_getMessages(std::move(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 info = get_messages_info(td_, DialogId(), result_ptr.move_as_ok(), "GetMessagesQuery"); LOG_IF(ERROR, info.is_channel_messages) << "Receive channel messages in GetMessagesQuery"; td_->messages_manager_->on_get_messages(std::move(info.messages), info.is_channel_messages, false, std::move(promise_), "GetMessagesQuery"); } void on_error(Status status) final { if (status.message() == "MESSAGE_IDS_EMPTY") { promise_.set_value(Unit()); return; } promise_.set_error(std::move(status)); } }; class GetChannelMessagesQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; MessageId last_new_message_id_; bool can_be_inaccessible_ = false; public: explicit GetChannelMessagesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, tl_object_ptr &&input_channel, vector> &&message_ids, MessageId last_new_message_id) { channel_id_ = channel_id; last_new_message_id_ = last_new_message_id; can_be_inaccessible_ = message_ids.size() == 1 && message_ids[0]->get_id() != telegram_api::inputMessageID::ID; CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_getMessages(std::move(input_channel), std::move(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 info = get_messages_info(td_, DialogId(channel_id_), result_ptr.move_as_ok(), "GetChannelMessagesQuery"); LOG_IF(ERROR, !info.is_channel_messages) << "Receive ordinary messages in GetChannelMessagesQuery"; // messages with invalid big identifiers can be received as messageEmpty // bots can receive messageEmpty because of their privacy mode if (last_new_message_id_.is_valid() && !td_->auth_manager_->is_bot()) { vector empty_message_ids; for (auto &message : info.messages) { if (message->get_id() == telegram_api::messageEmpty::ID) { auto message_id = MessageId::get_message_id(message, false); if (message_id.is_valid() && message_id <= last_new_message_id_) { empty_message_ids.push_back(message_id); } } } td_->messages_manager_->on_get_empty_messages(DialogId(channel_id_), empty_message_ids); } const char *source = can_be_inaccessible_ ? "GetRepliedChannelMessageQuery" : "GetChannelMessagesQuery"; td_->messages_manager_->get_channel_difference_if_needed( DialogId(channel_id_), std::move(info), PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), source, 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, &MessagesManager::on_get_messages, std::move(info.messages), info.is_channel_messages, false, std::move(promise), source); } }), source); } void on_error(Status status) final { if (status.message() == "MESSAGE_IDS_EMPTY") { promise_.set_value(Unit()); return; } td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelMessagesQuery"); promise_.set_error(std::move(status)); } }; class GetScheduledMessagesQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit GetScheduledMessagesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, tl_object_ptr &&input_peer, vector &&message_ids) { dialog_id_ = dialog_id; CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create( telegram_api::messages_getScheduledMessages(std::move(input_peer), std::move(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 info = get_messages_info(td_, dialog_id_, result_ptr.move_as_ok(), "GetScheduledMessagesQuery"); LOG_IF(ERROR, info.is_channel_messages != (dialog_id_.get_type() == DialogType::Channel)) << "Receive wrong messages constructor in GetScheduledMessagesQuery"; td_->messages_manager_->on_get_messages(std::move(info.messages), info.is_channel_messages, true, std::move(promise_), "GetScheduledMessagesQuery"); } void on_error(Status status) final { if (status.message() == "MESSAGE_IDS_EMPTY") { promise_.set_value(Unit()); return; } td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetScheduledMessagesQuery"); promise_.set_error(std::move(status)); } }; class UpdateDialogPinnedMessageQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; MessageId message_id_; public: explicit UpdateDialogPinnedMessageQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId message_id, bool is_unpin, bool disable_notification, bool only_for_self) { dialog_id_ = dialog_id; message_id_ = message_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { LOG(INFO) << "Can't update pinned message in " << dialog_id; return on_error(Status::Error(400, "Can't update pinned message")); } int32 flags = 0; if (disable_notification) { flags |= telegram_api::messages_updatePinnedMessage::SILENT_MASK; } if (is_unpin) { flags |= telegram_api::messages_updatePinnedMessage::UNPIN_MASK; } if (only_for_self) { flags |= telegram_api::messages_updatePinnedMessage::PM_ONESIDE_MASK; } send_query(G()->net_query_creator().create( telegram_api::messages_updatePinnedMessage(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, 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 UpdateDialogPinnedMessageQuery: " << 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, "UpdateDialogPinnedMessageQuery"); promise_.set_error(std::move(status)); } }; class UnpinAllMessagesQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; MessageId top_thread_message_id_; public: explicit UnpinAllMessagesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId top_thread_message_id) { dialog_id_ = dialog_id; top_thread_message_id_ = top_thread_message_id; auto input_peer = td_->messages_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 (top_thread_message_id.is_valid()) { flags |= telegram_api::messages_unpinAllMessages::TOP_MSG_ID_MASK; } send_query(G()->net_query_creator().create(telegram_api::messages_unpinAllMessages( flags, std::move(input_peer), top_thread_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(AffectedHistory(result_ptr.move_as_ok())); } void on_error(Status status) final { td_->messages_manager_->on_get_message_error(dialog_id_, top_thread_message_id_, status, "UnpinAllMessagesQuery"); 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_->messages_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 ExportChannelMessageLinkQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; MessageId message_id_; bool for_group_ = false; bool ignore_result_ = false; public: explicit ExportChannelMessageLinkQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, MessageId message_id, bool for_group, bool ignore_result) { channel_id_ = channel_id; message_id_ = message_id; for_group_ = for_group; ignore_result_ = ignore_result; auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } int32 flags = 0; if (for_group) { flags |= telegram_api::channels_exportMessageLink::GROUPED_MASK; } send_query(G()->net_query_creator().create( telegram_api::channels_exportMessageLink(flags, false /*ignored*/, false /*ignored*/, std::move(input_channel), 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(DEBUG) << "Receive result for ExportChannelMessageLinkQuery: " << to_string(ptr); if (!ignore_result_) { td_->messages_manager_->on_get_public_message_link({DialogId(channel_id_), message_id_}, for_group_, std::move(ptr->link_), std::move(ptr->html_)); } promise_.set_value(Unit()); } void on_error(Status status) final { if (!ignore_result_) { td_->messages_manager_->on_get_message_error(DialogId(channel_id_), message_id_, status, "ExportChannelMessageLinkQuery"); } promise_.set_error(std::move(status)); } }; class GetDialogListQuery final : public Td::ResultHandler { FolderId folder_id_; Promise promise_; public: explicit GetDialogListQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(FolderId folder_id, int32 offset_date, ServerMessageId offset_message_id, DialogId offset_dialog_id, int32 limit) { folder_id_ = folder_id; auto input_peer = MessagesManager::get_input_peer_force(offset_dialog_id); CHECK(input_peer != nullptr); int32 flags = telegram_api::messages_getDialogs::EXCLUDE_PINNED_MASK | telegram_api::messages_getDialogs::FOLDER_ID_MASK; send_query(G()->net_query_creator().create( telegram_api::messages_getDialogs(flags, false /*ignored*/, folder_id.get(), offset_date, offset_message_id.get(), std::move(input_peer), limit, 0), {{folder_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 chats from chat list of " << folder_id_ << ": " << to_string(ptr); switch (ptr->get_id()) { case telegram_api::messages_dialogs::ID: { auto dialogs = move_tl_object_as(ptr); td_->contacts_manager_->on_get_users(std::move(dialogs->users_), "GetDialogListQuery"); td_->contacts_manager_->on_get_chats(std::move(dialogs->chats_), "GetDialogListQuery"); td_->messages_manager_->on_get_dialogs(folder_id_, std::move(dialogs->dialogs_), narrow_cast(dialogs->dialogs_.size()), std::move(dialogs->messages_), std::move(promise_)); break; } case telegram_api::messages_dialogsSlice::ID: { auto dialogs = move_tl_object_as(ptr); td_->contacts_manager_->on_get_users(std::move(dialogs->users_), "GetDialogListQuery slice"); td_->contacts_manager_->on_get_chats(std::move(dialogs->chats_), "GetDialogListQuery slice"); td_->messages_manager_->on_get_dialogs(folder_id_, std::move(dialogs->dialogs_), max(dialogs->count_, 0), std::move(dialogs->messages_), std::move(promise_)); break; } case telegram_api::messages_dialogsNotModified::ID: LOG(ERROR) << "Receive " << to_string(ptr); return on_error(Status::Error(500, "Receive wrong server response messages.dialogsNotModified")); default: UNREACHABLE(); } } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class SearchPublicDialogsQuery final : public Td::ResultHandler { string query_; public: void send(const string &query) { query_ = query; send_query( G()->net_query_creator().create(telegram_api::contacts_search(query, 20 /* mostly ignored server-side */))); } 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 dialogs = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for SearchPublicDialogsQuery: " << to_string(dialogs); td_->contacts_manager_->on_get_users(std::move(dialogs->users_), "SearchPublicDialogsQuery"); td_->contacts_manager_->on_get_chats(std::move(dialogs->chats_), "SearchPublicDialogsQuery"); td_->messages_manager_->on_get_public_dialogs_search_result(query_, std::move(dialogs->my_results_), std::move(dialogs->results_)); } void on_error(Status status) final { if (!G()->is_expected_error(status)) { if (status.message() == "QUERY_TOO_SHORT") { return td_->messages_manager_->on_get_public_dialogs_search_result(query_, {}, {}); } LOG(ERROR) << "Receive error for SearchPublicDialogsQuery: " << status; } td_->messages_manager_->on_failed_public_dialogs_search(query_, std::move(status)); } }; class GetCommonDialogsQuery final : public Td::ResultHandler { Promise promise_; UserId user_id_; int64 offset_chat_id_ = 0; public: explicit GetCommonDialogsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(UserId user_id, tl_object_ptr &&input_user, int64 offset_chat_id, int32 limit) { user_id_ = user_id; offset_chat_id_ = offset_chat_id; send_query(G()->net_query_creator().create( telegram_api::messages_getCommonChats(std::move(input_user), offset_chat_id, 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 chats_ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetCommonDialogsQuery: " << to_string(chats_ptr); switch (chats_ptr->get_id()) { case telegram_api::messages_chats::ID: { auto chats = move_tl_object_as(chats_ptr); td_->messages_manager_->on_get_common_dialogs(user_id_, offset_chat_id_, std::move(chats->chats_), narrow_cast(chats->chats_.size())); break; } case telegram_api::messages_chatsSlice::ID: { auto chats = move_tl_object_as(chats_ptr); td_->messages_manager_->on_get_common_dialogs(user_id_, offset_chat_id_, std::move(chats->chats_), chats->count_); break; } default: UNREACHABLE(); } promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetBlockedDialogsQuery final : public Td::ResultHandler { Promise> promise_; int32 offset_; int32 limit_; public: explicit GetBlockedDialogsQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(BlockListId block_list_id, int32 offset, int32 limit) { offset_ = offset; limit_ = limit; int32 flags = 0; if (block_list_id == BlockListId::stories()) { flags |= telegram_api::contacts_getBlocked::MY_STORIES_FROM_MASK; } send_query( G()->net_query_creator().create(telegram_api::contacts_getBlocked(flags, false /*ignored*/, offset, limit))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetBlockedDialogsQuery: " << to_string(ptr); switch (ptr->get_id()) { case telegram_api::contacts_blocked::ID: { auto blocked_peers = move_tl_object_as(ptr); td_->contacts_manager_->on_get_users(std::move(blocked_peers->users_), "GetBlockedDialogsQuery"); td_->contacts_manager_->on_get_chats(std::move(blocked_peers->chats_), "GetBlockedDialogsQuery"); td_->messages_manager_->on_get_blocked_dialogs(offset_, limit_, narrow_cast(blocked_peers->blocked_.size()), std::move(blocked_peers->blocked_), std::move(promise_)); break; } case telegram_api::contacts_blockedSlice::ID: { auto blocked_peers = move_tl_object_as(ptr); td_->contacts_manager_->on_get_users(std::move(blocked_peers->users_), "GetBlockedDialogsQuery slice"); td_->contacts_manager_->on_get_chats(std::move(blocked_peers->chats_), "GetBlockedDialogsQuery slice"); td_->messages_manager_->on_get_blocked_dialogs(offset_, limit_, blocked_peers->count_, std::move(blocked_peers->blocked_), std::move(promise_)); break; } default: UNREACHABLE(); } } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class CreateChatQuery final : public Td::ResultHandler { Promise promise_; int64 random_id_; public: explicit CreateChatQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(vector> &&input_users, const string &title, MessageTtl message_ttl, int64 random_id) { random_id_ = random_id; int32 flags = telegram_api::messages_createChat::TTL_PERIOD_MASK; send_query(G()->net_query_creator().create( telegram_api::messages_createChat(flags, std::move(input_users), title, message_ttl.get_input_ttl_period()))); } 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 CreateChatQuery: " << to_string(ptr); td_->messages_manager_->on_create_new_dialog_success(random_id_, std::move(ptr), DialogType::Chat, std::move(promise_)); } void on_error(Status status) final { td_->messages_manager_->on_create_new_dialog_fail(random_id_, std::move(status), std::move(promise_)); } }; class CreateChannelQuery final : public Td::ResultHandler { Promise promise_; int64 random_id_; public: explicit CreateChannelQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const string &title, bool is_forum, bool is_megagroup, const string &about, const DialogLocation &location, bool for_import, MessageTtl message_ttl, int64 random_id) { int32 flags = telegram_api::channels_createChannel::TTL_PERIOD_MASK; if (is_forum) { flags |= telegram_api::channels_createChannel::FORUM_MASK; } else if (is_megagroup) { flags |= telegram_api::channels_createChannel::MEGAGROUP_MASK; } else { flags |= telegram_api::channels_createChannel::BROADCAST_MASK; } if (!location.empty()) { flags |= telegram_api::channels_createChannel::GEO_POINT_MASK; } if (for_import) { flags |= telegram_api::channels_createChannel::FOR_IMPORT_MASK; } random_id_ = random_id; send_query(G()->net_query_creator().create(telegram_api::channels_createChannel( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, title, about, location.get_input_geo_point(), location.get_address(), message_ttl.get_input_ttl_period()))); } 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 CreateChannelQuery: " << to_string(ptr); td_->messages_manager_->on_create_new_dialog_success(random_id_, std::move(ptr), DialogType::Channel, std::move(promise_)); } void on_error(Status status) final { td_->messages_manager_->on_create_new_dialog_fail(random_id_, std::move(status), std::move(promise_)); } }; class CheckHistoryImportQuery final : public Td::ResultHandler { Promise> promise_; public: explicit CheckHistoryImportQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(const string &message_file_head) { send_query(G()->net_query_creator().create(telegram_api::messages_checkHistoryImport(message_file_head))); } 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 CheckHistoryImportQuery: " << to_string(ptr); auto file_type = [&]() -> td_api::object_ptr { if (ptr->pm_) { return td_api::make_object(ptr->title_); } else if (ptr->group_) { return td_api::make_object(ptr->title_); } else { return td_api::make_object(); } }(); promise_.set_value(std::move(file_type)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class CheckHistoryImportPeerQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit CheckHistoryImportPeerQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create(telegram_api::messages_checkHistoryImportPeer(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()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for CheckHistoryImportPeerQuery: " << to_string(ptr); promise_.set_value(std::move(ptr->confirm_text_)); } void on_error(Status status) final { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "CheckHistoryImportPeerQuery"); promise_.set_error(std::move(status)); } }; class InitHistoryImportQuery final : public Td::ResultHandler { Promise promise_; FileId file_id_; DialogId dialog_id_; vector attached_file_ids_; public: explicit InitHistoryImportQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, FileId file_id, tl_object_ptr &&input_file, vector attached_file_ids) { CHECK(input_file != nullptr); file_id_ = file_id; dialog_id_ = dialog_id; attached_file_ids_ = std::move(attached_file_ids); auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create(telegram_api::messages_initHistoryImport( std::move(input_peer), std::move(input_file), narrow_cast(attached_file_ids_.size())))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); td_->messages_manager_->start_import_messages(dialog_id_, ptr->id_, std::move(attached_file_ids_), std::move(promise_)); td_->file_manager_->delete_partial_remote_location(file_id_); } void on_error(Status status) final { if (FileReferenceManager::is_file_reference_error(status)) { LOG(ERROR) << "Receive file reference error " << status; } auto bad_parts = FileManager::get_missing_file_parts(status); if (!bad_parts.empty()) { // TODO reupload the file } td_->file_manager_->delete_partial_remote_location(file_id_); td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "InitHistoryImportQuery"); promise_.set_error(std::move(status)); } }; class UploadImportedMediaQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; int64 import_id_; FileId file_id_; public: explicit UploadImportedMediaQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, int64 import_id, const string &file_name, FileId file_id, tl_object_ptr &&input_media) { CHECK(input_media != nullptr); dialog_id_ = dialog_id; import_id_ = import_id; file_id_ = file_id; auto input_peer = td_->messages_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_uploadImportedMedia( std::move(input_peer), import_id, file_name, 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()); } // ignore response promise_.set_value(Unit()); td_->file_manager_->delete_partial_remote_location(file_id_); } void on_error(Status status) final { if (FileReferenceManager::is_file_reference_error(status)) { LOG(ERROR) << "Receive file reference error " << status; } auto bad_parts = FileManager::get_missing_file_parts(status); if (!bad_parts.empty()) { // TODO reupload the file } td_->file_manager_->delete_partial_remote_location(file_id_); td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "UploadImportedMediaQuery"); promise_.set_error(std::move(status)); } }; class StartImportHistoryQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit StartImportHistoryQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, int64 import_id) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); CHECK(input_peer != nullptr); send_query( G()->net_query_creator().create(telegram_api::messages_startHistoryImport(std::move(input_peer), import_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()); } if (!result_ptr.ok()) { return on_error(Status::Error(500, "Import history returned false")); } promise_.set_value(Unit()); } void on_error(Status status) final { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "StartImportHistoryQuery"); promise_.set_error(std::move(status)); } }; class EditDialogPhotoQuery final : public Td::ResultHandler { Promise promise_; FileId file_id_; bool was_uploaded_ = false; string file_reference_; DialogId dialog_id_; public: explicit EditDialogPhotoQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, FileId file_id, tl_object_ptr &&input_chat_photo) { CHECK(input_chat_photo != nullptr); file_id_ = file_id; was_uploaded_ = FileManager::extract_was_uploaded(input_chat_photo); file_reference_ = FileManager::extract_file_reference(input_chat_photo); dialog_id_ = dialog_id; switch (dialog_id.get_type()) { case DialogType::Chat: send_query(G()->net_query_creator().create( telegram_api::messages_editChatPhoto(dialog_id.get_chat_id().get(), std::move(input_chat_photo)))); break; case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_editPhoto(std::move(input_channel), std::move(input_chat_photo)))); break; } default: UNREACHABLE(); } } void on_result(BufferSlice packet) final { static_assert(std::is_same::value, ""); auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for EditDialogPhotoQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); if (file_id_.is_valid() && was_uploaded_) { td_->file_manager_->delete_partial_remote_location(file_id_); } } void on_error(Status status) final { if (file_id_.is_valid() && was_uploaded_) { td_->file_manager_->delete_partial_remote_location(file_id_); } if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) { if (file_id_.is_valid() && !was_uploaded_) { VLOG(file_references) << "Receive " << status << " for " << file_id_; td_->file_manager_->delete_file_reference(file_id_, file_reference_); td_->messages_manager_->upload_dialog_photo(dialog_id_, file_id_, false, 0.0, false, std::move(promise_), {-1}); return; } else { LOG(ERROR) << "Receive file reference error, but file_id = " << file_id_ << ", was_uploaded = " << was_uploaded_; } } if (status.message() == "CHAT_NOT_MODIFIED") { if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditDialogPhotoQuery"); } promise_.set_error(std::move(status)); } }; class EditDialogTitleQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit EditDialogTitleQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, const string &title) { dialog_id_ = dialog_id; switch (dialog_id.get_type()) { case DialogType::Chat: send_query(G()->net_query_creator().create( telegram_api::messages_editChatTitle(dialog_id.get_chat_id().get(), title))); break; case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create(telegram_api::channels_editTitle(std::move(input_channel), title))); break; } default: UNREACHABLE(); } } void on_result(BufferSlice packet) final { static_assert(std::is_same::value, ""); auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for EditDialogTitleQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditDialogTitleQuery"); } promise_.set_error(std::move(status)); } }; class SetChatAvailableReactionsQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit SetChatAvailableReactionsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, const ChatReactions &available_reactions) { dialog_id_ = dialog_id; auto input_peer = td_->messages_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_setChatAvailableReactions( std::move(input_peer), available_reactions.get_input_chat_reactions()))); } 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 SetChatAvailableReactionsQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetChatAvailableReactionsQuery"); td_->messages_manager_->reload_dialog_info_full(dialog_id_, "SetChatAvailableReactionsQuery"); } promise_.set_error(std::move(status)); } }; class SetChatThemeQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit SetChatThemeQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, const string &theme_name) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create(telegram_api::messages_setChatTheme(std::move(input_peer), theme_name))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for SetChatThemeQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetChatThemeQuery"); } promise_.set_error(std::move(status)); } }; class SetHistoryTtlQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit SetHistoryTtlQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, int32 period) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create(telegram_api::messages_setHistoryTTL(std::move(input_peer), period))); } 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 SetHistoryTtlQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetHistoryTtlQuery"); } promise_.set_error(std::move(status)); } }; class EditChatDefaultBannedRightsQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit EditChatDefaultBannedRightsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, RestrictedRights permissions) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create(telegram_api::messages_editChatDefaultBannedRights( std::move(input_peer), permissions.get_chat_banned_rights()))); } 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 EditChatDefaultBannedRightsQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditChatDefaultBannedRightsQuery"); } promise_.set_error(std::move(status)); } }; class ToggleNoForwardsQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit ToggleNoForwardsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, bool has_protected_content) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create( telegram_api::messages_toggleNoForwards(std::move(input_peer), has_protected_content))); } 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 ToggleNoForwardsQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { promise_.set_value(Unit()); return; } else { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ToggleNoForwardsQuery"); } promise_.set_error(std::move(status)); } }; class ToggleDialogPinQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; bool is_pinned_; public: explicit ToggleDialogPinQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, bool is_pinned) { dialog_id_ = dialog_id; is_pinned_ = is_pinned; auto input_peer = td_->messages_manager_->get_input_dialog_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } int32 flags = 0; if (is_pinned) { flags |= telegram_api::messages_toggleDialogPin::PINNED_MASK; } send_query(G()->net_query_creator().create( telegram_api::messages_toggleDialogPin(flags, false /*ignored*/, std::move(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()); } bool result = result_ptr.ok(); if (!result) { return on_error(Status::Error(400, "Toggle dialog pin failed")); } promise_.set_value(Unit()); } void on_error(Status status) final { if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ToggleDialogPinQuery")) { LOG(ERROR) << "Receive error for ToggleDialogPinQuery: " << status; } td_->messages_manager_->on_update_pinned_dialogs(FolderId::main()); td_->messages_manager_->on_update_pinned_dialogs(FolderId::archive()); promise_.set_error(std::move(status)); } }; class ReorderPinnedDialogsQuery final : public Td::ResultHandler { FolderId folder_id_; Promise promise_; public: explicit ReorderPinnedDialogsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(FolderId folder_id, const vector &dialog_ids) { folder_id_ = folder_id; int32 flags = telegram_api::messages_reorderPinnedDialogs::FORCE_MASK; send_query(G()->net_query_creator().create(telegram_api::messages_reorderPinnedDialogs( flags, true /*ignored*/, folder_id.get(), td_->messages_manager_->get_input_dialog_peers(dialog_ids, AccessRights::Read)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.move_as_ok(); if (!result) { return on_error(Status::Error(400, "Result is false")); } promise_.set_value(Unit()); } void on_error(Status status) final { if (!G()->is_expected_error(status)) { LOG(ERROR) << "Receive error for ReorderPinnedDialogsQuery: " << status; } td_->messages_manager_->on_update_pinned_dialogs(folder_id_); promise_.set_error(std::move(status)); } }; class ToggleViewForumAsMessagesQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; bool view_as_messages_; public: explicit ToggleViewForumAsMessagesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, bool view_as_messages) { dialog_id_ = dialog_id; view_as_messages_ = view_as_messages; CHECK(dialog_id.get_type() == DialogType::Channel); auto input_channel = td_->contacts_manager_->get_input_channel(dialog_id.get_channel_id()); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_toggleViewForumAsMessages(std::move(input_channel), view_as_messages), {{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 ToggleViewForumAsMessagesQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ToggleViewForumAsMessagesQuery")) { LOG(ERROR) << "Receive error for ToggleViewForumAsMessagesQuery: " << status; } if (!G()->close_flag()) { td_->messages_manager_->on_update_dialog_view_as_messages(dialog_id_, !view_as_messages_); } promise_.set_error(std::move(status)); } }; class ToggleDialogUnreadMarkQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; bool is_marked_as_unread_; public: explicit ToggleDialogUnreadMarkQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, bool is_marked_as_unread) { dialog_id_ = dialog_id; is_marked_as_unread_ = is_marked_as_unread; auto input_peer = td_->messages_manager_->get_input_dialog_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } int32 flags = 0; if (is_marked_as_unread) { flags |= telegram_api::messages_markDialogUnread::UNREAD_MASK; } send_query(G()->net_query_creator().create( telegram_api::messages_markDialogUnread(flags, false /*ignored*/, std::move(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()); } bool result = result_ptr.ok(); if (!result) { return on_error(Status::Error(400, "Toggle dialog mark failed")); } promise_.set_value(Unit()); } void on_error(Status status) final { if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ToggleDialogUnreadMarkQuery")) { LOG(ERROR) << "Receive error for ToggleDialogUnreadMarkQuery: " << status; } if (!G()->close_flag()) { td_->messages_manager_->on_update_dialog_is_marked_as_unread(dialog_id_, !is_marked_as_unread_); } promise_.set_error(std::move(status)); } }; class ToggleDialogTranslationsQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; bool is_translatable_; public: explicit ToggleDialogTranslationsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, bool is_translatable) { dialog_id_ = dialog_id; is_translatable_ = is_translatable; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } int32 flags = 0; if (!is_translatable) { flags |= telegram_api::messages_togglePeerTranslations::DISABLED_MASK; } send_query(G()->net_query_creator().create( telegram_api::messages_togglePeerTranslations(flags, false /*ignored*/, std::move(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()); } bool result = result_ptr.ok(); if (!result) { return on_error(Status::Error(400, "Toggle dialog translations failed")); } promise_.set_value(Unit()); } void on_error(Status status) final { if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ToggleDialogTranslationsQuery")) { LOG(ERROR) << "Receive error for ToggleDialogTranslationsQuery: " << status; } if (!G()->close_flag()) { td_->messages_manager_->on_update_dialog_is_translatable(dialog_id_, !is_translatable_); } promise_.set_error(std::move(status)); } }; class ToggleDialogIsBlockedQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit ToggleDialogIsBlockedQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, bool is_blocked, bool is_blocked_for_stories) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Know); CHECK(input_peer != nullptr && input_peer->get_id() != telegram_api::inputPeerEmpty::ID); int32 flags = 0; if (is_blocked_for_stories) { flags |= telegram_api::contacts_block::MY_STORIES_FROM_MASK; } vector chain_ids{{dialog_id, MessageContentType::Photo}, {dialog_id, MessageContentType::Text}}; auto query = is_blocked || is_blocked_for_stories ? G()->net_query_creator().create( telegram_api::contacts_block(flags, false /*ignored*/, std::move(input_peer)), std::move(chain_ids)) : G()->net_query_creator().create( telegram_api::contacts_unblock(flags, false /*ignored*/, std::move(input_peer)), std::move(chain_ids)); send_query(std::move(query)); } 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()); } bool result = result_ptr.ok(); LOG_IF(WARNING, !result) << "Block/Unblock " << dialog_id_ << " has failed"; promise_.set_value(Unit()); } void on_error(Status status) final { if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ToggleDialogIsBlockedQuery")) { LOG(ERROR) << "Receive error for ToggleDialogIsBlockedQuery: " << status; } if (!G()->close_flag()) { td_->messages_manager_->get_dialog_info_full(dialog_id_, Auto(), "ToggleDialogIsBlockedQuery"); td_->messages_manager_->reget_dialog_action_bar(dialog_id_, "ToggleDialogIsBlockedQuery"); } 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_->messages_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_->contacts_manager_->on_get_users(std::move(result->users_), "GetMessagesViewsQuery"); td_->contacts_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(); auto flags = info->flags_; auto view_count = (flags & telegram_api::messageViews::VIEWS_MASK) != 0 ? info->views_ : 0; auto forward_count = (flags & telegram_api::messageViews::FORWARDS_MASK) != 0 ? info->forwards_ : 0; td_->messages_manager_->on_update_message_interaction_info(message_full_id, view_count, forward_count, true, std::move(info->replies_)); } td_->messages_manager_->finish_get_message_views(dialog_id_, message_ids_); } void on_error(Status status) final { if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetMessagesViewsQuery")) { LOG(ERROR) << "Receive error for GetMessagesViewsQuery: " << status; } td_->messages_manager_->finish_get_message_views(dialog_id_, message_ids_); } }; 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_->messages_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_->messages_manager_->finish_get_message_extended_media(dialog_id_, message_ids_); } void on_error(Status status) final { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetExtendedMediaQuery"); td_->messages_manager_->finish_get_message_extended_media(dialog_id_, message_ids_); } }; 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_->contacts_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_->contacts_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 GetDialogMessageByDateQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; int32 date_; int64 random_id_; public: explicit GetDialogMessageByDateQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, int32 date, int64 random_id) { auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return promise_.set_error(Status::Error(400, "Can't access the chat")); } dialog_id_ = dialog_id; date_ = date; random_id_ = random_id; send_query(G()->net_query_creator().create( telegram_api::messages_getHistory(std::move(input_peer), 0, date, -3, 5, 0, 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 info = get_messages_info(td_, dialog_id_, result_ptr.move_as_ok(), "GetDialogMessageByDateQuery"); td_->messages_manager_->get_channel_difference_if_needed( dialog_id_, std::move(info), PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), dialog_id = dialog_id_, date = date_, random_id = random_id_, 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, &MessagesManager::on_get_dialog_message_by_date_success, dialog_id, date, random_id, std::move(info.messages), std::move(promise)); } }), "GetDialogMessageByDateQuery"); } void on_error(Status status) final { if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetDialogMessageByDateQuery")) { LOG(ERROR) << "Receive error for GetDialogMessageByDateQuery in " << dialog_id_ << ": " << status; } promise_.set_error(std::move(status)); td_->messages_manager_->on_get_dialog_message_by_date_fail(random_id_); } }; class GetHistoryQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; MessageId from_message_id_; MessageId old_last_new_message_id_; int32 offset_; int32 limit_; bool from_the_end_; public: explicit GetHistoryQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId from_message_id, MessageId old_last_new_message_id, int32 offset, int32 limit) { auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return promise_.set_error(Status::Error(400, "Can't access the chat")); } CHECK(offset < 0); dialog_id_ = dialog_id; from_message_id_ = from_message_id; old_last_new_message_id_ = old_last_new_message_id; offset_ = offset; limit_ = limit; from_the_end_ = false; send_query(G()->net_query_creator().create(telegram_api::messages_getHistory( std::move(input_peer), from_message_id.get_server_message_id().get(), 0, offset, limit, 0, 0, 0))); } void send_get_from_the_end(DialogId dialog_id, MessageId old_last_new_message_id, int32 limit) { auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return promise_.set_error(Status::Error(400, "Can't access the chat")); } dialog_id_ = dialog_id; old_last_new_message_id_ = old_last_new_message_id; offset_ = 0; limit_ = limit; from_the_end_ = true; send_query(G()->net_query_creator().create( telegram_api::messages_getHistory(std::move(input_peer), 0, 0, 0, limit, 0, 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 info = get_messages_info(td_, dialog_id_, result_ptr.move_as_ok(), "GetHistoryQuery"); td_->messages_manager_->get_channel_difference_if_needed( dialog_id_, std::move(info), PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), dialog_id = dialog_id_, from_message_id = from_message_id_, old_last_new_message_id = old_last_new_message_id_, offset = offset_, limit = limit_, from_the_end = from_the_end_, 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(); // TODO use info.total_count, info.pts send_closure(actor_id, &MessagesManager::on_get_history, dialog_id, from_message_id, old_last_new_message_id, offset, limit, from_the_end, std::move(info.messages), std::move(promise)); } }), "GetHistoryQuery"); } void on_error(Status status) final { if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetHistoryQuery")) { LOG(ERROR) << "Receive error for GetHistoryQuery in " << dialog_id_ << ": " << status; } promise_.set_error(std::move(status)); } }; class ReadHistoryQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit ReadHistoryQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId max_message_id) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create( telegram_api::messages_readHistory(std::move(input_peer), max_message_id.get_server_message_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()); } auto affected_messages = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for ReadHistoryQuery: " << 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 history query"); } promise_.set_value(Unit()); } void on_error(Status status) final { if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReadHistoryQuery")) { LOG(ERROR) << "Receive error for ReadHistoryQuery: " << status; } promise_.set_error(std::move(status)); } }; class ReadChannelHistoryQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; public: explicit ReadChannelHistoryQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, MessageId max_message_id) { channel_id_ = channel_id; auto input_channel = td_->contacts_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_readHistory(std::move(input_channel), max_message_id.get_server_message_id().get()), {{DialogId(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()); } promise_.set_value(Unit()); } void on_error(Status status) final { if (!td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ReadChannelHistoryQuery")) { LOG(ERROR) << "Receive error for ReadChannelHistoryQuery: " << status; } promise_.set_error(std::move(status)); } }; class ReadDiscussionQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit ReadDiscussionQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId top_thread_message_id, MessageId max_message_id) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create( telegram_api::messages_readDiscussion(std::move(input_peer), top_thread_message_id.get_server_message_id().get(), max_message_id.get_server_message_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(Unit()); } void on_error(Status status) final { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReadDiscussionQuery"); promise_.set_error(std::move(status)); } }; class GetSearchResultCalendarQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; MessageId from_message_id_; MessageSearchFilter filter_; int64 random_id_; public: explicit GetSearchResultCalendarQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId from_message_id, MessageSearchFilter filter, int64 random_id) { auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); dialog_id_ = dialog_id; from_message_id_ = from_message_id; filter_ = filter; random_id_ = random_id; send_query(G()->net_query_creator().create(telegram_api::messages_getSearchResultsCalendar( std::move(input_peer), get_input_messages_filter(filter), from_message_id.get_server_message_id().get(), 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 result = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetSearchResultCalendarQuery: " << to_string(result); td_->contacts_manager_->on_get_users(std::move(result->users_), "GetSearchResultCalendarQuery"); td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetSearchResultCalendarQuery"); // unused: inexact:flags.0?true min_date:int min_msg_id:int offset_id_offset:flags.1?int MessagesInfo info; info.messages = std::move(result->messages_); info.total_count = result->count_; info.is_channel_messages = dialog_id_.get_type() == DialogType::Channel; td_->messages_manager_->get_channel_difference_if_needed( dialog_id_, std::move(info), PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), dialog_id = dialog_id_, from_message_id = from_message_id_, filter = filter_, random_id = random_id_, periods = std::move(result->periods_), 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, &MessagesManager::on_get_message_search_result_calendar, dialog_id, from_message_id, filter, random_id, info.total_count, std::move(info.messages), std::move(periods), std::move(promise)); } }), "GetSearchResultCalendarQuery"); } void on_error(Status status) final { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetSearchResultCalendarQuery"); td_->messages_manager_->on_failed_get_message_search_result_calendar(dialog_id_, random_id_); promise_.set_error(std::move(status)); } }; class GetMessagePositionQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; MessageId message_id_; MessageId top_thread_message_id_; MessageSearchFilter filter_; public: explicit GetMessagePositionQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId message_id, MessageSearchFilter filter, MessageId top_thread_message_id) { auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); dialog_id_ = dialog_id; message_id_ = message_id; top_thread_message_id_ = top_thread_message_id; filter_ = filter; if (filter == MessageSearchFilter::Empty && !top_thread_message_id.is_valid()) { 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 (top_thread_message_id.is_valid()) { 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, top_thread_message_id.get_server_message_id().get(), 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 { 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 = move_tl_object_as(messages_ptr); if (messages->messages_.size() != 1 || MessageId::get_message_id(messages->messages_[0], false) != message_id_) { return promise_.set_error(Status::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 = move_tl_object_as(messages_ptr); if (messages->messages_.size() != 1 || MessageId::get_message_id(messages->messages_[0], false) != message_id_) { return promise_.set_error(Status::Error(400, "Message not found by the filter")); } if (messages->offset_id_offset_ <= 0) { LOG(ERROR) << "Failed to receive position for " << message_id_ << " in thread of " << top_thread_message_id_ << " in " << dialog_id_ << " by " << filter_; return promise_.set_error(Status::Error(400, "Message position is unknown")); } return promise_.set_value(std::move(messages->offset_id_offset_)); } case telegram_api::messages_channelMessages::ID: { auto messages = move_tl_object_as(messages_ptr); if (messages->messages_.size() != 1 || MessageId::get_message_id(messages->messages_[0], false) != message_id_) { return promise_.set_error(Status::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(Status::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(Status::Error(500, "Receive invalid response")); default: UNREACHABLE(); break; } } void on_error(Status status) final { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetMessagePositionQuery"); promise_.set_error(std::move(status)); } }; class SearchMessagesQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; string query_; DialogId sender_dialog_id_; MessageId from_message_id_; int32 offset_; int32 limit_; MessageSearchFilter filter_; MessageId top_thread_message_id_; int64 random_id_; bool handle_errors_ = true; public: explicit SearchMessagesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, const string &query, DialogId sender_dialog_id, MessageId from_message_id, int32 offset, int32 limit, MessageSearchFilter filter, MessageId top_thread_message_id, int64 random_id) { auto input_peer = dialog_id.is_valid() ? td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read) : make_tl_object(); CHECK(input_peer != nullptr); dialog_id_ = dialog_id; query_ = query; sender_dialog_id_ = sender_dialog_id; from_message_id_ = from_message_id; offset_ = offset; limit_ = limit; filter_ = filter; top_thread_message_id_ = top_thread_message_id; random_id_ = random_id; auto top_msg_id = top_thread_message_id.get_server_message_id().get(); auto offset_id = from_message_id.get_server_message_id().get(); if (filter == MessageSearchFilter::UnreadMention) { int32 flags = 0; if (top_thread_message_id.is_valid()) { flags |= telegram_api::messages_getUnreadMentions::TOP_MSG_ID_MASK; } send_query(G()->net_query_creator().create(telegram_api::messages_getUnreadMentions( flags, std::move(input_peer), top_msg_id, offset_id, offset, limit, std::numeric_limits::max(), 0))); } else if (filter == MessageSearchFilter::UnreadReaction) { int32 flags = 0; if (top_thread_message_id.is_valid()) { flags |= telegram_api::messages_getUnreadReactions::TOP_MSG_ID_MASK; } send_query(G()->net_query_creator().create(telegram_api::messages_getUnreadReactions( flags, std::move(input_peer), top_msg_id, offset_id, offset, limit, std::numeric_limits::max(), 0))); } else if (top_thread_message_id.is_valid() && query.empty() && !sender_dialog_id.is_valid() && filter == MessageSearchFilter::Empty) { handle_errors_ = dialog_id.get_type() != DialogType::Channel || !td_->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id()); send_query(G()->net_query_creator().create(telegram_api::messages_getReplies( std::move(input_peer), top_msg_id, offset_id, 0, offset, limit, std::numeric_limits::max(), 0, 0))); } else { int32 flags = 0; tl_object_ptr sender_input_peer; if (sender_dialog_id.is_valid()) { flags |= telegram_api::messages_search::FROM_ID_MASK; sender_input_peer = td_->messages_manager_->get_input_peer(sender_dialog_id, AccessRights::Know); CHECK(sender_input_peer != nullptr); } if (top_thread_message_id.is_valid()) { 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), query, std::move(sender_input_peer), top_msg_id, get_input_messages_filter(filter), 0, std::numeric_limits::max(), offset_id, offset, limit, std::numeric_limits::max(), 0, 0))); } } void on_result(BufferSlice packet) final { static_assert(std::is_same::value, ""); static_assert(std::is_same::value, ""); 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 info = get_messages_info(td_, dialog_id_, result_ptr.move_as_ok(), "SearchMessagesQuery"); td_->messages_manager_->get_channel_difference_if_needed( dialog_id_, std::move(info), PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), dialog_id = dialog_id_, query = std::move(query_), sender_dialog_id = sender_dialog_id_, from_message_id = from_message_id_, offset = offset_, limit = limit_, filter = filter_, top_thread_message_id = top_thread_message_id_, random_id = random_id_, 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, &MessagesManager::on_get_dialog_messages_search_result, dialog_id, query, sender_dialog_id, from_message_id, offset, limit, filter, top_thread_message_id, random_id, info.total_count, std::move(info.messages), std::move(promise)); } }), "SearchMessagesQuery"); } void on_error(Status status) final { if (handle_errors_) { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SearchMessagesQuery"); } td_->messages_manager_->on_failed_dialog_messages_search(dialog_id_, random_id_); promise_.set_error(std::move(status)); } }; class GetSearchResultPositionsQuery final : public Td::ResultHandler { Promise> promise_; DialogId dialog_id_; MessageSearchFilter filter_; public: explicit GetSearchResultPositionsQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageSearchFilter filter, MessageId from_message_id, int32 limit) { auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return promise_.set_error(Status::Error(400, "Can't access the chat")); } dialog_id_ = dialog_id; filter_ = filter; send_query(G()->net_query_creator().create( telegram_api::messages_getSearchResultsPositions(std::move(input_peer), get_input_messages_filter(filter), from_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()); } td_->messages_manager_->on_get_dialog_sparse_message_positions(dialog_id_, filter_, result_ptr.move_as_ok(), std::move(promise_)); } void on_error(Status status) final { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetSearchResultPositionsQuery"); promise_.set_error(std::move(status)); } }; class GetSearchCountersQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; MessageSearchFilter filter_; public: explicit GetSearchCountersQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageSearchFilter filter) { auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return promise_.set_error(Status::Error(400, "Can't access the chat")); } dialog_id_ = dialog_id; filter_ = filter; CHECK(filter != MessageSearchFilter::Empty); CHECK(filter != MessageSearchFilter::UnreadMention); CHECK(filter != MessageSearchFilter::FailedToSend); CHECK(filter != MessageSearchFilter::UnreadReaction); vector> filters; filters.push_back(get_input_messages_filter(filter)); int32 flags = 0; send_query(G()->net_query_creator().create( telegram_api::messages_getSearchCounters(flags, std::move(input_peer), 0, std::move(filters)))); } 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(); if (result.size() != 1 || result[0]->filter_->get_id() != get_input_messages_filter(filter_)->get_id()) { LOG(ERROR) << "Receive unexpected response for get message count in " << dialog_id_ << " with filter " << filter_ << ": " << to_string(result); return on_error(Status::Error(500, "Receive wrong response")); } td_->messages_manager_->on_get_dialog_message_count(dialog_id_, filter_, result[0]->count_, std::move(promise_)); } void on_error(Status status) final { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetSearchCountersQuery"); 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_; int64 random_id_; 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, int32 min_date, int32 max_date, int64 random_id) { query_ = query; offset_date_ = offset_date; offset_dialog_id_ = offset_dialog_id; offset_message_id_ = offset_message_id; limit_ = limit; random_id_ = random_id; filter_ = filter; min_date_ = min_date; max_date_ = max_date; auto input_peer = MessagesManager::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; } send_query(G()->net_query_creator().create(telegram_api::messages_searchGlobal( flags, folder_id.get(), 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_->messages_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_, random_id = random_id_, 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, &MessagesManager::on_get_messages_search_result, query, offset_date, offset_dialog_id, offset_message_id, limit, filter, min_date, max_date, random_id, info.total_count, std::move(info.messages), info.next_rate, std::move(promise)); } }), "SearchMessagesGlobalQuery"); } void on_error(Status status) final { td_->messages_manager_->on_failed_messages_search(random_id_); promise_.set_error(std::move(status)); } }; class GetAllScheduledMessagesQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; uint32 generation_; public: explicit GetAllScheduledMessagesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, int64 hash, uint32 generation) { auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); dialog_id_ = dialog_id; generation_ = generation; send_query( G()->net_query_creator().create(telegram_api::messages_getScheduledHistory(std::move(input_peer), hash))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } if (result_ptr.ok()->get_id() == telegram_api::messages_messagesNotModified::ID) { td_->messages_manager_->on_get_scheduled_server_messages(dialog_id_, generation_, Auto(), true); } else { auto info = get_messages_info(td_, dialog_id_, result_ptr.move_as_ok(), "GetAllScheduledMessagesQuery"); td_->messages_manager_->on_get_scheduled_server_messages(dialog_id_, generation_, std::move(info.messages), false); } promise_.set_value(Unit()); } void on_error(Status status) final { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetAllScheduledMessagesQuery"); 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_->messages_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, &MessagesManager::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 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) { auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return on_error(Status::Error(400, "Chat is not accessible")); } dialog_id_ = dialog_id; limit_ = limit; 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_->messages_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, &MessagesManager::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_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetRecentLocationsQuery"); promise_.set_error(std::move(status)); } }; class HidePromoDataQuery final : public Td::ResultHandler { DialogId dialog_id_; public: void send(DialogId dialog_id) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create(telegram_api::help_hidePromoData(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()); } // we are not interested in the result } void on_error(Status status) final { if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "HidePromoDataQuery")) { LOG(ERROR) << "Receive error for sponsored chat hiding: " << 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_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); if (input_peer == nullptr) { return promise_.set_error(Status::Error(400, "Chat is not accessible")); } int32 flags = 0; if (!remove_from_dialog_list) { flags |= telegram_api::messages_deleteHistory::JUST_CLEAR_MASK; } if (revoke) { flags |= telegram_api::messages_deleteHistory::REVOKE_MASK; } send_query(G()->net_query_creator().create( telegram_api::messages_deleteHistory(flags, false /*ignored*/, false /*ignored*/, 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_->messages_manager_->on_get_dialog_error(dialog_id_, status, "DeleteHistoryQuery"); promise_.set_error(std::move(status)); } }; class DeleteTopicHistoryQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; MessageId top_thread_message_id_; public: explicit DeleteTopicHistoryQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId top_thread_message_id) { CHECK(dialog_id.get_type() == DialogType::Channel); channel_id_ = dialog_id.get_channel_id(); top_thread_message_id_ = top_thread_message_id; auto input_channel = td_->contacts_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_deleteTopicHistory( std::move(input_channel), top_thread_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(AffectedHistory(result_ptr.move_as_ok())); } void on_error(Status status) final { td_->messages_manager_->on_get_message_error(DialogId(channel_id_), top_thread_message_id_, status, "DeleteTopicHistoryQuery"); 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_->contacts_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } int32 flags = 0; if (revoke) { flags |= telegram_api::channels_deleteHistory::FOR_EVERYONE_MASK; } send_query(G()->net_query_creator().create(telegram_api::channels_deleteHistory( flags, false /*ignored*/, 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_->contacts_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_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); if (input_peer == nullptr) { return promise_.set_error(Status::Error(400, "Chat is not accessible")); } int32 flags = telegram_api::messages_deleteHistory::JUST_CLEAR_MASK | telegram_api::messages_deleteHistory::MIN_DATE_MASK | telegram_api::messages_deleteHistory::MAX_DATE_MASK; if (revoke) { flags |= telegram_api::messages_deleteHistory::REVOKE_MASK; } send_query(G()->net_query_creator().create(telegram_api::messages_deleteHistory( flags, false /*ignored*/, false /*ignored*/, 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_->messages_manager_->on_get_dialog_error(dialog_id_, status, "DeleteMessagesByDateQuery"); 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) { int32 flags = 0; if (revoke) { flags |= telegram_api::messages_deletePhoneCallHistory::REVOKE_MASK; } send_query( G()->net_query_creator().create(telegram_api::messages_deletePhoneCallHistory(flags, false /*ignored*/))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } 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 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) { int32 flags = 0; if (need_delete_message) { flags |= telegram_api::contacts_blockFromReplies::DELETE_MESSAGE_MASK; } if (need_delete_all_messages) { flags |= telegram_api::contacts_blockFromReplies::DELETE_HISTORY_MASK; } if (report_spam) { flags |= telegram_api::contacts_blockFromReplies::REPORT_SPAM_MASK; } send_query(G()->net_query_creator().create(telegram_api::contacts_blockFromReplies( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, 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 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_->contacts_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return promise_.set_error(Status::Error(400, "Chat is not accessible")); } auto input_peer = td_->messages_manager_->get_input_peer(sender_dialog_id, AccessRights::Know); if (input_peer == nullptr) { return promise_.set_error(Status::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_->contacts_manager_->on_get_channel_error(channel_id_, status, "DeleteParticipantHistoryQuery"); } promise_.set_error(std::move(status)); } }; class ReadMentionsQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit ReadMentionsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId top_thread_message_id) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); if (input_peer == nullptr) { return promise_.set_error(Status::Error(400, "Chat is not accessible")); } int32 flags = 0; if (top_thread_message_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), top_thread_message_id.get_server_message_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_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReadMentionsQuery"); promise_.set_error(std::move(status)); } }; class ReadReactionsQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit ReadReactionsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId top_thread_message_id) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); if (input_peer == nullptr) { return promise_.set_error(Status::Error(400, "Chat is not accessible")); } int32 flags = 0; if (top_thread_message_id.is_valid()) { flags |= telegram_api::messages_readReactions::TOP_MSG_ID_MASK; } send_query(G()->net_query_creator().create( telegram_api::messages_readReactions(flags, std::move(input_peer), top_thread_message_id.get_server_message_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_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReadReactionsQuery"); promise_.set_error(std::move(status)); } }; class SaveDefaultSendAsQuery final : public Td::ResultHandler { Promise promise_; public: explicit SaveDefaultSendAsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, DialogId send_as_dialog_id) { auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); auto send_as_input_peer = td_->messages_manager_->get_input_peer(send_as_dialog_id, AccessRights::Read); CHECK(send_as_input_peer != nullptr); send_query(G()->net_query_creator().create( telegram_api::messages_saveDefaultSendAs(std::move(input_peer), std::move(send_as_input_peer)), {{dialog_id, MessageContentType::Photo}, {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 success = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for SaveDefaultSendAsQuery: " << success; promise_.set_value(Unit()); } void on_error(Status status) final { // td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SaveDefaultSendAsQuery"); promise_.set_error(std::move(status)); } }; class SendMessageQuery final : public Td::ResultHandler { int64 random_id_; DialogId dialog_id_; public: void send(int32 flags, DialogId dialog_id, tl_object_ptr as_input_peer, const MessageInputReplyTo &input_reply_to, MessageId top_thread_message_id, int32 schedule_date, tl_object_ptr &&reply_markup, vector> &&entities, const string &text, bool is_copy, int64 random_id, NetQueryRef *send_query_ref) { random_id_ = random_id; dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { return on_error(Status::Error(400, "Have no write access to the chat")); } auto reply_to = input_reply_to.get_input_reply_to(td_, top_thread_message_id); if (reply_to != nullptr) { flags |= telegram_api::messages_sendMessage::REPLY_TO_MASK; } if (!entities.empty()) { flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_ENTITIES; } if (as_input_peer != nullptr) { flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_SEND_AS; } auto query = G()->net_query_creator().create( telegram_api::messages_sendMessage( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer), std::move(reply_to), text, random_id, std::move(reply_markup), std::move(entities), schedule_date, std::move(as_input_peer)), {{dialog_id, MessageContentType::Text}, {dialog_id, is_copy ? MessageContentType::Photo : MessageContentType::Text}}); if (td_->option_manager_->get_option_boolean("use_quick_ack")) { query->quick_ack_promise_ = PromiseCreator::lambda([random_id](Result result) { if (result.is_ok()) { send_closure(G()->messages_manager(), &MessagesManager::on_send_message_get_quick_ack, random_id); } }); } *send_query_ref = query.get_weak(); send_query(std::move(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()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for SendMessageQuery for " << random_id_ << ": " << to_string(ptr); auto constructor_id = ptr->get_id(); if (constructor_id != telegram_api::updateShortSentMessage::ID) { td_->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(), "SendMessage"); return td_->updates_manager_->on_get_updates(std::move(ptr), Promise()); } auto sent_message = move_tl_object_as(ptr); td_->messages_manager_->on_update_sent_text_message(random_id_, std::move(sent_message->media_), std::move(sent_message->entities_)); auto message_id = MessageId(ServerMessageId(sent_message->id_)); auto ttl_period = sent_message->ttl_period_; auto update = make_tl_object(random_id_, message_id, sent_message->date_, ttl_period); if (dialog_id_.get_type() == DialogType::Channel) { td_->messages_manager_->add_pending_channel_update(dialog_id_, std::move(update), sent_message->pts_, sent_message->pts_count_, Promise(), "send message actor"); return; } td_->updates_manager_->add_pending_pts_update(std::move(update), sent_message->pts_, sent_message->pts_count_, Time::now(), Promise(), "send message actor"); } void on_error(Status status) final { LOG(INFO) << "Receive error for SendMessage: " << status; if (G()->close_flag() && G()->use_message_database()) { // do not send error, message will be re-sent after restart return; } td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendMessageQuery"); td_->messages_manager_->on_send_message_fail(random_id_, std::move(status)); } }; class StartBotQuery final : public Td::ResultHandler { int64 random_id_; DialogId dialog_id_; public: NetQueryRef send(tl_object_ptr bot_input_user, DialogId dialog_id, tl_object_ptr input_peer, const string ¶meter, int64 random_id) { CHECK(bot_input_user != nullptr); CHECK(input_peer != nullptr); random_id_ = random_id; dialog_id_ = dialog_id; auto query = G()->net_query_creator().create( telegram_api::messages_startBot(std::move(bot_input_user), std::move(input_peer), random_id, parameter), {{dialog_id, MessageContentType::Text}, {dialog_id, MessageContentType::Photo}}); if (td_->option_manager_->get_option_boolean("use_quick_ack")) { query->quick_ack_promise_ = PromiseCreator::lambda([random_id](Result result) { if (result.is_ok()) { send_closure(G()->messages_manager(), &MessagesManager::on_send_message_get_quick_ack, random_id); } }); } auto send_query_ref = query.get_weak(); send_query(std::move(query)); return send_query_ref; } 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 StartBotQuery for " << random_id_ << ": " << to_string(ptr); // Result may contain messageActionChatAddUser // td_->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(), "StartBot"); td_->updates_manager_->on_get_updates(std::move(ptr), Promise()); } void on_error(Status status) final { LOG(INFO) << "Receive error for StartBotQuery: " << status; if (G()->close_flag() && G()->use_message_database()) { // do not send error, message should be re-sent after restart return; } td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "StartBotQuery"); td_->messages_manager_->on_send_message_fail(random_id_, std::move(status)); } }; class SendInlineBotResultQuery final : public Td::ResultHandler { int64 random_id_; DialogId dialog_id_; public: NetQueryRef send(int32 flags, DialogId dialog_id, tl_object_ptr as_input_peer, const MessageInputReplyTo &input_reply_to, MessageId top_thread_message_id, int32 schedule_date, int64 random_id, int64 query_id, const string &result_id) { random_id_ = random_id; dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); CHECK(input_peer != nullptr); auto reply_to = input_reply_to.get_input_reply_to(td_, top_thread_message_id); if (reply_to != nullptr) { flags |= telegram_api::messages_sendInlineBotResult::REPLY_TO_MASK; } if (as_input_peer != nullptr) { flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_SEND_AS; } auto query = G()->net_query_creator().create( telegram_api::messages_sendInlineBotResult( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer), std::move(reply_to), random_id, query_id, result_id, schedule_date, std::move(as_input_peer)), {{dialog_id, MessageContentType::Text}, {dialog_id, MessageContentType::Photo}}); auto send_query_ref = query.get_weak(); send_query(std::move(query)); return send_query_ref; } 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 SendInlineBotResultQuery for " << random_id_ << ": " << to_string(ptr); td_->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(), "SendInlineBotResult"); td_->updates_manager_->on_get_updates(std::move(ptr), Promise()); } void on_error(Status status) final { LOG(INFO) << "Receive error for SendInlineBotResultQuery: " << status; if (G()->close_flag() && G()->use_message_database()) { // do not send error, message will be re-sent after restart return; } td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendInlineBotResultQuery"); td_->messages_manager_->on_send_message_fail(random_id_, std::move(status)); } }; class SendMultiMediaQuery final : public Td::ResultHandler { vector file_ids_; vector file_references_; vector random_ids_; DialogId dialog_id_; public: void send(int32 flags, DialogId dialog_id, tl_object_ptr as_input_peer, const MessageInputReplyTo &input_reply_to, MessageId top_thread_message_id, int32 schedule_date, vector &&file_ids, vector> &&input_single_media, bool is_copy) { for (auto &single_media : input_single_media) { random_ids_.push_back(single_media->random_id_); CHECK(FileManager::extract_was_uploaded(single_media->media_) == false); file_references_.push_back(FileManager::extract_file_reference(single_media->media_)); } dialog_id_ = dialog_id; file_ids_ = std::move(file_ids); CHECK(file_ids_.size() == random_ids_.size()); auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { return on_error(Status::Error(400, "Have no write access to the chat")); } auto reply_to = input_reply_to.get_input_reply_to(td_, top_thread_message_id); if (reply_to != nullptr) { flags |= telegram_api::messages_sendMultiMedia::REPLY_TO_MASK; } if (as_input_peer != nullptr) { flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_SEND_AS; } // no quick ack, because file reference errors are very likely to happen send_query(G()->net_query_creator().create( telegram_api::messages_sendMultiMedia(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer), std::move(reply_to), std::move(input_single_media), schedule_date, std::move(as_input_peer)), {{dialog_id, is_copy ? MessageContentType::Text : MessageContentType::Photo}, {dialog_id, MessageContentType::Photo}})); } 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 SendMultiMedia for " << format::as_array(random_ids_) << ": " << to_string(ptr); auto sent_random_ids = UpdatesManager::get_sent_messages_random_ids(ptr.get()); bool is_result_wrong = false; auto sent_random_ids_size = sent_random_ids.size(); for (auto &random_id : random_ids_) { auto it = sent_random_ids.find(random_id); if (it == sent_random_ids.end()) { if (random_ids_.size() == 1) { is_result_wrong = true; } td_->messages_manager_->on_send_message_fail(random_id, Status::Error(400, "Message was not sent")); } else { sent_random_ids.erase(it); } } if (!sent_random_ids.empty()) { is_result_wrong = true; } if (!is_result_wrong) { auto sent_messages = UpdatesManager::get_new_messages(ptr.get()); if (sent_random_ids_size != sent_messages.size()) { is_result_wrong = true; } for (auto &sent_message : sent_messages) { if (DialogId::get_message_dialog_id(sent_message.first) != dialog_id_) { is_result_wrong = true; } } } if (is_result_wrong) { LOG(ERROR) << "Receive wrong result for SendMultiMediaQuery with random_ids " << format::as_array(random_ids_) << " to " << dialog_id_ << ": " << oneline(to_string(ptr)); td_->updates_manager_->schedule_get_difference("Wrong sendMultiMedia result"); } td_->updates_manager_->on_get_updates(std::move(ptr), Promise()); } void on_error(Status status) final { LOG(INFO) << "Receive error for SendMultiMedia: " << status; if (G()->close_flag() && G()->use_message_database()) { // do not send error, message will be re-sent after restart return; } if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) { auto pos = FileReferenceManager::get_file_reference_error_pos(status); if (1 <= pos && pos <= file_ids_.size() && file_ids_[pos - 1].is_valid()) { VLOG(file_references) << "Receive " << status << " for " << file_ids_[pos - 1]; td_->file_manager_->delete_file_reference(file_ids_[pos - 1], file_references_[pos - 1]); td_->messages_manager_->on_send_media_group_file_reference_error(dialog_id_, std::move(random_ids_)); return; } else { LOG(ERROR) << "Receive file reference error " << status << ", but file_ids = " << file_ids_ << ", message_count = " << file_ids_.size(); } } td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendMultiMediaQuery"); for (auto &random_id : random_ids_) { td_->messages_manager_->on_send_message_fail(random_id, status.clone()); } } }; class SendMediaQuery final : public Td::ResultHandler { int64 random_id_ = 0; FileId file_id_; FileId thumbnail_file_id_; DialogId dialog_id_; string file_reference_; bool was_uploaded_ = false; bool was_thumbnail_uploaded_ = false; public: void send(FileId file_id, FileId thumbnail_file_id, int32 flags, DialogId dialog_id, tl_object_ptr as_input_peer, const MessageInputReplyTo &input_reply_to, MessageId top_thread_message_id, int32 schedule_date, tl_object_ptr &&reply_markup, vector> &&entities, const string &text, tl_object_ptr &&input_media, MessageContentType content_type, bool is_copy, int64 random_id, NetQueryRef *send_query_ref) { random_id_ = random_id; file_id_ = file_id; thumbnail_file_id_ = thumbnail_file_id; dialog_id_ = dialog_id; file_reference_ = FileManager::extract_file_reference(input_media); was_uploaded_ = FileManager::extract_was_uploaded(input_media); was_thumbnail_uploaded_ = FileManager::extract_was_thumbnail_uploaded(input_media); auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { return on_error(Status::Error(400, "Have no write access to the chat")); } auto reply_to = input_reply_to.get_input_reply_to(td_, top_thread_message_id); if (reply_to != nullptr) { flags |= telegram_api::messages_sendMedia::REPLY_TO_MASK; } if (!entities.empty()) { flags |= telegram_api::messages_sendMedia::ENTITIES_MASK; } if (as_input_peer != nullptr) { flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_SEND_AS; } auto query = G()->net_query_creator().create( telegram_api::messages_sendMedia( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer), std::move(reply_to), std::move(input_media), text, random_id, std::move(reply_markup), std::move(entities), schedule_date, std::move(as_input_peer)), {{dialog_id, content_type}, {dialog_id, is_copy ? MessageContentType::Text : content_type}}); if (td_->option_manager_->get_option_boolean("use_quick_ack") && was_uploaded_) { query->quick_ack_promise_ = PromiseCreator::lambda([random_id](Result result) { if (result.is_ok()) { send_closure(G()->messages_manager(), &MessagesManager::on_send_message_get_quick_ack, random_id); } }); } *send_query_ref = query.get_weak(); send_query(std::move(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()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for SendMediaQuery for " << random_id_ << ": " << to_string(ptr); td_->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(), "SendMedia"); td_->updates_manager_->on_get_updates(std::move(ptr), Promise()); if (was_thumbnail_uploaded_) { CHECK(thumbnail_file_id_.is_valid()); // always delete partial remote location for the thumbnail, because it can't be reused anyway td_->file_manager_->delete_partial_remote_location(thumbnail_file_id_); } } void on_error(Status status) final { LOG(INFO) << "Receive error for SendMedia: " << status; if (G()->close_flag() && G()->use_message_database()) { // do not send error, message will be re-sent after restart return; } if (was_uploaded_) { if (was_thumbnail_uploaded_) { CHECK(thumbnail_file_id_.is_valid()); // always delete partial remote location for the thumbnail, because it can't be reused anyway td_->file_manager_->delete_partial_remote_location(thumbnail_file_id_); } CHECK(file_id_.is_valid()); auto bad_parts = FileManager::get_missing_file_parts(status); if (!bad_parts.empty()) { td_->messages_manager_->on_send_message_file_parts_missing(random_id_, std::move(bad_parts)); return; } else { if (status.code() != 429 && status.code() < 500 && !G()->close_flag()) { td_->file_manager_->delete_partial_remote_location(file_id_); } } } else if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) { if (file_id_.is_valid() && !was_uploaded_) { VLOG(file_references) << "Receive " << status << " for " << file_id_; td_->file_manager_->delete_file_reference(file_id_, file_reference_); td_->messages_manager_->on_send_message_file_reference_error(random_id_); return; } else { LOG(ERROR) << "Receive file reference error, but file_id = " << file_id_ << ", was_uploaded = " << was_uploaded_; } } td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendMediaQuery"); td_->messages_manager_->on_send_message_fail(random_id_, std::move(status)); } }; class UploadMediaQuery final : public Td::ResultHandler { DialogId dialog_id_; MessageId message_id_; FileId file_id_; FileId thumbnail_file_id_; string file_reference_; bool was_uploaded_ = false; bool was_thumbnail_uploaded_ = false; public: void send(DialogId dialog_id, MessageId message_id, FileId file_id, FileId thumbnail_file_id, tl_object_ptr &&input_media) { CHECK(input_media != nullptr); dialog_id_ = dialog_id; message_id_ = message_id; file_id_ = file_id; thumbnail_file_id_ = thumbnail_file_id; file_reference_ = FileManager::extract_file_reference(input_media); was_uploaded_ = FileManager::extract_was_uploaded(input_media); was_thumbnail_uploaded_ = FileManager::extract_was_thumbnail_uploaded(input_media); auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { return on_error(Status::Error(400, "Have no write access to the chat")); } send_query(G()->net_query_creator().create( telegram_api::messages_uploadMedia(std::move(input_peer), std::move(input_media)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } if (was_thumbnail_uploaded_) { CHECK(thumbnail_file_id_.is_valid()); // always delete partial remote location for the thumbnail, because it can't be reused anyway td_->file_manager_->delete_partial_remote_location(thumbnail_file_id_); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for UploadMediaQuery for " << message_id_ << " in " << dialog_id_ << ": " << to_string(ptr); td_->messages_manager_->on_upload_message_media_success(dialog_id_, message_id_, std::move(ptr)); } void on_error(Status status) final { LOG(INFO) << "Receive error for UploadMediaQuery for " << message_id_ << " in " << dialog_id_ << ": " << status; if (G()->close_flag() && G()->use_message_database()) { // do not send error, message will be re-sent after restart return; } td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "UploadMediaQuery"); if (was_uploaded_) { if (was_thumbnail_uploaded_) { CHECK(thumbnail_file_id_.is_valid()); // always delete partial remote location for the thumbnail, because it can't be reused anyway td_->file_manager_->delete_partial_remote_location(thumbnail_file_id_); } CHECK(file_id_.is_valid()); auto bad_parts = FileManager::get_missing_file_parts(status); if (!bad_parts.empty()) { td_->messages_manager_->on_upload_message_media_file_parts_missing(dialog_id_, message_id_, std::move(bad_parts)); return; } else { if (status.code() != 429 && status.code() < 500 && !G()->close_flag()) { td_->file_manager_->delete_partial_remote_location(file_id_); } } } else if (FileReferenceManager::is_file_reference_error(status)) { LOG(ERROR) << "Receive file reference error for UploadMediaQuery"; } td_->messages_manager_->on_upload_message_media_fail(dialog_id_, message_id_, std::move(status)); } }; class SendScheduledMessageQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit SendScheduledMessageQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId message_id) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Edit); if (input_peer == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } int32 server_message_id = message_id.get_scheduled_server_message_id().get(); send_query(G()->net_query_creator().create( telegram_api::messages_sendScheduledMessages(std::move(input_peer), {server_message_id}), {{dialog_id, MessageContentType::Text}, {dialog_id, MessageContentType::Photo}})); } 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 SendScheduledMessageQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { LOG(INFO) << "Receive error for SendScheduledMessageQuery: " << status; td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendScheduledMessageQuery"); promise_.set_error(std::move(status)); } }; class EditMessageQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; MessageId message_id_; public: explicit EditMessageQuery(Promise &&promise) { promise_ = 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()); } }); } explicit EditMessageQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(int32 flags, DialogId dialog_id, MessageId message_id, const string &text, vector> &&entities, tl_object_ptr &&input_media, bool invert_media, tl_object_ptr &&reply_markup, int32 schedule_date) { dialog_id_ = dialog_id; message_id_ = message_id; if (input_media != nullptr && false) { return on_error(Status::Error(400, "FILE_PART_1_MISSING")); } auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Edit); if (input_peer == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } if (reply_markup != nullptr) { flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_REPLY_MARKUP; } if (!entities.empty()) { flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_ENTITIES; } if (!text.empty()) { flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_MESSAGE; } if (input_media != nullptr) { flags |= telegram_api::messages_editMessage::MEDIA_MASK; } if (invert_media) { flags |= telegram_api::messages_editMessage::INVERT_MEDIA_MASK; } if (schedule_date != 0) { flags |= telegram_api::messages_editMessage::SCHEDULE_DATE_MASK; } int32 server_message_id = schedule_date != 0 ? message_id.get_scheduled_server_message_id().get() : message_id.get_server_message_id().get(); send_query(G()->net_query_creator().create( telegram_api::messages_editMessage(flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), server_message_id, text, std::move(input_media), std::move(reply_markup), std::move(entities), schedule_date), {{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 EditMessageQuery: " << to_string(ptr); auto pts = td_->updates_manager_->get_update_edit_message_pts(ptr.get(), {dialog_id_, message_id_}); auto promise = PromiseCreator::lambda( [promise = std::move(promise_), pts](Result result) mutable { promise.set_value(std::move(pts)); }); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise)); } void on_error(Status status) final { LOG(INFO) << "Receive error for EditMessageQuery: " << status; if (!td_->auth_manager_->is_bot() && status.message() == "MESSAGE_NOT_MODIFIED") { return promise_.set_value(0); } td_->messages_manager_->on_get_message_error(dialog_id_, message_id_, status, "EditMessageQuery"); promise_.set_error(std::move(status)); } }; class EditInlineMessageQuery final : public Td::ResultHandler { Promise promise_; public: explicit EditInlineMessageQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(int32 flags, tl_object_ptr input_bot_inline_message_id, const string &text, vector> &&entities, tl_object_ptr &&input_media, bool invert_media, tl_object_ptr &&reply_markup) { CHECK(input_bot_inline_message_id != nullptr); // file in an inline message can't be uploaded to another datacenter, // so only previously uploaded files or URLs can be used in the InputMedia CHECK(!FileManager::extract_was_uploaded(input_media)); if (reply_markup != nullptr) { flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_REPLY_MARKUP; } if (!entities.empty()) { flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_ENTITIES; } if (!text.empty()) { flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_MESSAGE; } if (input_media != nullptr) { flags |= telegram_api::messages_editInlineBotMessage::MEDIA_MASK; } if (invert_media) { flags |= telegram_api::messages_editInlineBotMessage::INVERT_MEDIA_MASK; } auto dc_id = DcId::internal(InlineQueriesManager::get_inline_message_dc_id(input_bot_inline_message_id)); send_query(G()->net_query_creator().create( telegram_api::messages_editInlineBotMessage( flags, false /*ignored*/, false /*ignored*/, std::move(input_bot_inline_message_id), text, std::move(input_media), std::move(reply_markup), std::move(entities)), {}, dc_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()); } LOG_IF(ERROR, !result_ptr.ok()) << "Receive false in result of editInlineMessage"; promise_.set_value(Unit()); } void on_error(Status status) final { LOG(INFO) << "Receive error for EditInlineMessageQuery: " << status; promise_.set_error(std::move(status)); } }; class ForwardMessagesQuery final : public Td::ResultHandler { Promise promise_; vector random_ids_; DialogId from_dialog_id_; DialogId to_dialog_id_; MessageId message_id_; public: explicit ForwardMessagesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(int32 flags, DialogId to_dialog_id, MessageId top_thread_message_id, DialogId from_dialog_id, tl_object_ptr as_input_peer, const vector &message_ids, vector &&random_ids, int32 schedule_date) { random_ids_ = random_ids; from_dialog_id_ = from_dialog_id; to_dialog_id_ = to_dialog_id; if (message_ids.size() == 1) { message_id_ = message_ids[0]; } auto to_input_peer = td_->messages_manager_->get_input_peer(to_dialog_id, AccessRights::Write); if (to_input_peer == nullptr) { return on_error(Status::Error(400, "Have no write access to the chat")); } auto from_input_peer = td_->messages_manager_->get_input_peer(from_dialog_id, AccessRights::Read); if (from_input_peer == nullptr) { return on_error(Status::Error(400, "Can't access the chat to forward messages from")); } if (as_input_peer != nullptr) { flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_SEND_AS; } if (top_thread_message_id.is_valid()) { flags |= MessagesManager::SEND_MESSAGE_FLAG_IS_FROM_THREAD; } auto query = G()->net_query_creator().create( telegram_api::messages_forwardMessages( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(from_input_peer), MessageId::get_server_message_ids(message_ids), std::move(random_ids), std::move(to_input_peer), top_thread_message_id.get_server_message_id().get(), schedule_date, std::move(as_input_peer)), {{to_dialog_id, MessageContentType::Text}, {to_dialog_id, MessageContentType::Photo}}); if (td_->option_manager_->get_option_boolean("use_quick_ack")) { query->quick_ack_promise_ = PromiseCreator::lambda([random_ids = random_ids_](Result result) { if (result.is_ok()) { for (auto random_id : random_ids) { send_closure(G()->messages_manager(), &MessagesManager::on_send_message_get_quick_ack, random_id); } } }); } send_query(std::move(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()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for ForwardMessagesQuery for " << format::as_array(random_ids_) << ": " << to_string(ptr); auto sent_random_ids = UpdatesManager::get_sent_messages_random_ids(ptr.get()); bool is_result_wrong = false; auto sent_random_ids_size = sent_random_ids.size(); for (auto &random_id : random_ids_) { auto it = sent_random_ids.find(random_id); if (it == sent_random_ids.end()) { if (random_ids_.size() == 1) { is_result_wrong = true; } td_->messages_manager_->on_send_message_fail(random_id, Status::Error(400, "Message was not forwarded")); } else { sent_random_ids.erase(it); } } if (!sent_random_ids.empty()) { is_result_wrong = true; } if (!is_result_wrong) { auto sent_messages = UpdatesManager::get_new_messages(ptr.get()); if (sent_random_ids_size != sent_messages.size()) { is_result_wrong = true; } for (auto &sent_message : sent_messages) { if (DialogId::get_message_dialog_id(sent_message.first) != to_dialog_id_) { is_result_wrong = true; } } } if (is_result_wrong) { LOG(ERROR) << "Receive wrong result for forwarding messages with random_ids " << format::as_array(random_ids_) << " to " << to_dialog_id_ << ": " << oneline(to_string(ptr)); td_->updates_manager_->schedule_get_difference("Wrong forwardMessages result"); } td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { LOG(INFO) << "Receive error for forward messages: " << status; if (G()->close_flag() && G()->use_message_database()) { // do not send error, messages will be re-sent after restart return; } // no on_get_dialog_error call, because two dialogs are involved if (status.code() == 400 && status.message() == CSlice("CHAT_FORWARDS_RESTRICTED")) { td_->contacts_manager_->reload_dialog_info(from_dialog_id_, Promise()); } if (status.code() == 400 && status.message() == CSlice("SEND_AS_PEER_INVALID")) { td_->messages_manager_->reload_dialog_info_full(to_dialog_id_, "SEND_AS_PEER_INVALID"); } if (message_id_.is_valid() && status.message() == CSlice("MESSAGE_ID_INVALID")) { td_->messages_manager_->get_message_from_server({from_dialog_id_, message_id_}, Promise(), "ForwardMessagesQuery"); } for (auto &random_id : random_ids_) { td_->messages_manager_->on_send_message_fail(random_id, status.clone()); } promise_.set_error(std::move(status)); } }; class SendScreenshotNotificationQuery final : public Td::ResultHandler { Promise promise_; int64 random_id_; DialogId dialog_id_; public: explicit SendScreenshotNotificationQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, int64 random_id) { random_id_ = random_id; dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create( telegram_api::messages_sendScreenshotNotification( std::move(input_peer), telegram_api::make_object(0, 0, 0, nullptr, string(), Auto(), 0), random_id), {{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 SendScreenshotNotificationQuery for " << random_id_ << ": " << to_string(ptr); td_->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(), "SendScreenshotNotificationQuery"); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { LOG(INFO) << "Receive error for SendScreenshotNotificationQuery: " << status; if (G()->close_flag() && G()->use_message_database()) { // do not send error, messages will be re-sent after restart return; } td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendScreenshotNotificationQuery"); td_->messages_manager_->on_send_message_fail(random_id_, status.clone()); promise_.set_error(std::move(status)); } }; class SendBotRequestedPeerQuery final : public Td::ResultHandler { Promise promise_; public: explicit SendBotRequestedPeerQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(MessageFullId message_full_id, int32 button_id, vector &&requested_dialog_ids) { auto dialog_id = message_full_id.get_dialog_id(); auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); 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_->messages_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(std::move(input_peer), message_full_id.get_message_id().get_server_message_id().get(), 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 SetTypingQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; int32 generation_ = 0; public: explicit SetTypingQuery(Promise &&promise) : promise_(std::move(promise)) { } NetQueryRef send(DialogId dialog_id, tl_object_ptr &&input_peer, MessageId top_thread_message_id, tl_object_ptr &&action) { dialog_id_ = dialog_id; CHECK(input_peer != nullptr); int32 flags = 0; if (top_thread_message_id.is_valid()) { flags |= telegram_api::messages_setTyping::TOP_MSG_ID_MASK; } auto query = G()->net_query_creator().create(telegram_api::messages_setTyping( flags, std::move(input_peer), top_thread_message_id.get_server_message_id().get(), std::move(action))); query->total_timeout_limit_ = 2; auto result = query.get_weak(); generation_ = result.generation(); send_query(std::move(query)); return result; } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } // ignore result promise_.set_value(Unit()); send_closure_later(G()->messages_manager(), &MessagesManager::after_set_typing_query, dialog_id_, generation_); } void on_error(Status status) final { if (status.code() == NetQuery::Canceled) { return promise_.set_value(Unit()); } if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetTypingQuery")) { LOG(INFO) << "Receive error for set typing: " << status; } promise_.set_error(std::move(status)); send_closure_later(G()->messages_manager(), &MessagesManager::after_set_typing_query, dialog_id_, generation_); } }; 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; int32 flags = 0; if (revoke) { flags |= telegram_api::messages_deleteMessages::REVOKE_MASK; } send_query(G()->net_query_creator().create( telegram_api::messages_deleteMessages(flags, false /*ignored*/, 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); 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(), std::move(promise_), "delete messages query"); } else { promise_.set_value(Unit()); } } void on_error(Status status) final { if (!G()->is_expected_error(status)) { // MESSAGE_DELETE_FORBIDDEN can be returned in group chats when administrator rights was 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_->contacts_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); if (affected_messages->pts_count_ > 0) { td_->messages_manager_->add_pending_channel_update(DialogId(channel_id_), make_tl_object(), affected_messages->pts_, affected_messages->pts_count_, std::move(promise_), "DeleteChannelMessagesQuery"); } else { promise_.set_value(Unit()); } } void on_error(Status status) final { if (!td_->contacts_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_->messages_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_->messages_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 GetPeerSettingsQuery final : public Td::ResultHandler { DialogId dialog_id_; public: void send(DialogId dialog_id) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create(telegram_api::messages_getPeerSettings(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()); } auto ptr = result_ptr.move_as_ok(); td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetPeerSettingsQuery"); td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetPeerSettingsQuery"); td_->messages_manager_->on_get_peer_settings(dialog_id_, std::move(ptr->settings_)); } void on_error(Status status) final { LOG(INFO) << "Receive error for get peer settings: " << status; td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetPeerSettingsQuery"); } }; class UpdatePeerSettingsQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit UpdatePeerSettingsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, bool is_spam_dialog) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return promise_.set_value(Unit()); } if (is_spam_dialog) { send_query(G()->net_query_creator().create(telegram_api::messages_reportSpam(std::move(input_peer)))); } else { send_query(G()->net_query_creator().create(telegram_api::messages_hidePeerSettingsBar(std::move(input_peer)))); } } 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()); } td_->messages_manager_->on_get_peer_settings(dialog_id_, make_tl_object(), true); promise_.set_value(Unit()); } void on_error(Status status) final { LOG(INFO) << "Receive error for update peer settings: " << status; td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "UpdatePeerSettingsQuery"); td_->messages_manager_->reget_dialog_action_bar(dialog_id_, "UpdatePeerSettingsQuery"); promise_.set_error(std::move(status)); } }; class ReportEncryptedSpamQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit ReportEncryptedSpamQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_encrypted_chat(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create(telegram_api::messages_reportEncryptedSpam(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()); } td_->messages_manager_->on_get_peer_settings(dialog_id_, make_tl_object(), true); promise_.set_value(Unit()); } void on_error(Status status) final { LOG(INFO) << "Receive error for report encrypted spam: " << status; td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReportEncryptedSpamQuery"); td_->messages_manager_->reget_dialog_action_bar( DialogId(td_->contacts_manager_->get_secret_chat_user_id(dialog_id_.get_secret_chat_id())), "ReportEncryptedSpamQuery"); promise_.set_error(std::move(status)); } }; class ReportPeerQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit ReportPeerQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, const vector &message_ids, ReportReason &&report_reason) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); if (message_ids.empty()) { send_query(G()->net_query_creator().create(telegram_api::account_reportPeer( std::move(input_peer), report_reason.get_input_report_reason(), report_reason.get_message()))); } else { send_query(G()->net_query_creator().create( telegram_api::messages_report(std::move(input_peer), MessageId::get_server_message_ids(message_ids), report_reason.get_input_report_reason(), report_reason.get_message()))); } } 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()); } bool result = result_ptr.ok(); if (!result) { return on_error(Status::Error(400, "Receive false as result")); } promise_.set_value(Unit()); } void on_error(Status status) final { td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReportPeerQuery"); td_->messages_manager_->reget_dialog_action_bar(dialog_id_, "ReportPeerQuery"); promise_.set_error(std::move(status)); } }; class ReportProfilePhotoQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; FileId file_id_; string file_reference_; ReportReason report_reason_; public: explicit ReportProfilePhotoQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, FileId file_id, tl_object_ptr &&input_photo, ReportReason &&report_reason) { dialog_id_ = dialog_id; file_id_ = file_id; file_reference_ = FileManager::extract_file_reference(input_photo); report_reason_ = std::move(report_reason); auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create(telegram_api::account_reportProfilePhoto( std::move(input_peer), std::move(input_photo), report_reason_.get_input_report_reason(), report_reason_.get_message()))); } 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(); if (!result) { return on_error(Status::Error(400, "Receive false as result")); } promise_.set_value(Unit()); } void on_error(Status status) final { LOG(INFO) << "Receive error for report chat photo: " << status; if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) { VLOG(file_references) << "Receive " << status << " for " << file_id_; td_->file_manager_->delete_file_reference(file_id_, file_reference_); td_->file_reference_manager_->repair_file_reference( file_id_, PromiseCreator::lambda([dialog_id = dialog_id_, file_id = file_id_, report_reason = std::move(report_reason_), promise = std::move(promise_)](Result result) mutable { if (result.is_error()) { LOG(INFO) << "Reported photo " << file_id << " is likely to be deleted"; return promise.set_value(Unit()); } send_closure(G()->messages_manager(), &MessagesManager::report_dialog_photo, dialog_id, file_id, std::move(report_reason), std::move(promise)); })); return; } td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReportProfilePhotoQuery"); promise_.set_error(std::move(status)); } }; class EditPeerFoldersQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit EditPeerFoldersQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, FolderId folder_id) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); vector> input_folder_peers; input_folder_peers.push_back( telegram_api::make_object(std::move(input_peer), folder_id.get())); send_query(G()->net_query_creator().create(telegram_api::folders_editPeerFolders(std::move(input_folder_peers)))); } 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 EditPeerFoldersQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditPeerFoldersQuery")) { LOG(INFO) << "Receive error for EditPeerFoldersQuery: " << status; } // trying to repair folder ID for this dialog td_->messages_manager_->get_dialog_info_full(dialog_id_, Auto(), "EditPeerFoldersQuery"); promise_.set_error(std::move(status)); } }; class GetChannelDifferenceQuery final : public Td::ResultHandler { DialogId dialog_id_; int32 pts_; int32 limit_; public: void send(DialogId dialog_id, tl_object_ptr &&input_channel, int32 pts, int32 limit, bool force) { CHECK(pts >= 0); dialog_id_ = dialog_id; pts_ = pts; limit_ = limit; CHECK(input_channel != nullptr); int32 flags = 0; if (force) { flags |= telegram_api::updates_getChannelDifference::FORCE_MASK; } send_query(G()->net_query_creator().create(telegram_api::updates_getChannelDifference( flags, false /*ignored*/, std::move(input_channel), make_tl_object(), pts, 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()); } td_->messages_manager_->on_get_channel_difference(dialog_id_, pts_, limit_, result_ptr.move_as_ok(), Status::OK()); } void on_error(Status status) final { if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetChannelDifferenceQuery") && status.message() != "PERSISTENT_TIMESTAMP_INVALID") { LOG(ERROR) << "Receive error for GetChannelDifferenceQuery for " << dialog_id_ << " with PTS " << pts_ << " and limit " << limit_ << ": " << status; } td_->messages_manager_->on_get_channel_difference(dialog_id_, pts_, limit_, nullptr, std::move(status)); } }; class ResolveUsernameQuery final : public Td::ResultHandler { Promise promise_; string username_; public: explicit ResolveUsernameQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const string &username) { username_ = username; send_query(G()->net_query_creator().create(telegram_api::contacts_resolveUsername(username))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(DEBUG) << "Receive result for ResolveUsernameQuery: " << to_string(ptr); td_->contacts_manager_->on_get_users(std::move(ptr->users_), "ResolveUsernameQuery"); td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "ResolveUsernameQuery"); td_->messages_manager_->on_resolved_username(username_, DialogId(ptr->peer_)); promise_.set_value(Unit()); } void on_error(Status status) final { if (status.message() == Slice("USERNAME_NOT_OCCUPIED")) { td_->messages_manager_->drop_username(username_); } promise_.set_error(std::move(status)); } }; class MessagesManager::UploadMediaCallback final : public FileManager::UploadCallback { public: void on_upload_ok(FileId file_id, tl_object_ptr input_file) final { send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_media, file_id, std::move(input_file), nullptr); } void on_upload_encrypted_ok(FileId file_id, tl_object_ptr input_file) final { send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_media, file_id, nullptr, std::move(input_file)); } void on_upload_secure_ok(FileId file_id, tl_object_ptr input_file) final { UNREACHABLE(); } void on_upload_error(FileId file_id, Status error) final { send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_media_error, file_id, std::move(error)); } }; class MessagesManager::UploadThumbnailCallback final : public FileManager::UploadCallback { public: void on_upload_ok(FileId file_id, tl_object_ptr input_file) final { send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_thumbnail, file_id, std::move(input_file)); } void on_upload_encrypted_ok(FileId file_id, tl_object_ptr input_file) final { UNREACHABLE(); } void on_upload_secure_ok(FileId file_id, tl_object_ptr input_file) final { UNREACHABLE(); } void on_upload_error(FileId file_id, Status error) final { send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_thumbnail, file_id, nullptr); } }; class MessagesManager::UploadDialogPhotoCallback final : public FileManager::UploadCallback { public: void on_upload_ok(FileId file_id, tl_object_ptr input_file) final { send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_dialog_photo, file_id, std::move(input_file)); } void on_upload_encrypted_ok(FileId file_id, tl_object_ptr input_file) final { UNREACHABLE(); } void on_upload_secure_ok(FileId file_id, tl_object_ptr input_file) final { UNREACHABLE(); } void on_upload_error(FileId file_id, Status error) final { send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_dialog_photo_error, file_id, std::move(error)); } }; class MessagesManager::UploadImportedMessagesCallback final : public FileManager::UploadCallback { public: void on_upload_ok(FileId file_id, tl_object_ptr input_file) final { send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_imported_messages, file_id, std::move(input_file)); } void on_upload_encrypted_ok(FileId file_id, tl_object_ptr input_file) final { UNREACHABLE(); } void on_upload_secure_ok(FileId file_id, tl_object_ptr input_file) final { UNREACHABLE(); } void on_upload_error(FileId file_id, Status error) final { send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_imported_messages_error, file_id, std::move(error)); } }; class MessagesManager::UploadImportedMessageAttachmentCallback final : public FileManager::UploadCallback { public: void on_upload_ok(FileId file_id, tl_object_ptr input_file) final { send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_imported_message_attachment, file_id, std::move(input_file)); } void on_upload_encrypted_ok(FileId file_id, tl_object_ptr input_file) final { UNREACHABLE(); } void on_upload_secure_ok(FileId file_id, tl_object_ptr input_file) final { UNREACHABLE(); } void on_upload_error(FileId file_id, Status error) final { send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_imported_message_attachment_error, file_id, std::move(error)); } }; template void MessagesManager::Message::store(StorerT &storer) const { using td::store; bool has_sender = sender_user_id.is_valid(); bool has_edit_date = edit_date > 0; bool has_random_id = random_id != 0; bool is_forwarded = forward_info != nullptr; bool is_reply_to_random_id = reply_to_random_id != 0; bool is_via_bot = via_bot_user_id.is_valid(); bool has_view_count = view_count > 0; bool has_reply_markup = reply_markup != nullptr; bool has_ttl = ttl != 0; bool has_author_signature = !author_signature.empty(); bool has_media_album_id = media_album_id != 0; bool has_forward_from = is_forwarded && (forward_info->from_dialog_id.is_valid() || forward_info->from_message_id.is_valid()); bool has_send_date = message_id.is_yet_unsent() && send_date != 0; bool has_flags2 = true; bool has_notification_id = notification_id.is_valid(); bool has_send_error_code = send_error_code != 0; bool has_real_forward_from = real_forward_from_dialog_id.is_valid() && real_forward_from_message_id.is_valid(); bool has_legacy_layer = legacy_layer != 0; bool has_restriction_reasons = !restriction_reasons.empty(); bool has_forward_psa_type = is_forwarded && !forward_info->psa_type.empty(); bool has_forward_count = forward_count > 0; bool has_reply_info = !reply_info.is_empty(); bool has_sender_dialog_id = sender_dialog_id.is_valid(); bool has_top_thread_message_id = top_thread_message_id.is_valid(); bool has_thread_draft_message = thread_draft_message != nullptr; bool has_local_thread_message_ids = !local_thread_message_ids.empty(); bool has_linked_top_thread_message_id = linked_top_thread_message_id.is_valid(); bool has_interaction_info_update_date = interaction_info_update_date != 0; bool has_send_emoji = !send_emoji.empty(); bool is_imported = is_forwarded && forward_info->is_imported; bool has_ttl_period = ttl_period != 0; bool has_max_reply_media_timestamp = max_reply_media_timestamp >= 0; bool are_message_media_timestamp_entities_found = true; bool has_flags3 = true; bool has_reactions = reactions != nullptr; bool has_available_reactions_generation = available_reactions_generation != 0; bool has_history_generation = history_generation != 0; bool is_reply_to_story = reply_to_story_full_id != StoryFullId(); bool has_forward_origin = is_forwarded; bool has_input_reply_to = !message_id.is_any_server() && input_reply_to.is_valid(); bool has_replied_message_info = !replied_message_info.is_empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(is_channel_post); STORE_FLAG(is_outgoing); STORE_FLAG(is_failed_to_send); STORE_FLAG(disable_notification); STORE_FLAG(contains_mention); STORE_FLAG(from_background); STORE_FLAG(disable_web_page_preview); STORE_FLAG(clear_draft); STORE_FLAG(false); STORE_FLAG(false); STORE_FLAG(has_sender); STORE_FLAG(has_edit_date); STORE_FLAG(has_random_id); STORE_FLAG(is_forwarded); STORE_FLAG(false); STORE_FLAG(is_reply_to_random_id); STORE_FLAG(is_via_bot); STORE_FLAG(has_view_count); STORE_FLAG(has_reply_markup); STORE_FLAG(has_ttl); STORE_FLAG(has_author_signature); STORE_FLAG(false); STORE_FLAG(had_reply_markup); STORE_FLAG(contains_unread_mention); STORE_FLAG(has_media_album_id); STORE_FLAG(has_forward_from); STORE_FLAG(in_game_share); STORE_FLAG(is_content_secret); STORE_FLAG(has_send_date); STORE_FLAG(has_flags2); END_STORE_FLAGS(); if (has_flags2) { BEGIN_STORE_FLAGS(); STORE_FLAG(has_notification_id); STORE_FLAG(is_mention_notification_disabled); STORE_FLAG(had_forward_info); STORE_FLAG(false); STORE_FLAG(has_send_error_code); STORE_FLAG(hide_via_bot); STORE_FLAG(is_bot_start_message); STORE_FLAG(has_real_forward_from); STORE_FLAG(has_legacy_layer); STORE_FLAG(hide_edit_date); STORE_FLAG(has_restriction_reasons); STORE_FLAG(is_from_scheduled); STORE_FLAG(is_copy); STORE_FLAG(has_forward_psa_type); STORE_FLAG(has_forward_count); STORE_FLAG(has_reply_info); STORE_FLAG(has_sender_dialog_id); STORE_FLAG(false); STORE_FLAG(has_top_thread_message_id); STORE_FLAG(has_thread_draft_message); STORE_FLAG(has_local_thread_message_ids); STORE_FLAG(has_linked_top_thread_message_id); STORE_FLAG(is_pinned); STORE_FLAG(has_interaction_info_update_date); STORE_FLAG(has_send_emoji); STORE_FLAG(is_imported); STORE_FLAG(has_ttl_period); STORE_FLAG(has_max_reply_media_timestamp); STORE_FLAG(are_message_media_timestamp_entities_found); STORE_FLAG(has_flags3); END_STORE_FLAGS(); } if (has_flags3) { BEGIN_STORE_FLAGS(); STORE_FLAG(noforwards); STORE_FLAG(has_explicit_sender); STORE_FLAG(has_reactions); STORE_FLAG(has_available_reactions_generation); STORE_FLAG(update_stickersets_order); STORE_FLAG(is_topic_message); STORE_FLAG(has_history_generation); STORE_FLAG(is_reply_to_story); STORE_FLAG(has_forward_origin); STORE_FLAG(invert_media); STORE_FLAG(has_input_reply_to); STORE_FLAG(has_replied_message_info); END_STORE_FLAGS(); } store(message_id, storer); if (has_sender) { store(sender_user_id, storer); } store(date, storer); if (has_edit_date) { store(edit_date, storer); } if (has_send_date) { store(send_date, storer); } if (has_random_id) { store(random_id, storer); } if (is_forwarded) { store(forward_info->origin, storer); store(forward_info->date, storer); if (has_forward_from) { store(forward_info->from_dialog_id, storer); store(forward_info->from_message_id, storer); } if (has_forward_psa_type) { store(forward_info->psa_type, storer); } } if (has_real_forward_from) { store(real_forward_from_dialog_id, storer); store(real_forward_from_message_id, storer); } if (is_reply_to_random_id) { store(reply_to_random_id, storer); } if (is_via_bot) { store(via_bot_user_id, storer); } if (has_view_count) { store(view_count, storer); } if (has_forward_count) { store(forward_count, storer); } if (has_reply_info) { store(reply_info, storer); } if (has_ttl) { store(ttl, storer); store_time(ttl_expires_at, storer); } if (has_send_error_code) { store(send_error_code, storer); store(send_error_message, storer); if (send_error_code == 429) { store_time(try_resend_at, storer); } } if (has_author_signature) { store(author_signature, storer); } if (has_media_album_id) { store(media_album_id, storer); } if (has_notification_id) { store(notification_id, storer); } if (has_legacy_layer) { store(legacy_layer, storer); } if (has_restriction_reasons) { store(restriction_reasons, storer); } if (has_sender_dialog_id) { store(sender_dialog_id, storer); } if (has_top_thread_message_id) { store(top_thread_message_id, storer); } if (has_thread_draft_message) { store(thread_draft_message, storer); } if (has_local_thread_message_ids) { store(local_thread_message_ids, storer); } if (has_linked_top_thread_message_id) { store(linked_top_thread_message_id, storer); } if (has_interaction_info_update_date) { store(interaction_info_update_date, storer); } if (has_send_emoji) { store(send_emoji, storer); } store_message_content(content.get(), storer); if (has_reply_markup) { store(reply_markup, storer); } if (has_ttl_period) { store(ttl_period, storer); } if (has_max_reply_media_timestamp) { store(max_reply_media_timestamp, storer); } if (has_reactions) { store(reactions, storer); } if (has_available_reactions_generation) { store(available_reactions_generation, storer); } if (has_history_generation) { store(history_generation, storer); } if (is_reply_to_story) { store(reply_to_story_full_id, storer); } if (has_input_reply_to) { store(input_reply_to, storer); } if (has_replied_message_info) { store(replied_message_info, storer); } } // do not forget to resolve message dependencies template void MessagesManager::Message::parse(ParserT &parser) { using td::parse; bool legacy_have_previous; bool legacy_have_next; bool has_sender; bool has_edit_date; bool has_random_id; bool is_forwarded; bool legacy_is_reply; bool is_reply_to_random_id; bool is_via_bot; bool has_view_count; bool has_reply_markup; bool has_ttl; bool has_author_signature; bool legacy_has_forward_author_signature; bool has_media_album_id; bool has_forward_from; bool has_send_date; bool has_flags2; bool has_notification_id = false; bool legacy_has_forward_sender_name = false; bool has_send_error_code = false; bool has_real_forward_from = false; bool has_legacy_layer = false; bool has_restriction_reasons = false; bool has_forward_psa_type = false; bool has_forward_count = false; bool has_reply_info = false; bool has_sender_dialog_id = false; bool legacy_has_reply_in_dialog_id = false; bool has_top_thread_message_id = false; bool has_thread_draft_message = false; bool has_local_thread_message_ids = false; bool has_linked_top_thread_message_id = false; bool has_interaction_info_update_date = false; bool has_send_emoji = false; bool is_imported = false; bool has_ttl_period = false; bool has_max_reply_media_timestamp = false; bool has_flags3 = false; bool has_reactions = false; bool has_available_reactions_generation = false; bool has_history_generation = false; bool is_reply_to_story = false; bool has_forward_origin = false; bool has_input_reply_to = false; bool has_replied_message_info = false; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_channel_post); PARSE_FLAG(is_outgoing); PARSE_FLAG(is_failed_to_send); PARSE_FLAG(disable_notification); PARSE_FLAG(contains_mention); PARSE_FLAG(from_background); PARSE_FLAG(disable_web_page_preview); PARSE_FLAG(clear_draft); PARSE_FLAG(legacy_have_previous); PARSE_FLAG(legacy_have_next); PARSE_FLAG(has_sender); PARSE_FLAG(has_edit_date); PARSE_FLAG(has_random_id); PARSE_FLAG(is_forwarded); PARSE_FLAG(legacy_is_reply); PARSE_FLAG(is_reply_to_random_id); PARSE_FLAG(is_via_bot); PARSE_FLAG(has_view_count); PARSE_FLAG(has_reply_markup); PARSE_FLAG(has_ttl); PARSE_FLAG(has_author_signature); PARSE_FLAG(legacy_has_forward_author_signature); PARSE_FLAG(had_reply_markup); PARSE_FLAG(contains_unread_mention); PARSE_FLAG(has_media_album_id); PARSE_FLAG(has_forward_from); PARSE_FLAG(in_game_share); PARSE_FLAG(is_content_secret); PARSE_FLAG(has_send_date); PARSE_FLAG(has_flags2); END_PARSE_FLAGS(); if (has_flags2) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_notification_id); PARSE_FLAG(is_mention_notification_disabled); PARSE_FLAG(had_forward_info); PARSE_FLAG(legacy_has_forward_sender_name); PARSE_FLAG(has_send_error_code); PARSE_FLAG(hide_via_bot); PARSE_FLAG(is_bot_start_message); PARSE_FLAG(has_real_forward_from); PARSE_FLAG(has_legacy_layer); PARSE_FLAG(hide_edit_date); PARSE_FLAG(has_restriction_reasons); PARSE_FLAG(is_from_scheduled); PARSE_FLAG(is_copy); PARSE_FLAG(has_forward_psa_type); PARSE_FLAG(has_forward_count); PARSE_FLAG(has_reply_info); PARSE_FLAG(has_sender_dialog_id); PARSE_FLAG(legacy_has_reply_in_dialog_id); PARSE_FLAG(has_top_thread_message_id); PARSE_FLAG(has_thread_draft_message); PARSE_FLAG(has_local_thread_message_ids); PARSE_FLAG(has_linked_top_thread_message_id); PARSE_FLAG(is_pinned); PARSE_FLAG(has_interaction_info_update_date); PARSE_FLAG(has_send_emoji); PARSE_FLAG(is_imported); PARSE_FLAG(has_ttl_period); PARSE_FLAG(has_max_reply_media_timestamp); PARSE_FLAG(are_media_timestamp_entities_found); PARSE_FLAG(has_flags3); END_PARSE_FLAGS(); } if (has_flags3) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(noforwards); PARSE_FLAG(has_explicit_sender); PARSE_FLAG(has_reactions); PARSE_FLAG(has_available_reactions_generation); PARSE_FLAG(update_stickersets_order); PARSE_FLAG(is_topic_message); PARSE_FLAG(has_history_generation); PARSE_FLAG(is_reply_to_story); PARSE_FLAG(has_forward_origin); PARSE_FLAG(invert_media); PARSE_FLAG(has_input_reply_to); PARSE_FLAG(has_replied_message_info); END_PARSE_FLAGS(); } parse(message_id, parser); if (!message_id.is_valid() && !message_id.is_valid_scheduled()) { return parser.set_error("Invalid message identifier"); } if (has_sender) { parse(sender_user_id, parser); } parse(date, parser); if (has_edit_date) { parse(edit_date, parser); } if (has_send_date) { if (!message_id.is_yet_unsent()) { return parser.set_error("Unexpected send date"); } parse(send_date, parser); } else if (message_id.is_valid() && message_id.is_yet_unsent()) { send_date = date; // for backward compatibility } if (has_random_id) { parse(random_id, parser); } if (is_forwarded) { forward_info = make_unique(); if (has_forward_origin) { parse(forward_info->origin, parser); parse(forward_info->date, parser); } else { UserId forward_sender_user_id; DialogId forward_sender_dialog_id; MessageId forward_message_id; string forward_author_signature; string forward_sender_name; parse(forward_sender_user_id, parser); parse(forward_info->date, parser); parse(forward_sender_dialog_id, parser); parse(forward_message_id, parser); if (legacy_has_forward_author_signature) { parse(forward_author_signature, parser); } if (legacy_has_forward_sender_name) { parse(forward_sender_name, parser); } forward_info->origin = MessageOrigin(forward_sender_user_id, forward_sender_dialog_id, forward_message_id, std::move(forward_author_signature), std::move(forward_sender_name)); } if (has_forward_from) { parse(forward_info->from_dialog_id, parser); parse(forward_info->from_message_id, parser); } if (has_forward_psa_type) { parse(forward_info->psa_type, parser); } forward_info->is_imported = is_imported; } if (has_real_forward_from) { parse(real_forward_from_dialog_id, parser); parse(real_forward_from_message_id, parser); } MessageId legacy_reply_to_message_id; if (legacy_is_reply) { parse(legacy_reply_to_message_id, parser); } if (is_reply_to_random_id) { parse(reply_to_random_id, parser); } if (is_via_bot) { parse(via_bot_user_id, parser); } if (has_view_count) { parse(view_count, parser); } if (has_forward_count) { parse(forward_count, parser); } if (has_reply_info) { parse(reply_info, parser); } if (has_ttl) { parse(ttl, parser); parse_time(ttl_expires_at, parser); } if (has_send_error_code) { parse(send_error_code, parser); parse(send_error_message, parser); if (send_error_code == 429) { parse_time(try_resend_at, parser); } } if (has_author_signature) { parse(author_signature, parser); } if (has_media_album_id) { parse(media_album_id, parser); } if (has_notification_id) { parse(notification_id, parser); } if (has_legacy_layer) { parse(legacy_layer, parser); } if (has_restriction_reasons) { parse(restriction_reasons, parser); } if (has_sender_dialog_id) { parse(sender_dialog_id, parser); } DialogId legacy_reply_in_dialog_id; if (legacy_has_reply_in_dialog_id) { parse(legacy_reply_in_dialog_id, parser); } if (has_top_thread_message_id) { parse(top_thread_message_id, parser); } if (has_thread_draft_message) { parse(thread_draft_message, parser); } if (has_local_thread_message_ids) { parse(local_thread_message_ids, parser); } if (has_linked_top_thread_message_id) { parse(linked_top_thread_message_id, parser); } if (has_interaction_info_update_date) { parse(interaction_info_update_date, parser); } if (has_send_emoji) { parse(send_emoji, parser); } parse_message_content(content, parser); if (has_reply_markup) { parse(reply_markup, parser); } if (has_ttl_period) { parse(ttl_period, parser); } if (has_max_reply_media_timestamp) { parse(max_reply_media_timestamp, parser); } if (has_reactions) { parse(reactions, parser); } if (has_available_reactions_generation) { parse(available_reactions_generation, parser); } if (has_history_generation) { parse(history_generation, parser); } if (is_reply_to_story) { parse(reply_to_story_full_id, parser); } if (has_input_reply_to) { parse(input_reply_to, parser); } else if (!message_id.is_any_server()) { if (reply_to_story_full_id.is_valid()) { input_reply_to = MessageInputReplyTo(reply_to_story_full_id); } else if (legacy_reply_to_message_id.is_valid()) { input_reply_to = MessageInputReplyTo{legacy_reply_to_message_id, DialogId(), FormattedText(), 0}; } } if (has_replied_message_info) { parse(replied_message_info, parser); } else { replied_message_info = RepliedMessageInfo::legacy(legacy_reply_to_message_id, legacy_reply_in_dialog_id); } CHECK(content != nullptr); is_content_secret |= is_secret_message_content(ttl, content->get_type()); // repair is_content_secret for old messages if (hide_edit_date && content->get_type() == MessageContentType::LiveLocation) { hide_edit_date = false; } } template void MessagesManager::Dialog::store(StorerT &storer) const { using td::store; const Message *last_database_message = nullptr; if (last_database_message_id.is_valid()) { last_database_message = get_message(this, last_database_message_id); } auto dialog_type = dialog_id.get_type(); bool has_draft_message = draft_message != nullptr; bool has_last_database_message = last_database_message != nullptr; bool has_first_database_message_id = first_database_message_id.is_valid(); bool has_first_database_message_id_by_index = true; bool has_message_count_by_index = true; bool has_client_data = !client_data.empty(); bool has_last_read_all_mentions_message_id = last_read_all_mentions_message_id.is_valid(); bool has_max_unavailable_message_id = max_unavailable_message_id.is_valid(); bool has_local_unread_count = local_unread_count != 0; bool has_deleted_last_message = delete_last_message_date > 0; bool has_last_clear_history_message_id = last_clear_history_message_id.is_valid(); bool has_last_database_message_id = !has_last_database_message && last_database_message_id.is_valid(); bool has_message_notification_group = notification_info != nullptr && notification_info->message_notification_group_.is_active(); bool has_mention_notification_group = notification_info != nullptr && notification_info->mention_notification_group_.is_active(); bool has_new_secret_chat_notification_id = notification_info != nullptr && notification_info->new_secret_chat_notification_id_.is_valid(); bool has_pinned_message_notification = notification_info != nullptr && notification_info->pinned_message_notification_message_id_.is_valid(); bool has_last_pinned_message_id = last_pinned_message_id.is_valid(); bool has_flags2 = true; bool has_max_push_notification_message_id = notification_info != nullptr && notification_info->max_push_notification_message_id_.is_valid() && notification_info->max_push_notification_message_id_ > last_new_message_id; bool has_folder_id = folder_id != FolderId(); bool has_pending_read_channel_inbox = pending_read_channel_inbox_pts != 0; bool has_last_yet_unsent_message = last_message_id.is_valid() && last_message_id.is_yet_unsent(); bool has_active_group_call_id = active_group_call_id.is_valid(); bool has_message_ttl = !message_ttl.is_empty(); bool has_default_join_group_call_as_dialog_id = default_join_group_call_as_dialog_id.is_valid(); bool store_has_bots = dialog_type == DialogType::Chat || dialog_type == DialogType::Channel; bool has_theme_name = !theme_name.empty(); bool has_flags3 = true; bool has_pending_join_requests = pending_join_request_count != 0; bool has_action_bar = action_bar != nullptr; bool has_default_send_message_as_dialog_id = default_send_message_as_dialog_id.is_valid(); bool has_legacy_available_reactions = false; bool has_available_reactions_generation = available_reactions_generation != 0; bool has_have_full_history_source = have_full_history && have_full_history_source != 0; bool has_available_reactions = !available_reactions.empty(); bool has_history_generation = history_generation != 0; bool has_background = background_info.is_valid(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_draft_message); STORE_FLAG(has_last_database_message); STORE_FLAG(false); // legacy_know_can_report_spam STORE_FLAG(false); // action_bar->can_report_spam STORE_FLAG(has_first_database_message_id); STORE_FLAG(false); // legacy_is_pinned STORE_FLAG(has_first_database_message_id_by_index); STORE_FLAG(has_message_count_by_index); STORE_FLAG(has_client_data); STORE_FLAG(need_restore_reply_markup); STORE_FLAG(have_full_history); STORE_FLAG(has_last_read_all_mentions_message_id); STORE_FLAG(has_max_unavailable_message_id); STORE_FLAG(is_last_read_inbox_message_id_inited); STORE_FLAG(is_last_read_outbox_message_id_inited); STORE_FLAG(has_local_unread_count); STORE_FLAG(has_deleted_last_message); STORE_FLAG(has_last_clear_history_message_id); STORE_FLAG(is_last_message_deleted_locally); STORE_FLAG(has_contact_registered_message); STORE_FLAG(has_last_database_message_id); STORE_FLAG(need_repair_server_unread_count); STORE_FLAG(is_marked_as_unread); STORE_FLAG(has_message_notification_group); STORE_FLAG(has_mention_notification_group); STORE_FLAG(has_new_secret_chat_notification_id); STORE_FLAG(has_pinned_message_notification); STORE_FLAG(has_last_pinned_message_id); STORE_FLAG(is_last_pinned_message_id_inited); STORE_FLAG(has_flags2); END_STORE_FLAGS(); store(dialog_id, storer); // must be stored at offset 4 if (has_flags2) { BEGIN_STORE_FLAGS(); STORE_FLAG(has_max_push_notification_message_id); STORE_FLAG(has_folder_id); STORE_FLAG(is_folder_id_inited); STORE_FLAG(has_pending_read_channel_inbox); STORE_FLAG(know_action_bar); STORE_FLAG(false); // action_bar->can_add_contact STORE_FLAG(false); // action_bar->can_block_user STORE_FLAG(false); // action_bar->can_share_phone_number STORE_FLAG(false); // action_bar->can_report_location STORE_FLAG(has_scheduled_server_messages); STORE_FLAG(has_scheduled_database_messages); STORE_FLAG(need_repair_channel_server_unread_count); STORE_FLAG(false); // action_bar->can_unarchive STORE_FLAG(false); // action_bar_has_distance STORE_FLAG(has_outgoing_messages); STORE_FLAG(has_last_yet_unsent_message); STORE_FLAG(is_blocked); STORE_FLAG(is_is_blocked_inited); STORE_FLAG(has_active_group_call); STORE_FLAG(is_group_call_empty); STORE_FLAG(has_active_group_call_id); STORE_FLAG(false); // action_bar->can_invite_members STORE_FLAG(has_message_ttl); STORE_FLAG(is_message_ttl_inited); STORE_FLAG(has_default_join_group_call_as_dialog_id); STORE_FLAG(store_has_bots ? has_bots : false); STORE_FLAG(store_has_bots ? is_has_bots_inited : false); STORE_FLAG(is_theme_name_inited); STORE_FLAG(has_theme_name); STORE_FLAG(has_flags3); END_STORE_FLAGS(); } if (has_flags3) { BEGIN_STORE_FLAGS(); STORE_FLAG(has_pending_join_requests); STORE_FLAG(need_repair_action_bar); STORE_FLAG(has_action_bar); STORE_FLAG(has_default_send_message_as_dialog_id); STORE_FLAG(need_drop_default_send_message_as_dialog_id); STORE_FLAG(has_legacy_available_reactions); STORE_FLAG(is_available_reactions_inited); STORE_FLAG(has_available_reactions_generation); STORE_FLAG(has_have_full_history_source); STORE_FLAG(has_available_reactions); STORE_FLAG(has_history_generation); STORE_FLAG(need_repair_unread_reaction_count); STORE_FLAG(is_translatable); STORE_FLAG(need_repair_unread_mention_count); STORE_FLAG(is_background_inited); STORE_FLAG(has_background); STORE_FLAG(is_blocked_for_stories); STORE_FLAG(is_is_blocked_for_stories_inited); STORE_FLAG(view_as_messages); STORE_FLAG(is_view_as_messages_inited); STORE_FLAG(is_forum); END_STORE_FLAGS(); } store(last_new_message_id, storer); store(server_unread_count, storer); if (has_local_unread_count) { store(local_unread_count, storer); } store(last_read_inbox_message_id, storer); store(last_read_outbox_message_id, storer); store(reply_markup_message_id, storer); store(notification_settings, storer); if (has_draft_message) { store(draft_message, storer); } store(last_clear_history_date, storer); store(order, storer); if (has_last_database_message) { store(*last_database_message, storer); } if (has_first_database_message_id) { store(first_database_message_id, storer); } if (has_deleted_last_message) { store(delete_last_message_date, storer); store(deleted_last_message_id, storer); } if (has_last_clear_history_message_id) { store(last_clear_history_message_id, storer); } if (has_first_database_message_id_by_index) { store(static_cast(first_database_message_id_by_index.size()), storer); for (auto first_message_id : first_database_message_id_by_index) { store(first_message_id, storer); } } if (has_message_count_by_index) { store(static_cast(message_count_by_index.size()), storer); for (auto message_count : message_count_by_index) { store(message_count, storer); } } if (has_client_data) { store(client_data, storer); } if (has_last_read_all_mentions_message_id) { store(last_read_all_mentions_message_id, storer); } if (has_max_unavailable_message_id) { store(max_unavailable_message_id, storer); } if (has_last_database_message_id) { store(last_database_message_id, storer); } if (has_message_notification_group) { store(notification_info->message_notification_group_, storer); } if (has_mention_notification_group) { store(notification_info->mention_notification_group_, storer); } if (has_new_secret_chat_notification_id) { store(notification_info->new_secret_chat_notification_id_, storer); } if (has_pinned_message_notification) { store(notification_info->pinned_message_notification_message_id_, storer); } if (has_last_pinned_message_id) { store(last_pinned_message_id, storer); } if (has_max_push_notification_message_id) { store(notification_info->max_push_notification_message_id_, storer); } if (has_folder_id) { store(folder_id, storer); } if (has_pending_read_channel_inbox) { store(pending_read_channel_inbox_pts, storer); store(pending_read_channel_inbox_max_message_id, storer); store(pending_read_channel_inbox_server_unread_count, storer); } if (has_active_group_call_id) { store(active_group_call_id, storer); } if (has_message_ttl) { store(message_ttl, storer); } if (has_default_join_group_call_as_dialog_id) { store(default_join_group_call_as_dialog_id, storer); } if (has_theme_name) { store(theme_name, storer); } if (has_pending_join_requests) { store(pending_join_request_count, storer); store(pending_join_request_user_ids, storer); } if (has_action_bar) { store(action_bar, storer); } if (has_default_send_message_as_dialog_id) { store(default_send_message_as_dialog_id, storer); } if (has_available_reactions) { store(available_reactions, storer); } if (has_available_reactions_generation) { store(available_reactions_generation, storer); } if (has_have_full_history_source) { store(have_full_history_source, storer); } if (has_history_generation) { store(history_generation, storer); } if (has_background) { store(background_info, storer); } } // do not forget to resolve dialog dependencies including dependencies of last_message template void MessagesManager::Dialog::parse(ParserT &parser) { using td::parse; bool has_draft_message; bool has_last_database_message; bool legacy_know_can_report_spam; bool has_first_database_message_id; bool legacy_is_pinned; bool has_first_database_message_id_by_index; bool has_message_count_by_index; bool has_client_data; bool has_last_read_all_mentions_message_id; bool has_max_unavailable_message_id; bool has_local_unread_count; bool has_deleted_last_message; bool has_last_clear_history_message_id; bool has_last_database_message_id; bool has_message_notification_group; bool has_mention_notification_group; bool has_new_secret_chat_notification_id; bool has_pinned_message_notification; bool has_last_pinned_message_id; bool has_flags2; bool has_max_push_notification_message_id = false; bool has_folder_id = false; bool has_pending_read_channel_inbox = false; bool has_active_group_call_id = false; bool has_message_ttl = false; bool has_default_join_group_call_as_dialog_id = false; bool has_theme_name = false; bool has_flags3 = false; bool has_pending_join_requests = false; bool action_bar_can_report_spam = false; bool action_bar_can_add_contact = false; bool action_bar_can_block_user = false; bool action_bar_can_share_phone_number = false; bool action_bar_can_report_location = false; bool action_bar_can_unarchive = false; bool action_bar_has_distance = false; bool action_bar_can_invite_members = false; bool has_action_bar = false; bool has_default_send_message_as_dialog_id = false; bool has_legacy_available_reactions = false; bool has_available_reactions_generation = false; bool has_have_full_history_source = false; bool has_available_reactions = false; bool has_history_generation = false; bool has_background = false; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_draft_message); PARSE_FLAG(has_last_database_message); PARSE_FLAG(legacy_know_can_report_spam); PARSE_FLAG(action_bar_can_report_spam); PARSE_FLAG(has_first_database_message_id); PARSE_FLAG(legacy_is_pinned); PARSE_FLAG(has_first_database_message_id_by_index); PARSE_FLAG(has_message_count_by_index); PARSE_FLAG(has_client_data); PARSE_FLAG(need_restore_reply_markup); PARSE_FLAG(have_full_history); PARSE_FLAG(has_last_read_all_mentions_message_id); PARSE_FLAG(has_max_unavailable_message_id); PARSE_FLAG(is_last_read_inbox_message_id_inited); PARSE_FLAG(is_last_read_outbox_message_id_inited); PARSE_FLAG(has_local_unread_count); PARSE_FLAG(has_deleted_last_message); PARSE_FLAG(has_last_clear_history_message_id); PARSE_FLAG(is_last_message_deleted_locally); PARSE_FLAG(has_contact_registered_message); PARSE_FLAG(has_last_database_message_id); PARSE_FLAG(need_repair_server_unread_count); PARSE_FLAG(is_marked_as_unread); PARSE_FLAG(has_message_notification_group); PARSE_FLAG(has_mention_notification_group); PARSE_FLAG(has_new_secret_chat_notification_id); PARSE_FLAG(has_pinned_message_notification); PARSE_FLAG(has_last_pinned_message_id); PARSE_FLAG(is_last_pinned_message_id_inited); PARSE_FLAG(has_flags2); END_PARSE_FLAGS(); parse(dialog_id, parser); // must be stored at offset 4 if (has_flags2) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_max_push_notification_message_id); PARSE_FLAG(has_folder_id); PARSE_FLAG(is_folder_id_inited); PARSE_FLAG(has_pending_read_channel_inbox); PARSE_FLAG(know_action_bar); PARSE_FLAG(action_bar_can_add_contact); PARSE_FLAG(action_bar_can_block_user); PARSE_FLAG(action_bar_can_share_phone_number); PARSE_FLAG(action_bar_can_report_location); PARSE_FLAG(has_scheduled_server_messages); PARSE_FLAG(has_scheduled_database_messages); PARSE_FLAG(need_repair_channel_server_unread_count); PARSE_FLAG(action_bar_can_unarchive); PARSE_FLAG(action_bar_has_distance); PARSE_FLAG(has_outgoing_messages); PARSE_FLAG(had_last_yet_unsent_message); PARSE_FLAG(is_blocked); PARSE_FLAG(is_is_blocked_inited); PARSE_FLAG(has_active_group_call); PARSE_FLAG(is_group_call_empty); PARSE_FLAG(has_active_group_call_id); PARSE_FLAG(action_bar_can_invite_members); PARSE_FLAG(has_message_ttl); PARSE_FLAG(is_message_ttl_inited); PARSE_FLAG(has_default_join_group_call_as_dialog_id); PARSE_FLAG(has_bots); PARSE_FLAG(is_has_bots_inited); PARSE_FLAG(is_theme_name_inited); PARSE_FLAG(has_theme_name); PARSE_FLAG(has_flags3); END_PARSE_FLAGS(); } else { is_folder_id_inited = false; has_scheduled_server_messages = false; has_scheduled_database_messages = false; need_repair_channel_server_unread_count = false; has_outgoing_messages = false; had_last_yet_unsent_message = false; is_blocked = false; is_is_blocked_inited = false; has_active_group_call = false; is_group_call_empty = false; is_message_ttl_inited = false; has_bots = false; is_has_bots_inited = false; is_theme_name_inited = false; } if (has_flags3) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_pending_join_requests); PARSE_FLAG(need_repair_action_bar); PARSE_FLAG(has_action_bar); PARSE_FLAG(has_default_send_message_as_dialog_id); PARSE_FLAG(need_drop_default_send_message_as_dialog_id); PARSE_FLAG(has_legacy_available_reactions); PARSE_FLAG(is_available_reactions_inited); PARSE_FLAG(has_available_reactions_generation); PARSE_FLAG(has_have_full_history_source); PARSE_FLAG(has_available_reactions); PARSE_FLAG(has_history_generation); PARSE_FLAG(need_repair_unread_reaction_count); PARSE_FLAG(is_translatable); PARSE_FLAG(need_repair_unread_mention_count); PARSE_FLAG(is_background_inited); PARSE_FLAG(has_background); PARSE_FLAG(is_blocked_for_stories); PARSE_FLAG(is_is_blocked_for_stories_inited); PARSE_FLAG(view_as_messages); PARSE_FLAG(is_view_as_messages_inited); PARSE_FLAG(is_forum); END_PARSE_FLAGS(); } else { need_repair_action_bar = false; is_available_reactions_inited = false; is_background_inited = false; is_blocked_for_stories = false; is_is_blocked_for_stories_inited = false; view_as_messages = false; is_view_as_messages_inited = false; is_forum = false; } parse(last_new_message_id, parser); parse(server_unread_count, parser); if (has_local_unread_count) { parse(local_unread_count, parser); } parse(last_read_inbox_message_id, parser); if (last_read_inbox_message_id.is_valid()) { is_last_read_inbox_message_id_inited = true; } parse(last_read_outbox_message_id, parser); if (last_read_outbox_message_id.is_valid()) { is_last_read_outbox_message_id_inited = true; } parse(reply_markup_message_id, parser); parse(notification_settings, parser); if (has_draft_message) { parse(draft_message, parser); } parse(last_clear_history_date, parser); parse(order, parser); if (has_last_database_message) { unique_ptr last_database_message; parse(last_database_message, parser); auto loaded_last_database_message_id = last_database_message->message_id; if (loaded_last_database_message_id.is_valid()) { messages.set(loaded_last_database_message_id, std::move(last_database_message)); } } if (has_first_database_message_id) { parse(first_database_message_id, parser); } if (legacy_is_pinned) { int64 legacy_pinned_order; parse(legacy_pinned_order, parser); } if (has_deleted_last_message) { parse(delete_last_message_date, parser); parse(deleted_last_message_id, parser); } if (has_last_clear_history_message_id) { parse(last_clear_history_message_id, parser); } if (has_first_database_message_id_by_index) { int32 size; parse(size, parser); if (static_cast(size) > first_database_message_id_by_index.size()) { // the log event is broken return parser.set_error("Wrong first_database_message_id_by_index table size"); } for (int32 i = 0; i < size; i++) { parse(first_database_message_id_by_index[i], parser); } } if (has_message_count_by_index) { int32 size; parse(size, parser); if (static_cast(size) > message_count_by_index.size()) { // the log event is broken return parser.set_error("Wrong message_count_by_index table size"); } for (int32 i = 0; i < size; i++) { parse(message_count_by_index[i], parser); } } unread_mention_count = message_count_by_index[message_search_filter_index(MessageSearchFilter::UnreadMention)]; LOG(INFO) << "Set unread mention message count in " << dialog_id << " to " << unread_mention_count; if (unread_mention_count < 0) { unread_mention_count = 0; } unread_reaction_count = message_count_by_index[message_search_filter_index(MessageSearchFilter::UnreadReaction)]; LOG(INFO) << "Set unread reaction count in " << dialog_id << " to " << unread_reaction_count; if (unread_reaction_count < 0) { unread_reaction_count = 0; } if (has_client_data) { parse(client_data, parser); } if (has_last_read_all_mentions_message_id) { parse(last_read_all_mentions_message_id, parser); } if (has_max_unavailable_message_id) { parse(max_unavailable_message_id, parser); } if (has_last_database_message_id) { parse(last_database_message_id, parser); } if (has_message_notification_group) { parse(add_dialog_notification_info(this)->message_notification_group_, parser); } if (has_mention_notification_group) { parse(add_dialog_notification_info(this)->mention_notification_group_, parser); } if (has_new_secret_chat_notification_id) { parse(add_dialog_notification_info(this)->new_secret_chat_notification_id_, parser); } if (has_pinned_message_notification) { parse(add_dialog_notification_info(this)->pinned_message_notification_message_id_, parser); } if (has_last_pinned_message_id) { parse(last_pinned_message_id, parser); } if (has_max_push_notification_message_id) { parse(add_dialog_notification_info(this)->max_push_notification_message_id_, parser); } if (has_folder_id) { parse(folder_id, parser); } if (has_pending_read_channel_inbox) { parse(pending_read_channel_inbox_pts, parser); parse(pending_read_channel_inbox_max_message_id, parser); parse(pending_read_channel_inbox_server_unread_count, parser); } int32 action_bar_distance = -1; if (action_bar_has_distance) { parse(action_bar_distance, parser); } if (has_active_group_call_id) { parse(active_group_call_id, parser); } if (has_message_ttl) { parse(message_ttl, parser); } if (has_default_join_group_call_as_dialog_id) { parse(default_join_group_call_as_dialog_id, parser); } if (has_theme_name) { parse(theme_name, parser); } if (has_pending_join_requests) { parse(pending_join_request_count, parser); parse(pending_join_request_user_ids, parser); } if (has_action_bar) { parse(action_bar, parser); } if (has_default_send_message_as_dialog_id) { parse(default_send_message_as_dialog_id, parser); } if (has_available_reactions) { parse(available_reactions, parser); } else if (has_legacy_available_reactions) { vector legacy_available_reaction_types; parse(legacy_available_reaction_types, parser); available_reactions = ChatReactions(std::move(legacy_available_reaction_types)); } if (has_available_reactions_generation) { parse(available_reactions_generation, parser); } if (has_have_full_history_source) { parse(have_full_history_source, parser); } if (has_history_generation) { parse(history_generation, parser); } if (has_background) { parse(background_info, parser); } (void)legacy_know_can_report_spam; if (know_action_bar && !has_action_bar) { action_bar = DialogActionBar::create( action_bar_can_report_spam, action_bar_can_add_contact, action_bar_can_block_user, action_bar_can_share_phone_number, action_bar_can_report_location, action_bar_can_unarchive, has_outgoing_messages ? -1 : action_bar_distance, action_bar_can_invite_members, string(), false, 0); } } template void MessagesManager::CallsDbState::store(StorerT &storer) const { using td::store; store(static_cast(first_calls_database_message_id_by_index.size()), storer); for (auto first_message_id : first_calls_database_message_id_by_index) { store(first_message_id, storer); } store(static_cast(message_count_by_index.size()), storer); for (auto message_count : message_count_by_index) { store(message_count, storer); } } template void MessagesManager::CallsDbState::parse(ParserT &parser) { using td::parse; int32 size; parse(size, parser); if (static_cast(size) > first_calls_database_message_id_by_index.size()) { return parser.set_error("Wrong first_calls_database_message_id_by_index table size"); } for (int32 i = 0; i < size; i++) { parse(first_calls_database_message_id_by_index[i], parser); } parse(size, parser); if (static_cast(size) > message_count_by_index.size()) { return parser.set_error("Wrong message_count_by_index table size"); } for (int32 i = 0; i < size; i++) { parse(message_count_by_index[i], parser); } } void MessagesManager::load_calls_db_state() { if (!G()->use_message_database()) { return; } std::fill(calls_db_state_.message_count_by_index.begin(), calls_db_state_.message_count_by_index.end(), -1); auto value = G()->td_db()->get_sqlite_sync_pmc()->get("calls_db_state"); if (value.empty()) { return; } log_event_parse(calls_db_state_, value).ensure(); LOG(INFO) << "Save calls database state " << calls_db_state_.first_calls_database_message_id_by_index[0] << " (" << calls_db_state_.message_count_by_index[0] << ") " << calls_db_state_.first_calls_database_message_id_by_index[1] << " (" << calls_db_state_.message_count_by_index[1] << ")"; } void MessagesManager::save_calls_db_state() { if (!G()->use_message_database()) { return; } LOG(INFO) << "Save calls database state " << calls_db_state_.first_calls_database_message_id_by_index[0] << " (" << calls_db_state_.message_count_by_index[0] << ") " << calls_db_state_.first_calls_database_message_id_by_index[1] << " (" << calls_db_state_.message_count_by_index[1] << ")"; G()->td_db()->get_sqlite_pmc()->set("calls_db_state", log_event_store(calls_db_state_).as_slice().str(), Auto()); } MessagesManager::MessagesManager(Td *td, ActorShared<> parent) : recently_found_dialogs_{td, "recently_found", MAX_RECENT_DIALOGS} , recently_opened_dialogs_{td, "recently_opened", MAX_RECENT_DIALOGS} , td_(td) , parent_(std::move(parent)) { upload_media_callback_ = std::make_shared(); upload_thumbnail_callback_ = std::make_shared(); upload_dialog_photo_callback_ = std::make_shared(); upload_imported_messages_callback_ = std::make_shared(); upload_imported_message_attachment_callback_ = std::make_shared(); channel_get_difference_timeout_.set_callback(on_channel_get_difference_timeout_callback); channel_get_difference_timeout_.set_callback_data(static_cast(this)); channel_get_difference_retry_timeout_.set_callback(on_channel_get_difference_timeout_callback); channel_get_difference_retry_timeout_.set_callback_data(static_cast(this)); pending_message_views_timeout_.set_callback(on_pending_message_views_timeout_callback); pending_message_views_timeout_.set_callback_data(static_cast(this)); pending_message_live_location_view_timeout_.set_callback(on_pending_message_live_location_view_timeout_callback); pending_message_live_location_view_timeout_.set_callback_data(static_cast(this)); pending_draft_message_timeout_.set_callback(on_pending_draft_message_timeout_callback); pending_draft_message_timeout_.set_callback_data(static_cast(this)); pending_read_history_timeout_.set_callback(on_pending_read_history_timeout_callback); pending_read_history_timeout_.set_callback_data(static_cast(this)); pending_updated_dialog_timeout_.set_callback(on_pending_updated_dialog_timeout_callback); pending_updated_dialog_timeout_.set_callback_data(static_cast(this)); pending_unload_dialog_timeout_.set_callback(on_pending_unload_dialog_timeout_callback); pending_unload_dialog_timeout_.set_callback_data(static_cast(this)); dialog_unmute_timeout_.set_callback(on_dialog_unmute_timeout_callback); dialog_unmute_timeout_.set_callback_data(static_cast(this)); pending_send_dialog_action_timeout_.set_callback(on_pending_send_dialog_action_timeout_callback); pending_send_dialog_action_timeout_.set_callback_data(static_cast(this)); active_dialog_action_timeout_.set_callback(on_active_dialog_action_timeout_callback); active_dialog_action_timeout_.set_callback_data(static_cast(this)); update_dialog_online_member_count_timeout_.set_callback(on_update_dialog_online_member_count_timeout_callback); update_dialog_online_member_count_timeout_.set_callback_data(static_cast(this)); preload_folder_dialog_list_timeout_.set_callback(on_preload_folder_dialog_list_timeout_callback); preload_folder_dialog_list_timeout_.set_callback_data(static_cast(this)); update_viewed_messages_timeout_.set_callback(on_update_viewed_messages_timeout_callback); update_viewed_messages_timeout_.set_callback_data(static_cast(this)); send_update_chat_read_inbox_timeout_.set_callback(on_send_update_chat_read_inbox_timeout_callback); send_update_chat_read_inbox_timeout_.set_callback_data(static_cast(this)); } MessagesManager::~MessagesManager() { Scheduler::instance()->destroy_on_scheduler( G()->get_gc_scheduler_id(), ttl_nodes_, ttl_heap_, being_sent_messages_, update_message_ids_, update_scheduled_message_ids_, message_id_to_dialog_id_, last_clear_history_message_id_to_dialog_id_, dialogs_, postponed_chat_read_inbox_updates_, found_public_dialogs_, found_on_server_dialogs_, found_common_dialogs_, message_embedding_codes_[0], message_embedding_codes_[1], message_to_replied_media_timestamp_messages_, story_to_replied_media_timestamp_messages_, notification_group_id_to_dialog_id_, pending_get_channel_differences_, active_get_channel_differences_, get_channel_difference_to_log_event_id_, channel_get_difference_retry_timeouts_, is_channel_difference_finished_, expected_channel_pts_, expected_channel_max_message_id_, resolved_usernames_, inaccessible_resolved_usernames_, dialog_bot_command_message_ids_, message_full_id_to_file_source_id_, last_outgoing_forwarded_message_date_, dialog_viewed_messages_, dialog_online_member_counts_, previous_repaired_read_inbox_max_message_id_, failed_to_load_dialogs_); } MessagesManager::AddDialogData::AddDialogData(int32 dependent_dialog_count, unique_ptr &&last_message, unique_ptr &&draft_message) : dependent_dialog_count_(dependent_dialog_count) , last_message_(std::move(last_message)) , draft_message_(std::move(draft_message)) { } void MessagesManager::on_channel_get_difference_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) { if (G()->close_flag()) { return; } auto messages_manager = static_cast(messages_manager_ptr); send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_channel_get_difference_timeout, DialogId(dialog_id_int)); } void MessagesManager::on_pending_message_views_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) { if (G()->close_flag()) { return; } auto messages_manager = static_cast(messages_manager_ptr); send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_pending_message_views_timeout, DialogId(dialog_id_int)); } void MessagesManager::on_pending_message_live_location_view_timeout_callback(void *messages_manager_ptr, int64 task_id) { if (G()->close_flag()) { return; } auto messages_manager = static_cast(messages_manager_ptr); send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::view_message_live_location_on_server, task_id); } void MessagesManager::on_pending_draft_message_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) { if (G()->close_flag()) { return; } auto messages_manager = static_cast(messages_manager_ptr); send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::save_dialog_draft_message_on_server, DialogId(dialog_id_int)); } void MessagesManager::on_pending_read_history_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) { if (G()->close_flag()) { return; } auto messages_manager = static_cast(messages_manager_ptr); send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::do_read_history_on_server, DialogId(dialog_id_int)); } void MessagesManager::on_pending_updated_dialog_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) { // no check for G()->close_flag() to save dialogs even while closing auto messages_manager = static_cast(messages_manager_ptr); // TODO it is unsafe to save dialog to database before binlog is flushed // no send_closure_later, because messages_manager can be not an actor while closing messages_manager->save_dialog_to_database(DialogId(dialog_id_int)); } void MessagesManager::on_pending_unload_dialog_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) { if (G()->close_flag()) { return; } auto messages_manager = static_cast(messages_manager_ptr); send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::unload_dialog, DialogId(dialog_id_int), -1); } void MessagesManager::on_dialog_unmute_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) { if (G()->close_flag()) { return; } auto messages_manager = static_cast(messages_manager_ptr); send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_dialog_unmute, DialogId(dialog_id_int)); } void MessagesManager::on_pending_send_dialog_action_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) { if (G()->close_flag()) { return; } auto messages_manager = static_cast(messages_manager_ptr); send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_send_dialog_action_timeout, DialogId(dialog_id_int)); } void MessagesManager::on_active_dialog_action_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) { if (G()->close_flag()) { return; } auto messages_manager = static_cast(messages_manager_ptr); send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_active_dialog_action_timeout, DialogId(dialog_id_int)); } void MessagesManager::on_update_dialog_online_member_count_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) { if (G()->close_flag()) { return; } auto messages_manager = static_cast(messages_manager_ptr); send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_update_dialog_online_member_count_timeout, DialogId(dialog_id_int)); } void MessagesManager::on_preload_folder_dialog_list_timeout_callback(void *messages_manager_ptr, int64 folder_id_int) { if (G()->close_flag()) { return; } auto messages_manager = static_cast(messages_manager_ptr); send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::preload_folder_dialog_list, FolderId(narrow_cast(folder_id_int))); } void MessagesManager::on_update_viewed_messages_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) { if (G()->close_flag()) { return; } auto messages_manager = static_cast(messages_manager_ptr); send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_update_viewed_messages_timeout, DialogId(dialog_id_int)); } void MessagesManager::on_send_update_chat_read_inbox_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) { if (G()->close_flag()) { return; } auto messages_manager = static_cast(messages_manager_ptr); send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_send_update_chat_read_inbox_timeout, DialogId(dialog_id_int)); } BufferSlice MessagesManager::get_dialog_database_value(const Dialog *d) { // can't use log_event_store, because it tries to parse stored Dialog LogEventStorerCalcLength storer_calc_length; store(*d, storer_calc_length); BufferSlice value_buffer{storer_calc_length.get_length()}; auto value = value_buffer.as_mutable_slice(); LogEventStorerUnsafe storer_unsafe(value.ubegin()); store(*d, storer_unsafe); return value_buffer; } void MessagesManager::save_dialog_to_database(DialogId dialog_id) { CHECK(G()->use_message_database()); auto d = get_dialog(dialog_id); CHECK(d != nullptr); LOG(INFO) << "Save " << dialog_id << " to database"; vector changed_group_keys; if (d->notification_info != nullptr) { d->notification_info->message_notification_group_.add_group_key_if_changed(changed_group_keys, dialog_id); d->notification_info->mention_notification_group_.add_group_key_if_changed(changed_group_keys, dialog_id); } bool can_reuse_notification_group = false; for (auto &group_key : changed_group_keys) { if (group_key.dialog_id == DialogId()) { can_reuse_notification_group = true; } } G()->td_db()->get_dialog_db_async()->add_dialog( dialog_id, d->folder_id, d->order, get_dialog_database_value(d), std::move(changed_group_keys), PromiseCreator::lambda([dialog_id, can_reuse_notification_group](Result<> result) { send_closure(G()->messages_manager(), &MessagesManager::on_save_dialog_to_database, dialog_id, can_reuse_notification_group, result.is_ok()); })); } void MessagesManager::on_save_dialog_to_database(DialogId dialog_id, bool can_reuse_notification_group, bool success) { LOG(INFO) << "Successfully saved " << dialog_id << " to database"; if (success && can_reuse_notification_group && !G()->close_flag()) { auto d = get_dialog(dialog_id); CHECK(d != nullptr); if (d->notification_info != nullptr) { try_reuse_notification_group(d->notification_info->message_notification_group_); try_reuse_notification_group(d->notification_info->mention_notification_group_); } } // TODO erase some events from binlog } void MessagesManager::try_reuse_notification_group(NotificationGroupInfo &group_info) { auto group_id = group_info.get_reused_group_id(); if (group_id.is_valid()) { send_closure_later(G()->notification_manager(), &NotificationManager::try_reuse_notification_group_id, group_id); notification_group_id_to_dialog_id_.erase(group_id); } } void MessagesManager::invalidate_message_indexes(Dialog *d) { CHECK(d != nullptr); bool is_secret = d->dialog_id.get_type() == DialogType::SecretChat; for (size_t i = 0; i < d->message_count_by_index.size(); i++) { if (is_secret || i == static_cast(message_search_filter_index(MessageSearchFilter::FailedToSend))) { // always know all messages d->first_database_message_id_by_index[i] = MessageId::min(); // keep the count } else { // some messages are unknown; drop first_database_message_id and count d->first_database_message_id_by_index[i] = MessageId(); d->message_count_by_index[i] = -1; } } } void MessagesManager::update_message_count_by_index(Dialog *d, int diff, const Message *m) { auto index_mask = get_message_index_mask(d->dialog_id, m); index_mask &= ~message_search_filter_index_mask( MessageSearchFilter::UnreadMention); // unread mention count has been already manually updated index_mask &= ~message_search_filter_index_mask( MessageSearchFilter::UnreadReaction); // unread reaction count has been already manually updated update_message_count_by_index(d, diff, index_mask); } void MessagesManager::update_message_count_by_index(Dialog *d, int diff, int32 index_mask) { if (index_mask == 0) { return; } LOG(INFO) << "Update message count by " << diff << " in index mask " << index_mask; int i = 0; for (auto &message_count : d->message_count_by_index) { if (((index_mask >> i) & 1) != 0 && message_count != -1) { message_count += diff; if (message_count < 0) { if (d->dialog_id.get_type() == DialogType::SecretChat || i == message_search_filter_index(MessageSearchFilter::FailedToSend)) { message_count = 0; } else { message_count = -1; } } on_dialog_updated(d->dialog_id, "update_message_count_by_index"); } i++; } i = static_cast(MessageSearchFilter::Call) - 1; for (auto &message_count : calls_db_state_.message_count_by_index) { if (((index_mask >> i) & 1) != 0 && message_count != -1) { message_count += diff; if (message_count < 0) { if (d->dialog_id.get_type() == DialogType::SecretChat) { message_count = 0; } else { message_count = -1; } } save_calls_db_state(); } i++; } } int32 MessagesManager::get_message_index_mask(DialogId dialog_id, const Message *m) const { CHECK(m != nullptr); if (td_->auth_manager_->is_bot() || m->message_id.is_scheduled() || m->message_id.is_yet_unsent()) { return 0; } if (m->is_failed_to_send) { return message_search_filter_index_mask(MessageSearchFilter::FailedToSend); } bool is_secret = dialog_id.get_type() == DialogType::SecretChat; if (!m->message_id.is_server() && !is_secret) { return 0; } int32 index_mask = 0; if (m->is_pinned) { index_mask |= message_search_filter_index_mask(MessageSearchFilter::Pinned); } // retain second condition just in case if (m->is_content_secret || (m->ttl > 0 && !is_secret)) { return index_mask; } index_mask |= get_message_content_index_mask(m->content.get(), td_, m->is_outgoing); if (m->contains_mention) { index_mask |= message_search_filter_index_mask(MessageSearchFilter::Mention); if (m->contains_unread_mention) { index_mask |= message_search_filter_index_mask(MessageSearchFilter::UnreadMention); } } if (has_unread_message_reactions(dialog_id, m)) { index_mask |= message_search_filter_index_mask(MessageSearchFilter::UnreadReaction); } LOG(INFO) << "Have index mask " << index_mask << " for " << m->message_id << " in " << dialog_id; return index_mask; } void MessagesManager::update_reply_count_by_message(Dialog *d, int diff, const Message *m) { CHECK(d != nullptr); CHECK(m != nullptr); if (td_->auth_manager_->is_bot() || !m->top_thread_message_id.is_valid() || m->top_thread_message_id == m->message_id || !m->message_id.is_valid() || !m->message_id.is_server()) { return; } update_message_reply_count(d, m->top_thread_message_id, get_message_sender(m), m->message_id, diff < 0 ? G()->unix_time() : m->date, diff); } void MessagesManager::update_message_reply_count(Dialog *d, MessageId message_id, DialogId replier_dialog_id, MessageId reply_message_id, int32 update_date, int diff, bool is_recursive) { if (d == nullptr) { return; } Message *m = get_message(d, message_id); if (m == nullptr || !is_active_message_reply_info(d->dialog_id, m->reply_info)) { return; } LOG(INFO) << "Update reply count to " << message_id << " in " << d->dialog_id << " by " << diff << " from " << reply_message_id << " sent by " << replier_dialog_id; if (m->interaction_info_update_date < update_date && m->reply_info.add_reply(replier_dialog_id, reply_message_id, diff)) { on_message_reply_info_changed(d->dialog_id, m); on_message_changed(d, m, true, "update_message_reply_count_by_message"); } if (!is_recursive && is_discussion_message(d->dialog_id, m)) { update_message_reply_count(get_dialog(m->forward_info->from_dialog_id), m->forward_info->from_message_id, replier_dialog_id, reply_message_id, update_date, diff, true); } } tl_object_ptr MessagesManager::get_input_peer(DialogId dialog_id, AccessRights access_rights) const { switch (dialog_id.get_type()) { case DialogType::User: return td_->contacts_manager_->get_input_peer_user(dialog_id.get_user_id(), access_rights); case DialogType::Chat: return td_->contacts_manager_->get_input_peer_chat(dialog_id.get_chat_id(), access_rights); case DialogType::Channel: return td_->contacts_manager_->get_input_peer_channel(dialog_id.get_channel_id(), access_rights); case DialogType::SecretChat: return nullptr; case DialogType::None: return make_tl_object(); default: UNREACHABLE(); return nullptr; } } tl_object_ptr MessagesManager::get_input_peer_force(DialogId dialog_id) { switch (dialog_id.get_type()) { case DialogType::User: { UserId user_id = dialog_id.get_user_id(); return make_tl_object(user_id.get(), 0); } case DialogType::Chat: { ChatId chat_id = dialog_id.get_chat_id(); return make_tl_object(chat_id.get()); } case DialogType::Channel: { ChannelId channel_id = dialog_id.get_channel_id(); return make_tl_object(channel_id.get(), 0); } case DialogType::SecretChat: case DialogType::None: return make_tl_object(); default: UNREACHABLE(); return nullptr; } } vector> MessagesManager::get_input_peers(const vector &dialog_ids, AccessRights access_rights) const { vector> input_peers; input_peers.reserve(dialog_ids.size()); for (auto &dialog_id : dialog_ids) { auto input_peer = get_input_peer(dialog_id, access_rights); if (input_peer == nullptr) { LOG(ERROR) << "Have no access to " << dialog_id; continue; } input_peers.push_back(std::move(input_peer)); } return input_peers; } tl_object_ptr MessagesManager::get_input_dialog_peer(DialogId dialog_id, AccessRights access_rights) const { switch (dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: case DialogType::Channel: case DialogType::None: return make_tl_object(get_input_peer(dialog_id, access_rights)); case DialogType::SecretChat: return nullptr; default: UNREACHABLE(); return nullptr; } } vector> MessagesManager::get_input_dialog_peers( const vector &dialog_ids, AccessRights access_rights) const { vector> input_dialog_peers; input_dialog_peers.reserve(dialog_ids.size()); for (auto &dialog_id : dialog_ids) { auto input_dialog_peer = get_input_dialog_peer(dialog_id, access_rights); if (input_dialog_peer == nullptr) { LOG(ERROR) << "Have no access to " << dialog_id; continue; } input_dialog_peers.push_back(std::move(input_dialog_peer)); } return input_dialog_peers; } bool MessagesManager::have_input_peer(DialogId dialog_id, AccessRights access_rights) const { switch (dialog_id.get_type()) { case DialogType::User: { UserId user_id = dialog_id.get_user_id(); return td_->contacts_manager_->have_input_peer_user(user_id, access_rights); } case DialogType::Chat: { ChatId chat_id = dialog_id.get_chat_id(); return td_->contacts_manager_->have_input_peer_chat(chat_id, access_rights); } case DialogType::Channel: { ChannelId channel_id = dialog_id.get_channel_id(); return td_->contacts_manager_->have_input_peer_channel(channel_id, access_rights); } case DialogType::SecretChat: { SecretChatId secret_chat_id = dialog_id.get_secret_chat_id(); return td_->contacts_manager_->have_input_encrypted_peer(secret_chat_id, access_rights); } case DialogType::None: return false; default: UNREACHABLE(); return false; } } bool MessagesManager::have_dialog_info(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: { UserId user_id = dialog_id.get_user_id(); return td_->contacts_manager_->have_user(user_id); } case DialogType::Chat: { ChatId chat_id = dialog_id.get_chat_id(); return td_->contacts_manager_->have_chat(chat_id); } case DialogType::Channel: { ChannelId channel_id = dialog_id.get_channel_id(); return td_->contacts_manager_->have_channel(channel_id); } case DialogType::SecretChat: { SecretChatId secret_chat_id = dialog_id.get_secret_chat_id(); return td_->contacts_manager_->have_secret_chat(secret_chat_id); } case DialogType::None: default: return false; } } bool MessagesManager::have_dialog_info_force(DialogId dialog_id, const char *source) const { switch (dialog_id.get_type()) { case DialogType::User: { UserId user_id = dialog_id.get_user_id(); return td_->contacts_manager_->have_user_force(user_id, source); } case DialogType::Chat: { ChatId chat_id = dialog_id.get_chat_id(); return td_->contacts_manager_->have_chat_force(chat_id, source); } case DialogType::Channel: { ChannelId channel_id = dialog_id.get_channel_id(); return td_->contacts_manager_->have_channel_force(channel_id, source); } case DialogType::SecretChat: { SecretChatId secret_chat_id = dialog_id.get_secret_chat_id(); return td_->contacts_manager_->have_secret_chat_force(secret_chat_id, source); } case DialogType::None: default: return false; } } tl_object_ptr MessagesManager::get_input_encrypted_chat( DialogId dialog_id, AccessRights access_rights) const { switch (dialog_id.get_type()) { case DialogType::SecretChat: { SecretChatId secret_chat_id = dialog_id.get_secret_chat_id(); return td_->contacts_manager_->get_input_encrypted_chat(secret_chat_id, access_rights); } case DialogType::User: case DialogType::Chat: case DialogType::Channel: case DialogType::None: default: UNREACHABLE(); return nullptr; } } bool MessagesManager::have_dialog_scheduled_messages_in_memory(const Dialog *d) { return d->scheduled_messages != nullptr && !d->scheduled_messages->scheduled_messages_.empty(); } bool MessagesManager::is_allowed_useless_update(const tl_object_ptr &update) { auto constructor_id = update->get_id(); if (constructor_id == dummyUpdate::ID) { // allow dummyUpdate just in case return true; } if (constructor_id == telegram_api::updateNewMessage::ID || constructor_id == telegram_api::updateNewChannelMessage::ID) { // new outgoing messages are received again if random_id coincide return true; } return false; } void MessagesManager::skip_old_pending_pts_update(tl_object_ptr &&update, int32 new_pts, int32 old_pts, int32 pts_count, const char *source) { if (update->get_id() == telegram_api::updateNewMessage::ID) { auto update_new_message = static_cast(update.get()); auto message_full_id = MessageFullId::get_message_full_id(update_new_message->message_, false); if (update_message_ids_.count(message_full_id) > 0) { // apply the sent message anyway, even it could have been deleted or edited already CHECK(message_full_id.get_dialog_id().get_type() == DialogType::User || message_full_id.get_dialog_id().get_type() == DialogType::Chat); // checked in check_pts_update delete_messages_from_updates({message_full_id.get_message_id()}, false); auto added_message_full_id = on_get_message(std::move(update_new_message->message_), true, false, false, "updateNewMessage with an awaited message"); if (added_message_full_id != message_full_id) { LOG(ERROR) << "Failed to add an awaited " << message_full_id << " from " << source; } return; } } if (update->get_id() == updateSentMessage::ID) { auto update_sent_message = static_cast(update.get()); if (being_sent_messages_.count(update_sent_message->random_id_) > 0) { // apply the sent message anyway, even it could have been deleted or edited already delete_messages_from_updates({update_sent_message->message_id_}, false); on_send_message_success(update_sent_message->random_id_, update_sent_message->message_id_, update_sent_message->date_, update_sent_message->ttl_period_, FileId(), "process old updateSentMessage"); return; } return; } // very old or unuseful update LOG_IF(WARNING, new_pts == old_pts && pts_count == 0 && !is_allowed_useless_update(update)) << "Receive useless update " << oneline(to_string(update)) << " from " << source; } MessagesManager::Dialog *MessagesManager::get_service_notifications_dialog() { UserId service_notifications_user_id = td_->contacts_manager_->add_service_notifications_user(); DialogId service_notifications_dialog_id(service_notifications_user_id); force_create_dialog(service_notifications_dialog_id, "get_service_notifications_dialog"); return get_dialog(service_notifications_dialog_id); } void MessagesManager::extract_authentication_codes(DialogId dialog_id, const Message *m, vector &authentication_codes) { CHECK(m != nullptr); if (dialog_id != DialogId(ContactsManager::get_service_notifications_user_id()) || !m->message_id.is_valid() || !m->message_id.is_server() || m->content->get_type() != MessageContentType::Text || m->is_outgoing) { return; } auto *formatted_text = get_message_content_text(m->content.get()); CHECK(formatted_text != nullptr); const string &text = formatted_text->text; for (size_t i = 0; i < text.size(); i++) { if (is_digit(text[i])) { string code; do { if (is_digit(text[i])) { code += text[i++]; continue; } if (text[i] == '-') { i++; continue; } break; } while (true); if (5 <= code.size() && code.size() <= 7) { authentication_codes.push_back(code); } } } } void MessagesManager::save_auth_notification_ids() { auto min_date = G()->unix_time() - AUTH_NOTIFICATION_ID_CACHE_TIME; vector stored_ids; for (const auto &it : auth_notification_id_date_) { auto date = it.second; if (date < min_date) { continue; } stored_ids.push_back(it.first); stored_ids.push_back(to_string(date)); } if (stored_ids.empty()) { G()->td_db()->get_binlog_pmc()->erase("auth_notification_ids"); return; } G()->td_db()->get_binlog_pmc()->set("auth_notification_ids", implode(stored_ids, ',')); } void MessagesManager::on_update_service_notification(tl_object_ptr &&update, bool skip_new_entities, Promise &&promise) { if (td_->auth_manager_->is_bot()) { return; } bool has_date = (update->flags_ & telegram_api::updateServiceNotification::INBOX_DATE_MASK) != 0; auto date = has_date ? update->inbox_date_ : G()->unix_time(); if (date <= 0) { LOG(ERROR) << "Receive message date " << date << " in " << to_string(update); return; } bool is_auth_notification = update->type_.size() >= 5 && begins_with(update->type_, "auth"); if (is_auth_notification) { auto &old_date = auth_notification_id_date_[update->type_.substr(4)]; if (date <= old_date) { LOG(INFO) << "Skip already applied " << to_string(update); return; } old_date = date; if (auth_notification_id_date_.size() > MAX_SAVED_AUTH_NOTIFICATION_IDS) { auto min_date = date + 1; const string *min_key = nullptr; for (const auto &it : auth_notification_id_date_) { if (it.second < min_date) { min_date = it.second; min_key = &it.first; } } CHECK(min_key != nullptr); auth_notification_id_date_.erase(*min_key); } } bool is_authorized = td_->auth_manager_->is_authorized(); bool is_user = is_authorized && !td_->auth_manager_->is_bot(); auto contacts_manager = is_authorized ? td_->contacts_manager_.get() : nullptr; auto message_text = get_message_text(contacts_manager, std::move(update->message_), std::move(update->entities_), skip_new_entities, !is_user, date, false, "on_update_service_notification"); DialogId owner_dialog_id = is_user ? get_service_notifications_dialog()->dialog_id : DialogId(); int32 ttl = 0; bool disable_web_page_preview = false; auto content = get_message_content(td_, std::move(message_text), std::move(update->media_), owner_dialog_id, date, false, UserId(), &ttl, &disable_web_page_preview, "updateServiceNotification"); bool is_content_secret = is_secret_message_content(ttl, content->get_type()); if (update->popup_) { send_closure( G()->td(), &Td::send_update, td_api::make_object( update->type_, get_message_content_object(content.get(), td_, owner_dialog_id, date, is_content_secret, true, -1, update->invert_media_, disable_web_page_preview))); } if (has_date && is_user) { Dialog *d = get_service_notifications_dialog(); CHECK(d != nullptr); auto dialog_id = d->dialog_id; CHECK(dialog_id.get_type() == DialogType::User); auto new_message = make_unique(); new_message->message_id = get_next_local_message_id(d); new_message->sender_user_id = dialog_id.get_user_id(); new_message->date = date; new_message->ttl = ttl; new_message->disable_web_page_preview = disable_web_page_preview; new_message->is_content_secret = is_content_secret; new_message->invert_media = update->invert_media_; new_message->content = std::move(content); bool need_update = true; bool need_update_dialog_pos = false; Dependencies dependencies; add_message_dependencies(dependencies, new_message.get()); for (auto dependent_dialog_id : dependencies.get_dialog_ids()) { force_create_dialog(dependent_dialog_id, "on_update_service_notification", true); } const Message *m = add_message_to_dialog(d, std::move(new_message), false, true, &need_update, &need_update_dialog_pos, "on_update_service_notification"); if (m != nullptr && need_update) { send_update_new_message(d, m); } register_new_local_message_id(d, m); if (need_update_dialog_pos) { send_update_chat_last_message(d, "on_update_service_notification"); } } promise.set_value(Unit()); if (is_auth_notification) { save_auth_notification_ids(); } } void MessagesManager::on_update_read_channel_inbox(tl_object_ptr &&update) { ChannelId channel_id(update->channel_id_); if (!channel_id.is_valid()) { LOG(ERROR) << "Receive invalid " << channel_id << " in updateReadChannelInbox"; return; } on_update_dialog_folder_id(DialogId(channel_id), FolderId(update->folder_id_)); on_read_channel_inbox(channel_id, MessageId(ServerMessageId(update->max_id_)), update->still_unread_count_, update->pts_, "updateReadChannelInbox"); } void MessagesManager::on_update_read_channel_outbox(tl_object_ptr &&update) { ChannelId channel_id(update->channel_id_); if (!channel_id.is_valid()) { LOG(ERROR) << "Receive invalid " << channel_id << " in updateReadChannelOutbox"; return; } DialogId dialog_id = DialogId(channel_id); read_history_outbox(dialog_id, MessageId(ServerMessageId(update->max_id_))); } void MessagesManager::on_update_read_channel_messages_contents( tl_object_ptr &&update) { ChannelId channel_id(update->channel_id_); if (!channel_id.is_valid()) { LOG(ERROR) << "Receive invalid " << channel_id << " in updateChannelReadMessagesContents"; return; } if (td_->auth_manager_->is_bot()) { return; } DialogId dialog_id = DialogId(channel_id); Dialog *d = get_dialog_force(dialog_id, "on_update_read_channel_messages_contents"); if (d == nullptr) { LOG(INFO) << "Receive read channel messages contents update in unknown " << dialog_id; return; } if ((update->flags_ & telegram_api::updateChannelReadMessagesContents::TOP_MSG_ID_MASK) != 0) { // TODO return; } for (auto &server_message_id : update->messages_) { read_channel_message_content_from_updates(d, MessageId(ServerMessageId(server_message_id))); } } void MessagesManager::on_update_read_message_comments(DialogId dialog_id, MessageId message_id, MessageId max_message_id, MessageId last_read_inbox_message_id, MessageId last_read_outbox_message_id, int32 unread_count) { Dialog *d = get_dialog_force(dialog_id, "on_update_read_message_comments"); if (d == nullptr) { LOG(INFO) << "Ignore update of read message comments in unknown " << dialog_id << " in updateReadDiscussion"; return; } auto m = get_message_force(d, message_id, "on_update_read_message_comments"); if (m == nullptr || !m->message_id.is_server() || m->top_thread_message_id != m->message_id) { return; } if (m->is_topic_message) { td_->forum_topic_manager_->on_update_forum_topic_unread( dialog_id, message_id, max_message_id, last_read_inbox_message_id, last_read_outbox_message_id, unread_count); } if (!is_active_message_reply_info(dialog_id, m->reply_info)) { return; } if (m->reply_info.update_max_message_ids(max_message_id, last_read_inbox_message_id, last_read_outbox_message_id)) { on_message_reply_info_changed(dialog_id, m); on_message_changed(d, m, true, "on_update_read_message_comments"); } } void MessagesManager::on_update_channel_too_long(tl_object_ptr &&update, bool force_apply) { ChannelId channel_id(update->channel_id_); if (!channel_id.is_valid()) { LOG(ERROR) << "Receive invalid " << channel_id << " in updateChannelTooLong"; return; } if (!td_->contacts_manager_->have_channel_force(channel_id, "on_update_channel_too_long")) { LOG(INFO) << "Skip updateChannelTooLong about unknown " << channel_id; return; } DialogId dialog_id = DialogId(channel_id); auto d = get_dialog_force(dialog_id, "on_update_channel_too_long 4"); if (d == nullptr) { auto pts = load_channel_pts(dialog_id); if (pts > 0) { d = add_dialog(dialog_id, "on_update_channel_too_long 5"); CHECK(d != nullptr); CHECK(d->pts == pts); update_dialog_pos(d, "on_update_channel_too_long 6"); } } if (d != nullptr) { if (update->pts_ == 0 || update->pts_ > d->pts) { get_channel_difference(dialog_id, d->pts, update->pts_, MessageId(), true, "on_update_channel_too_long 1"); } } else { if (force_apply) { get_channel_difference(dialog_id, -1, update->pts_, MessageId(), true, "on_update_channel_too_long 2"); } else { td_->updates_manager_->schedule_get_difference("on_update_channel_too_long 3"); } } } void MessagesManager::on_update_message_view_count(MessageFullId message_full_id, int32 view_count) { if (view_count < 0) { LOG(ERROR) << "Receive " << view_count << " views in updateChannelMessageViews for " << message_full_id; return; } update_message_interaction_info(message_full_id, view_count, -1, false, nullptr, false, nullptr); } void MessagesManager::on_update_message_forward_count(MessageFullId message_full_id, int32 forward_count) { if (forward_count < 0) { LOG(ERROR) << "Receive " << forward_count << " forwards in updateChannelMessageForwards for " << message_full_id; return; } update_message_interaction_info(message_full_id, -1, forward_count, false, nullptr, false, nullptr); } void MessagesManager::on_update_message_reactions(MessageFullId message_full_id, tl_object_ptr &&reactions, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); auto new_reactions = MessageReactions::get_message_reactions(td_, std::move(reactions), td_->auth_manager_->is_bot()); if (!have_message_force(message_full_id, "on_update_message_reactions")) { auto dialog_id = message_full_id.get_dialog_id(); if (!have_input_peer(dialog_id, AccessRights::Read)) { LOG(INFO) << "Ignore updateMessageReaction in inaccessible " << message_full_id; promise.set_value(Unit()); return; } Dialog *d = get_dialog(dialog_id); if (d == nullptr) { LOG(INFO) << "Ignore updateMessageReaction in unknown " << dialog_id; promise.set_value(Unit()); return; } // there is no message, so the update can be ignored if ((new_reactions != nullptr && !new_reactions->unread_reactions_.empty()) || d->unread_reaction_count > 0) { // but if there are unread reactions or the chat has unread reactions, // then number of unread reactions could have been changed, so reload the number of unread reactions repair_dialog_unread_reaction_count(d, std::move(promise), "on_update_message_reactions"); } else { promise.set_value(Unit()); } return; } update_message_interaction_info(message_full_id, -1, -1, false, nullptr, true, std::move(new_reactions)); promise.set_value(Unit()); } void MessagesManager::update_message_reactions(MessageFullId message_full_id, unique_ptr &&reactions) { update_message_interaction_info(message_full_id, -1, -1, false, nullptr, true, std::move(reactions)); } void MessagesManager::on_get_message_reaction_list( MessageFullId message_full_id, const ReactionType &reaction_type, FlatHashMap, ReactionTypeHash> reaction_types, int32 total_count) { const Message *m = get_message_force(message_full_id, "on_get_message_reaction_list"); if (m == nullptr || m->reactions == nullptr) { return; } // it's impossible to use received reactions to update message reactions, because there is no way to find, // which reactions are chosen by the current user, so just reload message reactions for consistency if (m->reactions->are_consistent_with_list(reaction_type, std::move(reaction_types), total_count)) { return; } LOG(INFO) << "Need reload reactions in " << message_full_id << " for consistency"; auto it = pending_reactions_.find(message_full_id); if (it != pending_reactions_.end()) { it->second.was_updated = true; } else { queue_message_reactions_reload(message_full_id); } } void MessagesManager::on_update_message_interaction_info(MessageFullId message_full_id, int32 view_count, int32 forward_count, bool has_reply_info, tl_object_ptr &&reply_info) { if (view_count < 0 || forward_count < 0) { LOG(ERROR) << "Receive " << view_count << "/" << forward_count << " interaction counters for " << message_full_id; return; } update_message_interaction_info(message_full_id, view_count, forward_count, has_reply_info, std::move(reply_info), false, nullptr); } void MessagesManager::on_pending_message_views_timeout(DialogId dialog_id) { if (G()->close_flag()) { return; } auto it = pending_message_views_.find(dialog_id); if (it == pending_message_views_.end()) { return; } auto &pending_viewed_message_ids = it->second.message_ids_; bool increment_view_counter = it->second.increment_view_counter_; auto d = get_dialog(dialog_id); CHECK(d != nullptr); const size_t MAX_MESSAGE_VIEWS = 100; // server side limit vector message_ids; message_ids.reserve(min(pending_viewed_message_ids.size(), MAX_MESSAGE_VIEWS)); for (auto message_id : pending_viewed_message_ids) { auto *m = get_message(d, message_id); if (m == nullptr) { continue; } if (m->has_get_message_views_query) { if (!increment_view_counter || m->need_view_counter_increment) { continue; } m->need_view_counter_increment = true; } else { m->has_get_message_views_query = true; m->need_view_counter_increment = increment_view_counter; } message_ids.push_back(message_id); if (message_ids.size() >= MAX_MESSAGE_VIEWS) { td_->create_handler()->send(dialog_id, std::move(message_ids), increment_view_counter); message_ids.clear(); } } if (!message_ids.empty()) { td_->create_handler()->send(dialog_id, std::move(message_ids), increment_view_counter); } pending_message_views_.erase(it); } void MessagesManager::update_message_interaction_info(MessageFullId message_full_id, int32 view_count, int32 forward_count, bool has_reply_info, tl_object_ptr &&reply_info, bool has_reactions, unique_ptr &&reactions) { if (td_->auth_manager_->is_bot()) { return; } auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "update_message_interaction_info"); if (d == nullptr) { return; } auto message_id = message_full_id.get_message_id(); Message *m = get_message_force(d, message_id, "update_message_interaction_info"); if (m == nullptr) { LOG(INFO) << "Ignore message interaction info about unknown " << message_full_id; if (!message_id.is_scheduled() && d->last_new_message_id.is_valid() && message_id > d->last_new_message_id && dialog_id.get_type() == DialogType::Channel) { get_channel_difference(dialog_id, d->pts, 0, message_id, true, "update_message_interaction_info"); } return; } if (view_count < 0) { view_count = m->view_count; } if (forward_count < 0) { forward_count = m->forward_count; } bool is_empty_reply_info = reply_info == nullptr; MessageReplyInfo new_reply_info(td_, std::move(reply_info), td_->auth_manager_->is_bot()); if (new_reply_info.is_empty() && !is_empty_reply_info) { has_reply_info = false; } if (update_message_interaction_info(d, m, view_count, forward_count, has_reply_info, std::move(new_reply_info), has_reactions, std::move(reactions), "update_message_interaction_info")) { on_message_changed(d, m, true, "update_message_interaction_info"); } } bool MessagesManager::is_thread_message(DialogId dialog_id, const Message *m) const { CHECK(m != nullptr); return is_thread_message(dialog_id, m->message_id, m->reply_info, m->content->get_type()); } bool MessagesManager::is_thread_message(DialogId dialog_id, MessageId message_id, const MessageReplyInfo &reply_info, MessageContentType content_type) const { if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id)) { return false; } if (!message_id.is_valid() || !message_id.is_server()) { return false; } return !reply_info.is_empty() || reply_info.was_dropped() || content_type == MessageContentType::TopicCreate; } bool MessagesManager::is_active_message_reply_info(DialogId dialog_id, const MessageReplyInfo &reply_info) const { if (reply_info.is_empty()) { return false; } if (dialog_id.get_type() != DialogType::Channel) { return false; } if (!reply_info.is_comment_) { return true; } if (!is_broadcast_channel(dialog_id)) { return true; } auto channel_id = dialog_id.get_channel_id(); if (!td_->contacts_manager_->get_channel_has_linked_channel(channel_id)) { return false; } auto linked_channel_id = td_->contacts_manager_->get_channel_linked_channel_id(channel_id, "is_active_message_reply_info"); if (!linked_channel_id.is_valid()) { // keep the comment button while linked channel is unknown send_closure_later(G()->contacts_manager(), &ContactsManager::load_channel_full, channel_id, false, Promise(), "is_active_message_reply_info"); return true; } if (linked_channel_id != reply_info.channel_id_) { return false; } return true; } bool MessagesManager::is_visible_message_reply_info(DialogId dialog_id, const Message *m) const { CHECK(m != nullptr); if (!m->message_id.is_valid()) { return false; } bool is_broadcast = is_broadcast_channel(dialog_id); if (!m->message_id.is_server() && !(is_broadcast && m->message_id.is_yet_unsent())) { return false; } if (is_broadcast && (m->had_reply_markup || m->reply_markup != nullptr)) { return false; } if (!is_active_message_reply_info(dialog_id, m->reply_info)) { return false; } if (m->reply_info.is_comment_ && is_broadcast && td_->contacts_manager_->have_channel_force(m->reply_info.channel_id_, "is_visible_message_reply_info") && !td_->contacts_manager_->have_input_peer_channel(m->reply_info.channel_id_, AccessRights::Read)) { // keep the comment button while have no information about the linked channel return false; } return true; } bool MessagesManager::is_visible_message_reactions(DialogId dialog_id, const Message *m) const { if (m == nullptr || !m->message_id.is_valid()) { return false; } const Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); if (get_message_active_reactions(d, m).empty()) { return false; } if (m->available_reactions_generation != d->available_reactions_generation) { return false; } return true; } bool MessagesManager::has_unread_message_reactions(DialogId dialog_id, const Message *m) const { CHECK(m != nullptr); return m->reactions != nullptr && !m->reactions->unread_reactions_.empty() && is_visible_message_reactions(dialog_id, m); } void MessagesManager::on_message_reply_info_changed(DialogId dialog_id, const Message *m) const { if (td_->auth_manager_->is_bot()) { return; } if (is_visible_message_reply_info(dialog_id, m)) { send_update_message_interaction_info(dialog_id, m); } } td_api::object_ptr MessagesManager::get_message_interaction_info_object( DialogId dialog_id, const Message *m) const { bool is_visible_reply_info = is_visible_message_reply_info(dialog_id, m); bool has_reactions = m->reactions != nullptr && !m->reactions->reactions_.empty() && is_visible_message_reactions(dialog_id, m); if (m->view_count == 0 && m->forward_count == 0 && !is_visible_reply_info && !has_reactions) { return nullptr; } if (m->message_id.is_scheduled() && (m->forward_info == nullptr || is_broadcast_channel(dialog_id))) { return nullptr; } if (m->message_id.is_local() && m->forward_info == nullptr) { return nullptr; } td_api::object_ptr reply_info; if (is_visible_reply_info) { auto expected_dialog_id = m->reply_info.is_comment_ ? DialogId(m->reply_info.channel_id_) : dialog_id; const Dialog *d = get_dialog(expected_dialog_id); reply_info = m->reply_info.get_message_reply_info_object(td_, d != nullptr ? d->last_read_inbox_message_id : MessageId()); CHECK(reply_info != nullptr); } vector> reactions; if (has_reactions) { UserId my_user_id; UserId peer_user_id; if (dialog_id.get_type() == DialogType::User) { my_user_id = td_->contacts_manager_->get_my_id(); peer_user_id = dialog_id.get_user_id(); } reactions = m->reactions->get_message_reactions_object(td_, my_user_id, peer_user_id); } return td_api::make_object(m->view_count, m->forward_count, std::move(reply_info), std::move(reactions)); } vector> MessagesManager::get_unread_reactions_object( DialogId dialog_id, const Message *m) const { if (!has_unread_message_reactions(dialog_id, m)) { return {}; } vector> unread_reactions; for (const auto &unread_reaction : m->reactions->unread_reactions_) { auto unread_reaction_object = unread_reaction.get_unread_reaction_object(td_); if (unread_reaction_object != nullptr) { unread_reactions.push_back(std::move(unread_reaction_object)); } } return unread_reactions; } bool MessagesManager::update_message_interaction_info(Dialog *d, Message *m, int32 view_count, int32 forward_count, bool has_reply_info, MessageReplyInfo &&reply_info, bool has_reactions, unique_ptr &&reactions, const char *source) { if (td_->auth_manager_->is_bot()) { return false; } CHECK(d != nullptr); CHECK(m != nullptr); auto dialog_id = d->dialog_id; m->interaction_info_update_date = G()->unix_time(); // doesn't force message saving if (m->message_id.is_valid_scheduled()) { has_reply_info = false; has_reactions = false; } bool need_update_reply_info = has_reply_info && m->reply_info.need_update_to(reply_info); if (has_reply_info && m->reply_info.channel_id_ == reply_info.channel_id_) { if (need_update_reply_info) { reply_info.update_max_message_ids(m->reply_info); } else { if (m->reply_info.update_max_message_ids(reply_info) && view_count <= m->view_count && forward_count <= m->forward_count) { on_message_reply_info_changed(dialog_id, m); on_message_changed(d, m, true, "update_message_interaction_info"); } } } MessageFullId message_full_id{dialog_id, m->message_id}; if (has_reactions) { auto it = pending_reactions_.find(message_full_id); if (it != pending_reactions_.end()) { LOG(INFO) << "Ignore reactions for " << message_full_id << ", because they are being changed"; has_reactions = false; it->second.was_updated = true; } if (has_reactions && pending_read_reactions_.count(message_full_id) > 0) { LOG(INFO) << "Ignore reactions for " << message_full_id << ", because they are being read"; has_reactions = false; } } if (has_reactions && reactions != nullptr) { if (m->reactions != nullptr) { reactions->update_from(*m->reactions); } reactions->sort_reactions(active_reaction_pos_); reactions->fix_chosen_reaction(); reactions->fix_my_recent_chooser_dialog_id(get_my_dialog_id()); } bool need_update_reactions = has_reactions && MessageReactions::need_update_message_reactions(m->reactions.get(), reactions.get()); bool need_update_unread_reactions = has_reactions && MessageReactions::need_update_unread_reactions(m->reactions.get(), reactions.get()); bool need_update_chosen_reaction_order = has_reactions && reactions != nullptr && m->reactions != nullptr && m->reactions->chosen_reaction_order_ != reactions->chosen_reaction_order_; if (view_count > m->view_count || forward_count > m->forward_count || need_update_reply_info || need_update_reactions || need_update_unread_reactions || need_update_chosen_reaction_order) { LOG(DEBUG) << "Update interaction info of " << message_full_id << " from " << m->view_count << '/' << m->forward_count << '/' << m->reply_info << '/' << m->reactions << " to " << view_count << '/' << forward_count << '/' << reply_info << '/' << reactions << ", need_update_reply_info = " << need_update_reply_info << ", need_update_reactions = " << need_update_reactions << ", need_update_unread_reactions = " << need_update_unread_reactions << ", need_update_chosen_reaction_order = " << need_update_chosen_reaction_order; bool need_update = false; if (view_count > m->view_count) { m->view_count = view_count; need_update = true; } if (forward_count > m->forward_count) { m->forward_count = forward_count; need_update = true; } if (need_update_reply_info) { if (m->reply_info.channel_id_ != reply_info.channel_id_) { if (m->reply_info.channel_id_.is_valid() && reply_info.channel_id_.is_valid() && m->message_id.is_server()) { LOG(ERROR) << "Reply info of " << message_full_id << " changed from " << m->reply_info << " to " << reply_info << " from " << source; } } m->reply_info = std::move(reply_info); if (!m->top_thread_message_id.is_valid() && is_thread_message(dialog_id, m)) { m->top_thread_message_id = m->message_id; } need_update |= is_visible_message_reply_info(dialog_id, m); } int32 new_dialog_unread_reaction_count = -1; if (need_update_reactions || need_update_unread_reactions) { CHECK(m->message_id.is_valid()); int32 unread_reaction_diff = 0; unread_reaction_diff -= (has_unread_message_reactions(dialog_id, m) ? 1 : 0); m->reactions = std::move(reactions); m->available_reactions_generation = d->available_reactions_generation; unread_reaction_diff += (has_unread_message_reactions(dialog_id, m) ? 1 : 0); if (is_visible_message_reactions(dialog_id, m)) { need_update |= need_update_reactions; if (need_update_unread_reactions) { if (unread_reaction_diff != 0) { // remove_message_notification_id(d, m, true, true); if (d->unread_reaction_count + unread_reaction_diff < 0) { if (is_dialog_inited(d)) { LOG(ERROR) << "Unread reaction count of " << dialog_id << " became negative from " << source; } } else { set_dialog_unread_reaction_count(d, d->unread_reaction_count + unread_reaction_diff); on_dialog_updated(dialog_id, "update_message_interaction_info"); } } if (unread_reaction_diff < 0) { send_update_message_unread_reactions(dialog_id, m, d->unread_reaction_count); } else { new_dialog_unread_reaction_count = d->unread_reaction_count; } } } } else if (has_reactions) { bool is_changed = false; if (m->available_reactions_generation != d->available_reactions_generation) { m->available_reactions_generation = d->available_reactions_generation; is_changed = true; } if (need_update_chosen_reaction_order) { m->reactions->chosen_reaction_order_ = std::move(reactions->chosen_reaction_order_); is_changed = true; } if (is_changed) { on_message_changed(d, m, false, "update_message_interaction_info"); } } if (need_update) { send_update_message_interaction_info(dialog_id, m); } if (new_dialog_unread_reaction_count >= 0) { send_update_message_unread_reactions(dialog_id, m, new_dialog_unread_reaction_count); } return true; } else if (has_reactions && m->available_reactions_generation != d->available_reactions_generation) { m->available_reactions_generation = d->available_reactions_generation; on_message_changed(d, m, false, "update_message_interaction_info"); } return false; } void MessagesManager::on_update_live_location_viewed(MessageFullId message_full_id) { LOG(DEBUG) << "Live location was viewed in " << message_full_id; if (!are_active_live_location_messages_loaded_) { get_active_live_location_messages(PromiseCreator::lambda([actor_id = actor_id(this), message_full_id](Unit result) { send_closure(actor_id, &MessagesManager::on_update_live_location_viewed, message_full_id); })); return; } auto active_live_location_message_ids = get_active_live_location_messages(Auto()); if (!td::contains(active_live_location_message_ids, message_full_id)) { LOG(DEBUG) << "Can't find " << message_full_id << " in " << active_live_location_message_ids; return; } send_update_message_live_location_viewed(message_full_id); } void MessagesManager::on_update_some_live_location_viewed(Promise &&promise) { LOG(DEBUG) << "Some live location was viewed"; if (!are_active_live_location_messages_loaded_) { get_active_live_location_messages( PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](Unit result) mutable { send_closure(actor_id, &MessagesManager::on_update_some_live_location_viewed, std::move(promise)); })); return; } // update all live locations, because it is unknown, which exactly was viewed auto active_live_location_message_ids = get_active_live_location_messages(Auto()); for (const auto &message_full_id : active_live_location_message_ids) { send_update_message_live_location_viewed(message_full_id); } promise.set_value(Unit()); } void MessagesManager::on_update_message_extended_media( MessageFullId message_full_id, telegram_api::object_ptr extended_media) { auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "on_update_message_extended_media"); if (d == nullptr) { LOG(INFO) << "Ignore update of message extended media in unknown " << dialog_id; return; } auto m = get_message_force(d, message_full_id.get_message_id(), "on_update_message_extended_media"); if (m == nullptr) { LOG(INFO) << "Ignore update of message extended media in unknown " << message_full_id; return; } auto content = m->content.get(); auto content_type = content->get_type(); if (content_type != MessageContentType::Invoice) { if (content_type != MessageContentType::Unsupported) { LOG(ERROR) << "Receive updateMessageExtendedMedia for " << message_full_id << " of type " << content_type; } return; } if (update_message_content_extended_media(content, std::move(extended_media), dialog_id, td_)) { send_update_message_content(d, m, true, "on_update_message_extended_media"); on_message_changed(d, m, true, "on_update_message_extended_media"); on_message_notification_changed(d, m, "on_update_message_extended_media"); // usually a no-op } } bool MessagesManager::need_skip_bot_commands(DialogId dialog_id, const Message *m) const { if (td_->auth_manager_->is_bot()) { return false; } if (m != nullptr && m->message_id.is_scheduled()) { return true; } auto d = get_dialog(dialog_id); CHECK(d != nullptr); return (d->is_has_bots_inited && !d->has_bots) || is_broadcast_channel(dialog_id); } void MessagesManager::on_external_update_message_content(MessageFullId message_full_id) { Dialog *d = get_dialog(message_full_id.get_dialog_id()); CHECK(d != nullptr); Message *m = get_message(d, message_full_id.get_message_id()); CHECK(m != nullptr); send_update_message_content(d, m, true, "on_external_update_message_content"); // must not call on_message_changed, because the message itself wasn't changed if (m->message_id == d->last_message_id) { send_update_chat_last_message_impl(d, "on_external_update_message_content"); } on_message_notification_changed(d, m, "on_external_update_message_content"); } void MessagesManager::on_update_message_content(MessageFullId message_full_id) { Dialog *d = get_dialog(message_full_id.get_dialog_id()); CHECK(d != nullptr); Message *m = get_message(d, message_full_id.get_message_id()); CHECK(m != nullptr); send_update_message_content(d, m, true, "on_update_message_content"); on_message_changed(d, m, true, "on_update_message_content"); on_message_notification_changed(d, m, "on_update_message_content"); } bool MessagesManager::update_message_contains_unread_mention(Dialog *d, Message *m, bool contains_unread_mention, const char *source) { LOG_CHECK(m != nullptr) << source; CHECK(!m->message_id.is_scheduled()); if (!contains_unread_mention && m->contains_unread_mention) { remove_message_notification_id(d, m, true, true); // must be called before contains_unread_mention is updated m->contains_unread_mention = false; if (d->unread_mention_count == 0) { if (is_dialog_inited(d)) { LOG(ERROR) << "Unread mention count of " << d->dialog_id << " became negative from " << source; } } else { set_dialog_unread_mention_count(d, d->unread_mention_count - 1); on_dialog_updated(d->dialog_id, "update_message_contains_unread_mention"); } LOG(INFO) << "Update unread mention message count in " << d->dialog_id << " to " << d->unread_mention_count << " by reading " << m->message_id << " from " << source; send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateMessageMentionRead"), m->message_id.get(), d->unread_mention_count)); return true; } return false; } bool MessagesManager::remove_message_unread_reactions(Dialog *d, Message *m, const char *source) { CHECK(m != nullptr); CHECK(!m->message_id.is_scheduled()); if (!has_unread_message_reactions(d->dialog_id, m)) { return false; } // remove_message_notification_id(d, m, true, true); m->reactions->unread_reactions_.clear(); if (d->unread_reaction_count == 0) { if (is_dialog_inited(d)) { LOG(ERROR) << "Unread reaction count of " << d->dialog_id << " became negative from " << source; } } else { set_dialog_unread_reaction_count(d, d->unread_reaction_count - 1); on_dialog_updated(d->dialog_id, "remove_message_unread_reactions"); } LOG(INFO) << "Update unread reaction count in " << d->dialog_id << " to " << d->unread_reaction_count << " by reading " << m->message_id << " from " << source; send_update_message_unread_reactions(d->dialog_id, m, d->unread_reaction_count); return true; } void MessagesManager::on_read_channel_inbox(ChannelId channel_id, MessageId max_message_id, int32 server_unread_count, int32 pts, const char *source) { if (td_->auth_manager_->is_bot()) { return; } CHECK(!max_message_id.is_scheduled()); if (!max_message_id.is_valid() && server_unread_count <= 0) { return; } DialogId dialog_id(channel_id); Dialog *d = get_dialog_force(dialog_id, source); if (d == nullptr) { LOG(INFO) << "Receive read inbox in unknown " << dialog_id << " from " << source; return; } /* // dropping unread count can make things worse, so don't drop it if (server_unread_count > 0 && G()->use_message_database() && d->is_last_read_inbox_message_id_inited) { server_unread_count = -1; } */ if (d->pts == pts) { read_history_inbox(d, max_message_id, server_unread_count, source); } else if (d->pts > pts) { // outdated update, need to repair server_unread_count from the server repair_channel_server_unread_count(d); } else { // update from the future, keep it until it can be applied if (pts >= d->pending_read_channel_inbox_pts) { if (d->pending_read_channel_inbox_pts == 0) { schedule_get_channel_difference(dialog_id, pts, MessageId(), 0.001, "on_read_channel_inbox"); } d->pending_read_channel_inbox_pts = pts; d->pending_read_channel_inbox_max_message_id = max_message_id; d->pending_read_channel_inbox_server_unread_count = server_unread_count; on_dialog_updated(dialog_id, "on_read_channel_inbox"); } } } void MessagesManager::on_read_channel_outbox(ChannelId channel_id, MessageId max_message_id) { DialogId dialog_id(channel_id); CHECK(!max_message_id.is_scheduled()); if (max_message_id.is_valid()) { read_history_outbox(dialog_id, max_message_id); } } void MessagesManager::on_update_channel_max_unavailable_message_id(ChannelId channel_id, MessageId max_unavailable_message_id, const char *source) { if (!channel_id.is_valid()) { LOG(ERROR) << "Receive max_unavailable_message_id in invalid " << channel_id << " from " << source; return; } DialogId dialog_id(channel_id); CHECK(!max_unavailable_message_id.is_scheduled()); if (!max_unavailable_message_id.is_valid() && max_unavailable_message_id != MessageId()) { LOG(ERROR) << "Receive wrong max_unavailable_message_id: " << max_unavailable_message_id << " from " << source; max_unavailable_message_id = MessageId(); } set_dialog_max_unavailable_message_id(dialog_id, max_unavailable_message_id, true, source); } void MessagesManager::on_update_dialog_online_member_count(DialogId dialog_id, int32 online_member_count, bool is_from_server) { if (td_->auth_manager_->is_bot()) { return; } if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive number of online members in invalid " << dialog_id; return; } if (is_broadcast_channel(dialog_id)) { LOG_IF(ERROR, online_member_count != 0) << "Receive " << online_member_count << " as a number of online members in a channel " << dialog_id; return; } if (online_member_count < 0) { LOG(ERROR) << "Receive " << online_member_count << " as a number of online members in a " << dialog_id; return; } set_dialog_online_member_count(dialog_id, online_member_count, is_from_server, "on_update_channel_online_member_count"); } void MessagesManager::on_update_delete_scheduled_messages(DialogId dialog_id, vector &&server_message_ids) { if (td_->auth_manager_->is_bot()) { // just in case return; } if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive deleted scheduled messages in invalid " << dialog_id; return; } Dialog *d = get_dialog_force(dialog_id, "on_update_delete_scheduled_messages"); if (d == nullptr) { LOG(INFO) << "Skip updateDeleteScheduledMessages in unknown " << dialog_id; return; } vector deleted_message_ids; for (auto server_message_id : server_message_ids) { if (!server_message_id.is_valid()) { LOG(ERROR) << "Incoming update tries to delete scheduled message " << server_message_id.get(); continue; } auto message = do_delete_scheduled_message(d, MessageId(server_message_id, std::numeric_limits::max()), true, "on_update_delete_scheduled_messages"); if (message != nullptr) { deleted_message_ids.push_back(message->message_id.get()); } } send_update_delete_messages(dialog_id, std::move(deleted_message_ids), true); send_update_chat_has_scheduled_messages(d, true); } void MessagesManager::on_update_created_public_broadcasts(vector channel_ids) { if (td_->auth_manager_->is_bot()) { // just in case return; } if (created_public_broadcasts_inited_ && created_public_broadcasts_ == channel_ids) { return; } LOG(INFO) << "Update create public channels to " << channel_ids; for (auto channel_id : channel_ids) { force_create_dialog(DialogId(channel_id), "on_update_created_public_broadcasts"); } created_public_broadcasts_inited_ = true; created_public_broadcasts_ = std::move(channel_ids); } void MessagesManager::on_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, DialogId typing_dialog_id, DialogAction action, int32 date, MessageContentType message_content_type) { if (td_->auth_manager_->is_bot() || !typing_dialog_id.is_valid()) { return; } if (top_thread_message_id != MessageId() && !top_thread_message_id.is_valid()) { LOG(ERROR) << "Ignore " << action << " in the message thread of " << top_thread_message_id; return; } auto dialog_type = dialog_id.get_type(); if (action == DialogAction::get_speaking_action()) { if ((dialog_type != DialogType::Chat && dialog_type != DialogType::Channel) || top_thread_message_id.is_valid()) { LOG(ERROR) << "Receive " << action << " in thread of " << top_thread_message_id << " in " << dialog_id; return; } const Dialog *d = get_dialog_force(dialog_id, "on_dialog_action"); if (d != nullptr && d->active_group_call_id.is_valid()) { auto group_call_id = td_->group_call_manager_->get_group_call_id(d->active_group_call_id, dialog_id); td_->group_call_manager_->on_user_speaking_in_group_call(group_call_id, typing_dialog_id, false, date); } return; } if (is_broadcast_channel(dialog_id)) { return; } auto typing_dialog_type = typing_dialog_id.get_type(); if (typing_dialog_type != DialogType::User && dialog_type != DialogType::Chat && dialog_type != DialogType::Channel) { LOG(ERROR) << "Ignore " << action << " of " << typing_dialog_id << " in " << dialog_id; return; } { auto message_import_progress = action.get_importing_messages_action_progress(); if (message_import_progress >= 0) { // TODO return; } } { auto clicking_info = action.get_clicking_animated_emoji_action_info(); if (!clicking_info.data.empty()) { if (date > G()->unix_time() - 10 && dialog_type == DialogType::User && dialog_id == typing_dialog_id) { MessageFullId message_full_id{dialog_id, MessageId(ServerMessageId(clicking_info.message_id))}; auto *m = get_message_force(message_full_id, "on_dialog_action"); if (m != nullptr) { on_message_content_animated_emoji_clicked(m->content.get(), message_full_id, td_, std::move(clicking_info.emoji), std::move(clicking_info.data)); } } return; } } if (is_unsent_animated_emoji_click(td_, dialog_id, action)) { LOG(DEBUG) << "Ignore unsent " << action; return; } if (!have_dialog(dialog_id)) { LOG(DEBUG) << "Ignore " << action << " in unknown " << dialog_id; return; } if (typing_dialog_type == DialogType::User) { if (!td_->contacts_manager_->have_min_user(typing_dialog_id.get_user_id())) { LOG(DEBUG) << "Ignore " << action << " of unknown " << typing_dialog_id.get_user_id(); return; } } else { if (!have_dialog_info_force(typing_dialog_id, "on_dialog_action")) { LOG(DEBUG) << "Ignore " << action << " of unknown " << typing_dialog_id; return; } force_create_dialog(typing_dialog_id, "on_dialog_action", true); if (!have_dialog(typing_dialog_id)) { LOG(ERROR) << "Failed to create typing " << typing_dialog_id; return; } } bool is_canceled = action == DialogAction(); if ((!is_canceled || message_content_type != MessageContentType::None) && typing_dialog_type == DialogType::User) { td_->contacts_manager_->on_update_user_local_was_online(typing_dialog_id.get_user_id(), date); } if (dialog_type == DialogType::User || dialog_type == DialogType::SecretChat) { CHECK(typing_dialog_type == DialogType::User); auto user_id = typing_dialog_id.get_user_id(); if (!td_->contacts_manager_->is_user_bot(user_id) && !td_->contacts_manager_->is_user_status_exact(user_id) && !is_dialog_opened(dialog_id) && !is_canceled) { return; } } if (is_canceled) { // passed top_thread_message_id must be ignored auto actions_it = active_dialog_actions_.find(dialog_id); if (actions_it == active_dialog_actions_.end()) { return; } auto &active_actions = actions_it->second; auto it = std::find_if( active_actions.begin(), active_actions.end(), [typing_dialog_id](const ActiveDialogAction &action) { return action.typing_dialog_id == typing_dialog_id; }); if (it == active_actions.end()) { return; } if (!(typing_dialog_type == DialogType::User && td_->contacts_manager_->is_user_bot(typing_dialog_id.get_user_id())) && !it->action.is_canceled_by_message_of_type(message_content_type)) { return; } LOG(DEBUG) << "Cancel action of " << typing_dialog_id << " in " << dialog_id; top_thread_message_id = it->top_thread_message_id; active_actions.erase(it); if (active_actions.empty()) { active_dialog_actions_.erase(dialog_id); LOG(DEBUG) << "Cancel action timeout in " << dialog_id; active_dialog_action_timeout_.cancel_timeout(dialog_id.get()); } } else { if (date < G()->unix_time() - DIALOG_ACTION_TIMEOUT - 60) { LOG(DEBUG) << "Ignore too old action of " << typing_dialog_id << " in " << dialog_id << " sent at " << date; return; } auto &active_actions = active_dialog_actions_[dialog_id]; auto it = std::find_if( active_actions.begin(), active_actions.end(), [typing_dialog_id](const ActiveDialogAction &action) { return action.typing_dialog_id == typing_dialog_id; }); MessageId prev_top_thread_message_id; DialogAction prev_action; if (it != active_actions.end()) { LOG(DEBUG) << "Re-add action of " << typing_dialog_id << " in " << dialog_id; prev_top_thread_message_id = it->top_thread_message_id; prev_action = it->action; active_actions.erase(it); } else { LOG(DEBUG) << "Add action of " << typing_dialog_id << " in " << dialog_id; } active_actions.emplace_back(top_thread_message_id, typing_dialog_id, action, Time::now()); if (top_thread_message_id == prev_top_thread_message_id && action == prev_action) { return; } if (top_thread_message_id != prev_top_thread_message_id && prev_top_thread_message_id.is_valid()) { send_update_chat_action(dialog_id, prev_top_thread_message_id, typing_dialog_id, DialogAction()); } if (active_actions.size() == 1u) { LOG(DEBUG) << "Set action timeout in " << dialog_id; active_dialog_action_timeout_.set_timeout_in(dialog_id.get(), DIALOG_ACTION_TIMEOUT); } } if (top_thread_message_id.is_valid()) { send_update_chat_action(dialog_id, MessageId(), typing_dialog_id, action); } send_update_chat_action(dialog_id, top_thread_message_id, typing_dialog_id, action); } void MessagesManager::cancel_dialog_action(DialogId dialog_id, const Message *m) { CHECK(m != nullptr); if (td_->auth_manager_->is_bot() || m->forward_info != nullptr || m->had_forward_info || m->via_bot_user_id.is_valid() || m->hide_via_bot || m->is_channel_post || m->message_id.is_scheduled()) { return; } on_dialog_action(dialog_id, MessageId() /*ignored*/, get_message_sender(m), DialogAction(), m->date, m->content->get_type()); } void MessagesManager::add_postponed_channel_update(DialogId dialog_id, tl_object_ptr &&update, int32 new_pts, int32 pts_count, Promise &&promise) { postponed_channel_updates_[dialog_id].emplace( new_pts, PendingPtsUpdate(std::move(update), new_pts, pts_count, std::move(promise))); } void MessagesManager::add_pending_channel_update(DialogId dialog_id, tl_object_ptr &&update, int32 new_pts, int32 pts_count, Promise &&promise, const char *source, bool is_postponed_update) { LOG(INFO) << "Receive from " << source << " pending " << to_string(update); CHECK(update != nullptr); if (dialog_id.get_type() != DialogType::Channel) { LOG(ERROR) << "Receive channel update in invalid " << dialog_id << " from " << source << ": " << oneline(to_string(update)); promise.set_value(Unit()); return; } if (pts_count < 0 || new_pts <= pts_count) { LOG(ERROR) << "Receive channel update from " << source << " with wrong PTS = " << new_pts << " or pts_count = " << pts_count << ": " << oneline(to_string(update)); promise.set_value(Unit()); return; } auto channel_id = dialog_id.get_channel_id(); if (!td_->contacts_manager_->have_channel(channel_id) && td_->contacts_manager_->have_min_channel(channel_id)) { td_->updates_manager_->schedule_get_difference("add_pending_channel_update 1"); promise.set_value(Unit()); return; } // TODO need to save all updates that can change result of running queries not associated with PTS (for example // getHistory) and apply them to result of these queries Dialog *d = get_dialog_force(dialog_id, "add_pending_channel_update 2"); if (d == nullptr) { auto pts = load_channel_pts(dialog_id); if (pts > 0) { if (!td_->contacts_manager_->have_channel(channel_id)) { // do not create dialog if there is no info about the channel LOG(INFO) << "There is no info about " << channel_id << ", so ignore " << oneline(to_string(update)); promise.set_value(Unit()); return; } d = add_dialog(dialog_id, "add_pending_channel_update 4"); CHECK(d != nullptr); CHECK(d->pts == pts); update_dialog_pos(d, "add_pending_channel_update 5"); } } if (d == nullptr) { // if there is no dialog, it can be created by the update LOG(INFO) << "Receive pending update from " << source << " about unknown " << dialog_id; if (running_get_channel_difference(dialog_id)) { add_postponed_channel_update(dialog_id, std::move(update), new_pts, pts_count, std::move(promise)); return; } } else { int32 old_pts = d->pts; if (new_pts <= old_pts) { // very old or unuseful update if (update->get_id() == telegram_api::updateNewChannelMessage::ID) { auto update_new_channel_message = static_cast(update.get()); auto message_id = MessageId::get_message_id(update_new_channel_message->message_, false); MessageFullId message_full_id(dialog_id, message_id); if (update_message_ids_.count(message_full_id) > 0) { // apply sent channel message auto added_message_full_id = on_get_message(std::move(update_new_channel_message->message_), true, true, false, "updateNewChannelMessage with an awaited message"); if (added_message_full_id != message_full_id) { LOG(ERROR) << "Failed to add an awaited " << message_full_id << " from " << source; } promise.set_value(Unit()); return; } } if (update->get_id() == updateSentMessage::ID) { auto update_sent_message = static_cast(update.get()); if (being_sent_messages_.count(update_sent_message->random_id_) > 0) { // apply sent channel message on_send_message_success(update_sent_message->random_id_, update_sent_message->message_id_, update_sent_message->date_, update_sent_message->ttl_period_, FileId(), "process old updateSentChannelMessage"); promise.set_value(Unit()); return; } } LOG_IF(WARNING, new_pts == old_pts && pts_count == 0 && !is_allowed_useless_update(update)) << "Receive from " << source << " useless channel update " << oneline(to_string(update)); LOG(INFO) << "Skip already applied channel update"; promise.set_value(Unit()); return; } if (running_get_channel_difference(dialog_id)) { LOG(INFO) << "Postpone channel update, because getChannelDifference is run"; add_postponed_channel_update(dialog_id, std::move(update), new_pts, pts_count, std::move(promise)); return; } if (old_pts == 0) { old_pts = new_pts - pts_count; LOG(INFO) << "Receive first update in " << dialog_id << " with PTS = " << new_pts << " from " << source; } else if (old_pts != new_pts - pts_count) { LOG(INFO) << "Found a gap in the " << dialog_id << " with PTS = " << old_pts << ". new_pts = " << new_pts << ", pts_count = " << pts_count << " in update from " << source; if (d->was_opened || td_->contacts_manager_->get_channel_status(channel_id).is_member() || is_dialog_sponsored(d)) { add_postponed_channel_update(dialog_id, std::move(update), new_pts, pts_count, std::move(promise)); get_channel_difference(dialog_id, old_pts, new_pts, MessageId(), true, "add_pending_channel_update PTS mismatch"); } else { promise.set_value(Unit()); } return; } CHECK(old_pts + pts_count == new_pts); // the update can be applied } if (d == nullptr || pts_count > 0) { if (!process_channel_update(std::move(update)) && channel_get_difference_retry_timeout_.has_timeout(dialog_id.get())) { promise.set_value(Unit()); return; } LOG_CHECK(!running_get_channel_difference(dialog_id)) << '"' << active_get_channel_differences_[dialog_id] << '"'; } else { LOG_IF(INFO, update->get_id() != dummyUpdate::ID) << "Skip useless channel update from " << source << ": " << to_string(update); } if (d == nullptr) { d = get_dialog(dialog_id); if (d == nullptr) { LOG(INFO) << "Update didn't created " << dialog_id; promise.set_value(Unit()); return; } } CHECK(new_pts > d->pts); set_channel_pts(d, new_pts, source); promise.set_value(Unit()); } bool MessagesManager::is_old_channel_update(DialogId dialog_id, int32 new_pts) { CHECK(dialog_id.get_type() == DialogType::Channel); const Dialog *d = get_dialog_force(dialog_id, "is_old_channel_update"); return new_pts <= (d == nullptr ? load_channel_pts(dialog_id) : d->pts); } void MessagesManager::process_pts_update(tl_object_ptr &&update_ptr) { switch (update_ptr->get_id()) { case dummyUpdate::ID: LOG(INFO) << "Process dummyUpdate"; break; case telegram_api::updateNewMessage::ID: { auto update = move_tl_object_as(update_ptr); LOG(INFO) << "Process updateNewMessage"; on_get_message(std::move(update->message_), true, false, false, "updateNewMessage"); break; } case updateSentMessage::ID: { auto update = move_tl_object_as(update_ptr); LOG(INFO) << "Process updateSentMessage " << update->random_id_; on_send_message_success(update->random_id_, update->message_id_, update->date_, update->ttl_period_, FileId(), "process updateSentMessage"); break; } case telegram_api::updateReadMessagesContents::ID: { auto update = move_tl_object_as(update_ptr); LOG(INFO) << "Process updateReadMessageContents"; for (auto &message_id : update->messages_) { read_message_content_from_updates(MessageId(ServerMessageId(message_id)), update->date_); } break; } case telegram_api::updateEditMessage::ID: { auto update = move_tl_object_as(update_ptr); LOG(INFO) << "Process updateEditMessage"; bool had_message = have_message_force(MessageFullId::get_message_full_id(update->message_, false), "updateEditMessage"); auto message_full_id = on_get_message(std::move(update->message_), false, false, false, "updateEditMessage"); on_message_edited(message_full_id, update->pts_, had_message); break; } case telegram_api::updateDeleteMessages::ID: { auto update = move_tl_object_as(update_ptr); LOG(INFO) << "Process updateDeleteMessages"; vector message_ids; for (auto message : update->messages_) { message_ids.push_back(MessageId(ServerMessageId(message))); } delete_messages_from_updates(message_ids, true); break; } case telegram_api::updateReadHistoryInbox::ID: { auto update = move_tl_object_as(update_ptr); LOG(INFO) << "Process updateReadHistoryInbox"; DialogId dialog_id(update->peer_); on_update_dialog_folder_id(dialog_id, FolderId(update->folder_id_)); read_history_inbox(dialog_id, MessageId(ServerMessageId(update->max_id_)), -1 /*update->still_unread_count*/, "updateReadHistoryInbox"); break; } case telegram_api::updateReadHistoryOutbox::ID: { auto update = move_tl_object_as(update_ptr); LOG(INFO) << "Process updateReadHistoryOutbox"; read_history_outbox(DialogId(update->peer_), MessageId(ServerMessageId(update->max_id_))); break; } case telegram_api::updatePinnedMessages::ID: { auto update = move_tl_object_as(update_ptr); LOG(INFO) << "Process updatePinnedMessages"; vector message_ids; for (auto message : update->messages_) { message_ids.push_back(MessageId(ServerMessageId(message))); } update_dialog_pinned_messages_from_updates(DialogId(update->peer_), message_ids, update->pinned_); break; } default: UNREACHABLE(); } update_ptr = nullptr; CHECK(!td_->updates_manager_->running_get_difference()); } bool MessagesManager::process_channel_update(tl_object_ptr &&update_ptr) { switch (update_ptr->get_id()) { case dummyUpdate::ID: LOG(INFO) << "Process dummyUpdate"; break; case updateSentMessage::ID: { auto update = move_tl_object_as(update_ptr); LOG(INFO) << "Process updateSentMessage " << update->random_id_; on_send_message_success(update->random_id_, update->message_id_, update->date_, update->ttl_period_, FileId(), "process updateSentChannelMessage"); break; } case telegram_api::updateNewChannelMessage::ID: { auto update = move_tl_object_as(update_ptr); LOG(INFO) << "Process updateNewChannelMessage"; on_get_message(std::move(update->message_), true, true, false, "updateNewChannelMessage"); break; } case telegram_api::updateDeleteChannelMessages::ID: { auto update = move_tl_object_as(update_ptr); LOG(INFO) << "Process updateDeleteChannelMessages"; ChannelId channel_id(update->channel_id_); if (!channel_id.is_valid()) { LOG(ERROR) << "Receive invalid " << channel_id; break; } vector message_ids; for (auto &message : update->messages_) { auto message_id = MessageId(ServerMessageId(message)); if (message_id.is_valid()) { message_ids.push_back(message_id); } else { LOG(ERROR) << "Receive updateDeleteChannelMessages with message " << message << " in " << channel_id; } } delete_dialog_messages(DialogId(channel_id), message_ids, true, "updateDeleteChannelMessages"); break; } case telegram_api::updateEditChannelMessage::ID: { auto update = move_tl_object_as(update_ptr); LOG(INFO) << "Process updateEditChannelMessage"; bool had_message = have_message_force(MessageFullId::get_message_full_id(update->message_, false), "updateEditChannelMessage"); auto message_full_id = on_get_message(std::move(update->message_), false, true, false, "updateEditChannelMessage"); if (message_full_id == MessageFullId()) { return false; } on_message_edited(message_full_id, update->pts_, had_message); break; } case telegram_api::updatePinnedChannelMessages::ID: { auto update = move_tl_object_as(update_ptr); LOG(INFO) << "Process updatePinnedChannelMessages"; ChannelId channel_id(update->channel_id_); if (!channel_id.is_valid()) { LOG(ERROR) << "Receive invalid " << channel_id; break; } vector message_ids; for (auto &message : update->messages_) { message_ids.push_back(MessageId(ServerMessageId(message))); } update_dialog_pinned_messages_from_updates(DialogId(channel_id), message_ids, update->pinned_); break; } default: UNREACHABLE(); } return true; } void MessagesManager::on_message_edited(MessageFullId message_full_id, int32 pts, bool had_message) { if (message_full_id == MessageFullId()) { return; } auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog(dialog_id); Message *m = get_message(d, message_full_id.get_message_id()); CHECK(m != nullptr); m->last_edit_pts = pts; d->last_edited_message_id = m->message_id; if (td_->auth_manager_->is_bot()) { send_update_message_edited(dialog_id, m); } update_used_hashtags(dialog_id, m); if (!had_message && ((m->reactions != nullptr && !m->reactions->unread_reactions_.empty()) || d->unread_reaction_count > 0)) { // if new message with unread reactions was added or the chat has unread reactions, // then number of unread reactions could have been changed, so reload the number of unread reactions repair_dialog_unread_reaction_count(d, Promise(), "on_message_edited"); } } bool MessagesManager::update_dialog_notification_settings(DialogId dialog_id, DialogNotificationSettings *current_settings, DialogNotificationSettings &&new_settings) { if (td_->auth_manager_->is_bot()) { // just in case return false; } auto need_update = need_update_dialog_notification_settings(current_settings, new_settings); if (need_update.are_changed) { Dialog *d = get_dialog(dialog_id); LOG_CHECK(d != nullptr) << "Wrong " << dialog_id << " in update_dialog_notification_settings"; bool was_dialog_mentions_disabled = is_dialog_mention_notifications_disabled(d); VLOG(notifications) << "Update notification settings in " << dialog_id << " from " << *current_settings << " to " << new_settings; update_dialog_unmute_timeout(d, current_settings->use_default_mute_until, current_settings->mute_until, new_settings.use_default_mute_until, new_settings.mute_until); *current_settings = std::move(new_settings); on_dialog_updated(dialog_id, "update_dialog_notification_settings"); if (is_dialog_muted(d)) { // no check for was_muted to clean pending message notifications in chats with unsynchronized settings remove_all_dialog_notifications(d, false, "update_dialog_notification_settings 2"); } if (is_dialog_pinned_message_notifications_disabled(d) && d->notification_info != nullptr && d->notification_info->mention_notification_group_.is_valid() && d->notification_info->pinned_message_notification_message_id_.is_valid()) { remove_dialog_pinned_message_notification(d, "update_dialog_notification_settings 3"); } if (was_dialog_mentions_disabled != is_dialog_mention_notifications_disabled(d)) { if (was_dialog_mentions_disabled) { update_dialog_mention_notification_count(d); } else { remove_dialog_mention_notifications(d); } } if (need_update.need_update_server || need_update.need_update_local) { send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(dialog_id, "updateChatNotificationSettings"), get_chat_notification_settings_object(current_settings))); } } return need_update.need_update_server; } void MessagesManager::schedule_dialog_unmute(DialogId dialog_id, bool use_default, int32 mute_until, int32 unix_time) { if (!use_default && mute_until >= unix_time && mute_until < unix_time + 366 * 86400) { dialog_unmute_timeout_.set_timeout_in(dialog_id.get(), mute_until - unix_time + 1); } else { dialog_unmute_timeout_.cancel_timeout(dialog_id.get()); } } void MessagesManager::update_dialog_unmute_timeout(Dialog *d, bool &old_use_default, int32 &old_mute_until, bool new_use_default, int32 new_mute_until) { if (td_->auth_manager_->is_bot()) { // just in case return; } if (old_use_default == new_use_default && old_mute_until == new_mute_until) { return; } CHECK(d != nullptr); CHECK(old_mute_until >= 0); schedule_dialog_unmute(d->dialog_id, new_use_default, new_mute_until, G()->unix_time()); auto scope = get_dialog_notification_setting_scope(d->dialog_id); auto scope_mute_until = td_->notification_settings_manager_->get_scope_mute_until(scope); bool was_muted = (old_use_default ? scope_mute_until : old_mute_until) != 0; bool is_muted = (new_use_default ? scope_mute_until : new_mute_until) != 0; if (was_muted != is_muted && need_unread_counter(d->order)) { auto unread_count = d->server_unread_count + d->local_unread_count; if (unread_count != 0 || d->is_marked_as_unread) { for (auto &list : get_dialog_lists(d)) { if (unread_count != 0 && list.is_message_unread_count_inited_) { int32 delta = was_muted ? -unread_count : unread_count; list.unread_message_muted_count_ += delta; send_update_unread_message_count(list, d->dialog_id, true, "update_dialog_unmute_timeout"); } if (list.is_dialog_unread_count_inited_) { int32 delta = was_muted ? -1 : 1; list.unread_dialog_muted_count_ += delta; if (unread_count == 0 && d->is_marked_as_unread) { list.unread_dialog_muted_marked_count_ += delta; } send_update_unread_chat_count(list, d->dialog_id, true, "update_dialog_unmute_timeout"); } } } } old_use_default = new_use_default; old_mute_until = new_mute_until; if (was_muted != is_muted && td_->dialog_filter_manager_->have_dialog_filters()) { update_dialog_lists(d, get_dialog_positions(d), true, false, "update_dialog_unmute_timeout"); } } void MessagesManager::on_update_notification_scope_is_muted(NotificationSettingsScope scope, bool is_muted) { if (td_->auth_manager_->is_bot()) { // just in case return; } if (G()->use_message_database()) { std::unordered_map delta; std::unordered_map total_count; std::unordered_map marked_count; std::unordered_set dialog_list_ids; dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr &dialog) { Dialog *d = dialog.get(); if (need_unread_counter(d->order) && d->notification_settings.use_default_mute_until && get_dialog_notification_setting_scope(d->dialog_id) == scope) { int32 unread_count = d->server_unread_count + d->local_unread_count; if (unread_count != 0) { for (auto dialog_list_id : get_dialog_list_ids(d)) { delta[dialog_list_id] += unread_count; total_count[dialog_list_id]++; dialog_list_ids.insert(dialog_list_id); } } else if (d->is_marked_as_unread) { for (auto dialog_list_id : get_dialog_list_ids(d)) { total_count[dialog_list_id]++; marked_count[dialog_list_id]++; dialog_list_ids.insert(dialog_list_id); } } } }); for (auto dialog_list_id : dialog_list_ids) { auto *list = get_dialog_list(dialog_list_id); CHECK(list != nullptr); if (delta[dialog_list_id] != 0 && list->is_message_unread_count_inited_) { if (is_muted) { list->unread_message_muted_count_ += delta[dialog_list_id]; } else { list->unread_message_muted_count_ -= delta[dialog_list_id]; } send_update_unread_message_count(*list, DialogId(), true, "on_update_notification_scope_is_muted"); } if (total_count[dialog_list_id] != 0 && list->is_dialog_unread_count_inited_) { if (is_muted) { list->unread_dialog_muted_count_ += total_count[dialog_list_id]; list->unread_dialog_muted_marked_count_ += marked_count[dialog_list_id]; } else { list->unread_dialog_muted_count_ -= total_count[dialog_list_id]; list->unread_dialog_muted_marked_count_ -= marked_count[dialog_list_id]; } send_update_unread_chat_count(*list, DialogId(), true, "on_update_notification_scope_is_muted"); } } } if (td_->dialog_filter_manager_->have_dialog_filters()) { dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr &dialog) { Dialog *d = dialog.get(); if (need_unread_counter(d->order) && d->notification_settings.use_default_mute_until && get_dialog_notification_setting_scope(d->dialog_id) == scope) { update_dialog_lists(d, get_dialog_positions(d), true, false, "on_update_notification_scope_is_muted"); } }); } if (is_muted) { dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr &dialog) { Dialog *d = dialog.get(); if (need_unread_counter(d->order) && d->notification_settings.use_default_mute_until && get_dialog_notification_setting_scope(d->dialog_id) == scope) { remove_all_dialog_notifications(d, false, "on_update_notification_scope_is_muted"); } }); } } void MessagesManager::on_dialog_unmute(DialogId dialog_id) { if (td_->auth_manager_->is_bot()) { // just in case return; } auto d = get_dialog(dialog_id); CHECK(d != nullptr); if (d->notification_settings.use_default_mute_until) { return; } if (d->notification_settings.mute_until == 0) { return; } auto unix_time = G()->unix_time(); if (d->notification_settings.mute_until > unix_time) { LOG(INFO) << "Failed to unmute " << dialog_id << " in " << unix_time << ", will be unmuted in " << d->notification_settings.mute_until; schedule_dialog_unmute(dialog_id, false, d->notification_settings.mute_until, unix_time); return; } LOG(INFO) << "Unmute " << dialog_id; update_dialog_unmute_timeout(d, d->notification_settings.use_default_mute_until, d->notification_settings.mute_until, false, 0); send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(dialog_id, "updateChatNotificationSettings 2"), get_chat_notification_settings_object(&d->notification_settings))); on_dialog_updated(dialog_id, "on_dialog_unmute"); } void MessagesManager::on_update_dialog_notify_settings( DialogId dialog_id, tl_object_ptr &&peer_notify_settings, const char *source) { if (td_->auth_manager_->is_bot()) { return; } VLOG(notifications) << "Receive notification settings for " << dialog_id << " from " << source << ": " << to_string(peer_notify_settings); DialogNotificationSettings *current_settings = get_dialog_notification_settings(dialog_id, true); if (current_settings == nullptr) { return; } DialogNotificationSettings notification_settings = ::td::get_dialog_notification_settings(std::move(peer_notify_settings), current_settings); if (!notification_settings.is_synchronized) { return; } update_dialog_notification_settings(dialog_id, current_settings, std::move(notification_settings)); } void MessagesManager::on_update_dialog_available_reactions( DialogId dialog_id, telegram_api::object_ptr &&available_reactions) { Dialog *d = get_dialog_force(dialog_id, "on_update_dialog_available_reactions"); if (d == nullptr) { return; } set_dialog_available_reactions(d, ChatReactions(std::move(available_reactions))); } void MessagesManager::set_dialog_available_reactions(Dialog *d, ChatReactions &&available_reactions) { CHECK(d != nullptr); switch (d->dialog_id.get_type()) { case DialogType::Chat: case DialogType::Channel: // ok break; case DialogType::User: case DialogType::SecretChat: default: UNREACHABLE(); break; } if (d->available_reactions == available_reactions) { if (!d->is_available_reactions_inited) { d->is_available_reactions_inited = true; on_dialog_updated(d->dialog_id, "set_dialog_available_reactions"); } return; } LOG(INFO) << "Update available reactions in " << d->dialog_id << " to " << available_reactions; auto old_active_reactions = get_active_reactions(d->available_reactions); auto new_active_reactions = get_active_reactions(available_reactions); bool need_update = old_active_reactions != new_active_reactions; bool need_update_message_reactions_visibility = old_active_reactions.empty() != new_active_reactions.empty() && !td_->auth_manager_->is_bot(); d->available_reactions = std::move(available_reactions); d->is_available_reactions_inited = true; if (need_update_message_reactions_visibility) { if (!old_active_reactions.empty()) { hide_dialog_message_reactions(d); } set_dialog_next_available_reactions_generation(d, d->available_reactions_generation); } on_dialog_updated(d->dialog_id, "set_dialog_available_reactions"); if (need_update) { send_update_chat_available_reactions(d); } } void MessagesManager::set_dialog_next_available_reactions_generation(Dialog *d, uint32 generation) { CHECK(d != nullptr); switch (d->dialog_id.get_type()) { case DialogType::Chat: case DialogType::Channel: // ok break; case DialogType::User: case DialogType::SecretChat: default: UNREACHABLE(); break; } if (get_active_reactions(d->available_reactions).empty()) { // 0 -> 1 // 1 -> 3 generation = generation + (generation & 1) + 1; } else { // 0 -> 2 // 1 -> 2 generation = generation - (generation & 1) + 2; } LOG(INFO) << "Change available reactions generation from " << d->available_reactions_generation << " to " << generation << " in " << d->dialog_id; d->available_reactions_generation = generation; } void MessagesManager::hide_dialog_message_reactions(Dialog *d) { CHECK(!td_->auth_manager_->is_bot()); auto message_ids = find_dialog_messages( d, [](const Message *m) { return m->reactions != nullptr && !m->reactions->reactions_.empty(); }); for (auto message_id : message_ids) { Message *m = get_message(d, message_id); CHECK(m != nullptr); CHECK(m->reactions != nullptr); bool need_update_unread_reactions = !m->reactions->unread_reactions_.empty(); m->reactions = nullptr; // don't resave all messages to the database if (need_update_unread_reactions) { send_update_message_unread_reactions(d->dialog_id, m, d->unread_reaction_count); } send_update_message_interaction_info(d->dialog_id, m); } if (d->unread_reaction_count != 0) { set_dialog_unread_reaction_count(d, 0); } } void MessagesManager::set_active_reactions(vector active_reaction_types) { if (active_reaction_types == active_reaction_types_) { return; } LOG(INFO) << "Set active reactions to " << active_reaction_types; bool is_changed = active_reaction_types != active_reaction_types_; active_reaction_types_ = std::move(active_reaction_types); auto old_active_reaction_pos_ = std::move(active_reaction_pos_); active_reaction_pos_.clear(); for (size_t i = 0; i < active_reaction_types_.size(); i++) { CHECK(!active_reaction_types_[i].is_empty()); active_reaction_pos_[active_reaction_types_[i]] = i; } if (td_->auth_manager_->is_bot()) { return; } dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr &dialog) { Dialog *d = dialog.get(); switch (dialog_id.get_type()) { case DialogType::User: if (is_changed) { send_update_chat_available_reactions(d); } break; case DialogType::Chat: case DialogType::Channel: { auto old_reactions = d->available_reactions.get_active_reactions(old_active_reaction_pos_); auto new_reactions = d->available_reactions.get_active_reactions(active_reaction_pos_); if (old_reactions != new_reactions) { if (old_reactions.empty() != new_reactions.empty()) { if (!old_reactions.empty()) { hide_dialog_message_reactions(d); } set_dialog_next_available_reactions_generation(d, d->available_reactions_generation); on_dialog_updated(d->dialog_id, "set_active_reactions"); } send_update_chat_available_reactions(d); } break; } case DialogType::SecretChat: break; default: UNREACHABLE(); break; } }); } ChatReactions MessagesManager::get_active_reactions(const ChatReactions &available_reactions) const { if (td_->auth_manager_->is_bot()) { return available_reactions; } return available_reactions.get_active_reactions(active_reaction_pos_); } ChatReactions MessagesManager::get_dialog_active_reactions(const Dialog *d) const { CHECK(d != nullptr); switch (d->dialog_id.get_type()) { case DialogType::User: return ChatReactions(true, true); case DialogType::Chat: case DialogType::Channel: return get_active_reactions(d->available_reactions); case DialogType::SecretChat: return {}; default: UNREACHABLE(); return {}; } } // affects all users ChatReactions MessagesManager::get_message_active_reactions(const Dialog *d, const Message *m) const { CHECK(d != nullptr); CHECK(m != nullptr); if (is_service_message_content(m->content->get_type()) || m->ttl > 0) { return ChatReactions(); } auto dialog_id = d->dialog_id; if (is_discussion_message(dialog_id, m)) { d = get_dialog(m->forward_info->from_dialog_id); if (d == nullptr) { LOG(ERROR) << "Failed to find linked " << m->forward_info->from_dialog_id << " to determine correct active reactions"; return ChatReactions(); } } return get_dialog_active_reactions(d); } bool MessagesManager::need_poll_dialog_message_reactions(const Dialog *d) { CHECK(d != nullptr); switch (d->dialog_id.get_type()) { case DialogType::User: case DialogType::SecretChat: return false; case DialogType::Chat: case DialogType::Channel: return (d->available_reactions_generation & 1) == 0; default: UNREACHABLE(); return {}; } } bool MessagesManager::need_poll_message_reactions(const Dialog *d, const Message *m) { CHECK(m != nullptr); if (!m->message_id.is_valid() || !m->message_id.is_server()) { return false; } if (!need_poll_dialog_message_reactions(d)) { return false; } if (m->reactions != nullptr) { return true; } if (m->available_reactions_generation == d->available_reactions_generation) { return false; } if (is_service_message_content(m->content->get_type())) { return false; } return true; } void MessagesManager::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 MessagesManager::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 MessagesManager::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 (pending_read_reactions_.count({dialog_id, message_id}) == 0) { message_ids.push_back(message_id); } } for (auto message_id : message_ids) { it->second.message_ids.erase(message_id); } reload_message_reactions(td_, dialog_id, std::move(message_ids)); } bool MessagesManager::update_dialog_silent_send_message(Dialog *d, bool silent_send_message) { if (td_->auth_manager_->is_bot()) { // just in case return false; } CHECK(d != nullptr); LOG_IF(WARNING, !d->notification_settings.is_synchronized) << "Have unknown notification settings in " << d->dialog_id; if (d->notification_settings.silent_send_message == silent_send_message) { return false; } LOG(INFO) << "Update silent send message in " << d->dialog_id << " to " << silent_send_message; d->notification_settings.silent_send_message = silent_send_message; on_dialog_updated(d->dialog_id, "update_dialog_silent_send_message"); send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateChatDefaultDisableNotification"), silent_send_message)); return true; } void MessagesManager::reget_dialog_action_bar(DialogId dialog_id, const char *source, bool is_repair) { if (G()->close_flag() || !dialog_id.is_valid() || td_->auth_manager_->is_bot()) { return; } Dialog *d = get_dialog_force(dialog_id, source); if (d == nullptr) { return; } if (is_repair && !d->need_repair_action_bar) { d->need_repair_action_bar = true; on_dialog_updated(dialog_id, source); } LOG(INFO) << "Reget action bar in " << dialog_id << " from " << source; switch (dialog_id.get_type()) { case DialogType::User: td_->contacts_manager_->reload_user_full(dialog_id.get_user_id(), Auto(), source); return; case DialogType::Chat: case DialogType::Channel: if (!have_input_peer(dialog_id, AccessRights::Read)) { return; } return td_->create_handler()->send(dialog_id); case DialogType::SecretChat: case DialogType::None: default: UNREACHABLE(); } } void MessagesManager::repair_dialog_action_bar(Dialog *d, const char *source) { CHECK(d != nullptr); auto dialog_id = d->dialog_id; d->need_repair_action_bar = true; if (have_input_peer(dialog_id, AccessRights::Read)) { create_actor( "RepairChatActionBarActor", 1.0, PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, source](Result result) { send_closure(actor_id, &MessagesManager::reget_dialog_action_bar, dialog_id, source, true); })) .release(); } // there is no need to change action bar on_dialog_updated(dialog_id, source); } void MessagesManager::hide_dialog_action_bar(DialogId dialog_id) { Dialog *d = get_dialog_force(dialog_id, "hide_dialog_action_bar"); if (d == nullptr) { return; } hide_dialog_action_bar(d); } void MessagesManager::hide_dialog_action_bar(Dialog *d) { CHECK(d->dialog_id.get_type() != DialogType::SecretChat); if (!d->know_action_bar) { return; } if (d->need_repair_action_bar) { d->need_repair_action_bar = false; on_dialog_updated(d->dialog_id, "hide_dialog_action_bar"); } if (d->action_bar == nullptr) { return; } d->action_bar = nullptr; send_update_chat_action_bar(d); } void MessagesManager::remove_dialog_action_bar(DialogId dialog_id, Promise &&promise) { Dialog *d = get_dialog_force(dialog_id, "remove_dialog_action_bar"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } if (dialog_id.get_type() == DialogType::SecretChat) { dialog_id = DialogId(td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id())); d = get_dialog_force(dialog_id, "remove_dialog_action_bar 2"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat with the user not found")); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } } if (!d->know_action_bar) { return promise.set_error(Status::Error(400, "Can't update chat action bar")); } if (d->need_repair_action_bar) { d->need_repair_action_bar = false; on_dialog_updated(dialog_id, "remove_dialog_action_bar"); } if (d->action_bar == nullptr) { return promise.set_value(Unit()); } d->action_bar = nullptr; send_update_chat_action_bar(d); toggle_dialog_report_spam_state_on_server(dialog_id, false, 0, std::move(promise)); } void MessagesManager::repair_dialog_active_group_call_id(DialogId dialog_id) { if (have_input_peer(dialog_id, AccessRights::Read)) { LOG(INFO) << "Repair active voice chat ID in " << dialog_id; create_actor("RepairChatActiveVoiceChatId", 1.0, PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](Result result) { send_closure(actor_id, &MessagesManager::do_repair_dialog_active_group_call_id, dialog_id); })) .release(); } } void MessagesManager::do_repair_dialog_active_group_call_id(DialogId dialog_id) { if (G()->close_flag()) { return; } Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); bool need_repair_active_group_call_id = d->has_active_group_call && !d->active_group_call_id.is_valid(); bool need_repair_expected_group_call_id = d->has_expected_active_group_call_id && d->active_group_call_id != d->expected_active_group_call_id; d->has_expected_active_group_call_id = false; if (!need_repair_active_group_call_id && !need_repair_expected_group_call_id) { return; } if (!have_input_peer(dialog_id, AccessRights::Read)) { return; } reload_dialog_info_full(dialog_id, "do_repair_dialog_active_group_call_id"); } class MessagesManager::ToggleDialogReportSpamStateOnServerLogEvent { public: DialogId dialog_id_; bool is_spam_dialog_; template void store(StorerT &storer) const { td::store(dialog_id_, storer); td::store(is_spam_dialog_, storer); } template void parse(ParserT &parser) { td::parse(dialog_id_, parser); td::parse(is_spam_dialog_, parser); } }; uint64 MessagesManager::save_toggle_dialog_report_spam_state_on_server_log_event(DialogId dialog_id, bool is_spam_dialog) { ToggleDialogReportSpamStateOnServerLogEvent log_event{dialog_id, is_spam_dialog}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ToggleDialogReportSpamStateOnServer, get_log_event_storer(log_event)); } void MessagesManager::toggle_dialog_report_spam_state_on_server(DialogId dialog_id, bool is_spam_dialog, uint64 log_event_id, Promise &&promise) { if (log_event_id == 0 && G()->use_message_database()) { log_event_id = save_toggle_dialog_report_spam_state_on_server_log_event(dialog_id, is_spam_dialog); } 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: case DialogType::Channel: return td_->create_handler(std::move(promise))->send(dialog_id, is_spam_dialog); case DialogType::SecretChat: if (is_spam_dialog) { return td_->create_handler(std::move(promise))->send(dialog_id); } else { auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (!user_id.is_valid()) { return promise.set_error(Status::Error(400, "Peer user not found")); } return td_->create_handler(std::move(promise))->send(DialogId(user_id), false); } case DialogType::None: default: UNREACHABLE(); return; } } bool MessagesManager::can_report_dialog(DialogId dialog_id) const { // doesn't include possibility of report from action bar switch (dialog_id.get_type()) { case DialogType::User: return td_->contacts_manager_->can_report_user(dialog_id.get_user_id()); case DialogType::Chat: return false; case DialogType::Channel: return !td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).is_creator(); case DialogType::SecretChat: return false; case DialogType::None: default: UNREACHABLE(); return false; } } void MessagesManager::report_dialog(DialogId dialog_id, const vector &message_ids, ReportReason &&reason, Promise &&promise) { Dialog *d = get_dialog_force(dialog_id, "report_dialog"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } Dialog *user_d = d; bool is_dialog_spam_report = false; bool can_report_spam = false; if (reason.is_spam() && message_ids.empty()) { // report from action bar if (dialog_id.get_type() == DialogType::SecretChat) { auto user_dialog_id = DialogId(td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id())); user_d = get_dialog_force(user_dialog_id, "report_dialog 2"); if (user_d == nullptr) { return promise.set_error(Status::Error(400, "Chat with the user not found")); } } is_dialog_spam_report = user_d->know_action_bar; can_report_spam = user_d->action_bar != nullptr && user_d->action_bar->can_report_spam(); } if (is_dialog_spam_report && can_report_spam) { hide_dialog_action_bar(user_d); return toggle_dialog_report_spam_state_on_server(dialog_id, true, 0, std::move(promise)); } if (!can_report_dialog(dialog_id)) { if (is_dialog_spam_report) { return promise.set_value(Unit()); } return promise.set_error(Status::Error(400, "Chat can't be reported")); } vector server_message_ids; for (auto message_id : message_ids) { if (message_id.is_scheduled()) { return promise.set_error(Status::Error(400, "Can't report scheduled messages")); } if (message_id.is_valid() && message_id.is_server()) { server_message_ids.push_back(message_id); } } if (dialog_id.get_type() == DialogType::Channel && reason.is_unrelated_location()) { hide_dialog_action_bar(d); } td_->create_handler(std::move(promise))->send(dialog_id, server_message_ids, std::move(reason)); } void MessagesManager::report_dialog_photo(DialogId dialog_id, FileId file_id, ReportReason &&reason, Promise &&promise) { Dialog *d = get_dialog_force(dialog_id, "report_dialog_photo"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } if (!can_report_dialog(dialog_id)) { return promise.set_error(Status::Error(400, "Chat photo can't be reported")); } auto file_view = td_->file_manager_->get_file_view(file_id); if (file_view.empty()) { return promise.set_error(Status::Error(400, "Unknown file ID")); } if (get_main_file_type(file_view.get_type()) != FileType::Photo || !file_view.has_remote_location() || !file_view.remote_location().is_photo()) { return promise.set_error(Status::Error(400, "Only full chat photos can be reported")); } td_->create_handler(std::move(promise)) ->send(dialog_id, file_id, file_view.remote_location().as_input_photo(), std::move(reason)); } void MessagesManager::on_get_peer_settings(DialogId dialog_id, tl_object_ptr &&peer_settings, bool ignore_privacy_exception) { CHECK(peer_settings != nullptr); if (td_->auth_manager_->is_bot()) { return; } if (dialog_id.get_type() == DialogType::User && !ignore_privacy_exception) { td_->contacts_manager_->on_update_user_need_phone_number_privacy_exception(dialog_id.get_user_id(), peer_settings->need_contacts_exception_); } Dialog *d = get_dialog_force(dialog_id, "on_get_peer_settings"); if (d == nullptr) { return; } auto distance = (peer_settings->flags_ & telegram_api::peerSettings::GEO_DISTANCE_MASK) != 0 ? peer_settings->geo_distance_ : -1; if (distance < -1 || d->has_outgoing_messages) { distance = -1; } auto action_bar = DialogActionBar::create(peer_settings->report_spam_, peer_settings->add_contact_, peer_settings->block_contact_, peer_settings->share_contact_, peer_settings->report_geo_, peer_settings->autoarchived_, distance, peer_settings->invite_members_, peer_settings->request_chat_title_, peer_settings->request_chat_broadcast_, peer_settings->request_chat_date_); fix_dialog_action_bar(d, action_bar.get()); if (d->action_bar == action_bar) { if (!d->know_action_bar || d->need_repair_action_bar) { d->know_action_bar = true; d->need_repair_action_bar = false; on_dialog_updated(d->dialog_id, "on_get_peer_settings"); } return; } d->know_action_bar = true; d->need_repair_action_bar = false; d->action_bar = std::move(action_bar); send_update_chat_action_bar(d); } void MessagesManager::fix_dialog_action_bar(const Dialog *d, DialogActionBar *action_bar) { if (action_bar == nullptr) { return; } CHECK(d != nullptr); action_bar->fix(td_, d->dialog_id, d->is_blocked, d->folder_id); } Result MessagesManager::get_login_button_url(MessageFullId message_full_id, int64 button_id) { Dialog *d = get_dialog_force(message_full_id.get_dialog_id(), "get_login_button_url"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } if (!have_input_peer(d->dialog_id, AccessRights::Read)) { return Status::Error(400, "Can't access the chat"); } auto m = get_message_force(d, message_full_id.get_message_id(), "get_login_button_url"); if (m == nullptr) { return Status::Error(400, "Message not found"); } if (m->reply_markup == nullptr || m->reply_markup->type != ReplyMarkup::Type::InlineKeyboard) { return Status::Error(400, "Message has no inline keyboard"); } if (m->message_id.is_scheduled()) { return Status::Error(400, "Can't use login buttons from scheduled messages"); } if (!m->message_id.is_server()) { // it shouldn't have UrlAuth buttons anyway return Status::Error(400, "Message is not server"); } if (d->dialog_id.get_type() == DialogType::SecretChat) { // secret chat messages can't have reply markup, so this shouldn't happen now return Status::Error(400, "Message is in a secret chat"); } if (button_id < std::numeric_limits::min() || button_id > std::numeric_limits::max()) { return Status::Error(400, "Invalid button identifier specified"); } for (auto &row : m->reply_markup->inline_keyboard) { for (auto &button : row) { if (button.type == InlineKeyboardButton::Type::UrlAuth && button.id == button_id) { return button.data; } } } return Status::Error(400, "Button not found"); } void MessagesManager::load_secret_thumbnail(FileId thumbnail_file_id) { class Callback final : public FileManager::DownloadCallback { public: explicit Callback(Promise download_promise) : download_promise_(std::move(download_promise)) { } void on_download_ok(FileId file_id) final { download_promise_.set_value(Unit()); } void on_download_error(FileId file_id, Status error) final { download_promise_.set_error(std::move(error)); } private: Promise download_promise_; }; auto thumbnail_promise = PromiseCreator::lambda([actor_id = actor_id(this), thumbnail_file_id](Result r_thumbnail) { BufferSlice thumbnail_slice; if (r_thumbnail.is_ok()) { thumbnail_slice = r_thumbnail.move_as_ok(); } send_closure(actor_id, &MessagesManager::on_load_secret_thumbnail, thumbnail_file_id, std::move(thumbnail_slice)); }); auto download_promise = PromiseCreator::lambda( [thumbnail_file_id, thumbnail_promise = std::move(thumbnail_promise)](Result r_download) mutable { if (r_download.is_error()) { thumbnail_promise.set_error(r_download.move_as_error()); return; } send_closure(G()->file_manager(), &FileManager::get_content, thumbnail_file_id, std::move(thumbnail_promise)); }); send_closure(G()->file_manager(), &FileManager::download, thumbnail_file_id, std::make_shared(std::move(download_promise)), 1, -1, -1, Promise>()); } void MessagesManager::on_upload_media(FileId file_id, tl_object_ptr input_file, tl_object_ptr input_encrypted_file) { LOG(INFO) << "File " << file_id << " has been uploaded"; auto it = being_uploaded_files_.find(file_id); if (it == being_uploaded_files_.end()) { // callback may be called just before the file upload was canceled return; } auto message_full_id = it->second.first; auto thumbnail_file_id = it->second.second; being_uploaded_files_.erase(it); Message *m = get_message(message_full_id); if (m == nullptr) { // message has already been deleted by the user or sent to inaccessible channel, do not need to send or edit it // file upload should be already canceled in cancel_send_message_query, it shouldn't happen LOG(ERROR) << "Message with a media has already been deleted"; return; } bool is_edit = m->message_id.is_any_server(); auto dialog_id = message_full_id.get_dialog_id(); auto can_send_status = can_send_message(dialog_id); if (!is_edit && can_send_status.is_error()) { // user has left the chat during upload of the file or lost their privileges LOG(INFO) << "Can't send a message to " << dialog_id << ": " << can_send_status; fail_send_message(message_full_id, std::move(can_send_status)); return; } switch (dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: case DialogType::Channel: if (input_file && thumbnail_file_id.is_valid()) { // TODO: download thumbnail if needed (like in secret chats) LOG(INFO) << "Ask to upload thumbnail " << thumbnail_file_id; bool is_inserted = being_uploaded_thumbnails_ .emplace(thumbnail_file_id, UploadedThumbnailInfo{message_full_id, file_id, std::move(input_file)}) .second; CHECK(is_inserted); td_->file_manager_->upload(thumbnail_file_id, upload_thumbnail_callback_, 32, m->message_id.get()); } else { do_send_media(dialog_id, m, file_id, thumbnail_file_id, std::move(input_file), nullptr); } break; case DialogType::SecretChat: if (thumbnail_file_id.is_valid()) { LOG(INFO) << "Ask to load thumbnail " << thumbnail_file_id; bool is_inserted = being_loaded_secret_thumbnails_ .emplace(thumbnail_file_id, UploadedSecretThumbnailInfo{message_full_id, file_id, std::move(input_encrypted_file)}) .second; CHECK(is_inserted); load_secret_thumbnail(thumbnail_file_id); } else { do_send_secret_media(dialog_id, m, file_id, thumbnail_file_id, std::move(input_encrypted_file), BufferSlice()); } break; case DialogType::None: default: UNREACHABLE(); break; } } void MessagesManager::do_send_media(DialogId dialog_id, Message *m, FileId file_id, FileId thumbnail_file_id, tl_object_ptr input_file, tl_object_ptr input_thumbnail) { CHECK(m != nullptr); bool have_input_file = input_file != nullptr; bool have_input_thumbnail = input_thumbnail != nullptr; LOG(INFO) << "Do send media file " << file_id << " with thumbnail " << thumbnail_file_id << ", have_input_file = " << have_input_file << ", have_input_thumbnail = " << have_input_thumbnail << ", self-destruct time = " << m->ttl; MessageContent *content = nullptr; if (m->message_id.is_any_server()) { content = m->edited_content.get(); if (content == nullptr) { LOG(ERROR) << "Message has no edited content"; return; } } else { content = m->content.get(); } auto input_media = get_input_media(content, td_, std::move(input_file), std::move(input_thumbnail), file_id, thumbnail_file_id, m->ttl, m->send_emoji, true); LOG_CHECK(input_media != nullptr) << to_string(get_message_object(dialog_id, m, "do_send_media")) << ' ' << have_input_file << ' ' << have_input_thumbnail << ' ' << file_id << ' ' << thumbnail_file_id << ' ' << m->ttl; on_message_media_uploaded(dialog_id, m, std::move(input_media), file_id, thumbnail_file_id); } void MessagesManager::do_send_secret_media(DialogId dialog_id, Message *m, FileId file_id, FileId thumbnail_file_id, tl_object_ptr input_encrypted_file, BufferSlice thumbnail) { CHECK(dialog_id.get_type() == DialogType::SecretChat); CHECK(m != nullptr); CHECK(m->message_id.is_valid()); CHECK(m->message_id.is_yet_unsent()); bool have_input_file = input_encrypted_file != nullptr; LOG(INFO) << "Do send secret media file " << file_id << " with thumbnail " << thumbnail_file_id << ", have_input_file = " << have_input_file; auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()); on_secret_message_media_uploaded( dialog_id, m, get_secret_input_media(m->content.get(), td_, std::move(input_encrypted_file), std::move(thumbnail), layer), file_id, thumbnail_file_id); } void MessagesManager::on_upload_media_error(FileId file_id, Status status) { if (G()->close_flag()) { // do not fail upload if closing return; } LOG(WARNING) << "File " << file_id << " has upload error " << status; CHECK(status.is_error()); auto it = being_uploaded_files_.find(file_id); if (it == being_uploaded_files_.end()) { // callback may be called just before the file upload was canceled return; } auto message_full_id = it->second.first; being_uploaded_files_.erase(it); bool is_edit = message_full_id.get_message_id().is_any_server(); if (is_edit) { fail_edit_message_media(message_full_id, std::move(status)); } else { fail_send_message(message_full_id, std::move(status)); } } void MessagesManager::on_load_secret_thumbnail(FileId thumbnail_file_id, BufferSlice thumbnail) { if (G()->close_flag()) { // do not send secret media if closing, thumbnail may be wrong return; } LOG(INFO) << "SecretThumbnail " << thumbnail_file_id << " has been loaded with size " << thumbnail.size(); auto it = being_loaded_secret_thumbnails_.find(thumbnail_file_id); if (it == being_loaded_secret_thumbnails_.end()) { // just in case, as in on_upload_thumbnail return; } auto message_full_id = it->second.message_full_id; auto file_id = it->second.file_id; auto input_file = std::move(it->second.input_file); being_loaded_secret_thumbnails_.erase(it); Message *m = get_message(message_full_id); if (m == nullptr) { // message has already been deleted by the user, do not need to send it // cancel file upload of the main file to allow next upload with the same file to succeed LOG(INFO) << "Message with a media has already been deleted"; cancel_upload_file(file_id, "on_load_secret_thumbnail"); return; } CHECK(m->message_id.is_yet_unsent()); if (thumbnail.empty()) { delete_message_content_thumbnail(m->content.get(), td_); } auto dialog_id = message_full_id.get_dialog_id(); auto can_send_status = can_send_message(dialog_id); if (can_send_status.is_error()) { // secret chat was closed during load of the file LOG(INFO) << "Can't send a message to " << dialog_id << ": " << can_send_status; fail_send_message(message_full_id, std::move(can_send_status)); return; } do_send_secret_media(dialog_id, m, file_id, thumbnail_file_id, std::move(input_file), std::move(thumbnail)); } void MessagesManager::on_upload_thumbnail(FileId thumbnail_file_id, tl_object_ptr thumbnail_input_file) { if (G()->close_flag()) { // do not fail upload if closing return; } LOG(INFO) << "Thumbnail " << thumbnail_file_id << " has been uploaded as " << to_string(thumbnail_input_file); auto it = being_uploaded_thumbnails_.find(thumbnail_file_id); if (it == being_uploaded_thumbnails_.end()) { // callback may be called just before the thumbnail upload was canceled return; } auto message_full_id = it->second.message_full_id; auto file_id = it->second.file_id; auto input_file = std::move(it->second.input_file); being_uploaded_thumbnails_.erase(it); Message *m = get_message(message_full_id); if (m == nullptr) { // message has already been deleted by the user or sent to inaccessible channel, do not need to send or edit it // thumbnail file upload should be already canceled in cancel_send_message_query LOG(ERROR) << "Message with a media has already been deleted"; return; } bool is_edit = m->message_id.is_any_server(); if (thumbnail_input_file == nullptr) { delete_message_content_thumbnail(is_edit ? m->edited_content.get() : m->content.get(), td_); } auto dialog_id = message_full_id.get_dialog_id(); auto can_send_status = can_send_message(dialog_id); if (!is_edit && can_send_status.is_error()) { // user has left the chat during upload of the thumbnail or lost their privileges LOG(INFO) << "Can't send a message to " << dialog_id << ": " << can_send_status; fail_send_message(message_full_id, std::move(can_send_status)); return; } do_send_media(dialog_id, m, file_id, thumbnail_file_id, std::move(input_file), std::move(thumbnail_input_file)); } void MessagesManager::on_upload_dialog_photo(FileId file_id, tl_object_ptr input_file) { LOG(INFO) << "File " << file_id << " has been uploaded"; auto it = being_uploaded_dialog_photos_.find(file_id); if (it == being_uploaded_dialog_photos_.end()) { // just in case, as in on_upload_media return; } DialogId dialog_id = it->second.dialog_id; double main_frame_timestamp = it->second.main_frame_timestamp; bool is_animation = it->second.is_animation; bool is_reupload = it->second.is_reupload; Promise promise = std::move(it->second.promise); being_uploaded_dialog_photos_.erase(it); FileView file_view = td_->file_manager_->get_file_view(file_id); CHECK(!file_view.is_encrypted()); if (input_file == nullptr && file_view.has_remote_location()) { if (file_view.main_remote_location().is_web()) { return promise.set_error(Status::Error(400, "Can't use web photo as profile photo")); } if (is_reupload) { return promise.set_error(Status::Error(400, "Failed to reupload the file")); } if (is_animation) { CHECK(file_view.get_type() == FileType::Animation); // delete file reference and forcely reupload the file auto file_reference = FileManager::extract_file_reference(file_view.main_remote_location().as_input_document()); td_->file_manager_->delete_file_reference(file_id, file_reference); upload_dialog_photo(dialog_id, file_id, is_animation, main_frame_timestamp, true, std::move(promise), {-1}); } else { CHECK(file_view.get_type() == FileType::Photo); auto input_photo = file_view.main_remote_location().as_input_photo(); auto input_chat_photo = make_tl_object(std::move(input_photo)); send_edit_dialog_photo_query(dialog_id, file_id, std::move(input_chat_photo), std::move(promise)); } return; } CHECK(input_file != nullptr); int32 flags = 0; tl_object_ptr photo_input_file; tl_object_ptr video_input_file; if (is_animation) { flags |= telegram_api::inputChatUploadedPhoto::VIDEO_MASK; video_input_file = std::move(input_file); if (main_frame_timestamp != 0.0) { flags |= telegram_api::inputChatUploadedPhoto::VIDEO_START_TS_MASK; } } else { flags |= telegram_api::inputChatUploadedPhoto::FILE_MASK; photo_input_file = std::move(input_file); } auto input_chat_photo = make_tl_object( flags, std::move(photo_input_file), std::move(video_input_file), main_frame_timestamp, nullptr); send_edit_dialog_photo_query(dialog_id, file_id, std::move(input_chat_photo), std::move(promise)); } void MessagesManager::on_upload_dialog_photo_error(FileId file_id, Status status) { if (G()->close_flag()) { // do not fail upload if closing return; } LOG(INFO) << "File " << file_id << " has upload error " << status; CHECK(status.is_error()); auto it = being_uploaded_dialog_photos_.find(file_id); if (it == being_uploaded_dialog_photos_.end()) { // just in case, as in on_upload_media_error return; } Promise promise = std::move(it->second.promise); being_uploaded_dialog_photos_.erase(it); promise.set_error(std::move(status)); } void MessagesManager::on_upload_imported_messages(FileId file_id, tl_object_ptr input_file) { LOG(INFO) << "File " << file_id << " has been uploaded"; auto it = being_uploaded_imported_messages_.find(file_id); if (it == being_uploaded_imported_messages_.end()) { // just in case, as in on_upload_media return; } CHECK(it->second != nullptr); DialogId dialog_id = it->second->dialog_id; vector attached_file_ids = std::move(it->second->attached_file_ids); bool is_reupload = it->second->is_reupload; Promise promise = std::move(it->second->promise); being_uploaded_imported_messages_.erase(it); TRY_STATUS_PROMISE(promise, can_send_message(dialog_id)); FileView file_view = td_->file_manager_->get_file_view(file_id); CHECK(!file_view.is_encrypted()); if (input_file == nullptr && file_view.has_remote_location()) { if (file_view.main_remote_location().is_web()) { return promise.set_error(Status::Error(400, "Can't use web file")); } if (is_reupload) { return promise.set_error(Status::Error(400, "Failed to reupload the file")); } CHECK(file_view.get_type() == FileType::Document); // delete file reference and forcely reupload the file auto file_reference = FileManager::extract_file_reference(file_view.main_remote_location().as_input_document()); td_->file_manager_->delete_file_reference(file_id, file_reference); upload_imported_messages(dialog_id, file_id, std::move(attached_file_ids), true, std::move(promise), {-1}); return; } CHECK(input_file != nullptr); td_->create_handler(std::move(promise)) ->send(dialog_id, file_id, std::move(input_file), std::move(attached_file_ids)); } void MessagesManager::on_upload_imported_messages_error(FileId file_id, Status status) { if (G()->close_flag()) { // do not fail upload if closing return; } LOG(INFO) << "File " << file_id << " has upload error " << status; CHECK(status.is_error()); auto it = being_uploaded_imported_messages_.find(file_id); if (it == being_uploaded_imported_messages_.end()) { // just in case, as in on_upload_media_error return; } Promise promise = std::move(it->second->promise); being_uploaded_imported_messages_.erase(it); promise.set_error(std::move(status)); } void MessagesManager::on_upload_imported_message_attachment(FileId file_id, tl_object_ptr input_file) { LOG(INFO) << "File " << file_id << " has been uploaded"; auto it = being_uploaded_imported_message_attachments_.find(file_id); if (it == being_uploaded_imported_message_attachments_.end()) { // just in case, as in on_upload_media return; } CHECK(it->second != nullptr); DialogId dialog_id = it->second->dialog_id; int64 import_id = it->second->import_id; bool is_reupload = it->second->is_reupload; Promise promise = std::move(it->second->promise); being_uploaded_imported_message_attachments_.erase(it); FileView file_view = td_->file_manager_->get_file_view(file_id); CHECK(!file_view.is_encrypted()); if (input_file == nullptr && file_view.has_remote_location()) { if (file_view.main_remote_location().is_web()) { return promise.set_error(Status::Error(400, "Can't use web file")); } if (is_reupload) { return promise.set_error(Status::Error(400, "Failed to reupload the file")); } // delete file reference and forcely reupload the file auto file_reference = file_view.get_type() == FileType::Photo ? FileManager::extract_file_reference(file_view.main_remote_location().as_input_photo()) : FileManager::extract_file_reference(file_view.main_remote_location().as_input_document()); td_->file_manager_->delete_file_reference(file_id, file_reference); upload_imported_message_attachment(dialog_id, import_id, file_id, true, std::move(promise), {-1}); return; } CHECK(input_file != nullptr); auto suggested_path = file_view.suggested_path(); const PathView path_view(suggested_path); td_->create_handler(std::move(promise)) ->send(dialog_id, import_id, path_view.file_name().str(), file_id, get_fake_input_media(td_, std::move(input_file), file_id)); } void MessagesManager::on_upload_imported_message_attachment_error(FileId file_id, Status status) { if (G()->close_flag()) { // do not fail upload if closing return; } LOG(INFO) << "File " << file_id << " has upload error " << status; CHECK(status.is_error()); auto it = being_uploaded_imported_message_attachments_.find(file_id); if (it == being_uploaded_imported_message_attachments_.end()) { // just in case, as in on_upload_media_error return; } Promise promise = std::move(it->second->promise); being_uploaded_imported_message_attachments_.erase(it); promise.set_error(std::move(status)); } void MessagesManager::before_get_difference() { running_get_difference_ = true; // scheduled messages are not returned in getDifference, so we must always reget them after it scheduled_messages_sync_generation_++; } void MessagesManager::after_get_difference() { CHECK(!td_->updates_manager_->running_get_difference()); running_get_difference_ = false; if (!pending_on_get_dialogs_.empty()) { LOG(INFO) << "Apply postponed results of getDialogs"; for (auto &res : pending_on_get_dialogs_) { on_get_dialogs(res.folder_id, std::move(res.dialogs), res.total_count, std::move(res.messages), std::move(res.promise)); } pending_on_get_dialogs_.clear(); } if (!postponed_chat_read_inbox_updates_.empty()) { LOG(INFO) << "Send postponed chat read inbox updates"; auto dialog_ids = std::move(postponed_chat_read_inbox_updates_); for (auto dialog_id : dialog_ids) { send_update_chat_read_inbox(get_dialog(dialog_id), false, "after_get_difference"); } } while (!postponed_unread_message_count_updates_.empty()) { auto *list = get_dialog_list(*postponed_unread_message_count_updates_.begin()); CHECK(list != nullptr); send_update_unread_message_count(*list, DialogId(), true, "after_get_difference"); } while (!postponed_unread_chat_count_updates_.empty()) { auto *list = get_dialog_list(*postponed_unread_chat_count_updates_.begin()); CHECK(list != nullptr); send_update_unread_chat_count(*list, DialogId(), true, "after_get_difference"); } vector update_message_ids_to_delete; for (auto &it : update_message_ids_) { // there can be unhandled updateMessageId updates after getDifference even for ordinary chats, // because despite updates coming during getDifference have already been applied, // some of them could be postponed because of PTS gap auto message_full_id = it.first; auto dialog_id = message_full_id.get_dialog_id(); auto message_id = message_full_id.get_message_id(); auto old_message_id = it.second; CHECK(message_id.is_valid()); CHECK(message_id.is_server()); switch (dialog_id.get_type()) { case DialogType::Channel: // get channel difference may prevent updates from being applied if (running_get_channel_difference(dialog_id)) { break; } // fallthrough case DialogType::User: case DialogType::Chat: { if (!have_message_force({dialog_id, old_message_id}, "after get difference")) { // The sent message has already been deleted by the user or sent to inaccessible channel. // The sent message may never be received, but we will need updateMessageId in case the message is received // to delete it from the server and not add to the chat. // But if the chat is inaccessible or the message is in an inaccessible chat part, then we will not be able to // add the message or delete it from the server. In this case we forget updateMessageId for such messages in // order to not check them over and over. const Dialog *d = get_dialog(dialog_id); if (!have_input_peer(dialog_id, AccessRights::Read) || (d != nullptr && message_id <= td::max(d->last_clear_history_message_id, d->max_unavailable_message_id))) { update_message_ids_to_delete.push_back(message_full_id); } break; } const Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); if (dialog_id.get_type() == DialogType::Channel || message_id <= d->last_new_message_id) { LOG(ERROR) << "Receive updateMessageId from " << old_message_id << " to " << message_full_id << " but not receive corresponding message, last_new_message_id = " << d->last_new_message_id; } if (message_id <= d->last_new_message_id) { get_message_from_server( message_full_id, PromiseCreator::lambda([actor_id = actor_id(this), message_full_id, old_message_id](Result result) { send_closure(actor_id, &MessagesManager::on_restore_missing_message_after_get_difference, message_full_id, old_message_id, std::move(result)); }), "get missing"); } else if (dialog_id.get_type() == DialogType::Channel) { schedule_get_channel_difference(dialog_id, 0, message_id, 0.001, "after_get_difference"); } break; } case DialogType::SecretChat: break; case DialogType::None: default: UNREACHABLE(); break; } } for (const auto &message_full_id : update_message_ids_to_delete) { update_message_ids_.erase(message_full_id); } if (!td_->auth_manager_->is_bot()) { if (!G()->td_db()->get_binlog_pmc()->isset("fetched_marks_as_unread")) { td_->create_handler()->send(); } auto dialog_list_id = DialogListId(FolderId::archive()); auto *list = get_dialog_list(dialog_list_id); CHECK(list != nullptr); if (!list->is_dialog_unread_count_inited_) { int32 limit = list->are_pinned_dialogs_inited_ ? static_cast(list->pinned_dialogs_.size()) : get_pinned_dialogs_limit(dialog_list_id); LOG(INFO) << "Loading chat list in " << dialog_list_id << " to init total unread count"; get_dialogs_from_list(dialog_list_id, limit + 2, Auto()); } } } void MessagesManager::on_restore_missing_message_after_get_difference(MessageFullId message_full_id, MessageId old_message_id, Result result) { if (result.is_error()) { LOG(WARNING) << "Failed to get missing " << message_full_id << " for " << old_message_id << ": " << result.error(); } else { LOG(WARNING) << "Successfully get missing " << message_full_id << " for " << old_message_id; bool have_message = have_message_force(message_full_id, "on_restore_missing_message_after_get_difference"); if (!have_message && update_message_ids_.count(message_full_id)) { LOG(ERROR) << "Receive messageEmpty instead of missing " << message_full_id << " for " << old_message_id; delete_dialog_messages(message_full_id.get_dialog_id(), {old_message_id}, false, "on_restore_missing_message_after_get_difference"); update_message_ids_.erase(message_full_id); } } } void MessagesManager::on_get_empty_messages(DialogId dialog_id, const vector &empty_message_ids) { if (!empty_message_ids.empty()) { delete_dialog_messages(dialog_id, empty_message_ids, false, "on_get_empty_messages"); } } void MessagesManager::get_channel_difference_if_needed(DialogId dialog_id, MessagesInfo &&messages_info, Promise &&promise, const char *source) { if (td_->auth_manager_->is_bot()) { return promise.set_value(std::move(messages_info)); } if (!dialog_id.is_valid()) { return get_channel_differences_if_needed(std::move(messages_info), std::move(promise), source); } for (auto &message : messages_info.messages) { if (need_channel_difference_to_add_message(dialog_id, message)) { auto max_message_id = MessageId::get_max_message_id(messages_info.messages); return run_after_channel_difference( dialog_id, max_message_id, PromiseCreator::lambda([messages_info = std::move(messages_info), promise = std::move(promise)]( Unit ignored) mutable { promise.set_value(std::move(messages_info)); }), source); } } promise.set_value(std::move(messages_info)); } void MessagesManager::get_channel_differences_if_needed(MessagesInfo &&messages_info, Promise &&promise, const char *source) { if (td_->auth_manager_->is_bot()) { return promise.set_value(std::move(messages_info)); } MultiPromiseActorSafe mpas{"GetChannelDifferencesIfNeededMultiPromiseActor"}; mpas.add_promise(Promise()); mpas.set_ignore_errors(true); auto lock = mpas.get_promise(); for (auto &message : messages_info.messages) { if (message == nullptr) { continue; } auto dialog_id = DialogId::get_message_dialog_id(message); if (need_channel_difference_to_add_message(dialog_id, message)) { run_after_channel_difference(dialog_id, MessageId::get_message_id(message, false), mpas.get_promise(), source); } } // must be added after messages_info is checked mpas.add_promise(PromiseCreator::lambda([messages_info = std::move(messages_info), promise = std::move(promise)]( Unit ignored) mutable { promise.set_value(std::move(messages_info)); })); lock.set_value(Unit()); } void MessagesManager::get_channel_differences_if_needed( const vector *> &messages, Promise &&promise, const char *source) { if (td_->auth_manager_->is_bot()) { return promise.set_value(Unit()); } MultiPromiseActorSafe mpas{"GetChannelDifferencesIfNeededGenericMultiPromiseActor"}; mpas.add_promise(std::move(promise)); mpas.set_ignore_errors(true); auto lock = mpas.get_promise(); for (const auto &message : messages) { if (message == nullptr) { continue; } auto dialog_id = DialogId::get_message_dialog_id(*message); if (need_channel_difference_to_add_message(dialog_id, *message)) { run_after_channel_difference(dialog_id, MessageId::get_message_id(*message, false), mpas.get_promise(), source); } } lock.set_value(Unit()); } void MessagesManager::on_get_messages(vector> &&messages, bool is_channel_message, bool is_scheduled, Promise &&promise, const char *source) { TRY_STATUS_PROMISE(promise, G()->close_status()); for (auto &message : messages) { LOG(INFO) << "Receive " << to_string(message); on_get_message(std::move(message), false, is_channel_message, is_scheduled, source); } promise.set_value(Unit()); } bool MessagesManager::delete_newer_server_messages_at_the_end(Dialog *d, MessageId max_message_id) { CHECK(!td_->auth_manager_->is_bot()); CHECK(!max_message_id.is_scheduled()); auto message_ids = d->ordered_messages.find_newer_messages(max_message_id); if (message_ids.empty()) { return false; } vector server_message_ids; vector kept_message_ids; for (auto message_id : message_ids) { CHECK(message_id > max_message_id); if (message_id.is_server()) { server_message_ids.push_back(message_id); } else { kept_message_ids.push_back(message_id); } } delete_dialog_messages(d, server_message_ids, false, "delete_newer_server_messages_at_the_end"); // connect all messages with ID > max_message_id for (size_t i = 0; i + 1 < kept_message_ids.size(); i++) { d->ordered_messages.attach_message_to_next(kept_message_ids[i], "delete_newer_server_messages_at_the_end"); } return !kept_message_ids.empty(); } void MessagesManager::on_get_history(DialogId dialog_id, MessageId from_message_id, MessageId old_last_new_message_id, int32 offset, int32 limit, bool from_the_end, vector> &&messages, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); LOG(INFO) << "Receive " << messages.size() << " history messages " << (from_the_end ? "from the end " : "") << "in " << dialog_id << " from " << from_message_id << " with offset " << offset << " and limit " << limit; CHECK(-limit < offset && offset <= 0); CHECK(offset < 0 || from_the_end); if (from_the_end) { CHECK(from_message_id == MessageId()); } else { CHECK(!from_message_id.is_scheduled()); } Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); MessageId last_received_message_id = messages.empty() ? MessageId() : MessageId::get_message_id(messages[0], false); if (old_last_new_message_id < d->last_new_message_id && (from_the_end || old_last_new_message_id < from_message_id) && last_received_message_id < d->last_new_message_id) { // new server messages were added to the dialog since the request was sent, but weren't received // they should have been received, so we must repeat the request to get them get_history_impl(d, from_message_id, offset, limit, false, false, std::move(promise), "on_get_history"); return; } // the server can return less messages than requested if some of messages are deleted during request // but if it happens, it is likely that there are no more messages on the server bool have_full_history = from_the_end && narrow_cast(messages.size()) < limit && messages.size() <= 1; if (messages.empty()) { if (have_full_history) { d->have_full_history = true; d->have_full_history_source = 1; on_dialog_updated(dialog_id, "set have_full_history"); } if (from_the_end && d->have_full_history && d->ordered_messages.empty()) { if (!d->last_database_message_id.is_valid()) { set_dialog_is_empty(d, "on_get_history empty"); } else { LOG(INFO) << "Skip marking " << dialog_id << " as empty, because it probably has messages from " << d->first_database_message_id << " to " << d->last_database_message_id << " in the database"; } } // be aware that in some cases an empty answer may be returned, because of the race of getHistory and deleteMessages // and not because there are no more messages promise.set_value(Unit()); return; } if (messages.size() > 1) { // check that messages are received in decreasing message_id order MessageId cur_message_id = MessageId::max(); for (const auto &message : messages) { MessageId message_id = MessageId::get_message_id(message, false); if (message_id >= cur_message_id) { string error = PSTRING() << "Receive messages in the wrong order in history of " << dialog_id << " from " << from_message_id << " with offset " << offset << ", limit " << limit << ", from_the_end = " << from_the_end << ": "; for (const auto &debug_message : messages) { error += to_string(debug_message); } LOG(ERROR) << error; promise.set_value(Unit()); return; } cur_message_id = message_id; } } // be aware that returned messages are guaranteed to be consecutive messages, but if !from_the_end they // may be older (if some messages was deleted) or newer (if some messages were added) than an expected answer // be aware that any subset of the returned messages may be already deleted and returned as MessageEmpty bool is_channel_message = dialog_id.get_type() == DialogType::Channel; MessageId first_added_message_id; MessageId last_added_message_id; bool have_next = false; if (narrow_cast(messages.size()) < limit + offset && messages.size() <= 1) { MessageId first_received_message_id = MessageId::get_message_id(messages.back(), false); if (first_received_message_id >= from_message_id && d->first_database_message_id.is_valid() && first_received_message_id >= d->first_database_message_id) { // it is likely that there are no more history messages on the server have_full_history = true; } } if (d->last_new_message_id.is_valid()) { // remove too new messages from response while (!messages.empty()) { if (DialogId::get_message_dialog_id(messages[0]) == dialog_id && MessageId::get_message_id(messages[0], false) <= d->last_new_message_id) { // the message is old enough break; } LOG(INFO) << "Ignore too new " << MessageId::get_message_id(messages[0], false); messages.erase(messages.begin()); if (messages.empty()) { // received no suitable messages; try again return promise.set_value(Unit()); } else { last_received_message_id = MessageId::get_message_id(messages[0], false); } } } bool prev_have_full_history = d->have_full_history; MessageId prev_last_new_message_id = d->last_new_message_id; MessageId prev_first_database_message_id = d->first_database_message_id; MessageId prev_last_database_message_id = d->last_database_message_id; MessageId prev_last_message_id = d->last_message_id; if (from_the_end) { // delete all server messages with ID > last_received_message_id // there were no new messages received after the getHistory request was sent, so they are already deleted message if (last_received_message_id.is_valid() && delete_newer_server_messages_at_the_end(d, last_received_message_id)) { have_next = true; } } for (auto &message : messages) { auto expected_message_id = MessageId::get_message_id(message, false); if (!have_next && from_the_end && expected_message_id < d->last_message_id) { // last message in the dialog should be attached to the next message if there is some have_next = true; } auto message_dialog_id = DialogId::get_message_dialog_id(message); if (message_dialog_id != dialog_id) { LOG(ERROR) << "Receive " << expected_message_id << " in wrong " << message_dialog_id << " instead of " << dialog_id << ": " << oneline(to_string(message)); continue; } auto message_full_id = on_get_message(std::move(message), false, is_channel_message, false, "get history"); auto message_id = message_full_id.get_message_id(); if (message_id.is_valid()) { CHECK(message_id == expected_message_id); if (have_next) { d->ordered_messages.attach_message_to_next(message_id, "on_get_history"); } if (!last_added_message_id.is_valid()) { last_added_message_id = message_id; } if (!have_next) { have_next = true; } else if (first_added_message_id.is_valid()) { d->ordered_messages.attach_message_to_previous(first_added_message_id, "on_get_history"); } first_added_message_id = message_id; } } if (from_the_end && last_added_message_id.is_valid() && last_added_message_id != last_received_message_id) { CHECK(last_added_message_id < last_received_message_id); delete_newer_server_messages_at_the_end(d, last_added_message_id); } if (have_full_history) { d->have_full_history = true; d->have_full_history_source = 2; on_dialog_updated(dialog_id, "set have_full_history 2"); } if (from_the_end && !d->last_new_message_id.is_valid()) { set_dialog_last_new_message_id( d, last_added_message_id.is_valid() ? last_added_message_id : last_received_message_id, "on_get_history"); } bool intersect_last_database_message_ids = last_added_message_id >= d->first_database_message_id && d->last_database_message_id >= first_added_message_id; bool need_update_database_message_ids = last_added_message_id.is_valid() && (from_the_end || intersect_last_database_message_ids); if (from_the_end && last_added_message_id.is_valid() && last_added_message_id > d->last_message_id) { CHECK(d->last_new_message_id.is_valid()); set_dialog_last_message_id(d, last_added_message_id, "on_get_history"); send_update_chat_last_message(d, "on_get_history"); } if (need_update_database_message_ids) { if (from_the_end && !intersect_last_database_message_ids && d->last_database_message_id.is_valid()) { if (d->last_database_message_id < first_added_message_id || last_added_message_id == d->last_message_id) { set_dialog_first_database_message_id(d, MessageId(), "on_get_history 1"); set_dialog_last_database_message_id(d, MessageId(), "on_get_history 1"); } else { auto min_message_id = td::min(d->first_database_message_id, d->last_message_id); LOG_CHECK(last_added_message_id < min_message_id) << need_update_database_message_ids << ' ' << first_added_message_id << ' ' << last_added_message_id << ' ' << d->first_database_message_id << ' ' << d->last_database_message_id << ' ' << d->last_new_message_id << ' ' << d->last_message_id << ' ' << prev_first_database_message_id << ' ' << prev_last_database_message_id << ' ' << prev_last_new_message_id << ' ' << prev_last_message_id; if (min_message_id <= last_added_message_id.get_next_message_id(MessageType::Server)) { // connect local messages with last received server message set_dialog_first_database_message_id(d, last_added_message_id, "on_get_history 2"); } else { LOG(WARNING) << "Have last " << d->last_message_id << " and first database " << d->first_database_message_id << " in " << dialog_id << ", but received history from the end only up to " << last_added_message_id; // can't connect messages, because there can be unknown server messages after last_added_message_id } } } if (!d->last_database_message_id.is_valid()) { CHECK(d->last_message_id.is_valid()); auto it = d->ordered_messages.get_const_iterator(d->last_message_id); MessageId new_first_database_message_id; while (*it != nullptr) { auto message_id = (*it)->get_message_id(); if (message_id.is_server() || message_id.is_local()) { if (!d->last_database_message_id.is_valid()) { set_dialog_last_database_message_id(d, message_id, "on_get_history"); } new_first_database_message_id = message_id; try_restore_dialog_reply_markup(d, get_message(d, message_id)); } --it; } if (new_first_database_message_id.is_valid()) { set_dialog_first_database_message_id(d, new_first_database_message_id, "on_get_history"); } } else { LOG_CHECK(d->last_new_message_id.is_valid()) << dialog_id << " " << from_the_end << " " << d->first_database_message_id << " " << d->last_database_message_id << " " << first_added_message_id << " " << last_added_message_id << " " << d->last_message_id << " " << d->last_new_message_id << " " << d->have_full_history << " " << prev_last_new_message_id << " " << prev_first_database_message_id << " " << prev_last_database_message_id << " " << prev_last_message_id << " " << prev_have_full_history << " " << d->debug_last_new_message_id << " " << d->debug_first_database_message_id << " " << d->debug_last_database_message_id << " " << from_message_id << " " << offset << " " << limit << " " << messages.size() << " " << last_received_message_id << " " << d->debug_set_dialog_last_database_message_id; CHECK(d->first_database_message_id.is_valid()); { auto it = d->ordered_messages.get_const_iterator(d->first_database_message_id); if (*it != nullptr && ((*it)->get_message_id() == d->first_database_message_id || (*it)->have_next())) { MessageId new_first_database_message_id = d->first_database_message_id; while (*it != nullptr) { auto message_id = (*it)->get_message_id(); if ((message_id.is_server() || message_id.is_local()) && message_id < new_first_database_message_id) { new_first_database_message_id = message_id; try_restore_dialog_reply_markup(d, get_message(d, message_id)); } --it; } if (new_first_database_message_id != d->first_database_message_id) { set_dialog_first_database_message_id(d, new_first_database_message_id, "on_get_history 2"); } } } { auto it = d->ordered_messages.get_const_iterator(d->last_database_message_id); if (*it != nullptr && ((*it)->get_message_id() == d->last_database_message_id || (*it)->have_next())) { MessageId new_last_database_message_id = d->last_database_message_id; while (*it != nullptr) { auto message_id = (*it)->get_message_id(); if ((message_id.is_server() || message_id.is_local()) && message_id > new_last_database_message_id) { new_last_database_message_id = message_id; } ++it; } if (new_last_database_message_id != d->last_database_message_id) { set_dialog_last_database_message_id(d, new_last_database_message_id, "on_get_history 2"); } } } } LOG_CHECK(d->first_database_message_id.is_valid()) << dialog_id << " " << from_the_end << " " << d->first_database_message_id << " " << d->last_database_message_id << " " << first_added_message_id << " " << last_added_message_id << " " << d->last_message_id << " " << d->last_new_message_id << " " << d->have_full_history << " " << prev_last_new_message_id << " " << prev_first_database_message_id << " " << prev_last_database_message_id << " " << prev_last_message_id << " " << prev_have_full_history << " " << d->debug_last_new_message_id << " " << d->debug_first_database_message_id << " " << d->debug_last_database_message_id << " " << from_message_id << " " << offset << " " << limit << " " << messages.size() << " " << last_received_message_id << " " << d->debug_set_dialog_last_database_message_id; CHECK(d->last_database_message_id.is_valid()); for (auto &first_message_id : d->first_database_message_id_by_index) { if (first_added_message_id < first_message_id && first_message_id <= last_added_message_id) { first_message_id = first_added_message_id; } } } promise.set_value(Unit()); } vector MessagesManager::get_peers_dialog_ids(vector> &&peers, bool expect_no_access) { vector result; result.reserve(peers.size()); for (auto &peer : peers) { DialogId dialog_id(peer); if (dialog_id.is_valid()) { force_create_dialog(dialog_id, "get_peers_dialog_ids", expect_no_access); result.push_back(dialog_id); } } return result; } void MessagesManager::on_get_public_dialogs_search_result(const string &query, vector> &&my_peers, vector> &&peers) { auto it = search_public_dialogs_queries_.find(query); CHECK(it != search_public_dialogs_queries_.end()); CHECK(!it->second.empty()); auto promises = std::move(it->second); search_public_dialogs_queries_.erase(it); found_public_dialogs_[query] = get_peers_dialog_ids(std::move(peers)); found_on_server_dialogs_[query] = get_peers_dialog_ids(std::move(my_peers)); set_promises(promises); } void MessagesManager::on_failed_public_dialogs_search(const string &query, Status &&error) { auto it = search_public_dialogs_queries_.find(query); CHECK(it != search_public_dialogs_queries_.end()); CHECK(!it->second.empty()); auto promises = std::move(it->second); search_public_dialogs_queries_.erase(it); found_public_dialogs_[query]; // negative cache found_on_server_dialogs_[query]; // negative cache fail_promises(promises, std::move(error)); } void MessagesManager::on_get_message_search_result_calendar( DialogId dialog_id, MessageId from_message_id, MessageSearchFilter filter, int64 random_id, int32 total_count, vector> &&messages, vector> &&periods, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); auto it = found_dialog_message_calendars_.find(random_id); CHECK(it != found_dialog_message_calendars_.end()); int32 received_message_count = 0; for (auto &message : messages) { auto new_message_full_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, false, "on_get_message_search_result_calendar"); if (new_message_full_id == MessageFullId()) { total_count--; continue; } if (new_message_full_id.get_dialog_id() != dialog_id) { LOG(ERROR) << "Receive " << new_message_full_id << " instead of a message in " << dialog_id; total_count--; continue; } received_message_count++; } if (total_count < received_message_count) { LOG(ERROR) << "Receive " << received_message_count << " valid messages out of " << total_count << " in " << messages.size() << " messages"; total_count = received_message_count; } Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); auto &old_message_count = d->message_count_by_index[message_search_filter_index(filter)]; if (old_message_count != total_count) { old_message_count = total_count; on_dialog_updated(dialog_id, "on_get_message_search_result_calendar"); } vector> days; for (auto &period : periods) { auto message_id = MessageId(ServerMessageId(period->min_msg_id_)); const auto *m = get_message(d, message_id); if (m == nullptr) { LOG(ERROR) << "Failed to find " << message_id; continue; } if (period->count_ <= 0) { LOG(ERROR) << "Receive " << to_string(period); continue; } days.push_back(td_api::make_object( period->count_, get_message_object(dialog_id, m, "on_get_message_search_result_calendar"))); } it->second = td_api::make_object(total_count, std::move(days)); promise.set_value(Unit()); } void MessagesManager::on_failed_get_message_search_result_calendar(DialogId dialog_id, int64 random_id) { auto it = found_dialog_message_calendars_.find(random_id); CHECK(it != found_dialog_message_calendars_.end()); found_dialog_message_calendars_.erase(it); } void MessagesManager::on_get_dialog_messages_search_result( DialogId dialog_id, const string &query, DialogId sender_dialog_id, MessageId from_message_id, int32 offset, int32 limit, MessageSearchFilter filter, MessageId top_thread_message_id, int64 random_id, int32 total_count, vector> &&messages, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); LOG(INFO) << "Receive " << messages.size() << " found messages in " << dialog_id; if (!dialog_id.is_valid()) { CHECK(query.empty()); CHECK(!sender_dialog_id.is_valid()); CHECK(!top_thread_message_id.is_valid()); auto it = found_call_messages_.find(random_id); CHECK(it != found_call_messages_.end()); MessageId first_added_message_id; if (messages.empty()) { // messages may be empty because there are no more messages or they can't be found due to global limit // anyway pretend that there are no more messages first_added_message_id = MessageId::min(); } auto &result = it->second.message_full_ids; CHECK(result.empty()); int32 added_message_count = 0; MessageId next_offset_message_id; for (auto &message : messages) { auto message_id = MessageId::get_message_id(message, false); if (message_id.is_valid() && (!next_offset_message_id.is_valid() || message_id < next_offset_message_id)) { next_offset_message_id = message_id; } auto new_message_full_id = on_get_message(std::move(message), false, false, false, "search call messages"); if (new_message_full_id == MessageFullId()) { continue; } result.push_back(new_message_full_id); added_message_count++; CHECK(message_id == new_message_full_id.get_message_id()); CHECK(message_id.is_valid()); if (message_id < first_added_message_id || !first_added_message_id.is_valid()) { first_added_message_id = message_id; } } if (total_count < added_message_count) { LOG(ERROR) << "Receive total_count = " << total_count << ", but added " << added_message_count << " messages out of " << messages.size(); total_count = added_message_count; } if (G()->use_message_database()) { bool update_state = false; auto &old_message_count = calls_db_state_.message_count_by_index[call_message_search_filter_index(filter)]; if (old_message_count != total_count) { LOG(INFO) << "Update calls database message count to " << total_count; old_message_count = total_count; update_state = true; } auto &old_first_db_message_id = calls_db_state_.first_calls_database_message_id_by_index[call_message_search_filter_index(filter)]; bool from_the_end = !from_message_id.is_valid() || from_message_id >= MessageId::max(); LOG(INFO) << "Have from_the_end = " << from_the_end << ", old_first_db_message_id = " << old_first_db_message_id << ", first_added_message_id = " << first_added_message_id << ", from_message_id = " << from_message_id; if ((from_the_end || (old_first_db_message_id.is_valid() && old_first_db_message_id <= from_message_id)) && (!old_first_db_message_id.is_valid() || first_added_message_id < old_first_db_message_id)) { LOG(INFO) << "Update calls database first message to " << first_added_message_id; old_first_db_message_id = first_added_message_id; update_state = true; } if (update_state) { save_calls_db_state(); } } it->second.total_count = total_count; if (next_offset_message_id.is_valid()) { it->second.next_offset = PSTRING() << next_offset_message_id.get_server_message_id().get(); } promise.set_value(Unit()); return; } auto it = found_dialog_messages_.find(random_id); CHECK(it != found_dialog_messages_.end()); auto &result = it->second.message_ids; CHECK(result.empty()); MessageId first_added_message_id; if (messages.empty()) { // messages may be empty because there are no more messages or they can't be found due to global limit // anyway pretend that there are no more messages first_added_message_id = MessageId::min(); } bool can_be_in_different_dialog = top_thread_message_id.is_valid() && is_broadcast_channel(dialog_id); DialogId real_dialog_id; MessageId next_from_message_id; Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); for (auto &message : messages) { auto message_id = MessageId::get_message_id(message, false); if (message_id.is_valid() && (!next_from_message_id.is_valid() || message_id < next_from_message_id)) { next_from_message_id = message_id; } auto new_message_full_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, false, "on_get_dialog_messages_search_result"); if (new_message_full_id == MessageFullId()) { total_count--; continue; } if (new_message_full_id.get_dialog_id() != dialog_id) { if (!can_be_in_different_dialog) { LOG(ERROR) << "Receive " << new_message_full_id << " instead of a message in " << dialog_id; total_count--; continue; } else { if (!real_dialog_id.is_valid()) { real_dialog_id = new_message_full_id.get_dialog_id(); found_dialog_messages_dialog_id_[random_id] = real_dialog_id; } else if (new_message_full_id.get_dialog_id() != real_dialog_id) { LOG(ERROR) << "Receive " << new_message_full_id << " instead of a message in " << real_dialog_id << " or " << dialog_id; total_count--; continue; } } } CHECK(message_id == new_message_full_id.get_message_id()); if (filter == MessageSearchFilter::UnreadMention && message_id <= d->last_read_all_mentions_message_id && !real_dialog_id.is_valid()) { total_count--; continue; } if (filter != MessageSearchFilter::Empty) { const Message *m = get_message(new_message_full_id); CHECK(m != nullptr); auto index_mask = get_message_index_mask(new_message_full_id.get_dialog_id(), m); if ((message_search_filter_index_mask(filter) & index_mask) == 0) { LOG(INFO) << "Skip " << new_message_full_id << " of unexpected type"; total_count--; continue; } } // TODO check that messages are returned in decreasing message_id order if (message_id < first_added_message_id || !first_added_message_id.is_valid()) { first_added_message_id = message_id; } result.push_back(message_id); } 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()); } if (query.empty() && !sender_dialog_id.is_valid() && filter != MessageSearchFilter::Empty && !top_thread_message_id.is_valid()) { bool from_the_end = !from_message_id.is_valid() || (d->last_message_id != MessageId() && from_message_id > d->last_message_id) || from_message_id >= MessageId::max(); bool update_dialog = false; auto &old_message_count = d->message_count_by_index[message_search_filter_index(filter)]; if (old_message_count != total_count) { old_message_count = total_count; if (filter == MessageSearchFilter::UnreadMention) { d->unread_mention_count = old_message_count; update_dialog_mention_notification_count(d); send_update_chat_unread_mention_count(d); } if (filter == MessageSearchFilter::UnreadReaction) { d->unread_reaction_count = old_message_count; // update_dialog_mention_notification_count(d); send_update_chat_unread_reaction_count(d, "on_get_dialog_messages_search_result"); } update_dialog = true; } auto &old_first_database_message_id = d->first_database_message_id_by_index[message_search_filter_index(filter)]; if ((from_the_end || (old_first_database_message_id.is_valid() && old_first_database_message_id <= from_message_id)) && (!old_first_database_message_id.is_valid() || first_added_message_id < old_first_database_message_id)) { old_first_database_message_id = first_added_message_id; update_dialog = true; } if (update_dialog) { on_dialog_updated(dialog_id, "search results"); } if (from_the_end && filter == MessageSearchFilter::Pinned) { set_dialog_last_pinned_message_id(d, result.empty() ? MessageId() : result[0]); } } it->second.total_count = total_count; it->second.next_from_message_id = next_from_message_id; promise.set_value(Unit()); } void MessagesManager::on_failed_dialog_messages_search(DialogId dialog_id, int64 random_id) { if (!dialog_id.is_valid()) { auto it = found_call_messages_.find(random_id); CHECK(it != found_call_messages_.end()); found_call_messages_.erase(it); return; } auto it = found_dialog_messages_.find(random_id); CHECK(it != found_dialog_messages_.end()); found_dialog_messages_.erase(it); } void MessagesManager::on_get_dialog_message_count(DialogId dialog_id, MessageSearchFilter filter, int32 total_count, Promise &&promise) { LOG(INFO) << "Receive " << total_count << " message count in " << dialog_id << " with filter " << filter; if (total_count < 0) { LOG(ERROR) << "Receive total message count = " << total_count << " in " << dialog_id << " with filter " << filter; total_count = 0; } Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); CHECK(filter != MessageSearchFilter::Empty); CHECK(filter != MessageSearchFilter::UnreadMention); CHECK(filter != MessageSearchFilter::UnreadReaction); CHECK(filter != MessageSearchFilter::FailedToSend); auto &old_message_count = d->message_count_by_index[message_search_filter_index(filter)]; if (old_message_count != total_count) { old_message_count = total_count; on_dialog_updated(dialog_id, "on_get_dialog_message_count"); } if (total_count == 0) { auto &old_first_database_message_id = d->first_database_message_id_by_index[message_search_filter_index(filter)]; if (old_first_database_message_id != MessageId::min()) { old_first_database_message_id = MessageId::min(); on_dialog_updated(dialog_id, "on_get_dialog_message_count"); } if (filter == MessageSearchFilter::Pinned) { set_dialog_last_pinned_message_id(d, MessageId()); } } promise.set_value(std::move(total_count)); } void MessagesManager::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, int64 random_id, int32 total_count, vector> &&messages, int32 next_rate, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); LOG(INFO) << "Receive " << messages.size() << " found messages"; auto it = found_messages_.find(random_id); CHECK(it != found_messages_.end()); auto &result = it->second.message_full_ids; CHECK(result.empty()); int32 last_message_date = 0; MessageId last_message_id; DialogId last_dialog_id; for (auto &message : messages) { auto message_date = get_message_date(message); auto message_id = MessageId::get_message_id(message, false); auto dialog_id = DialogId::get_message_dialog_id(message); if (message_date > 0 && message_id.is_valid() && dialog_id.is_valid()) { last_message_date = message_date; last_message_id = message_id; last_dialog_id = dialog_id; } auto new_message_full_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, false, "search messages"); if (new_message_full_id != MessageFullId()) { CHECK(dialog_id == new_message_full_id.get_dialog_id()); 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()); } it->second.total_count = total_count; if (!result.empty()) { if (next_rate > 0) { last_message_date = next_rate; } it->second.next_offset = PSTRING() << last_message_date << ',' << last_dialog_id.get() << ',' << last_message_id.get_server_message_id().get(); } promise.set_value(Unit()); } void MessagesManager::on_failed_messages_search(int64 random_id) { auto it = found_messages_.find(random_id); CHECK(it != found_messages_.end()); found_messages_.erase(it); } void MessagesManager::on_get_outgoing_document_messages(vector> &&messages, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); FoundMessages found_messages; for (auto &message : messages) { auto dialog_id = DialogId::get_message_dialog_id(message); auto message_full_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, false, "on_get_outgoing_document_messages"); if (message_full_id != MessageFullId()) { CHECK(dialog_id == message_full_id.get_dialog_id()); found_messages.message_full_ids.push_back(message_full_id); } } auto result = 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 MessagesManager::on_get_scheduled_server_messages(DialogId dialog_id, uint32 generation, vector> &&messages, bool is_not_modified) { Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); if (generation < d->scheduled_messages_sync_generation) { LOG(INFO) << "Ignore scheduled messages with old generation " << generation << " instead of " << d->scheduled_messages_sync_generation << " in " << dialog_id; return; } d->scheduled_messages_sync_generation = generation; if (is_not_modified) { LOG(INFO) << "Scheduled messages are mot modified in " << dialog_id; return; } vector old_message_ids; if (d->scheduled_messages != nullptr) { for (const auto &it : d->scheduled_messages->scheduled_messages_) { old_message_ids.push_back(it.first); }; } FlatHashMap old_server_message_ids; for (auto &message_id : old_message_ids) { if (message_id.is_scheduled_server()) { old_server_message_ids[message_id.get_scheduled_server_message_id()] = message_id; } } bool is_channel_message = dialog_id.get_type() == DialogType::Channel; bool has_scheduled_server_messages = false; for (auto &message : messages) { auto message_dialog_id = DialogId::get_message_dialog_id(message); if (message_dialog_id != dialog_id) { // server can send messageEmpty for deleted scheduled messages auto message_id = MessageId::get_message_id(message, true); if (message_id.is_valid() || message_dialog_id.is_valid()) { LOG(ERROR) << "Receive " << message_id << " in wrong " << message_dialog_id << " instead of " << dialog_id << ": " << oneline(to_string(message)); } continue; } auto message_full_id = on_get_message(std::move(message), d->sent_scheduled_messages, is_channel_message, true, "on_get_scheduled_server_messages"); auto message_id = message_full_id.get_message_id(); if (message_id.is_valid_scheduled()) { CHECK(message_id.is_scheduled_server()); old_server_message_ids.erase(message_id.get_scheduled_server_message_id()); has_scheduled_server_messages = true; } } on_update_dialog_has_scheduled_server_messages(dialog_id, has_scheduled_server_messages); for (const auto &it : old_server_message_ids) { auto message_id = it.second; auto message = do_delete_scheduled_message(d, message_id, true, "on_get_scheduled_server_messages"); CHECK(message != nullptr); send_update_delete_messages(dialog_id, {message->message_id.get()}, true); } send_update_chat_has_scheduled_messages(d, false); } void MessagesManager::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 result; for (auto &message : messages) { auto new_message_full_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, false, "get recent locations"); if (new_message_full_id != MessageFullId()) { if (new_message_full_id.get_dialog_id() != dialog_id) { LOG(ERROR) << "Receive " << new_message_full_id << " instead of a message in " << dialog_id; total_count--; continue; } auto m = get_message(new_message_full_id); CHECK(m != nullptr); if (m->content->get_type() != MessageContentType::LiveLocation) { LOG(ERROR) << "Receive a message of wrong type " << m->content->get_type() << " in on_get_recent_locations in " << dialog_id; total_count--; continue; } result.push_back(m->message_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()); } promise.set_value(get_messages_object(total_count, dialog_id, result, true, "on_get_recent_locations")); } void MessagesManager::delete_messages_from_updates(const vector &message_ids, bool is_permanent) { FlatHashMap, DialogIdHash> deleted_message_ids; FlatHashMap need_update_dialog_pos; vector> deleted_messages; for (auto message_id : message_ids) { if (!message_id.is_valid() || !message_id.is_server()) { LOG(ERROR) << "Incoming update tries to delete " << message_id; continue; } Dialog *d = get_dialog_by_message_id(message_id); if (d != nullptr) { auto message = delete_message(d, message_id, is_permanent, &need_update_dialog_pos[d->dialog_id], "delete_messages_from_updates"); CHECK(message != nullptr); LOG_CHECK(message->message_id == message_id) << message_id << " " << message->message_id << " " << d->dialog_id; deleted_message_ids[d->dialog_id].push_back(message->message_id.get()); deleted_messages.push_back(std::move(message)); } if (last_clear_history_message_id_to_dialog_id_.count(message_id)) { d = get_dialog(last_clear_history_message_id_to_dialog_id_[message_id]); CHECK(d != nullptr); auto message = delete_message(d, message_id, is_permanent, &need_update_dialog_pos[d->dialog_id], "delete_messages_from_updates"); CHECK(message == nullptr); } } if (deleted_messages.size() >= MIN_DELETED_ASYNCHRONOUSLY_MESSAGES) { Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), deleted_messages); } for (auto &it : need_update_dialog_pos) { if (it.second) { auto dialog_id = it.first; Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); send_update_chat_last_message(d, "delete_messages_from_updates"); } } for (auto &it : deleted_message_ids) { auto dialog_id = it.first; send_update_delete_messages(dialog_id, std::move(it.second), is_permanent); } } void MessagesManager::delete_dialog_messages(DialogId dialog_id, const vector &message_ids, bool force_update_for_not_found_messages, const char *source) { Dialog *d = get_dialog_force(dialog_id, "delete_dialog_messages"); if (d == nullptr) { LOG(INFO) << "Ignore deleteChannelMessages for unknown " << dialog_id << " from " << source; CHECK(dialog_id.get_type() == DialogType::Channel); return; } delete_dialog_messages(d, message_ids, force_update_for_not_found_messages, source); } void MessagesManager::delete_dialog_messages(Dialog *d, const vector &message_ids, bool force_update_for_not_found_messages, const char *source) { vector> deleted_messages; vector deleted_message_ids; bool need_update_dialog_pos = false; bool need_update_chat_has_scheduled_messages = false; for (auto message_id : message_ids) { CHECK(message_id.is_valid() || message_id.is_valid_scheduled()); bool force_update = force_update_for_not_found_messages && !is_deleted_message(d, message_id); auto message = delete_message(d, message_id, true, &need_update_dialog_pos, source); if (message == nullptr) { if (force_update) { deleted_message_ids.push_back(message_id.get()); } } else { need_update_chat_has_scheduled_messages |= message->message_id.is_scheduled(); deleted_message_ids.push_back(message->message_id.get()); deleted_messages.push_back(std::move(message)); } } if (deleted_messages.size() >= MIN_DELETED_ASYNCHRONOUSLY_MESSAGES) { Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), deleted_messages); } if (need_update_dialog_pos) { send_update_chat_last_message(d, source); } send_update_delete_messages(d->dialog_id, std::move(deleted_message_ids), true); if (need_update_chat_has_scheduled_messages) { send_update_chat_has_scheduled_messages(d, true); } } void MessagesManager::update_dialog_pinned_messages_from_updates(DialogId dialog_id, const vector &message_ids, bool is_pin) { Dialog *d = get_dialog_force(dialog_id, "update_dialog_pinned_messages_from_updates"); if (d == nullptr) { LOG(INFO) << "Ignore updatePinnedMessages for unknown " << dialog_id; return; } for (auto message_id : message_ids) { if (!message_id.is_valid() || (!message_id.is_server() && dialog_id.get_type() != DialogType::SecretChat)) { LOG(ERROR) << "Incoming update tries to pin/unpin " << message_id << " in " << dialog_id; continue; } Message *m = get_message_force(d, message_id, "update_dialog_pinned_messages_from_updates"); if (m != nullptr && update_message_is_pinned(d, m, is_pin, "update_dialog_pinned_messages_from_updates")) { on_message_changed(d, m, true, "update_dialog_pinned_messages_from_updates"); } } } bool MessagesManager::update_message_is_pinned(Dialog *d, Message *m, bool is_pinned, const char *source) { CHECK(m != nullptr); CHECK(!m->message_id.is_scheduled()); if (m->is_pinned == is_pinned) { return false; } LOG(INFO) << "Update message is_pinned of " << m->message_id << " in " << d->dialog_id << " to " << is_pinned << " from " << source; auto old_index_mask = get_message_index_mask(d->dialog_id, m); m->is_pinned = is_pinned; auto new_index_mask = get_message_index_mask(d->dialog_id, m); update_message_count_by_index(d, -1, old_index_mask & ~new_index_mask); update_message_count_by_index(d, +1, new_index_mask & ~old_index_mask); send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateMessageIsPinned"), m->message_id.get(), is_pinned)); if (is_pinned) { if (d->is_last_pinned_message_id_inited && m->message_id > d->last_pinned_message_id) { set_dialog_last_pinned_message_id(d, m->message_id); } } else { if (d->is_last_pinned_message_id_inited && m->message_id == d->last_pinned_message_id) { if (!td_->auth_manager_->is_bot() && d->message_count_by_index[message_search_filter_index(MessageSearchFilter::Pinned)] == 0) { set_dialog_last_pinned_message_id(d, MessageId()); } else { drop_dialog_last_pinned_message_id(d); } } } return true; } string MessagesManager::get_message_search_text(const Message *m) const { if (m->is_content_secret) { return string(); } return get_message_content_search_text(td_, m->content.get()); } bool MessagesManager::can_forward_message(DialogId from_dialog_id, const Message *m) { if (m == nullptr) { return false; } if (m->ttl > 0) { return false; } if (m->message_id.is_scheduled()) { return false; } switch (from_dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: case DialogType::Channel: // ok break; case DialogType::SecretChat: return false; case DialogType::None: default: UNREACHABLE(); return false; } return can_forward_message_content(m->content.get()); } bool MessagesManager::can_save_message(DialogId dialog_id, const Message *m) const { if (m == nullptr || m->noforwards || m->is_content_secret) { return false; } return !get_dialog_has_protected_content(dialog_id); } bool MessagesManager::can_get_message_statistics(MessageFullId message_full_id) { return can_get_message_statistics(message_full_id.get_dialog_id(), get_message_force(message_full_id, "can_get_message_statistics")); } bool MessagesManager::can_get_message_statistics(DialogId dialog_id, const Message *m) const { if (td_->auth_manager_->is_bot()) { return false; } if (m == nullptr || m->message_id.is_scheduled() || !m->message_id.is_server() || m->view_count == 0 || m->had_forward_info || (m->forward_info != nullptr && m->forward_info->origin.is_channel_post())) { return false; } return td_->contacts_manager_->can_get_channel_message_statistics(dialog_id); } bool MessagesManager::can_delete_channel_message(const DialogParticipantStatus &status, const Message *m, bool is_bot) { if (m == nullptr) { return true; } if (m->message_id.is_local() || m->message_id.is_yet_unsent()) { return true; } if (m->message_id.is_scheduled()) { if (m->is_channel_post) { return status.can_post_messages(); } return true; } if (is_bot && G()->unix_time() >= m->date + 2 * 86400) { // bots can't delete messages older than 2 days return false; } CHECK(m->message_id.is_server()); if (m->message_id.get_server_message_id().get() == 1) { return false; } auto content_type = m->content->get_type(); if (content_type == MessageContentType::ChannelMigrateFrom || content_type == MessageContentType::ChannelCreate || content_type == MessageContentType::TopicCreate) { return false; } if (status.can_delete_messages()) { return true; } if (!m->is_outgoing) { return false; } if (m->is_channel_post || is_service_message_content(content_type)) { return status.can_post_messages(); } return true; } bool MessagesManager::can_delete_message(DialogId dialog_id, const Message *m) const { if (m == nullptr) { return true; } if (m->message_id.is_local() || m->message_id.is_yet_unsent()) { return true; } switch (dialog_id.get_type()) { case DialogType::User: return true; case DialogType::Chat: return true; case DialogType::Channel: { auto dialog_status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()); return can_delete_channel_message(dialog_status, m, td_->auth_manager_->is_bot()); } case DialogType::SecretChat: return true; case DialogType::None: default: UNREACHABLE(); return false; } } bool MessagesManager::can_revoke_message(DialogId dialog_id, const Message *m) const { if (m == nullptr) { return true; } if (m->message_id.is_local()) { return false; } if (dialog_id == get_my_dialog_id()) { return false; } if (m->message_id.is_scheduled()) { return false; } if (m->message_id.is_yet_unsent()) { return true; } CHECK(m->message_id.is_server()); const int32 DEFAULT_REVOKE_TIME_LIMIT = td_->auth_manager_->is_bot() ? 2 * 86400 : std::numeric_limits::max(); auto content_type = m->content->get_type(); switch (dialog_id.get_type()) { case DialogType::User: { bool can_revoke_incoming = td_->option_manager_->get_option_boolean("revoke_pm_inbox", true); int64 revoke_time_limit = td_->option_manager_->get_option_integer("revoke_pm_time_limit", DEFAULT_REVOKE_TIME_LIMIT); if (G()->unix_time() - m->date < 86400 && content_type == MessageContentType::Dice) { return false; } return ((m->is_outgoing && !is_service_message_content(content_type)) || (can_revoke_incoming && content_type != MessageContentType::ScreenshotTaken)) && G()->unix_time() - m->date <= revoke_time_limit; } case DialogType::Chat: { bool is_appointed_administrator = td_->contacts_manager_->is_appointed_chat_administrator(dialog_id.get_chat_id()); int64 revoke_time_limit = td_->option_manager_->get_option_integer("revoke_time_limit", DEFAULT_REVOKE_TIME_LIMIT); return ((m->is_outgoing && !is_service_message_content(content_type)) || is_appointed_administrator) && G()->unix_time() - m->date <= revoke_time_limit; } case DialogType::Channel: return true; // any server message that can be deleted will be deleted for all participants case DialogType::SecretChat: // all non-service messages will be deleted for everyone if secret chat is active return td_->contacts_manager_->get_secret_chat_state(dialog_id.get_secret_chat_id()) == SecretChatState::Active && !is_service_message_content(content_type); case DialogType::None: default: UNREACHABLE(); return false; } } void MessagesManager::delete_messages(DialogId dialog_id, const vector &input_message_ids, bool revoke, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); Dialog *d = get_dialog_force(dialog_id, "delete_messages"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat is not found")); } if (input_message_ids.empty()) { return promise.set_value(Unit()); } auto dialog_type = dialog_id.get_type(); bool is_secret = dialog_type == DialogType::SecretChat; vector message_ids; message_ids.reserve(input_message_ids.size()); vector deleted_server_message_ids; vector deleted_scheduled_server_message_ids; for (auto message_id : input_message_ids) { if (!message_id.is_valid() && !message_id.is_valid_scheduled()) { return promise.set_error(Status::Error(400, "Invalid message identifier")); } message_id = get_persistent_message_id(d, message_id); message_ids.push_back(message_id); auto m = get_message_force(d, message_id, "delete_messages"); if (m != nullptr) { if (m->message_id.is_scheduled()) { if (m->message_id.is_scheduled_server()) { deleted_scheduled_server_message_ids.push_back(m->message_id); } } else { if (m->message_id.is_server() || is_secret) { deleted_server_message_ids.push_back(m->message_id); } } } } bool is_bot = td_->auth_manager_->is_bot(); for (auto message_id : message_ids) { auto m = get_message(d, message_id); if (!can_delete_message(dialog_id, m)) { return promise.set_error(Status::Error(400, "Message can't be deleted")); } if (is_bot && !message_id.is_scheduled() && message_id.is_server() && !can_revoke_message(dialog_id, m)) { return promise.set_error(Status::Error(400, "Message can't be deleted for everyone")); } } MultiPromiseActorSafe mpas{"DeleteMessagesMultiPromiseActor"}; mpas.add_promise(std::move(promise)); auto lock = mpas.get_promise(); delete_messages_on_server(dialog_id, std::move(deleted_server_message_ids), revoke, 0, mpas.get_promise()); delete_scheduled_messages_on_server(dialog_id, std::move(deleted_scheduled_server_message_ids), 0, mpas.get_promise()); lock.set_value(Unit()); delete_dialog_messages(d, message_ids, false, DELETE_MESSAGE_USER_REQUEST_SOURCE); } void MessagesManager::erase_delete_messages_log_event(uint64 log_event_id) { if (!G()->close_flag()) { binlog_erase(G()->td_db()->get_binlog(), log_event_id); } } void MessagesManager::delete_sent_message_on_server(DialogId dialog_id, MessageId message_id, MessageId old_message_id) { // this would be a no-op, because replies have already been removed in cancel_send_message_query // update_reply_to_message_id(dialog_id, old_message_id, message_id, false, "delete_sent_message_on_server"); // being sent message was deleted by the user or is in an inaccessible channel // don't need to send an update to the user, because the message has already been deleted if (!have_input_peer(dialog_id, AccessRights::Read)) { LOG(INFO) << "Ignore sent " << message_id << " in inaccessible " << dialog_id; return; } LOG(INFO) << "Delete already deleted sent " << message_id << " in " << dialog_id << " from server"; Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); if (get_message_force(d, message_id, "delete_sent_message_on_server") != nullptr) { delete_messages(dialog_id, {message_id}, true, Auto()); } else { if (message_id.is_valid()) { CHECK(message_id.is_server()); delete_messages_on_server(dialog_id, {message_id}, true, 0, Auto()); } else { CHECK(message_id.is_scheduled_server()); delete_scheduled_messages_on_server(dialog_id, {message_id}, 0, Auto()); } bool need_update_dialog_pos = false; auto message = delete_message(d, message_id, true, &need_update_dialog_pos, "delete_sent_message_on_server"); CHECK(message == nullptr); if (need_update_dialog_pos) { // last_clear_history_message_id might be removed update_dialog_pos(d, "delete_sent_message_on_server"); } } } class MessagesManager::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 MessagesManager::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 MessagesManager::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 ") << format::as_array(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, &MessagesManager::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 (size_t i = 0; i < server_message_ids.size(); i += MAX_SLICE_SIZE) { auto end_i = i + MAX_SLICE_SIZE; auto end = end_i < server_message_ids.size() ? server_message_ids.begin() + end_i : server_message_ids.end(); if (dialog_type != DialogType::Channel) { td_->create_handler(mpas.get_promise()) ->send(dialog_id, {server_message_ids.begin() + i, end}, revoke); } else { td_->create_handler(mpas.get_promise()) ->send(dialog_id.get_channel_id(), {server_message_ids.begin() + i, end}); } } break; } case DialogType::SecretChat: { vector random_ids; auto d = get_dialog_force(dialog_id, "delete_messages_on_server"); CHECK(d != nullptr); for (auto &message_id : message_ids) { auto *m = get_message(d, message_id); if (m != nullptr) { random_ids.push_back(m->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 MessagesManager::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 MessagesManager::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 MessagesManager::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 " << format::as_array(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)); } void MessagesManager::on_failed_message_deletion(DialogId dialog_id, const vector &server_message_ids) { if (G()->close_flag()) { return; } Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); vector message_full_ids; for (auto &server_message_id : server_message_ids) { auto message_id = MessageId(ServerMessageId(server_message_id)); d->deleted_message_ids.erase(message_id); message_full_ids.emplace_back(dialog_id, message_id); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return; } get_messages_from_server(std::move(message_full_ids), Promise(), "on_failed_message_deletion"); } void MessagesManager::on_failed_scheduled_message_deletion(DialogId dialog_id, const vector &message_ids) { if (G()->close_flag()) { return; } Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); if (d->scheduled_messages != nullptr) { for (auto &message_id : message_ids) { d->scheduled_messages->deleted_scheduled_server_message_ids_.erase(message_id.get_scheduled_server_message_id()); } } if (!have_input_peer(dialog_id, AccessRights::Read) || dialog_id.get_type() == DialogType::SecretChat) { return; } if (is_broadcast_channel(dialog_id) && !td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).can_post_messages()) { return; } load_dialog_scheduled_messages(dialog_id, false, 0, Promise()); } MessagesManager::CanDeleteDialog MessagesManager::can_delete_dialog(const Dialog *d) const { if (is_dialog_sponsored(d)) { auto chat_source = sponsored_dialog_source_.get_chat_source_object(); if (chat_source != nullptr) { switch (chat_source->get_id()) { case td_api::chatSourcePublicServiceAnnouncement::ID: // can delete for self (but only while removing from dialog list) return {true, false}; default: return {false, false}; } } } if (td_->auth_manager_->is_bot() || !have_input_peer(d->dialog_id, AccessRights::Read)) { return {false, false}; } switch (d->dialog_id.get_type()) { case DialogType::User: if (d->dialog_id == get_my_dialog_id() || td_->contacts_manager_->is_user_deleted(d->dialog_id.get_user_id()) || td_->contacts_manager_->is_user_bot(d->dialog_id.get_user_id())) { return {true, false}; } return {true, td_->option_manager_->get_option_boolean("revoke_pm_inbox", true)}; case DialogType::Chat: // chats can be deleted only for self and can be deleted for everyone by their creator return {true, td_->contacts_manager_->get_chat_status(d->dialog_id.get_chat_id()).is_creator()}; case DialogType::Channel: { // private non-forum joined supergroups can be deleted for self auto channel_id = d->dialog_id.get_channel_id(); return {!td_->contacts_manager_->is_broadcast_channel(channel_id) && !td_->contacts_manager_->is_channel_public(channel_id) && !td_->contacts_manager_->is_forum_channel(channel_id) && td_->contacts_manager_->get_channel_status(channel_id).is_member(), td_->contacts_manager_->get_channel_can_be_deleted(channel_id)}; } case DialogType::SecretChat: if (td_->contacts_manager_->get_secret_chat_state(d->dialog_id.get_secret_chat_id()) == SecretChatState::Closed) { // in a closed secret chats there is no way to delete messages for both users return {true, false}; } // active secret chats can be deleted only for both users return {false, true}; case DialogType::None: default: UNREACHABLE(); return {false, false}; } } void MessagesManager::delete_dialog_history(DialogId dialog_id, bool remove_from_dialog_list, bool revoke, Promise &&promise) { LOG(INFO) << "Receive deleteChatHistory request to delete all messages in " << dialog_id << ", remove_from_chat_list is " << remove_from_dialog_list << ", revoke is " << revoke; Dialog *d = get_dialog_force(dialog_id, "delete_dialog_history"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Chat info not found")); } if (is_dialog_sponsored(d)) { auto chat_source = sponsored_dialog_source_.get_chat_source_object(); if (chat_source == nullptr || chat_source->get_id() != td_api::chatSourcePublicServiceAnnouncement::ID) { return promise.set_error(Status::Error(400, "Can't delete the chat")); } if (!remove_from_dialog_list) { return promise.set_error(Status::Error(400, "Can't delete chat history without removing the chat")); } removed_sponsored_dialog_id_ = dialog_id; remove_sponsored_dialog(); if (dialog_id.get_type() != DialogType::SecretChat) { td_->create_handler()->send(dialog_id); } promise.set_value(Unit()); return; } auto can_delete = can_delete_dialog(d); if (revoke) { if (!can_delete.for_all_users_) { if (!can_delete.for_self_) { return promise.set_error(Status::Error(400, "Chat history can't be deleted")); } LOG(INFO) << "Can't delete history of " << dialog_id << " for everyone; delete it only for self"; revoke = false; } } else { if (!can_delete.for_self_) { return promise.set_error( Status::Error(400, PSLICE() << "Can't delete history of " << dialog_id << " only for self")); } } auto last_new_message_id = d->last_new_message_id; if (dialog_id.get_type() != DialogType::SecretChat && last_new_message_id == MessageId()) { // TODO get dialog from the server and delete history from last message identifier } bool allow_error = d->messages.empty(); auto old_order = d->order; delete_all_dialog_messages(d, remove_from_dialog_list, true); if (last_new_message_id.is_valid() && last_new_message_id == d->max_unavailable_message_id && !revoke && !(old_order != DEFAULT_ORDER && remove_from_dialog_list)) { // history has already been cleared, nothing to do promise.set_value(Unit()); return; } set_dialog_max_unavailable_message_id(dialog_id, last_new_message_id, false, "delete_dialog_history"); delete_dialog_history_on_server(dialog_id, last_new_message_id, remove_from_dialog_list, revoke, allow_error, 0, std::move(promise)); } class MessagesManager::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 MessagesManager::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 MessagesManager::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; } } void MessagesManager::delete_topic_history(DialogId dialog_id, MessageId top_thread_message_id, Promise &&promise) { Dialog *d = get_dialog_force(dialog_id, "delete_dialog_history"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Chat info not found")); } // auto old_order = d->order; // delete_all_dialog_topic_messages(d, top_thread_message_id); delete_topic_history_on_server(dialog_id, top_thread_message_id, 0, std::move(promise)); } class MessagesManager::DeleteTopicHistoryOnServerLogEvent { public: DialogId dialog_id_; MessageId top_thread_message_id_; template void store(StorerT &storer) const { BEGIN_STORE_FLAGS(); END_STORE_FLAGS(); td::store(dialog_id_, storer); td::store(top_thread_message_id_, storer); } template void parse(ParserT &parser) { BEGIN_PARSE_FLAGS(); END_PARSE_FLAGS(); td::parse(dialog_id_, parser); td::parse(top_thread_message_id_, parser); } }; uint64 MessagesManager::save_delete_topic_history_on_server_log_event(DialogId dialog_id, MessageId top_thread_message_id) { DeleteTopicHistoryOnServerLogEvent log_event{dialog_id, top_thread_message_id}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteTopicHistoryOnServer, get_log_event_storer(log_event)); } void MessagesManager::delete_topic_history_on_server(DialogId dialog_id, MessageId top_thread_message_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, top_thread_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 AffectedHistoryQuery query = [td = td_, top_thread_message_id](DialogId dialog_id, Promise &&query_promise) { td->create_handler(std::move(query_promise))->send(dialog_id, top_thread_message_id); }; run_affected_history_query_until_complete(dialog_id, std::move(query), true, std::move(promise)); } void MessagesManager::delete_all_call_messages(bool revoke, Promise &&promise) { delete_all_call_messages_on_server(revoke, 0, std::move(promise)); } class MessagesManager::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 MessagesManager::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 MessagesManager::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))); } vector MessagesManager::find_dialog_messages(const Dialog *d, const std::function &condition) { vector message_ids; d->messages.foreach([&](const MessageId &message_id, const unique_ptr &message) { CHECK(message_id == message->message_id); if (condition(message.get())) { message_ids.push_back(message_id); } }); return message_ids; } vector MessagesManager::find_unloadable_messages(const Dialog *d, int32 unload_before_date, bool &has_left_to_unload_messages) const { vector message_ids; for (auto it = d->message_lru_list.next; it != &d->message_lru_list; it = it->next) { if (message_ids.size() >= MAX_UNLOADED_MESSAGES) { has_left_to_unload_messages = true; break; } const auto *m = static_cast(it); if (can_unload_message(d, m)) { if (m->last_access_date <= unload_before_date) { message_ids.push_back(m->message_id); } else { has_left_to_unload_messages = true; } } if (has_left_to_unload_messages && m->date > unload_before_date) { // we aren't interested in unloading too new messages break; } } return message_ids; } void MessagesManager::delete_dialog_messages_by_sender(DialogId dialog_id, DialogId sender_dialog_id, Promise &&promise) { bool is_bot = td_->auth_manager_->is_bot(); CHECK(!is_bot); Dialog *d = get_dialog_force(dialog_id, "delete_dialog_messages_by_sender"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Write)) { return promise.set_error(Status::Error(400, "Not enough rights")); } if (!have_input_peer(sender_dialog_id, AccessRights::Know)) { return promise.set_error(Status::Error(400, "Message sender not found")); } ChannelId channel_id; DialogParticipantStatus channel_status = DialogParticipantStatus::Left(); switch (dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: case DialogType::SecretChat: return promise.set_error( Status::Error(400, "All messages from a sender can be deleted only in supergroup chats")); case DialogType::Channel: { channel_id = dialog_id.get_channel_id(); if (!td_->contacts_manager_->is_megagroup_channel(channel_id)) { return promise.set_error(Status::Error(400, "The method is available only in supergroup chats")); } channel_status = td_->contacts_manager_->get_channel_permissions(channel_id); if (!channel_status.can_delete_messages()) { return promise.set_error(Status::Error(400, "Need delete messages administator right in the supergroup chat")); } channel_id = dialog_id.get_channel_id(); break; } case DialogType::None: default: UNREACHABLE(); break; } CHECK(channel_id.is_valid()); if (sender_dialog_id.get_type() == DialogType::SecretChat) { return promise.set_value(Unit()); } if (G()->use_message_database()) { LOG(INFO) << "Delete all messages from " << sender_dialog_id << " in " << dialog_id << " from database"; G()->td_db()->get_message_db_async()->delete_dialog_messages_by_sender(dialog_id, sender_dialog_id, Auto()); // TODO Promise } vector message_ids = find_dialog_messages(d, [sender_dialog_id, channel_status, is_bot](const Message *m) { return sender_dialog_id == get_message_sender(m) && can_delete_channel_message(channel_status, m, is_bot); }); delete_dialog_messages(d, message_ids, false, DELETE_MESSAGE_USER_REQUEST_SOURCE); delete_all_channel_messages_by_sender_on_server(channel_id, sender_dialog_id, 0, std::move(promise)); } class MessagesManager::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 MessagesManager::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 MessagesManager::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))); } void MessagesManager::delete_dialog_messages_by_date(DialogId dialog_id, int32 min_date, int32 max_date, bool revoke, Promise &&promise) { bool is_bot = td_->auth_manager_->is_bot(); CHECK(!is_bot); Dialog *d = get_dialog_force(dialog_id, "delete_dialog_messages_by_date"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } if (min_date > max_date) { return promise.set_error(Status::Error(400, "Wrong date interval specified")); } const int32 telegram_launch_date = 1376438400; if (max_date < telegram_launch_date) { return promise.set_value(Unit()); } 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) { return promise.set_value(Unit()); } if (max_date >= current_date - 30) { max_date = current_date - 31; } CHECK(min_date <= max_date); switch (dialog_id.get_type()) { case DialogType::User: break; case DialogType::Chat: if (revoke) { return promise.set_error(Status::Error(400, "Bulk message revocation is unsupported in basic group chats")); } break; case DialogType::Channel: return promise.set_error(Status::Error(400, "Bulk message deletion is unsupported in supergroup chats")); case DialogType::SecretChat: return promise.set_error(Status::Error(400, "Bulk message deletion is unsupported in secret chats")); case DialogType::None: default: UNREACHABLE(); break; } // TODO delete in database by dates auto message_ids = d->ordered_messages.find_messages_by_date(min_date, max_date, get_get_message_date(d)); delete_dialog_messages(d, message_ids, false, DELETE_MESSAGE_USER_REQUEST_SOURCE); delete_dialog_messages_by_date_on_server(dialog_id, min_date, max_date, revoke, 0, std::move(promise)); } class MessagesManager::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 MessagesManager::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)); } void MessagesManager::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))); } int32 MessagesManager::get_unload_dialog_delay() const { constexpr int32 DIALOG_UNLOAD_DELAY = 60; // seconds constexpr int32 DIALOG_UNLOAD_BOT_DELAY = 1800; // seconds CHECK(is_message_unload_enabled()); auto default_unload_delay = td_->auth_manager_->is_bot() ? DIALOG_UNLOAD_BOT_DELAY : DIALOG_UNLOAD_DELAY; return narrow_cast(td_->option_manager_->get_option_integer("message_unload_delay", default_unload_delay)); } double MessagesManager::get_next_unload_dialog_delay(Dialog *d) const { if (d->unload_dialog_delay_seed == 0) { d->unload_dialog_delay_seed = Random::fast(1, 1000000000); } auto delay = get_unload_dialog_delay() / 4; return delay + delay * 1e-9 * d->unload_dialog_delay_seed; } void MessagesManager::unload_dialog(DialogId dialog_id, int32 delay) { if (G()->close_flag()) { return; } if (delay < 0) { delay = get_unload_dialog_delay() - 2; } Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); if (!d->has_unload_timeout) { LOG(INFO) << "Don't need to unload " << dialog_id; // possible right after the dialog was opened return; } if (!is_message_unload_enabled()) { // just in case LOG(INFO) << "Message unload is disabled in " << dialog_id; d->has_unload_timeout = false; return; } bool has_left_to_unload_messages = false; auto to_unload_message_ids = find_unloadable_messages(d, G()->unix_time() - delay, has_left_to_unload_messages); vector unloaded_message_ids; vector> unloaded_messages; for (auto message_id : to_unload_message_ids) { auto message = unload_message(d, message_id); CHECK(message != nullptr); if (message->is_update_sent) { unloaded_message_ids.push_back(message->message_id.get()); } unloaded_messages.push_back(std::move(message)); } if (unloaded_messages.size() >= MIN_DELETED_ASYNCHRONOUSLY_MESSAGES) { Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), unloaded_messages); } if (!to_unload_message_ids.empty() && !G()->use_message_database() && !d->is_empty) { d->have_full_history = false; d->have_full_history_source = 0; } if (!unloaded_message_ids.empty()) { send_closure_later( G()->td(), &Td::send_update, td_api::make_object(get_chat_id_object(dialog_id, "updateDeleteMessages"), std::move(unloaded_message_ids), false, true)); } if (has_left_to_unload_messages) { LOG(DEBUG) << "Need to unload more messages in " << dialog_id; pending_unload_dialog_timeout_.add_timeout_in( d->dialog_id.get(), to_unload_message_ids.size() >= MAX_UNLOADED_MESSAGES ? 1.0 : get_next_unload_dialog_delay(d)); } else { d->has_unload_timeout = false; } } void MessagesManager::clear_dialog_message_list(Dialog *d, bool remove_from_dialog_list, int32 last_message_date) { if (d->server_unread_count + d->local_unread_count > 0) { MessageId max_message_id = d->last_database_message_id.is_valid() ? d->last_database_message_id : d->last_new_message_id; if (max_message_id.is_valid()) { read_history_inbox(d, max_message_id, -1, "delete_all_dialog_messages 1"); } if (d->server_unread_count != 0 || d->local_unread_count != 0) { set_dialog_last_read_inbox_message_id(d, MessageId::min(), 0, 0, true, "delete_all_dialog_messages 2"); } } if (d->unread_mention_count > 0) { set_dialog_unread_mention_count(d, 0); send_update_chat_unread_mention_count(d); } if (d->unread_reaction_count > 0) { set_dialog_unread_reaction_count(d, 0); send_update_chat_unread_reaction_count(d, "delete_all_dialog_messages"); } bool has_last_message_id = d->last_message_id != MessageId(); MessageId last_clear_history_message_id; if (!remove_from_dialog_list) { if (has_last_message_id) { last_clear_history_message_id = d->last_message_id; } else { last_message_date = d->last_clear_history_date; last_clear_history_message_id = d->last_clear_history_message_id; } } if (d->reply_markup_message_id != MessageId()) { set_dialog_reply_markup(d, MessageId()); } set_dialog_first_database_message_id(d, MessageId(), "delete_all_dialog_messages 4"); set_dialog_last_database_message_id(d, MessageId(), "delete_all_dialog_messages 5"); set_dialog_last_clear_history_date(d, last_message_date, last_clear_history_message_id, "delete_all_dialog_messages 6"); d->last_read_all_mentions_message_id = MessageId(); // it is not needed anymore std::fill(d->message_count_by_index.begin(), d->message_count_by_index.end(), 0); if (has_last_message_id) { set_dialog_last_message_id(d, MessageId(), "delete_all_dialog_messages 7"); send_update_chat_last_message(d, "delete_all_dialog_messages 8"); } if (remove_from_dialog_list) { set_dialog_order(d, DEFAULT_ORDER, true, false, "delete_all_dialog_messages 9"); } else { update_dialog_pos(d, "delete_all_dialog_messages 10"); } } void MessagesManager::delete_all_dialog_messages(Dialog *d, bool remove_from_dialog_list, bool is_permanently_deleted) { CHECK(d != nullptr); LOG(INFO) << "Delete all messages in " << d->dialog_id << " with remove_from_dialog_list = " << remove_from_dialog_list << " and is_permanently_deleted = " << is_permanently_deleted; if (!td_->auth_manager_->is_bot()) { int32 last_message_date = 0; if (!remove_from_dialog_list && d->last_message_id.is_valid()) { auto m = get_message(d, d->last_message_id); CHECK(m != nullptr); last_message_date = m->date; } clear_dialog_message_list(d, remove_from_dialog_list, last_message_date); } vector deleted_message_ids; d->messages.foreach([&](const MessageId &message_id, unique_ptr &message) { CHECK(message_id == message->message_id); Message *m = message.get(); static_cast(m)->remove(); LOG(INFO) << "Delete " << message_id; deleted_message_ids.push_back(message_id.get()); delete_active_live_location(d->dialog_id, m); remove_message_file_sources(d->dialog_id, m); on_message_deleted(d, m, is_permanently_deleted, "do_delete_all_dialog_messages"); if (is_permanently_deleted) { d->deleted_message_ids.insert(m->message_id); } }); Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), d->messages, d->ordered_messages); delete_all_dialog_messages_from_database(d, MessageId::max(), "delete_all_dialog_messages 3"); if (d->notification_info != nullptr) { // they aren't needed anymore delete_all_dialog_notifications(d, MessageId::max(), "delete_all_dialog_messages 4"); d->notification_info->message_notification_group_.drop_max_removed_notification_id(); d->notification_info->mention_notification_group_.drop_max_removed_notification_id(); d->notification_info->notification_id_to_message_id_.clear(); } on_dialog_updated(d->dialog_id, "delete_all_dialog_messages 11"); send_update_delete_messages(d->dialog_id, std::move(deleted_message_ids), is_permanently_deleted); } void MessagesManager::on_dialog_deleted(DialogId dialog_id, Promise &&promise) { LOG(INFO) << "Delete " << dialog_id; Dialog *d = get_dialog_force(dialog_id, "on_dialog_deleted"); if (d == nullptr) { return promise.set_value(Unit()); } delete_all_dialog_messages(d, true, false); if (dialog_id.get_type() != DialogType::SecretChat) { d->have_full_history = false; d->have_full_history_source = 0; d->is_empty = false; d->need_restore_reply_markup = true; on_dialog_updated(dialog_id, "on_dialog_deleted"); } recently_found_dialogs_.remove_dialog(dialog_id); recently_opened_dialogs_.remove_dialog(dialog_id); if (dialog_id.get_type() == DialogType::Channel) { G()->td_db()->get_binlog_pmc()->erase(get_channel_pts_key(dialog_id)); } close_dialog(d); td_->forum_topic_manager_->delete_all_dialog_topics(dialog_id); promise.set_value(Unit()); } void MessagesManager::on_update_dialog_group_call_rights(DialogId dialog_id) { auto d = get_dialog(dialog_id); if (d == nullptr) { // nothing to do return; } if (d->active_group_call_id.is_valid()) { td_->group_call_manager_->on_update_group_call_rights(d->active_group_call_id); } } void MessagesManager::read_all_dialog_mentions(DialogId dialog_id, MessageId top_thread_message_id, Promise &&promise) { Dialog *d = get_dialog_force(dialog_id, "read_all_dialog_mentions"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } TRY_STATUS_PROMISE(promise, can_use_top_thread_message_id(d, top_thread_message_id, MessageInputReplyTo())); if (!have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Chat is not accessible")); } if (top_thread_message_id.is_valid()) { LOG(INFO) << "Receive readAllChatMentions request in thread of " << top_thread_message_id << " in " << dialog_id; AffectedHistoryQuery query = [td = td_, top_thread_message_id](DialogId dialog_id, Promise &&query_promise) { td->create_handler(std::move(query_promise))->send(dialog_id, top_thread_message_id); }; run_affected_history_query_until_complete(dialog_id, std::move(query), true, std::move(promise)); return; } else { LOG(INFO) << "Receive readAllChatMentions request in " << dialog_id << " with " << d->unread_mention_count << " unread mentions"; } if (dialog_id.get_type() == DialogType::SecretChat) { CHECK(d->unread_mention_count == 0); return promise.set_value(Unit()); } if (d->last_new_message_id > d->last_read_all_mentions_message_id) { d->last_read_all_mentions_message_id = d->last_new_message_id; on_dialog_updated(dialog_id, "read_all_dialog_mentions"); } auto message_ids = find_dialog_messages(d, [](const Message *m) { return m->contains_unread_mention; }); LOG(INFO) << "Found " << message_ids.size() << " messages with unread mentions in memory"; bool is_update_sent = false; for (auto message_id : message_ids) { auto m = get_message(d, message_id); CHECK(m != nullptr); CHECK(m->contains_unread_mention); CHECK(m->message_id == message_id); CHECK(m->message_id.is_valid()); remove_message_notification_id(d, m, true, false); // must be called before contains_unread_mention is updated m->contains_unread_mention = false; send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(dialog_id, "updateMessageMentionRead"), m->message_id.get(), 0)); is_update_sent = true; on_message_changed(d, m, true, "read_all_dialog_mentions"); } if (d->unread_mention_count != 0) { set_dialog_unread_mention_count(d, 0); if (!is_update_sent) { send_update_chat_unread_mention_count(d); } else { LOG(INFO) << "Update unread mention message count in " << dialog_id << " to " << d->unread_mention_count; on_dialog_updated(dialog_id, "read_all_dialog_mentions"); } } remove_message_dialog_notifications(d, MessageId::max(), true, "read_all_dialog_mentions"); read_all_dialog_mentions_on_server(dialog_id, 0, std::move(promise)); } class MessagesManager::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 MessagesManager::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 MessagesManager::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, MessageId()); }; run_affected_history_query_until_complete(dialog_id, std::move(query), false, get_erase_log_event_promise(log_event_id, std::move(promise))); } void MessagesManager::read_all_dialog_reactions(DialogId dialog_id, MessageId top_thread_message_id, Promise &&promise) { Dialog *d = get_dialog_force(dialog_id, "read_all_dialog_reactions"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } TRY_STATUS_PROMISE(promise, can_use_top_thread_message_id(d, top_thread_message_id, MessageInputReplyTo())); if (!have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Chat is not accessible")); } if (top_thread_message_id.is_valid()) { LOG(INFO) << "Receive readAllChatReactions request in thread of " << top_thread_message_id << " in " << dialog_id; AffectedHistoryQuery query = [td = td_, top_thread_message_id](DialogId dialog_id, Promise &&query_promise) { td->create_handler(std::move(query_promise))->send(dialog_id, top_thread_message_id); }; run_affected_history_query_until_complete(dialog_id, std::move(query), true, std::move(promise)); return; } else { LOG(INFO) << "Receive readAllChatReactions request in " << dialog_id << " with " << d->unread_reaction_count << " unread reactions"; } if (dialog_id.get_type() == DialogType::SecretChat) { CHECK(d->unread_reaction_count == 0); return promise.set_value(Unit()); } auto message_ids = find_dialog_messages( d, [this, dialog_id](const Message *m) { return has_unread_message_reactions(dialog_id, m); }); LOG(INFO) << "Found " << message_ids.size() << " messages with unread reactions in memory"; bool is_update_sent = false; for (auto message_id : message_ids) { auto m = get_message(d, message_id); CHECK(m != nullptr); CHECK(has_unread_message_reactions(dialog_id, m)); CHECK(m->message_id == message_id); CHECK(m->message_id.is_valid()); // remove_message_notification_id(d, m, true, false); // must be called before unread_reactions are cleared m->reactions->unread_reactions_.clear(); send_update_message_unread_reactions(dialog_id, m, 0); is_update_sent = true; on_message_changed(d, m, true, "read_all_dialog_reactions"); } if (d->unread_reaction_count != 0) { set_dialog_unread_reaction_count(d, 0); if (!is_update_sent) { send_update_chat_unread_reaction_count(d, "read_all_dialog_reactions"); } else { LOG(INFO) << "Update unread reaction message count in " << dialog_id << " to " << d->unread_reaction_count; on_dialog_updated(dialog_id, "read_all_dialog_reactions"); } } // remove_message_dialog_notifications(d, MessageId::max(), true, "read_all_dialog_reactions"); read_all_dialog_reactions_on_server(dialog_id, 0, std::move(promise)); } class MessagesManager::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 MessagesManager::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 MessagesManager::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, MessageId()); }; run_affected_history_query_until_complete(dialog_id, std::move(query), false, get_erase_log_event_promise(log_event_id, std::move(promise))); } void MessagesManager::read_message_content_from_updates(MessageId message_id, int32 read_date) { if (!message_id.is_valid() || !message_id.is_server()) { LOG(ERROR) << "Incoming update tries to read content of " << message_id; return; } Dialog *d = get_dialog_by_message_id(message_id); if (d != nullptr) { Message *m = get_message(d, message_id); CHECK(m != nullptr); read_message_content(d, m, false, read_date, "read_message_content_from_updates"); } } void MessagesManager::read_channel_message_content_from_updates(Dialog *d, MessageId message_id) { CHECK(d != nullptr); if (!message_id.is_valid() || !message_id.is_server()) { LOG(ERROR) << "Incoming update tries to read content of " << message_id << " in " << d->dialog_id; return; } if (td_->auth_manager_->is_bot()) { return; } Message *m = get_message_force(d, message_id, "read_channel_message_content_from_updates"); if (m != nullptr) { read_message_content(d, m, false, 0, "read_channel_message_content_from_updates"); } else { if (!have_input_peer(d->dialog_id, AccessRights::Read)) { LOG(INFO) << "Ignore updateChannelReadMessagesContents in inaccessible " << d->dialog_id; if (d->unread_mention_count != 0) { set_dialog_unread_mention_count(d, 0); } return; } if (message_id > d->last_new_message_id && d->last_new_message_id.is_valid()) { get_channel_difference(d->dialog_id, d->pts, 0, message_id, true, "read_channel_message_content_from_updates"); } else { // there is no message, so the update can be ignored if (d->unread_mention_count > 0) { // but if the chat has unread mentions, then number of unread mentions could have been changed repair_dialog_unread_mention_count(d, "read_channel_message_content_from_updates"); } } } } bool MessagesManager::read_message_content(Dialog *d, Message *m, bool is_local_read, int32 read_date, const char *source) { LOG_CHECK(m != nullptr) << source; CHECK(!m->message_id.is_scheduled()); bool is_mention_read = update_message_contains_unread_mention(d, m, false, "read_message_content"); bool is_content_read = update_opened_message_content(m->content.get()); if (ttl_on_open(d, m, Time::now(), is_local_read, read_date)) { is_content_read = true; } LOG(INFO) << "Read message content of " << m->message_id << " in " << d->dialog_id << ": is_mention_read = " << is_mention_read << ", is_content_read = " << is_content_read; if (is_mention_read || is_content_read) { on_message_changed(d, m, true, "read_message_content"); if (is_content_read) { send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateMessageContentOpened"), m->message_id.get())); } return true; } return false; } bool MessagesManager::has_incoming_notification(DialogId dialog_id, const Message *m) const { CHECK(m != nullptr); if (m->is_from_scheduled) { return true; } return !m->message_id.is_scheduled() && !m->is_outgoing && dialog_id != get_my_dialog_id(); } int32 MessagesManager::calc_new_unread_count_from_last_unread(Dialog *d, MessageId max_message_id, MessageType type) const { CHECK(!max_message_id.is_scheduled()); auto it = d->ordered_messages.get_const_iterator(max_message_id); if (*it == nullptr || (*it)->get_message_id() != max_message_id) { return -1; } int32 unread_count = type == MessageType::Server ? d->server_unread_count : d->local_unread_count; while (*it != nullptr && (*it)->get_message_id() > d->last_read_inbox_message_id) { auto message_id = (*it)->get_message_id(); if (message_id.get_type() == type && has_incoming_notification(d->dialog_id, get_message(d, message_id))) { unread_count--; } --it; } if (*it == nullptr || (*it)->get_message_id() != d->last_read_inbox_message_id) { return -1; } LOG(INFO) << "Found " << unread_count << " unread messages in " << d->dialog_id << " from last unread message"; return unread_count; } int32 MessagesManager::calc_new_unread_count_from_the_end(Dialog *d, MessageId max_message_id, MessageType type, int32 hint_unread_count) const { CHECK(!max_message_id.is_scheduled()); int32 unread_count = 0; auto it = d->ordered_messages.get_const_iterator(MessageId::max()); while (*it != nullptr && (*it)->get_message_id() > max_message_id) { auto message_id = (*it)->get_message_id(); if (message_id.get_type() == type && has_incoming_notification(d->dialog_id, get_message(d, message_id))) { unread_count++; } --it; } bool is_count_exact = d->last_message_id.is_valid() && *it != nullptr; if (hint_unread_count >= 0) { if (is_count_exact) { if (hint_unread_count == unread_count) { return hint_unread_count; } } else { if (hint_unread_count >= unread_count) { return hint_unread_count; } } // hint_unread_count is definitely wrong, ignore it if (need_unread_counter(d->order)) { LOG(ERROR) << "Receive hint_unread_count = " << hint_unread_count << ", but found " << unread_count << " unread messages in " << d->dialog_id; } } if (!is_count_exact) { // unread count is likely to be calculated wrong, so ignore it return -1; } LOG(INFO) << "Found " << unread_count << " unread messages in " << d->dialog_id << " from the end"; return unread_count; } int32 MessagesManager::calc_new_unread_count(Dialog *d, MessageId max_message_id, MessageType type, int32 hint_unread_count) const { CHECK(!td_->auth_manager_->is_bot()); CHECK(!max_message_id.is_scheduled()); if (d->is_empty) { return 0; } if (!d->last_read_inbox_message_id.is_valid()) { return calc_new_unread_count_from_the_end(d, max_message_id, type, hint_unread_count); } if (!d->last_message_id.is_valid() || (d->last_message_id.get() - max_message_id.get() > max_message_id.get() - d->last_read_inbox_message_id.get())) { int32 unread_count = calc_new_unread_count_from_last_unread(d, max_message_id, type); return unread_count >= 0 ? unread_count : calc_new_unread_count_from_the_end(d, max_message_id, type, hint_unread_count); } else { int32 unread_count = calc_new_unread_count_from_the_end(d, max_message_id, type, hint_unread_count); return unread_count >= 0 ? unread_count : calc_new_unread_count_from_last_unread(d, max_message_id, type); } } void MessagesManager::repair_server_unread_count(DialogId dialog_id, int32 unread_count, const char *source) { if (td_->auth_manager_->is_bot() || !have_input_peer(dialog_id, AccessRights::Read)) { return; } if (pending_read_history_timeout_.has_timeout(dialog_id.get())) { return; // postpone until read history request is sent } LOG(INFO) << "Repair server unread count in " << dialog_id << " from " << unread_count << " from " << source; create_actor("RepairServerUnreadCountSleepActor", 0.2, PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](Result result) { send_closure(actor_id, &MessagesManager::send_get_dialog_query, dialog_id, Promise(), 0, "repair_server_unread_count"); })) .release(); } void MessagesManager::repair_channel_server_unread_count(Dialog *d) { CHECK(d != nullptr); CHECK(d->dialog_id.get_type() == DialogType::Channel); if (td_->auth_manager_->is_bot()) { return; } if (d->last_read_inbox_message_id >= d->last_new_message_id) { // all messages are already read return; } if (!need_unread_counter(d->order)) { // there are no unread counters in left channels return; } if (!d->need_repair_channel_server_unread_count) { d->need_repair_channel_server_unread_count = true; on_dialog_updated(d->dialog_id, "repair_channel_server_unread_count"); } LOG(INFO) << "Reload ChannelFull for " << d->dialog_id << " to repair unread message counts"; get_dialog_info_full(d->dialog_id, Auto(), "repair_channel_server_unread_count"); } void MessagesManager::repair_dialog_unread_reaction_count(Dialog *d, Promise &&promise, const char *source) { CHECK(d != nullptr); if (td_->auth_manager_->is_bot()) { return; } if (!d->need_repair_unread_reaction_count) { d->need_repair_unread_reaction_count = true; on_dialog_updated(d->dialog_id, "repair_dialog_unread_reaction_count"); } send_get_dialog_query(d->dialog_id, std::move(promise), 0, source); } void MessagesManager::repair_dialog_unread_mention_count(Dialog *d, const char *source) { CHECK(d != nullptr); if (td_->auth_manager_->is_bot()) { return; } if (!d->need_repair_unread_mention_count) { d->need_repair_unread_mention_count = true; on_dialog_updated(d->dialog_id, "repair_dialog_unread_mention_count"); } send_get_dialog_query(d->dialog_id, Promise(), 0, source); } void MessagesManager::read_history_inbox(DialogId dialog_id, MessageId max_message_id, int32 unread_count, const char *source) { CHECK(!max_message_id.is_scheduled()); if (td_->auth_manager_->is_bot()) { return; } Dialog *d = get_dialog_force(dialog_id, "read_history_inbox"); if (d != nullptr) { read_history_inbox(d, max_message_id, unread_count, source); } else { LOG(INFO) << "Receive read inbox about unknown " << dialog_id << " from " << source; } } void MessagesManager::read_history_inbox(Dialog *d, MessageId max_message_id, int32 unread_count, const char *source) { if (td_->auth_manager_->is_bot()) { return; } auto dialog_id = d->dialog_id; if (d->need_repair_channel_server_unread_count) { d->need_repair_channel_server_unread_count = false; on_dialog_updated(dialog_id, "read_history_inbox"); } // there can be updateReadHistoryInbox up to message 0, if messages where read and then all messages where deleted if (!max_message_id.is_valid() && max_message_id != MessageId()) { LOG(ERROR) << "Receive read inbox update in " << dialog_id << " up to " << max_message_id << " from " << source; return; } if (d->is_last_read_inbox_message_id_inited && max_message_id <= d->last_read_inbox_message_id) { LOG(INFO) << "Receive read inbox update in " << dialog_id << " up to " << max_message_id << " from " << source << ", but all messages have already been read up to " << d->last_read_inbox_message_id; if (max_message_id == d->last_read_inbox_message_id && unread_count >= 0 && unread_count != d->server_unread_count) { set_dialog_last_read_inbox_message_id(d, MessageId::min(), unread_count, d->local_unread_count, true, source); } return; } if (max_message_id != MessageId() && max_message_id.is_yet_unsent()) { LOG(ERROR) << "Tried to update last read inbox message in " << dialog_id << " with " << max_message_id << " from " << source; return; } if (max_message_id != MessageId() && unread_count > 0 && max_message_id >= d->last_new_message_id && max_message_id >= d->last_message_id && max_message_id >= d->last_database_message_id) { if (d->last_new_message_id.is_valid()) { LOG(ERROR) << "Have unknown " << unread_count << " unread messages up to " << max_message_id << " in " << dialog_id << " with last_new_message_id = " << d->last_new_message_id << ", last_message_id = " << d->last_message_id << ", last_database_message_id = " << d->last_database_message_id << ", and " << d->server_unread_count << " unread messages up to " << d->last_read_inbox_message_id << " from " << source; unread_count = d->server_unread_count; } else { unread_count = 0; } } LOG_IF(INFO, d->last_new_message_id.is_valid() && max_message_id > d->last_new_message_id && (d->notification_info != nullptr && max_message_id > d->notification_info->max_push_notification_message_id_) && max_message_id.is_server() && dialog_id.get_type() != DialogType::Channel && !running_get_difference_) << "Receive read inbox update up to unknown " << max_message_id << " in " << dialog_id << " from " << source << ". Last new is " << d->last_new_message_id << ", unread_count = " << unread_count << ". Possible only for deleted incoming message"; if (dialog_id.get_type() == DialogType::SecretChat) { ttl_read_history(d, false, max_message_id, d->last_read_inbox_message_id, Time::now()); } if (max_message_id > d->last_new_message_id && dialog_id.get_type() == DialogType::Channel) { schedule_get_channel_difference(dialog_id, 0, max_message_id, 0.001, "read_history_inbox"); } int32 server_unread_count = calc_new_unread_count(d, max_message_id, MessageType::Server, unread_count); int32 local_unread_count = d->local_unread_count == 0 ? 0 : calc_new_unread_count(d, max_message_id, MessageType::Local, -1); if (server_unread_count < 0) { server_unread_count = unread_count >= 0 ? unread_count : d->server_unread_count; if (dialog_id.get_type() != DialogType::SecretChat && have_input_peer(dialog_id, AccessRights::Read) && need_unread_counter(d->order)) { d->need_repair_server_unread_count = true; on_dialog_updated(dialog_id, "read_history_inbox"); repair_server_unread_count(dialog_id, server_unread_count, "read_history_inbox"); } } if (local_unread_count < 0) { // TODO repair local unread count local_unread_count = d->local_unread_count; } set_dialog_last_read_inbox_message_id(d, max_message_id, server_unread_count, local_unread_count, true, source); if (d->is_marked_as_unread && max_message_id != MessageId()) { set_dialog_is_marked_as_unread(d, false); } } void MessagesManager::read_history_outbox(DialogId dialog_id, MessageId max_message_id, int32 read_date) { CHECK(!max_message_id.is_scheduled()); if (td_->auth_manager_->is_bot()) { return; } Dialog *d = get_dialog_force(dialog_id, "read_history_outbox"); if (d != nullptr) { read_history_outbox(d, max_message_id, read_date); } else { LOG(INFO) << "Receive read outbox update about unknown " << dialog_id; } } void MessagesManager::read_history_outbox(Dialog *d, MessageId max_message_id, int32 read_date) { if (td_->auth_manager_->is_bot()) { return; } auto dialog_id = d->dialog_id; if (!max_message_id.is_valid()) { LOG(ERROR) << "Receive read outbox update in " << dialog_id << " with " << max_message_id; return; } if (max_message_id <= d->last_read_outbox_message_id) { LOG(INFO) << "Receive read outbox update up to " << max_message_id << ", but all messages have already been read up to " << d->last_read_outbox_message_id; return; } if (max_message_id.is_yet_unsent()) { LOG(ERROR) << "Tried to update last read outbox message with " << max_message_id << " in " << dialog_id; return; } // it is impossible for just sent outgoing messages because updates are ordered by PTS if (d->last_new_message_id.is_valid() && max_message_id > d->last_new_message_id && dialog_id.get_type() != DialogType::Channel) { LOG(INFO) << "Receive read outbox update about unknown " << max_message_id << " in " << dialog_id << " with last new " << d->last_new_message_id << ". Possible only for deleted outgoing message"; } if (dialog_id.get_type() == DialogType::SecretChat) { double server_time = G()->server_time(); double read_time = Time::now(); if (read_date <= 0) { LOG(ERROR) << "Receive wrong read date " << read_date << " in " << dialog_id; } else if (read_date < server_time) { read_time -= (server_time - read_date); } ttl_read_history(d, true, max_message_id, d->last_read_outbox_message_id, read_time); } set_dialog_last_read_outbox_message_id(d, max_message_id); } bool MessagesManager::need_unread_counter(int64 dialog_order) { return dialog_order != DEFAULT_ORDER; } int32 MessagesManager::get_dialog_total_count(const DialogList &list) const { int32 sponsored_dialog_count = 0; if (sponsored_dialog_id_.is_valid() && list.dialog_list_id == DialogListId(FolderId::main())) { auto d = get_dialog(sponsored_dialog_id_); CHECK(d != nullptr); if (is_dialog_sponsored(d)) { sponsored_dialog_count = 1; } } if (list.server_dialog_total_count_ != -1 && list.secret_chat_total_count_ != -1) { return std::max(list.server_dialog_total_count_ + list.secret_chat_total_count_, list.in_memory_dialog_total_count_) + sponsored_dialog_count; } if (list.list_last_dialog_date_ == MAX_DIALOG_DATE) { return list.in_memory_dialog_total_count_ + sponsored_dialog_count; } return list.in_memory_dialog_total_count_ + sponsored_dialog_count + 1; } void MessagesManager::repair_server_dialog_total_count(DialogListId dialog_list_id) { if (G()->close_flag()) { return; } if (td_->auth_manager_->is_bot()) { return; } if (!dialog_list_id.is_folder()) { // can repair total count only in folders return; } LOG(INFO) << "Repair total chat count in " << dialog_list_id; td_->create_handler(Promise()) ->send(dialog_list_id.get_folder_id(), 2147483647, ServerMessageId(), DialogId(), 1); } void MessagesManager::repair_secret_chat_total_count(DialogListId dialog_list_id) { if (td_->auth_manager_->is_bot()) { return; } if (G()->use_message_database() && dialog_list_id.is_folder()) { // race-prone G()->td_db()->get_dialog_db_async()->get_secret_chat_count( dialog_list_id.get_folder_id(), PromiseCreator::lambda([actor_id = actor_id(this), dialog_list_id](Result result) { if (result.is_error()) { return; } send_closure(actor_id, &MessagesManager::on_get_secret_chat_total_count, dialog_list_id, result.move_as_ok()); })); } else { int32 total_count = 0; auto *list = get_dialog_list(dialog_list_id); CHECK(list != nullptr); for (auto &folder_id : get_dialog_list_folder_ids(*list)) { const auto *folder_list = get_dialog_list(DialogListId(folder_id)); CHECK(folder_list != nullptr); if (folder_list->need_unread_count_recalc_) { // can't repair total secret chat count yet return; } const auto *folder = get_dialog_folder(folder_id); CHECK(folder != nullptr); for (const auto &dialog_date : folder->ordered_dialogs_) { auto dialog_id = dialog_date.get_dialog_id(); if (dialog_id.get_type() == DialogType::SecretChat && dialog_date.get_order() != DEFAULT_ORDER) { total_count++; } } } on_get_secret_chat_total_count(dialog_list_id, total_count); } } void MessagesManager::on_get_secret_chat_total_count(DialogListId dialog_list_id, int32 total_count) { if (G()->close_flag()) { return; } CHECK(!td_->auth_manager_->is_bot()); auto *list = get_dialog_list(dialog_list_id); if (list == nullptr) { // just in case return; } CHECK(total_count >= 0); if (list->secret_chat_total_count_ != total_count) { auto old_dialog_total_count = get_dialog_total_count(*list); list->secret_chat_total_count_ = total_count; if (list->is_dialog_unread_count_inited_) { if (old_dialog_total_count != get_dialog_total_count(*list)) { send_update_unread_chat_count(*list, DialogId(), true, "on_get_secret_chat_total_count"); } else { save_unread_chat_count(*list); } } } } void MessagesManager::recalc_unread_count(DialogListId dialog_list_id, int32 old_dialog_total_count, bool force) { if (G()->close_flag() || td_->auth_manager_->is_bot() || !G()->use_message_database()) { return; } auto *list_ptr = get_dialog_list(dialog_list_id); CHECK(list_ptr != nullptr); auto &list = *list_ptr; if (!list.need_unread_count_recalc_ && !force) { return; } LOG(INFO) << "Recalculate unread counts in " << dialog_list_id; list.need_unread_count_recalc_ = false; list.is_message_unread_count_inited_ = true; list.is_dialog_unread_count_inited_ = true; int32 message_total_count = 0; int32 message_muted_count = 0; int32 dialog_total_count = 0; int32 dialog_muted_count = 0; int32 dialog_marked_count = 0; int32 dialog_muted_marked_count = 0; int32 server_dialog_total_count = 0; int32 secret_chat_total_count = 0; for (auto folder_id : get_dialog_list_folder_ids(list)) { const auto &folder = *get_dialog_folder(folder_id); for (const auto &dialog_date : folder.ordered_dialogs_) { if (dialog_date.get_order() == DEFAULT_ORDER) { break; } auto dialog_id = dialog_date.get_dialog_id(); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); if (!is_dialog_in_list(d, dialog_list_id)) { continue; } int unread_count = d->server_unread_count + d->local_unread_count; if (need_unread_counter(d->order) && (unread_count > 0 || d->is_marked_as_unread)) { message_total_count += unread_count; dialog_total_count++; if (unread_count == 0 && d->is_marked_as_unread) { dialog_marked_count++; } LOG(DEBUG) << "Have " << unread_count << " messages in " << dialog_id; if (is_dialog_muted(d)) { message_muted_count += unread_count; dialog_muted_count++; if (unread_count == 0 && d->is_marked_as_unread) { dialog_muted_marked_count++; } } } if (d->order != DEFAULT_ORDER) { // must not count sponsored dialog, which is added independently if (dialog_id.get_type() == DialogType::SecretChat) { secret_chat_total_count++; } else { server_dialog_total_count++; } } } } if (list.unread_message_total_count_ != message_total_count || list.unread_message_muted_count_ != message_muted_count) { list.unread_message_total_count_ = message_total_count; list.unread_message_muted_count_ = message_muted_count; send_update_unread_message_count(list, DialogId(), true, "recalc_unread_count"); } if (old_dialog_total_count == -1) { old_dialog_total_count = get_dialog_total_count(list); } bool need_save = false; if (list.list_last_dialog_date_ == MAX_DIALOG_DATE) { if (server_dialog_total_count != list.server_dialog_total_count_ || secret_chat_total_count != list.secret_chat_total_count_) { list.server_dialog_total_count_ = server_dialog_total_count; list.secret_chat_total_count_ = secret_chat_total_count; need_save = true; } } else { if (list.server_dialog_total_count_ == -1) { // recalc_unread_count is called only after getDialogs request; it is unneeded to call getDialogs again repair_server_dialog_total_count(dialog_list_id); } if (list.secret_chat_total_count_ == -1) { repair_secret_chat_total_count(dialog_list_id); } } if (list.unread_dialog_total_count_ != dialog_total_count || list.unread_dialog_muted_count_ != dialog_muted_count || list.unread_dialog_marked_count_ != dialog_marked_count || list.unread_dialog_muted_marked_count_ != dialog_muted_marked_count || old_dialog_total_count != get_dialog_total_count(list)) { list.unread_dialog_total_count_ = dialog_total_count; list.unread_dialog_muted_count_ = dialog_muted_count; list.unread_dialog_marked_count_ = dialog_marked_count; list.unread_dialog_muted_marked_count_ = dialog_muted_marked_count; send_update_unread_chat_count(list, DialogId(), true, "recalc_unread_count"); } else if (need_save) { save_unread_chat_count(list); } } void MessagesManager::set_dialog_last_read_inbox_message_id(Dialog *d, MessageId message_id, int32 server_unread_count, int32 local_unread_count, bool force_update, const char *source) { CHECK(!message_id.is_scheduled()); if (td_->auth_manager_->is_bot()) { return; } CHECK(d != nullptr); LOG(INFO) << "Update last read inbox message in " << d->dialog_id << " from " << d->last_read_inbox_message_id << " to " << message_id << " and update unread message count from " << d->server_unread_count << " + " << d->local_unread_count << " to " << server_unread_count << " + " << local_unread_count << " from " << source; if (message_id != MessageId::min()) { d->last_read_inbox_message_id = message_id; d->is_last_read_inbox_message_id_inited = true; } int32 old_unread_count = d->server_unread_count + d->local_unread_count; d->server_unread_count = server_unread_count; d->local_unread_count = local_unread_count; if (need_unread_counter(d->order)) { const int32 new_unread_count = d->server_unread_count + d->local_unread_count; for (auto &list : get_dialog_lists(d)) { int32 delta = new_unread_count - old_unread_count; if (delta != 0 && list.is_message_unread_count_inited_) { list.unread_message_total_count_ += delta; if (is_dialog_muted(d)) { list.unread_message_muted_count_ += delta; } send_update_unread_message_count(list, d->dialog_id, force_update, source); } delta = static_cast(new_unread_count != 0) - static_cast(old_unread_count != 0); if (delta != 0 && list.is_dialog_unread_count_inited_) { if (d->is_marked_as_unread) { list.unread_dialog_marked_count_ -= delta; } else { list.unread_dialog_total_count_ += delta; } if (is_dialog_muted(d)) { if (d->is_marked_as_unread) { list.unread_dialog_muted_marked_count_ -= delta; } else { list.unread_dialog_muted_count_ += delta; } } send_update_unread_chat_count(list, d->dialog_id, force_update, source); } } bool was_unread = old_unread_count != 0 || d->is_marked_as_unread; bool is_unread = new_unread_count != 0 || d->is_marked_as_unread; if (td_->dialog_filter_manager_->have_dialog_filters() && was_unread != is_unread) { update_dialog_lists(d, get_dialog_positions(d), true, false, "set_dialog_last_read_inbox_message_id"); } } if (message_id != MessageId::min() && d->last_read_inbox_message_id.is_valid() && (d->order != DEFAULT_ORDER || is_dialog_sponsored(d))) { VLOG(notifications) << "Remove some notifications in " << d->dialog_id << " after updating last read inbox message to " << message_id << " and unread message count to " << server_unread_count << " + " << local_unread_count << " from " << source; if (d->notification_info != nullptr && d->notification_info->message_notification_group_.is_valid()) { auto total_count = get_dialog_pending_notification_count(d, false); if (total_count == 0) { set_dialog_last_notification(d->dialog_id, d->notification_info->message_notification_group_, 0, NotificationId(), source); } if (!d->notification_info->pending_new_message_notifications_.empty()) { for (auto &it : d->notification_info->pending_new_message_notifications_) { if (it.second <= message_id) { it.first = DialogId(); } } flush_pending_new_message_notifications(d->dialog_id, false, DialogId(UserId(static_cast(1)))); } total_count -= static_cast(d->notification_info->pending_new_message_notifications_.size()); if (total_count < 0) { LOG(ERROR) << "Total message notification count is " << total_count << " in " << d->dialog_id << " with old unread_count = " << old_unread_count << " and " << d->notification_info->pending_new_message_notifications_ << " pending new message notifications after reading history up to " << message_id; total_count = 0; } send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification_group, d->notification_info->message_notification_group_.get_group_id(), NotificationId(), d->last_read_inbox_message_id, total_count, Slice(source) == Slice("view_messages"), Promise()); } if (d->notification_info != nullptr && d->notification_info->mention_notification_group_.is_valid() && d->notification_info->pinned_message_notification_message_id_.is_valid() && d->notification_info->pinned_message_notification_message_id_ <= d->last_read_inbox_message_id) { // remove pinned message notification when it is read remove_dialog_pinned_message_notification(d, source); } } on_dialog_updated(d->dialog_id, source); send_update_chat_read_inbox(d, force_update, source); } void MessagesManager::set_dialog_last_read_outbox_message_id(Dialog *d, MessageId message_id) { CHECK(!message_id.is_scheduled()); if (td_->auth_manager_->is_bot()) { return; } CHECK(d != nullptr); LOG(INFO) << "Update last read outbox message in " << d->dialog_id << " from " << d->last_read_outbox_message_id << " to " << message_id; d->last_read_outbox_message_id = message_id; d->is_last_read_outbox_message_id_inited = true; send_update_chat_read_outbox(d); } void MessagesManager::set_dialog_max_unavailable_message_id(DialogId dialog_id, MessageId max_unavailable_message_id, bool from_update, const char *source) { CHECK(!max_unavailable_message_id.is_scheduled()); Dialog *d = get_dialog_force(dialog_id, source); if (d != nullptr) { if (d->last_new_message_id.is_valid() && max_unavailable_message_id > d->last_new_message_id && from_update) { // possible if the last unavailable message has already been deleted LOG(INFO) << "Tried to set " << dialog_id << " max unavailable message to " << max_unavailable_message_id << " from " << source << ", but last new message is " << d->last_new_message_id; max_unavailable_message_id = d->last_new_message_id; } if (d->max_unavailable_message_id == max_unavailable_message_id) { return; } if (max_unavailable_message_id.is_valid() && max_unavailable_message_id.is_yet_unsent()) { LOG(ERROR) << "Tried to update " << dialog_id << " max unavailable message with " << max_unavailable_message_id << " from " << source; return; } LOG(INFO) << "Set max unavailable message to " << max_unavailable_message_id << " in " << dialog_id << " from " << source; on_dialog_updated(dialog_id, "set_dialog_max_unavailable_message_id"); if (d->max_unavailable_message_id > max_unavailable_message_id) { d->max_unavailable_message_id = max_unavailable_message_id; return; } d->max_unavailable_message_id = max_unavailable_message_id; auto message_ids = d->ordered_messages.find_older_messages(max_unavailable_message_id); vector deleted_message_ids; bool need_update_dialog_pos = false; for (auto message_id : message_ids) { if (message_id.is_yet_unsent()) { continue; } auto m = get_message(d, message_id); CHECK(m != nullptr); CHECK(m->message_id <= max_unavailable_message_id); CHECK(m->message_id == message_id); auto message = delete_message(d, message_id, !from_update, &need_update_dialog_pos, "set_dialog_max_unavailable_message_id"); CHECK(message.get() == m); deleted_message_ids.push_back(m->message_id.get()); } if (need_update_dialog_pos) { send_update_chat_last_message(d, "set_dialog_max_unavailable_message_id"); } send_update_delete_messages(dialog_id, std::move(deleted_message_ids), !from_update); if (d->server_unread_count + d->local_unread_count > 0) { read_history_inbox(d, max_unavailable_message_id, -1, "set_dialog_max_unavailable_message_id"); } } else { LOG(INFO) << "Receive max unavailable message in unknown " << dialog_id << " from " << source; } } void MessagesManager::set_dialog_online_member_count(DialogId dialog_id, int32 online_member_count, bool is_from_server, const char *source) { if (td_->auth_manager_->is_bot()) { return; } Dialog *d = get_dialog(dialog_id); if (d == nullptr) { return; } if (online_member_count < 0) { LOG(ERROR) << "Receive online_member_count = " << online_member_count << " in " << dialog_id; online_member_count = 0; } switch (dialog_id.get_type()) { case DialogType::Chat: { auto participant_count = td_->contacts_manager_->get_chat_participant_count(dialog_id.get_chat_id()); if (online_member_count > participant_count) { online_member_count = participant_count; } break; } case DialogType::Channel: { auto participant_count = td_->contacts_manager_->get_channel_participant_count(dialog_id.get_channel_id()); if (participant_count != 0 && online_member_count > participant_count) { online_member_count = participant_count; } break; } default: break; } auto &info = dialog_online_member_counts_[dialog_id]; LOG(INFO) << "Change number of online members from " << info.online_member_count << " to " << online_member_count << " in " << dialog_id << " from " << source; bool need_update = d->open_count > 0 && (!info.is_update_sent || info.online_member_count != online_member_count); info.online_member_count = online_member_count; info.update_time = Time::now(); if (need_update) { info.is_update_sent = true; send_update_chat_online_member_count(dialog_id, online_member_count); } if (d->open_count > 0) { if (is_from_server) { update_dialog_online_member_count_timeout_.set_timeout_in(dialog_id.get(), ONLINE_MEMBER_COUNT_UPDATE_TIME); } else { update_dialog_online_member_count_timeout_.add_timeout_in(dialog_id.get(), ONLINE_MEMBER_COUNT_UPDATE_TIME); } } } void MessagesManager::on_update_dialog_online_member_count_timeout(DialogId dialog_id) { if (G()->close_flag()) { return; } LOG(INFO) << "Expired timeout for number of online members in " << dialog_id; Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); if (d->open_count == 0) { send_update_chat_online_member_count(dialog_id, 0); return; } if (dialog_id.get_type() == DialogType::Channel && !is_broadcast_channel(dialog_id)) { auto participant_count = td_->contacts_manager_->get_channel_participant_count(dialog_id.get_channel_id()); auto has_hidden_participants = td_->contacts_manager_->get_channel_effective_has_hidden_participants( dialog_id.get_channel_id(), "on_update_dialog_online_member_count_timeout"); if (participant_count == 0 || participant_count >= 195 || has_hidden_participants) { td_->create_handler()->send(dialog_id); } else { td_->contacts_manager_->get_channel_participants(dialog_id.get_channel_id(), td_api::make_object(), string(), 0, 200, 200, Auto()); } return; } if (dialog_id.get_type() == DialogType::Chat) { // we need actual online status state, so we need to reget chat participants td_->contacts_manager_->repair_chat_participants(dialog_id.get_chat_id()); return; } } void MessagesManager::on_update_viewed_messages_timeout(DialogId dialog_id) { if (G()->close_flag()) { return; } LOG(INFO) << "Expired timeout for updating of recently viewed messages in " << dialog_id; Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); if (d->open_count == 0) { return; } auto it = dialog_viewed_messages_.find(dialog_id); if (it == dialog_viewed_messages_.end() || !td_->is_online()) { return; } auto &info = it->second; CHECK(info != nullptr); vector reaction_message_ids; vector views_message_ids; vector extended_media_message_ids; int32 newest_message_date = 0; for (auto &message_it : info->message_id_to_view_id) { Message *m = get_message_force(d, message_it.first, "on_update_viewed_messages_timeout"); CHECK(m != nullptr); CHECK(m->message_id.is_valid()); CHECK(m->message_id.is_server()); if (need_poll_message_reactions(d, m)) { reaction_message_ids.push_back(m->message_id); } if (m->view_count > 0 && !m->has_get_message_views_query) { m->has_get_message_views_query = true; views_message_ids.push_back(m->message_id); } if (need_poll_message_content_extended_media(m->content.get()) && !m->has_get_extended_media_query) { m->has_get_extended_media_query = true; extended_media_message_ids.push_back(m->message_id); } if (m->date > newest_message_date) { newest_message_date = m->date; } } if (!reaction_message_ids.empty()) { queue_message_reactions_reload(dialog_id, reaction_message_ids); } if (!views_message_ids.empty()) { td_->create_handler()->send(dialog_id, std::move(views_message_ids), false); } if (!extended_media_message_ids.empty()) { td_->create_handler()->send(dialog_id, std::move(extended_media_message_ids)); } int divisor = 5 - min(max(G()->unix_time() - newest_message_date, 0) / UPDATE_VIEWED_MESSAGES_PERIOD, 4); update_viewed_messages_timeout_.add_timeout_in(dialog_id.get(), UPDATE_VIEWED_MESSAGES_PERIOD / divisor); } void MessagesManager::on_send_update_chat_read_inbox_timeout(DialogId dialog_id) { if (G()->close_flag()) { return; } if (postponed_chat_read_inbox_updates_.erase(dialog_id) > 0) { send_update_chat_read_inbox(get_dialog(dialog_id), true, "on_send_update_chat_read_inbox_timeout"); } } int32 MessagesManager::get_message_date(const tl_object_ptr &message_ptr) { switch (message_ptr->get_id()) { case telegram_api::messageEmpty::ID: return 0; case telegram_api::message::ID: { auto message = static_cast(message_ptr.get()); return message->date_; } case telegram_api::messageService::ID: { auto message = static_cast(message_ptr.get()); return message->date_; } default: UNREACHABLE(); return 0; } } vector MessagesManager::get_message_user_ids(const Message *m) const { vector user_ids; if (m->sender_user_id.is_valid()) { user_ids.push_back(m->sender_user_id); } if (m->via_bot_user_id.is_valid()) { user_ids.push_back(m->via_bot_user_id); } if (m->forward_info != nullptr) { m->forward_info->origin.add_user_ids(user_ids); } append(user_ids, get_message_content_min_user_ids(td_, m->content.get())); if (!m->replied_message_info.is_empty()) { append(user_ids, m->replied_message_info.get_min_user_ids(td_)); } return user_ids; } vector MessagesManager::get_message_channel_ids(const Message *m) const { vector channel_ids; if (m->sender_dialog_id.is_valid() && m->sender_dialog_id.get_type() == DialogType::Channel) { channel_ids.push_back(m->sender_dialog_id.get_channel_id()); } if (m->forward_info != nullptr) { m->forward_info->origin.add_channel_ids(channel_ids); } if (m->forward_info != nullptr && m->forward_info->from_dialog_id.is_valid() && m->forward_info->from_dialog_id.get_type() == DialogType::Channel) { channel_ids.push_back(m->forward_info->from_dialog_id.get_channel_id()); } append(channel_ids, get_message_content_min_channel_ids(td_, m->content.get())); if (!m->replied_message_info.is_empty()) { append(channel_ids, m->replied_message_info.get_min_channel_ids(td_)); } return channel_ids; } void MessagesManager::ttl_read_history(Dialog *d, bool is_outgoing, MessageId from_message_id, MessageId till_message_id, double view_date) { CHECK(!from_message_id.is_scheduled()); CHECK(!till_message_id.is_scheduled()); // TODO: protect with log event suffix_load_till_message_id(d, till_message_id, PromiseCreator::lambda([actor_id = actor_id(this), dialog_id = d->dialog_id, is_outgoing, from_message_id, till_message_id, view_date](Result) { send_closure(actor_id, &MessagesManager::ttl_read_history_impl, dialog_id, is_outgoing, from_message_id, till_message_id, view_date); })); } void MessagesManager::ttl_read_history_impl(DialogId dialog_id, bool is_outgoing, MessageId from_message_id, MessageId till_message_id, double view_date) { CHECK(!from_message_id.is_scheduled()); CHECK(!till_message_id.is_scheduled()); auto *d = get_dialog(dialog_id); CHECK(d != nullptr); auto now = Time::now(); for (auto it = d->ordered_messages.get_const_iterator(from_message_id); *it && (*it)->get_message_id() >= till_message_id; --it) { auto *m = get_message(d, (*it)->get_message_id()); CHECK(m != nullptr); if (m->is_outgoing == is_outgoing) { ttl_on_view(d, m, view_date, now); } } } void MessagesManager::ttl_on_view(const Dialog *d, Message *m, double view_date, double now) { if (m->ttl > 0 && m->ttl_expires_at == 0 && !m->message_id.is_scheduled() && !m->message_id.is_yet_unsent() && !m->is_failed_to_send && !m->is_content_secret) { m->ttl_expires_at = m->ttl + view_date; ttl_register_message(d->dialog_id, m, now); on_message_changed(d, m, true, "ttl_on_view"); } } bool MessagesManager::ttl_on_open(Dialog *d, Message *m, double now, bool is_local_read, int32 read_date) { CHECK(!m->message_id.is_scheduled()); if (m->ttl > 0 && m->ttl_expires_at == 0) { int32 passed_after_read_time = 0; auto can_destroy_immediately = [&] { if (m->ttl == 0x7FFFFFFF) { return true; } if (is_local_read) { return false; } if (read_date <= 0) { return d->dialog_id.get_type() != DialogType::SecretChat; } passed_after_read_time = max(G()->unix_time() - read_date, 0); if (m->ttl <= passed_after_read_time) { return true; } return false; }(); if (can_destroy_immediately) { on_message_ttl_expired(d, m); } else { m->ttl_expires_at = m->ttl + now - passed_after_read_time; ttl_register_message(d->dialog_id, m, now); } return true; } return false; } void MessagesManager::ttl_register_message(DialogId dialog_id, const Message *m, double now) { CHECK(m != nullptr); CHECK(m->ttl_expires_at != 0); CHECK(!m->message_id.is_scheduled()); auto it_flag = ttl_nodes_.emplace(dialog_id, m->message_id, false); CHECK(it_flag.second); auto it = it_flag.first; ttl_heap_.insert(m->ttl_expires_at, it->as_heap_node()); ttl_update_timeout(now); } void MessagesManager::ttl_period_register_message(DialogId dialog_id, const Message *m, double server_time) { CHECK(m != nullptr); CHECK(m->ttl_period != 0); CHECK(!m->message_id.is_scheduled()); auto it_flag = ttl_nodes_.emplace(dialog_id, m->message_id, true); CHECK(it_flag.second); auto it = it_flag.first; auto now = Time::now(); ttl_heap_.insert(now + (m->date + m->ttl_period - server_time), it->as_heap_node()); ttl_update_timeout(now); } void MessagesManager::ttl_unregister_message(DialogId dialog_id, const Message *m, const char *source) { if (m->ttl_expires_at == 0) { return; } CHECK(!m->message_id.is_scheduled()); auto it = ttl_nodes_.find(TtlNode(dialog_id, m->message_id, false)); // expect m->ttl == 0, but m->ttl_expires_at > 0 from binlog CHECK(it != ttl_nodes_.end()); auto *heap_node = it->as_heap_node(); if (heap_node->in_heap()) { ttl_heap_.erase(heap_node); } ttl_nodes_.erase(it); ttl_update_timeout(Time::now()); } void MessagesManager::ttl_period_unregister_message(DialogId dialog_id, const Message *m) { if (m->ttl_period == 0) { return; } CHECK(!m->message_id.is_scheduled()); auto it = ttl_nodes_.find(TtlNode(dialog_id, m->message_id, true)); CHECK(it != ttl_nodes_.end()); auto *heap_node = it->as_heap_node(); if (heap_node->in_heap()) { ttl_heap_.erase(heap_node); } ttl_nodes_.erase(it); ttl_update_timeout(Time::now()); } void MessagesManager::ttl_loop(double now) { FlatHashMap, DialogIdHash> to_delete; while (!ttl_heap_.empty() && ttl_heap_.top_key() < now) { TtlNode *ttl_node = TtlNode::from_heap_node(ttl_heap_.pop()); auto message_full_id = ttl_node->message_full_id_; auto dialog_id = message_full_id.get_dialog_id(); CHECK(dialog_id.is_valid()); if (dialog_id.get_type() == DialogType::SecretChat || ttl_node->by_ttl_period_) { to_delete[dialog_id].push_back(message_full_id.get_message_id()); } else { auto d = get_dialog(dialog_id); CHECK(d != nullptr); auto m = get_message(d, message_full_id.get_message_id()); CHECK(m != nullptr); on_message_ttl_expired(d, m); on_message_changed(d, m, true, "ttl_loop"); } } for (auto &it : to_delete) { delete_dialog_messages(it.first, it.second, false, "ttl_loop"); } ttl_update_timeout(now); } void MessagesManager::ttl_update_timeout(double now) { if (ttl_heap_.empty()) { if (!ttl_slot_.empty()) { ttl_slot_.cancel_timeout(); } return; } ttl_slot_.set_event(EventCreator::yield(actor_id())); ttl_slot_.set_timeout_in(ttl_heap_.top_key() - now); } void MessagesManager::on_message_ttl_expired(Dialog *d, Message *m) { CHECK(d != nullptr); CHECK(m != nullptr); CHECK(m->ttl > 0); CHECK(d->dialog_id.get_type() != DialogType::SecretChat); ttl_unregister_message(d->dialog_id, m, "on_message_ttl_expired"); unregister_message_content(td_, m->content.get(), {d->dialog_id, m->message_id}, "on_message_ttl_expired"); remove_message_file_sources(d->dialog_id, m); on_message_ttl_expired_impl(d, m, true); register_message_content(td_, m->content.get(), {d->dialog_id, m->message_id}, "on_message_ttl_expired"); send_update_message_content(d, m, true, "on_message_ttl_expired"); // the caller must call on_message_changed } void MessagesManager::on_message_ttl_expired_impl(Dialog *d, Message *m, bool is_message_in_dialog) { CHECK(d != nullptr); CHECK(m != nullptr); CHECK(m->message_id.is_valid()); CHECK(!m->message_id.is_yet_unsent()); CHECK(m->ttl > 0); CHECK(d->dialog_id.get_type() != DialogType::SecretChat); delete_message_files(d->dialog_id, m); update_expired_message_content(m->content); m->ttl = 0; m->ttl_expires_at = 0; if (m->reply_markup != nullptr) { if (m->reply_markup->type != ReplyMarkup::Type::InlineKeyboard) { if (d->reply_markup_message_id == m->message_id) { set_dialog_reply_markup(d, MessageId()); } m->had_reply_markup = true; } m->reply_markup = nullptr; } remove_message_notification_id(d, m, true, true); update_message_contains_unread_mention(d, m, false, "on_message_ttl_expired_impl"); remove_message_unread_reactions(d, m, "on_message_ttl_expired_impl"); set_message_reply(d, m, MessageInputReplyTo(), is_message_in_dialog); m->noforwards = false; m->contains_mention = false; m->linked_top_thread_message_id = MessageId(); m->is_content_secret = false; m->invert_media = false; } void MessagesManager::loop() { auto token = get_link_token(); if (token == YieldType::TtlDb) { ttl_db_loop(); } else { ttl_loop(Time::now()); } } void MessagesManager::tear_down() { parent_.reset(); LOG(DEBUG) << "Have " << dialogs_.calc_size() << " chats with " << added_message_count_ << " messages to free"; } void MessagesManager::hangup() { postponed_channel_updates_.clear(); if (!G()->use_message_database()) { while (!being_uploaded_files_.empty()) { auto it = being_uploaded_files_.begin(); auto message_full_id = it->second.first; being_uploaded_files_.erase(it); if (message_full_id.get_message_id().is_yet_unsent()) { fail_send_message(message_full_id, Global::request_aborted_error()); } } while (!being_uploaded_thumbnails_.empty()) { auto it = being_uploaded_thumbnails_.begin(); auto message_full_id = it->second.message_full_id; being_uploaded_thumbnails_.erase(it); if (message_full_id.get_message_id().is_yet_unsent()) { fail_send_message(message_full_id, Global::request_aborted_error()); } } while (!being_loaded_secret_thumbnails_.empty()) { auto it = being_loaded_secret_thumbnails_.begin(); auto message_full_id = it->second.message_full_id; being_loaded_secret_thumbnails_.erase(it); if (message_full_id.get_message_id().is_yet_unsent()) { fail_send_message(message_full_id, Global::request_aborted_error()); } } while (!yet_unsent_media_queues_.empty()) { auto it = yet_unsent_media_queues_.begin(); auto queue = std::move(it->second); yet_unsent_media_queues_.erase(it); for (auto &promise_it : queue.queue_) { auto message_id = promise_it.first; if (message_id.is_yet_unsent()) { fail_send_message({queue.dialog_id_, message_id}, Global::request_aborted_error()); } } } while (!being_sent_messages_.empty()) { on_send_message_fail(being_sent_messages_.begin()->first, Global::request_aborted_error()); } if (!update_message_ids_.empty()) { LOG(ERROR) << "Have " << update_message_ids_.size() << " awaited sent messages"; } if (!update_scheduled_message_ids_.empty()) { LOG(ERROR) << "Have " << update_scheduled_message_ids_.size() << " awaited sent scheduled messages"; } } fail_promises(load_active_live_location_messages_queries_, Global::request_aborted_error()); auto fail_promise_map = [](auto &queries) { while (!queries.empty()) { auto it = queries.begin(); auto promises = std::move(it->second); queries.erase(it); fail_promises(promises, Global::request_aborted_error()); } }; fail_promise_map(get_dialog_queries_); fail_promise_map(load_scheduled_messages_from_database_queries_); fail_promise_map(run_after_get_channel_difference_); fail_promise_map(search_public_dialogs_queries_); fail_promise_map(get_history_queries_); while (!pending_channel_on_get_dialogs_.empty()) { auto it = pending_channel_on_get_dialogs_.begin(); auto promise = std::move(it->second.promise); pending_channel_on_get_dialogs_.erase(it); promise.set_error(Global::request_aborted_error()); } while (!get_dialogs_tasks_.empty()) { auto it = get_dialogs_tasks_.begin(); auto promise = std::move(it->second.promise); get_dialogs_tasks_.erase(it); promise.set_error(Global::request_aborted_error()); } stop(); } void MessagesManager::start_up() { init(); } void MessagesManager::create_folders() { LOG(INFO) << "Create folders"; dialog_folders_[FolderId::main()].folder_id = FolderId::main(); dialog_folders_[FolderId::archive()].folder_id = FolderId::archive(); add_dialog_list(DialogListId(FolderId::main())); add_dialog_list(DialogListId(FolderId::archive())); } void MessagesManager::init() { if (is_inited_) { return; } is_inited_ = true; td_->notification_settings_manager_->init(); // load scope notification settings td_->reaction_manager_->init(); // load available reactions start_time_ = Time::now(); last_channel_pts_jump_warning_time_ = start_time_ - 3600; bool was_authorized_user = td_->auth_manager_->was_authorized() && !td_->auth_manager_->is_bot(); if (was_authorized_user) { create_folders(); // ensure that Main and Archive dialog lists are created } authorization_date_ = td_->option_manager_->get_option_integer("authorization_date"); td_->dialog_filter_manager_->init(); // load dialog filters if (G()->use_message_database() && was_authorized_user) { // erase old keys G()->td_db()->get_binlog_pmc()->erase("last_server_dialog_date"); G()->td_db()->get_binlog_pmc()->erase("unread_message_count"); G()->td_db()->get_binlog_pmc()->erase("unread_dialog_count"); auto last_database_server_dialog_dates = G()->td_db()->get_binlog_pmc()->prefix_get("last_server_dialog_date"); for (auto &it : last_database_server_dialog_dates) { auto r_folder_id = to_integer_safe(it.first); if (r_folder_id.is_error()) { LOG(ERROR) << "Can't parse folder ID from " << it.first; continue; } string order_str; string dialog_id_str; std::tie(order_str, dialog_id_str) = split(it.second); auto r_order = to_integer_safe(order_str); auto r_dialog_id = to_integer_safe(dialog_id_str); if (r_order.is_error() || r_dialog_id.is_error()) { LOG(ERROR) << "Can't parse " << it.second; } else { FolderId folder_id(r_folder_id.ok()); auto *folder = get_dialog_folder(folder_id); CHECK(folder != nullptr); DialogDate dialog_date(r_order.ok(), DialogId(r_dialog_id.ok())); if (dialog_date.get_date() == 0 && dialog_date != MAX_DIALOG_DATE) { LOG(ERROR) << "Ignore incorrect last database server dialog date " << dialog_date << " in " << folder_id; } else { if (folder->last_database_server_dialog_date_ < dialog_date) { folder->last_database_server_dialog_date_ = dialog_date; } LOG(INFO) << "Loaded last_database_server_dialog_date_ " << folder->last_database_server_dialog_date_ << " in " << folder_id; } } } auto sponsored_dialog_id_string = G()->td_db()->get_binlog_pmc()->get("sponsored_dialog_id"); if (!sponsored_dialog_id_string.empty()) { auto dialog_id_source = split(Slice(sponsored_dialog_id_string)); auto r_dialog_id = to_integer_safe(dialog_id_source.first); auto r_source = DialogSource::unserialize(dialog_id_source.second); if (r_dialog_id.is_error() || r_source.is_error()) { LOG(ERROR) << "Can't parse " << sponsored_dialog_id_string; } else { DialogId dialog_id(r_dialog_id.ok()); const Dialog *d = get_dialog_force(dialog_id, "init"); if (d != nullptr) { LOG(INFO) << "Loaded sponsored " << dialog_id; add_sponsored_dialog(d, r_source.move_as_ok()); } else { LOG(ERROR) << "Can't load " << dialog_id; } } } auto pinned_dialog_ids = G()->td_db()->get_binlog_pmc()->prefix_get("pinned_dialog_ids"); for (auto &it : pinned_dialog_ids) { auto r_folder_id = to_integer_safe(it.first); if (r_folder_id.is_error()) { LOG(ERROR) << "Can't parse folder ID from " << it.first; continue; } FolderId folder_id(r_folder_id.ok()); auto r_dialog_ids = transform(full_split(Slice(it.second), ','), [](Slice str) -> Result { TRY_RESULT(dialog_id_int, to_integer_safe(str)); DialogId dialog_id(dialog_id_int); if (!dialog_id.is_valid()) { return Status::Error("Have invalid dialog ID"); } return dialog_id; }); if (any_of(r_dialog_ids, [](const auto &r_dialog_id) { return r_dialog_id.is_error(); })) { LOG(ERROR) << "Can't parse " << it.second; reload_pinned_dialogs(DialogListId(folder_id), Auto()); } else { auto *list = get_dialog_list(DialogListId(folder_id)); CHECK(list != nullptr); CHECK(list->pinned_dialogs_.empty()); for (auto &r_dialog_id : reversed(r_dialog_ids)) { auto dialog_id = r_dialog_id.move_as_ok(); if (!dialog_id.is_valid()) { LOG(ERROR) << "Loaded " << dialog_id << " as a pinned dialog"; continue; } auto order = get_next_pinned_dialog_order(); list->pinned_dialogs_.emplace_back(order, dialog_id); list->pinned_dialog_id_orders_.emplace(dialog_id, order); } std::reverse(list->pinned_dialogs_.begin(), list->pinned_dialogs_.end()); list->are_pinned_dialogs_inited_ = true; update_list_last_pinned_dialog_date(*list); LOG(INFO) << "Loaded pinned chats " << list->pinned_dialogs_ << " in " << folder_id; } } auto unread_message_counts = G()->td_db()->get_binlog_pmc()->prefix_get("unread_message_count"); for (auto &it : unread_message_counts) { auto r_dialog_list_id = to_integer_safe(it.first); if (r_dialog_list_id.is_error()) { LOG(ERROR) << "Can't parse dialog list ID from " << it.first; continue; } string total_count; string muted_count; std::tie(total_count, muted_count) = split(it.second); auto r_total_count = to_integer_safe(total_count); auto r_muted_count = to_integer_safe(muted_count); if (r_total_count.is_error() || r_muted_count.is_error()) { LOG(ERROR) << "Can't parse " << it.second; } else { DialogListId dialog_list_id(r_dialog_list_id.ok()); auto *list = get_dialog_list(dialog_list_id); if (list != nullptr) { list->unread_message_total_count_ = r_total_count.ok(); list->unread_message_muted_count_ = r_muted_count.ok(); list->is_message_unread_count_inited_ = true; send_update_unread_message_count(*list, DialogId(), true, "load unread_message_count", true); } else { G()->td_db()->get_binlog_pmc()->erase("unread_message_count" + it.first); } } } auto unread_dialog_counts = G()->td_db()->get_binlog_pmc()->prefix_get("unread_dialog_count"); for (auto &it : unread_dialog_counts) { auto r_dialog_list_id = to_integer_safe(it.first); if (r_dialog_list_id.is_error()) { LOG(ERROR) << "Can't parse dialog list ID from " << it.first; continue; } auto counts = transform(full_split(Slice(it.second)), [](Slice str) { return to_integer_safe(str); }); if ((counts.size() != 4 && counts.size() != 6) || any_of(counts, [](const auto &c) { return c.is_error(); })) { LOG(ERROR) << "Can't parse " << it.second; } else { DialogListId dialog_list_id(r_dialog_list_id.ok()); auto *list = get_dialog_list(dialog_list_id); if (list != nullptr) { list->unread_dialog_total_count_ = counts[0].ok(); list->unread_dialog_muted_count_ = counts[1].ok(); list->unread_dialog_marked_count_ = counts[2].ok(); list->unread_dialog_muted_marked_count_ = counts[3].ok(); if (counts.size() == 6) { list->server_dialog_total_count_ = counts[4].ok(); list->secret_chat_total_count_ = counts[5].ok(); } if (list->server_dialog_total_count_ == -1) { repair_server_dialog_total_count(dialog_list_id); } if (list->secret_chat_total_count_ == -1) { repair_secret_chat_total_count(dialog_list_id); } list->is_dialog_unread_count_inited_ = true; send_update_unread_chat_count(*list, DialogId(), true, "load unread_dialog_count", true); } else { G()->td_db()->get_binlog_pmc()->erase("unread_dialog_count" + it.first); } } } } else if (!td_->auth_manager_->is_bot()) { G()->td_db()->get_binlog_pmc()->erase_by_prefix("pinned_dialog_ids"); G()->td_db()->get_binlog_pmc()->erase_by_prefix("last_server_dialog_date"); G()->td_db()->get_binlog_pmc()->erase_by_prefix("unread_message_count"); G()->td_db()->get_binlog_pmc()->erase_by_prefix("unread_dialog_count"); G()->td_db()->get_binlog_pmc()->erase("sponsored_dialog_id"); G()->td_db()->get_binlog_pmc()->erase("fetched_marks_as_unread"); } G()->td_db()->get_binlog_pmc()->erase("dialog_pinned_current_order"); if (G()->use_message_database()) { ttl_db_loop(); } load_calls_db_state(); auto auth_notification_ids_string = G()->td_db()->get_binlog_pmc()->get("auth_notification_ids"); if (!auth_notification_ids_string.empty()) { VLOG(notifications) << "Loaded auth_notification_ids = " << auth_notification_ids_string; auto stored_ids = full_split(auth_notification_ids_string, ','); CHECK(stored_ids.size() % 2 == 0); bool is_changed = false; auto min_date = G()->unix_time() - AUTH_NOTIFICATION_ID_CACHE_TIME; for (size_t i = 0; i < stored_ids.size(); i += 2) { auto date = to_integer_safe(stored_ids[i + 1]).ok(); if (date < min_date || stored_ids[i].empty()) { is_changed = true; continue; } if (auth_notification_id_date_.size() == MAX_SAVED_AUTH_NOTIFICATION_IDS) { is_changed = true; break; } auth_notification_id_date_.emplace(std::move(stored_ids[i]), date); } if (is_changed) { save_auth_notification_ids(); } } } void MessagesManager::on_authorization_success() { CHECK(td_->auth_manager_->is_authorized()); authorization_date_ = td_->option_manager_->get_option_integer("authorization_date"); if (td_->auth_manager_->is_bot()) { return; } create_folders(); } void MessagesManager::ttl_db_loop() { if (ttl_db_has_query_) { return; } auto now = Time::now(); if (now < ttl_db_next_request_time_) { ttl_db_slot_.set_event(EventCreator::yield(actor_shared(this, YieldType::TtlDb))); auto wakeup_in = ttl_db_next_request_time_ - now; ttl_db_slot_.set_timeout_in(wakeup_in); LOG(INFO) << "Set ttl_db timeout in " << wakeup_in; return; } ttl_db_has_query_ = true; LOG(INFO) << "Send ttl_db query with limit " << ttl_db_next_limit_; G()->td_db()->get_message_db_async()->get_expiring_messages( G()->unix_time() - 1, ttl_db_next_limit_, PromiseCreator::lambda([actor_id = actor_id(this)](Result> result) { send_closure(actor_id, &MessagesManager::ttl_db_on_result, std::move(result), false); })); } void MessagesManager::ttl_db_on_result(Result> r_result, bool dummy) { if (G()->close_flag()) { return; } CHECK(r_result.is_ok()); auto result = r_result.move_as_ok(); ttl_db_has_query_ = false; int32 next_request_delay; if (result.size() == static_cast(ttl_db_next_limit_)) { CHECK(ttl_db_next_limit_ < (1 << 30)); ttl_db_next_limit_ *= 2; next_request_delay = 1; } else { ttl_db_next_limit_ = DEFAULT_LOADED_EXPIRED_MESSAGES; next_request_delay = Random::fast(3000, 4200); } ttl_db_next_request_time_ = Time::now() + next_request_delay; LOG(INFO) << "Receive " << result.size() << " expired messages from ttl_db with next request in " << next_request_delay << " seconds"; for (auto &dialog_message : result) { on_get_message_from_database(dialog_message, false, "ttl_db_on_result"); } ttl_db_loop(); } void MessagesManager::on_send_secret_message_error(int64 random_id, Status error, Promise promise) { promise.set_value(Unit()); // TODO: set after error is saved auto it = being_sent_messages_.find(random_id); if (it != being_sent_messages_.end()) { auto message_full_id = it->second; auto *m = get_message(message_full_id); if (m != nullptr) { auto file_id = get_message_content_upload_file_id(m->content.get()); if (file_id.is_valid()) { if (G()->close_flag() && G()->use_message_database()) { // do not send error, message will be re-sent after restart return; } auto bad_parts = FileManager::get_missing_file_parts(error); if (!bad_parts.empty()) { on_send_message_file_parts_missing(random_id, std::move(bad_parts)); return; } if (error.code() != 429 && error.code() < 500 && !G()->close_flag()) { td_->file_manager_->delete_partial_remote_location(file_id); } } } } on_send_message_fail(random_id, std::move(error)); } void MessagesManager::on_send_secret_message_success(int64 random_id, MessageId message_id, int32 date, unique_ptr file, Promise promise) { promise.set_value(Unit()); // TODO: set after message is saved FileId new_file_id; if (file != nullptr) { if (!DcId::is_valid(file->dc_id_)) { LOG(ERROR) << "Wrong dc_id = " << file->dc_id_ << " in file " << *file; } else { DialogId owner_dialog_id; auto it = being_sent_messages_.find(random_id); if (it != being_sent_messages_.end()) { owner_dialog_id = it->second.get_dialog_id(); } new_file_id = td_->file_manager_->register_remote( FullRemoteFileLocation(FileType::Encrypted, file->id_, file->access_hash_, DcId::internal(file->dc_id_), ""), FileLocationSource::FromServer, owner_dialog_id, 0, file->size_, to_string(static_cast(file->id_))); } } on_send_message_success(random_id, message_id, date, 0, new_file_id, "on_send_secret_message_success"); } void MessagesManager::delete_secret_messages(SecretChatId secret_chat_id, std::vector random_ids, Promise promise) { LOG(DEBUG) << "On delete messages in " << secret_chat_id << " with random_ids " << random_ids; CHECK(secret_chat_id.is_valid()); DialogId dialog_id(secret_chat_id); if (!have_dialog_force(dialog_id, "delete_secret_messages")) { LOG(ERROR) << "Ignore delete secret messages in unknown " << dialog_id; promise.set_value(Unit()); return; } auto pending_secret_message = make_unique(); pending_secret_message->success_promise = std::move(promise); pending_secret_message->type = PendingSecretMessage::Type::DeleteMessages; pending_secret_message->dialog_id = dialog_id; pending_secret_message->random_ids = std::move(random_ids); add_secret_message(std::move(pending_secret_message)); } void MessagesManager::finish_delete_secret_messages(DialogId dialog_id, std::vector random_ids, Promise promise) { LOG(INFO) << "Delete messages with random_ids " << random_ids << " in " << dialog_id; promise.set_value(Unit()); // TODO: set after event is saved Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); vector to_delete_message_ids; for (auto &random_id : random_ids) { auto message_id = get_message_id_by_random_id(d, random_id, "finish_delete_secret_messages"); if (!message_id.is_valid()) { LOG(INFO) << "Can't find message with random_id " << random_id; continue; } const Message *m = get_message(d, message_id); CHECK(m != nullptr); if (!is_service_message_content(m->content->get_type())) { to_delete_message_ids.push_back(message_id); } else { LOG(INFO) << "Skip deletion of service " << message_id; } } delete_dialog_messages(d, to_delete_message_ids, true, "finish_delete_secret_messages"); } void MessagesManager::delete_secret_chat_history(SecretChatId secret_chat_id, bool remove_from_dialog_list, MessageId last_message_id, Promise promise) { LOG(DEBUG) << "Delete history in " << secret_chat_id << " up to " << last_message_id; CHECK(secret_chat_id.is_valid()); CHECK(!last_message_id.is_scheduled()); DialogId dialog_id(secret_chat_id); if (!have_dialog_force(dialog_id, "delete_secret_chat_history")) { LOG(ERROR) << "Ignore delete history in unknown " << dialog_id; promise.set_value(Unit()); return; } auto pending_secret_message = make_unique(); pending_secret_message->success_promise = std::move(promise); pending_secret_message->type = PendingSecretMessage::Type::DeleteHistory; pending_secret_message->dialog_id = dialog_id; pending_secret_message->last_message_id = last_message_id; pending_secret_message->remove_from_dialog_list = remove_from_dialog_list; add_secret_message(std::move(pending_secret_message)); } void MessagesManager::finish_delete_secret_chat_history(DialogId dialog_id, bool remove_from_dialog_list, MessageId last_message_id, Promise promise) { LOG(DEBUG) << "Delete history in " << dialog_id << " up to " << last_message_id; Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); // TODO: probably last_message_id is not needed delete_all_dialog_messages(d, remove_from_dialog_list, true); promise.set_value(Unit()); // TODO: set after event is saved } void MessagesManager::read_secret_chat_outbox(SecretChatId secret_chat_id, int32 up_to_date, int32 read_date) { if (!secret_chat_id.is_valid()) { LOG(ERROR) << "Receive read secret chat outbox in the invalid " << secret_chat_id; return; } if (td_->auth_manager_->is_bot()) { return; } auto dialog_id = DialogId(secret_chat_id); Dialog *d = get_dialog_force(dialog_id, "read_secret_chat_outbox"); if (d == nullptr) { return; } if (read_date > 0) { auto user_id = td_->contacts_manager_->get_secret_chat_user_id(secret_chat_id); if (user_id.is_valid()) { td_->contacts_manager_->on_update_user_local_was_online(user_id, read_date); } } // TODO: protect with log event suffix_load_till_date( d, up_to_date, PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, up_to_date, read_date](Result result) { send_closure(actor_id, &MessagesManager::read_secret_chat_outbox_inner, dialog_id, up_to_date, read_date); })); } void MessagesManager::read_secret_chat_outbox_inner(DialogId dialog_id, int32 up_to_date, int32 read_date) { Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); auto end = d->ordered_messages.get_const_iterator(MessageId::max()); while (*end && (get_message(d, (*end)->get_message_id())->date > up_to_date || (*end)->get_message_id().is_yet_unsent())) { --end; } if (!*end) { LOG(INFO) << "Ignore read_secret_chat_outbox in " << dialog_id << " at " << up_to_date << ": no messages with such date are known"; return; } auto max_message_id = (*end)->get_message_id(); read_history_outbox(d, max_message_id, read_date); } void MessagesManager::open_secret_message(SecretChatId secret_chat_id, int64 random_id, Promise promise) { promise.set_value(Unit()); // TODO: set after event is saved DialogId dialog_id(secret_chat_id); Dialog *d = get_dialog_force(dialog_id, "open_secret_message"); if (d == nullptr) { LOG(ERROR) << "Ignore opening secret chat message in unknown " << dialog_id; return; } auto message_id = get_message_id_by_random_id(d, random_id, "open_secret_message"); if (!message_id.is_valid()) { return; } Message *m = get_message(d, message_id); CHECK(m != nullptr); if (m->message_id.is_yet_unsent() || m->is_failed_to_send || !m->is_outgoing) { LOG(ERROR) << "Peer has opened wrong " << message_id << " in " << dialog_id; return; } read_message_content(d, m, false, 0, "open_secret_message"); } void MessagesManager::on_update_secret_chat_state(SecretChatId secret_chat_id, SecretChatState state) { if (state == SecretChatState::Closed && !td_->auth_manager_->is_bot()) { DialogId dialog_id(secret_chat_id); Dialog *d = get_dialog_force(dialog_id, "on_update_secret_chat_state"); if (d != nullptr && d->notification_info != nullptr) { if (d->notification_info->new_secret_chat_notification_id_.is_valid()) { remove_new_secret_chat_notification(d, true); } if (d->notification_info->message_notification_group_.is_valid() && get_dialog_pending_notification_count(d, false) == 0 && !d->notification_info->message_notification_group_.get_last_notification_id().is_valid()) { d->notification_info->message_notification_group_.try_reuse(); on_dialog_updated(d->dialog_id, "on_update_secret_chat_state"); } CHECK(!d->notification_info->mention_notification_group_ .is_valid()); // there can't be unread mentions in secret chats } } } void MessagesManager::on_get_secret_message(SecretChatId secret_chat_id, UserId user_id, MessageId message_id, int32 date, unique_ptr file, tl_object_ptr message, Promise promise) { LOG(DEBUG) << "On get " << to_string(message); CHECK(message != nullptr); CHECK(secret_chat_id.is_valid()); CHECK(user_id.is_valid()); CHECK(message_id.is_valid()); CHECK(date > 0); if (message->random_id_ == 0) { LOG(ERROR) << "Ignore secret message with random_id == 0"; promise.set_error(Status::Error(400, "Invalid random_id")); return; } auto pending_secret_message = make_unique(); pending_secret_message->success_promise = std::move(promise); MessageInfo &message_info = pending_secret_message->message_info; message_info.dialog_id = DialogId(secret_chat_id); message_info.message_id = message_id; message_info.sender_user_id = user_id; message_info.date = date; message_info.random_id = message->random_id_; message_info.ttl = min(message->ttl_, 0x7FFFFFFE); message_info.has_unread_content = true; message_info.is_silent = message->silent_; message_info.media_album_id = message->grouped_id_; Dialog *d = get_dialog_force(message_info.dialog_id, "on_get_secret_message"); if (d == nullptr && have_dialog_info_force(message_info.dialog_id, "on_get_secret_message")) { force_create_dialog(message_info.dialog_id, "on_get_secret_message", true, true); d = get_dialog(message_info.dialog_id); } if (d == nullptr) { LOG(ERROR) << "Ignore secret message in unknown " << message_info.dialog_id; pending_secret_message->success_promise.set_error(Status::Error(500, "Chat not found")); return; } pending_secret_message_ids_[message_info.dialog_id][message_info.random_id] = message_id; pending_secret_message->load_data_multipromise.add_promise(Auto()); auto lock_promise = pending_secret_message->load_data_multipromise.get_promise(); if ((message->flags_ & secret_api::decryptedMessage::REPLY_TO_RANDOM_ID_MASK) != 0) { auto reply_to_message_id = get_message_id_by_random_id(d, message->reply_to_random_id_, "on_get_secret_message"); if (!reply_to_message_id.is_valid()) { auto dialog_it = pending_secret_message_ids_.find(message_info.dialog_id); if (dialog_it != pending_secret_message_ids_.end()) { auto message_it = dialog_it->second.find(message->reply_to_random_id_); if (message_it != dialog_it->second.end()) { reply_to_message_id = message_it->second; } } } message_info.reply_header.replied_message_info_ = RepliedMessageInfo::legacy(reply_to_message_id); } if (!clean_input_string(message->via_bot_name_)) { LOG(WARNING) << "Receive invalid bot username " << message->via_bot_name_; message->via_bot_name_.clear(); } if (!message->via_bot_name_.empty()) { auto request_promise = PromiseCreator::lambda( [actor_id = actor_id(this), via_bot_username = message->via_bot_name_, message_info_ptr = &message_info, promise = pending_secret_message->load_data_multipromise.get_promise()](Unit) mutable { send_closure(actor_id, &MessagesManager::on_resolve_secret_chat_message_via_bot_username, via_bot_username, message_info_ptr, std::move(promise)); }); search_public_dialog(message->via_bot_name_, false, std::move(request_promise)); } message_info.content = get_secret_message_content( td_, std::move(message->message_), std::move(file), std::move(message->media_), std::move(message->entities_), message_info.dialog_id, pending_secret_message->load_data_multipromise, td_->contacts_manager_->is_user_premium(user_id)); add_secret_message(std::move(pending_secret_message), std::move(lock_promise)); } void MessagesManager::on_resolve_secret_chat_message_via_bot_username(const string &via_bot_username, MessageInfo *message_info_ptr, Promise &&promise) { if (!G()->close_flag()) { auto dialog_id = resolve_dialog_username(via_bot_username); if (dialog_id.is_valid() && dialog_id.get_type() == DialogType::User) { auto user_id = dialog_id.get_user_id(); auto r_bot_data = td_->contacts_manager_->get_bot_data(user_id); if (r_bot_data.is_ok() && r_bot_data.ok().is_inline) { message_info_ptr->via_bot_user_id = user_id; } } } promise.set_value(Unit()); } void MessagesManager::on_secret_chat_screenshot_taken(SecretChatId secret_chat_id, UserId user_id, MessageId message_id, int32 date, int64 random_id, Promise promise) { LOG(DEBUG) << "On screenshot taken in " << secret_chat_id; CHECK(secret_chat_id.is_valid()); CHECK(user_id.is_valid()); CHECK(message_id.is_valid()); CHECK(date > 0); auto pending_secret_message = make_unique(); pending_secret_message->success_promise = std::move(promise); MessageInfo &message_info = pending_secret_message->message_info; message_info.dialog_id = DialogId(secret_chat_id); message_info.message_id = message_id; message_info.sender_user_id = user_id; message_info.date = date; message_info.random_id = random_id; message_info.content = create_screenshot_taken_message_content(); Dialog *d = get_dialog_force(message_info.dialog_id, "on_secret_chat_screenshot_taken"); if (d == nullptr && have_dialog_info_force(message_info.dialog_id, "on_secret_chat_screenshot_taken")) { force_create_dialog(message_info.dialog_id, "on_get_secret_message", true, true); d = get_dialog(message_info.dialog_id); } if (d == nullptr) { LOG(ERROR) << "Ignore secret message in unknown " << message_info.dialog_id; pending_secret_message->success_promise.set_error(Status::Error(500, "Chat not found")); return; } add_secret_message(std::move(pending_secret_message)); } void MessagesManager::on_secret_chat_ttl_changed(SecretChatId secret_chat_id, UserId user_id, MessageId message_id, int32 date, int32 ttl, int64 random_id, Promise promise) { LOG(DEBUG) << "On self-destruct timer set in " << secret_chat_id << " to " << ttl; CHECK(secret_chat_id.is_valid()); CHECK(user_id.is_valid()); CHECK(message_id.is_valid()); CHECK(date > 0); if (ttl < 0) { LOG(WARNING) << "Receive wrong self-destruct time = " << ttl; promise.set_value(Unit()); return; } auto pending_secret_message = make_unique(); pending_secret_message->success_promise = std::move(promise); MessageInfo &message_info = pending_secret_message->message_info; message_info.dialog_id = DialogId(secret_chat_id); message_info.message_id = message_id; message_info.sender_user_id = user_id; message_info.date = date; message_info.random_id = random_id; message_info.content = create_chat_set_ttl_message_content(ttl, UserId()); Dialog *d = get_dialog_force(message_info.dialog_id, "on_secret_chat_ttl_changed"); if (d == nullptr && have_dialog_info_force(message_info.dialog_id, "on_secret_chat_ttl_changed")) { force_create_dialog(message_info.dialog_id, "on_get_secret_message", true, true); d = get_dialog(message_info.dialog_id); } if (d == nullptr) { LOG(ERROR) << "Ignore secret message in unknown " << message_info.dialog_id; pending_secret_message->success_promise.set_error(Status::Error(500, "Chat not found")); return; } add_secret_message(std::move(pending_secret_message)); } void MessagesManager::add_secret_message(unique_ptr pending_secret_message, Promise lock_promise) { auto &multipromise = pending_secret_message->load_data_multipromise; multipromise.set_ignore_errors(true); int64 token = pending_secret_messages_.add(std::move(pending_secret_message)); multipromise.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), token](Result result) { if (result.is_ok()) { send_closure(actor_id, &MessagesManager::on_add_secret_message_ready, token); } })); if (!lock_promise) { lock_promise = multipromise.get_promise(); } lock_promise.set_value(Unit()); } void MessagesManager::on_add_secret_message_ready(int64 token) { if (G()->close_flag()) { return; } pending_secret_messages_.finish( token, [actor_id = actor_id(this)](unique_ptr pending_secret_message) { send_closure_later(actor_id, &MessagesManager::finish_add_secret_message, std::move(pending_secret_message)); }); } void MessagesManager::finish_add_secret_message(unique_ptr pending_secret_message) { if (G()->close_flag()) { return; } if (pending_secret_message->type == PendingSecretMessage::Type::DeleteMessages) { return finish_delete_secret_messages(pending_secret_message->dialog_id, std::move(pending_secret_message->random_ids), std::move(pending_secret_message->success_promise)); } if (pending_secret_message->type == PendingSecretMessage::Type::DeleteHistory) { return finish_delete_secret_chat_history( pending_secret_message->dialog_id, pending_secret_message->remove_from_dialog_list, pending_secret_message->last_message_id, std::move(pending_secret_message->success_promise)); } auto d = get_dialog(pending_secret_message->message_info.dialog_id); CHECK(d != nullptr); auto random_id = pending_secret_message->message_info.random_id; auto message_id = get_message_id_by_random_id(d, random_id, "finish_add_secret_message"); if (message_id.is_valid()) { if (message_id != pending_secret_message->message_info.message_id) { LOG(WARNING) << "Ignore duplicate " << pending_secret_message->message_info.message_id << " received earlier with " << message_id << " and random_id " << random_id; } } else { if (!td_->contacts_manager_->is_user_premium(pending_secret_message->message_info.sender_user_id)) { auto message_text = get_message_content_text_mutable(pending_secret_message->message_info.content.get()); if (message_text != nullptr) { remove_premium_custom_emoji_entities(td_, message_text->entities, true); } } on_get_message(std::move(pending_secret_message->message_info), true, false, "finish add secret message"); } auto dialog_it = pending_secret_message_ids_.find(d->dialog_id); if (dialog_it != pending_secret_message_ids_.end()) { auto message_it = dialog_it->second.find(random_id); if (message_it != dialog_it->second.end() && message_it->second == message_id) { dialog_it->second.erase(message_it); if (dialog_it->second.empty()) { pending_secret_message_ids_.erase(dialog_it); } } } pending_secret_message->success_promise.set_value(Unit()); // TODO: set after message is saved } MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message( tl_object_ptr message_ptr, bool is_scheduled, const char *source) const { LOG(DEBUG) << "Receive from " << source << " " << to_string(message_ptr); LOG_CHECK(message_ptr != nullptr) << source; MessageInfo message_info; message_info.message_id = MessageId::get_message_id(message_ptr, is_scheduled); switch (message_ptr->get_id()) { case telegram_api::messageEmpty::ID: message_info.message_id = MessageId(); break; case telegram_api::message::ID: { auto message = move_tl_object_as(message_ptr); message_info.dialog_id = DialogId(message->peer_id_); if (message->from_id_ != nullptr) { message_info.sender_dialog_id = DialogId(message->from_id_); } else { message_info.sender_dialog_id = message_info.dialog_id; } message_info.date = message->date_; message_info.forward_header = std::move(message->fwd_from_); bool can_have_thread = message_info.dialog_id.get_type() == DialogType::Channel && !is_broadcast_channel(message_info.dialog_id); message_info.reply_header = MessageReplyHeader(td_, std::move(message->reply_to_), message_info.dialog_id, message_info.message_id, message_info.date, can_have_thread); if (message->flags_ & telegram_api::message::VIA_BOT_ID_MASK) { message_info.via_bot_user_id = UserId(message->via_bot_id_); if (!message_info.via_bot_user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << message_info.via_bot_user_id << " from " << source; message_info.via_bot_user_id = UserId(); } } message_info.view_count = message->views_; message_info.forward_count = message->forwards_; message_info.reply_info = std::move(message->replies_); message_info.reactions = std::move(message->reactions_); message_info.edit_date = message->edit_date_; message_info.media_album_id = message->grouped_id_; message_info.ttl_period = message->ttl_period_; message_info.is_outgoing = message->out_; message_info.is_silent = message->silent_; message_info.is_channel_post = message->post_; message_info.is_legacy = message->legacy_; message_info.hide_edit_date = message->edit_hide_; message_info.is_from_scheduled = message->from_scheduled_; message_info.is_pinned = message->pinned_; message_info.noforwards = message->noforwards_; message_info.has_mention = message->mentioned_; message_info.has_unread_content = message->media_unread_; message_info.invert_media = message->invert_media_; bool is_content_read = true; if (!td_->auth_manager_->is_bot()) { if (is_scheduled) { is_content_read = false; } else if (is_message_auto_read(message_info.dialog_id, message_info.is_outgoing)) { is_content_read = true; } else { is_content_read = !message_info.has_unread_content; } } auto new_source = PSTRING() << MessageFullId(message_info.dialog_id, message_info.message_id) << " sent by " << message_info.sender_dialog_id << " from " << source; message_info.content = get_message_content( td_, get_message_text(td_->contacts_manager_.get(), std::move(message->message_), std::move(message->entities_), true, td_->auth_manager_->is_bot(), message_info.forward_header ? message_info.forward_header->date_ : message_info.date, message_info.media_album_id != 0, new_source.c_str()), std::move(message->media_), message_info.dialog_id, message_info.date, is_content_read, message_info.via_bot_user_id, &message_info.ttl, &message_info.disable_web_page_preview, new_source.c_str()); message_info.reply_markup = std::move(message->reply_markup_); message_info.restriction_reasons = get_restriction_reasons(std::move(message->restriction_reason_)); message_info.author_signature = std::move(message->post_author_); break; } case telegram_api::messageService::ID: { auto message = move_tl_object_as(message_ptr); message_info.dialog_id = DialogId(message->peer_id_); if (message->from_id_ != nullptr) { message_info.sender_dialog_id = DialogId(message->from_id_); } else { message_info.sender_dialog_id = message_info.dialog_id; } message_info.date = message->date_; message_info.ttl_period = message->ttl_period_; message_info.is_outgoing = message->out_; message_info.is_silent = message->silent_; message_info.is_channel_post = message->post_; message_info.is_legacy = message->legacy_; message_info.has_mention = message->mentioned_; message_info.has_unread_content = message->media_unread_; bool can_have_thread = message_info.dialog_id.get_type() == DialogType::Channel && !is_broadcast_channel(message_info.dialog_id); message_info.reply_header = MessageReplyHeader(td_, std::move(message->reply_to_), message_info.dialog_id, message_info.message_id, message_info.date, can_have_thread); message_info.content = get_action_message_content(td_, std::move(message->action_), message_info.dialog_id, message_info.date, message_info.reply_header.replied_message_info_); message_info.reply_header.replied_message_info_ = RepliedMessageInfo(); message_info.reply_header.story_full_id_ = StoryFullId(); break; } default: UNREACHABLE(); break; } if (message_info.sender_dialog_id.is_valid() && message_info.sender_dialog_id.get_type() == DialogType::User) { message_info.sender_user_id = message_info.sender_dialog_id.get_user_id(); message_info.sender_dialog_id = DialogId(); } return message_info; } std::pair> MessagesManager::create_message(MessageInfo &&message_info, bool is_channel_message) { DialogId dialog_id = message_info.dialog_id; MessageId message_id = message_info.message_id; if ((!message_id.is_valid() && !message_id.is_valid_scheduled()) || !dialog_id.is_valid()) { if (message_id != MessageId() || dialog_id != DialogId()) { LOG(ERROR) << "Receive " << message_id << " in " << dialog_id; } return {DialogId(), nullptr}; } if (message_id.is_yet_unsent() || message_id.is_local()) { LOG(ERROR) << "Receive " << message_id; return {DialogId(), nullptr}; } CHECK(message_info.content != nullptr); auto dialog_type = dialog_id.get_type(); UserId sender_user_id = message_info.sender_user_id; DialogId sender_dialog_id = message_info.sender_dialog_id; if (!sender_user_id.is_valid()) { if (sender_user_id != UserId()) { LOG(ERROR) << "Receive invalid " << sender_user_id; sender_user_id = UserId(); } if (!is_broadcast_channel(dialog_id) && td_->auth_manager_->is_bot()) { if (dialog_id == sender_dialog_id) { td_->contacts_manager_->add_anonymous_bot_user(); } else { td_->contacts_manager_->add_service_notifications_user(); td_->contacts_manager_->add_channel_bot_user(); } } } if (sender_dialog_id.is_valid()) { if (dialog_type == DialogType::User || dialog_type == DialogType::SecretChat) { LOG(ERROR) << "Receive " << message_id << " sent by " << sender_dialog_id << " in " << dialog_id; return {DialogId(), nullptr}; } } else if (sender_dialog_id != DialogId()) { LOG(ERROR) << "Receive invalid " << sender_dialog_id; sender_dialog_id = DialogId(); } if (message_id.is_scheduled()) { is_channel_message = (dialog_type == DialogType::Channel); } LOG_IF(ERROR, is_channel_message != (dialog_type == DialogType::Channel)) << "Receive wrong is_channel_message for " << message_id << " in " << dialog_id; bool is_channel_post = message_info.is_channel_post; if (is_channel_post && !is_broadcast_channel(dialog_id)) { LOG(ERROR) << "Receive is_channel_post for " << message_id << " in " << dialog_id; is_channel_post = false; } UserId my_id = td_->contacts_manager_->get_my_id(); DialogId my_dialog_id = DialogId(my_id); if (dialog_id == my_dialog_id && (sender_user_id != my_id || sender_dialog_id.is_valid())) { LOG(ERROR) << "Receive " << sender_user_id << "/" << sender_dialog_id << " as a sender of " << message_id << " instead of self"; sender_user_id = my_id; sender_dialog_id = DialogId(); } bool is_outgoing = message_info.is_outgoing; bool supposed_to_be_outgoing = sender_user_id == my_id && !(dialog_id == my_dialog_id && !message_id.is_scheduled()); if (sender_user_id.is_valid() && supposed_to_be_outgoing != is_outgoing) { LOG(ERROR) << "Receive wrong out flag for " << message_id << " in " << dialog_id << ": me is " << my_id << ", but message is from " << sender_user_id; is_outgoing = supposed_to_be_outgoing; /* // it is useless to call getChannelDifference, because the channel PTS will be increased already if (dialog_type == DialogType::Channel && !running_get_difference_ && !running_get_channel_difference(dialog_id) && !message_id.is_scheduled() && get_channel_difference_to_log_event_id_.count(dialog_id) == 0) { // it is safer to completely ignore the message and re-get it through getChannelDifference Dialog *d = get_dialog(dialog_id); if (d != nullptr) { schedule_get_channel_difference(dialog_id, 0, message_id, 0.001, "create_message"); return {DialogId(), nullptr}; } } */ } int32 date = message_info.date; if (date <= 0) { LOG(ERROR) << "Wrong date = " << date << " received in " << message_id << " in " << dialog_id; date = 1; } MessageId top_thread_message_id = message_info.reply_header.top_thread_message_id_; bool is_topic_message = message_info.reply_header.is_topic_message_; auto reply_to_story_full_id = message_info.reply_header.story_full_id_; if (reply_to_story_full_id != StoryFullId() && (dialog_type != DialogType::User || (reply_to_story_full_id.get_dialog_id() != my_dialog_id && reply_to_story_full_id.get_dialog_id() != dialog_id))) { LOG(ERROR) << "Receive reply to " << reply_to_story_full_id << " in " << dialog_id; reply_to_story_full_id = {}; } UserId via_bot_user_id = message_info.via_bot_user_id; if (!via_bot_user_id.is_valid()) { via_bot_user_id = UserId(); } int32 edit_date = message_info.edit_date; if (edit_date < 0) { LOG(ERROR) << "Wrong edit_date = " << edit_date << " received in " << message_id << " in " << dialog_id; edit_date = 0; } auto content_type = message_info.content->get_type(); if (content_type == MessageContentType::Sticker && get_message_content_sticker_type(td_, message_info.content.get()) == StickerType::CustomEmoji) { LOG(INFO) << "Replace emoji sticker with an empty message"; message_info.content = create_text_message_content("Invalid sticker", {}, WebPageId(), false, false, false, string()); message_info.invert_media = false; content_type = message_info.content->get_type(); } bool hide_edit_date = message_info.hide_edit_date; if (hide_edit_date && td_->auth_manager_->is_bot()) { hide_edit_date = false; } if (hide_edit_date && content_type == MessageContentType::LiveLocation) { hide_edit_date = false; } int32 ttl_period = message_info.ttl_period; if (ttl_period < 0 || (message_id.is_scheduled() && ttl_period != 0)) { LOG(ERROR) << "Wrong auto-delete time " << ttl_period << " received in " << message_id << " in " << dialog_id; ttl_period = 0; } int32 ttl = message_info.ttl; if (dialog_type == DialogType::SecretChat && ttl == 0x7FFFFFFF) { ttl = 0x7FFFFFFE; } bool is_content_secret = is_secret_message_content(ttl, content_type); // must be calculated before TTL is adjusted if (ttl < 0 || (message_id.is_scheduled() && ttl != 0)) { LOG(ERROR) << "Wrong self-destruct time " << ttl << " received in " << message_id << " in " << dialog_id; ttl = 0; } else if (ttl > 0) { ttl = max(ttl, get_message_content_duration(message_info.content.get(), td_) + 1); } if (message_id.is_scheduled()) { if (message_info.reply_info != nullptr) { LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with " << to_string(message_info.reply_info); message_info.reply_info = nullptr; } if (message_info.reactions != nullptr) { LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with " << to_string(message_info.reactions); message_info.reactions = nullptr; } } int32 view_count = message_info.view_count; if (view_count < 0) { LOG(ERROR) << "Wrong view_count = " << view_count << " received in " << message_id << " in " << dialog_id; view_count = 0; } int32 forward_count = message_info.forward_count; if (forward_count < 0) { LOG(ERROR) << "Wrong forward_count = " << forward_count << " received in " << message_id << " in " << dialog_id; forward_count = 0; } MessageReplyInfo reply_info(td_, std::move(message_info.reply_info), td_->auth_manager_->is_bot()); if (!top_thread_message_id.is_valid() && is_thread_message(dialog_id, message_id, reply_info, content_type)) { top_thread_message_id = message_id; is_topic_message = (content_type == MessageContentType::TopicCreate); } if (top_thread_message_id.is_valid() && dialog_type != DialogType::Channel) { // just in case top_thread_message_id = MessageId(); } if (!top_thread_message_id.is_valid()) { // just in case is_topic_message = false; } auto reactions = MessageReactions::get_message_reactions(td_, std::move(message_info.reactions), td_->auth_manager_->is_bot()); if (reactions != nullptr) { reactions->sort_reactions(active_reaction_pos_); reactions->fix_chosen_reaction(); reactions->fix_my_recent_chooser_dialog_id(get_my_dialog_id()); } bool has_forward_info = message_info.forward_header != nullptr; if (sender_dialog_id.is_valid() && sender_dialog_id != dialog_id && have_dialog_info_force(sender_dialog_id, "create_message")) { CHECK(sender_dialog_id.get_type() != DialogType::User); force_create_dialog(sender_dialog_id, "create_message", true); } bool noforwards = message_info.noforwards; bool is_expired = content_type == MessageContentType::ExpiredPhoto || content_type == MessageContentType::ExpiredVideo; if (is_expired) { CHECK(ttl == 0); // self-destruct time is ignored/set to 0 if the message has already been expired message_info.reply_header.replied_message_info_ = {}; reply_to_story_full_id = StoryFullId(); noforwards = false; is_content_secret = false; message_info.invert_media = false; } bool is_pinned = message_info.is_pinned; if (is_pinned && message_id.is_scheduled()) { LOG(ERROR) << "Receive pinned " << message_id << " in " << dialog_id; is_pinned = false; } LOG(INFO) << "Receive " << message_id << " in " << dialog_id << " from " << sender_user_id << "/" << sender_dialog_id; auto message = make_unique(); message->message_id = message_id; message->sender_user_id = sender_user_id; message->sender_dialog_id = sender_dialog_id; message->date = date; message->ttl_period = ttl_period; message->ttl = ttl; message->disable_web_page_preview = message_info.disable_web_page_preview; message->edit_date = edit_date; message->random_id = message_info.random_id; message->forward_info = get_message_forward_info(std::move(message_info.forward_header)); message->replied_message_info = std::move(message_info.reply_header.replied_message_info_); message->top_thread_message_id = top_thread_message_id; message->is_topic_message = is_topic_message; message->via_bot_user_id = via_bot_user_id; message->reply_to_story_full_id = reply_to_story_full_id; message->restriction_reasons = std::move(message_info.restriction_reasons); message->author_signature = std::move(message_info.author_signature); message->is_outgoing = is_outgoing; message->is_channel_post = is_channel_post; message->contains_mention = !is_outgoing && dialog_type != DialogType::User && !is_expired && (message_info.has_mention || content_type == MessageContentType::PinMessage) && !td_->auth_manager_->is_bot(); message->contains_unread_mention = !message_id.is_scheduled() && message_id.is_server() && message->contains_mention && message_info.has_unread_content && (dialog_type == DialogType::Chat || (dialog_type == DialogType::Channel && !is_broadcast_channel(dialog_id))); message->disable_notification = message_info.is_silent; message->is_content_secret = is_content_secret; message->hide_edit_date = hide_edit_date; message->is_from_scheduled = message_info.is_from_scheduled; message->is_pinned = is_pinned; message->noforwards = noforwards; message->interaction_info_update_date = G()->unix_time(); message->view_count = view_count; message->forward_count = forward_count; message->reply_info = std::move(reply_info); message->reactions = std::move(reactions); message->legacy_layer = (message_info.is_legacy ? MTPROTO_LAYER : 0); message->invert_media = message_info.invert_media; message->content = std::move(message_info.content); message->reply_markup = get_reply_markup(std::move(message_info.reply_markup), td_->auth_manager_->is_bot(), false, message->contains_mention || dialog_type == DialogType::User); if (message->reply_markup != nullptr && is_expired) { // just in case if (message->reply_markup->type != ReplyMarkup::Type::InlineKeyboard) { message->had_reply_markup = true; } message->reply_markup = nullptr; } if (message_info.media_album_id != 0) { if (!is_allowed_media_group_content(content_type)) { if (content_type != MessageContentType::Unsupported) { LOG(ERROR) << "Receive media group identifier " << message_info.media_album_id << " in " << message_id << " from " << dialog_id << " with content " << oneline(to_string(get_message_message_content_object(dialog_id, message.get()))); } } else { message->media_album_id = message_info.media_album_id; } } if (message->forward_info == nullptr && has_forward_info) { message->had_forward_info = true; } return {dialog_id, std::move(message)}; } MessageId MessagesManager::find_old_message_id(DialogId dialog_id, MessageId message_id) const { if (message_id.is_scheduled()) { CHECK(message_id.is_scheduled_server()); auto dialog_it = update_scheduled_message_ids_.find(dialog_id); if (dialog_it != update_scheduled_message_ids_.end()) { auto it = dialog_it->second.find(message_id.get_scheduled_server_message_id()); if (it != dialog_it->second.end()) { return it->second; } } } else { CHECK(message_id.is_server()); auto it = update_message_ids_.find(MessageFullId(dialog_id, message_id)); if (it != update_message_ids_.end()) { return it->second; } } return MessageId(); } void MessagesManager::delete_update_message_id(DialogId dialog_id, MessageId message_id) { if (message_id.is_scheduled()) { CHECK(message_id.is_scheduled_server()); auto dialog_it = update_scheduled_message_ids_.find(dialog_id); CHECK(dialog_it != update_scheduled_message_ids_.end()); auto erased_count = dialog_it->second.erase(message_id.get_scheduled_server_message_id()); CHECK(erased_count > 0); if (dialog_it->second.empty()) { update_scheduled_message_ids_.erase(dialog_it); } } else { CHECK(message_id.is_server()); auto erased_count = update_message_ids_.erase(MessageFullId(dialog_id, message_id)); CHECK(erased_count > 0); } } MessageFullId MessagesManager::on_get_message(tl_object_ptr message_ptr, bool from_update, bool is_channel_message, bool is_scheduled, const char *source) { return on_get_message(parse_telegram_api_message(std::move(message_ptr), is_scheduled, source), from_update, is_channel_message, source); } MessageFullId MessagesManager::on_get_message(MessageInfo &&message_info, const bool from_update, const bool is_channel_message, const char *source) { DialogId dialog_id; unique_ptr new_message; std::tie(dialog_id, new_message) = create_message(std::move(message_info), is_channel_message); if (new_message == nullptr) { return MessageFullId(); } MessageId message_id = new_message->message_id; bool need_update = from_update; bool need_update_dialog_pos = false; Dialog *d = get_dialog_force(dialog_id, source); MessageId old_message_id = find_old_message_id(dialog_id, message_id); bool is_sent_message = false; if (old_message_id.is_valid() || old_message_id.is_valid_scheduled()) { LOG(INFO) << "Found temporary " << old_message_id << " for " << MessageFullId{dialog_id, message_id}; CHECK(d != nullptr); if (!from_update && !message_id.is_scheduled()) { if (message_id <= d->last_new_message_id) { if (get_message_force(d, message_id, "receive missed unsent message not from update") != nullptr) { LOG(ERROR) << "New " << old_message_id << "/" << message_id << " in " << dialog_id << " from " << source << " has identifier less than last_new_message_id = " << d->last_new_message_id; return MessageFullId(); } // if there is no message yet, then it is likely was missed because of a server bug and is being repaired via // get_message_from_server from after_get_difference // TODO move to INFO LOG(ERROR) << "Receive " << old_message_id << "/" << message_id << " in " << dialog_id << " from " << source << " with identifier less than last_new_message_id = " << d->last_new_message_id << " and trying to add it anyway"; } else { LOG(INFO) << "Ignore " << old_message_id << "/" << message_id << " received not through update from " << source << ": " << oneline(to_string(get_message_object(dialog_id, new_message.get(), "on_get_message"))); if (dialog_id.get_type() == DialogType::Channel && have_input_peer(dialog_id, AccessRights::Read)) { schedule_get_channel_difference(dialog_id, 0, message_id, 0.001, "on_get_message"); } return MessageFullId(); } } delete_update_message_id(dialog_id, message_id); if (!new_message->is_outgoing && dialog_id != get_my_dialog_id()) { // sent message is not from me LOG(ERROR) << "Sent in " << dialog_id << " " << message_id << " is sent by " << new_message->sender_user_id << "/" << new_message->sender_dialog_id; return MessageFullId(); } // must be called before delete_message update_reply_to_message_id(dialog_id, old_message_id, message_id, true, "on_get_message"); being_readded_message_id_ = {dialog_id, old_message_id}; unique_ptr old_message = delete_message(d, old_message_id, false, &need_update_dialog_pos, "add sent message"); if (old_message == nullptr) { delete_sent_message_on_server(dialog_id, message_id, old_message_id); being_readded_message_id_ = MessageFullId(); return MessageFullId(); } old_message_id = old_message->message_id; need_update = false; if (old_message_id.is_valid() && message_id.is_valid() && message_id < old_message_id && !d->had_yet_unsent_message_id_overflow) { LOG(ERROR) << "Sent " << old_message_id << " to " << dialog_id << " as " << message_id; } new_message->message_id = old_message_id; update_message(d, old_message.get(), std::move(new_message), false); new_message = std::move(old_message); auto reply_message_full_id = new_message->replied_message_info.get_reply_message_full_id(dialog_id, false); auto reply_message_id = reply_message_full_id.get_message_id(); if (reply_message_id.is_valid() && reply_message_id.is_yet_unsent()) { set_message_reply(d, new_message.get(), MessageInputReplyTo(), false); } new_message->message_id = message_id; send_update_message_send_succeeded(d, old_message_id, new_message.get(), &need_update_dialog_pos); if (!message_id.is_scheduled()) { is_sent_message = true; } } if (d == nullptr) { d = add_dialog_for_new_message(dialog_id, from_update, &need_update_dialog_pos, source); } Dependencies dependencies; add_message_dependencies(dependencies, new_message.get()); for (auto dependent_dialog_id : dependencies.get_dialog_ids()) { force_create_dialog(dependent_dialog_id, source, true); } const Message *m = add_message_to_dialog(d, std::move(new_message), false, from_update, &need_update, &need_update_dialog_pos, source); being_readded_message_id_ = MessageFullId(); if (m == nullptr) { if (need_update_dialog_pos) { send_update_chat_last_message(d, "on_get_message"); } if (old_message_id.is_valid() || old_message_id.is_valid_scheduled()) { if (!old_message_id.is_valid() || !message_id.is_valid() || old_message_id <= message_id) { LOG(ERROR) << "Failed to add just sent " << old_message_id << " to " << dialog_id << " as " << message_id << " from " << source << ": " << debug_add_message_to_dialog_fail_reason_; } send_update_delete_messages(dialog_id, {message_id.get()}, true); } return MessageFullId(); } auto pcc_it = pending_created_dialogs_.find(dialog_id); if (from_update && pcc_it != pending_created_dialogs_.end()) { pcc_it->second.set_value(Unit()); pending_created_dialogs_.erase(pcc_it); } if (need_update) { send_update_new_message(d, m); } if (is_sent_message) { try_add_active_live_location(dialog_id, m); // add_message_to_dialog will not update counts, because need_update == false update_message_count_by_index(d, +1, m); } if (is_sent_message || (need_update && !message_id.is_scheduled())) { update_reply_count_by_message(d, +1, m); update_forward_count(dialog_id, m); } if (dialog_id.get_type() == DialogType::Channel && !have_input_peer(dialog_id, AccessRights::Read)) { auto message = delete_message(d, message_id, false, &need_update_dialog_pos, "get a message in inaccessible chat"); CHECK(message.get() == m); send_update_delete_messages(dialog_id, {m->message_id.get()}, false); // don't need to update chat position return MessageFullId(); } if (m->message_id.is_scheduled()) { send_update_chat_has_scheduled_messages(d, false); } if (need_update_dialog_pos) { send_update_chat_last_message(d, "on_get_message"); } // set dialog reply markup only after updateNewMessage and updateChatLastMessage are sent if (need_update && m->reply_markup != nullptr && !m->message_id.is_scheduled() && m->reply_markup->type != ReplyMarkup::Type::InlineKeyboard && m->reply_markup->is_personal && !td_->auth_manager_->is_bot()) { set_dialog_reply_markup(d, message_id); } return MessageFullId(dialog_id, message_id); } void MessagesManager::set_dialog_last_message_id(Dialog *d, MessageId last_message_id, const char *source, const Message *m) { CHECK(!last_message_id.is_scheduled()); CHECK(!td_->auth_manager_->is_bot()); LOG(INFO) << "Set " << d->dialog_id << " last message to " << last_message_id << " from " << source; d->last_message_id = last_message_id; if (m != nullptr) { d->last_media_album_id = m->media_album_id; } else if (!last_message_id.is_valid()) { d->last_media_album_id = 0; } else { m = get_message(d, last_message_id); if (m == nullptr) { LOG(ERROR) << "Failed to find last " << last_message_id << " in " << d->dialog_id; d->last_media_album_id = 0; } else { d->last_media_album_id = m->media_album_id; } } if (!last_message_id.is_valid()) { auto it = dialog_suffix_load_queries_.find(d->dialog_id); if (it != dialog_suffix_load_queries_.end()) { it->second->suffix_load_first_message_id_ = MessageId(); it->second->suffix_load_done_ = false; } } if (last_message_id.is_valid() && d->delete_last_message_date != 0) { d->delete_last_message_date = 0; d->deleted_last_message_id = MessageId(); d->is_last_message_deleted_locally = false; on_dialog_updated(d->dialog_id, "update_delete_last_message_date"); } d->pending_order = DEFAULT_ORDER; } void MessagesManager::set_dialog_first_database_message_id(Dialog *d, MessageId first_database_message_id, const char *source) { CHECK(!first_database_message_id.is_scheduled()); CHECK(!td_->auth_manager_->is_bot()); if (first_database_message_id == d->first_database_message_id) { return; } LOG(INFO) << "Set " << d->dialog_id << " first database message to " << first_database_message_id << " from " << source; d->first_database_message_id = first_database_message_id; on_dialog_updated(d->dialog_id, "set_dialog_first_database_message_id"); } void MessagesManager::set_dialog_last_database_message_id(Dialog *d, MessageId last_database_message_id, const char *source, bool is_loaded_from_database) { CHECK(!last_database_message_id.is_scheduled()); CHECK(!td_->auth_manager_->is_bot()); if (last_database_message_id == d->last_database_message_id) { return; } LOG(INFO) << "Set " << d->dialog_id << " last database message to " << last_database_message_id << " from " << source; d->debug_set_dialog_last_database_message_id = source; d->last_database_message_id = last_database_message_id; if (!is_loaded_from_database) { on_dialog_updated(d->dialog_id, "set_dialog_last_database_message_id"); } } void MessagesManager::remove_dialog_newer_messages(Dialog *d, MessageId from_message_id, const char *source) { LOG(INFO) << "Remove messages in " << d->dialog_id << " newer than " << from_message_id << " from " << source; CHECK(!d->last_new_message_id.is_valid()); CHECK(!td_->auth_manager_->is_bot()); // the function is called to handle channel gaps, and hence must always delete all database messages, // even first_database_message_id and last_database_message_id are empty delete_all_dialog_messages_from_database(d, MessageId::max(), "remove_dialog_newer_messages"); set_dialog_first_database_message_id(d, MessageId(), "remove_dialog_newer_messages"); set_dialog_last_database_message_id(d, MessageId(), source); if (d->dialog_id.get_type() != DialogType::SecretChat && !d->is_empty) { d->have_full_history = false; d->have_full_history_source = 0; } invalidate_message_indexes(d); auto to_delete_message_ids = d->ordered_messages.find_newer_messages(from_message_id); td::remove_if(to_delete_message_ids, [](MessageId message_id) { return message_id.is_yet_unsent(); }); if (!to_delete_message_ids.empty()) { LOG(INFO) << "Delete " << format::as_array(to_delete_message_ids) << " newer than " << from_message_id << " in " << d->dialog_id << " from " << source; vector deleted_message_ids; bool need_update_dialog_pos = false; for (auto message_id : to_delete_message_ids) { auto message = delete_message(d, message_id, false, &need_update_dialog_pos, "remove_dialog_newer_messages"); if (message != nullptr) { deleted_message_ids.push_back(message->message_id.get()); } } if (need_update_dialog_pos) { send_update_chat_last_message(d, "remove_dialog_newer_messages"); } send_update_delete_messages(d->dialog_id, std::move(deleted_message_ids), false); } } void MessagesManager::set_dialog_last_new_message_id(Dialog *d, MessageId last_new_message_id, const char *source) { CHECK(!last_new_message_id.is_scheduled()); CHECK(!td_->auth_manager_->is_bot()); LOG_CHECK(last_new_message_id > d->last_new_message_id) << last_new_message_id << " " << d->last_new_message_id << " " << source; CHECK(d->dialog_id.get_type() == DialogType::SecretChat || last_new_message_id.is_server()); if (!d->last_new_message_id.is_valid()) { remove_dialog_newer_messages(d, last_new_message_id, source); auto last_new_message = get_message(d, last_new_message_id); if (last_new_message != nullptr) { add_message_to_database(d, last_new_message, source); set_dialog_first_database_message_id(d, last_new_message_id, source); set_dialog_last_database_message_id(d, last_new_message_id, source); try_restore_dialog_reply_markup(d, last_new_message); } } LOG(INFO) << "Set " << d->dialog_id << " last new message to " << last_new_message_id << " from " << source; d->last_new_message_id = last_new_message_id; on_dialog_updated(d->dialog_id, source); } void MessagesManager::set_dialog_last_clear_history_date(Dialog *d, int32 date, MessageId last_clear_history_message_id, const char *source, bool is_loaded_from_database) { CHECK(!last_clear_history_message_id.is_scheduled()); if (d->last_clear_history_message_id == last_clear_history_message_id && d->last_clear_history_date == date) { return; } LOG(INFO) << "Set " << d->dialog_id << " last clear history date to " << date << " of " << last_clear_history_message_id << " from " << source; if (d->last_clear_history_message_id.is_valid()) { switch (d->dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: last_clear_history_message_id_to_dialog_id_.erase(d->last_clear_history_message_id); break; case DialogType::Channel: case DialogType::SecretChat: // nothing to do break; case DialogType::None: default: UNREACHABLE(); } } d->last_clear_history_date = date; d->last_clear_history_message_id = last_clear_history_message_id; if (!is_loaded_from_database) { on_dialog_updated(d->dialog_id, "set_dialog_last_clear_history_date"); } if (d->last_clear_history_message_id.is_valid()) { switch (d->dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: last_clear_history_message_id_to_dialog_id_[d->last_clear_history_message_id] = d->dialog_id; break; case DialogType::Channel: case DialogType::SecretChat: // nothing to do break; case DialogType::None: default: UNREACHABLE(); } } } void MessagesManager::set_dialog_unread_mention_count(Dialog *d, int32 unread_mention_count) { CHECK(d->unread_mention_count != unread_mention_count); CHECK(unread_mention_count >= 0); d->unread_mention_count = unread_mention_count; d->message_count_by_index[message_search_filter_index(MessageSearchFilter::UnreadMention)] = unread_mention_count; } void MessagesManager::set_dialog_unread_reaction_count(Dialog *d, int32 unread_reaction_count) { CHECK(d->unread_reaction_count != unread_reaction_count); CHECK(unread_reaction_count >= 0); d->unread_reaction_count = unread_reaction_count; d->message_count_by_index[message_search_filter_index(MessageSearchFilter::UnreadReaction)] = unread_reaction_count; } void MessagesManager::set_dialog_is_empty(Dialog *d, const char *source) { LOG(INFO) << "Set " << d->dialog_id << " is_empty to true from " << source; CHECK(d->have_full_history); if (!d->is_empty && d->order != DEFAULT_ORDER) { reload_dialog_info_full(d->dialog_id, "set_dialog_is_empty"); } d->is_empty = true; if (d->server_unread_count + d->local_unread_count > 0) { MessageId max_message_id = d->last_database_message_id.is_valid() ? d->last_database_message_id : d->last_new_message_id; if (max_message_id.is_valid()) { read_history_inbox(d, max_message_id, -1, "set_dialog_is_empty"); } if (d->server_unread_count != 0 || d->local_unread_count != 0) { set_dialog_last_read_inbox_message_id(d, MessageId::min(), 0, 0, true, "set_dialog_is_empty"); } } if (d->unread_mention_count > 0) { set_dialog_unread_mention_count(d, 0); send_update_chat_unread_mention_count(d); } if (d->unread_reaction_count > 0) { set_dialog_unread_reaction_count(d, 0); send_update_chat_unread_reaction_count(d, "set_dialog_is_empty"); } if (d->reply_markup_message_id != MessageId()) { set_dialog_reply_markup(d, MessageId()); } std::fill(d->message_count_by_index.begin(), d->message_count_by_index.end(), 0); if (d->notification_info != nullptr) { d->notification_info->notification_id_to_message_id_.clear(); } if (d->delete_last_message_date != 0) { if (d->is_last_message_deleted_locally && d->last_clear_history_date == 0) { set_dialog_last_clear_history_date(d, d->delete_last_message_date, d->deleted_last_message_id, "set_dialog_is_empty"); } d->delete_last_message_date = 0; d->deleted_last_message_id = MessageId(); d->is_last_message_deleted_locally = false; on_dialog_updated(d->dialog_id, "set_dialog_is_empty"); } d->pending_order = DEFAULT_ORDER; if (d->last_database_message_id.is_valid()) { set_dialog_first_database_message_id(d, MessageId(), "set_dialog_is_empty"); set_dialog_last_database_message_id(d, MessageId(), "set_dialog_is_empty"); } update_dialog_pos(d, source); } bool MessagesManager::is_dialog_pinned(DialogListId dialog_list_id, DialogId dialog_id) const { if (get_dialog_pinned_order(dialog_list_id, dialog_id) != DEFAULT_ORDER) { return true; } if (dialog_list_id.is_filter()) { return td_->dialog_filter_manager_->is_dialog_pinned(dialog_list_id.get_filter_id(), dialog_id); } return false; } int64 MessagesManager::get_dialog_pinned_order(DialogListId dialog_list_id, DialogId dialog_id) const { return get_dialog_pinned_order(get_dialog_list(dialog_list_id), dialog_id); } int64 MessagesManager::get_dialog_pinned_order(const DialogList *list, DialogId dialog_id) { if (list != nullptr && !list->pinned_dialog_id_orders_.empty()) { auto it = list->pinned_dialog_id_orders_.find(dialog_id); if (it != list->pinned_dialog_id_orders_.end()) { return it->second; } } return DEFAULT_ORDER; } bool MessagesManager::set_dialog_is_pinned(DialogId dialog_id, bool is_pinned) { if (td_->auth_manager_->is_bot()) { return false; } Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); return set_dialog_is_pinned(DialogListId(d->folder_id), d, is_pinned); } // only removes the Dialog from the dialog list, but changes nothing in the corresponding DialogFilter bool MessagesManager::set_dialog_is_pinned(DialogListId dialog_list_id, Dialog *d, bool is_pinned, bool need_update_dialog_lists) { if (td_->auth_manager_->is_bot()) { return false; } CHECK(d != nullptr); if (d->order == DEFAULT_ORDER && is_pinned) { // the chat can't be pinned return false; } auto positions = get_dialog_positions(d); auto *list = get_dialog_list(dialog_list_id); if (list == nullptr) { return false; } if (!list->are_pinned_dialogs_inited_) { return false; } auto dialog_id = d->dialog_id; auto is_changed_dialog = [dialog_id](const DialogDate &dialog_date) { return dialog_date.get_dialog_id() == dialog_id; }; if (is_pinned) { if (!list->pinned_dialogs_.empty() && is_changed_dialog(list->pinned_dialogs_[0])) { return false; } auto order = get_next_pinned_dialog_order(); DialogDate dialog_date(order, dialog_id); add_to_top_if(list->pinned_dialogs_, list->pinned_dialogs_.size() + 1, dialog_date, is_changed_dialog); auto it = list->pinned_dialog_id_orders_.find(dialog_id); if (it != list->pinned_dialog_id_orders_.end()) { CHECK(list->pinned_dialogs_[0] != dialog_date); list->pinned_dialogs_[0] = dialog_date; it->second = order; } else { CHECK(list->pinned_dialogs_[0] == dialog_date); list->pinned_dialog_id_orders_.emplace(dialog_id, order); } } else { if (!td::remove_if(list->pinned_dialogs_, is_changed_dialog)) { return false; } list->pinned_dialog_id_orders_.erase(dialog_id); } LOG(INFO) << "Set " << d->dialog_id << " is pinned in " << dialog_list_id << " to " << is_pinned; save_pinned_folder_dialog_ids(*list); if (need_update_dialog_lists) { update_dialog_lists(d, std::move(positions), true, false, "set_dialog_is_pinned"); } return true; } void MessagesManager::save_pinned_folder_dialog_ids(const DialogList &list) const { if (!list.dialog_list_id.is_folder() || !G()->use_message_database()) { return; } G()->td_db()->get_binlog_pmc()->set( PSTRING() << "pinned_dialog_ids" << list.dialog_list_id.get_folder_id().get(), implode(transform(list.pinned_dialogs_, [](auto &pinned_dialog) { return PSTRING() << pinned_dialog.get_dialog_id().get(); }), ',')); } void MessagesManager::set_dialog_reply_markup(Dialog *d, MessageId message_id) { if (td_->auth_manager_->is_bot()) { return; } CHECK(!message_id.is_scheduled()); if (d->reply_markup_message_id != message_id) { on_dialog_updated(d->dialog_id, "set_dialog_reply_markup"); } d->need_restore_reply_markup = false; if (d->reply_markup_message_id.is_valid() || message_id.is_valid()) { LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in set_dialog_reply_markup"; d->reply_markup_message_id = message_id; send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateChatReplyMarkup"), message_id.get())); } } void MessagesManager::try_restore_dialog_reply_markup(Dialog *d, const Message *m) { if (!d->need_restore_reply_markup || td_->auth_manager_->is_bot()) { return; } CHECK(!m->message_id.is_scheduled()); if (m->had_reply_markup) { LOG(INFO) << "Restore deleted reply markup in " << d->dialog_id; set_dialog_reply_markup(d, MessageId()); } else if (m->reply_markup != nullptr && m->reply_markup->type != ReplyMarkup::Type::InlineKeyboard && m->reply_markup->is_personal) { LOG(INFO) << "Restore reply markup in " << d->dialog_id << " to " << m->message_id; set_dialog_reply_markup(d, m->message_id); } } void MessagesManager::set_dialog_pinned_message_notification(Dialog *d, MessageId message_id, const char *source) { CHECK(d != nullptr); CHECK(!message_id.is_scheduled()); if (d->notification_info == nullptr && message_id == MessageId()) { return; } CHECK(!td_->auth_manager_->is_bot()); auto notification_info = add_dialog_notification_info(d); auto old_message_id = notification_info->pinned_message_notification_message_id_; if (old_message_id == message_id) { return; } VLOG(notifications) << "Change pinned message notification in " << d->dialog_id << " from " << old_message_id << " to " << message_id; if (old_message_id.is_valid()) { auto m = get_message_force(d, old_message_id, source); if (m != nullptr && m->notification_id.is_valid() && is_message_notification_active(d, m)) { // Can't remove pinned_message_notification_message_id before the call, // because the notification needs to be still active inside remove_message_notification_id remove_message_notification_id(d, m, true, false, true); on_message_changed(d, m, false, source); } else { send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_object_id, notification_info->mention_notification_group_.get_group_id(), old_message_id, false, source); } } notification_info->pinned_message_notification_message_id_ = message_id; on_dialog_updated(d->dialog_id, source); } void MessagesManager::remove_dialog_pinned_message_notification(Dialog *d, const char *source) { set_dialog_pinned_message_notification(d, MessageId(), source); } void MessagesManager::remove_scope_pinned_message_notifications(NotificationSettingsScope scope) { VLOG(notifications) << "Remove pinned message notifications in " << scope; dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr &dialog) { Dialog *d = dialog.get(); if (d->notification_settings.use_default_disable_pinned_message_notifications && d->notification_info != nullptr && d->notification_info->mention_notification_group_.is_valid() && d->notification_info->pinned_message_notification_message_id_.is_valid() && get_dialog_notification_setting_scope(dialog_id) == scope) { remove_dialog_pinned_message_notification(d, "remove_scope_pinned_message_notifications"); } }); } void MessagesManager::on_update_scope_mention_notifications(NotificationSettingsScope scope, bool disable_mention_notifications) { VLOG(notifications) << "Remove mention notifications in " << scope; dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr &dialog) { Dialog *d = dialog.get(); if (d->notification_settings.use_default_disable_mention_notifications && get_dialog_notification_setting_scope(dialog_id) == scope) { if (!disable_mention_notifications) { update_dialog_mention_notification_count(d); } else { remove_dialog_mention_notifications(d); } } }); } void MessagesManager::remove_dialog_mention_notifications(Dialog *d) { if (d->notification_info == nullptr || !d->notification_info->mention_notification_group_.is_valid() || d->unread_mention_count == 0) { return; } CHECK(!d->being_added_message_id.is_valid()); VLOG(notifications) << "Remove mention notifications in " << d->dialog_id; auto message_ids = find_dialog_messages(d, [](const Message *m) { return m->contains_unread_mention; }); VLOG(notifications) << "Found unread mentions in " << message_ids; FlatHashSet removed_notification_ids_set; for (auto &message_id : message_ids) { auto m = get_message(d, message_id); CHECK(m != nullptr); CHECK(m->message_id.is_valid()); if (m->notification_id.is_valid() && is_message_notification_active(d, m) && is_from_mention_notification_group(m)) { removed_notification_ids_set.insert(m->notification_id); } } auto notification_group_id = d->notification_info->mention_notification_group_.get_group_id(); CHECK(notification_group_id.is_valid()); message_ids = td_->notification_manager_->get_notification_group_message_ids(notification_group_id); VLOG(notifications) << "Found active mention notifications in " << message_ids; for (auto &message_id : message_ids) { CHECK(!message_id.is_scheduled()); if (message_id != d->notification_info->pinned_message_notification_message_id_) { auto m = get_message_force(d, message_id, "remove_dialog_mention_notifications"); if (m != nullptr && m->notification_id.is_valid()) { CHECK(is_from_mention_notification_group(m)); if (is_message_notification_active(d, m)) { removed_notification_ids_set.insert(m->notification_id); } } } } vector removed_notification_ids; for (auto notification_id : removed_notification_ids_set) { removed_notification_ids.push_back(notification_id); } for (size_t i = 0; i < removed_notification_ids.size(); i++) { send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification, notification_group_id, removed_notification_ids[i], false, i + 1 == removed_notification_ids.size(), Promise(), "remove_dialog_mention_notifications"); } } void MessagesManager::set_dialog_last_notification(DialogId dialog_id, NotificationGroupInfo &group_info, int32 last_notification_date, NotificationId last_notification_id, const char *source) { if (group_info.set_last_notification(last_notification_date, last_notification_id, source)) { on_dialog_updated(dialog_id, "set_dialog_last_notification"); } } void MessagesManager::set_dialog_last_notification_checked(DialogId dialog_id, NotificationGroupInfo &group_info, int32 last_notification_date, NotificationId last_notification_id, const char *source) { bool is_changed = group_info.set_last_notification(last_notification_date, last_notification_id, source); CHECK(is_changed); on_dialog_updated(dialog_id, "set_dialog_last_notification_checked"); } void MessagesManager::on_update_sent_text_message(int64 random_id, tl_object_ptr message_media, vector> &&entities) { int32 message_media_id = message_media == nullptr ? telegram_api::messageMediaEmpty::ID : message_media->get_id(); LOG_IF(ERROR, message_media_id != telegram_api::messageMediaWebPage::ID && message_media_id != telegram_api::messageMediaEmpty::ID) << "Receive non web-page media for text message: " << oneline(to_string(message_media)); auto it = being_sent_messages_.find(random_id); if (it == being_sent_messages_.end()) { // result of sending message has already been received through getDifference return; } auto message_full_id = it->second; auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog(dialog_id); auto m = get_message_force(d, message_full_id.get_message_id(), "on_update_sent_text_message"); if (m == nullptr) { // message has already been deleted return; } CHECK(m->message_id.is_yet_unsent()); message_full_id = MessageFullId(dialog_id, m->message_id); if (m->content->get_type() != MessageContentType::Text) { LOG(ERROR) << "Text message content has been already changed to " << m->content->get_type(); return; } const FormattedText *old_message_text = get_message_content_text(m->content.get()); CHECK(old_message_text != nullptr); FormattedText new_message_text = get_message_text( td_->contacts_manager_.get(), old_message_text->text, std::move(entities), true, td_->auth_manager_->is_bot(), m->forward_info ? m->forward_info->date : m->date, m->media_album_id != 0, "on_update_sent_text_message"); auto new_content = get_message_content(td_, std::move(new_message_text), std::move(message_media), dialog_id, m->date, true /*likely ignored*/, UserId() /*likely ignored*/, nullptr /*ignored*/, nullptr, "on_update_sent_text_message"); if (new_content->get_type() != MessageContentType::Text) { LOG(ERROR) << "Text message content has changed to " << new_content->get_type(); return; } bool need_update = false; bool is_content_changed = false; merge_message_contents(td_, m->content.get(), new_content.get(), need_message_changed_warning(m), dialog_id, false, is_content_changed, need_update); compare_message_contents(td_, m->content.get(), new_content.get(), is_content_changed, need_update); if (is_content_changed || need_update) { reregister_message_content(td_, m->content.get(), new_content.get(), message_full_id, "on_update_sent_text_message"); m->content = std::move(new_content); m->is_content_secret = is_secret_message_content(m->ttl, MessageContentType::Text); if (need_update) { send_update_message_content(d, m, true, "on_update_sent_text_message"); } on_message_changed(d, m, need_update, "on_update_sent_text_message"); } } void MessagesManager::delete_pending_message_web_page(MessageFullId message_full_id) { auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); Message *m = get_message(d, message_full_id.get_message_id()); CHECK(m != nullptr); MessageContent *content = m->content.get(); CHECK(has_message_content_web_page(content)); unregister_message_content(td_, content, message_full_id, "delete_pending_message_web_page"); remove_message_content_web_page(content); register_message_content(td_, content, message_full_id, "delete_pending_message_web_page"); // don't need to send an updateMessageContent, because the web page was pending on_message_changed(d, m, false, "delete_pending_message_web_page"); } void MessagesManager::on_get_dialogs(FolderId folder_id, vector> &&dialog_folders, int32 total_count, vector> &&messages, Promise &&promise) { if (td_->updates_manager_->running_get_difference()) { LOG(INFO) << "Postpone result of getDialogs"; pending_on_get_dialogs_.push_back(PendingOnGetDialogs{folder_id, std::move(dialog_folders), total_count, std::move(messages), std::move(promise)}); return; } bool from_dialog_list = total_count >= 0; bool from_get_dialog = total_count == -1; bool from_pinned_dialog_list = total_count == -2; if (from_get_dialog && dialog_folders.size() == 1 && dialog_folders[0]->get_id() == telegram_api::dialog::ID) { DialogId dialog_id(static_cast(dialog_folders[0].get())->peer_); if (dialog_id.is_valid() && running_get_channel_difference(dialog_id)) { LOG(INFO) << "Postpone result of channels getDialogs for " << dialog_id; pending_channel_on_get_dialogs_.emplace( dialog_id, PendingOnGetDialogs{folder_id, std::move(dialog_folders), total_count, std::move(messages), std::move(promise)}); return; } } vector> dialogs; for (auto &dialog_folder : dialog_folders) { switch (dialog_folder->get_id()) { case telegram_api::dialog::ID: dialogs.push_back(telegram_api::move_object_as(dialog_folder)); break; case telegram_api::dialogFolder::ID: { auto folder = telegram_api::move_object_as(dialog_folder); if (from_pinned_dialog_list) { // TODO update unread_muted_peers_count:int unread_unmuted_peers_count:int // unread_muted_messages_count:int unread_unmuted_messages_count:int FolderId folder_folder_id(folder->folder_->id_); if (folder_folder_id == FolderId::archive()) { // archive is expected in pinned dialogs list break; } } LOG(ERROR) << "Receive unexpected " << to_string(folder); break; } default: UNREACHABLE(); } } const char *source = nullptr; if (from_get_dialog) { LOG(INFO) << "Process " << dialogs.size() << " chats"; source = "get chat"; } else if (from_pinned_dialog_list) { LOG(INFO) << "Process " << dialogs.size() << " pinned chats in " << folder_id; source = "get pinned chats"; } else { LOG(INFO) << "Process " << dialogs.size() << " chats out of " << total_count << " in " << folder_id; source = "get chat list"; } FlatHashMap message_full_id_to_dialog_date; FlatHashMap, MessageFullIdHash> message_full_id_to_message; for (auto &message : messages) { auto message_full_id = MessageFullId::get_message_full_id(message, false); if (!message_full_id.get_message_id().is_valid()) { // must not check dialog_id because of messageEmpty continue; } if (from_dialog_list) { auto message_date = get_message_date(message); int64 order = get_dialog_order(message_full_id.get_message_id(), message_date); message_full_id_to_dialog_date.emplace(message_full_id, DialogDate(order, message_full_id.get_dialog_id())); } message_full_id_to_message[message_full_id] = std::move(message); } DialogDate max_dialog_date = MIN_DIALOG_DATE; for (auto &dialog : dialogs) { // LOG(INFO) << to_string(dialog); DialogId dialog_id(dialog->peer_); bool has_pts = (dialog->flags_ & DIALOG_FLAG_HAS_PTS) != 0; if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive wrong " << dialog_id; return promise.set_error(Status::Error(500, "Wrong query result returned: receive wrong chat identifier")); } switch (dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: if (has_pts) { LOG(ERROR) << "Receive user or group " << dialog_id << " with PTS"; return promise.set_error( Status::Error(500, "Wrong query result returned: receive user or basic group chat with PTS")); } break; case DialogType::Channel: if (!has_pts) { LOG(ERROR) << "Receive channel " << dialog_id << " without PTS"; return promise.set_error( Status::Error(500, "Wrong query result returned: receive supergroup chat without PTS")); } break; case DialogType::SecretChat: case DialogType::None: default: UNREACHABLE(); return promise.set_error(Status::Error(500, "UNREACHABLE")); } if (from_dialog_list) { MessageId last_message_id(ServerMessageId(dialog->top_message_)); if (last_message_id.is_valid()) { MessageFullId message_full_id(dialog_id, last_message_id); auto it = message_full_id_to_dialog_date.find(message_full_id); if (it == message_full_id_to_dialog_date.end() && dialog_id.get_type() != DialogType::Channel) { it = message_full_id_to_dialog_date.find({DialogId(), last_message_id}); } if (it == message_full_id_to_dialog_date.end()) { LOG(ERROR) << "Last " << last_message_id << " in " << dialog_id << " not found"; return promise.set_error(Status::Error(500, "Wrong query result returned: last message not found")); } FolderId dialog_folder_id(dialog->folder_id_); if (dialog_folder_id != folder_id) { LOG(ERROR) << "Receive " << dialog_id << " in " << dialog_folder_id << " instead of " << folder_id; continue; } DialogDate dialog_date = it->second; if (dialog_date.get_date() > 0 && dialog_date.get_dialog_id() == dialog_id && max_dialog_date < dialog_date) { max_dialog_date = dialog_date; } } else { LOG(ERROR) << "Receive " << last_message_id << " as last chat message"; continue; } } } if (from_dialog_list && total_count < narrow_cast(dialogs.size())) { LOG(ERROR) << "Receive chat total_count = " << total_count << ", but " << dialogs.size() << " chats"; total_count = narrow_cast(dialogs.size()); } vector added_dialog_ids; for (auto &dialog : dialogs) { MessageId last_message_id(ServerMessageId(dialog->top_message_)); if (!last_message_id.is_valid() && from_dialog_list) { // skip dialogs without messages total_count--; continue; } DialogId dialog_id(dialog->peer_); if (td::contains(added_dialog_ids, dialog_id)) { LOG(ERROR) << "Receive " << dialog_id << " twice in result of getChats with total_count = " << total_count; continue; } added_dialog_ids.push_back(dialog_id); Dialog *d = get_dialog_force(dialog_id, source); bool need_update_dialog_pos = false; CHECK(!being_added_dialog_id_.is_valid()); being_added_dialog_id_ = dialog_id; if (d == nullptr) { d = add_dialog(dialog_id, source); need_update_dialog_pos = true; } else { LOG(INFO) << "Receive already created " << dialog_id; CHECK(d->dialog_id == dialog_id); } bool is_new = d->last_new_message_id == MessageId(); auto positions = get_dialog_positions(d); set_dialog_folder_id(d, FolderId(dialog->folder_id_)); set_dialog_view_as_messages(d, dialog->view_forum_as_messages_); on_update_dialog_notify_settings(dialog_id, std::move(dialog->notify_settings_), source); if (!d->notification_settings.is_synchronized && !td_->auth_manager_->is_bot()) { LOG(ERROR) << "Failed to synchronize settings in " << dialog_id; d->notification_settings.is_synchronized = true; on_dialog_updated(dialog_id, "set notification_settings.is_synchronized"); } if (dialog->unread_count_ < 0) { LOG(ERROR) << "Receive " << dialog->unread_count_ << " as number of unread messages in " << dialog_id; dialog->unread_count_ = 0; } MessageId read_inbox_max_message_id = MessageId(ServerMessageId(dialog->read_inbox_max_id_)); if (!read_inbox_max_message_id.is_valid() && read_inbox_max_message_id != MessageId()) { LOG(ERROR) << "Receive " << read_inbox_max_message_id << " as last read inbox message in " << dialog_id; read_inbox_max_message_id = MessageId(); } MessageId read_outbox_max_message_id = MessageId(ServerMessageId(dialog->read_outbox_max_id_)); if (!read_outbox_max_message_id.is_valid() && read_outbox_max_message_id != MessageId()) { LOG(ERROR) << "Receive " << read_outbox_max_message_id << " as last read outbox message in " << dialog_id; read_outbox_max_message_id = MessageId(); } if (dialog->unread_mentions_count_ < 0) { LOG(ERROR) << "Receive " << dialog->unread_mentions_count_ << " as number of unread mention messages in " << dialog_id; dialog->unread_mentions_count_ = 0; } if (dialog->unread_reactions_count_ < 0) { LOG(ERROR) << "Receive " << dialog->unread_reactions_count_ << " as number of messages with unread reactions in " << dialog_id; dialog->unread_reactions_count_ = 0; } if (dialog->ttl_period_ < 0) { LOG(ERROR) << "Receive " << dialog->ttl_period_ << " as message auto-delete time in " << dialog_id; dialog->ttl_period_ = 0; } if (!d->is_is_blocked_for_stories_inited && !td_->auth_manager_->is_bot()) { // asynchronously get is_blocked_for_stories from the server // TODO add is_blocked/is_blocked_for_stories to telegram_api::dialog reload_dialog_info_full(dialog_id, "on_get_dialogs init is_blocked"); } else if (!d->is_has_bots_inited && !td_->auth_manager_->is_bot()) { // asynchronously get has_bots from the server // TODO add has_bots to telegram_api::dialog reload_dialog_info_full(dialog_id, "on_get_dialogs init has_bots"); } else if (!d->is_background_inited && !td_->auth_manager_->is_bot()) { // asynchronously get background from the server // TODO add background to telegram_api::dialog reload_dialog_info_full(dialog_id, "on_get_dialogs init background"); } else if (!d->is_theme_name_inited && !td_->auth_manager_->is_bot()) { // asynchronously get theme_name from the server // TODO add theme_name to telegram_api::dialog reload_dialog_info_full(dialog_id, "on_get_dialogs init theme_name"); } else if (!d->is_last_pinned_message_id_inited && !td_->auth_manager_->is_bot()) { // asynchronously get dialog pinned message from the server get_dialog_pinned_message(dialog_id, Auto()); } else if (!d->is_available_reactions_inited && !td_->auth_manager_->is_bot()) { // asynchronously get dialog available reactions from the server reload_dialog_info_full(dialog_id, "on_get_dialogs init available_reactions"); } need_update_dialog_pos |= update_dialog_draft_message(d, get_draft_message(td_, std::move(dialog->draft_)), true, false); if (is_new) { bool has_pts = (dialog->flags_ & DIALOG_FLAG_HAS_PTS) != 0; if (last_message_id.is_valid() && !td_->auth_manager_->is_bot()) { MessageFullId message_full_id(dialog_id, last_message_id); auto it = message_full_id_to_message.find(message_full_id); if (it == message_full_id_to_message.end() && dialog_id.get_type() != DialogType::Channel) { it = message_full_id_to_message.find({DialogId(), last_message_id}); } if (it == message_full_id_to_message.end()) { LOG(ERROR) << "Last " << message_full_id << " not found"; } else if (!has_pts || d->pts == 0 || dialog->pts_ <= d->pts || d->is_channel_difference_finished) { auto last_message = std::move(it->second); auto added_message_full_id = on_get_message(std::move(last_message), false, has_pts, false, source); CHECK(d->last_new_message_id == MessageId()); set_dialog_last_new_message_id(d, last_message_id, source); if (d->last_new_message_id > d->last_message_id && added_message_full_id.get_message_id().is_valid()) { CHECK(added_message_full_id.get_message_id() == d->last_new_message_id); set_dialog_last_message_id(d, d->last_new_message_id, source); send_update_chat_last_message(d, source); } } else if (dialog_id.get_type() == DialogType::Channel) { get_channel_difference(dialog_id, d->pts, dialog->pts_, last_message_id, true, source); } } if (has_pts && !running_get_channel_difference(dialog_id)) { set_channel_pts(d, dialog->pts_, source); } } bool is_marked_as_unread = dialog->unread_mark_; if (is_marked_as_unread != d->is_marked_as_unread) { set_dialog_is_marked_as_unread(d, is_marked_as_unread); } if (need_update_dialog_pos) { update_dialog_pos(d, source); } if (!td_->auth_manager_->is_bot() && !from_pinned_dialog_list) { // set is_pinned only after updating chat position to ensure that order is initialized bool is_pinned = (dialog->flags_ & DIALOG_FLAG_IS_PINNED) != 0; bool was_pinned = is_dialog_pinned(DialogListId(d->folder_id), dialog_id); if (is_pinned != was_pinned) { set_dialog_is_pinned(DialogListId(d->folder_id), d, is_pinned); } } if (!G()->use_message_database() || is_new || !d->is_last_read_inbox_message_id_inited || d->need_repair_server_unread_count) { if (d->last_read_inbox_message_id.is_valid() && !d->last_read_inbox_message_id.is_server() && read_inbox_max_message_id == d->last_read_inbox_message_id.get_prev_server_message_id()) { read_inbox_max_message_id = d->last_read_inbox_message_id; } if (d->need_repair_server_unread_count && (d->last_read_inbox_message_id <= read_inbox_max_message_id || !need_unread_counter(d->order) || !have_input_peer(dialog_id, AccessRights::Read))) { LOG(INFO) << "Repaired server unread count in " << dialog_id << " from " << d->last_read_inbox_message_id << "/" << d->server_unread_count << " to " << read_inbox_max_message_id << "/" << dialog->unread_count_; d->need_repair_server_unread_count = false; on_dialog_updated(dialog_id, "repaired dialog server unread count"); } if (d->need_repair_server_unread_count) { auto &previous_message_id = previous_repaired_read_inbox_max_message_id_[dialog_id]; if (previous_message_id >= read_inbox_max_message_id) { // protect from sending the request in a loop if (d->server_unread_count != 0) { LOG(ERROR) << "Failed to repair server unread count in " << dialog_id << ", because receive read_inbox_max_message_id = " << read_inbox_max_message_id << " after " << previous_message_id << ", but messages are read up to " << d->last_read_inbox_message_id; } else { LOG(INFO) << "Failed to repair server unread count in " << dialog_id << ", because receive read_inbox_max_message_id = " << read_inbox_max_message_id << ", but messages are read up to " << d->last_read_inbox_message_id << ". Likely all messages after " << read_inbox_max_message_id << " are outgoing"; } d->need_repair_server_unread_count = false; on_dialog_updated(dialog_id, "failed to repair dialog server unread count"); } else { LOG(INFO) << "Have last_read_inbox_message_id = " << d->last_read_inbox_message_id << ", but received only " << read_inbox_max_message_id << " from the server, trying to repair server unread count again"; previous_message_id = read_inbox_max_message_id; repair_server_unread_count(dialog_id, d->server_unread_count, source); } } if (!d->need_repair_server_unread_count) { previous_repaired_read_inbox_max_message_id_.erase(dialog_id); } if ((d->server_unread_count != dialog->unread_count_ && d->last_read_inbox_message_id == read_inbox_max_message_id) || d->last_read_inbox_message_id < read_inbox_max_message_id) { set_dialog_last_read_inbox_message_id(d, read_inbox_max_message_id, dialog->unread_count_, d->local_unread_count, true, source); } if (!d->is_last_read_inbox_message_id_inited) { d->is_last_read_inbox_message_id_inited = true; on_dialog_updated(dialog_id, "set is_last_read_inbox_message_id_inited"); } } if (!G()->use_message_database() || is_new || !d->is_last_read_outbox_message_id_inited) { if (d->last_read_outbox_message_id < read_outbox_max_message_id) { set_dialog_last_read_outbox_message_id(d, read_outbox_max_message_id); } if (!d->is_last_read_outbox_message_id_inited) { d->is_last_read_outbox_message_id_inited = true; on_dialog_updated(dialog_id, "set is_last_read_outbox_message_id_inited"); } } if (!G()->use_message_database() || is_new || d->need_repair_unread_mention_count) { if (d->need_repair_unread_mention_count) { if (d->unread_mention_count != dialog->unread_mentions_count_) { LOG(INFO) << "Repaired unread mention count in " << dialog_id << " from " << d->unread_mention_count << " to " << dialog->unread_mentions_count_; } d->need_repair_unread_mention_count = false; on_dialog_updated(dialog_id, "repaired dialog unread mention count"); } if (d->unread_mention_count != dialog->unread_mentions_count_ && !td_->auth_manager_->is_bot()) { set_dialog_unread_mention_count(d, dialog->unread_mentions_count_); update_dialog_mention_notification_count(d); send_update_chat_unread_mention_count(d); } } if (!G()->use_message_database() || is_new || d->need_repair_unread_reaction_count) { if (d->need_repair_unread_reaction_count) { if (d->unread_reaction_count != dialog->unread_reactions_count_) { LOG(INFO) << "Repaired unread reaction count in " << dialog_id << " from " << d->unread_reaction_count << " to " << dialog->unread_reactions_count_; } d->need_repair_unread_reaction_count = false; on_dialog_updated(dialog_id, "repaired dialog unread reaction count"); } if (d->unread_reaction_count != dialog->unread_reactions_count_ && !td_->auth_manager_->is_bot()) { set_dialog_unread_reaction_count(d, dialog->unread_reactions_count_); // update_dialog_reaction_notification_count(d); send_update_chat_unread_reaction_count(d, source); } } set_dialog_message_ttl(d, MessageTtl(dialog->ttl_period_)); being_added_dialog_id_ = DialogId(); update_dialog_lists(d, std::move(positions), true, false, source); if ((from_dialog_list || from_pinned_dialog_list) && d->order == DEFAULT_ORDER) { load_last_dialog_message(d, "on_get_dialog"); } } if (from_dialog_list) { CHECK(!td_->auth_manager_->is_bot()); CHECK(total_count >= 0); auto &folder_list = add_dialog_list(DialogListId(folder_id)); if (folder_list.server_dialog_total_count_ != total_count) { auto old_dialog_total_count = get_dialog_total_count(folder_list); folder_list.server_dialog_total_count_ = total_count; if (folder_list.is_dialog_unread_count_inited_) { if (old_dialog_total_count != get_dialog_total_count(folder_list)) { send_update_unread_chat_count(folder_list, DialogId(), true, source); } else { save_unread_chat_count(folder_list); } } } auto *folder = get_dialog_folder(folder_id); CHECK(folder != nullptr); if (dialogs.empty()) { // if there are no more dialogs on the server max_dialog_date = MAX_DIALOG_DATE; } if (folder->last_server_dialog_date_ < max_dialog_date) { folder->last_server_dialog_date_ = max_dialog_date; update_last_dialog_date(folder_id); } else if (promise) { LOG(ERROR) << "Last server dialog date didn't increased from " << folder->last_server_dialog_date_ << " to " << max_dialog_date << " after receiving " << dialogs.size() << " chats " << added_dialog_ids << " from " << total_count << " in " << folder_id << ". last_dialog_date = " << folder->folder_last_dialog_date_ << ", last_loaded_database_dialog_date = " << folder->last_loaded_database_dialog_date_; } } if (from_pinned_dialog_list) { CHECK(!td_->auth_manager_->is_bot()); auto *folder_list = get_dialog_list(DialogListId(folder_id)); CHECK(folder_list != nullptr); auto pinned_dialog_ids = remove_secret_chat_dialog_ids(get_pinned_dialog_ids(DialogListId(folder_id))); bool are_pinned_dialogs_saved = folder_list->are_pinned_dialogs_inited_; folder_list->are_pinned_dialogs_inited_ = true; if (pinned_dialog_ids != added_dialog_ids) { LOG(INFO) << "Update pinned chats order from " << format::as_array(pinned_dialog_ids) << " to " << format::as_array(added_dialog_ids); FlatHashSet old_pinned_dialog_ids; for (auto pinned_dialog_id : pinned_dialog_ids) { old_pinned_dialog_ids.insert(pinned_dialog_id); } std::reverse(pinned_dialog_ids.begin(), pinned_dialog_ids.end()); std::reverse(added_dialog_ids.begin(), added_dialog_ids.end()); auto old_it = pinned_dialog_ids.begin(); for (auto dialog_id : added_dialog_ids) { old_pinned_dialog_ids.erase(dialog_id); while (old_it < pinned_dialog_ids.end()) { if (*old_it == dialog_id) { break; } ++old_it; } if (old_it < pinned_dialog_ids.end()) { // leave dialog where it is ++old_it; continue; } if (set_dialog_is_pinned(dialog_id, true)) { are_pinned_dialogs_saved = true; } } for (auto dialog_id : old_pinned_dialog_ids) { Dialog *d = get_dialog_force(dialog_id, "on_get_dialogs pinned"); if (d == nullptr) { LOG(ERROR) << "Failed to find " << dialog_id << " to unpin in " << folder_id; force_create_dialog(dialog_id, "from_pinned_dialog_list", true); d = get_dialog_force(dialog_id, "on_get_dialogs pinned 2"); } if (d != nullptr && set_dialog_is_pinned(DialogListId(folder_id), d, false)) { are_pinned_dialogs_saved = true; } } } else { LOG(INFO) << "Pinned chats are not changed"; } update_list_last_pinned_dialog_date(*folder_list); if (!are_pinned_dialogs_saved && G()->use_message_database()) { LOG(INFO) << "Save empty pinned chat list in " << folder_id; G()->td_db()->get_binlog_pmc()->set(PSTRING() << "pinned_dialog_ids" << folder_id.get(), ""); } } promise.set_value(Unit()); } bool MessagesManager::is_message_unload_enabled() const { return G()->use_message_database() || td_->auth_manager_->is_bot(); } bool MessagesManager::can_unload_message(const Dialog *d, const Message *m) const { CHECK(d != nullptr); CHECK(m != nullptr); CHECK(m->message_id.is_valid()); MessageFullId message_full_id{d->dialog_id, m->message_id}; if (td_->auth_manager_->is_bot() && !G()->use_message_database()) { return !m->message_id.is_yet_unsent() && replied_by_yet_unsent_messages_.count(message_full_id) == 0 && m->edited_content == nullptr && m->message_id != d->last_pinned_message_id && m->message_id != d->last_edited_message_id; } // don't want to unload messages from opened dialogs // don't want to unload messages to which there are replies in yet unsent messages // don't want to unload message with active reply markup // don't want to unload the newest pinned message // don't want to unload last edited message, because server can send updateEditChannelMessage again // don't want to unload messages from the last album // can't unload from memory last dialog, last database messages, yet unsent messages, being edited media messages and active live locations // can't unload messages in dialog with active suffix load query { auto it = dialog_suffix_load_queries_.find(d->dialog_id); if (it != dialog_suffix_load_queries_.end() && !it->second->suffix_load_queries_.empty()) { return false; } } return d->open_count == 0 && m->message_id != d->last_message_id && m->message_id != d->last_database_message_id && !m->message_id.is_yet_unsent() && active_live_location_message_full_ids_.count(message_full_id) == 0 && replied_by_yet_unsent_messages_.count(message_full_id) == 0 && m->edited_content == nullptr && m->message_id != d->reply_markup_message_id && m->message_id != d->last_pinned_message_id && m->message_id != d->last_edited_message_id && (m->media_album_id != d->last_media_album_id || m->media_album_id == 0); } unique_ptr MessagesManager::unload_message(Dialog *d, MessageId message_id) { CHECK(d != nullptr); CHECK(message_id.is_valid()); bool need_update_dialog_pos = false; auto message = do_delete_message(d, message_id, false, true, &need_update_dialog_pos, "unload_message"); CHECK(!need_update_dialog_pos); return message; } unique_ptr MessagesManager::delete_message(Dialog *d, MessageId message_id, bool is_permanently_deleted, bool *need_update_dialog_pos, const char *source) { return do_delete_message(d, message_id, is_permanently_deleted, false, need_update_dialog_pos, source); } void MessagesManager::add_random_id_to_message_id_correspondence(Dialog *d, int64 random_id, MessageId message_id) { CHECK(d != nullptr); CHECK(d->dialog_id.get_type() == DialogType::SecretChat || message_id.is_yet_unsent()); auto it = d->random_id_to_message_id.find(random_id); if (it == d->random_id_to_message_id.end() || it->second.get() < message_id.get()) { LOG(INFO) << "Add correspondence from random_id " << random_id << " to " << message_id << " in " << d->dialog_id; if (random_id != 0) { d->random_id_to_message_id[random_id] = message_id; } } } void MessagesManager::delete_random_id_to_message_id_correspondence(Dialog *d, int64 random_id, MessageId message_id) { CHECK(d != nullptr); CHECK(d->dialog_id.get_type() == DialogType::SecretChat || message_id.is_yet_unsent()); auto it = d->random_id_to_message_id.find(random_id); if (it != d->random_id_to_message_id.end() && it->second == message_id) { LOG(INFO) << "Delete correspondence from random_id " << random_id << " to " << message_id << " in " << d->dialog_id; d->random_id_to_message_id.erase(it); } } void MessagesManager::add_notification_id_to_message_id_correspondence(NotificationInfo *notification_info, NotificationId notification_id, MessageId message_id) { CHECK(notification_info != nullptr); CHECK(notification_id.is_valid()); CHECK(message_id.is_valid()); auto it = notification_info->notification_id_to_message_id_.find(notification_id); if (it == notification_info->notification_id_to_message_id_.end()) { VLOG(notifications) << "Add correspondence from " << notification_id << " to " << message_id; notification_info->notification_id_to_message_id_.emplace(notification_id, message_id); } else if (it->second != message_id) { LOG(ERROR) << "Have the same " << notification_id << " for " << message_id << " and " << it->second; if (it->second < message_id) { it->second = message_id; } } } void MessagesManager::delete_notification_id_to_message_id_correspondence(NotificationInfo *notification_info, NotificationId notification_id, MessageId message_id) { CHECK(notification_info != nullptr); CHECK(notification_id.is_valid()); CHECK(message_id.is_valid()); auto it = notification_info->notification_id_to_message_id_.find(notification_id); if (it != notification_info->notification_id_to_message_id_.end() && it->second == message_id) { VLOG(notifications) << "Delete correspondence from " << notification_id << " to " << message_id; notification_info->notification_id_to_message_id_.erase(it); } else { LOG(ERROR) << "Can't find " << notification_id << " from " << message_id; } } bool MessagesManager::is_notification_info_group_id(const NotificationInfo *notification_info, NotificationGroupId group_id) { if (!group_id.is_valid() || notification_info == nullptr) { return false; } return notification_info->message_notification_group_.has_group_id(group_id) || notification_info->mention_notification_group_.has_group_id(group_id); } bool MessagesManager::is_dialog_notification_group_id(const Dialog *d, NotificationGroupId group_id) { if (d == nullptr) { return false; } return is_notification_info_group_id(d->notification_info.get(), group_id); } void MessagesManager::remove_message_notification_id(Dialog *d, Message *m, bool is_permanent, bool force_update, bool ignore_pinned_message_notification_removal) { CHECK(d != nullptr); CHECK(m != nullptr); CHECK(m->message_id.is_valid()); if (!m->notification_id.is_valid()) { return; } auto from_mentions = is_from_mention_notification_group(m); auto &group_info = get_notification_group_info(d, m); if (!group_info.is_valid()) { return; } bool had_active_notification = is_message_notification_active(d, m); auto notification_id = m->notification_id; VLOG(notifications) << "Remove " << notification_id << " from " << m->message_id << " in " << group_info.get_group_id() << '/' << d->dialog_id << " from database, was_active = " << had_active_notification << ", is_permanent = " << is_permanent; delete_notification_id_to_message_id_correspondence(d->notification_info.get(), notification_id, m->message_id); m->removed_notification_id = m->notification_id; m->notification_id = NotificationId(); if (d->notification_info->pinned_message_notification_message_id_ == m->message_id && is_permanent && !ignore_pinned_message_notification_removal) { remove_dialog_pinned_message_notification( d, "remove_message_notification_id"); // must be called after notification_id is removed } if (group_info.get_last_notification_id() == notification_id) { // last notification is deleted, need to find new last notification fix_dialog_last_notification_id(d, from_mentions, m->message_id); } if (is_permanent) { if (had_active_notification) { send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification, group_info.get_group_id(), notification_id, is_permanent, force_update, Promise(), "remove_message_notification_id"); } // on_message_changed will be called by the caller // don't need to call there to not save twice/or to save just deleted message } else { on_message_changed(d, m, false, "remove_message_notification_id"); } } void MessagesManager::remove_new_secret_chat_notification(Dialog *d, bool is_permanent) { CHECK(d != nullptr); CHECK(d->notification_info != nullptr); auto notification_id = d->notification_info->new_secret_chat_notification_id_; CHECK(notification_id.is_valid()); VLOG(notifications) << "Remove " << notification_id << " about new secret " << d->dialog_id << " from " << d->notification_info->message_notification_group_.get_group_id(); d->notification_info->new_secret_chat_notification_id_ = NotificationId(); set_dialog_last_notification_checked(d->dialog_id, d->notification_info->message_notification_group_, 0, NotificationId(), "remove_new_secret_chat_notification"); if (is_permanent) { CHECK(d->notification_info->message_notification_group_.is_valid()); send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification, d->notification_info->message_notification_group_.get_group_id(), notification_id, true, true, Promise(), "remove_new_secret_chat_notification"); } } void MessagesManager::fix_dialog_last_notification_id(Dialog *d, bool from_mentions, MessageId message_id) { CHECK(d != nullptr); CHECK(!message_id.is_scheduled()); if (d->notification_info == nullptr) { return; } CHECK(!td_->auth_manager_->is_bot()); auto &group_info = get_notification_group_info(d, from_mentions); CHECK(group_info.is_valid()); auto it = d->ordered_messages.get_const_iterator(message_id); VLOG(notifications) << "Trying to fix last notification identifier in " << group_info.get_group_id() << " from " << d->dialog_id << " from " << message_id << '/' << group_info.get_last_notification_id(); if (*it != nullptr && ((*it)->get_message_id() == message_id || (*it)->have_next())) { while (*it != nullptr) { const Message *m = get_message(d, (*it)->get_message_id()); if (is_from_mention_notification_group(m) == from_mentions && m->notification_id.is_valid() && is_message_notification_active(d, m) && m->message_id != message_id) { set_dialog_last_notification(d->dialog_id, group_info, m->date, m->notification_id, "fix_dialog_last_notification_id"); return; } --it; } } if (G()->use_message_database()) { get_message_notifications_from_database( d->dialog_id, group_info.get_group_id(), group_info.get_last_notification_id(), message_id, 1, PromiseCreator::lambda( [actor_id = actor_id(this), dialog_id = d->dialog_id, from_mentions, prev_last_notification_id = group_info.get_last_notification_id()](Result> result) { send_closure(actor_id, &MessagesManager::do_fix_dialog_last_notification_id, dialog_id, from_mentions, prev_last_notification_id, std::move(result)); })); } } void MessagesManager::do_fix_dialog_last_notification_id(DialogId dialog_id, bool from_mentions, NotificationId prev_last_notification_id, Result> result) { if (result.is_error()) { return; } Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); if (d->notification_info == nullptr) { return; } auto &group_info = get_notification_group_info(d, from_mentions); if (!group_info.is_valid()) { return; } VLOG(notifications) << "Receive " << result.ok().size() << " message notifications in " << group_info.get_group_id() << '/' << dialog_id << " from " << prev_last_notification_id; if (group_info.get_last_notification_id() != prev_last_notification_id) { // last_notification_id was changed return; } auto notifications = result.move_as_ok(); CHECK(notifications.size() <= 1); int32 last_notification_date = 0; NotificationId last_notification_id; if (!notifications.empty()) { last_notification_date = notifications[0].date; last_notification_id = notifications[0].notification_id; } set_dialog_last_notification(dialog_id, group_info, last_notification_date, last_notification_id, "do_fix_dialog_last_notification_id"); } // DO NOT FORGET TO ADD ALL CHANGES OF THIS FUNCTION AS WELL TO delete_all_dialog_messages unique_ptr MessagesManager::do_delete_message(Dialog *d, MessageId message_id, bool is_permanently_deleted, bool only_from_memory, bool *need_update_dialog_pos, const char *source) { CHECK(d != nullptr); if (!message_id.is_valid()) { if (message_id.is_valid_scheduled()) { return do_delete_scheduled_message(d, message_id, is_permanently_deleted, source); } LOG(ERROR) << "Trying to delete " << message_id << " in " << d->dialog_id << " from " << source; return nullptr; } MessageFullId message_full_id(d->dialog_id, message_id); const Message *m = get_message(d, message_id); if (m == nullptr) { if (only_from_memory) { return nullptr; } LOG(INFO) << message_id << " is not found in " << d->dialog_id << " to be deleted from " << source; m = get_message_force(d, message_id, "do_delete_message"); if (m == nullptr) { // currently there may be a race between add_message_to_database and get_message_force, // so delete a message from database just in case delete_message_from_database(d, message_id, nullptr, is_permanently_deleted, source); if (is_permanently_deleted && d->last_clear_history_message_id == message_id) { set_dialog_last_clear_history_date(d, 0, MessageId(), "do_delete_message"); *need_update_dialog_pos = true; } /* can't do this because the message may be never received in the dialog, unread count will became negative // if last_read_inbox_message_id is not known, we can't be sure whether unread_count should be decreased or not if (message_id.is_valid() && !message_id.is_yet_unsent() && d->is_last_read_inbox_message_id_inited && message_id > d->last_read_inbox_message_id && !td_->auth_manager_->is_bot()) { int32 server_unread_count = d->server_unread_count; int32 local_unread_count = d->local_unread_count; int32 &unread_count = message_id.is_server() ? server_unread_count : local_unread_count; if (unread_count == 0) { LOG(ERROR) << "Unread count became negative in " << d->dialog_id << " after deletion of " << message_id << ". Last read is " << d->last_read_inbox_message_id; } else { unread_count--; set_dialog_last_read_inbox_message_id(d, MessageId::min(), server_unread_count, local_unread_count, false, source); } } */ return nullptr; } } if (only_from_memory && !can_unload_message(d, m)) { return nullptr; } LOG_CHECK(!d->being_deleted_message_id.is_valid()) << d->being_deleted_message_id << " " << message_id << " " << source; d->being_deleted_message_id = message_id; bool need_get_history = false; if (!only_from_memory) { LOG(INFO) << "Deleting " << message_full_id << " from " << source; delete_message_from_database(d, message_id, m, is_permanently_deleted, source); delete_active_live_location(d->dialog_id, m); remove_message_file_sources(d->dialog_id, m); if (message_id == d->last_message_id) { auto it = d->ordered_messages.get_const_iterator(message_id); CHECK(*it != nullptr); CHECK((*it)->get_message_id() == message_id); --it; if (*it != nullptr) { set_dialog_last_message_id(d, (*it)->get_message_id(), "do_delete_message"); } else { need_get_history = true; set_dialog_last_message_id(d, MessageId(), "do_delete_message"); d->delete_last_message_date = m->date; d->deleted_last_message_id = message_id; d->is_last_message_deleted_locally = Slice(source) == Slice(DELETE_MESSAGE_USER_REQUEST_SOURCE); on_dialog_updated(d->dialog_id, "do delete last message"); } *need_update_dialog_pos = true; } if (message_id == d->last_database_message_id) { auto it = d->ordered_messages.get_const_iterator(message_id); CHECK(*it != nullptr); CHECK((*it)->get_message_id() == message_id); do { --it; } while (*it != nullptr && (*it)->get_message_id().is_yet_unsent()); if (*it != nullptr) { if ((*it)->get_message_id() < d->first_database_message_id && d->dialog_id.get_type() == DialogType::Channel) { // possible if messages was deleted from database, but not from memory after updateChannelTooLong set_dialog_last_database_message_id(d, MessageId(), "do_delete_message 1"); } else { set_dialog_last_database_message_id(d, (*it)->get_message_id(), "do_delete_message 2"); if (d->last_database_message_id < d->first_database_message_id) { LOG(ERROR) << "Last database " << d->last_database_message_id << " became less than first database " << d->first_database_message_id << " after deletion of " << message_full_id; set_dialog_first_database_message_id(d, d->last_database_message_id, "do_delete_message 2"); } } } else { if (d->first_database_message_id == d->last_database_message_id) { // database definitely has no more messages set_dialog_last_database_message_id(d, MessageId(), "do_delete_message 3"); } else { LOG(INFO) << "Need to get history to repair last_database_message_id in " << d->dialog_id; need_get_history = true; } } } if (d->last_database_message_id.is_valid()) { CHECK(d->first_database_message_id.is_valid()); } else if (d->first_database_message_id.is_valid()) { set_dialog_first_database_message_id(d, MessageId(), "do_delete_message"); } auto suffix_load_queries_it = dialog_suffix_load_queries_.find(d->dialog_id); if (suffix_load_queries_it != dialog_suffix_load_queries_.end() && message_id == suffix_load_queries_it->second->suffix_load_first_message_id_) { auto it = d->ordered_messages.get_const_iterator(message_id); CHECK(*it != nullptr); CHECK((*it)->get_message_id() == message_id); --it; if (*it != nullptr) { suffix_load_queries_it->second->suffix_load_first_message_id_ = (*it)->get_message_id(); } else { suffix_load_queries_it->second->suffix_load_first_message_id_ = MessageId(); suffix_load_queries_it->second->suffix_load_done_ = false; } } } else { auto suffix_load_queries_it = dialog_suffix_load_queries_.find(d->dialog_id); if (suffix_load_queries_it != dialog_suffix_load_queries_.end() && message_id >= suffix_load_queries_it->second->suffix_load_first_message_id_) { suffix_load_queries_it->second->suffix_load_first_message_id_ = MessageId(); suffix_load_queries_it->second->suffix_load_done_ = false; } } auto result = std::move(d->messages[message_id]); CHECK(m == result.get()); d->messages.erase(message_id); static_cast(result.get())->remove(); if (!td_->auth_manager_->is_bot()) { d->ordered_messages.erase(message_id, only_from_memory); } d->being_deleted_message_id = MessageId(); if (need_get_history) { send_closure_later(actor_id(this), &MessagesManager::load_last_dialog_message_later, d->dialog_id); } on_message_deleted(d, result.get(), is_permanently_deleted, source); return result; } void MessagesManager::on_message_deleted_from_database(Dialog *d, const Message *m, const char *source) { CHECK(d != nullptr); if (m == nullptr || m->message_id.is_scheduled() || td_->auth_manager_->is_bot()) { return; } auto message_id = m->message_id; if (d->reply_markup_message_id == message_id) { set_dialog_reply_markup(d, MessageId()); } // if last_read_inbox_message_id is not known, we can't be sure whether unread_count should be decreased or not if (has_incoming_notification(d->dialog_id, m) && message_id > d->last_read_inbox_message_id && d->is_last_read_inbox_message_id_inited) { int32 server_unread_count = d->server_unread_count; int32 local_unread_count = d->local_unread_count; int32 &unread_count = message_id.is_server() ? server_unread_count : local_unread_count; if (unread_count == 0) { if (need_unread_counter(d->order)) { LOG(ERROR) << "Unread count became negative in " << d->dialog_id << " after deletion of " << message_id << ". Last read is " << d->last_read_inbox_message_id; } } else { unread_count--; set_dialog_last_read_inbox_message_id(d, MessageId::min(), server_unread_count, local_unread_count, false, source); } } if (m->contains_unread_mention) { if (d->unread_mention_count == 0) { if (is_dialog_inited(d)) { LOG(ERROR) << "Unread mention count became negative in " << d->dialog_id << " after deletion of " << message_id; } } else { set_dialog_unread_mention_count(d, d->unread_mention_count - 1); send_update_chat_unread_mention_count(d); } } if (has_unread_message_reactions(d->dialog_id, m)) { if (d->unread_reaction_count == 0) { if (is_dialog_inited(d)) { LOG(ERROR) << "Unread reaction count became negative in " << d->dialog_id << " after deletion of " << message_id; } } else { set_dialog_unread_reaction_count(d, d->unread_reaction_count - 1); send_update_chat_unread_reaction_count(d, source); } } update_message_count_by_index(d, -1, m); update_reply_count_by_message(d, -1, m); } void MessagesManager::on_message_deleted(Dialog *d, Message *m, bool is_permanently_deleted, const char *source) { // also called for unloaded messages, but not for scheduled messages CHECK(m->message_id.is_valid()); if (m->message_id.is_yet_unsent() && !m->message_id.is_scheduled() && m->top_thread_message_id.is_valid() && !td_->auth_manager_->is_bot()) { auto it = yet_unsent_thread_message_ids_.find({d->dialog_id, m->top_thread_message_id}); CHECK(it != yet_unsent_thread_message_ids_.end()); auto is_deleted = it->second.erase(m->message_id) > 0; CHECK(is_deleted); if (it->second.empty()) { yet_unsent_thread_message_ids_.erase(it); } } if (d->open_count > 0) { auto it = dialog_viewed_messages_.find(d->dialog_id); if (it != dialog_viewed_messages_.end()) { auto &info = it->second; CHECK(info != nullptr); auto message_it = info->message_id_to_view_id.find(m->message_id); if (message_it != info->message_id_to_view_id.end()) { info->recently_viewed_messages.erase(message_it->second); info->message_id_to_view_id.erase(message_it); } } } cancel_send_deleted_message(d->dialog_id, m, is_permanently_deleted); auto dialog_type = d->dialog_id.get_type(); switch (dialog_type) { case DialogType::User: case DialogType::Chat: if (m->message_id.is_server()) { message_id_to_dialog_id_.erase(m->message_id); } break; case DialogType::Channel: if (m->message_id.is_server() && !td_->auth_manager_->is_bot()) { td_->contacts_manager_->unregister_message_users({d->dialog_id, m->message_id}, get_message_user_ids(m)); td_->contacts_manager_->unregister_message_channels({d->dialog_id, m->message_id}, get_message_channel_ids(m)); } break; case DialogType::SecretChat: // nothing to do break; case DialogType::None: default: UNREACHABLE(); } ttl_unregister_message(d->dialog_id, m, source); ttl_period_unregister_message(d->dialog_id, m); delete_bot_command_message_id(d->dialog_id, m->message_id); unregister_message_content(td_, m->content.get(), {d->dialog_id, m->message_id}, "on_message_deleted"); unregister_message_reply(d->dialog_id, m); if (m->notification_id.is_valid()) { delete_notification_id_to_message_id_correspondence(d->notification_info.get(), m->notification_id, m->message_id); } if (m->message_id.is_yet_unsent() || dialog_type == DialogType::SecretChat) { delete_random_id_to_message_id_correspondence(d, m->random_id, m->message_id); } if (m->is_topic_message) { td_->forum_topic_manager_->on_topic_message_count_changed(d->dialog_id, m->top_thread_message_id, -1); } added_message_count_--; } bool MessagesManager::is_deleted_message(const Dialog *d, MessageId message_id) { if (message_id.is_scheduled() && message_id.is_valid_scheduled() && message_id.is_scheduled_server()) { return d->scheduled_messages != nullptr && d->scheduled_messages->deleted_scheduled_server_message_ids_.count( message_id.get_scheduled_server_message_id()) > 0; } else { return d->deleted_message_ids.count(message_id) > 0; } } unique_ptr MessagesManager::do_delete_scheduled_message(Dialog *d, MessageId message_id, bool is_permanently_deleted, const char *source) { CHECK(d != nullptr); LOG_CHECK(message_id.is_valid_scheduled()) << d->dialog_id << ' ' << message_id << ' ' << source; if (d->scheduled_messages == nullptr) { auto message = get_message_force(d, message_id, "do_delete_scheduled_message"); if (message == nullptr) { // currently there may be a race between add_message_to_database and get_message_force, // so delete a message from database just in case delete_message_from_database(d, message_id, nullptr, is_permanently_deleted, source); return nullptr; } CHECK(d->scheduled_messages != nullptr); } auto it = d->scheduled_messages->scheduled_messages_.find(message_id); if (it == d->scheduled_messages->scheduled_messages_.end()) { LOG(INFO) << message_id << " is not found in " << d->dialog_id << " to be deleted from " << source; auto message = get_message_force(d, message_id, "do_delete_scheduled_message"); if (message == nullptr) { // currently there may be a race between add_message_to_database and get_message_force, // so delete a message from database just in case delete_message_from_database(d, message_id, nullptr, is_permanently_deleted, source); return nullptr; } message_id = message->message_id; it = d->scheduled_messages->scheduled_messages_.find(message_id); CHECK(it != d->scheduled_messages->scheduled_messages_.end()); } const Message *m = it->second.get(); CHECK(m->message_id == message_id); LOG(INFO) << "Deleting " << MessageFullId{d->dialog_id, message_id} << " from " << source; delete_message_from_database(d, message_id, m, is_permanently_deleted, source); remove_message_file_sources(d->dialog_id, m); it = d->scheduled_messages->scheduled_messages_.find(message_id); auto result = std::move(it->second); d->scheduled_messages->scheduled_messages_.erase(it); CHECK(m == result.get()); if (message_id.is_scheduled_server()) { size_t erased_count = d->scheduled_messages->scheduled_message_date_.erase(message_id.get_scheduled_server_message_id()); CHECK(erased_count != 0); } cancel_send_deleted_message(d->dialog_id, result.get(), is_permanently_deleted); unregister_message_content(td_, m->content.get(), {d->dialog_id, message_id}, "do_delete_scheduled_message"); unregister_message_reply(d->dialog_id, m); if (message_id.is_yet_unsent()) { delete_random_id_to_message_id_correspondence(d, m->random_id, m->message_id); } if (m->is_topic_message) { td_->forum_topic_manager_->on_topic_message_count_changed(d->dialog_id, m->top_thread_message_id, -1); } return result; } bool MessagesManager::have_dialog(DialogId dialog_id) const { return dialogs_.count(dialog_id) > 0; } void MessagesManager::load_dialogs(vector dialog_ids, Promise> &&promise) { LOG(INFO) << "Load chats " << format::as_array(dialog_ids); Dependencies dependencies; for (auto dialog_id : dialog_ids) { if (!have_dialog(dialog_id)) { dependencies.add_dialog_dependencies(dialog_id); } } dependencies.resolve_force(td_, "load_dialogs", true); td::remove_if(dialog_ids, [this](DialogId dialog_id) { return !have_dialog_info(dialog_id); }); for (auto dialog_id : dialog_ids) { force_create_dialog(dialog_id, "load_dialogs"); } LOG(INFO) << "Loaded chats " << format::as_array(dialog_ids); promise.set_value(std::move(dialog_ids)); } bool MessagesManager::load_dialog(DialogId dialog_id, int left_tries, Promise &&promise) { if (!dialog_id.is_valid()) { promise.set_error(Status::Error(400, "Invalid chat identifier specified")); return false; } if (!have_dialog_force(dialog_id, "load_dialog")) { // TODO remove _force if (G()->use_message_database()) { // TODO load dialog from database, DialogLoader // send_closure_later(actor_id(this), &MessagesManager::load_dialog_from_database, dialog_id, // std::move(promise)); // return false; } if (td_->auth_manager_->is_bot()) { switch (dialog_id.get_type()) { case DialogType::User: { auto user_id = dialog_id.get_user_id(); auto have_user = td_->contacts_manager_->get_user(user_id, left_tries, std::move(promise)); if (!have_user) { return false; } break; } case DialogType::Chat: { auto have_chat = td_->contacts_manager_->get_chat(dialog_id.get_chat_id(), left_tries, std::move(promise)); if (!have_chat) { return false; } break; } case DialogType::Channel: { auto have_channel = td_->contacts_manager_->get_channel(dialog_id.get_channel_id(), left_tries, std::move(promise)); if (!have_channel) { return false; } break; } case DialogType::SecretChat: promise.set_error(Status::Error(400, "Chat not found")); return false; case DialogType::None: default: UNREACHABLE(); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return false; } add_dialog(dialog_id, "load_dialog"); return true; } promise.set_error(Status::Error(400, "Chat not found")); return false; } promise.set_value(Unit()); return true; } Result MessagesManager::get_dialog_list_last_date(DialogListId dialog_list_id) { CHECK(!td_->auth_manager_->is_bot()); auto *list_ptr = get_dialog_list(dialog_list_id); if (list_ptr == nullptr) { return Status::Error(400, "Chat list not found"); } return list_ptr->list_last_dialog_date_; } vector MessagesManager::get_dialogs(DialogListId dialog_list_id, DialogDate offset, int32 limit, bool exact_limit, bool force, Promise &&promise) { CHECK(!td_->auth_manager_->is_bot()); auto *list_ptr = get_dialog_list(dialog_list_id); if (list_ptr == nullptr) { promise.set_error(Status::Error(400, "Chat list not found")); return {}; } auto &list = *list_ptr; LOG(INFO) << "Get chats in " << dialog_list_id << " with offset " << offset << " and limit " << limit << ". last_dialog_date = " << list.list_last_dialog_date_ << ", last_pinned_dialog_date_ = " << list.last_pinned_dialog_date_ << ", are_pinned_dialogs_inited_ = " << list.are_pinned_dialogs_inited_; if (limit <= 0) { promise.set_error(Status::Error(400, "Parameter limit must be positive")); return {}; } vector result; if (dialog_list_id == DialogListId(FolderId::main()) && sponsored_dialog_id_.is_valid()) { auto d = get_dialog(sponsored_dialog_id_); CHECK(d != nullptr); if (is_dialog_sponsored(d)) { DialogDate date(get_dialog_private_order(&list, d), d->dialog_id); if (offset < date) { result.push_back(sponsored_dialog_id_); offset = date; limit--; } } } if (!list.are_pinned_dialogs_inited_) { if (limit == 0 || force) { promise.set_value(Unit()); return result; } else { if (dialog_list_id.is_folder()) { auto &folder = *get_dialog_folder(dialog_list_id.get_folder_id()); if (folder.last_loaded_database_dialog_date_ == folder.last_database_server_dialog_date_ && folder.folder_last_dialog_date_ != MAX_DIALOG_DATE) { load_dialog_list(list, limit, std::move(promise)); return {}; } } reload_pinned_dialogs(dialog_list_id, std::move(promise)); return {}; } } if (dialog_list_id.is_filter()) { auto dialog_filter_id = dialog_list_id.get_filter_id(); vector input_dialog_ids; for (const auto &input_dialog_id : td_->dialog_filter_manager_->get_pinned_input_dialog_ids(dialog_filter_id)) { auto dialog_id = input_dialog_id.get_dialog_id(); if (!have_dialog_force(dialog_id, "get_dialogs")) { if (dialog_id.get_type() == DialogType::SecretChat) { if (have_dialog_info_force(dialog_id, "get_dialogs")) { force_create_dialog(dialog_id, "get_dialogs"); } } else { input_dialog_ids.push_back(input_dialog_id); } } } if (!input_dialog_ids.empty()) { if (limit == 0 || force) { promise.set_value(Unit()); return result; } else { td_->dialog_filter_manager_->load_dialog_filter_dialogs(dialog_filter_id, std::move(input_dialog_ids), std::move(promise)); return {}; } } } if (!list.pinned_dialogs_.empty() && offset < list.pinned_dialogs_.back() && limit > 0) { bool need_reload_pinned_dialogs = false; bool need_remove_unknown_secret_chats = false; for (auto &pinned_dialog : list.pinned_dialogs_) { if (offset < pinned_dialog) { auto dialog_id = pinned_dialog.get_dialog_id(); auto d = get_dialog_force(dialog_id, "get_dialogs"); if (d == nullptr) { LOG(ERROR) << "Failed to load pinned " << dialog_id << " from " << dialog_list_id; if (dialog_id.get_type() != DialogType::SecretChat) { need_reload_pinned_dialogs = true; } else { need_remove_unknown_secret_chats = true; } continue; } if (d->order == DEFAULT_ORDER) { LOG(INFO) << "Loaded pinned " << dialog_id << " with default order in " << dialog_list_id; continue; } result.push_back(dialog_id); offset = pinned_dialog; limit--; if (limit == 0) { break; } } } if (need_reload_pinned_dialogs) { reload_pinned_dialogs(dialog_list_id, Auto()); } if (need_remove_unknown_secret_chats) { td::remove_if(list.pinned_dialogs_, [this, &list](const DialogDate &dialog_date) { auto dialog_id = dialog_date.get_dialog_id(); if (dialog_id.get_type() == DialogType::SecretChat && !have_dialog_force(dialog_id, "get_dialogs 2")) { list.pinned_dialog_id_orders_.erase(dialog_id); return true; } return false; }); save_pinned_folder_dialog_ids(list); } } update_list_last_pinned_dialog_date(list); vector folders; vector::const_iterator> folder_iterators; for (auto folder_id : get_dialog_list_folder_ids(list)) { folders.push_back(get_dialog_folder(folder_id)); folder_iterators.push_back(folders.back()->ordered_dialogs_.upper_bound(offset)); } while (limit > 0) { size_t best_pos = 0; DialogDate best_dialog_date = MAX_DIALOG_DATE; for (size_t i = 0; i < folders.size(); i++) { while (folder_iterators[i] != folders[i]->ordered_dialogs_.end() && *folder_iterators[i] <= list.list_last_dialog_date_ && (!is_dialog_in_list(get_dialog(folder_iterators[i]->get_dialog_id()), dialog_list_id) || get_dialog_pinned_order(&list, folder_iterators[i]->get_dialog_id()) != DEFAULT_ORDER)) { ++folder_iterators[i]; } if (folder_iterators[i] != folders[i]->ordered_dialogs_.end() && *folder_iterators[i] <= list.list_last_dialog_date_ && *folder_iterators[i] < best_dialog_date) { best_pos = i; best_dialog_date = *folder_iterators[i]; } } if (best_dialog_date == MAX_DIALOG_DATE || best_dialog_date.get_order() == DEFAULT_ORDER) { break; } limit--; result.push_back(folder_iterators[best_pos]->get_dialog_id()); ++folder_iterators[best_pos]; } if ((!result.empty() && (!exact_limit || limit == 0)) || force || list.list_last_dialog_date_ == MAX_DIALOG_DATE) { if (limit > 0 && list.list_last_dialog_date_ != MAX_DIALOG_DATE) { LOG(INFO) << "Preload next " << limit << " chats in " << dialog_list_id; load_dialog_list(list, limit, Promise()); } promise.set_value(Unit()); return result; } else { if (!result.empty()) { LOG(INFO) << "Have only " << result.size() << " chats, but " << limit << " chats more are needed"; } load_dialog_list(list, limit, std::move(promise)); return {}; } } void MessagesManager::load_dialog_list(DialogList &list, int32 limit, Promise &&promise) { CHECK(!td_->auth_manager_->is_bot()); if (limit > MAX_GET_DIALOGS + 2) { limit = MAX_GET_DIALOGS + 2; } bool is_request_sent = false; for (auto folder_id : get_dialog_list_folder_ids(list)) { const auto &folder = *get_dialog_folder(folder_id); if (folder.folder_last_dialog_date_ != MAX_DIALOG_DATE) { load_folder_dialog_list(folder_id, limit, false); is_request_sent = true; } } if (is_request_sent) { LOG(INFO) << "Wait for loading of " << limit << " chats in " << list.dialog_list_id; list.load_list_queries_.push_back(std::move(promise)); } else { LOG(ERROR) << "There is nothing to load for " << list.dialog_list_id << " with folders " << get_dialog_list_folder_ids(list); promise.set_error(Status::Error(404, "Not Found")); } } void MessagesManager::load_folder_dialog_list(FolderId folder_id, int32 limit, bool only_local) { if (G()->close_flag()) { return; } CHECK(!td_->auth_manager_->is_bot()); auto &folder = *get_dialog_folder(folder_id); if (folder.folder_last_dialog_date_ == MAX_DIALOG_DATE) { return; } bool use_database = G()->use_message_database() && folder.last_loaded_database_dialog_date_ < folder.last_database_server_dialog_date_; if (only_local && !use_database) { return; } auto &multipromise = folder.load_folder_dialog_list_multipromise_; if (multipromise.promise_count() != 0) { // queries have already been sent, just wait for the result LOG(INFO) << "Skip loading of dialog list in " << folder_id << " with limit " << limit << ", because it is already being loaded"; if (use_database && folder.load_dialog_list_limit_max_ != 0) { folder.load_dialog_list_limit_max_ = max(folder.load_dialog_list_limit_max_, limit); } return; } LOG(INFO) << "Load chat list in " << folder_id << " with limit " << limit; multipromise.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), folder_id](Result result) { send_closure_later(actor_id, &MessagesManager::on_load_folder_dialog_list, folder_id, std::move(result)); })); bool is_query_sent = false; if (use_database) { load_folder_dialog_list_from_database(folder_id, limit, multipromise.get_promise()); is_query_sent = true; } else { LOG(INFO) << "Get chats from " << folder.last_server_dialog_date_; multipromise.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), folder_id](Result result) { if (result.is_ok()) { send_closure(actor_id, &MessagesManager::recalc_unread_count, DialogListId(folder_id), -1, true); } })); auto lock = multipromise.get_promise(); reload_pinned_dialogs(DialogListId(folder_id), multipromise.get_promise()); if (folder.folder_last_dialog_date_ == folder.last_server_dialog_date_) { td_->create_handler(multipromise.get_promise()) ->send(folder_id, folder.last_server_dialog_date_.get_date(), folder.last_server_dialog_date_.get_message_id().get_next_server_message_id().get_server_message_id(), folder.last_server_dialog_date_.get_dialog_id(), int32{MAX_GET_DIALOGS}); is_query_sent = true; } if (folder_id == FolderId::main() && folder.last_server_dialog_date_ == MIN_DIALOG_DATE) { // do not pass promise to not wait for drafts before showing the chat list load_all_draft_messages(td_); } lock.set_value(Unit()); } CHECK(is_query_sent); } void MessagesManager::on_load_folder_dialog_list(FolderId folder_id, Result &&result) { if (G()->close_flag()) { return; } CHECK(!td_->auth_manager_->is_bot()); const auto &folder = *get_dialog_folder(folder_id); if (result.is_ok()) { LOG(INFO) << "Successfully loaded chats in " << folder_id; if (folder.last_server_dialog_date_ == MAX_DIALOG_DATE) { return; } bool need_new_get_dialog_list = false; for (const auto &list_it : dialog_lists_) { auto &list = list_it.second; if (!list.load_list_queries_.empty() && has_dialogs_from_folder(list, folder)) { LOG(INFO) << "Need to load more chats in " << folder_id << " for " << list_it.first; need_new_get_dialog_list = true; } } if (need_new_get_dialog_list) { load_folder_dialog_list(folder_id, int32{MAX_GET_DIALOGS}, false); } return; } LOG(WARNING) << "Failed to load chats in " << folder_id << ": " << result.error(); vector> promises; for (auto &list_it : dialog_lists_) { auto &list = list_it.second; if (!list.load_list_queries_.empty() && has_dialogs_from_folder(list, folder)) { append(promises, std::move(list.load_list_queries_)); list.load_list_queries_.clear(); } } fail_promises(promises, result.move_as_error()); } void MessagesManager::load_folder_dialog_list_from_database(FolderId folder_id, int32 limit, Promise &&promise) { CHECK(!td_->auth_manager_->is_bot()); auto &folder = *get_dialog_folder(folder_id); LOG(INFO) << "Load " << limit << " chats in " << folder_id << " from database from " << folder.last_loaded_database_dialog_date_ << ", last database server dialog date = " << folder.last_database_server_dialog_date_; CHECK(folder.load_dialog_list_limit_max_ == 0); folder.load_dialog_list_limit_max_ = limit; G()->td_db()->get_dialog_db_async()->get_dialogs( folder_id, folder.last_loaded_database_dialog_date_.get_order(), folder.last_loaded_database_dialog_date_.get_dialog_id(), limit, PromiseCreator::lambda([actor_id = actor_id(this), folder_id, limit, promise = std::move(promise)](DialogDbGetDialogsResult result) mutable { send_closure(actor_id, &MessagesManager::on_get_dialogs_from_database, folder_id, limit, std::move(result), std::move(promise)); })); } void MessagesManager::on_get_dialogs_from_database(FolderId folder_id, int32 limit, DialogDbGetDialogsResult &&dialogs, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); CHECK(!td_->auth_manager_->is_bot()); auto &folder = *get_dialog_folder(folder_id); LOG(INFO) << "Receive " << dialogs.dialogs.size() << " from expected " << limit << " chats in " << folder_id << " in from database with next order " << dialogs.next_order << " and next " << dialogs.next_dialog_id; int32 new_get_dialogs_limit = 0; int32 have_more_dialogs_in_database = (limit == static_cast(dialogs.dialogs.size())); if (have_more_dialogs_in_database && limit < folder.load_dialog_list_limit_max_) { new_get_dialogs_limit = folder.load_dialog_list_limit_max_ - limit; } folder.load_dialog_list_limit_max_ = 0; size_t dialogs_skipped = 0; for (auto &dialog : dialogs.dialogs) { Dialog *d = on_load_dialog_from_database(DialogId(), std::move(dialog), "on_get_dialogs_from_database"); if (d == nullptr) { dialogs_skipped++; continue; } if (d->folder_id != folder_id) { LOG(INFO) << "Skip " << d->dialog_id << " received from database, because it is in " << d->folder_id << " instead of " << folder_id; dialogs_skipped++; continue; } LOG(INFO) << "Loaded from database " << d->dialog_id << " with order " << d->order; } DialogDate max_dialog_date(dialogs.next_order, dialogs.next_dialog_id); if (!have_more_dialogs_in_database) { folder.last_loaded_database_dialog_date_ = MAX_DIALOG_DATE; LOG(INFO) << "Set last loaded database dialog date to " << folder.last_loaded_database_dialog_date_; folder.last_server_dialog_date_ = max(folder.last_server_dialog_date_, folder.last_database_server_dialog_date_); LOG(INFO) << "Set last server dialog date to " << folder.last_server_dialog_date_; update_last_dialog_date(folder_id); } else if (folder.last_loaded_database_dialog_date_ < max_dialog_date) { folder.last_loaded_database_dialog_date_ = min(max_dialog_date, folder.last_database_server_dialog_date_); LOG(INFO) << "Set last loaded database dialog date to " << folder.last_loaded_database_dialog_date_; folder.last_server_dialog_date_ = max(folder.last_server_dialog_date_, folder.last_loaded_database_dialog_date_); LOG(INFO) << "Set last server dialog date to " << folder.last_server_dialog_date_; update_last_dialog_date(folder_id); for (const auto &list_it : dialog_lists_) { auto &list = list_it.second; if (!list.load_list_queries_.empty() && has_dialogs_from_folder(list, folder) && new_get_dialogs_limit < limit) { new_get_dialogs_limit = limit; } } } else { LOG(ERROR) << "Last loaded database dialog date didn't increased, skipped " << dialogs_skipped << " chats out of " << dialogs.dialogs.size(); } if (!(folder.last_loaded_database_dialog_date_ < folder.last_database_server_dialog_date_)) { // have_more_dialogs_in_database = false; new_get_dialogs_limit = 0; } if (new_get_dialogs_limit == 0) { preload_folder_dialog_list_timeout_.add_timeout_in(folder_id.get(), 0.2); promise.set_value(Unit()); } else { load_folder_dialog_list_from_database(folder_id, new_get_dialogs_limit, std::move(promise)); } } void MessagesManager::preload_folder_dialog_list(FolderId folder_id) { if (G()->close_flag()) { LOG(INFO) << "Skip chat list preload in " << folder_id << " because of closing"; return; } CHECK(!td_->auth_manager_->is_bot()); auto &folder = *get_dialog_folder(folder_id); CHECK(G()->use_message_database()); if (folder.load_folder_dialog_list_multipromise_.promise_count() != 0) { LOG(INFO) << "Skip chat list preload in " << folder_id << ", because there is a pending load chat list request"; return; } if (folder.last_loaded_database_dialog_date_ < folder.last_database_server_dialog_date_) { // if there are some dialogs in database, preload some of them load_folder_dialog_list(folder_id, 20, true); } else if (folder.folder_last_dialog_date_ != MAX_DIALOG_DATE) { // otherwise load more dialogs from the server load_folder_dialog_list(folder_id, MAX_GET_DIALOGS, false); } else { recalc_unread_count(DialogListId(folder_id), -1, false); } } void MessagesManager::get_dialogs_from_list(DialogListId dialog_list_id, int32 limit, Promise> &&promise) { CHECK(!td_->auth_manager_->is_bot()); if (get_dialog_list(dialog_list_id) == nullptr) { return promise.set_error(Status::Error(400, "Chat list not found")); } if (limit <= 0) { return promise.set_error(Status::Error(400, "Parameter limit must be positive")); } auto task_id = ++current_get_dialogs_task_id_; auto &task = get_dialogs_tasks_[task_id]; task.dialog_list_id = dialog_list_id; task.retry_count = 5; task.limit = limit; task.promise = std::move(promise); get_dialogs_from_list_impl(task_id); } void MessagesManager::get_dialogs_from_list_impl(int64 task_id) { auto task_it = get_dialogs_tasks_.find(task_id); CHECK(task_it != get_dialogs_tasks_.end()); auto &task = task_it->second; auto promise = PromiseCreator::lambda([actor_id = actor_id(this), task_id](Result &&result) { // on_get_dialogs_from_list can delete get_dialogs_tasks_[task_id], so it must be called later send_closure_later(actor_id, &MessagesManager::on_get_dialogs_from_list, task_id, std::move(result)); }); auto dialog_ids = get_dialogs(task.dialog_list_id, MIN_DIALOG_DATE, task.limit, true, false, std::move(promise)); auto &list = *get_dialog_list(task.dialog_list_id); auto total_count = get_dialog_total_count(list); LOG(INFO) << "Receive " << dialog_ids.size() << " chats instead of " << task.limit << " out of " << total_count << " in " << task.dialog_list_id; CHECK(dialog_ids.size() <= static_cast(total_count)); CHECK(dialog_ids.size() <= static_cast(task.limit)); if (dialog_ids.size() == static_cast(min(total_count, task.limit)) || list.list_last_dialog_date_ == MAX_DIALOG_DATE || task.retry_count == 0) { auto task_promise = std::move(task.promise); get_dialogs_tasks_.erase(task_it); if (!task_promise) { dialog_ids.clear(); } return task_promise.set_value(get_chats_object(total_count, dialog_ids, "get_dialogs_from_list_impl")); } // nor the limit, nor the end of the list were reached; wait for the promise } void MessagesManager::on_get_dialogs_from_list(int64 task_id, Result &&result) { auto task_it = get_dialogs_tasks_.find(task_id); if (task_it == get_dialogs_tasks_.end()) { // the task has already been completed LOG(INFO) << "Chat list load task " << task_id << " has already been completed"; return; } auto &task = task_it->second; if (result.is_error()) { LOG(INFO) << "Chat list load task " << task_id << " failed with the error " << result.error(); auto task_promise = std::move(task.promise); get_dialogs_tasks_.erase(task_it); return task_promise.set_error(result.move_as_error()); } auto list_ptr = get_dialog_list(task.dialog_list_id); CHECK(list_ptr != nullptr); auto &list = *list_ptr; if (task.last_dialog_date == list.list_last_dialog_date_) { // no new chats were loaded task.retry_count--; } else { CHECK(task.last_dialog_date < list.list_last_dialog_date_); task.last_dialog_date = list.list_last_dialog_date_; task.retry_count = 5; } get_dialogs_from_list_impl(task_id); } void MessagesManager::mark_dialog_as_read(Dialog *d) { if (is_forum_channel(d->dialog_id)) { // TODO read forum topics } if (d->server_unread_count + d->local_unread_count > 0 && d->last_message_id.is_valid()) { auto it = d->ordered_messages.get_const_iterator(d->last_message_id); while (*it != nullptr) { auto message_id = (*it)->get_message_id(); if (message_id.is_server() || message_id.is_local()) { read_dialog_inbox(d, message_id); break; } --it; } if (*it == nullptr) { // if can't find the last message, then d->last_new_message_id is the best guess read_dialog_inbox(d, d->last_new_message_id); } } if (d->is_marked_as_unread) { set_dialog_is_marked_as_unread(d, false); } } void MessagesManager::read_all_dialogs_from_list(DialogListId dialog_list_id, Promise &&promise, bool is_recursive) { TRY_STATUS_PROMISE(promise, G()->close_status()); if (get_dialog_list(dialog_list_id) == nullptr) { return promise.set_error(Status::Error(400, "Chat list not found")); } dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr &dialog) { Dialog *d = dialog.get(); if (is_dialog_in_list(d, dialog_list_id)) { mark_dialog_as_read(d); } }); if (is_recursive) { return promise.set_value(Unit()); } auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_list_id, promise = std::move(promise)]( Result> &&) mutable { // it is expected that loading of so many chats will fail, so just ignore the error and result itself send_closure(actor_id, &MessagesManager::read_all_dialogs_from_list, dialog_list_id, std::move(promise), true); }); get_dialogs_from_list(dialog_list_id, 10000, std::move(query_promise)); } vector MessagesManager::get_pinned_dialog_ids(DialogListId dialog_list_id) const { CHECK(!td_->auth_manager_->is_bot()); if (dialog_list_id.is_filter()) { return td_->dialog_filter_manager_->get_pinned_dialog_ids(dialog_list_id.get_filter_id()); } auto *list = get_dialog_list(dialog_list_id); if (list == nullptr || !list->are_pinned_dialogs_inited_) { return {}; } return transform(list->pinned_dialogs_, [](auto &pinned_dialog) { return pinned_dialog.get_dialog_id(); }); } void MessagesManager::reload_pinned_dialogs(DialogListId dialog_list_id, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); CHECK(!td_->auth_manager_->is_bot()); if (dialog_list_id.is_folder()) { td_->create_handler(std::move(promise))->send(dialog_list_id.get_folder_id()); } else if (dialog_list_id.is_filter()) { td_->dialog_filter_manager_->schedule_reload_dialog_filters(std::move(promise)); } } vector MessagesManager::search_public_dialogs(const string &query, Promise &&promise) { LOG(INFO) << "Search public chats with query = \"" << query << '"'; auto query_length = utf8_length(query); if (query_length < MIN_SEARCH_PUBLIC_DIALOG_PREFIX_LEN || (query_length == MIN_SEARCH_PUBLIC_DIALOG_PREFIX_LEN && query[0] == '@')) { string username = clean_username(query); if (username[0] == '@') { username = username.substr(1); } for (auto &short_username : get_valid_short_usernames()) { if (2 * username.size() > short_username.size() && begins_with(short_username, username)) { username = short_username.str(); auto resolved_username = resolved_usernames_.get(username); if (!resolved_username.dialog_id.is_valid()) { send_resolve_dialog_username_query(username, std::move(promise)); return {}; } if (resolved_username.expires_at < Time::now()) { send_resolve_dialog_username_query(username, Promise()); } auto dialog_id = resolved_username.dialog_id; force_create_dialog(dialog_id, "public dialogs search"); auto d = get_dialog(dialog_id); if (d == nullptr || d->order != DEFAULT_ORDER || (dialog_id.get_type() == DialogType::User && td_->contacts_manager_->is_user_contact(dialog_id.get_user_id()))) { continue; } promise.set_value(Unit()); return {dialog_id}; } } promise.set_value(Unit()); return {}; } auto it = found_public_dialogs_.find(query); if (it != found_public_dialogs_.end()) { promise.set_value(Unit()); return it->second; } send_search_public_dialogs_query(query, std::move(promise)); return {}; } void MessagesManager::send_search_public_dialogs_query(const string &query, Promise &&promise) { CHECK(!query.empty()); auto &promises = search_public_dialogs_queries_[query]; promises.push_back(std::move(promise)); if (promises.size() != 1) { // query has already been sent, just wait for the result return; } td_->create_handler()->send(query); } std::pair> MessagesManager::search_dialogs(const string &query, int32 limit, Promise &&promise) { LOG(INFO) << "Search chats with query \"" << query << "\" and limit " << limit; CHECK(!td_->auth_manager_->is_bot()); if (limit < 0) { promise.set_error(Status::Error(400, "Limit must be non-negative")); return {}; } if (query.empty()) { return recently_found_dialogs_.get_dialogs(limit, std::move(promise)); } auto result = dialogs_hints_.search(query, limit); vector dialog_ids; dialog_ids.reserve(result.second.size()); for (auto key : result.second) { dialog_ids.push_back(DialogId(-key)); } promise.set_value(Unit()); return {narrow_cast(result.first), std::move(dialog_ids)}; } std::pair> MessagesManager::search_recently_found_dialogs(const string &query, int32 limit, Promise &&promise) { auto result = recently_found_dialogs_.get_dialogs(query.empty() ? limit : 50, std::move(promise)); if (result.first == 0 || query.empty()) { return result; } Hints hints; int rating = 1; for (auto dialog_id : result.second) { hints.add(dialog_id.get(), td_->contacts_manager_->get_dialog_search_text(dialog_id)); hints.set_rating(dialog_id.get(), ++rating); } auto hints_result = hints.search(query, limit, false); return {narrow_cast(hints_result.first), transform(hints_result.second, [](int64 key) { return DialogId(key); })}; } std::pair> MessagesManager::get_recently_opened_dialogs(int32 limit, Promise &&promise) { CHECK(!td_->auth_manager_->is_bot()); return recently_opened_dialogs_.get_dialogs(limit, std::move(promise)); } vector MessagesManager::sort_dialogs_by_order(const vector &dialog_ids, int32 limit) const { CHECK(!td_->auth_manager_->is_bot()); auto fake_order = static_cast(dialog_ids.size()) + 1; auto dialog_dates = transform(dialog_ids, [this, &fake_order](DialogId dialog_id) { const Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); auto order = get_dialog_base_order(d); if (is_dialog_inited(d) || order != DEFAULT_ORDER) { return DialogDate(order, dialog_id); } // if the dialog is not inited yet, we need to assume that server knows better and the dialog needs to be returned return DialogDate(fake_order--, dialog_id); }); if (static_cast(limit) >= dialog_dates.size()) { std::sort(dialog_dates.begin(), dialog_dates.end()); } else { std::partial_sort(dialog_dates.begin(), dialog_dates.begin() + limit, dialog_dates.end()); dialog_dates.resize(limit, MAX_DIALOG_DATE); } while (!dialog_dates.empty() && dialog_dates.back().get_order() == DEFAULT_ORDER) { dialog_dates.pop_back(); } return transform(dialog_dates, [](auto dialog_date) { return dialog_date.get_dialog_id(); }); } vector MessagesManager::search_dialogs_on_server(const string &query, int32 limit, Promise &&promise) { LOG(INFO) << "Search chats on server with query \"" << query << "\" and limit " << limit; if (limit < 0) { promise.set_error(Status::Error(400, "Limit must be non-negative")); return {}; } if (limit > MAX_GET_DIALOGS) { limit = MAX_GET_DIALOGS; } if (query.empty()) { promise.set_value(Unit()); return {}; } auto it = found_on_server_dialogs_.find(query); if (it != found_on_server_dialogs_.end()) { promise.set_value(Unit()); return sort_dialogs_by_order(it->second, limit); } send_search_public_dialogs_query(query, std::move(promise)); return {}; } void MessagesManager::drop_common_dialogs_cache(UserId user_id) { auto it = found_common_dialogs_.find(user_id); if (it != found_common_dialogs_.end()) { it->second.is_outdated = true; } } std::pair> MessagesManager::get_common_dialogs(UserId user_id, DialogId offset_dialog_id, int32 limit, bool force, Promise &&promise) { auto r_input_user = td_->contacts_manager_->get_input_user(user_id); if (r_input_user.is_error()) { promise.set_error(r_input_user.move_as_error()); return {}; } if (user_id == td_->contacts_manager_->get_my_id()) { promise.set_error(Status::Error(400, "Can't get common chats with self")); return {}; } if (limit <= 0) { promise.set_error(Status::Error(400, "Parameter limit must be positive")); return {}; } if (limit > MAX_GET_DIALOGS) { limit = MAX_GET_DIALOGS; } int64 offset_chat_id = 0; switch (offset_dialog_id.get_type()) { case DialogType::Chat: offset_chat_id = offset_dialog_id.get_chat_id().get(); break; case DialogType::Channel: offset_chat_id = offset_dialog_id.get_channel_id().get(); break; case DialogType::None: if (offset_dialog_id == DialogId()) { break; } // fallthrough case DialogType::User: case DialogType::SecretChat: promise.set_error(Status::Error(400, "Wrong offset_chat_id")); return {}; default: UNREACHABLE(); break; } auto it = found_common_dialogs_.find(user_id); if (it != found_common_dialogs_.end() && !it->second.dialog_ids.empty()) { int32 total_count = it->second.total_count; vector &common_dialog_ids = it->second.dialog_ids; bool use_cache = (!it->second.is_outdated && it->second.receive_time >= Time::now() - 3600) || force || offset_chat_id != 0 || common_dialog_ids.size() >= static_cast(MAX_GET_DIALOGS); // use cache if it is up-to-date, or we required to use it or we can't update it if (use_cache) { auto offset_it = common_dialog_ids.begin(); if (offset_dialog_id != DialogId()) { offset_it = std::find(common_dialog_ids.begin(), common_dialog_ids.end(), offset_dialog_id); if (offset_it == common_dialog_ids.end()) { promise.set_error(Status::Error(400, "Wrong offset_chat_id")); return {}; } ++offset_it; } vector result; while (result.size() < static_cast(limit)) { if (offset_it == common_dialog_ids.end()) { break; } auto dialog_id = *offset_it++; if (dialog_id == DialogId()) { // end of the list promise.set_value(Unit()); return {total_count, std::move(result)}; } result.push_back(dialog_id); } if (result.size() == static_cast(limit) || force) { promise.set_value(Unit()); return {total_count, std::move(result)}; } } } td_->create_handler(std::move(promise)) ->send(user_id, r_input_user.move_as_ok(), offset_chat_id, MAX_GET_DIALOGS); return {}; } void MessagesManager::on_get_common_dialogs(UserId user_id, int64 offset_chat_id, vector> &&chats, int32 total_count) { CHECK(user_id.is_valid()); td_->contacts_manager_->on_update_user_common_chat_count(user_id, total_count); auto &common_dialogs = found_common_dialogs_[user_id]; if (common_dialogs.is_outdated && offset_chat_id == 0 && common_dialogs.dialog_ids.size() < static_cast(MAX_GET_DIALOGS)) { // drop outdated cache if possible common_dialogs = CommonDialogs(); } if (common_dialogs.receive_time == 0) { common_dialogs.receive_time = Time::now(); } common_dialogs.is_outdated = false; auto &result = common_dialogs.dialog_ids; if (!result.empty() && result.back() == DialogId()) { return; } bool is_last = chats.empty() && offset_chat_id == 0; for (auto &chat : chats) { auto dialog_id = ContactsManager::get_dialog_id(chat); if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive invalid " << to_string(chat); continue; } td_->contacts_manager_->on_get_chat(std::move(chat), "on_get_common_dialogs"); if (!td::contains(result, dialog_id)) { force_create_dialog(dialog_id, "get common dialogs"); result.push_back(dialog_id); } } if (result.size() >= static_cast(total_count) || is_last) { if (result.size() != static_cast(total_count)) { LOG(ERROR) << "Fix total count of common groups with " << user_id << " from " << total_count << " to " << result.size(); total_count = narrow_cast(result.size()); td_->contacts_manager_->on_update_user_common_chat_count(user_id, total_count); } result.emplace_back(); } common_dialogs.total_count = total_count; } void MessagesManager::block_message_sender_from_replies(MessageId message_id, bool need_delete_message, bool need_delete_all_messages, bool report_spam, Promise &&promise) { auto dialog_id = DialogId(ContactsManager::get_replies_bot_user_id()); Dialog *d = get_dialog_force(dialog_id, "block_message_sender_from_replies"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Not enough rights")); } auto *m = get_message_force(d, message_id, "block_message_sender_from_replies"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } if (m->is_outgoing || m->message_id.is_scheduled() || !m->message_id.is_server()) { return promise.set_error(Status::Error(400, "Wrong message specified")); } DialogId sender_dialog_id; if (m->forward_info != nullptr) { sender_dialog_id = m->forward_info->origin.get_sender(); } vector message_ids; if (need_delete_all_messages && sender_dialog_id.is_valid()) { message_ids = find_dialog_messages(d, [sender_dialog_id](const Message *m) { return !m->is_outgoing && m->forward_info != nullptr && m->forward_info->origin.get_sender() == sender_dialog_id; }); CHECK(td::contains(message_ids, message_id)); } else if (need_delete_message) { message_ids.push_back(message_id); } delete_dialog_messages(d, message_ids, false, DELETE_MESSAGE_USER_REQUEST_SOURCE); block_message_sender_from_replies_on_server(message_id, need_delete_message, need_delete_all_messages, report_spam, 0, std::move(promise)); } class MessagesManager::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 MessagesManager::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 MessagesManager::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); } bool MessagesManager::is_dialog_blocked(DialogId dialog_id) const { const Dialog *d = get_dialog(dialog_id); return d != nullptr && d->is_blocked; } void MessagesManager::get_blocked_dialogs(const td_api::object_ptr &block_list, int32 offset, int32 limit, Promise> &&promise) { if (offset < 0) { return promise.set_error(Status::Error(400, "Parameter offset must be non-negative")); } if (limit <= 0) { return promise.set_error(Status::Error(400, "Parameter limit must be positive")); } auto block_list_id = BlockListId(block_list); if (!block_list_id.is_valid()) { return promise.set_error(Status::Error(400, "Block list must be non-empty")); } td_->create_handler(std::move(promise))->send(block_list_id, offset, limit); } void MessagesManager::on_get_blocked_dialogs(int32 offset, int32 limit, int32 total_count, vector> &&blocked_peers, Promise> &&promise) { LOG(INFO) << "Receive " << blocked_peers.size() << " blocked chats from offset " << offset << " out of " << total_count; auto peers = transform(std::move(blocked_peers), [](tl_object_ptr &&blocked_peer) { return std::move(blocked_peer->peer_id_); }); auto dialog_ids = get_message_sender_dialog_ids(td_, std::move(peers)); if (!dialog_ids.empty() && offset + dialog_ids.size() > static_cast(total_count)) { LOG(ERROR) << "Fix total count of blocked chats from " << total_count << " to " << offset + dialog_ids.size(); total_count = offset + narrow_cast(dialog_ids.size()); } auto senders = transform(dialog_ids, [td = td_](DialogId dialog_id) { return get_message_sender_object(td, dialog_id, "on_get_blocked_dialogs"); }); promise.set_value(td_api::make_object(total_count, std::move(senders))); } DialogId MessagesManager::get_dialog_message_sender(MessageFullId message_full_id) { const auto *m = get_message_force(message_full_id, "get_dialog_message_sender"); if (m == nullptr) { return DialogId(); } return get_message_sender(m); } bool MessagesManager::have_message_force(MessageFullId message_full_id, const char *source) { return get_message_force(message_full_id, source) != nullptr; } bool MessagesManager::have_message_force(Dialog *d, MessageId message_id, const char *source) { return get_message_force(d, message_id, source) != nullptr; } MessagesManager::Message *MessagesManager::get_message(MessageFullId message_full_id) { Dialog *d = get_dialog(message_full_id.get_dialog_id()); if (d == nullptr) { return nullptr; } return get_message(d, message_full_id.get_message_id()); } const MessagesManager::Message *MessagesManager::get_message(MessageFullId message_full_id) const { const Dialog *d = get_dialog(message_full_id.get_dialog_id()); if (d == nullptr) { return nullptr; } return get_message(d, message_full_id.get_message_id()); } MessagesManager::Message *MessagesManager::get_message_force(MessageFullId message_full_id, const char *source) { Dialog *d = get_dialog_force(message_full_id.get_dialog_id(), source); if (d == nullptr) { return nullptr; } return get_message_force(d, message_full_id.get_message_id(), source); } MessageFullId MessagesManager::get_replied_message_id(DialogId dialog_id, const Message *m) { if (m->reply_to_story_full_id.is_valid()) { return {}; } auto message_full_id = get_message_content_replied_message_id(dialog_id, m->content.get()); if (message_full_id.get_message_id().is_valid()) { CHECK(m->replied_message_info.is_empty()); return message_full_id; } auto reply_message_full_id = m->replied_message_info.get_reply_message_full_id(dialog_id, true); if (reply_message_full_id.get_message_id() != MessageId()) { return reply_message_full_id; } if (m->top_thread_message_id.is_valid() && m->top_thread_message_id != m->message_id) { return {dialog_id, m->top_thread_message_id}; } return {}; } void MessagesManager::get_message_force_from_server(Dialog *d, MessageId message_id, Promise &&promise, tl_object_ptr input_message) { LOG(INFO) << "Get " << message_id << " in " << d->dialog_id << " using " << to_string(input_message); auto dialog_type = d->dialog_id.get_type(); auto m = get_message_force(d, message_id, "get_message_force_from_server"); if (m == nullptr && !is_deleted_message(d, message_id) && dialog_type != DialogType::SecretChat) { if (message_id.is_valid() && message_id.is_server()) { if (d->last_new_message_id != MessageId() && message_id > d->last_new_message_id && dialog_type != DialogType::Channel && !td_->auth_manager_->is_bot()) { // message will not be added to the dialog anyway return promise.set_value(Unit()); } return get_message_from_server({d->dialog_id, message_id}, std::move(promise), "get_message_force_from_server", std::move(input_message)); } if (message_id.is_valid_scheduled() && message_id.is_scheduled_server() && input_message == nullptr) { return get_message_from_server({d->dialog_id, message_id}, std::move(promise), "get_message_force_from_server"); } } promise.set_value(Unit()); } void MessagesManager::get_message(MessageFullId message_full_id, Promise &&promise) { Dialog *d = get_dialog_force(message_full_id.get_dialog_id(), "get_message"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } get_message_force_from_server(d, message_full_id.get_message_id(), std::move(promise)); } MessageFullId MessagesManager::get_replied_message(DialogId dialog_id, MessageId message_id, bool force, Promise &&promise) { LOG(INFO) << "Get replied message to " << message_id << " in " << dialog_id; Dialog *d = get_dialog_force(dialog_id, "get_replied_message"); if (d == nullptr) { promise.set_error(Status::Error(400, "Chat not found")); return MessageFullId(); } message_id = get_persistent_message_id(d, message_id); auto m = get_message_force(d, message_id, "get_replied_message"); if (m == nullptr) { if (force) { promise.set_value(Unit()); } else { get_message_force_from_server(d, message_id, std::move(promise)); } return MessageFullId(); } tl_object_ptr input_message; auto replied_message_id = get_replied_message_id(dialog_id, m); if (replied_message_id.get_dialog_id() != dialog_id) { dialog_id = replied_message_id.get_dialog_id(); if (!have_dialog_info_force(dialog_id, "get_replied_message")) { promise.set_value(Unit()); return {}; } if (!have_input_peer(dialog_id, AccessRights::Read)) { promise.set_value(Unit()); return {}; } force_create_dialog(dialog_id, "get_replied_message"); d = get_dialog_force(dialog_id, "get_replied_message"); if (d == nullptr) { promise.set_error(Status::Error(500, "Chat with replied message not found")); return {}; } } else if (m->message_id.is_valid() && m->message_id.is_server()) { input_message = make_tl_object(m->message_id.get_server_message_id().get()); } get_message_force_from_server(d, replied_message_id.get_message_id(), std::move(promise), std::move(input_message)); return replied_message_id; } Result MessagesManager::get_top_thread_message_full_id(DialogId dialog_id, const Message *m, bool allow_non_root) const { CHECK(m != nullptr); if (m->message_id.is_scheduled()) { return Status::Error(400, "Message is scheduled"); } if (dialog_id.get_type() != DialogType::Channel) { return Status::Error(400, "Chat can't have message threads"); } if (!m->reply_info.is_empty() && m->reply_info.is_comment_) { if (!is_visible_message_reply_info(dialog_id, m)) { return Status::Error(400, "Message has no comments"); } if (m->message_id.is_yet_unsent()) { return Status::Error(400, "Message is not sent yet"); } return MessageFullId{DialogId(m->reply_info.channel_id_), m->linked_top_thread_message_id}; } else { if (!m->top_thread_message_id.is_valid()) { return Status::Error(400, "Message has no thread"); } if (!allow_non_root && m->top_thread_message_id != m->message_id && !td_->contacts_manager_->get_channel_has_linked_channel(dialog_id.get_channel_id())) { return Status::Error(400, "Root message must be used to get the message thread"); } return MessageFullId{dialog_id, m->top_thread_message_id}; } } void MessagesManager::get_message_thread(DialogId dialog_id, MessageId message_id, Promise &&promise) { LOG(INFO) << "Get message thread from " << message_id << " in " << dialog_id; Dialog *d = get_dialog_force(dialog_id, "get_message_thread"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } if (dialog_id.get_type() != DialogType::Channel) { return promise.set_error(Status::Error(400, "Chat is not a supergroup or a channel")); } if (message_id.is_scheduled()) { return promise.set_error(Status::Error(400, "Scheduled messages can't have message threads")); } MessageFullId top_thread_message_full_id; if (message_id == MessageId(ServerMessageId(1)) && is_forum_channel(dialog_id)) { top_thread_message_full_id = MessageFullId{dialog_id, message_id}; } else { message_id = get_persistent_message_id(d, message_id); auto m = get_message_force(d, message_id, "get_message_thread"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } TRY_RESULT_PROMISE_ASSIGN(promise, top_thread_message_full_id, get_top_thread_message_full_id(dialog_id, m, true)); if ((m->reply_info.is_empty() || !m->reply_info.is_comment_) && top_thread_message_full_id.get_message_id() != m->message_id) { CHECK(dialog_id == top_thread_message_full_id.get_dialog_id()); // get information about the thread from the top message message_id = top_thread_message_full_id.get_message_id(); CHECK(message_id.is_valid()); } } auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, message_id, promise = std::move(promise)](Result result) mutable { if (result.is_error()) { return promise.set_error(result.move_as_error()); } send_closure(actor_id, &MessagesManager::on_get_discussion_message, dialog_id, message_id, result.move_as_ok(), std::move(promise)); }); td_->create_handler(std::move(query_promise)) ->send(dialog_id, message_id, top_thread_message_full_id.get_dialog_id(), top_thread_message_full_id.get_message_id()); } void MessagesManager::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_->contacts_manager_->on_get_users(std::move(result->users_), "process_discussion_message"); td_->contacts_manager_->on_get_chats(std::move(result->chats_), "process_discussion_message"); for (auto &message : result->messages_) { auto message_dialog_id = DialogId::get_message_dialog_id(message); if (message_dialog_id != expected_dialog_id) { return promise.set_error(Status::Error(500, "Expected messages in a different chat")); } } for (auto &message : result->messages_) { if (need_channel_difference_to_add_message(expected_dialog_id, message)) { auto max_message_id = MessageId::get_max_message_id(result->messages_); return 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, &MessagesManager::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 MessagesManager::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 = on_get_message(std::move(message), false, true, false, "process_discussion_message_impl"); if (message_full_id.get_message_id().is_valid()) { CHECK(message_full_id.get_dialog_id() == expected_dialog_id); 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()) { 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) { 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 MessagesManager::on_get_discussion_message(DialogId dialog_id, MessageId message_id, MessageThreadInfo &&message_thread_info, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); Dialog *d = get_dialog_force(dialog_id, "on_get_discussion_message"); CHECK(d != nullptr); CHECK(message_id.is_valid()); auto m = get_message_force(d, message_id, "on_get_discussion_message"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } if (message_thread_info.message_ids.empty()) { if (message_thread_info.dialog_id != dialog_id && !have_input_peer(message_thread_info.dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access message comments")); } return promise.set_error(Status::Error(400, "Message has no thread")); } DialogId expected_dialog_id; if (m->reply_info.is_comment_) { if (!is_active_message_reply_info(dialog_id, m->reply_info)) { return promise.set_error(Status::Error(400, "Message has no comments")); } expected_dialog_id = DialogId(m->reply_info.channel_id_); } else if (message_id == MessageId(ServerMessageId(1)) && is_forum_channel(dialog_id)) { // General forum topic expected_dialog_id = dialog_id; } else { if (!m->top_thread_message_id.is_valid()) { return promise.set_error(Status::Error(400, "Message has no thread")); } expected_dialog_id = dialog_id; } if (expected_dialog_id != dialog_id && m->reply_info.is_comment_ && m->linked_top_thread_message_id != message_thread_info.message_ids.back()) { auto linked_d = get_dialog_force(expected_dialog_id, "on_get_discussion_message 2"); CHECK(linked_d != nullptr); auto linked_message_id = message_thread_info.message_ids.back(); Message *linked_m = get_message_force(linked_d, linked_message_id, "on_get_discussion_message 3"); CHECK(linked_m != nullptr && linked_m->message_id.is_server()); if (linked_m->top_thread_message_id == linked_m->message_id && is_active_message_reply_info(expected_dialog_id, linked_m->reply_info)) { if (m->linked_top_thread_message_id.is_valid()) { LOG(ERROR) << "Comment message identifier for " << message_id << " in " << dialog_id << " changed from " << m->linked_top_thread_message_id << " to " << linked_message_id; } m->linked_top_thread_message_id = linked_message_id; on_dialog_updated(dialog_id, "on_get_discussion_message"); } } promise.set_value(std::move(message_thread_info)); } td_api::object_ptr MessagesManager::get_message_thread_info_object( const MessageThreadInfo &info) { if (info.message_ids.empty()) { return nullptr; } Dialog *d = get_dialog(info.dialog_id); CHECK(d != nullptr); td_api::object_ptr reply_info; vector> messages; messages.reserve(info.message_ids.size()); bool is_forum_topic = false; for (auto message_id : info.message_ids) { const Message *m = get_message_force(d, message_id, "get_message_thread_info_object"); auto message = get_message_object(d->dialog_id, m, "get_message_thread_info_object"); if (message != nullptr) { if (message->interaction_info_ != nullptr && message->interaction_info_->reply_info_ != nullptr) { reply_info = m->reply_info.get_message_reply_info_object(td_, d->last_read_inbox_message_id); CHECK(reply_info != nullptr); } is_forum_topic = message->is_topic_message_; messages.push_back(std::move(message)); } } if (messages.size() != 1) { is_forum_topic = false; } else if (info.message_ids[0] == MessageId(ServerMessageId(1)) && is_forum_channel(info.dialog_id)) { // General forum topic is_forum_topic = true; } if (reply_info == nullptr && !is_forum_topic) { return nullptr; } MessageId top_thread_message_id; td_api::object_ptr draft_message; if (!info.message_ids.empty()) { top_thread_message_id = info.message_ids.back(); if (can_send_message(d->dialog_id).is_ok()) { const Message *m = get_message_force(d, top_thread_message_id, "get_message_thread_info_object 2"); if (m != nullptr && !m->reply_info.is_comment_ && is_active_message_reply_info(d->dialog_id, m->reply_info)) { draft_message = get_draft_message_object(td_, m->thread_draft_message); } } } return td_api::make_object( get_chat_id_object(d->dialog_id, "messageThreadInfo"), top_thread_message_id.get(), std::move(reply_info), info.unread_message_count, std::move(messages), std::move(draft_message)); } Status MessagesManager::can_get_message_viewers(MessageFullId message_full_id) { auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "get_message_viewers"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } auto m = get_message_force(d, message_full_id.get_message_id(), "get_message_viewers"); if (m == nullptr) { return Status::Error(400, "Message not found"); } return can_get_message_viewers(dialog_id, m); } Status MessagesManager::can_get_message_viewers(DialogId dialog_id, const Message *m) const { if (td_->auth_manager_->is_bot()) { return Status::Error(400, "User is bot"); } CHECK(m != nullptr); if (!m->is_outgoing) { return Status::Error(400, "Can't get viewers of incoming messages"); } if (G()->unix_time() - m->date > td_->option_manager_->get_option_integer("chat_read_mark_expire_period", 7 * 86400)) { return Status::Error(400, "Message is too old"); } int32 participant_count = 0; switch (dialog_id.get_type()) { case DialogType::User: return Status::Error(400, "Can't get message viewers in private chats"); case DialogType::Chat: if (!td_->contacts_manager_->get_chat_is_active(dialog_id.get_chat_id())) { return Status::Error(400, "Chat is deactivated"); } participant_count = td_->contacts_manager_->get_chat_participant_count(dialog_id.get_chat_id()); break; case DialogType::Channel: if (is_broadcast_channel(dialog_id)) { return Status::Error(400, "Can't get message viewers in channel chats"); } if (td_->contacts_manager_->get_channel_effective_has_hidden_participants(dialog_id.get_channel_id(), "can_get_message_viewers")) { return Status::Error(400, "Participant list is hidden in the chat"); } participant_count = td_->contacts_manager_->get_channel_participant_count(dialog_id.get_channel_id()); break; case DialogType::SecretChat: return Status::Error(400, "Can't get message viewers in secret chats"); case DialogType::None: default: UNREACHABLE(); return Status::OK(); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return Status::Error(400, "Can't access the chat"); } if (participant_count == 0) { return Status::Error(400, "Chat is empty or have unknown number of members"); } if (participant_count > td_->option_manager_->get_option_integer("chat_read_mark_size_threshold", 100)) { return Status::Error(400, "Chat is too big"); } if (m->message_id.is_scheduled()) { return Status::Error(400, "Scheduled messages can't have viewers"); } if (m->message_id.is_yet_unsent()) { return Status::Error(400, "Yet unsent messages can't have viewers"); } if (m->message_id.is_local()) { return Status::Error(400, "Local messages can't have viewers"); } CHECK(m->message_id.is_server()); if (m->content->get_type() == MessageContentType::Poll && get_message_content_poll_is_anonymous(td_, m->content.get())) { return Status::Error(400, "Anonymous poll viewers are unavailable"); } return Status::OK(); } void MessagesManager::get_message_viewers(MessageFullId message_full_id, Promise> &&promise) { TRY_STATUS_PROMISE(promise, 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, &MessagesManager::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 MessagesManager::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_->contacts_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, &MessagesManager::on_get_message_viewers, dialog_id, std::move(message_viewers), true, std::move(promise)); }); switch (dialog_id.get_type()) { case DialogType::Chat: return td_->contacts_manager_->reload_chat_full(dialog_id.get_chat_id(), std::move(query_promise), "on_get_message_viewers"); case DialogType::Channel: return td_->contacts_manager_->get_channel_participants( dialog_id.get_channel_id(), td_api::make_object(), 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_->contacts_manager_.get())); } void MessagesManager::translate_message_text(MessageFullId message_full_id, const string &to_language_code, Promise> &&promise) { auto m = get_message_force(message_full_id, "translate_message_text"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } auto text = get_message_content_text(m->content.get()); if (text == nullptr) { return promise.set_value(td_api::make_object()); } auto skip_bot_commands = need_skip_bot_commands(message_full_id.get_dialog_id(), m); auto max_media_timestamp = get_message_max_media_timestamp(m); td_->translation_manager_->translate_text(*text, skip_bot_commands, max_media_timestamp, to_language_code, std::move(promise)); } void MessagesManager::get_dialog_info_full(DialogId dialog_id, Promise &&promise, const char *source) { switch (dialog_id.get_type()) { case DialogType::User: send_closure_later(td_->contacts_manager_actor_, &ContactsManager::load_user_full, dialog_id.get_user_id(), false, std::move(promise), source); return; case DialogType::Chat: send_closure_later(td_->contacts_manager_actor_, &ContactsManager::load_chat_full, dialog_id.get_chat_id(), false, std::move(promise), source); return; case DialogType::Channel: send_closure_later(td_->contacts_manager_actor_, &ContactsManager::load_channel_full, dialog_id.get_channel_id(), false, std::move(promise), source); return; case DialogType::SecretChat: return promise.set_value(Unit()); case DialogType::None: default: UNREACHABLE(); return promise.set_error(Status::Error(500, "Wrong chat type")); } } void MessagesManager::reload_dialog_info_full(DialogId dialog_id, const char *source) { if (G()->close_flag()) { return; } LOG(INFO) << "Reload full info about " << dialog_id << " from " << source; switch (dialog_id.get_type()) { case DialogType::User: send_closure_later(td_->contacts_manager_actor_, &ContactsManager::reload_user_full, dialog_id.get_user_id(), Promise(), source); return; case DialogType::Chat: send_closure_later(td_->contacts_manager_actor_, &ContactsManager::reload_chat_full, dialog_id.get_chat_id(), Promise(), source); return; case DialogType::Channel: send_closure_later(td_->contacts_manager_actor_, &ContactsManager::reload_channel_full, dialog_id.get_channel_id(), Promise(), source); return; case DialogType::SecretChat: return; case DialogType::None: default: UNREACHABLE(); return; } } void MessagesManager::reload_dialog_notification_settings(DialogId dialog_id, Promise &&promise, const char *source) { LOG(INFO) << "Reload notification settings for " << dialog_id << " from " << source; const Dialog *d = get_dialog(dialog_id); if (d != nullptr) { td_->notification_settings_manager_->send_get_dialog_notification_settings_query(dialog_id, MessageId(), std::move(promise)); } else { send_get_dialog_query(dialog_id, std::move(promise), 0, source); } } void MessagesManager::on_dialog_info_full_invalidated(DialogId dialog_id) { Dialog *d = get_dialog(dialog_id); if (d != nullptr && d->open_count > 0) { reload_dialog_info_full(dialog_id, "on_dialog_info_full_invalidated"); } } MessageId MessagesManager::get_dialog_pinned_message(DialogId dialog_id, Promise &&promise) { Dialog *d = get_dialog_force(dialog_id, "get_dialog_pinned_message"); if (d == nullptr) { promise.set_error(Status::Error(400, "Chat not found")); return MessageId(); } LOG(INFO) << "Get pinned message in " << dialog_id << " with " << (d->is_last_pinned_message_id_inited ? "inited" : "unknown") << " pinned " << d->last_pinned_message_id; if (!d->is_last_pinned_message_id_inited) { // must call get_dialog_info_full as expected in fix_new_dialog get_dialog_info_full(dialog_id, std::move(promise), "get_dialog_pinned_message 1"); return MessageId(); } get_dialog_info_full(dialog_id, Auto(), "get_dialog_pinned_message 2"); if (d->last_pinned_message_id.is_valid()) { tl_object_ptr input_message; if (dialog_id.get_type() == DialogType::Channel) { input_message = make_tl_object(); } get_message_force_from_server(d, d->last_pinned_message_id, std::move(promise), std::move(input_message)); } else { promise.set_value(Unit()); } return d->last_pinned_message_id; } void MessagesManager::get_callback_query_message(DialogId dialog_id, MessageId message_id, int64 callback_query_id, Promise &&promise) { Dialog *d = get_dialog_force(dialog_id, "get_callback_query_message"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!message_id.is_valid() || !message_id.is_server()) { return promise.set_error(Status::Error(400, "Invalid message identifier specified")); } LOG(INFO) << "Get callback query " << message_id << " in " << dialog_id << " for query " << callback_query_id; auto input_message = make_tl_object(message_id.get_server_message_id().get(), callback_query_id); get_message_force_from_server(d, message_id, std::move(promise), std::move(input_message)); } bool MessagesManager::get_messages(DialogId dialog_id, const vector &message_ids, Promise &&promise) { Dialog *d = get_dialog_force(dialog_id, "get_messages"); if (d == nullptr) { promise.set_error(Status::Error(400, "Chat not found")); return false; } bool is_secret = dialog_id.get_type() == DialogType::SecretChat; vector missed_message_ids; for (auto message_id : message_ids) { if (!message_id.is_valid() && !message_id.is_valid_scheduled()) { promise.set_error(Status::Error(400, "Invalid message identifier")); return false; } auto *m = get_message_force(d, message_id, "get_messages"); if (m == nullptr && message_id.is_any_server() && !is_secret) { missed_message_ids.emplace_back(dialog_id, message_id); continue; } } if (!missed_message_ids.empty()) { get_messages_from_server(std::move(missed_message_ids), std::move(promise), "get_messages"); return false; } promise.set_value(Unit()); return true; } void MessagesManager::get_message_from_server(MessageFullId message_full_id, Promise &&promise, const char *source, tl_object_ptr input_message) { get_messages_from_server({message_full_id}, std::move(promise), source, std::move(input_message)); } void MessagesManager::get_messages_from_server(vector &&message_ids, Promise &&promise, const char *source, tl_object_ptr input_message) { TRY_STATUS_PROMISE(promise, G()->close_status()); if (message_ids.empty()) { LOG(ERROR) << "Empty message_ids from " << source; return promise.set_error(Status::Error(500, "There are no messages specified to fetch")); } if (input_message != nullptr) { CHECK(message_ids.size() == 1); } vector> ordinary_message_ids; FlatHashMap>, ChannelIdHash> channel_message_ids; FlatHashMap, DialogIdHash> scheduled_message_ids; for (auto &message_full_id : message_ids) { auto dialog_id = message_full_id.get_dialog_id(); auto message_id = message_full_id.get_message_id(); if (!message_id.is_valid() || !message_id.is_server()) { if (message_id.is_valid_scheduled() && message_id.is_scheduled_server() && dialog_id.is_valid()) { scheduled_message_ids[dialog_id].push_back(message_id.get_scheduled_server_message_id().get()); } continue; } if (input_message == nullptr) { input_message = make_tl_object(message_id.get_server_message_id().get()); } switch (dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: ordinary_message_ids.push_back(std::move(input_message)); break; case DialogType::Channel: channel_message_ids[dialog_id.get_channel_id()].push_back(std::move(input_message)); break; case DialogType::SecretChat: LOG(ERROR) << "Can't get " << message_full_id << " from server from " << source; break; case DialogType::None: default: UNREACHABLE(); break; } } MultiPromiseActorSafe mpas{"GetMessagesOnServerMultiPromiseActor"}; mpas.add_promise(std::move(promise)); auto lock = mpas.get_promise(); if (!ordinary_message_ids.empty()) { td_->create_handler(mpas.get_promise())->send(std::move(ordinary_message_ids)); } for (auto &it : scheduled_message_ids) { auto dialog_id = it.first; have_dialog_force(dialog_id, "get_messages_from_server"); auto input_peer = get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { LOG(ERROR) << "Can't find info about " << dialog_id << " to get a message from it from " << source; mpas.get_promise().set_error(Status::Error(400, "Can't access the chat")); continue; } td_->create_handler(mpas.get_promise()) ->send(dialog_id, std::move(input_peer), std::move(it.second)); } for (auto &it : channel_message_ids) { td_->contacts_manager_->have_channel_force(it.first, "get_messages_from_server"); auto input_channel = td_->contacts_manager_->get_input_channel(it.first); if (input_channel == nullptr) { LOG(ERROR) << "Can't find info about " << it.first << " to get a message from it from " << source; mpas.get_promise().set_error(Status::Error(400, "Can't access the chat")); continue; } const auto *d = get_dialog_force(DialogId(it.first)); td_->create_handler(mpas.get_promise()) ->send(it.first, std::move(input_channel), std::move(it.second), d == nullptr ? MessageId() : d->last_new_message_id); } lock.set_value(Unit()); } bool MessagesManager::is_message_edited_recently(MessageFullId message_full_id, int32 seconds) { if (seconds < 0) { return false; } if (!message_full_id.get_message_id().is_valid()) { return false; } auto m = get_message_force(message_full_id, "is_message_edited_recently"); if (m == nullptr) { return true; } return m->edit_date >= G()->unix_time() - seconds; } Status MessagesManager::can_get_media_timestamp_link(DialogId dialog_id, const Message *m) { if (m == nullptr) { return Status::Error(400, "Message not found"); } if (dialog_id.get_type() != DialogType::Channel) { auto forward_info = m->forward_info.get(); if (!can_message_content_have_media_timestamp(m->content.get()) || forward_info == nullptr || forward_info->is_imported) { return Status::Error(400, "Message links are available only for messages in supergroups and channel chats"); } auto origin_message_full_id = forward_info->origin.get_message_full_id(); auto origin_message_id = origin_message_full_id.get_message_id(); if (!origin_message_id.is_valid() || !origin_message_id.is_server()) { return Status::Error(400, "Message links are available only for messages in supergroups and channel chats"); } return Status::OK(); } if (m->message_id.is_yet_unsent()) { return Status::Error(400, "Message is not sent yet"); } if (m->message_id.is_scheduled()) { return Status::Error(400, "Message is scheduled"); } if (!m->message_id.is_server()) { return Status::Error(400, "Message is local"); } return Status::OK(); } bool MessagesManager::can_report_message_reactions(DialogId dialog_id, const Message *m) const { CHECK(m != nullptr); if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id) || !td_->contacts_manager_->is_channel_public(dialog_id.get_channel_id())) { return false; } if (m->message_id.is_scheduled() || !m->message_id.is_server()) { return false; } if (is_discussion_message(dialog_id, m)) { return false; } return true; } Result> MessagesManager::get_message_link(MessageFullId message_full_id, int32 media_timestamp, bool for_group, bool in_message_thread) { auto dialog_id = message_full_id.get_dialog_id(); auto d = get_dialog_force(dialog_id, "get_message_link"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return Status::Error(400, "Can't access the chat"); } auto *m = get_message_force(d, message_full_id.get_message_id(), "get_message_link"); TRY_STATUS(can_get_media_timestamp_link(dialog_id, m)); if (media_timestamp <= 0 || !can_message_content_have_media_timestamp(m->content.get())) { media_timestamp = 0; } if (media_timestamp != 0) { for_group = false; auto duration = get_message_content_media_duration(m->content.get(), td_); if (duration != 0 && media_timestamp > duration) { media_timestamp = 0; } } auto message_id = m->message_id; if (dialog_id.get_type() != DialogType::Channel) { CHECK(m->forward_info != nullptr); auto origin_message_full_id = m->forward_info->origin.get_message_full_id(); dialog_id = origin_message_full_id.get_dialog_id(); message_id = origin_message_full_id.get_message_id(); CHECK(dialog_id.get_type() == DialogType::Channel); for_group = false; in_message_thread = false; auto channel_message = get_message(origin_message_full_id); if (channel_message != nullptr && channel_message->media_album_id == 0) { for_group = true; // default is true } } else { if (m->media_album_id == 0) { for_group = true; // default is true } } bool is_forum = is_forum_channel(dialog_id); if (!is_forum && (!m->top_thread_message_id.is_valid() || !m->top_thread_message_id.is_server() || is_deleted_message(d, m->top_thread_message_id) || is_broadcast_channel(dialog_id))) { in_message_thread = false; } if (!td_->auth_manager_->is_bot()) { td_->create_handler(Promise()) ->send(dialog_id.get_channel_id(), message_id, for_group, true); } SliceBuilder sb; sb << LinkManager::get_t_me_url(); if (in_message_thread && !is_forum) { // try to generate a comment link auto *top_m = get_message_force(d, m->top_thread_message_id, "get_public_message_link"); if (is_discussion_message(dialog_id, top_m) && is_active_message_reply_info(dialog_id, top_m->reply_info)) { auto linked_dialog_id = top_m->forward_info->from_dialog_id; auto linked_message_id = top_m->forward_info->from_message_id; auto linked_d = get_dialog(linked_dialog_id); CHECK(linked_d != nullptr); CHECK(linked_dialog_id.get_type() == DialogType::Channel); auto *linked_m = get_message_force(linked_d, linked_message_id, "get_public_message_link"); auto channel_username = td_->contacts_manager_->get_channel_first_username(linked_dialog_id.get_channel_id()); if (linked_m != nullptr && is_active_message_reply_info(linked_dialog_id, linked_m->reply_info) && linked_message_id.is_server() && have_input_peer(linked_dialog_id, AccessRights::Read) && !channel_username.empty()) { sb << channel_username << '/' << linked_message_id.get_server_message_id().get() << "?comment=" << message_id.get_server_message_id().get(); if (!for_group) { sb << "&single"; } if (media_timestamp > 0) { sb << "&t=" << media_timestamp; } return std::make_pair(sb.as_cslice().str(), true); } } } auto dialog_username = td_->contacts_manager_->get_channel_first_username(dialog_id.get_channel_id()); bool is_public = !dialog_username.empty(); if (m->content->get_type() == MessageContentType::VideoNote && is_broadcast_channel(dialog_id) && is_public) { return std::make_pair( PSTRING() << "https://telesco.pe/" << dialog_username << '/' << message_id.get_server_message_id().get(), true); } if (is_public) { sb << dialog_username; } else { sb << "c/" << dialog_id.get_channel_id().get(); } if (in_message_thread && is_forum) { auto top_thread_message_id = m->is_topic_message ? m->top_thread_message_id : MessageId(ServerMessageId(1)); if (top_thread_message_id != message_id) { sb << '/' << top_thread_message_id.get_server_message_id().get(); } in_message_thread = false; } sb << '/' << message_id.get_server_message_id().get(); char separator = '?'; if (in_message_thread) { sb << separator << "thread=" << m->top_thread_message_id.get_server_message_id().get(); separator = '&'; } if (!for_group) { sb << separator << "single"; separator = '&'; } if (media_timestamp > 0) { sb << separator << "t=" << media_timestamp; separator = '&'; } CHECK(separator == '?' || separator == '&'); return std::make_pair(sb.as_cslice().str(), is_public); } string MessagesManager::get_message_embedding_code(MessageFullId message_full_id, bool for_group, Promise &&promise) { auto dialog_id = message_full_id.get_dialog_id(); auto d = get_dialog_force(dialog_id, "get_message_embedding_code"); if (d == nullptr) { promise.set_error(Status::Error(400, "Chat not found")); return {}; } if (!have_input_peer(dialog_id, AccessRights::Read)) { promise.set_error(Status::Error(400, "Can't access the chat")); return {}; } if (dialog_id.get_type() != DialogType::Channel || td_->contacts_manager_->get_channel_first_username(dialog_id.get_channel_id()).empty()) { promise.set_error(Status::Error( 400, "Message embedding code is available only for messages in public supergroups and channel chats")); return {}; } auto *m = get_message_force(d, message_full_id.get_message_id(), "get_message_embedding_code"); if (m == nullptr) { promise.set_error(Status::Error(400, "Message not found")); return {}; } if (m->message_id.is_yet_unsent()) { promise.set_error(Status::Error(400, "Message is not sent yet")); return {}; } if (m->message_id.is_scheduled()) { promise.set_error(Status::Error(400, "Message is scheduled")); return {}; } if (!m->message_id.is_server()) { promise.set_error(Status::Error(400, "Message is local")); return {}; } if (m->media_album_id == 0) { for_group = true; // default is true } auto &links = message_embedding_codes_[for_group][dialog_id].embedding_codes_; auto it = links.find(m->message_id); if (it == links.end()) { td_->create_handler(std::move(promise)) ->send(dialog_id.get_channel_id(), m->message_id, for_group, false); return {}; } promise.set_value(Unit()); return it->second; } void MessagesManager::on_get_public_message_link(MessageFullId message_full_id, bool for_group, string url, string html) { LOG_IF(ERROR, url.empty() && html.empty()) << "Receive empty public link for " << message_full_id; auto dialog_id = message_full_id.get_dialog_id(); auto message_id = message_full_id.get_message_id(); message_embedding_codes_[for_group][dialog_id].embedding_codes_[message_id] = std::move(html); } void MessagesManager::get_message_link_info(Slice url, Promise &&promise) { auto r_message_link_info = LinkManager::get_message_link_info(url); if (r_message_link_info.is_error()) { return promise.set_error(Status::Error(400, r_message_link_info.error().message())); } auto info = r_message_link_info.move_as_ok(); auto query_promise = PromiseCreator::lambda( [actor_id = actor_id(this), info, promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { return promise.set_value(std::move(info)); } send_closure(actor_id, &MessagesManager::on_get_message_link_dialog, std::move(info), result.ok(), std::move(promise)); }); resolve_dialog(info.username, info.channel_id, std::move(query_promise)); } void MessagesManager::on_get_message_link_dialog(MessageLinkInfo &&info, DialogId dialog_id, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); Dialog *d = get_dialog_force(dialog_id, "on_get_message_link_dialog"); CHECK(d != nullptr); get_message_force_from_server(d, info.message_id, PromiseCreator::lambda([actor_id = actor_id(this), info = std::move(info), dialog_id, promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { return promise.set_value(std::move(info)); } send_closure(actor_id, &MessagesManager::on_get_message_link_message, std::move(info), dialog_id, std::move(promise)); })); } void MessagesManager::on_get_message_link_message(MessageLinkInfo &&info, DialogId dialog_id, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); auto message_id = info.message_id; Message *m = get_message_force({dialog_id, message_id}, "on_get_message_link_message"); if (info.comment_message_id == MessageId() || m == nullptr || !is_broadcast_channel(dialog_id) || !m->reply_info.is_comment_ || !is_active_message_reply_info(dialog_id, m->reply_info)) { return promise.set_value(std::move(info)); } if (td_->contacts_manager_->have_channel_force(m->reply_info.channel_id_, "on_get_message_link_message")) { force_create_dialog(DialogId(m->reply_info.channel_id_), "on_get_message_link_message"); on_get_message_link_discussion_message(std::move(info), DialogId(m->reply_info.channel_id_), std::move(promise)); return; } auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), info = std::move(info), promise = std::move(promise)](Result result) mutable { if (result.is_error() || result.ok().message_ids.empty()) { return promise.set_value(std::move(info)); } send_closure(actor_id, &MessagesManager::on_get_message_link_discussion_message, std::move(info), result.ok().dialog_id, std::move(promise)); }); td_->create_handler(std::move(query_promise)) ->send(dialog_id, message_id, DialogId(m->reply_info.channel_id_), MessageId()); } void MessagesManager::on_get_message_link_discussion_message(MessageLinkInfo &&info, DialogId comment_dialog_id, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); CHECK(comment_dialog_id.is_valid()); info.comment_dialog_id = comment_dialog_id; Dialog *d = get_dialog_force(comment_dialog_id, "on_get_message_link_discussion_message"); if (d == nullptr) { return promise.set_error(Status::Error(500, "Chat not found")); } auto comment_message_id = info.comment_message_id; get_message_force_from_server( d, comment_message_id, PromiseCreator::lambda([info = std::move(info), promise = std::move(promise)](Result &&result) mutable { return promise.set_value(std::move(info)); })); } td_api::object_ptr MessagesManager::get_message_link_info_object( const MessageLinkInfo &info) const { CHECK(info.username.empty() == info.channel_id.is_valid()); bool is_public = !info.username.empty(); DialogId dialog_id = info.comment_dialog_id.is_valid() ? info.comment_dialog_id : (is_public ? resolve_dialog_username(info.username) : DialogId(info.channel_id)); MessageId top_thread_message_id; MessageId message_id = info.comment_dialog_id.is_valid() ? info.comment_message_id : info.message_id; td_api::object_ptr message; int32 media_timestamp = 0; bool for_album = false; const Dialog *d = get_dialog(dialog_id); if (d == nullptr) { dialog_id = DialogId(); top_thread_message_id = MessageId(); } else { const Message *m = get_message(d, message_id); if (m != nullptr) { message = get_message_object(dialog_id, m, "get_message_link_info_object"); for_album = !info.is_single && m->media_album_id != 0; if (info.comment_dialog_id.is_valid() || info.for_comment || m->is_topic_message) { top_thread_message_id = m->top_thread_message_id; } else if (is_forum_channel(dialog_id) && info.top_thread_message_id == MessageId(ServerMessageId(1))) { // General topic top_thread_message_id = info.top_thread_message_id; } else { top_thread_message_id = MessageId(); } if (can_message_content_have_media_timestamp(m->content.get())) { auto duration = get_message_content_media_duration(m->content.get(), td_); if (duration == 0 || info.media_timestamp <= duration) { media_timestamp = info.media_timestamp; } } if (m->content->get_type() == MessageContentType::TopicCreate && top_thread_message_id.is_valid()) { message = nullptr; CHECK(!for_album); CHECK(media_timestamp == 0); } } else if (!info.comment_dialog_id.is_valid() && dialog_id.get_type() == DialogType::Channel && !is_broadcast_channel(dialog_id)) { top_thread_message_id = info.top_thread_message_id; } } return td_api::make_object(is_public, get_chat_id_object(dialog_id, "messageLinkInfo"), top_thread_message_id.get(), std::move(message), media_timestamp, for_album); } InputDialogId MessagesManager::get_input_dialog_id(DialogId dialog_id) const { auto input_peer = get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr || input_peer->get_id() == telegram_api::inputPeerSelf::ID || input_peer->get_id() == telegram_api::inputPeerEmpty::ID) { return InputDialogId(dialog_id); } else { return InputDialogId(input_peer); } } Status MessagesManager::can_add_dialog_to_filter(DialogId dialog_id) { if (!dialog_id.is_valid()) { return Status::Error(400, "Invalid chat identifier specified"); } const Dialog *d = get_dialog_force(dialog_id, "can_add_dialog_to_filter"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return Status::Error(400, "Can't access the chat"); } if (d->order == DEFAULT_ORDER) { return Status::Error(400, "Chat is not in the chat list"); } return Status::OK(); } Status MessagesManager::delete_dialog_reply_markup(DialogId dialog_id, MessageId message_id) { if (td_->auth_manager_->is_bot()) { return Status::Error(400, "Bots can't delete chat reply markup"); } if (message_id.is_scheduled()) { return Status::Error(400, "Wrong message identifier specified"); } if (!message_id.is_valid()) { return Status::Error(400, "Invalid message identifier specified"); } Dialog *d = get_dialog_force(dialog_id, "delete_dialog_reply_markup"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } if (d->reply_markup_message_id != message_id) { return Status::OK(); } Message *m = get_message_force(d, message_id, "delete_dialog_reply_markup"); CHECK(m != nullptr); CHECK(m->reply_markup != nullptr); if (m->reply_markup->type == ReplyMarkup::Type::ForceReply) { set_dialog_reply_markup(d, MessageId()); } else if (m->reply_markup->type == ReplyMarkup::Type::ShowKeyboard) { if (!m->reply_markup->is_one_time_keyboard) { return Status::Error(400, "Do not need to delete non one-time keyboard"); } if (m->reply_markup->is_personal) { m->reply_markup->is_personal = false; set_dialog_reply_markup(d, message_id); on_message_changed(d, m, true, "delete_dialog_reply_markup"); } } else { // non-bots can't have messages with RemoveKeyboard UNREACHABLE(); } return Status::OK(); } class MessagesManager::SaveDialogDraftMessageOnServerLogEvent { 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); } }; Status MessagesManager::set_dialog_draft_message(DialogId dialog_id, MessageId top_thread_message_id, tl_object_ptr &&draft_message) { if (td_->auth_manager_->is_bot()) { return Status::Error(400, "Bots can't change chat draft message"); } Dialog *d = get_dialog_force(dialog_id, "set_dialog_draft_message"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } TRY_STATUS(can_send_message(dialog_id)); TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, MessageInputReplyTo())); TRY_RESULT(new_draft_message, DraftMessage::get_draft_message(td_, dialog_id, top_thread_message_id, std::move(draft_message))); if (top_thread_message_id != MessageId()) { CHECK(top_thread_message_id.is_valid()); CHECK(top_thread_message_id.is_server()); auto m = get_message_force(d, top_thread_message_id, "set_dialog_draft_message"); if (m == nullptr || m->reply_info.is_comment_ || !is_active_message_reply_info(dialog_id, m->reply_info)) { return Status::OK(); } if (need_update_draft_message(m->thread_draft_message, new_draft_message, false)) { m->thread_draft_message = std::move(new_draft_message); on_message_changed(d, m, false, "set_dialog_draft_message"); } return Status::OK(); } if (update_dialog_draft_message(d, std::move(new_draft_message), false, true)) { if (dialog_id.get_type() != DialogType::SecretChat) { if (G()->use_message_database()) { SaveDialogDraftMessageOnServerLogEvent log_event; log_event.dialog_id_ = dialog_id; add_log_event(d->save_draft_message_log_event_id, get_log_event_storer(log_event), LogEvent::HandlerType::SaveDialogDraftMessageOnServer, "draft"); } pending_draft_message_timeout_.set_timeout_in(dialog_id.get(), d->open_count > 0 ? MIN_SAVE_DRAFT_DELAY : 0); } } return Status::OK(); } void MessagesManager::save_dialog_draft_message_on_server(DialogId dialog_id) { if (G()->close_flag()) { return; } auto d = get_dialog(dialog_id); CHECK(d != nullptr); Promise promise; if (d->save_draft_message_log_event_id.log_event_id != 0) { d->save_draft_message_log_event_id.generation++; promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, generation = d->save_draft_message_log_event_id.generation](Result result) { if (!G()->close_flag()) { send_closure(actor_id, &MessagesManager::on_saved_dialog_draft_message, dialog_id, generation); } }); } save_draft_message(td_, dialog_id, d->draft_message, std::move(promise)); } void MessagesManager::on_saved_dialog_draft_message(DialogId dialog_id, uint64 generation) { auto d = get_dialog(dialog_id); CHECK(d != nullptr); delete_log_event(d->save_draft_message_log_event_id, generation, "draft"); } void MessagesManager::clear_all_draft_messages(bool exclude_secret_chats, Promise &&promise) { if (!exclude_secret_chats) { dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr &dialog) { Dialog *d = dialog.get(); if (dialog_id.get_type() == DialogType::SecretChat) { update_dialog_draft_message(d, nullptr, false, true); } }); } ::td::clear_all_draft_messages(td_, std::move(promise)); } int32 MessagesManager::get_pinned_dialogs_limit(DialogListId dialog_list_id) const { if (dialog_list_id.is_filter()) { return DialogFilter::get_max_filter_dialogs(); } Slice key{"pinned_chat_count_max"}; int32 default_limit = 5; if (!dialog_list_id.is_folder() || dialog_list_id.get_folder_id() != FolderId::main()) { key = Slice("pinned_archived_chat_count_max"); default_limit = 100; } int32 limit = clamp(narrow_cast(td_->option_manager_->get_option_integer(key)), 0, 1000); if (limit <= 0) { if (td_->option_manager_->get_option_boolean("is_premium")) { default_limit *= 2; } return default_limit; } return limit; } vector MessagesManager::remove_secret_chat_dialog_ids(vector dialog_ids) { td::remove_if(dialog_ids, [](DialogId dialog_id) { return dialog_id.get_type() == DialogType::SecretChat; }); return dialog_ids; } Status MessagesManager::toggle_dialog_is_pinned(DialogListId dialog_list_id, DialogId dialog_id, bool is_pinned) { if (td_->auth_manager_->is_bot()) { return Status::Error(400, "Bots can't change chat pin state"); } Dialog *d = get_dialog_force(dialog_id, "toggle_dialog_is_pinned"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return Status::Error(400, "Can't access the chat"); } if (d->order == DEFAULT_ORDER && is_pinned) { return Status::Error(400, "The chat can't be pinned"); } auto *list = get_dialog_list(dialog_list_id); if (list == nullptr) { return Status::Error(400, "Chat list not found"); } if (!list->are_pinned_dialogs_inited_) { return Status::Error(400, "Pinned chats must be loaded first"); } bool was_pinned = is_dialog_pinned(dialog_list_id, dialog_id); if (is_pinned == was_pinned) { return Status::OK(); } if (dialog_list_id.is_filter()) { return td_->dialog_filter_manager_->set_dialog_is_pinned(dialog_list_id.get_filter_id(), get_input_dialog_id(dialog_id), is_pinned); } CHECK(dialog_list_id.is_folder()); auto folder_id = dialog_list_id.get_folder_id(); if (is_pinned) { if (d->folder_id != folder_id) { return Status::Error(400, "Chat not in the list"); } auto pinned_dialog_ids = get_pinned_dialog_ids(dialog_list_id); auto pinned_dialog_count = pinned_dialog_ids.size(); auto secret_pinned_dialog_count = std::count_if(pinned_dialog_ids.begin(), pinned_dialog_ids.end(), [](DialogId dialog_id) { return dialog_id.get_type() == DialogType::SecretChat; }); size_t dialog_count = dialog_id.get_type() == DialogType::SecretChat ? secret_pinned_dialog_count : pinned_dialog_count - secret_pinned_dialog_count; if (dialog_count >= static_cast(get_pinned_dialogs_limit(dialog_list_id))) { return Status::Error(400, "The maximum number of pinned chats exceeded"); } } if (set_dialog_is_pinned(dialog_list_id, d, is_pinned)) { toggle_dialog_is_pinned_on_server(dialog_id, is_pinned, 0); } return Status::OK(); } class MessagesManager::ToggleDialogIsPinnedOnServerLogEvent { public: DialogId dialog_id_; bool is_pinned_; template void store(StorerT &storer) const { BEGIN_STORE_FLAGS(); STORE_FLAG(is_pinned_); END_STORE_FLAGS(); td::store(dialog_id_, storer); } template void parse(ParserT &parser) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_pinned_); END_PARSE_FLAGS(); td::parse(dialog_id_, parser); } }; uint64 MessagesManager::save_toggle_dialog_is_pinned_on_server_log_event(DialogId dialog_id, bool is_pinned) { ToggleDialogIsPinnedOnServerLogEvent log_event{dialog_id, is_pinned}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ToggleDialogIsPinnedOnServer, get_log_event_storer(log_event)); } void MessagesManager::toggle_dialog_is_pinned_on_server(DialogId dialog_id, bool is_pinned, uint64 log_event_id) { CHECK(!td_->auth_manager_->is_bot()); if (log_event_id == 0 && dialog_id.get_type() == DialogType::SecretChat) { // don't even create new binlog events return; } if (log_event_id == 0 && G()->use_message_database()) { log_event_id = save_toggle_dialog_is_pinned_on_server_log_event(dialog_id, is_pinned); } td_->create_handler(get_erase_log_event_promise(log_event_id))->send(dialog_id, is_pinned); } Status MessagesManager::set_pinned_dialogs(DialogListId dialog_list_id, vector dialog_ids) { if (td_->auth_manager_->is_bot()) { return Status::Error(400, "Bots can't reorder pinned chats"); } int32 dialog_count = 0; int32 secret_dialog_count = 0; auto dialog_count_limit = get_pinned_dialogs_limit(dialog_list_id); FlatHashSet new_pinned_dialog_ids; for (auto dialog_id : dialog_ids) { Dialog *d = get_dialog_force(dialog_id, "set_pinned_dialogs"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return Status::Error(400, "Can't access the chat"); } if (d->order == DEFAULT_ORDER) { return Status::Error(400, "The chat can't be pinned"); } if (dialog_list_id.is_folder() && d->folder_id != dialog_list_id.get_folder_id()) { return Status::Error(400, "Chat not in the list"); } if (dialog_id.get_type() == DialogType::SecretChat) { secret_dialog_count++; } else { dialog_count++; } if (dialog_count > dialog_count_limit || secret_dialog_count > dialog_count_limit) { return Status::Error(400, "The maximum number of pinned chats exceeded"); } new_pinned_dialog_ids.insert(dialog_id); } if (new_pinned_dialog_ids.size() != dialog_ids.size()) { return Status::Error(400, "Duplicate chats in the list of pinned chats"); } auto *list = get_dialog_list(dialog_list_id); if (list == nullptr) { return Status::Error(400, "Chat list not found"); } if (!list->are_pinned_dialogs_inited_) { return Status::Error(400, "Pinned chats must be loaded first"); } auto pinned_dialog_ids = get_pinned_dialog_ids(dialog_list_id); if (pinned_dialog_ids == dialog_ids) { return Status::OK(); } LOG(INFO) << "Reorder pinned chats in " << dialog_list_id << " from " << pinned_dialog_ids << " to " << dialog_ids; auto server_old_dialog_ids = remove_secret_chat_dialog_ids(pinned_dialog_ids); auto server_new_dialog_ids = remove_secret_chat_dialog_ids(dialog_ids); if (dialog_list_id.is_filter()) { return td_->dialog_filter_manager_->set_pinned_dialog_ids( dialog_list_id.get_filter_id(), transform(dialog_ids, [this](DialogId dialog_id) { return get_input_dialog_id(dialog_id); }), server_old_dialog_ids != server_new_dialog_ids); } CHECK(dialog_list_id.is_folder()); std::reverse(pinned_dialog_ids.begin(), pinned_dialog_ids.end()); std::reverse(dialog_ids.begin(), dialog_ids.end()); FlatHashSet old_pinned_dialog_ids; for (auto dialog_id : pinned_dialog_ids) { old_pinned_dialog_ids.insert(dialog_id); } auto old_it = pinned_dialog_ids.begin(); for (auto dialog_id : dialog_ids) { old_pinned_dialog_ids.erase(dialog_id); while (old_it < pinned_dialog_ids.end()) { if (*old_it == dialog_id) { break; } ++old_it; } if (old_it < pinned_dialog_ids.end()) { // leave dialog where it is ++old_it; continue; } set_dialog_is_pinned(dialog_id, true); } for (auto dialog_id : old_pinned_dialog_ids) { Dialog *d = get_dialog_force(dialog_id, "set_pinned_dialogs 2"); if (d == nullptr) { LOG(ERROR) << "Failed to find " << dialog_id << " to unpin in " << dialog_list_id; force_create_dialog(dialog_id, "set_pinned_dialogs", true); d = get_dialog_force(dialog_id, "set_pinned_dialogs 3"); } if (d != nullptr) { set_dialog_is_pinned(dialog_list_id, d, false); } } if (server_old_dialog_ids != server_new_dialog_ids) { reorder_pinned_dialogs_on_server(dialog_list_id.get_folder_id(), server_new_dialog_ids, 0); } return Status::OK(); } class MessagesManager::ReorderPinnedDialogsOnServerLogEvent { public: FolderId folder_id_; vector dialog_ids_; template void store(StorerT &storer) const { td::store(folder_id_, storer); td::store(dialog_ids_, storer); } template void parse(ParserT &parser) { if (parser.version() >= static_cast(Version::AddFolders)) { td::parse(folder_id_, parser); } else { folder_id_ = FolderId(); } td::parse(dialog_ids_, parser); } }; uint64 MessagesManager::save_reorder_pinned_dialogs_on_server_log_event(FolderId folder_id, const vector &dialog_ids) { ReorderPinnedDialogsOnServerLogEvent log_event{folder_id, dialog_ids}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ReorderPinnedDialogsOnServer, get_log_event_storer(log_event)); } void MessagesManager::reorder_pinned_dialogs_on_server(FolderId folder_id, const vector &dialog_ids, uint64 log_event_id) { if (log_event_id == 0 && G()->use_message_database()) { log_event_id = save_reorder_pinned_dialogs_on_server_log_event(folder_id, dialog_ids); } td_->create_handler(get_erase_log_event_promise(log_event_id)) ->send(folder_id, dialog_ids); } Status MessagesManager::toggle_dialog_view_as_messages(DialogId dialog_id, bool view_as_messages) { Dialog *d = get_dialog_force(dialog_id, "toggle_dialog_view_as_messages"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return Status::Error(400, "Can't access the chat"); } if (!is_forum_channel(dialog_id)) { return Status::Error(400, "The method is available only in forum channels"); } if (view_as_messages == d->view_as_messages) { return Status::OK(); } set_dialog_view_as_messages(d, view_as_messages); toggle_dialog_view_as_messages_on_server(dialog_id, view_as_messages, 0); return Status::OK(); } class MessagesManager::ToggleDialogViewAsMessagesOnServerLogEvent { public: DialogId dialog_id_; bool view_as_messages_; template void store(StorerT &storer) const { BEGIN_STORE_FLAGS(); STORE_FLAG(view_as_messages_); END_STORE_FLAGS(); td::store(dialog_id_, storer); } template void parse(ParserT &parser) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(view_as_messages_); END_PARSE_FLAGS(); td::parse(dialog_id_, parser); } }; uint64 MessagesManager::save_toggle_dialog_view_as_messages_on_server_log_event(DialogId dialog_id, bool view_as_messages) { ToggleDialogViewAsMessagesOnServerLogEvent log_event{dialog_id, view_as_messages}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ToggleDialogViewAsMessagesOnServer, get_log_event_storer(log_event)); } void MessagesManager::toggle_dialog_view_as_messages_on_server(DialogId dialog_id, bool view_as_messages, uint64 log_event_id) { if (log_event_id == 0 && G()->use_message_database()) { log_event_id = save_toggle_dialog_view_as_messages_on_server_log_event(dialog_id, view_as_messages); } td_->create_handler(get_erase_log_event_promise(log_event_id)) ->send(dialog_id, view_as_messages); } Status MessagesManager::toggle_dialog_is_marked_as_unread(DialogId dialog_id, bool is_marked_as_unread) { Dialog *d = get_dialog_force(dialog_id, "toggle_dialog_is_marked_as_unread"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return Status::Error(400, "Can't access the chat"); } if (is_marked_as_unread == d->is_marked_as_unread) { return Status::OK(); } set_dialog_is_marked_as_unread(d, is_marked_as_unread); toggle_dialog_is_marked_as_unread_on_server(dialog_id, is_marked_as_unread, 0); return Status::OK(); } class MessagesManager::ToggleDialogIsMarkedAsUnreadOnServerLogEvent { public: DialogId dialog_id_; bool is_marked_as_unread_; template void store(StorerT &storer) const { BEGIN_STORE_FLAGS(); STORE_FLAG(is_marked_as_unread_); END_STORE_FLAGS(); td::store(dialog_id_, storer); } template void parse(ParserT &parser) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_marked_as_unread_); END_PARSE_FLAGS(); td::parse(dialog_id_, parser); } }; uint64 MessagesManager::save_toggle_dialog_is_marked_as_unread_on_server_log_event(DialogId dialog_id, bool is_marked_as_unread) { ToggleDialogIsMarkedAsUnreadOnServerLogEvent log_event{dialog_id, is_marked_as_unread}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ToggleDialogIsMarkedAsUnreadOnServer, get_log_event_storer(log_event)); } void MessagesManager::toggle_dialog_is_marked_as_unread_on_server(DialogId dialog_id, bool is_marked_as_unread, uint64 log_event_id) { if (log_event_id == 0 && dialog_id.get_type() == DialogType::SecretChat) { // don't even create new binlog events return; } if (log_event_id == 0 && G()->use_message_database()) { log_event_id = save_toggle_dialog_is_marked_as_unread_on_server_log_event(dialog_id, is_marked_as_unread); } td_->create_handler(get_erase_log_event_promise(log_event_id)) ->send(dialog_id, is_marked_as_unread); } Status MessagesManager::toggle_dialog_is_translatable(DialogId dialog_id, bool is_translatable) { Dialog *d = get_dialog_force(dialog_id, "toggle_dialog_is_translatable"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return Status::Error(400, "Can't access the chat"); } if (is_translatable == d->is_translatable) { return Status::OK(); } set_dialog_is_translatable(d, is_translatable); toggle_dialog_is_translatable_on_server(dialog_id, is_translatable, 0); return Status::OK(); } class MessagesManager::ToggleDialogIsTranslatableOnServerLogEvent { public: DialogId dialog_id_; bool is_translatable_; template void store(StorerT &storer) const { BEGIN_STORE_FLAGS(); STORE_FLAG(is_translatable_); END_STORE_FLAGS(); td::store(dialog_id_, storer); } template void parse(ParserT &parser) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_translatable_); END_PARSE_FLAGS(); td::parse(dialog_id_, parser); } }; uint64 MessagesManager::save_toggle_dialog_is_translatable_on_server_log_event(DialogId dialog_id, bool is_translatable) { ToggleDialogIsTranslatableOnServerLogEvent log_event{dialog_id, is_translatable}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ToggleDialogIsTranslatableOnServer, get_log_event_storer(log_event)); } void MessagesManager::toggle_dialog_is_translatable_on_server(DialogId dialog_id, bool is_translatable, uint64 log_event_id) { if (log_event_id == 0 && dialog_id.get_type() == DialogType::SecretChat) { // don't even create new binlog events return; } if (log_event_id == 0 && G()->use_message_database()) { log_event_id = save_toggle_dialog_is_translatable_on_server_log_event(dialog_id, is_translatable); } td_->create_handler(get_erase_log_event_promise(log_event_id)) ->send(dialog_id, is_translatable); } Status MessagesManager::set_message_sender_block_list(const td_api::object_ptr &sender, const td_api::object_ptr &block_list) { TRY_RESULT(dialog_id, get_message_sender_dialog_id(td_, sender, true, false)); BlockListId block_list_id(block_list); bool is_blocked = block_list_id == BlockListId::main(); bool is_blocked_for_stories = block_list_id == BlockListId::stories(); switch (dialog_id.get_type()) { case DialogType::User: if (dialog_id == get_my_dialog_id()) { return Status::Error( 400, is_blocked || is_blocked_for_stories ? Slice("Can't block self") : Slice("Can't unblock self")); } break; case DialogType::Chat: return Status::Error(400, "Basic group chats can't be blocked"); case DialogType::Channel: // ok break; case DialogType::SecretChat: { auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (!user_id.is_valid() || !td_->contacts_manager_->have_user_force(user_id, "set_message_sender_block_list")) { return Status::Error(400, "The secret chat can't be blocked"); } dialog_id = DialogId(user_id); break; } case DialogType::None: default: UNREACHABLE(); } Dialog *d = get_dialog_force(dialog_id, "set_message_sender_block_list"); if (!have_input_peer(dialog_id, AccessRights::Know)) { return Status::Error(400, "Message sender isn't accessible"); } if (d != nullptr) { if (is_blocked == d->is_blocked && is_blocked_for_stories == d->is_blocked_for_stories) { return Status::OK(); } set_dialog_is_blocked(d, is_blocked, is_blocked_for_stories); } else { CHECK(dialog_id.get_type() == DialogType::User); td_->contacts_manager_->on_update_user_is_blocked(dialog_id.get_user_id(), is_blocked, is_blocked_for_stories); } toggle_dialog_is_blocked_on_server(dialog_id, is_blocked, is_blocked_for_stories, 0); return Status::OK(); } class MessagesManager::ToggleDialogIsBlockedOnServerLogEvent { public: DialogId dialog_id_; bool is_blocked_; bool is_blocked_for_stories_; template void store(StorerT &storer) const { BEGIN_STORE_FLAGS(); STORE_FLAG(is_blocked_); STORE_FLAG(is_blocked_for_stories_); END_STORE_FLAGS(); td::store(dialog_id_, storer); } template void parse(ParserT &parser) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_blocked_); PARSE_FLAG(is_blocked_for_stories_); END_PARSE_FLAGS(); td::parse(dialog_id_, parser); } }; uint64 MessagesManager::save_toggle_dialog_is_blocked_on_server_log_event(DialogId dialog_id, bool is_blocked, bool is_blocked_for_stories) { ToggleDialogIsBlockedOnServerLogEvent log_event{dialog_id, is_blocked, is_blocked_for_stories}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ToggleDialogIsBlockedOnServer, get_log_event_storer(log_event)); } void MessagesManager::toggle_dialog_is_blocked_on_server(DialogId dialog_id, bool is_blocked, bool is_blocked_for_stories, uint64 log_event_id) { if (log_event_id == 0 && G()->use_message_database()) { log_event_id = save_toggle_dialog_is_blocked_on_server_log_event(dialog_id, is_blocked, is_blocked_for_stories); } td_->create_handler(get_erase_log_event_promise(log_event_id)) ->send(dialog_id, is_blocked, is_blocked_for_stories); } Status MessagesManager::toggle_dialog_silent_send_message(DialogId dialog_id, bool silent_send_message) { CHECK(!td_->auth_manager_->is_bot()); Dialog *d = get_dialog_force(dialog_id, "toggle_dialog_silent_send_message"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return Status::Error(400, "Can't access the chat"); } if (update_dialog_silent_send_message(d, silent_send_message)) { update_dialog_notification_settings_on_server(dialog_id, false); } return Status::OK(); } class MessagesManager::UpdateDialogNotificationSettingsOnServerLogEvent { 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); } }; void MessagesManager::update_dialog_notification_settings_on_server(DialogId dialog_id, bool from_binlog) { if (td_->auth_manager_->is_bot()) { // just in case return; } if (!from_binlog && td_->notification_settings_manager_->get_input_notify_peer(dialog_id, MessageId()) == nullptr) { // don't even create new binlog events return; } auto d = get_dialog(dialog_id); CHECK(d != nullptr); if (!from_binlog && G()->use_message_database()) { UpdateDialogNotificationSettingsOnServerLogEvent log_event; log_event.dialog_id_ = dialog_id; add_log_event(d->save_notification_settings_log_event_id, get_log_event_storer(log_event), LogEvent::HandlerType::UpdateDialogNotificationSettingsOnServer, "notification settings"); } Promise promise; if (d->save_notification_settings_log_event_id.log_event_id != 0) { d->save_notification_settings_log_event_id.generation++; promise = PromiseCreator::lambda( [actor_id = actor_id(this), dialog_id, generation = d->save_notification_settings_log_event_id.generation](Result result) { if (!G()->close_flag()) { send_closure(actor_id, &MessagesManager::on_updated_dialog_notification_settings, dialog_id, generation); } }); } send_update_dialog_notification_settings_query(d, std::move(promise)); } void MessagesManager::send_update_dialog_notification_settings_query(const Dialog *d, Promise &&promise) { CHECK(!td_->auth_manager_->is_bot()); CHECK(d != nullptr); // TODO do not send two queries simultaneously or use InvokeAfter td_->notification_settings_manager_->update_dialog_notify_settings(d->dialog_id, MessageId(), d->notification_settings, std::move(promise)); } void MessagesManager::on_updated_dialog_notification_settings(DialogId dialog_id, uint64 generation) { CHECK(!td_->auth_manager_->is_bot()); auto d = get_dialog(dialog_id); CHECK(d != nullptr); delete_log_event(d->save_notification_settings_log_event_id, generation, "notification settings"); } Status MessagesManager::set_dialog_client_data(DialogId dialog_id, string &&client_data) { Dialog *d = get_dialog_force(dialog_id, "set_dialog_client_data"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } d->client_data = std::move(client_data); on_dialog_updated(d->dialog_id, "set_dialog_client_data"); return Status::OK(); } bool MessagesManager::is_dialog_inited(const Dialog *d) { return d != nullptr && d->notification_settings.is_synchronized && d->is_last_read_inbox_message_id_inited && d->is_last_read_outbox_message_id_inited; } int32 MessagesManager::get_dialog_mute_until(const Dialog *d) const { CHECK(!td_->auth_manager_->is_bot()); CHECK(d != nullptr); if (d->notification_settings.use_default_mute_until) { auto scope = get_dialog_notification_setting_scope(d->dialog_id); return td_->notification_settings_manager_->get_scope_mute_until(scope); } else { return d->notification_settings.mute_until; } } bool MessagesManager::is_dialog_muted(const Dialog *d) const { return get_dialog_mute_until(d) != 0; } bool MessagesManager::is_dialog_pinned_message_notifications_disabled(const Dialog *d) const { CHECK(!td_->auth_manager_->is_bot()); CHECK(d != nullptr); if (d->notification_settings.use_default_disable_pinned_message_notifications) { auto scope = get_dialog_notification_setting_scope(d->dialog_id); return td_->notification_settings_manager_->get_scope_disable_pinned_message_notifications(scope); } return d->notification_settings.disable_pinned_message_notifications; } bool MessagesManager::is_dialog_mention_notifications_disabled(const Dialog *d) const { CHECK(!td_->auth_manager_->is_bot()); CHECK(d != nullptr); if (d->notification_settings.use_default_disable_mention_notifications) { auto scope = get_dialog_notification_setting_scope(d->dialog_id); return td_->notification_settings_manager_->get_scope_disable_mention_notifications(scope); } return d->notification_settings.disable_mention_notifications; } void MessagesManager::create_dialog(DialogId dialog_id, bool force, Promise &&promise) { if (!have_input_peer(dialog_id, AccessRights::Read)) { if (!have_dialog_info_force(dialog_id, "create dialog")) { return promise.set_error(Status::Error(400, "Chat info not found")); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } } if (force || td_->auth_manager_->is_bot() || dialog_id.get_type() == DialogType::SecretChat) { force_create_dialog(dialog_id, "create dialog"); } else { const Dialog *d = get_dialog_force(dialog_id, "create_dialog"); if (!is_dialog_inited(d)) { return send_get_dialog_query(dialog_id, std::move(promise), 0, "create_dialog"); } } promise.set_value(Unit()); } DialogId MessagesManager::create_new_group_chat(const vector &user_ids, const string &title, MessageTtl message_ttl, int64 &random_id, Promise &&promise) { LOG(INFO) << "Trying to create group chat \"" << title << "\" with members " << format::as_array(user_ids); if (random_id != 0) { // request has already been sent before auto it = created_dialogs_.find(random_id); CHECK(it != created_dialogs_.end()); auto dialog_id = it->second; CHECK(dialog_id.get_type() == DialogType::Chat); CHECK(have_dialog(dialog_id)); created_dialogs_.erase(it); // set default notification settings to newly created chat on_update_dialog_notify_settings(dialog_id, nullptr, "create_new_group_chat"); promise.set_value(Unit()); return dialog_id; } auto new_title = clean_name(title, MAX_TITLE_LENGTH); if (new_title.empty()) { promise.set_error(Status::Error(400, "Title must be non-empty")); return DialogId(); } vector> input_users; for (auto user_id : user_ids) { auto r_input_user = td_->contacts_manager_->get_input_user(user_id); if (r_input_user.is_error()) { promise.set_error(r_input_user.move_as_error()); return DialogId(); } input_users.push_back(r_input_user.move_as_ok()); } do { random_id = Random::secure_int64(); } while (random_id == 0 || created_dialogs_.count(random_id) > 0); created_dialogs_[random_id]; // reserve place for result td_->create_handler(std::move(promise)) ->send(std::move(input_users), new_title, message_ttl, random_id); return DialogId(); } DialogId MessagesManager::create_new_channel_chat(const string &title, bool is_forum, bool is_megagroup, const string &description, const DialogLocation &location, bool for_import, MessageTtl message_ttl, int64 &random_id, Promise &&promise) { LOG(INFO) << "Trying to create " << (is_megagroup ? "supergroup" : "broadcast") << " with title \"" << title << "\", description \"" << description << "\" and " << location; if (random_id != 0) { // request has already been sent before auto it = created_dialogs_.find(random_id); CHECK(it != created_dialogs_.end()); auto dialog_id = it->second; CHECK(dialog_id.get_type() == DialogType::Channel); CHECK(have_dialog(dialog_id)); created_dialogs_.erase(it); // set default notification settings to newly created chat on_update_dialog_notify_settings(dialog_id, nullptr, "create_new_channel_chat"); promise.set_value(Unit()); return dialog_id; } auto new_title = clean_name(title, MAX_TITLE_LENGTH); if (new_title.empty()) { promise.set_error(Status::Error(400, "Title must be non-empty")); return DialogId(); } do { random_id = Random::secure_int64(); } while (random_id == 0 || created_dialogs_.count(random_id) > 0); created_dialogs_[random_id]; // reserve place for result td_->create_handler(std::move(promise)) ->send(new_title, is_forum, is_megagroup, strip_empty_characters(description, MAX_DESCRIPTION_LENGTH), location, for_import, message_ttl, random_id); return DialogId(); } void MessagesManager::create_new_secret_chat(UserId user_id, Promise &&promise) { TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); if (input_user->get_id() != telegram_api::inputUser::ID) { return promise.set_error(Status::Error(400, "Can't create secret chat with the user")); } auto user = static_cast(input_user.get()); send_closure(G()->secret_chats_manager(), &SecretChatsManager::create_chat, UserId(user->user_id_), user->access_hash_, std::move(promise)); } DialogId MessagesManager::migrate_dialog_to_megagroup(DialogId dialog_id, Promise &&promise) { LOG(INFO) << "Trying to convert " << dialog_id << " to supergroup"; if (dialog_id.get_type() != DialogType::Chat) { promise.set_error(Status::Error(400, "Only basic group chats can be converted to supergroup")); return DialogId(); } auto channel_id = td_->contacts_manager_->migrate_chat_to_megagroup(dialog_id.get_chat_id(), promise); if (!channel_id.is_valid()) { return DialogId(); } if (!td_->contacts_manager_->have_channel(channel_id)) { LOG(ERROR) << "Can't find info about supergroup to which the group has migrated"; promise.set_error(Status::Error(400, "Supergroup is not found")); return DialogId(); } auto new_dialog_id = DialogId(channel_id); Dialog *d = get_dialog_force(new_dialog_id, "migrate_dialog_to_megagroup"); if (d == nullptr) { d = add_dialog(new_dialog_id, "migrate_dialog_to_megagroup"); if (d->pts == 0) { d->pts = 1; } update_dialog_pos(d, "migrate_dialog_to_megagroup"); } promise.set_value(Unit()); return new_dialog_id; } bool MessagesManager::is_dialog_opened(DialogId dialog_id) const { const Dialog *d = get_dialog(dialog_id); return d != nullptr && d->open_count > 0; } Status MessagesManager::open_dialog(DialogId dialog_id) { Dialog *d = get_dialog_force(dialog_id, "open_dialog"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } open_dialog(d); return Status::OK(); } Status MessagesManager::close_dialog(DialogId dialog_id) { Dialog *d = get_dialog_force(dialog_id, "close_dialog"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } close_dialog(d); return Status::OK(); } DialogId MessagesManager::get_my_dialog_id() const { return DialogId(td_->contacts_manager_->get_my_id()); } Status MessagesManager::view_messages(DialogId dialog_id, vector message_ids, MessageSource source, bool force_read) { CHECK(!td_->auth_manager_->is_bot()); Dialog *d = get_dialog_force(dialog_id, "view_messages"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return Status::Error(400, "Can't access the chat"); } if (source == MessageSource::Auto) { if (d->open_count > 0) { source = MessageSource::DialogHistory; } else { source = MessageSource::Other; } } bool is_dialog_history = source == MessageSource::DialogHistory || source == MessageSource::MessageThreadHistory || source == MessageSource::ForumTopicHistory; bool need_read = force_read || is_dialog_history; bool need_update_view_count = is_dialog_history || source == MessageSource::HistoryPreview || source == MessageSource::DialogList || source == MessageSource::Other; bool need_mark_download_as_viewed = is_dialog_history || source == MessageSource::HistoryPreview || source == MessageSource::Search || source == MessageSource::Other; bool need_invalidate_authentication_code = dialog_id == DialogId(ContactsManager::get_service_notifications_user_id()) && source == MessageSource::Screenshot; auto dialog_type = dialog_id.get_type(); bool need_screenshot_notification = source == MessageSource::Screenshot && (dialog_type == DialogType::User || dialog_type == DialogType::SecretChat) && can_send_message(dialog_id).is_ok(); if (source == MessageSource::DialogList && dialog_type == DialogType::User) { td_->contacts_manager_->on_view_dialog_active_stories({dialog_id}); } // keep only valid message identifiers size_t pos = 0; for (auto message_id : message_ids) { if (!message_id.is_valid()) { if (message_id.is_valid_scheduled()) { // nothing to do for scheduled messages continue; } if (message_id.is_valid_sponsored()) { if (is_dialog_history) { td_->sponsored_message_manager_->view_sponsored_message(dialog_id, message_id); continue; } else if (source == MessageSource::HistoryPreview || source == MessageSource::Other) { continue; } else { return Status::Error(400, "Can't view the message from the specified source"); } } return Status::Error(400, "Invalid message identifier"); } message_ids[pos++] = message_id; } message_ids.resize(pos); if (message_ids.empty()) { // nothing to do return Status::OK(); } if (source == MessageSource::DialogEventLog) { // nothing more to do return Status::OK(); } for (auto message_id : message_ids) { auto *m = get_message_force(d, message_id, "view_messages 20"); if (m != nullptr) { auto file_ids = get_message_file_ids(m); for (auto file_id : file_ids) { td_->file_manager_->check_local_location_async(file_id, true); } } } // get information about thread of the messages MessageId top_thread_message_id; if (source == MessageSource::MessageThreadHistory) { if (dialog_type != DialogType::Channel || is_broadcast_channel(dialog_id)) { return Status::Error(400, "There are no message threads in the chat"); } std::sort(message_ids.begin(), message_ids.end()); vector top_thread_message_ids; vector media_album_ids; for (auto message_id : message_ids) { auto *m = get_message_force(d, message_id, "view_messages 1"); if (m != nullptr) { top_thread_message_ids.push_back(m->top_thread_message_id); media_album_ids.push_back(m->media_album_id); } } if (!top_thread_message_ids.empty()) { // first we expect a root album, then messages from the thread // the root album can have all messages from their own threads, // or all messages except the first one without thread for automatic forwards size_t thread_start = 0; if (media_album_ids[0] != 0) { thread_start++; while (thread_start < media_album_ids.size() && media_album_ids[thread_start] == media_album_ids[0]) { thread_start++; } } if (thread_start < media_album_ids.size()) { top_thread_message_id = top_thread_message_ids[thread_start]; for (size_t i = thread_start; i < top_thread_message_ids.size(); i++) { if (!top_thread_message_ids[i].is_valid()) { return Status::Error(400, "Messages must be from a message thread"); } if (top_thread_message_ids[i] != top_thread_message_id) { return Status::Error(400, "All messages must be from the same message thread"); } } // do not check album messages to belong to the thread top_thread_message_id } else { // all messages are from the same album; thread of the first message is always the best guess top_thread_message_id = top_thread_message_ids[0]; } } } MessageId max_thread_message_id; if (top_thread_message_id.is_valid()) { const auto *top_m = get_message_force(d, top_thread_message_id, "view_messages 2"); if (top_m != nullptr && !top_m->reply_info.is_comment_) { max_thread_message_id = top_m->reply_info.max_message_id_; } } // get forum topic identifier for the messages MessageId forum_topic_id; if (source == MessageSource::ForumTopicHistory) { if (!is_forum_channel(dialog_id)) { return Status::Error(400, "Chat has no topics"); } for (auto message_id : message_ids) { auto *m = get_message_force(d, message_id, "view_messages 3"); if (m != nullptr) { auto message_forum_topic_id = m->is_topic_message ? m->top_thread_message_id : MessageId(ServerMessageId(1)); if (forum_topic_id.is_valid()) { if (message_forum_topic_id != forum_topic_id) { return Status::Error(400, "All messages must be from the same forum topic"); } } else { forum_topic_id = message_forum_topic_id; } } } } MessageId max_message_id; // max server or local viewed message_id vector read_content_message_ids; vector read_reaction_message_ids; vector new_viewed_message_ids; vector viewed_reaction_message_ids; vector authentication_codes; vector screenshotted_secret_message_ids; for (auto message_id : message_ids) { auto *m = get_message_force(d, message_id, "view_messages 4"); if (m != nullptr) { if (m->message_id.is_server() && m->view_count > 0 && need_update_view_count) { pending_message_views_[dialog_id].message_ids_.insert(m->message_id); } if (!m->message_id.is_yet_unsent() && m->message_id > max_message_id) { max_message_id = m->message_id; } auto message_content_type = m->content->get_type(); if (message_content_type == MessageContentType::LiveLocation) { on_message_live_location_viewed(d, m); } if (need_read && message_content_type != MessageContentType::VoiceNote && message_content_type != MessageContentType::VideoNote && update_message_contains_unread_mention(d, m, false, "view_messages 5")) { CHECK(m->message_id.is_server()); read_content_message_ids.push_back(m->message_id); on_message_changed(d, m, true, "view_messages 6"); } if (need_read && remove_message_unread_reactions(d, m, "view_messages 7")) { CHECK(m->message_id.is_server()); read_reaction_message_ids.push_back(m->message_id); on_message_changed(d, m, true, "view_messages 8"); } auto file_source_id = message_full_id_to_file_source_id_.get({dialog_id, m->message_id}); if (file_source_id.is_valid() && need_mark_download_as_viewed) { LOG(INFO) << "Have " << file_source_id << " for " << m->message_id; CHECK(file_source_id.is_valid()); for (auto file_id : get_message_file_ids(m)) { auto file_view = td_->file_manager_->get_file_view(file_id); CHECK(!file_view.empty()); send_closure(td_->download_manager_actor_, &DownloadManager::update_file_viewed, file_view.get_main_file_id(), file_source_id); } } auto story_full_id = get_message_content_story_full_id(td_, m->content.get()); if (story_full_id.is_valid()) { td_->story_manager_->view_story_message(story_full_id); } if (m->message_id.is_server() && d->open_count > 0) { auto &info = dialog_viewed_messages_[dialog_id]; if (info == nullptr) { info = make_unique(); } auto &view_id = info->message_id_to_view_id[message_id]; if (view_id == 0) { new_viewed_message_ids.push_back(message_id); if (need_poll_message_reactions(d, m)) { viewed_reaction_message_ids.push_back(message_id); } } else { info->recently_viewed_messages.erase(view_id); } view_id = ++info->current_view_id; info->recently_viewed_messages[view_id] = message_id; } if (need_invalidate_authentication_code) { extract_authentication_codes(dialog_id, m, authentication_codes); } if (need_screenshot_notification && !m->is_outgoing) { if ((dialog_type == DialogType::User && m->is_content_secret) || dialog_type == DialogType::SecretChat) { screenshotted_secret_message_ids.push_back(m->message_id); } } } else if (!message_id.is_yet_unsent() && message_id > max_message_id) { if ((d->notification_info != nullptr && message_id <= d->notification_info->max_push_notification_message_id_) || message_id <= d->last_new_message_id || message_id <= max_thread_message_id) { max_message_id = message_id; } } } if (pending_message_views_.count(dialog_id) != 0) { pending_message_views_timeout_.add_timeout_in(dialog_id.get(), MAX_MESSAGE_VIEW_DELAY); if (is_dialog_history) { pending_message_views_[dialog_id].increment_view_counter_ = true; } } if (!read_content_message_ids.empty()) { read_message_contents_on_server(dialog_id, std::move(read_content_message_ids), 0, Auto()); } if (!read_reaction_message_ids.empty()) { for (auto message_id : read_reaction_message_ids) { pending_read_reactions_[{dialog_id, message_id}]++; } auto promise = PromiseCreator::lambda( [actor_id = actor_id(this), dialog_id, read_reaction_message_ids](Result &&result) mutable { send_closure(actor_id, &MessagesManager::on_read_message_reactions, dialog_id, std::move(read_reaction_message_ids), std::move(result)); }); read_message_contents_on_server(dialog_id, std::move(read_reaction_message_ids), 0, std::move(promise)); } if (!new_viewed_message_ids.empty()) { LOG(INFO) << "Have new viewed " << new_viewed_message_ids; auto &info = dialog_viewed_messages_[dialog_id]; CHECK(info != nullptr); CHECK(info->message_id_to_view_id.size() == info->recently_viewed_messages.size()); constexpr size_t MAX_RECENTLY_VIEWED_MESSAGES = 25; while (info->recently_viewed_messages.size() > MAX_RECENTLY_VIEWED_MESSAGES) { auto it = info->recently_viewed_messages.begin(); info->message_id_to_view_id.erase(it->second); info->recently_viewed_messages.erase(it); } if (!viewed_reaction_message_ids.empty()) { queue_message_reactions_reload(dialog_id, viewed_reaction_message_ids); } } if (td_->is_online() && dialog_viewed_messages_.count(dialog_id) != 0) { update_viewed_messages_timeout_.add_timeout_in(dialog_id.get(), UPDATE_VIEWED_MESSAGES_PERIOD); } if (!authentication_codes.empty()) { td_->account_manager_->invalidate_authentication_codes(std::move(authentication_codes)); } if (!screenshotted_secret_message_ids.empty()) { send_screenshot_taken_notification_message(d); } if (!need_read) { return Status::OK(); } if (source == MessageSource::MessageThreadHistory) { if (!top_thread_message_id.is_valid() || !max_message_id.is_valid()) { return Status::OK(); } MessageId prev_last_read_inbox_message_id; max_thread_message_id = MessageId(); Message *top_m = get_message_force(d, top_thread_message_id, "view_messages 9"); if (top_m != nullptr && is_active_message_reply_info(dialog_id, top_m->reply_info)) { prev_last_read_inbox_message_id = top_m->reply_info.last_read_inbox_message_id_; if (top_m->reply_info.update_max_message_ids(MessageId(), max_message_id, MessageId())) { on_message_reply_info_changed(dialog_id, top_m); on_message_changed(d, top_m, true, "view_messages 10"); } max_thread_message_id = top_m->reply_info.max_message_id_; if (is_discussion_message(dialog_id, top_m)) { auto linked_dialog_id = top_m->forward_info->from_dialog_id; auto linked_d = get_dialog(linked_dialog_id); CHECK(linked_d != nullptr); CHECK(linked_dialog_id.get_type() == DialogType::Channel); auto *linked_m = get_message_force(linked_d, top_m->forward_info->from_message_id, "view_messages 11"); if (linked_m != nullptr && is_active_message_reply_info(linked_dialog_id, linked_m->reply_info)) { if (linked_m->reply_info.last_read_inbox_message_id_ < prev_last_read_inbox_message_id) { prev_last_read_inbox_message_id = linked_m->reply_info.last_read_inbox_message_id_; } if (linked_m->reply_info.update_max_message_ids(MessageId(), max_message_id, MessageId())) { on_message_reply_info_changed(linked_dialog_id, linked_m); on_message_changed(linked_d, linked_m, true, "view_messages 12"); } if (linked_m->reply_info.max_message_id_ > max_thread_message_id) { max_thread_message_id = linked_m->reply_info.max_message_id_; } } } } if (max_message_id.get_prev_server_message_id().get() > prev_last_read_inbox_message_id.get_prev_server_message_id().get()) { read_message_thread_history_on_server(d, top_thread_message_id, max_message_id.get_prev_server_message_id(), max_thread_message_id.get_prev_server_message_id()); } return Status::OK(); } if (source == MessageSource::ForumTopicHistory) { if (!forum_topic_id.is_valid() || !max_message_id.is_valid()) { return Status::OK(); } td_->forum_topic_manager_->read_forum_topic_messages(dialog_id, forum_topic_id, max_message_id); return Status::OK(); } CHECK(source == MessageSource::DialogHistory || force_read); read_dialog_inbox(d, max_message_id); if (d->is_marked_as_unread) { set_dialog_is_marked_as_unread(d, false); } return Status::OK(); } void MessagesManager::read_dialog_inbox(Dialog *d, MessageId max_message_id) { if (max_message_id == MessageId()) { return; } CHECK(d != nullptr); CHECK(max_message_id.is_valid()); CHECK(max_message_id.is_server() || max_message_id.is_local()); if (max_message_id <= d->last_read_inbox_message_id) { return; } const MessageId last_read_message_id = max_message_id; const MessageId prev_last_read_inbox_message_id = d->last_read_inbox_message_id; MessageId read_history_on_server_message_id; if (d->dialog_id.get_type() != DialogType::SecretChat) { if (last_read_message_id.get_prev_server_message_id().get() > prev_last_read_inbox_message_id.get_prev_server_message_id().get()) { read_history_on_server_message_id = last_read_message_id.get_prev_server_message_id(); } } else { if (last_read_message_id > prev_last_read_inbox_message_id) { read_history_on_server_message_id = last_read_message_id; } } if (read_history_on_server_message_id.is_valid()) { // add dummy timeout to not try to repair unread_count in read_history_inbox before server request succeeds // the timeout will be overwritten in the read_history_on_server call pending_read_history_timeout_.add_timeout_in(d->dialog_id.get(), 0); } read_history_inbox(d, last_read_message_id, -1, "read_dialog_inbox"); if (read_history_on_server_message_id.is_valid()) { // call read_history_on_server after read_history_inbox to not have delay before request if all messages are read read_history_on_server(d, read_history_on_server_message_id); } } void MessagesManager::finish_get_message_views(DialogId dialog_id, const vector &message_ids) { Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); for (auto message_id : message_ids) { auto *m = get_message(d, message_id); if (m != nullptr) { m->has_get_message_views_query = false; m->need_view_counter_increment = false; } } } void MessagesManager::finish_get_message_extended_media(DialogId dialog_id, const vector &message_ids) { Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); for (auto message_id : message_ids) { auto *m = get_message(d, message_id); if (m != nullptr) { m->has_get_extended_media_query = false; } } } Status MessagesManager::open_message_content(MessageFullId message_full_id) { auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "open_message_content"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } auto *m = get_message_force(d, message_full_id.get_message_id(), "open_message_content"); if (m == nullptr) { return Status::Error(400, "Message not found"); } if (m->message_id.is_scheduled() || m->message_id.is_yet_unsent() || m->is_outgoing) { return Status::OK(); } if (read_message_content(d, m, true, 0, "open_message_content") && (m->message_id.is_server() || dialog_id.get_type() == DialogType::SecretChat)) { read_message_contents_on_server(dialog_id, {m->message_id}, 0, Auto()); } if (m->content->get_type() == MessageContentType::LiveLocation) { on_message_live_location_viewed(d, m); } auto file_ids = get_message_file_ids(m); for (auto file_id : file_ids) { td_->file_manager_->check_local_location_async(file_id, true); } return Status::OK(); } class MessagesManager::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 MessagesManager::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 MessagesManager::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 " << format::as_array(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 m = get_message_force({dialog_id, message_ids[0]}, "read_message_contents_on_server"); if (m != nullptr) { send_closure(G()->secret_chats_manager(), &SecretChatsManager::send_open_message, dialog_id.get_secret_chat_id(), m->random_id, std::move(promise)); } else { promise.set_error(Status::Error(400, "Message not found")); } break; } case DialogType::None: default: UNREACHABLE(); } } void MessagesManager::click_animated_emoji_message(MessageFullId message_full_id, Promise> &&promise) { auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "click_animated_emoji_message"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } auto message_id = get_persistent_message_id(d, message_full_id.get_message_id()); auto *m = get_message_force(d, message_id, "click_animated_emoji_message"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } if (m->message_id.is_scheduled() || dialog_id.get_type() != DialogType::User || !m->message_id.is_server()) { return promise.set_value(nullptr); } get_message_content_animated_emoji_click_sticker(m->content.get(), message_full_id, td_, std::move(promise)); } void MessagesManager::open_dialog(Dialog *d) { CHECK(!td_->auth_manager_->is_bot()); DialogId dialog_id = d->dialog_id; if (!have_input_peer(dialog_id, AccessRights::Read)) { return; } recently_opened_dialogs_.add_dialog(dialog_id); if (d->open_count == std::numeric_limits::max()) { return; } d->open_count++; if (d->open_count != 1) { return; } d->was_opened = true; auto min_message_id = MessageId(ServerMessageId(1)); if (d->last_message_id == MessageId() && d->last_read_outbox_message_id < min_message_id) { auto it = d->ordered_messages.get_const_iterator(MessageId::max()); if (*it != nullptr && (*it)->get_message_id() < min_message_id) { read_history_inbox(d, (*it)->get_message_id(), -1, "open_dialog"); } } if (d->has_unload_timeout) { LOG(INFO) << "Cancel unload timeout for " << dialog_id; pending_unload_dialog_timeout_.cancel_timeout(dialog_id.get()); d->has_unload_timeout = false; } if (d->notification_info != nullptr && d->notification_info->new_secret_chat_notification_id_.is_valid()) { remove_new_secret_chat_notification(d, true); } get_dialog_pinned_message(dialog_id, Auto()); if (d->active_group_call_id.is_valid()) { td_->group_call_manager_->reload_group_call(d->active_group_call_id, Auto()); } if (d->need_drop_default_send_message_as_dialog_id) { CHECK(d->default_send_message_as_dialog_id.is_valid()); d->need_drop_default_send_message_as_dialog_id = false; d->default_send_message_as_dialog_id = DialogId(); LOG(INFO) << "Set message sender in " << d->dialog_id << " to " << d->default_send_message_as_dialog_id; on_dialog_updated(dialog_id, "open_dialog"); send_update_chat_message_sender(d); } switch (dialog_id.get_type()) { case DialogType::User: td_->contacts_manager_->on_view_dialog_active_stories({dialog_id}); break; case DialogType::Chat: td_->contacts_manager_->repair_chat_participants(dialog_id.get_chat_id()); reget_dialog_action_bar(dialog_id, "open_dialog", false); break; case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); if (!is_broadcast_channel(dialog_id)) { auto participant_count = td_->contacts_manager_->get_channel_participant_count(channel_id); auto has_hidden_participants = td_->contacts_manager_->get_channel_effective_has_hidden_participants( dialog_id.get_channel_id(), "open_dialog"); if (participant_count < 195 && !has_hidden_participants) { // include unknown participant_count td_->contacts_manager_->get_channel_participants( channel_id, td_api::make_object(), string(), 0, 200, 200, Auto()); } } else { td_->contacts_manager_->on_view_dialog_active_stories({dialog_id}); } get_channel_difference(dialog_id, d->pts, 0, MessageId(), true, "open_dialog"); reget_dialog_action_bar(dialog_id, "open_dialog", false); if (td_->contacts_manager_->get_channel_has_linked_channel(channel_id)) { auto linked_channel_id = td_->contacts_manager_->get_channel_linked_channel_id(channel_id, "open_dialog"); if (!linked_channel_id.is_valid()) { // load linked_channel_id send_closure_later(G()->contacts_manager(), &ContactsManager::load_channel_full, channel_id, false, Promise(), "open_dialog"); } else { get_dialog_info_full(DialogId(linked_channel_id), Auto(), "open_dialog"); } } break; } case DialogType::SecretChat: { // to repair dialog action bar auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (user_id.is_valid()) { td_->contacts_manager_->reload_user_full(user_id, Promise(), "open_dialog"); } break; } case DialogType::None: default: UNREACHABLE(); } if (!td_->auth_manager_->is_bot()) { auto online_count_it = dialog_online_member_counts_.find(dialog_id); if (online_count_it != dialog_online_member_counts_.end()) { auto &info = online_count_it->second; CHECK(!info.is_update_sent); if (Time::now() - info.update_time < ONLINE_MEMBER_COUNT_CACHE_EXPIRE_TIME) { info.is_update_sent = true; send_update_chat_online_member_count(dialog_id, info.online_member_count); } } if (d->has_scheduled_database_messages && !d->is_has_scheduled_database_messages_checked) { CHECK(G()->use_message_database()); LOG(INFO) << "Send check has_scheduled_database_messages request"; d->is_has_scheduled_database_messages_checked = true; G()->td_db()->get_message_db_async()->get_scheduled_messages( dialog_id, 1, PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](vector messages) { if (messages.empty()) { send_closure(actor_id, &MessagesManager::set_dialog_has_scheduled_database_messages, dialog_id, false); } })); } } } void MessagesManager::close_dialog(Dialog *d) { if (d->open_count == 0) { return; } d->open_count--; if (d->open_count > 0) { return; } auto dialog_id = d->dialog_id; if (have_input_peer(dialog_id, AccessRights::Write)) { if (pending_draft_message_timeout_.has_timeout(dialog_id.get())) { pending_draft_message_timeout_.set_timeout_in(dialog_id.get(), 0.0); } } else { pending_draft_message_timeout_.cancel_timeout(dialog_id.get()); } if (have_input_peer(dialog_id, AccessRights::Read)) { if (pending_message_views_timeout_.has_timeout(dialog_id.get())) { pending_message_views_timeout_.set_timeout_in(dialog_id.get(), 0.0); } if (pending_read_history_timeout_.has_timeout(dialog_id.get())) { pending_read_history_timeout_.set_timeout_in(dialog_id.get(), 0.0); } } else { pending_message_views_timeout_.cancel_timeout(dialog_id.get()); pending_message_views_.erase(dialog_id); pending_read_history_timeout_.cancel_timeout(dialog_id.get()); } if (is_message_unload_enabled()) { CHECK(!d->has_unload_timeout); pending_unload_dialog_timeout_.set_timeout_in(dialog_id.get(), get_next_unload_dialog_delay(d)); d->has_unload_timeout = true; if (d->need_unload_on_close) { unload_dialog(dialog_id, 0); d->need_unload_on_close = false; } } dialog_viewed_messages_.erase(dialog_id); update_viewed_messages_timeout_.cancel_timeout(dialog_id.get()); auto live_locations_it = pending_viewed_live_locations_.find(dialog_id); if (live_locations_it != pending_viewed_live_locations_.end()) { for (auto &it : live_locations_it->second) { auto live_location_task_id = it.second; auto erased_count = viewed_live_location_tasks_.erase(live_location_task_id); CHECK(erased_count > 0); } pending_viewed_live_locations_.erase(live_locations_it); } switch (dialog_id.get_type()) { case DialogType::User: break; case DialogType::Chat: break; case DialogType::Channel: channel_get_difference_timeout_.cancel_timeout(dialog_id.get()); break; case DialogType::SecretChat: break; case DialogType::None: default: UNREACHABLE(); } if (!td_->auth_manager_->is_bot()) { if (postponed_chat_read_inbox_updates_.erase(dialog_id) > 0) { send_update_chat_read_inbox(d, false, "close_dialog"); } auto online_count_it = dialog_online_member_counts_.find(dialog_id); if (online_count_it != dialog_online_member_counts_.end()) { auto &info = online_count_it->second; info.is_update_sent = false; } update_dialog_online_member_count_timeout_.set_timeout_in(dialog_id.get(), ONLINE_MEMBER_COUNT_CACHE_EXPIRE_TIME); } } int64 MessagesManager::get_chat_id_object(DialogId dialog_id, const char *source) const { const Dialog *d = get_dialog(dialog_id); if (d == nullptr) { if (dialog_id != DialogId()) { LOG(ERROR) << "Can't find " << dialog_id << ", needed from " << source; } } else if (!d->is_update_new_chat_sent && !d->is_update_new_chat_being_sent) { LOG(ERROR) << "Didn't send updateNewChat for " << dialog_id << ", needed from " << source; } return dialog_id.get(); } vector MessagesManager::get_chat_ids_object(const vector &dialog_ids, const char *source) const { return transform(dialog_ids, [this, source](DialogId dialog_id) { return get_chat_id_object(dialog_id, source); }); } td_api::object_ptr MessagesManager::get_chat_type_object(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: return td_api::make_object( td_->contacts_manager_->get_user_id_object(dialog_id.get_user_id(), "chatTypePrivate")); case DialogType::Chat: return td_api::make_object( td_->contacts_manager_->get_basic_group_id_object(dialog_id.get_chat_id(), "chatTypeBasicGroup")); case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); return td_api::make_object( td_->contacts_manager_->get_supergroup_id_object(channel_id, "chatTypeSupergroup"), !td_->contacts_manager_->is_megagroup_channel(channel_id)); } case DialogType::SecretChat: { auto secret_chat_id = dialog_id.get_secret_chat_id(); auto user_id = td_->contacts_manager_->get_secret_chat_user_id(secret_chat_id); return td_api::make_object( td_->contacts_manager_->get_secret_chat_id_object(secret_chat_id, "chatTypeSecret"), td_->contacts_manager_->get_user_id_object(user_id, "chatTypeSecret")); } case DialogType::None: default: UNREACHABLE(); return nullptr; } } td_api::object_ptr MessagesManager::get_chat_action_bar_object(const Dialog *d) const { CHECK(d != nullptr); auto dialog_type = d->dialog_id.get_type(); if (dialog_type == DialogType::SecretChat) { auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id()); if (!user_id.is_valid()) { return nullptr; } const Dialog *user_d = get_dialog(DialogId(user_id)); if (user_d == nullptr || user_d->action_bar == nullptr) { return nullptr; } return user_d->action_bar->get_chat_action_bar_object(DialogType::User, d->folder_id != FolderId::archive()); } if (d->action_bar == nullptr) { return nullptr; } return d->action_bar->get_chat_action_bar_object(dialog_type, false); } td_api::object_ptr MessagesManager::get_chat_background_object(const Dialog *d) const { CHECK(d != nullptr); if (d->dialog_id.get_type() == DialogType::SecretChat) { auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id()); if (!user_id.is_valid()) { return nullptr; } d = get_dialog(DialogId(user_id)); if (d == nullptr) { return nullptr; } } return d->background_info.get_chat_background_object(td_); } string MessagesManager::get_dialog_theme_name(const Dialog *d) const { CHECK(d != nullptr); if (d->dialog_id.get_type() == DialogType::SecretChat) { auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id()); if (!user_id.is_valid()) { return string(); } d = get_dialog(DialogId(user_id)); if (d == nullptr) { return string(); } } return d->theme_name; } td_api::object_ptr MessagesManager::get_chat_join_requests_info_object( const Dialog *d) const { if (d->pending_join_request_count == 0) { return nullptr; } return td_api::make_object( d->pending_join_request_count, td_->contacts_manager_->get_user_ids_object(d->pending_join_request_user_ids, "get_chat_join_requests_info_object")); } td_api::object_ptr MessagesManager::get_video_chat_object(const Dialog *d) const { auto active_group_call_id = td_->group_call_manager_->get_group_call_id(d->active_group_call_id, d->dialog_id); auto default_participant_alias = d->default_join_group_call_as_dialog_id.is_valid() ? get_message_sender_object_const(td_, d->default_join_group_call_as_dialog_id, "get_video_chat_object") : nullptr; return make_tl_object(active_group_call_id.get(), active_group_call_id.is_valid() ? !d->is_group_call_empty : false, std::move(default_participant_alias)); } td_api::object_ptr MessagesManager::get_default_message_sender_object(const Dialog *d) const { auto as_dialog_id = d->default_send_message_as_dialog_id; return as_dialog_id.is_valid() ? get_message_sender_object_const(td_, as_dialog_id, "get_default_message_sender_object") : nullptr; } td_api::object_ptr MessagesManager::get_chat_object(const Dialog *d) const { CHECK(d != nullptr); bool is_premium = td_->option_manager_->get_option_boolean("is_premium"); auto chat_source = is_dialog_sponsored(d) ? sponsored_dialog_source_.get_chat_source_object() : nullptr; auto can_delete = can_delete_dialog(d); // TODO hide/show draft message when need_hide_dialog_draft_message changes auto draft_message = !need_hide_dialog_draft_message(d) ? get_draft_message_object(td_, d->draft_message) : nullptr; auto available_reactions = get_dialog_active_reactions(d).get_chat_available_reactions_object(); auto is_translatable = d->is_translatable && is_premium; auto block_list_id = BlockListId(d->is_blocked, d->is_blocked_for_stories); return make_tl_object( d->dialog_id.get(), get_chat_type_object(d->dialog_id), get_dialog_title(d->dialog_id), get_chat_photo_info_object(td_->file_manager_.get(), get_dialog_photo(d->dialog_id)), get_dialog_accent_color_id_object(d->dialog_id), get_dialog_background_custom_emoji_id(d->dialog_id).get(), get_dialog_profile_accent_color_id_object(d->dialog_id), get_dialog_profile_background_custom_emoji_id(d->dialog_id).get(), get_dialog_default_permissions(d->dialog_id).get_chat_permissions_object(), get_message_object(d->dialog_id, get_message(d, d->last_message_id), "get_chat_object"), get_chat_positions_object(d), get_default_message_sender_object(d), block_list_id.get_block_list_object(), get_dialog_has_protected_content(d->dialog_id), is_translatable, d->is_marked_as_unread, get_dialog_view_as_topics(d), get_dialog_has_scheduled_messages(d), can_delete.for_self_, can_delete.for_all_users_, can_report_dialog(d->dialog_id), d->notification_settings.silent_send_message, d->server_unread_count + d->local_unread_count, d->last_read_inbox_message_id.get(), d->last_read_outbox_message_id.get(), d->unread_mention_count, d->unread_reaction_count, get_chat_notification_settings_object(&d->notification_settings), std::move(available_reactions), d->message_ttl.get_message_auto_delete_time_object(), get_dialog_emoji_status_object(d->dialog_id), get_chat_background_object(d), get_dialog_theme_name(d), get_chat_action_bar_object(d), get_video_chat_object(d), get_chat_join_requests_info_object(d), d->reply_markup_message_id.get(), std::move(draft_message), d->client_data); } td_api::object_ptr MessagesManager::get_chat_object(DialogId dialog_id) { const Dialog *d = get_dialog(dialog_id); if (postponed_chat_read_inbox_updates_.erase(dialog_id) > 0) { send_update_chat_read_inbox(d, true, "get_chat_object"); } return get_chat_object(d); } tl_object_ptr MessagesManager::get_chats_object(int32 total_count, const vector &dialog_ids, const char *source) const { if (total_count == -1) { total_count = narrow_cast(dialog_ids.size()); } return td_api::make_object(total_count, get_chat_ids_object(dialog_ids, source)); } tl_object_ptr MessagesManager::get_chats_object(const std::pair> &dialog_ids, const char *source) const { return get_chats_object(dialog_ids.first, dialog_ids.second, source); } std::pair MessagesManager::get_dialog_mute_until(DialogId dialog_id, const Dialog *d) const { CHECK(!td_->auth_manager_->is_bot()); if (d == nullptr || !d->notification_settings.is_synchronized) { auto scope = get_dialog_notification_setting_scope(dialog_id); return {false, td_->notification_settings_manager_->get_scope_mute_until(scope)}; } return {d->notification_settings.is_use_default_fixed, get_dialog_mute_until(d)}; } int64 MessagesManager::get_dialog_notification_ringtone_id(DialogId dialog_id, const Dialog *d) const { CHECK(!td_->auth_manager_->is_bot()); if (d == nullptr || !d->notification_settings.is_synchronized || is_notification_sound_default(d->notification_settings.sound)) { auto scope = get_dialog_notification_setting_scope(dialog_id); return get_notification_sound_ringtone_id(td_->notification_settings_manager_->get_scope_notification_sound(scope)); } return get_notification_sound_ringtone_id(d->notification_settings.sound); } NotificationSettingsScope MessagesManager::get_dialog_notification_setting_scope(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: case DialogType::SecretChat: return NotificationSettingsScope::Private; case DialogType::Chat: return NotificationSettingsScope::Group; case DialogType::Channel: return is_broadcast_channel(dialog_id) ? NotificationSettingsScope::Channel : NotificationSettingsScope::Group; case DialogType::None: default: UNREACHABLE(); return NotificationSettingsScope::Private; } } StoryNotificationSettings MessagesManager::get_story_notification_settings(DialogId dialog_id) { bool need_dialog_settings = false; bool need_top_dialogs = false; bool are_muted = false; bool hide_sender = false; int64 ringtone_id = 0; const Dialog *d = get_dialog_force(dialog_id, "get_story_notification_settings"); if (d == nullptr || !d->notification_settings.is_synchronized) { need_dialog_settings = true; } auto scope = get_dialog_notification_setting_scope(dialog_id); if (need_dialog_settings || d->notification_settings.use_default_mute_stories) { bool use_default; std::tie(use_default, are_muted) = td_->notification_settings_manager_->get_scope_mute_stories(scope); if (use_default) { auto is_top_dialog = td_->top_dialog_manager_->is_top_dialog(TopDialogCategory::Correspondent, 5, dialog_id); if (is_top_dialog == -1) { need_top_dialogs = true; } else { are_muted = is_top_dialog != 0; } } } else { are_muted = d->notification_settings.mute_stories; } if (need_dialog_settings || d->notification_settings.use_default_hide_story_sender) { hide_sender = td_->notification_settings_manager_->get_scope_hide_story_sender(scope); } else { hide_sender = d->notification_settings.hide_story_sender; } if (need_dialog_settings || is_notification_sound_default(d->notification_settings.story_sound)) { ringtone_id = get_notification_sound_ringtone_id( td_->notification_settings_manager_->get_scope_story_notification_sound(scope)); } else { ringtone_id = get_notification_sound_ringtone_id(d->notification_settings.story_sound); } return {need_dialog_settings, need_top_dialogs, are_muted, hide_sender, ringtone_id}; } vector MessagesManager::get_dialog_notification_settings_exceptions(NotificationSettingsScope scope, bool filter_scope, bool compare_sound, bool force, Promise &&promise) { CHECK(!td_->auth_manager_->is_bot()); bool have_all_dialogs = true; for (const auto &list : dialog_folders_) { if (list.second.folder_last_dialog_date_ != MAX_DIALOG_DATE) { have_all_dialogs = false; } } if (have_all_dialogs || force) { vector ordered_dialogs; auto my_dialog_id = get_my_dialog_id(); for (const auto &list : dialog_folders_) { for (const auto &dialog_date : list.second.ordered_dialogs_) { auto dialog_id = dialog_date.get_dialog_id(); if (filter_scope && get_dialog_notification_setting_scope(dialog_id) != scope) { continue; } if (dialog_id == my_dialog_id) { continue; } const Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); LOG_CHECK(d->folder_id == list.first) << list.first << ' ' << dialog_id << ' ' << d->folder_id << ' ' << d->order; if (d->order == DEFAULT_ORDER) { break; } if (are_default_dialog_notification_settings(d->notification_settings, compare_sound)) { continue; } if (is_dialog_message_notification_disabled(dialog_id, std::numeric_limits::max())) { continue; } ordered_dialogs.push_back(DialogDate(get_dialog_base_order(d), dialog_id)); } } std::sort(ordered_dialogs.begin(), ordered_dialogs.end()); vector result; for (auto &dialog_date : ordered_dialogs) { CHECK(result.empty() || result.back() != dialog_date.get_dialog_id()); result.push_back(dialog_date.get_dialog_id()); } promise.set_value(Unit()); return result; } for (const auto &folder : dialog_folders_) { load_folder_dialog_list(folder.first, MAX_GET_DIALOGS, true); } td_->notification_settings_manager_->get_notify_settings_exceptions(scope, filter_scope, compare_sound, std::move(promise)); return {}; } DialogNotificationSettings *MessagesManager::get_dialog_notification_settings(DialogId dialog_id, bool force) { Dialog *d = get_dialog_force(dialog_id, "get_dialog_notification_settings"); if (d == nullptr) { return nullptr; } if (!force && !have_input_peer(dialog_id, AccessRights::Read)) { return nullptr; } return &d->notification_settings; } Status MessagesManager::set_dialog_notification_settings( DialogId dialog_id, tl_object_ptr &¬ification_settings) { CHECK(!td_->auth_manager_->is_bot()); auto current_settings = get_dialog_notification_settings(dialog_id, false); if (current_settings == nullptr) { return Status::Error(400, "Wrong chat identifier specified"); } if (dialog_id == get_my_dialog_id()) { return Status::Error(400, "Notification settings of the Saved Messages chat can't be changed"); } TRY_RESULT(new_settings, ::td::get_dialog_notification_settings(std::move(notification_settings), current_settings)); if (update_dialog_notification_settings(dialog_id, current_settings, std::move(new_settings))) { update_dialog_notification_settings_on_server(dialog_id, false); } return Status::OK(); } void MessagesManager::reset_all_notification_settings() { CHECK(!td_->auth_manager_->is_bot()); dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr &dialog) { DialogNotificationSettings new_dialog_settings; new_dialog_settings.is_synchronized = true; Dialog *d = dialog.get(); update_dialog_notification_settings(dialog_id, &d->notification_settings, std::move(new_dialog_settings)); }); td_->notification_settings_manager_->reset_scope_notification_settings(); reset_all_notification_settings_on_server(0); } class MessagesManager::ResetAllNotificationSettingsOnServerLogEvent { public: template void store(StorerT &storer) const { } template void parse(ParserT &parser) { } }; uint64 MessagesManager::save_reset_all_notification_settings_on_server_log_event() { ResetAllNotificationSettingsOnServerLogEvent log_event; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ResetAllNotificationSettingsOnServer, get_log_event_storer(log_event)); } void MessagesManager::reset_all_notification_settings_on_server(uint64 log_event_id) { CHECK(!td_->auth_manager_->is_bot()); if (log_event_id == 0) { log_event_id = save_reset_all_notification_settings_on_server_log_event(); } LOG(INFO) << "Reset all notification settings"; td_->notification_settings_manager_->reset_notify_settings(get_erase_log_event_promise(log_event_id)); } tl_object_ptr MessagesManager::get_dialog_history(DialogId dialog_id, MessageId from_message_id, int32 offset, int32 limit, int left_tries, bool only_local, Promise &&promise) { if (limit <= 0) { promise.set_error(Status::Error(400, "Parameter limit must be positive")); return nullptr; } if (limit > MAX_GET_HISTORY) { limit = MAX_GET_HISTORY; } if (offset > 0) { promise.set_error(Status::Error(400, "Parameter offset must be non-positive")); return nullptr; } if (offset <= -MAX_GET_HISTORY) { promise.set_error(Status::Error(400, "Parameter offset must be greater than -100")); return nullptr; } if (offset < -limit) { promise.set_error(Status::Error(400, "Parameter offset must be greater than or equal to -limit")); return nullptr; } const Dialog *d = get_dialog_force(dialog_id, "get_dialog_history"); if (d == nullptr) { promise.set_error(Status::Error(400, "Chat not found")); return nullptr; } if (!have_input_peer(dialog_id, AccessRights::Read)) { promise.set_error(Status::Error(400, "Can't access the chat")); return nullptr; } if (from_message_id == MessageId() || from_message_id.get() > MessageId::max().get()) { from_message_id = MessageId::max(); } if (!from_message_id.is_valid()) { promise.set_error(Status::Error(400, "Invalid value of parameter from_message_id specified")); return nullptr; } LOG(INFO) << "Get " << (only_local ? "local " : "") << "history in " << dialog_id << " from " << from_message_id << " with offset " << offset << " and limit " << limit << ", " << left_tries << " tries left, is_empty = " << d->is_empty << ", have_full_history = " << d->have_full_history << ", have_full_history_source = " << d->have_full_history_source; auto message_ids = d->ordered_messages.get_history(d->last_message_id, from_message_id, offset, limit, left_tries == 0 && !only_local); if (!message_ids.empty()) { // maybe need some messages CHECK(offset == 0); preload_newer_messages(d, message_ids[0]); preload_older_messages(d, message_ids.back()); } else if (limit > 0 && left_tries != 0 && !(d->is_empty && d->have_full_history && left_tries < 3)) { // there can be more messages in the database or on the server, need to load them send_closure_later(actor_id(this), &MessagesManager::load_messages, dialog_id, from_message_id, offset, limit, left_tries, only_local, std::move(promise)); return nullptr; } LOG(INFO) << "Return " << message_ids << " in result to getChatHistory"; promise.set_value(Unit()); // can return some messages return get_messages_object(-1, dialog_id, message_ids, true, "get_dialog_history"); // TODO return real total_count of messages in the dialog } class MessagesManager::ReadHistoryOnServerLogEvent { public: DialogId dialog_id_; MessageId max_message_id_; template void store(StorerT &storer) const { td::store(dialog_id_, storer); td::store(max_message_id_, storer); } template void parse(ParserT &parser) { td::parse(dialog_id_, parser); td::parse(max_message_id_, parser); } }; class MessagesManager::ReadHistoryInSecretChatLogEvent { public: DialogId dialog_id_; int32 max_date_ = 0; template void store(StorerT &storer) const { td::store(dialog_id_, storer); td::store(max_date_, storer); } template void parse(ParserT &parser) { td::parse(dialog_id_, parser); td::parse(max_date_, parser); } }; class MessagesManager::ReadMessageThreadHistoryOnServerLogEvent { public: DialogId dialog_id_; MessageId top_thread_message_id_; MessageId max_message_id_; template void store(StorerT &storer) const { td::store(dialog_id_, storer); td::store(top_thread_message_id_, storer); td::store(max_message_id_, storer); } template void parse(ParserT &parser) { td::parse(dialog_id_, parser); td::parse(top_thread_message_id_, parser); td::parse(max_message_id_, parser); } }; void MessagesManager::read_history_on_server(Dialog *d, MessageId max_message_id) { if (td_->auth_manager_->is_bot()) { return; } CHECK(d != nullptr); CHECK(!max_message_id.is_scheduled()); auto dialog_id = d->dialog_id; bool is_secret = dialog_id.get_type() == DialogType::SecretChat; bool need_delay = d->open_count > 0 && !is_secret && (d->server_unread_count > 0 || (!need_unread_counter(d->order) && d->last_message_id.is_valid() && max_message_id < d->last_message_id)); LOG(INFO) << "Read history in " << dialog_id << " on server up to " << max_message_id << " with" << (need_delay ? "" : "out") << " delay"; if (is_secret) { auto *m = get_message_force(d, max_message_id, "read_history_on_server"); if (m == nullptr) { LOG(ERROR) << "Failed to read history in " << dialog_id << " up to " << max_message_id; return; } ReadHistoryInSecretChatLogEvent log_event; log_event.dialog_id_ = dialog_id; log_event.max_date_ = m->date; add_log_event(read_history_log_event_ids_[dialog_id][0], get_log_event_storer(log_event), LogEvent::HandlerType::ReadHistoryInSecretChat, "read history"); d->last_read_inbox_message_date = m->date; } else if (G()->use_message_database()) { ReadHistoryOnServerLogEvent log_event; log_event.dialog_id_ = dialog_id; log_event.max_message_id_ = max_message_id; add_log_event(read_history_log_event_ids_[dialog_id][0], get_log_event_storer(log_event), LogEvent::HandlerType::ReadHistoryOnServer, "read history"); } updated_read_history_message_ids_[dialog_id].insert(MessageId()); pending_read_history_timeout_.set_timeout_in(dialog_id.get(), need_delay ? MIN_READ_HISTORY_DELAY : 0); } void MessagesManager::read_message_thread_history_on_server(Dialog *d, MessageId top_thread_message_id, MessageId max_message_id, MessageId last_message_id) { if (td_->auth_manager_->is_bot()) { return; } CHECK(d != nullptr); CHECK(top_thread_message_id.is_valid()); CHECK(top_thread_message_id.is_server()); CHECK(max_message_id.is_server()); auto dialog_id = d->dialog_id; LOG(INFO) << "Read history in thread of " << top_thread_message_id << " in " << dialog_id << " on server up to " << max_message_id; if (G()->use_message_database()) { ReadMessageThreadHistoryOnServerLogEvent log_event; log_event.dialog_id_ = dialog_id; log_event.top_thread_message_id_ = top_thread_message_id; log_event.max_message_id_ = max_message_id; add_log_event(read_history_log_event_ids_[dialog_id][top_thread_message_id.get()], get_log_event_storer(log_event), LogEvent::HandlerType::ReadMessageThreadHistoryOnServer, "read history"); } updated_read_history_message_ids_[dialog_id].insert(top_thread_message_id); bool need_delay = d->open_count > 0 && last_message_id.is_valid() && max_message_id < last_message_id; pending_read_history_timeout_.set_timeout_in(dialog_id.get(), need_delay ? MIN_READ_HISTORY_DELAY : 0); } void MessagesManager::do_read_history_on_server(DialogId dialog_id) { if (G()->close_flag()) { return; } Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); auto it = updated_read_history_message_ids_.find(dialog_id); if (it != updated_read_history_message_ids_.end()) { auto top_thread_message_ids = std::move(it->second); updated_read_history_message_ids_.erase(it); for (auto top_thread_message_id : top_thread_message_ids) { if (!top_thread_message_id.is_valid()) { read_history_on_server_impl(d, MessageId()); } else { read_message_thread_history_on_server_impl(d, top_thread_message_id, MessageId()); } } } } void MessagesManager::read_history_on_server_impl(Dialog *d, MessageId max_message_id) { CHECK(d != nullptr); CHECK(max_message_id == MessageId() || max_message_id.is_valid()); auto dialog_id = d->dialog_id; { auto message_id = d->last_read_inbox_message_id; if (dialog_id.get_type() != DialogType::SecretChat) { message_id = message_id.get_prev_server_message_id(); } if (message_id > max_message_id) { max_message_id = message_id; } } Promise promise; auto &log_event_id = read_history_log_event_ids_[dialog_id][0]; if (log_event_id.log_event_id != 0) { log_event_id.generation++; promise = PromiseCreator::lambda( [actor_id = actor_id(this), dialog_id, generation = log_event_id.generation](Result result) { if (!G()->close_flag()) { send_closure(actor_id, &MessagesManager::on_read_history_finished, dialog_id, MessageId(), generation); } }); } if (d->need_repair_server_unread_count && need_unread_counter(d->order)) { repair_server_unread_count(dialog_id, d->server_unread_count, "read_history_on_server_impl"); } if (!max_message_id.is_valid() || !have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_value(Unit()); } LOG(INFO) << "Send read history request in " << dialog_id << " up to " << max_message_id; switch (dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: td_->create_handler(std::move(promise))->send(dialog_id, max_message_id); break; case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); td_->create_handler(std::move(promise))->send(channel_id, max_message_id); break; } case DialogType::SecretChat: { auto secret_chat_id = dialog_id.get_secret_chat_id(); auto date = d->last_read_inbox_message_date; auto *m = get_message_force(d, max_message_id, "read_history_on_server_impl"); if (m != nullptr && m->date > date) { date = m->date; } if (date == 0) { LOG(ERROR) << "Don't know last read inbox message date in " << dialog_id; return promise.set_value(Unit()); } send_closure(G()->secret_chats_manager(), &SecretChatsManager::send_read_history, secret_chat_id, date, std::move(promise)); break; } case DialogType::None: default: UNREACHABLE(); } } void MessagesManager::read_message_thread_history_on_server_impl(Dialog *d, MessageId top_thread_message_id, MessageId max_message_id) { CHECK(d != nullptr); CHECK(max_message_id == MessageId() || max_message_id.is_valid()); auto dialog_id = d->dialog_id; CHECK(dialog_id.get_type() == DialogType::Channel); const Message *m = get_message_force(d, top_thread_message_id, "read_message_thread_history_on_server_impl"); if (m != nullptr) { auto message_id = m->reply_info.last_read_inbox_message_id_.get_prev_server_message_id(); if (message_id > max_message_id) { max_message_id = message_id; } } Promise promise; auto &log_event_id = read_history_log_event_ids_[dialog_id][top_thread_message_id.get()]; if (log_event_id.log_event_id != 0) { log_event_id.generation++; promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, top_thread_message_id, generation = log_event_id.generation](Result result) { if (!G()->close_flag()) { send_closure(actor_id, &MessagesManager::on_read_history_finished, dialog_id, top_thread_message_id, generation); } }); } if (!max_message_id.is_valid() || !have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_value(Unit()); } LOG(INFO) << "Send read history request in thread of " << top_thread_message_id << " in " << dialog_id << " up to " << max_message_id; td_->create_handler(std::move(promise))->send(dialog_id, top_thread_message_id, max_message_id); } void MessagesManager::on_read_history_finished(DialogId dialog_id, MessageId top_thread_message_id, uint64 generation) { auto dialog_it = read_history_log_event_ids_.find(dialog_id); if (dialog_it == read_history_log_event_ids_.end()) { return; } auto it = dialog_it->second.find(top_thread_message_id.get()); if (it == dialog_it->second.end()) { return; } delete_log_event(it->second, generation, "read history"); if (it->second.log_event_id == 0) { dialog_it->second.erase(it); if (dialog_it->second.empty()) { read_history_log_event_ids_.erase(dialog_it); } } } template vector MessagesManager::get_message_history_slice(const T &begin, It it, const T &end, MessageId from_message_id, int32 offset, int32 limit) { int32 left_offset = -offset; int32 left_limit = limit + offset; while (left_offset > 0 && it != end) { ++it; left_offset--; left_limit++; } vector message_ids; while (left_limit > 0 && it != begin) { --it; left_limit--; message_ids.push_back(*it); } return message_ids; } std::pair> MessagesManager::get_message_thread_history( DialogId dialog_id, MessageId message_id, MessageId from_message_id, int32 offset, int32 limit, int64 &random_id, Promise &&promise) { if (limit <= 0) { promise.set_error(Status::Error(400, "Parameter limit must be positive")); return {}; } if (limit > MAX_GET_HISTORY) { limit = MAX_GET_HISTORY; } if (offset > 0) { promise.set_error(Status::Error(400, "Parameter offset must be non-positive")); return {}; } if (offset <= -MAX_GET_HISTORY) { promise.set_error(Status::Error(400, "Parameter offset must be greater than -100")); return {}; } if (offset < -limit) { promise.set_error(Status::Error(400, "Parameter offset must be greater than or equal to -limit")); return {}; } bool is_limit_increased = false; if (limit == -offset) { limit++; is_limit_increased = true; } CHECK(0 < limit && limit <= MAX_GET_HISTORY); CHECK(-limit < offset && offset <= 0); Dialog *d = get_dialog_force(dialog_id, "get_message_thread_history"); if (d == nullptr) { promise.set_error(Status::Error(400, "Chat not found")); return {}; } if (!have_input_peer(dialog_id, AccessRights::Read)) { promise.set_error(Status::Error(400, "Can't access the chat")); return {}; } if (dialog_id.get_type() != DialogType::Channel) { promise.set_error(Status::Error(400, "Can't get message thread history in the chat")); return {}; } if (from_message_id == MessageId() || from_message_id.get() > MessageId::max().get()) { from_message_id = MessageId::max(); } if (!from_message_id.is_valid()) { promise.set_error(Status::Error(400, "Parameter from_message_id must be identifier of a chat message or 0")); return {}; } MessageFullId top_thread_message_full_id; if (message_id == MessageId(ServerMessageId(1)) && is_forum_channel(dialog_id)) { top_thread_message_full_id = MessageFullId{dialog_id, message_id}; } else { message_id = get_persistent_message_id(d, message_id); Message *m = get_message_force(d, message_id, "get_message_thread_history 1"); if (m == nullptr) { promise.set_error(Status::Error(400, "Message not found")); return {}; } auto r_top_thread_message_full_id = get_top_thread_message_full_id(dialog_id, m, true); if (r_top_thread_message_full_id.is_error()) { promise.set_error(r_top_thread_message_full_id.move_as_error()); return {}; } top_thread_message_full_id = r_top_thread_message_full_id.move_as_ok(); if ((m->reply_info.is_empty() || !m->reply_info.is_comment_) && top_thread_message_full_id.get_message_id() != m->message_id) { CHECK(dialog_id == top_thread_message_full_id.get_dialog_id()); // get information about the thread from the top message message_id = top_thread_message_full_id.get_message_id(); CHECK(message_id.is_valid()); } if (!top_thread_message_full_id.get_message_id().is_valid()) { CHECK(m->reply_info.is_comment_); get_message_thread( dialog_id, message_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()); } })); return {}; } } if (random_id != 0) { // request has already been sent before auto it = found_dialog_messages_.find(random_id); CHECK(it != found_dialog_messages_.end()); auto result = std::move(it->second.message_ids); found_dialog_messages_.erase(it); auto dialog_id_it = found_dialog_messages_dialog_id_.find(random_id); if (dialog_id_it != found_dialog_messages_dialog_id_.end()) { dialog_id = dialog_id_it->second; found_dialog_messages_dialog_id_.erase(dialog_id_it); d = get_dialog(dialog_id); CHECK(d != nullptr); } if (dialog_id != top_thread_message_full_id.get_dialog_id()) { promise.set_error(Status::Error(500, "Receive messages in an unexpected chat")); return {}; } auto yet_unsent_it = yet_unsent_thread_message_ids_.find(top_thread_message_full_id); if (yet_unsent_it != yet_unsent_thread_message_ids_.end()) { const std::set &message_ids = yet_unsent_it->second; auto merge_message_ids = get_message_history_slice(message_ids.begin(), message_ids.lower_bound(from_message_id), message_ids.end(), from_message_id, offset, limit); vector new_result(result.size() + merge_message_ids.size()); std::merge(result.begin(), result.end(), merge_message_ids.begin(), merge_message_ids.end(), new_result.begin(), std::greater<>()); result = std::move(new_result); } Message *top_m = get_message_force(d, top_thread_message_full_id.get_message_id(), "get_message_thread_history 2"); if (top_m != nullptr && !top_m->local_thread_message_ids.empty()) { vector &message_ids = top_m->local_thread_message_ids; vector merge_message_ids; while (true) { merge_message_ids = get_message_history_slice( message_ids.begin(), std::lower_bound(message_ids.begin(), message_ids.end(), from_message_id), message_ids.end(), from_message_id, offset, limit); bool found_deleted = false; for (auto local_message_id : merge_message_ids) { Message *local_m = get_message_force(d, local_message_id, "get_message_thread_history 3"); if (local_m == nullptr) { auto local_it = std::lower_bound(message_ids.begin(), message_ids.end(), local_message_id); CHECK(local_it != message_ids.end() && *local_it == local_message_id); message_ids.erase(local_it); found_deleted = true; } } if (!found_deleted) { break; } on_message_changed(d, top_m, false, "get_message_thread_history"); } vector new_result(result.size() + merge_message_ids.size()); std::merge(result.begin(), result.end(), merge_message_ids.begin(), merge_message_ids.end(), new_result.begin(), std::greater<>()); result = std::move(new_result); } if (is_limit_increased) { limit--; } std::reverse(result.begin(), result.end()); result = get_message_history_slice(result.begin(), std::lower_bound(result.begin(), result.end(), from_message_id), result.end(), from_message_id, offset, limit); LOG(INFO) << "Return " << result.size() << " messages in result to getMessageThreadHistory"; promise.set_value(Unit()); return {dialog_id, std::move(result)}; } do { random_id = Random::secure_int64(); } while (random_id == 0 || found_dialog_messages_.count(random_id) > 0); found_dialog_messages_[random_id]; // reserve place for result td_->create_handler(std::move(promise)) ->send(dialog_id, string(), DialogId(), from_message_id.get_next_server_message_id(), offset, limit, MessageSearchFilter::Empty, message_id, random_id); return {}; } td_api::object_ptr MessagesManager::get_dialog_message_calendar(DialogId dialog_id, MessageId from_message_id, MessageSearchFilter filter, int64 &random_id, bool use_db, Promise &&promise) { if (random_id != 0) { // request has already been sent before auto it = found_dialog_message_calendars_.find(random_id); if (it != found_dialog_message_calendars_.end()) { auto result = std::move(it->second); found_dialog_message_calendars_.erase(it); promise.set_value(Unit()); return result; } random_id = 0; } LOG(INFO) << "Get message calendar in " << dialog_id << " filtered by " << filter << " from " << from_message_id; if (from_message_id.get() > MessageId::max().get()) { from_message_id = MessageId::max(); } if (!from_message_id.is_valid() && from_message_id != MessageId()) { promise.set_error(Status::Error(400, "Parameter from_message_id must be identifier of a chat message or 0")); return {}; } from_message_id = from_message_id.get_next_server_message_id(); const Dialog *d = get_dialog_force(dialog_id, "get_dialog_message_calendar"); if (d == nullptr) { promise.set_error(Status::Error(400, "Chat not found")); return {}; } if (!have_input_peer(dialog_id, AccessRights::Read)) { promise.set_error(Status::Error(400, "Can't access the chat")); return {}; } do { random_id = Random::secure_int64(); } while (random_id == 0 || found_dialog_message_calendars_.count(random_id) > 0); found_dialog_message_calendars_[random_id]; // reserve place for result CHECK(filter != MessageSearchFilter::Call && filter != MessageSearchFilter::MissedCall); if (filter == MessageSearchFilter::Empty || filter == MessageSearchFilter::Mention || filter == MessageSearchFilter::UnreadMention || filter == MessageSearchFilter::UnreadReaction) { promise.set_error(Status::Error(400, "The filter is not supported")); return {}; } // Trying to use database if (use_db && G()->use_message_database()) { MessageId first_db_message_id = get_first_database_message_id_by_index(d, filter); int32 message_count = d->message_count_by_index[message_search_filter_index(filter)]; auto fixed_from_message_id = from_message_id; if (fixed_from_message_id == MessageId()) { fixed_from_message_id = MessageId::max(); } LOG(INFO) << "Get message calendar in " << dialog_id << " from " << fixed_from_message_id << ", have up to " << first_db_message_id << ", message_count = " << message_count; if (first_db_message_id < fixed_from_message_id && message_count != -1) { LOG(INFO) << "Get message calendar from database in " << dialog_id << " from " << fixed_from_message_id; auto new_promise = PromiseCreator::lambda([random_id, dialog_id, fixed_from_message_id, first_db_message_id, filter, promise = std::move(promise)](Result r_calendar) mutable { send_closure(G()->messages_manager(), &MessagesManager::on_get_message_calendar_from_database, random_id, dialog_id, fixed_from_message_id, first_db_message_id, filter, std::move(r_calendar), std::move(promise)); }); MessageDbDialogCalendarQuery db_query; db_query.dialog_id = dialog_id; db_query.filter = filter; db_query.from_message_id = fixed_from_message_id; db_query.tz_offset = static_cast(td_->option_manager_->get_option_integer("utc_time_offset")); G()->td_db()->get_message_db_async()->get_dialog_message_calendar(db_query, std::move(new_promise)); return {}; } } if (filter == MessageSearchFilter::FailedToSend) { promise.set_value(Unit()); return {}; } LOG(DEBUG) << "Get message calendar from server in " << dialog_id << " from " << from_message_id; switch (dialog_id.get_type()) { case DialogType::None: case DialogType::User: case DialogType::Chat: case DialogType::Channel: td_->create_handler(std::move(promise)) ->send(dialog_id, from_message_id, filter, random_id); break; case DialogType::SecretChat: promise.set_value(Unit()); break; default: UNREACHABLE(); promise.set_error(Status::Error(500, "Message search is not supported")); } return {}; } void MessagesManager::on_get_message_calendar_from_database(int64 random_id, DialogId dialog_id, MessageId from_message_id, MessageId first_db_message_id, MessageSearchFilter filter, Result r_calendar, Promise promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); if (r_calendar.is_error()) { LOG(ERROR) << "Failed to get message calendar from the database: " << r_calendar.error(); if (first_db_message_id != MessageId::min() && dialog_id.get_type() != DialogType::SecretChat && filter != MessageSearchFilter::FailedToSend) { found_dialog_message_calendars_.erase(random_id); } return promise.set_value(Unit()); } CHECK(!from_message_id.is_scheduled()); CHECK(!first_db_message_id.is_scheduled()); auto calendar = r_calendar.move_as_ok(); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); auto it = found_dialog_message_calendars_.find(random_id); CHECK(it != found_dialog_message_calendars_.end()); CHECK(it->second == nullptr); vector> periods; periods.reserve(calendar.messages.size()); for (size_t i = 0; i < calendar.messages.size(); i++) { auto m = on_get_message_from_database(d, calendar.messages[i], false, "on_get_message_calendar_from_database"); if (m != nullptr && first_db_message_id <= m->message_id) { CHECK(!m->message_id.is_scheduled()); periods.emplace_back(m->message_id, calendar.total_counts[i]); } } if (periods.empty() && first_db_message_id != MessageId::min() && dialog_id.get_type() != DialogType::SecretChat) { LOG(INFO) << "No messages found in database"; found_dialog_message_calendars_.erase(it); } else { auto total_count = d->message_count_by_index[message_search_filter_index(filter)]; vector> days; for (auto &period : periods) { const auto *m = get_message(d, period.first); CHECK(m != nullptr); days.push_back(td_api::make_object( period.second, get_message_object(dialog_id, m, "on_get_message_calendar_from_database"))); } it->second = td_api::make_object(total_count, std::move(days)); } promise.set_value(Unit()); } MessagesManager::FoundDialogMessages MessagesManager::search_dialog_messages( DialogId dialog_id, const string &query, const td_api::object_ptr &sender, MessageId from_message_id, int32 offset, int32 limit, MessageSearchFilter filter, MessageId top_thread_message_id, int64 &random_id, bool use_db, Promise &&promise) { if (random_id != 0) { // request has already been sent before auto it = found_dialog_messages_.find(random_id); if (it != found_dialog_messages_.end()) { CHECK(found_dialog_messages_dialog_id_.count(random_id) == 0); auto result = std::move(it->second); found_dialog_messages_.erase(it); promise.set_value(Unit()); return result; } random_id = 0; } LOG(INFO) << "Search messages with query \"" << query << "\" in " << dialog_id << " sent by " << oneline(to_string(sender)) << " in thread of " << top_thread_message_id << " filtered by " << filter << " from " << from_message_id << " with offset " << offset << " and limit " << limit; FoundDialogMessages result; if (limit <= 0) { promise.set_error(Status::Error(400, "Parameter limit must be positive")); return result; } if (limit > MAX_SEARCH_MESSAGES) { limit = MAX_SEARCH_MESSAGES; } if (limit <= -offset) { promise.set_error(Status::Error(400, "Parameter limit must be greater than -offset")); return result; } if (offset > 0) { promise.set_error(Status::Error(400, "Parameter offset must be non-positive")); return result; } if (from_message_id.get() > MessageId::max().get()) { from_message_id = MessageId::max(); } if (!from_message_id.is_valid() && from_message_id != MessageId()) { promise.set_error(Status::Error(400, "Parameter from_message_id must be identifier of a chat message or 0")); return result; } from_message_id = from_message_id.get_next_server_message_id(); const Dialog *d = get_dialog_force(dialog_id, "search_dialog_messages"); if (d == nullptr) { promise.set_error(Status::Error(400, "Chat not found")); return result; } if (!have_input_peer(dialog_id, AccessRights::Read)) { promise.set_error(Status::Error(400, "Can't access the chat")); return result; } auto r_sender_dialog_id = get_message_sender_dialog_id(td_, sender, true, true); if (r_sender_dialog_id.is_error()) { promise.set_error(r_sender_dialog_id.move_as_error()); return result; } auto sender_dialog_id = r_sender_dialog_id.move_as_ok(); if (sender_dialog_id != DialogId() && !have_input_peer(sender_dialog_id, AccessRights::Know)) { promise.set_error(Status::Error(400, "Invalid message sender specified")); return result; } if (sender_dialog_id == dialog_id && is_broadcast_channel(dialog_id)) { sender_dialog_id = DialogId(); } if (filter == MessageSearchFilter::FailedToSend && sender_dialog_id.is_valid()) { if (sender_dialog_id != get_my_dialog_id()) { promise.set_value(Unit()); return result; } sender_dialog_id = DialogId(); } if (top_thread_message_id != MessageId()) { if (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server()) { promise.set_error(Status::Error(400, "Invalid message thread ID specified")); return result; } if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id)) { promise.set_error(Status::Error(400, "Can't filter by message thread ID in the chat")); return result; } } if (sender_dialog_id.get_type() == DialogType::SecretChat) { promise.set_value(Unit()); return result; } do { random_id = Random::secure_int64(); } while (random_id == 0 || found_dialog_messages_.count(random_id) > 0); found_dialog_messages_[random_id]; // reserve place for result if (filter == MessageSearchFilter::UnreadMention || filter == MessageSearchFilter::UnreadReaction) { if (!query.empty()) { promise.set_error(Status::Error(400, "Non-empty query is unsupported with the specified filter")); return result; } if (sender_dialog_id.is_valid()) { promise.set_error(Status::Error(400, "Filtering by sender is unsupported with the specified filter")); return result; } } // Trying to use database if (use_db && query.empty() && G()->use_message_database() && filter != MessageSearchFilter::Empty && !sender_dialog_id.is_valid() && top_thread_message_id == MessageId()) { MessageId first_db_message_id = get_first_database_message_id_by_index(d, filter); int32 message_count = d->message_count_by_index[message_search_filter_index(filter)]; auto fixed_from_message_id = from_message_id; if (fixed_from_message_id == MessageId()) { fixed_from_message_id = MessageId::max(); } LOG(INFO) << "Search messages in " << dialog_id << " from " << fixed_from_message_id << ", have up to " << first_db_message_id << ", message_count = " << message_count; if ((first_db_message_id < fixed_from_message_id || (first_db_message_id == fixed_from_message_id && offset < 0)) && message_count != -1) { LOG(INFO) << "Search messages in database in " << dialog_id << " from " << fixed_from_message_id << " and with limit " << limit; auto new_promise = PromiseCreator::lambda( [random_id, dialog_id, fixed_from_message_id, first_db_message_id, filter, offset, limit, promise = std::move(promise)](Result> r_messages) mutable { send_closure(G()->messages_manager(), &MessagesManager::on_search_dialog_message_db_result, random_id, dialog_id, fixed_from_message_id, first_db_message_id, filter, offset, limit, std::move(r_messages), std::move(promise)); }); MessageDbMessagesQuery db_query; db_query.dialog_id = dialog_id; db_query.filter = filter; db_query.from_message_id = fixed_from_message_id; db_query.offset = offset; db_query.limit = limit; G()->td_db()->get_message_db_async()->get_messages(db_query, std::move(new_promise)); return result; } } if (filter == MessageSearchFilter::FailedToSend) { promise.set_value(Unit()); return result; } LOG(DEBUG) << "Search messages on server in " << dialog_id << " with query \"" << query << "\" from " << sender_dialog_id << " in thread of " << top_thread_message_id << " from " << from_message_id << " and with limit " << limit; switch (dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: case DialogType::Channel: td_->create_handler(std::move(promise)) ->send(dialog_id, query, sender_dialog_id, from_message_id, offset, limit, filter, top_thread_message_id, random_id); break; case DialogType::SecretChat: if (filter == MessageSearchFilter::UnreadMention || filter == MessageSearchFilter::Pinned || filter == MessageSearchFilter::UnreadReaction) { promise.set_value(Unit()); } else { promise.set_error(Status::Error(500, "Message search is not supported in secret chats")); } break; case DialogType::None: default: UNREACHABLE(); promise.set_error(Status::Error(500, "Message search is not supported")); } return result; } MessagesManager::FoundMessages MessagesManager::search_call_messages(const string &offset, int32 limit, bool only_missed, int64 &random_id, bool use_db, Promise &&promise) { if (random_id != 0) { // request has already been sent before auto it = found_call_messages_.find(random_id); if (it != found_call_messages_.end()) { auto result = std::move(it->second); found_call_messages_.erase(it); promise.set_value(Unit()); return result; } random_id = 0; } LOG(INFO) << "Search call messages from " << offset << " with limit " << limit; FoundMessages result; if (limit <= 0) { promise.set_error(Status::Error(400, "Parameter limit must be positive")); return result; } if (limit > MAX_SEARCH_MESSAGES) { limit = MAX_SEARCH_MESSAGES; } MessageId offset_message_id; if (!offset.empty()) { auto r_offset_server_message_id = to_integer_safe(offset); if (r_offset_server_message_id.is_error()) { promise.set_error(Status::Error(400, "Invalid offset specified")); return result; } offset_message_id = MessageId(ServerMessageId(r_offset_server_message_id.ok())); } do { random_id = Random::secure_int64(); } while (random_id == 0 || found_call_messages_.count(random_id) > 0); found_call_messages_[random_id]; // reserve place for result auto filter = only_missed ? MessageSearchFilter::MissedCall : MessageSearchFilter::Call; if (use_db && G()->use_message_database()) { // try to use database MessageId first_db_message_id = calls_db_state_.first_calls_database_message_id_by_index[call_message_search_filter_index(filter)]; int32 message_count = calls_db_state_.message_count_by_index[call_message_search_filter_index(filter)]; auto fixed_from_message_id = offset_message_id; if (fixed_from_message_id == MessageId()) { fixed_from_message_id = MessageId::max(); } CHECK(fixed_from_message_id.is_valid() && fixed_from_message_id.is_server()); LOG(INFO) << "Search call messages from " << fixed_from_message_id << ", have up to " << first_db_message_id << ", message_count = " << message_count; if (first_db_message_id < fixed_from_message_id && message_count != -1) { LOG(INFO) << "Search messages in database from " << fixed_from_message_id << " and with limit " << limit; MessageDbCallsQuery db_query; db_query.filter = filter; db_query.from_unique_message_id = fixed_from_message_id.get_server_message_id().get(); db_query.limit = limit; G()->td_db()->get_message_db_async()->get_calls( db_query, PromiseCreator::lambda([random_id, first_db_message_id, filter, promise = std::move(promise)]( Result calls_result) mutable { send_closure(G()->messages_manager(), &MessagesManager::on_message_db_calls_result, std::move(calls_result), random_id, first_db_message_id, filter, std::move(promise)); })); return result; } } td_->create_handler(std::move(promise)) ->send(DialogId(), "", DialogId(), offset_message_id, 0, limit, filter, MessageId(), random_id); return result; } void MessagesManager::search_outgoing_document_messages(const string &query, int32 limit, Promise> &&promise) { if (limit <= 0) { return promise.set_error(Status::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 MessagesManager::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(Status::Error(400, "Parameter limit must be positive")); } if (limit > MAX_SEARCH_MESSAGES) { limit = MAX_SEARCH_MESSAGES; } const Dialog *d = get_dialog_force(dialog_id, "search_dialog_recent_location_messages"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } 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(get_messages_object(0, vector>(), false)); default: UNREACHABLE(); promise.set_error(Status::Error(500, "Message search is not supported")); } } vector MessagesManager::get_active_live_location_messages(Promise &&promise) { if (!G()->use_message_database()) { are_active_live_location_messages_loaded_ = true; } if (!are_active_live_location_messages_loaded_) { load_active_live_location_messages_queries_.push_back(std::move(promise)); if (load_active_live_location_messages_queries_.size() == 1u) { LOG(INFO) << "Trying to load active live location messages from database"; G()->td_db()->get_sqlite_pmc()->get( "di_active_live_location_messages", PromiseCreator::lambda([](string value) { send_closure(G()->messages_manager(), &MessagesManager::on_load_active_live_location_message_full_ids_from_database, std::move(value)); })); } return {}; } promise.set_value(Unit()); vector result; for (auto &message_full_id : active_live_location_message_full_ids_) { auto m = get_message(message_full_id); CHECK(m != nullptr); CHECK(m->content->get_type() == MessageContentType::LiveLocation); CHECK(!m->message_id.is_scheduled()); if (m->is_failed_to_send) { continue; } auto live_period = get_message_content_live_location_period(m->content.get()); if (live_period <= G()->unix_time() - m->date) { // bool is_expired flag? // live location is expired continue; } result.push_back(message_full_id); } return result; } void MessagesManager::on_load_active_live_location_message_full_ids_from_database(string value) { if (G()->close_flag()) { return; } if (value.empty()) { LOG(INFO) << "Active live location messages aren't found in the database"; on_load_active_live_location_messages_finished(); if (!active_live_location_message_full_ids_.empty()) { save_active_live_locations(); } return; } LOG(INFO) << "Successfully loaded active live location messages list of size " << value.size() << " from database"; auto new_message_full_ids = std::move(active_live_location_message_full_ids_); vector old_message_full_ids; log_event_parse(old_message_full_ids, value).ensure(); // TODO asynchronously load messages from database active_live_location_message_full_ids_.clear(); for (const auto &message_full_id : old_message_full_ids) { Message *m = get_message_force(message_full_id, "on_load_active_live_location_message_full_ids_from_database"); if (m != nullptr) { try_add_active_live_location(message_full_id.get_dialog_id(), m); } } for (const auto &message_full_id : new_message_full_ids) { add_active_live_location(message_full_id); } on_load_active_live_location_messages_finished(); if (!new_message_full_ids.empty() || old_message_full_ids.size() != active_live_location_message_full_ids_.size()) { save_active_live_locations(); } } void MessagesManager::on_load_active_live_location_messages_finished() { are_active_live_location_messages_loaded_ = true; set_promises(load_active_live_location_messages_queries_); } void MessagesManager::try_add_active_live_location(DialogId dialog_id, const Message *m) { CHECK(m != nullptr); if (td_->auth_manager_->is_bot()) { return; } if (m->content->get_type() != MessageContentType::LiveLocation || m->message_id.is_scheduled() || m->message_id.is_local() || m->via_bot_user_id.is_valid() || m->forward_info != nullptr) { return; } auto live_period = get_message_content_live_location_period(m->content.get()); if (live_period <= G()->unix_time() - m->date + 1) { // bool is_expired flag? // live location is expired return; } add_active_live_location({dialog_id, m->message_id}); } void MessagesManager::add_active_live_location(MessageFullId message_full_id) { if (td_->auth_manager_->is_bot()) { return; } CHECK(message_full_id.get_message_id().is_valid()); if (!active_live_location_message_full_ids_.insert(message_full_id).second) { return; } // TODO add timer for live location expiration if (!G()->use_message_database()) { return; } if (are_active_live_location_messages_loaded_) { save_active_live_locations(); } else if (load_active_live_location_messages_queries_.empty()) { // load active live locations and save after that get_active_live_location_messages(Auto()); } } bool MessagesManager::delete_active_live_location(DialogId dialog_id, const Message *m) { CHECK(m != nullptr); return active_live_location_message_full_ids_.erase(MessageFullId{dialog_id, m->message_id}) != 0; } void MessagesManager::save_active_live_locations() { CHECK(are_active_live_location_messages_loaded_); LOG(INFO) << "Save active live locations of size " << active_live_location_message_full_ids_.size() << " to database"; if (G()->use_message_database()) { G()->td_db()->get_sqlite_pmc()->set("di_active_live_location_messages", log_event_store(active_live_location_message_full_ids_).as_slice().str(), Auto()); } } void MessagesManager::on_message_live_location_viewed(Dialog *d, const Message *m) { CHECK(d != nullptr); CHECK(m != nullptr); CHECK(m->content->get_type() == MessageContentType::LiveLocation); CHECK(!m->message_id.is_scheduled()); if (td_->auth_manager_->is_bot()) { // just in case return; } switch (d->dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: case DialogType::Channel: // ok break; case DialogType::SecretChat: return; default: UNREACHABLE(); return; } if (d->open_count == 0) { return; } if (m->is_outgoing || !m->message_id.is_server() || m->via_bot_user_id.is_valid() || !m->sender_user_id.is_valid() || td_->contacts_manager_->is_user_bot(m->sender_user_id) || m->forward_info != nullptr) { return; } auto live_period = get_message_content_live_location_period(m->content.get()); if (live_period <= G()->unix_time() - m->date + 1) { // live location is expired return; } auto &live_location_task_id = pending_viewed_live_locations_[d->dialog_id][m->message_id]; if (live_location_task_id != 0) { return; } live_location_task_id = ++viewed_live_location_task_id_; auto &message_full_id = viewed_live_location_tasks_[live_location_task_id]; message_full_id = MessageFullId(d->dialog_id, m->message_id); view_message_live_location_on_server_impl(live_location_task_id, message_full_id); } void MessagesManager::view_message_live_location_on_server(int64 task_id) { if (G()->close_flag()) { return; } auto it = viewed_live_location_tasks_.find(task_id); if (it == viewed_live_location_tasks_.end()) { return; } auto message_full_id = it->second; Dialog *d = get_dialog(message_full_id.get_dialog_id()); const Message *m = get_message_force(d, message_full_id.get_message_id(), "view_message_live_location_on_server"); if (m == nullptr || get_message_content_live_location_period(m->content.get()) <= G()->unix_time() - m->date + 1) { // the message was deleted or live location is expired viewed_live_location_tasks_.erase(it); auto live_locations_it = pending_viewed_live_locations_.find(d->dialog_id); CHECK(live_locations_it != pending_viewed_live_locations_.end()); auto erased_count = live_locations_it->second.erase(message_full_id.get_message_id()); CHECK(erased_count > 0); if (live_locations_it->second.empty()) { pending_viewed_live_locations_.erase(live_locations_it); } return; } view_message_live_location_on_server_impl(task_id, message_full_id); } void MessagesManager::view_message_live_location_on_server_impl(int64 task_id, MessageFullId message_full_id) { auto promise = PromiseCreator::lambda([actor_id = actor_id(this), task_id](Unit result) { send_closure(actor_id, &MessagesManager::on_message_live_location_viewed_on_server, task_id); }); read_message_contents_on_server(message_full_id.get_dialog_id(), {message_full_id.get_message_id()}, 0, std::move(promise), true); } void MessagesManager::on_message_live_location_viewed_on_server(int64 task_id) { if (G()->close_flag()) { return; } auto it = viewed_live_location_tasks_.find(task_id); if (it == viewed_live_location_tasks_.end()) { return; } pending_message_live_location_view_timeout_.add_timeout_in(task_id, LIVE_LOCATION_VIEW_PERIOD); } void MessagesManager::try_add_bot_command_message_id(DialogId dialog_id, const Message *m) { CHECK(m != nullptr); if (td_->auth_manager_->is_bot() || !is_group_dialog(dialog_id) || m->message_id.is_scheduled() || !has_bot_commands(get_message_content_text(m->content.get()))) { return; } dialog_bot_command_message_ids_[dialog_id].message_ids.insert(m->message_id); } void MessagesManager::delete_bot_command_message_id(DialogId dialog_id, MessageId message_id) { if (message_id.is_scheduled()) { return; } auto it = dialog_bot_command_message_ids_.find(dialog_id); if (it == dialog_bot_command_message_ids_.end()) { return; } if (it->second.message_ids.erase(message_id) && it->second.message_ids.empty()) { dialog_bot_command_message_ids_.erase(it); } } FileSourceId MessagesManager::get_message_file_source_id(MessageFullId message_full_id, bool force) { if (!force) { 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() || !(message_id.is_valid() || message_id.is_valid_scheduled()) || dialog_id.get_type() == DialogType::SecretChat || !message_id.is_any_server()) { return FileSourceId(); } } auto &file_source_id = message_full_id_to_file_source_id_[message_full_id]; if (!file_source_id.is_valid()) { file_source_id = td_->file_reference_manager_->create_message_file_source(message_full_id); } return file_source_id; } void MessagesManager::add_message_file_sources(DialogId dialog_id, const Message *m) { if (td_->auth_manager_->is_bot()) { return; } if (dialog_id.get_type() != DialogType::SecretChat && m->is_content_secret) { // return; } auto file_ids = get_message_file_ids(m); if (file_ids.empty()) { return; } // do not create file_source_id for messages without file_ids auto file_source_id = get_message_file_source_id(MessageFullId(dialog_id, m->message_id)); if (file_source_id.is_valid()) { for (auto file_id : file_ids) { td_->file_manager_->add_file_source(file_id, file_source_id); } } } void MessagesManager::remove_message_file_sources(DialogId dialog_id, const Message *m) { if (td_->auth_manager_->is_bot()) { return; } auto file_ids = get_message_file_ids(m); if (file_ids.empty()) { return; } // do not create file_source_id for messages without file_ids auto file_source_id = get_message_file_source_id(MessageFullId(dialog_id, m->message_id)); if (file_source_id.is_valid()) { for (auto file_id : file_ids) { auto file_view = td_->file_manager_->get_file_view(file_id); send_closure(td_->download_manager_actor_, &DownloadManager::remove_file, file_view.get_main_file_id(), file_source_id, false, Promise()); td_->file_manager_->remove_file_source(file_id, file_source_id); } } } void MessagesManager::change_message_files(DialogId dialog_id, const Message *m, const vector &old_file_ids) { if (dialog_id.get_type() != DialogType::SecretChat && m->is_content_secret) { // return; } auto new_file_ids = get_message_file_ids(m); if (new_file_ids == old_file_ids) { return; } MessageFullId message_full_id{dialog_id, m->message_id}; bool need_delete_files = need_delete_message_files(dialog_id, m); auto file_source_id = get_message_file_source_id(message_full_id); for (auto file_id : old_file_ids) { if (!td::contains(new_file_ids, file_id)) { if (need_delete_files && need_delete_file(message_full_id, file_id)) { send_closure(G()->file_manager(), &FileManager::delete_file, file_id, Promise(), "change_message_files"); } if (file_source_id.is_valid()) { auto file_view = td_->file_manager_->get_file_view(file_id); send_closure(td_->download_manager_actor_, &DownloadManager::remove_file, file_view.get_main_file_id(), file_source_id, false, Promise()); } } } if (file_source_id.is_valid()) { td_->file_manager_->change_files_source(file_source_id, old_file_ids, new_file_ids); } } MessageId MessagesManager::get_first_database_message_id_by_index(const Dialog *d, MessageSearchFilter filter) { CHECK(d != nullptr); auto message_id = filter == MessageSearchFilter::Empty ? d->first_database_message_id : d->first_database_message_id_by_index[message_search_filter_index(filter)]; CHECK(!message_id.is_scheduled()); if (!message_id.is_valid()) { if (d->dialog_id.get_type() == DialogType::SecretChat) { LOG(ERROR) << "Invalid first_database_message_id_by_index in " << d->dialog_id; return MessageId::min(); } return MessageId::max(); } return message_id; } void MessagesManager::on_search_dialog_message_db_result(int64 random_id, DialogId dialog_id, MessageId from_message_id, MessageId first_db_message_id, MessageSearchFilter filter, int32 offset, int32 limit, Result> r_messages, Promise promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); if (r_messages.is_error()) { LOG(ERROR) << "Failed to get messages from the database: " << r_messages.error(); if (first_db_message_id != MessageId::min() && dialog_id.get_type() != DialogType::SecretChat && filter != MessageSearchFilter::FailedToSend) { found_dialog_messages_.erase(random_id); } return promise.set_value(Unit()); } CHECK(!from_message_id.is_scheduled()); CHECK(!first_db_message_id.is_scheduled()); auto messages = r_messages.move_as_ok(); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); auto it = found_dialog_messages_.find(random_id); CHECK(it != found_dialog_messages_.end()); auto &res = it->second.message_ids; MessageId next_from_message_id; res.reserve(messages.size()); for (auto &message : messages) { auto m = on_get_message_from_database(d, message, false, "on_search_dialog_message_db_result"); if (m != nullptr && first_db_message_id <= m->message_id) { if (!next_from_message_id.is_valid() || m->message_id < next_from_message_id) { next_from_message_id = m->message_id; } if (filter == MessageSearchFilter::UnreadMention && !m->contains_unread_mention) { // skip already read by d->last_read_all_mentions_message_id mentions } else { CHECK(!m->message_id.is_scheduled()); res.push_back(m->message_id); } } } auto &message_count = d->message_count_by_index[message_search_filter_index(filter)]; auto result_size = narrow_cast(res.size()); bool from_the_end = from_message_id == MessageId::max() || (offset < 0 && (result_size == 0 || res[0] < from_message_id)); if ((message_count != -1 && message_count < result_size) || (message_count > result_size && from_the_end && first_db_message_id == MessageId::min() && result_size < limit + offset)) { LOG(INFO) << "Fix found message count in " << dialog_id << " from " << message_count << " to " << result_size; message_count = result_size; if (filter == MessageSearchFilter::UnreadMention) { d->unread_mention_count = message_count; update_dialog_mention_notification_count(d); send_update_chat_unread_mention_count(d); } if (filter == MessageSearchFilter::UnreadReaction) { d->unread_reaction_count = message_count; // update_dialog_mention_notification_count(d); send_update_chat_unread_reaction_count(d, "on_search_dialog_message_db_result"); } on_dialog_updated(dialog_id, "on_search_dialog_message_db_result"); } it->second.total_count = message_count; it->second.next_from_message_id = next_from_message_id; if (res.empty() && first_db_message_id != MessageId::min() && dialog_id.get_type() != DialogType::SecretChat) { LOG(INFO) << "No messages found in database"; found_dialog_messages_.erase(it); } else { LOG(INFO) << "Found " << res.size() << " messages out of " << message_count << " in database"; if (from_the_end && filter == MessageSearchFilter::Pinned) { set_dialog_last_pinned_message_id(d, res.empty() ? MessageId() : res[0]); } } promise.set_value(Unit()); } td_api::object_ptr MessagesManager::get_found_chat_messages_object( DialogId dialog_id, const FoundDialogMessages &found_dialog_messages, const char *source) { auto *d = get_dialog(dialog_id); CHECK(d != nullptr); vector> result; result.reserve(found_dialog_messages.message_ids.size()); for (const auto &message_id : found_dialog_messages.message_ids) { auto message = get_message_object(dialog_id, get_message_force(d, message_id, source), source); if (message != nullptr) { result.push_back(std::move(message)); } } return td_api::make_object(found_dialog_messages.total_count, std::move(result), found_dialog_messages.next_from_message_id.get()); } td_api::object_ptr MessagesManager::get_found_messages_object( const FoundMessages &found_messages, const char *source) { vector> result; result.reserve(found_messages.message_full_ids.size()); for (const auto &message_full_id : found_messages.message_full_ids) { auto message = get_message_object(message_full_id, source); if (message != nullptr) { result.push_back(std::move(message)); } } return td_api::make_object(found_messages.total_count, std::move(result), found_messages.next_offset); } MessagesManager::FoundMessages MessagesManager::offline_search_messages(DialogId dialog_id, const string &query, string offset, int32 limit, MessageSearchFilter filter, int64 &random_id, Promise &&promise) { if (!G()->use_message_database()) { promise.set_error(Status::Error(400, "Message database is required to search messages in secret chats")); return {}; } if (random_id != 0) { // request has already been sent before auto it = found_fts_messages_.find(random_id); CHECK(it != found_fts_messages_.end()); auto result = std::move(it->second); found_fts_messages_.erase(it); promise.set_value(Unit()); return result; } if (query.empty()) { promise.set_value(Unit()); return {}; } if (dialog_id != DialogId() && !have_dialog_force(dialog_id, "offline_search_messages")) { promise.set_error(Status::Error(400, "Chat not found")); return {}; } if (limit <= 0) { promise.set_error(Status::Error(400, "Limit must be positive")); return {}; } if (limit > MAX_SEARCH_MESSAGES) { limit = MAX_SEARCH_MESSAGES; } MessageDbFtsQuery fts_query; fts_query.query = query; fts_query.dialog_id = dialog_id; fts_query.filter = filter; if (!offset.empty()) { auto r_from_search_id = to_integer_safe(offset); if (r_from_search_id.is_error()) { promise.set_error(Status::Error(400, "Invalid offset specified")); return {}; } fts_query.from_search_id = r_from_search_id.ok(); } fts_query.limit = limit; do { random_id = Random::secure_int64(); } while (random_id == 0 || found_fts_messages_.count(random_id) > 0); found_fts_messages_[random_id]; // reserve place for result G()->td_db()->get_message_db_async()->get_messages_fts( std::move(fts_query), PromiseCreator::lambda([random_id, offset = std::move(offset), limit, promise = std::move(promise)](Result fts_result) mutable { send_closure(G()->messages_manager(), &MessagesManager::on_message_db_fts_result, std::move(fts_result), std::move(offset), limit, random_id, std::move(promise)); })); return {}; } void MessagesManager::on_message_db_fts_result(Result result, string offset, int32 limit, int64 random_id, Promise &&promise) { G()->ignore_result_if_closing(result); if (result.is_error()) { found_fts_messages_.erase(random_id); return promise.set_error(result.move_as_error()); } auto fts_result = result.move_as_ok(); auto it = found_fts_messages_.find(random_id); CHECK(it != found_fts_messages_.end()); auto &res = it->second.message_full_ids; res.reserve(fts_result.messages.size()); for (auto &message : fts_result.messages) { auto m = on_get_message_from_database(message, false, "on_message_db_fts_result"); if (m != nullptr) { res.emplace_back(message.dialog_id, m->message_id); } } it->second.next_offset = fts_result.next_search_id <= 1 ? string() : to_string(fts_result.next_search_id); it->second.total_count = offset.empty() && fts_result.messages.size() < static_cast(limit) ? static_cast(fts_result.messages.size()) : -1; promise.set_value(Unit()); } void MessagesManager::on_message_db_calls_result(Result result, int64 random_id, MessageId first_db_message_id, MessageSearchFilter filter, Promise &&promise) { G()->ignore_result_if_closing(result); if (result.is_error()) { found_call_messages_.erase(random_id); return promise.set_error(result.move_as_error()); } auto calls_result = result.move_as_ok(); auto it = found_call_messages_.find(random_id); CHECK(it != found_call_messages_.end()); auto &res = it->second.message_full_ids; CHECK(!first_db_message_id.is_scheduled()); res.reserve(calls_result.messages.size()); MessageId next_offset_message_id; for (auto &message : calls_result.messages) { auto m = on_get_message_from_database(message, false, "on_message_db_calls_result"); if (m != nullptr && first_db_message_id <= m->message_id) { if (!next_offset_message_id.is_valid() || m->message_id < next_offset_message_id) { next_offset_message_id = m->message_id; } res.emplace_back(message.dialog_id, m->message_id); } } it->second.total_count = calls_db_state_.message_count_by_index[call_message_search_filter_index(filter)]; if (next_offset_message_id.is_valid()) { it->second.next_offset = PSTRING() << next_offset_message_id.get_server_message_id().get(); } if (res.empty() && first_db_message_id != MessageId::min()) { LOG(INFO) << "No messages found in database"; found_call_messages_.erase(it); } promise.set_value(Unit()); } MessagesManager::FoundMessages MessagesManager::search_messages(FolderId folder_id, bool ignore_folder_id, const string &query, const string &offset, int32 limit, MessageSearchFilter filter, int32 min_date, int32 max_date, int64 &random_id, Promise &&promise) { if (random_id != 0) { // request has already been sent before auto it = found_messages_.find(random_id); CHECK(it != found_messages_.end()); auto result = std::move(it->second); found_messages_.erase(it); promise.set_value(Unit()); return result; } if (limit <= 0) { promise.set_error(Status::Error(400, "Parameter limit must be positive")); return {}; } if (limit > MAX_SEARCH_MESSAGES) { limit = MAX_SEARCH_MESSAGES; } int32 offset_date = std::numeric_limits::max(); DialogId offset_dialog_id; MessageId offset_message_id; bool is_offset_valid = [&] { if (offset.empty()) { return true; } auto parts = full_split(offset, ','); if (parts.size() != 3) { return false; } auto r_offset_date = to_integer_safe(parts[0]); auto r_offset_dialog_id = to_integer_safe(parts[1]); auto r_offset_message_id = to_integer_safe(parts[2]); if (r_offset_date.is_error() || r_offset_date.ok() <= 0 || r_offset_message_id.is_error() || r_offset_dialog_id.is_error()) { return false; } offset_date = r_offset_date.ok(); offset_message_id = MessageId(ServerMessageId(r_offset_message_id.ok())); offset_dialog_id = DialogId(r_offset_dialog_id.ok()); if (!offset_message_id.is_valid() || !offset_dialog_id.is_valid() || MessagesManager::get_input_peer_force(offset_dialog_id)->get_id() == telegram_api::inputPeerEmpty::ID) { return false; } return true; }(); if (!is_offset_valid) { promise.set_error(Status::Error(400, "Invalid offset specified")); return {}; } CHECK(filter != MessageSearchFilter::Call && filter != MessageSearchFilter::MissedCall); if (filter == MessageSearchFilter::Mention || filter == MessageSearchFilter::UnreadMention || filter == MessageSearchFilter::UnreadReaction || filter == MessageSearchFilter::FailedToSend || filter == MessageSearchFilter::Pinned) { promise.set_error(Status::Error(400, "The filter is not supported")); return {}; } if (query.empty() && filter == MessageSearchFilter::Empty) { promise.set_value(Unit()); return {}; } do { random_id = Random::secure_int64(); } while (random_id == 0 || found_messages_.count(random_id) > 0); found_messages_[random_id]; // reserve place for result LOG(DEBUG) << "Search all messages filtered by " << filter << " with query = \"" << query << "\" from offset " << offset << " and limit " << limit; td_->create_handler(std::move(promise)) ->send(folder_id, ignore_folder_id, query, offset_date, offset_dialog_id, offset_message_id, limit, filter, min_date, max_date, random_id); return {}; } int64 MessagesManager::get_dialog_message_by_date(DialogId dialog_id, int32 date, Promise &&promise) { Dialog *d = get_dialog_force(dialog_id, "get_dialog_message_by_date"); if (d == nullptr) { promise.set_error(Status::Error(400, "Chat not found")); return 0; } if (!have_input_peer(dialog_id, AccessRights::Read)) { promise.set_error(Status::Error(400, "Can't access the chat")); return 0; } if (date <= 0) { date = 1; } int64 random_id = 0; do { random_id = Random::secure_int64(); } while (random_id == 0 || get_dialog_message_by_date_results_.count(random_id) > 0); get_dialog_message_by_date_results_[random_id]; // reserve place for result auto message_id = d->ordered_messages.find_message_by_date(date, get_get_message_date(d)); if (message_id.is_valid() && (message_id == d->last_message_id || (*d->ordered_messages.get_const_iterator(message_id))->have_next())) { get_dialog_message_by_date_results_[random_id] = {dialog_id, message_id}; promise.set_value(Unit()); return random_id; } if (G()->use_message_database() && d->last_database_message_id != MessageId()) { CHECK(d->first_database_message_id != MessageId()); G()->td_db()->get_message_db_async()->get_dialog_message_by_date( dialog_id, d->first_database_message_id, d->last_database_message_id, date, PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, date, random_id, promise = std::move(promise)](Result result) mutable { send_closure(actor_id, &MessagesManager::on_get_dialog_message_by_date_from_database, dialog_id, date, random_id, std::move(result), std::move(promise)); })); } else { get_dialog_message_by_date_from_server(d, date, random_id, false, std::move(promise)); } return random_id; } void MessagesManager::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, &MessagesManager::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 MessagesManager::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) { add_pending_channel_update(dialog_id, make_tl_object(), affected_history.pts_, affected_history.pts_count_, std::move(update_promise), "on_get_affected_history"); } else { td_->updates_manager_->add_pending_pts_update(make_tl_object(), affected_history.pts_, affected_history.pts_count_, Time::now(), 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)); } } std::function MessagesManager::get_get_message_date(const Dialog *d) const { return [d](MessageId message_id) { const auto *m = get_message_static(d, message_id); CHECK(m != nullptr); return m->date; }; } void MessagesManager::on_get_dialog_message_by_date_from_database(DialogId dialog_id, int32 date, int64 random_id, Result result, Promise promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); if (result.is_ok()) { Message *m = on_get_message_from_database(d, result.ok(), false, "on_get_dialog_message_by_date_from_database"); if (m != nullptr) { auto message_id = d->ordered_messages.find_message_by_date(date, get_get_message_date(d)); if (!message_id.is_valid()) { LOG(ERROR) << "Failed to find " << m->message_id << " in " << dialog_id << " by date " << date; message_id = m->message_id; } get_dialog_message_by_date_results_[random_id] = {dialog_id, message_id}; promise.set_value(Unit()); return; } // TODO if m == nullptr, we need to just adjust it to the next non-nullptr message, not get from server } return get_dialog_message_by_date_from_server(d, date, random_id, true, std::move(promise)); } void MessagesManager::get_dialog_message_by_date_from_server(const Dialog *d, int32 date, int64 random_id, bool after_database_search, Promise &&promise) { CHECK(d != nullptr); if (d->have_full_history) { // request can always be done locally/in memory. There is no need to send request to the server if (after_database_search) { return promise.set_value(Unit()); } auto message_id = d->ordered_messages.find_message_by_date(date, get_get_message_date(d)); if (message_id.is_valid()) { get_dialog_message_by_date_results_[random_id] = {d->dialog_id, message_id}; } promise.set_value(Unit()); return; } if (d->dialog_id.get_type() == DialogType::SecretChat) { // there is no way to send request to the server return promise.set_value(Unit()); } td_->create_handler(std::move(promise))->send(d->dialog_id, date, random_id); } void MessagesManager::on_get_dialog_message_by_date_success(DialogId dialog_id, int32 date, int64 random_id, vector> &&messages, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); auto it = get_dialog_message_by_date_results_.find(random_id); CHECK(it != get_dialog_message_by_date_results_.end()); auto &result = it->second; CHECK(result == MessageFullId()); for (auto &message : messages) { auto message_date = get_message_date(message); auto message_dialog_id = DialogId::get_message_dialog_id(message); if (message_dialog_id != dialog_id) { LOG(ERROR) << "Receive message in wrong " << message_dialog_id << " instead of " << dialog_id; continue; } if (message_date != 0 && message_date <= date) { result = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, false, "on_get_dialog_message_by_date_success"); if (result != MessageFullId()) { const Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); auto message_id = d->ordered_messages.find_message_by_date(date, get_get_message_date(d)); if (!message_id.is_valid()) { LOG(ERROR) << "Failed to find " << result.get_message_id() << " in " << dialog_id << " by date " << date; message_id = result.get_message_id(); } get_dialog_message_by_date_results_[random_id] = {dialog_id, message_id}; // TODO result must be adjusted by local messages promise.set_value(Unit()); return; } } } promise.set_value(Unit()); } void MessagesManager::on_get_dialog_message_by_date_fail(int64 random_id) { auto erased_count = get_dialog_message_by_date_results_.erase(random_id); CHECK(erased_count > 0); } tl_object_ptr MessagesManager::get_dialog_message_by_date_object(int64 random_id) { auto it = get_dialog_message_by_date_results_.find(random_id); CHECK(it != get_dialog_message_by_date_results_.end()); auto message_full_id = std::move(it->second); get_dialog_message_by_date_results_.erase(it); return get_message_object(message_full_id, "get_dialog_message_by_date_object"); } void MessagesManager::get_dialog_sparse_message_positions( DialogId dialog_id, MessageSearchFilter filter, MessageId from_message_id, int32 limit, Promise> &&promise) { const Dialog *d = get_dialog_force(dialog_id, "get_dialog_sparse_message_positions"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (limit < 50 || limit > 2000) { // server-side limits return promise.set_error(Status::Error(400, "Invalid limit specified")); } CHECK(filter != MessageSearchFilter::Call && filter != MessageSearchFilter::MissedCall); if (filter == MessageSearchFilter::Empty || filter == MessageSearchFilter::Mention || filter == MessageSearchFilter::UnreadMention || filter == MessageSearchFilter::UnreadReaction || filter == MessageSearchFilter::Pinned) { return promise.set_error(Status::Error(400, "The filter is not supported")); } if (from_message_id.is_scheduled()) { return promise.set_error(Status::Error(400, "Invalid from_message_id specified")); } if (!from_message_id.is_valid() || from_message_id > d->last_new_message_id) { if (d->last_new_message_id.is_valid()) { from_message_id = d->last_new_message_id.get_next_message_id(MessageType::Server); } else { from_message_id = MessageId::max(); } } else { from_message_id = from_message_id.get_next_server_message_id(); } if (filter == MessageSearchFilter::FailedToSend || dialog_id.get_type() == DialogType::SecretChat) { if (!G()->use_message_database()) { return promise.set_error(Status::Error(400, "Unsupported without message database")); } LOG(INFO) << "Get sparse message positions from database"; auto new_promise = PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { TRY_STATUS_PROMISE(promise, G()->close_status()); if (result.is_error()) { return promise.set_error(result.move_as_error()); } auto positions = result.move_as_ok(); promise.set_value(td_api::make_object( positions.total_count, transform(positions.positions, [](const MessageDbMessagePosition &position) { return td_api::make_object(position.position, position.message_id.get(), position.date); }))); }); MessageDbGetDialogSparseMessagePositionsQuery db_query; db_query.dialog_id = dialog_id; db_query.filter = filter; db_query.from_message_id = from_message_id; db_query.limit = limit; G()->td_db()->get_message_db_async()->get_dialog_sparse_message_positions(db_query, std::move(new_promise)); return; } switch (dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: case DialogType::Channel: td_->create_handler(std::move(promise)) ->send(dialog_id, filter, from_message_id, limit); break; case DialogType::SecretChat: case DialogType::None: default: UNREACHABLE(); } } void MessagesManager::on_get_dialog_sparse_message_positions( DialogId dialog_id, MessageSearchFilter filter, telegram_api::object_ptr positions, Promise> &&promise) { auto message_positions = transform( positions->positions_, [](const telegram_api::object_ptr &position) { return td_api::make_object( position->offset_, MessageId(ServerMessageId(position->msg_id_)).get(), position->date_); }); promise.set_value(td_api::make_object(positions->count_, std::move(message_positions))); } void MessagesManager::get_dialog_message_count(DialogId dialog_id, MessageSearchFilter filter, bool return_local, Promise &&promise) { LOG(INFO) << "Get " << (return_local ? "local " : "") << "number of messages in " << dialog_id << " filtered by " << filter; const Dialog *d = get_dialog_force(dialog_id, "get_dialog_message_count"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (filter == MessageSearchFilter::Empty) { return promise.set_error(Status::Error(400, "Can't use searchMessagesFilterEmpty")); } auto dialog_type = dialog_id.get_type(); int32 message_count = d->message_count_by_index[message_search_filter_index(filter)]; if (message_count == -1 && filter == MessageSearchFilter::UnreadMention) { message_count = d->unread_mention_count; } if (message_count == -1 && filter == MessageSearchFilter::UnreadReaction) { message_count = d->unread_reaction_count; } if (message_count != -1 || return_local || dialog_type == DialogType::SecretChat || filter == MessageSearchFilter::FailedToSend) { return promise.set_value(std::move(message_count)); } get_dialog_message_count_from_server(dialog_id, filter, std::move(promise)); } void MessagesManager::get_dialog_message_count_from_server(DialogId dialog_id, MessageSearchFilter filter, Promise &&promise) { LOG(INFO) << "Get number of messages in " << dialog_id << " filtered by " << filter << " from the server"; switch (dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: case DialogType::Channel: td_->create_handler(std::move(promise))->send(dialog_id, filter); break; case DialogType::None: case DialogType::SecretChat: default: UNREACHABLE(); } } void MessagesManager::get_dialog_message_position(MessageFullId message_full_id, MessageSearchFilter filter, MessageId top_thread_message_id, Promise &&promise) { auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "get_dialog_message_position"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } auto message_id = message_full_id.get_message_id(); const Message *m = get_message_force(d, message_id, "get_dialog_message_position"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } if (!m->message_id.is_valid() || !m->message_id.is_server() || (filter != MessageSearchFilter::Empty && (get_message_index_mask(d->dialog_id, m) & message_search_filter_index_mask(filter)) == 0)) { return promise.set_error(Status::Error(400, "Message can't be found in the filter")); } if (top_thread_message_id != MessageId()) { if (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server()) { return promise.set_error(Status::Error(400, "Invalid message thread identifier specified")); } if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id)) { return promise.set_error(Status::Error(400, "Can't filter by message thread identifier in the chat")); } if (m->top_thread_message_id != top_thread_message_id || (m->message_id == top_thread_message_id && !m->is_topic_message)) { return promise.set_error(Status::Error(400, "Message doesn't belong to the message thread")); } } if (dialog_id.get_type() == DialogType::SecretChat) { return promise.set_error(Status::Error(400, "The method can't be used in secret chats")); } if (filter == MessageSearchFilter::UnreadMention || filter == MessageSearchFilter::UnreadReaction || filter == MessageSearchFilter::FailedToSend) { return promise.set_error(Status::Error(400, "The filter is not supported")); } td_->create_handler(std::move(promise)) ->send(dialog_id, message_id, filter, top_thread_message_id); } void MessagesManager::preload_newer_messages(const Dialog *d, MessageId max_message_id) { CHECK(d != nullptr); CHECK(max_message_id.is_valid()); CHECK(!td_->auth_manager_->is_bot()); auto it = d->ordered_messages.get_const_iterator(max_message_id); int32 limit = MAX_GET_HISTORY * 3 / 10; while (*it != nullptr && limit-- > 0) { ++it; if (*it) { max_message_id = (*it)->get_message_id(); } } if (limit > 0 && (d->last_message_id == MessageId() || max_message_id < d->last_message_id)) { // need to preload some new messages LOG(INFO) << "Preloading newer after " << max_message_id; load_messages_impl(d, max_message_id, -MAX_GET_HISTORY + 1, MAX_GET_HISTORY, 3, false, Promise()); } } void MessagesManager::preload_older_messages(const Dialog *d, MessageId min_message_id) { CHECK(d != nullptr); CHECK(min_message_id.is_valid()); CHECK(!td_->auth_manager_->is_bot()); /* if (d->first_remote_message_id == -1) { // nothing left to preload from server return; } */ auto it = d->ordered_messages.get_const_iterator(min_message_id); int32 limit = MAX_GET_HISTORY * 3 / 10 + 1; while (*it != nullptr && limit-- > 0) { min_message_id = (*it)->get_message_id(); --it; } if (limit > 0) { // need to preload some old messages LOG(INFO) << "Preloading older before " << min_message_id; load_messages_impl(d, min_message_id, 0, MAX_GET_HISTORY / 2, 3, false, Promise()); } } unique_ptr MessagesManager::parse_message(Dialog *d, MessageId expected_message_id, const BufferSlice &value, bool is_scheduled) { CHECK(d != nullptr); auto dialog_id = d->dialog_id; auto message = make_unique(); auto *m = message.get(); auto status = log_event_parse(*m, value.as_slice()); bool is_message_id_valid = [&] { if (is_scheduled) { if (!expected_message_id.is_valid_scheduled()) { return false; } if (m->message_id == expected_message_id) { return true; } return m->message_id.is_valid_scheduled() && expected_message_id.is_scheduled_server() && m->message_id.is_scheduled_server() && m->message_id.get_scheduled_server_message_id() == expected_message_id.get_scheduled_server_message_id(); } else { if (!expected_message_id.is_valid()) { return false; } return m->message_id == expected_message_id; } }(); if (status.is_error() || !is_message_id_valid) { // can't happen unless the database is broken, but has been seen in the wild LOG(ERROR) << "Receive invalid message from database: " << expected_message_id << ' ' << m->message_id << ' ' << status << ' ' << format::as_hex_dump<4>(value.as_slice()); if (!is_scheduled && dialog_id.get_type() != DialogType::SecretChat) { // trying to repair the message if (expected_message_id.is_valid() && expected_message_id.is_server()) { get_message_from_server({dialog_id, expected_message_id}, Auto(), "parse_message"); } if (m->message_id.is_valid() && m->message_id.is_server()) { get_message_from_server({dialog_id, m->message_id}, Auto(), "parse_message"); } } return nullptr; } if (m->reactions != nullptr) { if (td_->auth_manager_->is_bot() || m->available_reactions_generation < d->available_reactions_generation) { m->reactions = nullptr; m->available_reactions_generation = 0; } else if (m->available_reactions_generation > d->available_reactions_generation && m->available_reactions_generation - d->available_reactions_generation < 1000000000) { switch (dialog_id.get_type()) { case DialogType::Chat: case DialogType::Channel: LOG(ERROR) << "Fix available_reactions_generation in " << dialog_id << " from " << d->available_reactions_generation << " to " << m->available_reactions_generation; hide_dialog_message_reactions(d); set_dialog_next_available_reactions_generation(d, m->available_reactions_generation); on_dialog_updated(dialog_id, "parse_message"); break; case DialogType::User: case DialogType::SecretChat: default: LOG(ERROR) << "Receive available_reactions_generation = " << m->available_reactions_generation << " in " << m->message_id << " in " << dialog_id; break; } } if (m->reactions != nullptr) { m->reactions->fix_my_recent_chooser_dialog_id(get_my_dialog_id()); } } if (m->contains_mention && td_->auth_manager_->is_bot()) { m->contains_mention = false; m->contains_unread_mention = false; } if (m->history_generation > d->history_generation && m->history_generation - d->history_generation < 1000000000) { switch (dialog_id.get_type()) { case DialogType::Channel: LOG(ERROR) << "Fix history_generation in " << dialog_id << " from " << d->history_generation << " to " << m->history_generation; d->history_generation = m->history_generation + 1; on_dialog_updated(dialog_id, "parse_message"); break; case DialogType::Chat: case DialogType::User: case DialogType::SecretChat: default: LOG(ERROR) << "Receive history_generation = " << m->history_generation << " in " << m->message_id << " in " << dialog_id; break; } } if (m->is_pinned && is_scheduled) { m->is_pinned = false; } LOG(INFO) << "Loaded " << m->message_id << " in " << dialog_id << " of size " << value.size() << " from database"; return message; } void MessagesManager::on_get_history_from_database(DialogId dialog_id, MessageId from_message_id, MessageId old_last_database_message_id, int32 offset, int32 limit, bool only_local, vector &&messages, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); bool from_the_end = from_message_id == MessageId::max(); CHECK(-limit < offset && offset <= 0); CHECK(offset < 0 || from_the_end); CHECK(!from_message_id.is_scheduled()); auto d = get_dialog(dialog_id); CHECK(d != nullptr); if (old_last_database_message_id < d->last_database_message_id && old_last_database_message_id < from_message_id) { // new messages where added to the database since the request was sent // they should have been received from the database, so we must repeat the request to get them LOG(INFO) << "Reget chat history from database in " << dialog_id << ", because last database message was changed from " << old_last_database_message_id << " to " << d->last_database_message_id; get_history_impl(d, from_message_id, offset, limit, true, only_local, std::move(promise), "on_get_history_from_database 20"); return; } LOG(INFO) << "Receive " << messages.size() << " history messages from database " << (from_the_end ? "from the end " : "") << "in " << dialog_id << " from " << from_message_id << " with offset " << offset << " and limit " << limit << ". First database message is " << d->first_database_message_id << ", last database message is " << d->last_database_message_id << ", have_full_history = " << d->have_full_history << ", have_full_history_source = " << d->have_full_history_source; bool had_full_history = d->have_full_history; auto debug_first_database_message_id = d->first_database_message_id; auto debug_last_message_id = d->last_message_id; auto debug_last_new_message_id = d->last_new_message_id; bool have_error = false; auto message_ids = on_get_messages_from_database( d, std::move(messages), d->have_full_history ? MessageId::min() : d->first_database_message_id, have_error, "on_get_history_from_database"); if (have_error) { // database is broken message_ids.clear(); // ignore received messages just in case if (d->have_full_history) { d->have_full_history = false; d->have_full_history_source = 0; d->is_empty = false; // just in case on_dialog_updated(dialog_id, "drop have_full_history in on_get_history_from_database"); } } vector is_added_to_dialog; for (auto &message_id : message_ids) { Message *m = get_message(d, message_id); is_added_to_dialog.push_back(m != nullptr); } bool have_next = false; MessageId first_added_message_id; MessageId last_added_message_id; MessageId next_message_id; auto first_received_message_id = MessageId::max(); MessageId last_received_message_id; for (size_t pos = 0; pos < message_ids.size(); pos++) { auto message_id = message_ids[pos]; bool is_added = is_added_to_dialog[pos]; first_received_message_id = message_id; if (!last_received_message_id.is_valid()) { last_received_message_id = message_id; } if (!have_next && (from_the_end || (pos == 0 && offset < -1 && message_id <= from_message_id)) && message_id < d->last_message_id) { // last message in the dialog must be attached to the next local message have_next = true; } if (is_added) { if (have_next) { d->ordered_messages.attach_message_to_next(message_id, "on_get_history"); } first_added_message_id = message_id; if (!last_added_message_id.is_valid()) { last_added_message_id = message_id; } if (next_message_id.is_valid()) { CHECK(message_id < next_message_id); d->ordered_messages.attach_message_to_previous( next_message_id, (PSLICE() << "on_get_history_from_database 1 " << message_id << ' ' << from_message_id << ' ' << offset << ' ' << limit << ' ' << d->first_database_message_id << ' ' << d->have_full_history << ' ' << pos) .c_str()); } have_next = true; next_message_id = message_id; } } if (from_the_end && messages.empty() && d->ordered_messages.empty()) { if (d->have_full_history) { set_dialog_is_empty(d, "on_get_history_from_database empty"); } else if (d->last_database_message_id.is_valid()) { set_dialog_first_database_message_id(d, MessageId(), "on_get_history_from_database empty"); set_dialog_last_database_message_id(d, MessageId(), "on_get_history_from_database empty"); } } if (from_the_end && !last_added_message_id.is_valid() && d->first_database_message_id.is_valid() && !d->have_full_history) { if (first_received_message_id <= d->first_database_message_id) { // database definitely has no messages from first_database_message_id to last_database_message_id; drop them set_dialog_first_database_message_id(d, MessageId(), "on_get_history_from_database 8"); set_dialog_last_database_message_id(d, MessageId(), "on_get_history_from_database 9"); } else { CHECK(first_received_message_id.is_valid()); // if a message was received, but wasn't added, then it is likely to be already deleted // if it is less than d->last_database_message_id, then we can adjust d->last_database_message_id and // try again database search without chance to loop if (first_received_message_id < d->last_database_message_id) { set_dialog_last_database_message_id(d, first_received_message_id, "on_get_history_from_database 12"); get_history_impl(d, MessageId::max(), 0, -1, true, only_local, std::move(promise), "on_get_history_from_database 21"); return; } if (limit > 1) { // we expected to have messages [first_database_message_id, last_database_message_id] in the database, but // received messages [first_received_message_id, last_received_message_id], none of which can be added // first_database_message_id and last_database_message_id are very wrong, so it is better to drop them, // pretending that the database has no usable messages if (!is_deleted_message(d, d->first_database_message_id) || !is_deleted_message(d, d->last_database_message_id)) { if (first_received_message_id == MessageId::max()) { CHECK(last_received_message_id == MessageId()); LOG(ERROR) << "Receive no usable messages in " << dialog_id << " from database from the end, but expected messages from " << d->first_database_message_id << " up to " << d->last_database_message_id << ". Have old last_database_message_id = " << old_last_database_message_id << " and " << messages.size() << " received messages"; } else { LOG(ERROR) << "Receive " << messages.size() << " unusable messages [" << first_received_message_id << " ... " << last_received_message_id << "] in " << dialog_id << " from database from the end, but expected messages from " << d->first_database_message_id << " up to " << d->last_database_message_id; } } set_dialog_first_database_message_id(d, MessageId(), "on_get_history_from_database 13"); set_dialog_last_database_message_id(d, MessageId(), "on_get_history_from_database 14"); } } } if (!first_added_message_id.is_valid() && !only_local && dialog_id.get_type() != DialogType::SecretChat) { load_messages_impl(d, from_message_id, offset, limit, 1, false, std::move(promise)); return; } bool need_update_dialog_pos = false; if (from_the_end && last_added_message_id.is_valid()) { CHECK(next_message_id.is_valid()); // CHECK(d->first_database_message_id.is_valid()); // CHECK(last_added_message_id >= d->first_database_message_id); if ((had_full_history || d->have_full_history) && !d->last_new_message_id.is_valid() && (last_added_message_id.is_server() || d->dialog_id.get_type() == DialogType::SecretChat)) { LOG(ERROR) << "Trying to hard fix " << d->dialog_id << " last new message to " << last_added_message_id << " from on_get_history_from_database 2"; d->last_new_message_id = last_added_message_id; on_dialog_updated(d->dialog_id, "on_get_history_from_database 3"); } if (last_added_message_id > d->last_message_id && d->last_new_message_id.is_valid()) { set_dialog_last_message_id(d, last_added_message_id, "on_get_history_from_database 4"); need_update_dialog_pos = true; } if (last_added_message_id != d->last_database_message_id && d->last_new_message_id.is_valid()) { auto debug_last_database_message_id = d->last_database_message_id; auto debug_set_dialog_last_database_message_id = d->debug_set_dialog_last_database_message_id; if (!d->first_database_message_id.is_valid() && !d->last_database_message_id.is_valid()) { set_dialog_first_database_message_id(d, next_message_id, "on_get_history_from_database 5"); } set_dialog_last_database_message_id(d, last_added_message_id, "on_get_history_from_database 5"); if (last_added_message_id < d->first_database_message_id || !d->first_database_message_id.is_valid()) { LOG_CHECK(had_full_history || d->have_full_history) << had_full_history << ' ' << d->have_full_history << ' ' << next_message_id << ' ' << last_added_message_id << ' ' << d->first_database_message_id << ' ' << debug_first_database_message_id << ' ' << d->last_database_message_id << ' ' << debug_last_database_message_id << ' ' << dialog_id << ' ' << d->last_new_message_id << ' ' << debug_last_new_message_id << ' ' << d->last_message_id << ' ' << debug_last_message_id << ' ' << debug_set_dialog_last_database_message_id << ' ' << d->debug_set_dialog_last_database_message_id << ' ' << first_received_message_id << ' ' << last_received_message_id << ' ' << d->debug_first_database_message_id << ' ' << d->debug_last_database_message_id << ' ' << d->debug_last_new_message_id << ' ' << d->have_full_history_source; CHECK(next_message_id <= d->last_database_message_id); LOG(ERROR) << "Fix first database message in " << dialog_id << " from " << d->first_database_message_id << " to " << next_message_id; set_dialog_first_database_message_id(d, next_message_id, "on_get_history_from_database 6"); } } } if (first_added_message_id.is_valid() && first_added_message_id != d->first_database_message_id && first_received_message_id < d->first_database_message_id && d->last_new_message_id.is_valid() && !d->have_full_history) { CHECK(first_added_message_id > d->first_database_message_id); set_dialog_first_database_message_id(d, first_added_message_id, "on_get_history_from_database 10"); if (d->last_database_message_id < d->first_database_message_id) { set_dialog_last_database_message_id(d, d->first_database_message_id, "on_get_history_from_database 11"); } } if (need_update_dialog_pos) { send_update_chat_last_message(d, "on_get_history_from_database 7"); } promise.set_value(Unit()); } void MessagesManager::load_last_dialog_message_later(DialogId dialog_id) { if (G()->close_flag()) { return; } load_last_dialog_message(get_dialog(dialog_id), "load_last_dialog_message"); } void MessagesManager::load_last_dialog_message(const Dialog *d, const char *source) { get_history_impl(d, MessageId::max(), 0, -1, true, false, Promise(), source); } void MessagesManager::on_get_history_finished(const PendingGetHistoryQuery &query, Result &&result) { G()->ignore_result_if_closing(result); auto it = get_history_queries_.find(query); if (it == get_history_queries_.end()) { return; } auto promises = std::move(it->second); CHECK(!promises.empty()); get_history_queries_.erase(it); if (result.is_ok()) { set_promises(promises); } else { fail_promises(promises, result.move_as_error()); } } void MessagesManager::get_history_impl(const Dialog *d, MessageId from_message_id, int32 offset, int32 limit, bool from_database, bool only_local, Promise &&promise, const char *source) { CHECK(d != nullptr); bool from_the_end = from_message_id == MessageId() || from_message_id == MessageId::max(); if (!from_the_end) { CHECK(from_message_id.is_valid()); } else { from_message_id = MessageId::max(); } auto dialog_id = d->dialog_id; if (!have_input_peer(dialog_id, AccessRights::Read)) { // can't get history in dialogs without read access return promise.set_value(Unit()); } if ((!d->first_database_message_id.is_valid() || from_message_id <= d->first_database_message_id) && !d->have_full_history) { from_database = false; } if (!G()->use_message_database()) { from_database = false; } if (from_the_end) { // load only 10 messages when repairing the last message and can't save the result to the database limit = !promise && (from_database || !G()->use_message_database()) ? max(limit, 10) : MAX_GET_HISTORY; offset = 0; } else if (offset >= -1) { // get history before some server or local message limit = clamp(limit + offset + 1, MAX_GET_HISTORY / 2, MAX_GET_HISTORY); offset = -1; } else { // get history around some server or local message int32 messages_to_load = max(MAX_GET_HISTORY, limit); int32 max_add = max(messages_to_load - limit - 2, 0); offset -= max_add; limit = MAX_GET_HISTORY; } PendingGetHistoryQuery query; query.dialog_id_ = dialog_id; query.from_message_id_ = from_message_id; query.offset_ = offset; query.limit_ = limit; query.from_database_ = from_database; query.only_local_ = only_local; if (from_database) { LOG(INFO) << "Get history in " << dialog_id << " from " << from_message_id << " with offset " << offset << " and limit " << limit << " from database from " << source; query.old_last_message_id_ = d->last_database_message_id; auto &promises = get_history_queries_[query]; promises.push_back(std::move(promise)); if (promises.size() != 1) { // query has already been sent, just wait for the result return; } auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), query](Result &&result) { send_closure(actor_id, &MessagesManager::on_get_history_finished, query, std::move(result)); }); MessageDbMessagesQuery db_query; db_query.dialog_id = dialog_id; db_query.from_message_id = from_message_id; db_query.offset = offset; db_query.limit = limit; G()->td_db()->get_message_db_async()->get_messages( db_query, PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, from_message_id, old_last_database_message_id = d->last_database_message_id, offset, limit, only_local, promise = std::move(query_promise)](vector messages) mutable { send_closure(actor_id, &MessagesManager::on_get_history_from_database, dialog_id, from_message_id, old_last_database_message_id, offset, limit, only_local, std::move(messages), std::move(promise)); })); } else { if (only_local || dialog_id.get_type() == DialogType::SecretChat || (from_the_end && d->last_message_id.is_valid())) { // if the last message is known, there are no reasons to get message history from server from the end return promise.set_value(Unit()); } query.old_last_message_id_ = d->last_new_message_id; auto &promises = get_history_queries_[query]; promises.push_back(std::move(promise)); if (promises.size() != 1) { // query has already been sent, just wait for the result return; } auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), query](Result &&result) { send_closure(actor_id, &MessagesManager::on_get_history_finished, query, std::move(result)); }); if (from_the_end) { LOG(INFO) << "Get history from the end of " << dialog_id << " from server from " << source; td_->create_handler(std::move(query_promise)) ->send_get_from_the_end(dialog_id, d->last_new_message_id, limit); } else { LOG(INFO) << "Get history in " << dialog_id << " from " << from_message_id << " with offset " << offset << " and limit " << limit << " from server from " << source; td_->create_handler(std::move(query_promise)) ->send(dialog_id, from_message_id.get_next_server_message_id(), d->last_new_message_id, offset, limit); } } } void MessagesManager::load_messages(DialogId dialog_id, MessageId from_message_id, int32 offset, int32 limit, int left_tries, bool only_local, Promise &&promise) { load_messages_impl(get_dialog(dialog_id), from_message_id, offset, limit, left_tries, only_local, std::move(promise)); } void MessagesManager::load_messages_impl(const Dialog *d, MessageId from_message_id, int32 offset, int32 limit, int left_tries, bool only_local, Promise &&promise) { CHECK(d != nullptr); CHECK(offset <= 0); CHECK(left_tries > 0); auto dialog_id = d->dialog_id; LOG(INFO) << "Load " << (only_local ? "local " : "") << "messages in " << dialog_id << " from " << from_message_id << " with offset = " << offset << " and limit = " << limit << ". " << left_tries << " tries left"; only_local |= dialog_id.get_type() == DialogType::SecretChat; if (!only_local && d->have_full_history) { LOG(INFO) << "Have full history in " << dialog_id << ", so don't need to get chat history from server"; only_local = true; } bool from_database = (left_tries > 2 || only_local) && G()->use_message_database(); get_history_impl(d, from_message_id, offset, limit, from_database, only_local, std::move(promise), "load_messages_impl"); } vector MessagesManager::get_dialog_scheduled_messages(DialogId dialog_id, bool force, bool ignore_result, Promise &&promise) { if (G()->close_flag()) { promise.set_error(Global::request_aborted_error()); return {}; } LOG(INFO) << "Get scheduled messages in " << dialog_id; Dialog *d = get_dialog_force(dialog_id, "get_dialog_scheduled_messages"); if (d == nullptr) { promise.set_error(Status::Error(400, "Chat not found")); return {}; } if (!have_input_peer(dialog_id, AccessRights::Read)) { promise.set_error(Status::Error(400, "Can't access the chat")); return {}; } if (is_broadcast_channel(dialog_id) && !td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).can_post_messages()) { promise.set_error(Status::Error(400, "Not enough rights to get scheduled messages")); return {}; } if (d->dialog_id.get_type() == DialogType::SecretChat) { promise.set_value(Unit()); return {}; } if (!d->has_loaded_scheduled_messages_from_database) { load_dialog_scheduled_messages(dialog_id, true, 0, std::move(promise)); return {}; } vector message_ids; if (d->scheduled_messages != nullptr) { for (const auto &it : d->scheduled_messages->scheduled_messages_) { message_ids.push_back(it.first); }; std::sort(message_ids.begin(), message_ids.end(), std::greater<>()); } if (G()->use_message_database()) { bool has_scheduled_database_messages = false; for (auto &message_id : message_ids) { CHECK(message_id.is_valid_scheduled()); if (!message_id.is_yet_unsent()) { has_scheduled_database_messages = true; break; } } set_dialog_has_scheduled_database_messages(d->dialog_id, has_scheduled_database_messages); } if (d->scheduled_messages_sync_generation != scheduled_messages_sync_generation_) { vector numbers; for (auto &message_id : message_ids) { if (!message_id.is_scheduled_server()) { continue; } numbers.push_back(message_id.get_scheduled_server_message_id().get()); const Message *m = get_message(d, message_id); CHECK(m != nullptr); CHECK(m->message_id.get_scheduled_server_message_id() == message_id.get_scheduled_server_message_id()); numbers.push_back(m->edit_date); numbers.push_back(m->date); } auto hash = get_vector_hash(numbers); if (!force && (d->has_scheduled_server_messages || (d->scheduled_messages_sync_generation == 0 && !G()->use_message_database()))) { load_dialog_scheduled_messages(dialog_id, false, hash, std::move(promise)); return {}; } load_dialog_scheduled_messages(dialog_id, false, hash, Promise()); } if (!ignore_result) { d->sent_scheduled_messages = true; } promise.set_value(Unit()); return message_ids; } void MessagesManager::load_dialog_scheduled_messages(DialogId dialog_id, bool from_database, int64 hash, Promise &&promise) { CHECK(dialog_id.get_type() != DialogType::SecretChat); if (G()->use_message_database() && from_database) { LOG(INFO) << "Load scheduled messages from database in " << dialog_id; auto &queries = load_scheduled_messages_from_database_queries_[dialog_id]; queries.push_back(std::move(promise)); if (queries.size() == 1) { G()->td_db()->get_message_db_async()->get_scheduled_messages( dialog_id, 1000, PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](vector messages) { send_closure(actor_id, &MessagesManager::on_get_scheduled_messages_from_database, dialog_id, std::move(messages)); })); } } else { td_->create_handler(std::move(promise)) ->send(dialog_id, hash, scheduled_messages_sync_generation_); } } void MessagesManager::on_get_scheduled_messages_from_database(DialogId dialog_id, vector &&messages) { if (G()->close_flag()) { auto it = load_scheduled_messages_from_database_queries_.find(dialog_id); CHECK(it != load_scheduled_messages_from_database_queries_.end()); CHECK(!it->second.empty()); auto promises = std::move(it->second); load_scheduled_messages_from_database_queries_.erase(it); fail_promises(promises, Global::request_aborted_error()); return; } auto d = get_dialog(dialog_id); CHECK(d != nullptr); d->has_loaded_scheduled_messages_from_database = true; LOG(INFO) << "Receive " << messages.size() << " scheduled messages from database in " << dialog_id; Dependencies dependencies; vector added_message_ids; for (auto &message_slice : messages) { auto message = parse_message(d, message_slice.message_id, message_slice.data, true); if (message == nullptr) { continue; } if (get_message(d, message->message_id) != nullptr) { continue; } bool need_update = false; Message *m = add_scheduled_message_to_dialog(d, std::move(message), true, false, &need_update, "on_get_scheduled_messages_from_database"); if (m != nullptr) { add_message_dependencies(dependencies, m); added_message_ids.push_back(m->message_id); } } dependencies.resolve_force(td_, "on_get_scheduled_messages_from_database"); // for (auto message_id : added_message_ids) { // send_update_new_message(d, get_message(d, message_id)); // } send_update_chat_has_scheduled_messages(d, false); auto it = load_scheduled_messages_from_database_queries_.find(dialog_id); CHECK(it != load_scheduled_messages_from_database_queries_.end()); CHECK(!it->second.empty()); auto promises = std::move(it->second); load_scheduled_messages_from_database_queries_.erase(it); set_promises(promises); } Result> MessagesManager::get_message_available_reactions( MessageFullId message_full_id, int32 row_size) { auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "get_message_available_reactions"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } const Message *m = get_message_force(d, message_full_id.get_message_id(), "get_message_available_reactions"); if (m == nullptr) { return Status::Error(400, "Message not found"); } return td_->reaction_manager_->get_sorted_available_reactions(get_message_available_reactions(d, m, false), get_message_active_reactions(d, m), row_size); } ChatReactions MessagesManager::get_message_available_reactions(const Dialog *d, const Message *m, bool disallow_custom_for_non_premium) { CHECK(d != nullptr); CHECK(m != nullptr); auto active_reactions = get_message_active_reactions(d, m); if (!m->message_id.is_valid() || !m->message_id.is_server() || active_reactions.empty()) { return {}; } bool can_use_reactions = true; if (d->dialog_id.get_type() == DialogType::Channel) { auto channel_id = d->dialog_id.get_channel_id(); if (td_->contacts_manager_->is_megagroup_channel(channel_id) && !td_->contacts_manager_->get_channel_status(channel_id).is_member() && can_send_message(d->dialog_id).is_error()) { // can't use reactions if can't send messages to the group without joining can_use_reactions = false; } else if (is_anonymous_administrator(d->dialog_id, nullptr) && !is_broadcast_channel(d->dialog_id) && !td_->contacts_manager_->get_channel_status(channel_id).is_creator()) { // only creator can react as the chat can_use_reactions = false; } } int64 reactions_uniq_max = td_->option_manager_->get_option_integer("reactions_uniq_max", 11); bool can_add_new_reactions = m->reactions == nullptr || static_cast(m->reactions->reactions_.size()) < reactions_uniq_max; if (!can_use_reactions || !can_add_new_reactions) { active_reactions = ChatReactions(); } if (active_reactions.allow_all_regular_) { active_reactions.reaction_types_ = active_reaction_types_; active_reactions.allow_all_regular_ = false; } if (can_use_reactions && m->reactions != nullptr) { for (const auto &reaction : m->reactions->reactions_) { // an already used reaction can be added if it is an active reaction const auto &reaction_type = reaction.get_reaction_type(); if (reaction_type.is_active_reaction(active_reaction_pos_) && !td::contains(active_reactions.reaction_types_, reaction_type)) { active_reactions.reaction_types_.push_back(reaction_type); } } } if (disallow_custom_for_non_premium && !td_->option_manager_->get_option_boolean("is_premium")) { active_reactions.allow_all_custom_ = false; } return active_reactions; } DialogId MessagesManager::get_my_reaction_dialog_id(const Dialog *d) const { auto my_dialog_id = get_my_dialog_id(); auto reaction_dialog_id = d->default_send_message_as_dialog_id.is_valid() ? d->default_send_message_as_dialog_id : my_dialog_id; if (reaction_dialog_id == my_dialog_id && is_anonymous_administrator(d->dialog_id, nullptr) && !is_broadcast_channel(d->dialog_id)) { // react as the supergroup return d->dialog_id; } return reaction_dialog_id; } void MessagesManager::add_message_reaction(MessageFullId message_full_id, ReactionType reaction_type, bool is_big, bool add_to_recent, Promise &&promise) { auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "add_message_reaction"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } Message *m = get_message_force(d, message_full_id.get_message_id(), "add_message_reaction"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } if (!get_message_available_reactions(d, m, true).is_allowed_reaction_type(reaction_type)) { return promise.set_error(Status::Error(400, "The reaction isn't available for the message")); } bool have_recent_choosers = !is_broadcast_channel(dialog_id) && !is_discussion_message(dialog_id, m); if (m->reactions == nullptr) { m->reactions = make_unique(); m->reactions->can_get_added_reactions_ = have_recent_choosers && dialog_id.get_type() != DialogType::User; m->available_reactions_generation = d->available_reactions_generation; } LOG(INFO) << "Have message with " << *m->reactions; if (!m->reactions->add_my_reaction(reaction_type, is_big, get_my_reaction_dialog_id(d), have_recent_choosers)) { return promise.set_value(Unit()); } if (add_to_recent) { td_->reaction_manager_->add_recent_reaction(reaction_type); } set_message_reactions(d, m, is_big, add_to_recent, std::move(promise)); } void MessagesManager::remove_message_reaction(MessageFullId message_full_id, ReactionType reaction_type, Promise &&promise) { auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "remove_message_reaction"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } Message *m = get_message_force(d, message_full_id.get_message_id(), "remove_message_reaction"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } if (reaction_type.is_empty()) { return promise.set_error(Status::Error(400, "Invalid reaction specified")); } if (m->reactions == nullptr) { return promise.set_value(Unit()); } LOG(INFO) << "Have message with " << *m->reactions; if (!m->reactions->remove_my_reaction(reaction_type, get_my_reaction_dialog_id(d))) { return promise.set_value(Unit()); } set_message_reactions(d, m, false, false, std::move(promise)); } void MessagesManager::set_message_reactions(Dialog *d, Message *m, bool is_big, bool add_to_recent, Promise &&promise) { CHECK(m->reactions != nullptr); m->reactions->sort_reactions(active_reaction_pos_); LOG(INFO) << "Update message reactions to " << *m->reactions; MessageFullId message_full_id{d->dialog_id, m->message_id}; pending_reactions_[message_full_id].query_count++; send_update_message_interaction_info(d->dialog_id, m); on_message_changed(d, m, true, "set_message_reactions"); // TODO cancel previous queries, log event auto query_promise = PromiseCreator::lambda( [actor_id = actor_id(this), message_full_id, promise = std::move(promise)](Result &&result) mutable { send_closure(actor_id, &MessagesManager::on_set_message_reactions, message_full_id, std::move(result), std::move(promise)); }); send_message_reaction(td_, message_full_id, m->reactions->get_chosen_reaction_types(), is_big, add_to_recent, std::move(query_promise)); } void MessagesManager::on_set_message_reactions(MessageFullId message_full_id, Result result, Promise promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); bool need_reload = result.is_error(); auto it = pending_reactions_.find(message_full_id); CHECK(it != pending_reactions_.end()); if (--it->second.query_count == 0) { // need_reload |= it->second.was_updated; pending_reactions_.erase(it); } if (!have_message_force(message_full_id, "on_set_message_reactions")) { return promise.set_value(Unit()); } if (need_reload) { queue_message_reactions_reload(message_full_id); } promise.set_result(std::move(result)); } void MessagesManager::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 (!have_message_force(message_full_id, "on_read_message_reactions")) { continue; } if (result.is_error()) { queue_message_reactions_reload(message_full_id); } } } Result MessagesManager::get_message_schedule_date( td_api::object_ptr &&scheduling_state) { if (scheduling_state == nullptr) { return 0; } switch (scheduling_state->get_id()) { case td_api::messageSchedulingStateSendWhenOnline::ID: { auto send_date = SCHEDULE_WHEN_ONLINE_DATE; return send_date; } case td_api::messageSchedulingStateSendAtDate::ID: { auto send_at_date = td_api::move_object_as(scheduling_state); auto send_date = send_at_date->send_date_; if (send_date <= 0) { return Status::Error(400, "Invalid send date specified"); } if (send_date <= G()->unix_time() + 10) { return 0; } if (send_date - G()->unix_time() > 367 * 86400) { return Status::Error(400, "Send date is too far in the future"); } return send_date; } default: UNREACHABLE(); return 0; } } tl_object_ptr MessagesManager::get_message_sending_state_object(const Message *m) const { CHECK(m != nullptr); if (m->message_id.is_yet_unsent()) { return td_api::make_object(m->sending_id); } if (m->is_failed_to_send) { auto can_retry = can_resend_message(m); auto error_code = m->send_error_code > 0 ? m->send_error_code : 400; auto need_another_sender = can_retry && error_code == 400 && m->send_error_message == CSlice("SEND_AS_PEER_INVALID"); auto need_another_reply_quote = can_retry && error_code == 400 && m->send_error_message == CSlice("QUOTE_TEXT_INVALID"); auto need_drop_reply = can_retry && error_code == 400 && m->send_error_message == CSlice("REPLY_MESSAGE_ID_INVALID"); return td_api::make_object( td_api::make_object(error_code, m->send_error_message), can_retry, need_another_sender, need_another_reply_quote, need_drop_reply, max(m->try_resend_at - Time::now(), 0.0)); } return nullptr; } tl_object_ptr MessagesManager::get_message_scheduling_state_object(int32 send_date) { if (send_date == SCHEDULE_WHEN_ONLINE_DATE) { return td_api::make_object(); } return td_api::make_object(send_date); } td_api::object_ptr MessagesManager::get_message_message_content_object(DialogId dialog_id, const Message *m) const { auto live_location_date = m->is_failed_to_send ? 0 : m->date; return get_message_content_object(m->content.get(), td_, dialog_id, live_location_date, m->is_content_secret, need_skip_bot_commands(dialog_id, m), get_message_max_media_timestamp(m), m->invert_media, m->disable_web_page_preview); } td_api::object_ptr MessagesManager::get_dialog_event_log_message_object( DialogId dialog_id, tl_object_ptr &&message, DialogId &sender_dialog_id) { auto dialog_message = create_message(parse_telegram_api_message(std::move(message), false, "dialog_event_log"), dialog_id.get_type() == DialogType::Channel); const Message *m = dialog_message.second.get(); if (m == nullptr || dialog_message.first != dialog_id) { LOG(ERROR) << "Failed to create event log message in " << dialog_id; return nullptr; } sender_dialog_id = get_message_sender(m); auto sender = get_message_sender_object_const(td_, m->sender_user_id, m->sender_dialog_id, "get_dialog_event_log_message_object"); auto forward_info = get_message_forward_info_object(m->forward_info); auto import_info = get_message_import_info_object(m->forward_info); auto interaction_info = get_message_interaction_info_object(dialog_id, m); auto can_be_saved = can_save_message(dialog_id, m); auto via_bot_user_id = td_->contacts_manager_->get_user_id_object(m->via_bot_user_id, "via_bot_user_id"); auto edit_date = m->hide_edit_date ? 0 : m->edit_date; auto reply_markup = get_reply_markup_object(td_->contacts_manager_.get(), m->reply_markup); auto content = get_message_content_object(m->content.get(), td_, dialog_id, 0, false, true, get_message_own_max_media_timestamp(m), m->invert_media, m->disable_web_page_preview); return td_api::make_object( m->message_id.get(), std::move(sender), get_chat_id_object(dialog_id, "get_dialog_event_log_message_object"), nullptr, nullptr, m->is_outgoing, false, false, false, false, can_be_saved, false, false, false, false, false, false, false, false, true, m->is_channel_post, m->is_topic_message, false, m->date, edit_date, std::move(forward_info), std::move(import_info), std::move(interaction_info), Auto(), nullptr, 0, nullptr, 0.0, 0.0, via_bot_user_id, m->author_signature, 0, get_restriction_reason_description(m->restriction_reasons), std::move(content), std::move(reply_markup)); } tl_object_ptr MessagesManager::get_message_object(MessageFullId message_full_id, const char *source) { return get_message_object(message_full_id.get_dialog_id(), get_message_force(message_full_id, source), source); } tl_object_ptr MessagesManager::get_message_object(DialogId dialog_id, const Message *m, const char *source) const { if (m == nullptr) { return nullptr; } LOG_CHECK(have_dialog(dialog_id)) << source; auto sending_state = get_message_sending_state_object(m); if (sending_state == nullptr || !td_->auth_manager_->is_bot()) { m->is_update_sent = true; } bool can_delete = can_delete_message(dialog_id, m); bool is_scheduled = m->message_id.is_scheduled(); DialogId my_dialog_id = get_my_dialog_id(); bool can_delete_for_self = false; bool can_delete_for_all_users = can_delete && can_revoke_message(dialog_id, m); if (can_delete) { switch (dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: // TODO allow to delete yet unsent message just for self can_delete_for_self = !m->message_id.is_yet_unsent() || dialog_id == my_dialog_id; break; case DialogType::Channel: case DialogType::SecretChat: can_delete_for_self = !can_delete_for_all_users; break; case DialogType::None: default: UNREACHABLE(); } } if (is_scheduled) { can_delete_for_self = (dialog_id == my_dialog_id); can_delete_for_all_users = !can_delete_for_self; } bool is_outgoing = m->is_outgoing; if (dialog_id == my_dialog_id) { // in Saved Messages all non-forwarded messages must be outgoing // a forwarded message is outgoing, only if it doesn't have from_dialog_id and its sender isn't hidden // i.e. a message is incoming only if it's a forwarded message with known from_dialog_id or with a hidden sender auto forward_info = m->forward_info.get(); is_outgoing = is_scheduled || forward_info == nullptr || (!forward_info->from_dialog_id.is_valid() && !forward_info->origin.is_sender_hidden()); } double ttl_expires_in = m->ttl_expires_at != 0 ? clamp(m->ttl_expires_at - Time::now(), 1e-3, m->ttl - 1e-3) : 0.0; double auto_delete_in = m->ttl_period == 0 ? 0.0 : clamp(m->date + m->ttl_period - G()->server_time(), 1e-3, m->ttl_period - 1e-3); auto sender = get_message_sender_object_const(td_, m->sender_user_id, m->sender_dialog_id, source); auto scheduling_state = is_scheduled ? get_message_scheduling_state_object(m->date) : nullptr; auto forward_info = get_message_forward_info_object(m->forward_info); auto import_info = get_message_import_info_object(m->forward_info); auto interaction_info = get_message_interaction_info_object(dialog_id, m); auto unread_reactions = get_unread_reactions_object(dialog_id, m); auto can_be_saved = can_save_message(dialog_id, m); auto can_be_edited = can_edit_message(dialog_id, m, false, td_->auth_manager_->is_bot()); auto can_be_forwarded = can_be_saved && can_forward_message(dialog_id, m); auto can_be_replied_in_another_chat = can_be_forwarded && m->message_id.is_server(); auto can_get_added_reactions = m->reactions != nullptr && m->reactions->can_get_added_reactions_; auto can_get_statistics = can_get_message_statistics(dialog_id, m); auto can_get_message_thread = get_top_thread_message_full_id(dialog_id, m, false).is_ok(); auto can_get_viewers = can_get_message_viewers(dialog_id, m).is_ok(); auto can_get_media_timestamp_links = can_get_media_timestamp_link(dialog_id, m).is_ok(); auto can_report_reactions = can_report_message_reactions(dialog_id, m); auto via_bot_user_id = td_->contacts_manager_->get_user_id_object(m->via_bot_user_id, "via_bot_user_id"); auto reply_to = [&]() -> td_api::object_ptr { if (!m->replied_message_info.is_empty()) { if (m->is_topic_message && m->replied_message_info.get_same_chat_reply_to_message_id(false) == m->top_thread_message_id && !td_->auth_manager_->is_bot()) { return nullptr; } return m->replied_message_info.get_message_reply_to_message_object(td_, dialog_id); } if (m->reply_to_story_full_id.is_valid()) { return td_api::make_object( get_chat_id_object(m->reply_to_story_full_id.get_dialog_id(), "messageReplyToStory"), m->reply_to_story_full_id.get_story_id().get()); } return nullptr; }(); auto top_thread_message_id = m->top_thread_message_id.get(); auto date = is_scheduled ? 0 : m->date; auto edit_date = m->hide_edit_date ? 0 : m->edit_date; auto has_timestamped_media = reply_to == nullptr || m->max_own_media_timestamp >= 0; auto reply_markup = get_reply_markup_object(td_->contacts_manager_.get(), m->reply_markup); auto content = get_message_message_content_object(dialog_id, m); auto self_destruct_type = [&]() -> td_api::object_ptr { if (m->ttl == 0x7FFFFFFF) { return td_api::make_object(); } if (m->ttl > 0) { return td_api::make_object(m->ttl); } return nullptr; }(); return td_api::make_object( m->message_id.get(), std::move(sender), get_chat_id_object(dialog_id, "get_message_object"), std::move(sending_state), std::move(scheduling_state), is_outgoing, m->is_pinned, can_be_edited, can_be_forwarded, can_be_replied_in_another_chat, can_be_saved, can_delete_for_self, can_delete_for_all_users, can_get_added_reactions, can_get_statistics, can_get_message_thread, can_get_viewers, can_get_media_timestamp_links, can_report_reactions, has_timestamped_media, m->is_channel_post, m->is_topic_message, m->contains_unread_mention, date, edit_date, std::move(forward_info), std::move(import_info), std::move(interaction_info), std::move(unread_reactions), std::move(reply_to), top_thread_message_id, std::move(self_destruct_type), ttl_expires_in, auto_delete_in, via_bot_user_id, m->author_signature, m->media_album_id, get_restriction_reason_description(m->restriction_reasons), std::move(content), std::move(reply_markup)); } tl_object_ptr MessagesManager::get_messages_object(int32 total_count, DialogId dialog_id, const vector &message_ids, bool skip_not_found, const char *source) { Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); auto message_objects = transform(message_ids, [this, dialog_id, d, source](MessageId message_id) { return get_message_object(dialog_id, get_message_force(d, message_id, source), source); }); return get_messages_object(total_count, std::move(message_objects), skip_not_found); } tl_object_ptr MessagesManager::get_messages_object(int32 total_count, const vector &message_full_ids, bool skip_not_found, const char *source) { auto message_objects = transform(message_full_ids, [this, source](MessageFullId message_full_id) { return get_message_object(message_full_id, source); }); return get_messages_object(total_count, std::move(message_objects), skip_not_found); } tl_object_ptr MessagesManager::get_messages_object(int32 total_count, vector> &&messages, bool skip_not_found) { auto message_count = narrow_cast(messages.size()); if (total_count < message_count) { if (total_count != -1) { LOG(ERROR) << "Have wrong total_count = " << total_count << ", while having " << message_count << " messages"; } total_count = message_count; } if (skip_not_found && td::remove(messages, nullptr)) { total_count -= message_count - static_cast(messages.size()); } return td_api::make_object(total_count, std::move(messages)); } bool MessagesManager::is_anonymous_administrator(DialogId dialog_id, string *author_signature) const { CHECK(dialog_id.is_valid()); if (is_broadcast_channel(dialog_id)) { return true; } if (td_->auth_manager_->is_bot()) { return false; } if (dialog_id.get_type() != DialogType::Channel) { return false; } auto status = td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()); if (!status.is_anonymous()) { return false; } if (author_signature != nullptr) { *author_signature = status.get_rank(); } return true; } bool MessagesManager::get_dialog_silent_send_message(DialogId dialog_id) const { auto *d = get_dialog(dialog_id); CHECK(d != nullptr); return d->notification_settings.silent_send_message; } bool MessagesManager::get_dialog_has_last_message(DialogId dialog_id) const { const auto *d = get_dialog(dialog_id); return d != nullptr && d->last_message_id.is_valid(); } DialogId MessagesManager::get_dialog_default_send_message_as_dialog_id(DialogId dialog_id) const { auto *d = get_dialog(dialog_id); CHECK(d != nullptr); return d->default_send_message_as_dialog_id; } MessageInputReplyTo MessagesManager::get_message_input_reply_to( DialogId dialog_id, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, bool for_draft) { return get_message_input_reply_to(get_dialog(dialog_id), top_thread_message_id, std::move(reply_to), for_draft); } int64 MessagesManager::generate_new_random_id(const Dialog *d) { int64 random_id; do { random_id = Random::secure_int64(); } while (random_id == 0 || being_sent_messages_.count(random_id) > 0 || d->random_id_to_message_id.count(random_id) > 0); return random_id; } unique_ptr MessagesManager::create_message_to_send( Dialog *d, MessageId top_thread_message_id, MessageInputReplyTo &&input_reply_to, const MessageSendOptions &options, unique_ptr &&content, bool invert_media, bool suppress_reply_info, unique_ptr forward_info, bool is_copy, DialogId send_as_dialog_id) const { CHECK(d != nullptr); CHECK(content != nullptr); bool is_scheduled = options.schedule_date != 0; DialogId dialog_id = d->dialog_id; auto dialog_type = dialog_id.get_type(); auto my_id = td_->contacts_manager_->get_my_id(); int64 reply_to_random_id = 0; bool is_topic_message = false; auto same_chat_reply_to_message_id = input_reply_to.get_same_chat_reply_to_message_id(); if (same_chat_reply_to_message_id.is_valid() || same_chat_reply_to_message_id.is_valid_scheduled()) { // the message was forcely preloaded in get_message_input_reply_to // it can be missing, only if it is unknown message from a push notification, or an unknown top thread message const Message *reply_m = get_message(d, same_chat_reply_to_message_id); if (reply_m != nullptr && !same_chat_reply_to_message_id.is_scheduled()) { if (reply_m->top_thread_message_id.is_valid()) { top_thread_message_id = reply_m->top_thread_message_id; } is_topic_message = reply_m->is_topic_message; } if (dialog_type == DialogType::SecretChat || same_chat_reply_to_message_id.is_yet_unsent()) { if (reply_m != nullptr) { reply_to_random_id = reply_m->random_id; } else { CHECK(dialog_type == DialogType::SecretChat); CHECK(top_thread_message_id == MessageId()); input_reply_to = MessageInputReplyTo(); } } } if (top_thread_message_id.is_valid()) { const Message *top_m = get_message(d, top_thread_message_id); if (top_m != nullptr) { is_topic_message = top_m->is_topic_message; } } auto message = make_unique(); auto *m = message.get(); bool is_channel_post = is_broadcast_channel(dialog_id); if (is_channel_post) { // sender of the post can be hidden if (!is_scheduled && td_->contacts_manager_->get_channel_sign_messages(dialog_id.get_channel_id())) { m->author_signature = td_->contacts_manager_->get_user_title(my_id); } m->sender_dialog_id = dialog_id; } else { if (send_as_dialog_id.is_valid()) { if (send_as_dialog_id.get_type() == DialogType::User) { m->sender_user_id = send_as_dialog_id.get_user_id(); } else { m->sender_dialog_id = send_as_dialog_id; } } else if (d->default_send_message_as_dialog_id.is_valid()) { if (d->default_send_message_as_dialog_id.get_type() == DialogType::User) { m->sender_user_id = my_id; } else { m->sender_dialog_id = d->default_send_message_as_dialog_id; } m->has_explicit_sender = true; } else { if (is_anonymous_administrator(dialog_id, &m->author_signature)) { m->sender_dialog_id = dialog_id; } else { m->sender_user_id = my_id; } } } m->message_id = options.schedule_date != 0 ? get_next_yet_unsent_scheduled_message_id(d, options.schedule_date) : get_next_yet_unsent_message_id(d); m->send_date = G()->unix_time(); m->date = is_scheduled ? options.schedule_date : m->send_date; m->replied_message_info = RepliedMessageInfo(td_, input_reply_to); m->reply_to_story_full_id = input_reply_to.get_story_full_id(); m->input_reply_to = std::move(input_reply_to); m->reply_to_random_id = reply_to_random_id; m->top_thread_message_id = top_thread_message_id; m->is_topic_message = is_topic_message; m->is_channel_post = is_channel_post; m->is_outgoing = is_scheduled || dialog_id != DialogId(my_id); m->from_background = options.from_background; m->update_stickersets_order = options.update_stickersets_order; m->noforwards = options.protect_content; m->view_count = is_channel_post && !is_scheduled ? 1 : 0; m->forward_count = 0; if ([&] { if (suppress_reply_info) { return false; } if (is_scheduled) { return false; } if (dialog_type != DialogType::Channel) { return false; } if (td_->auth_manager_->is_bot()) { return false; } if (is_channel_post) { return td_->contacts_manager_->get_channel_has_linked_channel(dialog_id.get_channel_id()); } return !m->input_reply_to.is_valid(); }()) { m->reply_info.reply_count_ = 0; if (is_channel_post) { auto linked_channel_id = td_->contacts_manager_->get_channel_linked_channel_id(dialog_id.get_channel_id(), "create_message_to_send"); if (linked_channel_id.is_valid()) { m->reply_info.is_comment_ = true; m->reply_info.channel_id_ = linked_channel_id; } } } m->content = std::move(content); m->invert_media = invert_media; m->forward_info = std::move(forward_info); m->is_copy = is_copy || m->forward_info != nullptr; m->sending_id = options.sending_id; if (td_->auth_manager_->is_bot() || options.disable_notification || td_->option_manager_->get_option_boolean("ignore_default_disable_notification")) { m->disable_notification = options.disable_notification; } else { m->disable_notification = d->notification_settings.silent_send_message; } if (dialog_type == DialogType::SecretChat) { CHECK(!is_scheduled); m->ttl = min(td_->contacts_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id()), 0x7FFFFFFE); if (is_service_message_content(m->content->get_type())) { m->ttl = 0; } m->is_content_secret = is_secret_message_content(m->ttl, m->content->get_type()); } return message; } MessagesManager::Message *MessagesManager::get_message_to_send( Dialog *d, MessageId top_thread_message_id, MessageInputReplyTo &&input_reply_to, const MessageSendOptions &options, unique_ptr &&content, bool invert_media, bool *need_update_dialog_pos, bool suppress_reply_info, unique_ptr forward_info, bool is_copy, DialogId send_as_dialog_id) { d->was_opened = true; auto message = create_message_to_send(d, top_thread_message_id, std::move(input_reply_to), options, std::move(content), invert_media, suppress_reply_info, std::move(forward_info), is_copy, send_as_dialog_id); auto message_id = message->message_id; message->random_id = generate_new_random_id(d); bool need_update = false; CHECK(have_input_peer(d->dialog_id, AccessRights::Read)); auto result = add_message_to_dialog(d, std::move(message), false, true, &need_update, need_update_dialog_pos, "send message"); LOG_CHECK(result != nullptr) << message_id << " " << debug_add_message_to_dialog_fail_reason_; if (result->message_id.is_scheduled()) { send_update_chat_has_scheduled_messages(d, false); } if (options.update_stickersets_order && !td_->auth_manager_->is_bot()) { move_message_content_sticker_set_to_top(td_, result->content.get()); } return result; } int64 MessagesManager::begin_send_message(DialogId dialog_id, const Message *m) { LOG(INFO) << "Begin to send " << MessageFullId(dialog_id, m->message_id) << " with random_id = " << m->random_id; CHECK(m->random_id != 0); CHECK(m->message_id.is_yet_unsent()); bool is_inserted = being_sent_messages_.emplace(m->random_id, MessageFullId(dialog_id, m->message_id)).second; CHECK(is_inserted); return m->random_id; } Status MessagesManager::can_send_message(DialogId dialog_id) const { if (!have_input_peer(dialog_id, AccessRights::Write)) { return Status::Error(400, "Have no write access to the chat"); } if (dialog_id.get_type() == DialogType::Channel) { auto channel_id = dialog_id.get_channel_id(); auto channel_type = td_->contacts_manager_->get_channel_type(channel_id); auto channel_status = td_->contacts_manager_->get_channel_permissions(channel_id); switch (channel_type) { case ChannelType::Unknown: case ChannelType::Megagroup: break; case ChannelType::Broadcast: { if (!channel_status.can_post_messages()) { return Status::Error(400, "Need administrator rights in the channel chat"); } break; } default: UNREACHABLE(); } } return Status::OK(); } MessageId MessagesManager::get_persistent_message_id(const Dialog *d, MessageId message_id) const { if (!message_id.is_valid() && !message_id.is_valid_scheduled()) { return MessageId(); } if (message_id.is_yet_unsent()) { // it is possible that user tries to do something with an already sent message by its temporary identifier // we need to use real message in this case and transparently replace message_id auto it = yet_unsent_message_full_id_to_persistent_message_id_.find({d->dialog_id, message_id}); if (it != yet_unsent_message_full_id_to_persistent_message_id_.end()) { return it->second; } } return message_id; } MessageInputReplyTo MessagesManager::get_message_input_reply_to( Dialog *d, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, bool for_draft) { CHECK(d != nullptr); if (top_thread_message_id.is_valid() && !have_message_force(d, top_thread_message_id, "get_message_input_reply_to 1")) { LOG(INFO) << "Have reply in the thread of unknown " << top_thread_message_id; } if (reply_to == nullptr) { if (!for_draft && top_thread_message_id.is_valid() && top_thread_message_id.is_server()) { return MessageInputReplyTo{top_thread_message_id, DialogId(), FormattedText(), 0}; } return {}; } switch (reply_to->get_id()) { case td_api::inputMessageReplyToStory::ID: { if (for_draft) { return {}; } auto reply_to_story = td_api::move_object_as(reply_to); auto story_id = StoryId(reply_to_story->story_id_); auto sender_dialog_id = DialogId(reply_to_story->story_sender_chat_id_); if (d->dialog_id != sender_dialog_id || sender_dialog_id.get_type() != DialogType::User) { LOG(INFO) << "Ignore reply to story from " << sender_dialog_id << " in a wrong " << d->dialog_id; return {}; } if (!story_id.is_server()) { LOG(INFO) << "Ignore reply to invalid " << story_id; return {}; } return MessageInputReplyTo{StoryFullId(sender_dialog_id, story_id)}; } case td_api::inputMessageReplyToMessage::ID: { auto reply_to_message = td_api::move_object_as(reply_to); auto message_id = MessageId(reply_to_message->message_id_); if (!message_id.is_valid()) { if (!for_draft && message_id == MessageId() && top_thread_message_id.is_valid() && top_thread_message_id.is_server()) { return MessageInputReplyTo{top_thread_message_id, DialogId(), FormattedText(), 0}; } return {}; } auto *reply_d = d; auto reply_dialog_id = DialogId(reply_to_message->chat_id_); if (reply_dialog_id != DialogId()) { reply_d = get_dialog_force(reply_dialog_id, "get_message_input_reply_to"); if (reply_d == nullptr) { return {}; } if (d->dialog_id.get_type() == DialogType::SecretChat) { return {}; } } message_id = get_persistent_message_id(reply_d, message_id); if (message_id == MessageId(ServerMessageId(1)) && reply_d->dialog_id.get_type() == DialogType::Channel) { return {}; } FormattedText quote; int32 quote_position = 0; if (reply_to_message->quote_ != nullptr) { int32 ltrim_count = 0; auto r_quote = get_formatted_text(td_, get_my_dialog_id(), std::move(reply_to_message->quote_->text_), td_->auth_manager_->is_bot(), true, true, false, <rim_count); if (r_quote.is_ok() && d->dialog_id.get_type() != DialogType::SecretChat && !r_quote.ok().text.empty()) { quote = r_quote.move_as_ok(); quote_position = reply_to_message->quote_->position_; if (0 <= quote_position && quote_position <= 1000000) { // some unreasonably big bound quote_position += ltrim_count; } else { quote_position = 0; } } } const Message *m = get_message_force(reply_d, message_id, "get_message_input_reply_to 2"); if (m == nullptr || m->message_id.is_yet_unsent() || (m->message_id.is_local() && reply_d->dialog_id.get_type() != DialogType::SecretChat)) { if (message_id.is_server() && reply_d->dialog_id.get_type() != DialogType::SecretChat && reply_dialog_id == DialogId() && message_id > reply_d->last_new_message_id && (reply_d->notification_info != nullptr && message_id <= reply_d->notification_info->max_push_notification_message_id_)) { // allow to reply yet unreceived server message in the same chat return MessageInputReplyTo{message_id, reply_dialog_id, std::move(quote), quote_position}; } if (!for_draft && top_thread_message_id.is_valid() && top_thread_message_id.is_server()) { return MessageInputReplyTo{top_thread_message_id, DialogId(), FormattedText(), 0}; } LOG(INFO) << "Can't find " << message_id << " in " << reply_d->dialog_id; // TODO local replies to local messages can be allowed // TODO replies to yet unsent messages can be allowed with special handling of them on application restart return {}; } if (reply_dialog_id != DialogId() && (!can_forward_message(reply_dialog_id, m) || !m->message_id.is_server())) { LOG(INFO) << "Can't reply in another chat " << m->message_id << " in " << reply_d->dialog_id; return {}; } return MessageInputReplyTo{m->message_id, reply_dialog_id, std::move(quote), quote_position}; } default: UNREACHABLE(); return {}; } } MessagesManager::ForwardedMessageInfo MessagesManager::get_forwarded_message_info(MessageFullId message_full_id) { ForwardedMessageInfo result; auto *m = get_message_force(message_full_id, "get_forwarded_message_info"); if (m == nullptr || m->message_id.is_scheduled()) { return result; } auto dialog_id = message_full_id.get_dialog_id(); result.origin_date_ = m->forward_info != nullptr ? m->forward_info->date : m->date; result.origin_ = get_forwarded_message_origin(dialog_id, m); result.content_ = dup_message_content(td_, get_my_dialog_id(), m->content.get(), MessageContentDupType::Forward, MessageCopyOptions()); return result; } const MessageInputReplyTo *MessagesManager::get_message_input_reply_to(const Message *m) { CHECK(m != nullptr); CHECK(!m->message_id.is_any_server()); return &m->input_reply_to; } vector MessagesManager::get_message_file_ids(const Message *m) const { CHECK(m != nullptr); auto file_ids = get_message_content_file_ids(m->content.get(), td_); if (!m->replied_message_info.is_empty()) { append(file_ids, m->replied_message_info.get_file_ids(td_)); } return file_ids; } void MessagesManager::cancel_upload_message_content_files(const MessageContent *content) { auto file_id = get_message_content_upload_file_id(content); // always cancel file upload, it should be a no-op in the worst case if (being_uploaded_files_.erase(file_id) || file_id.is_valid()) { cancel_upload_file(file_id, "cancel_upload_message_content_files"); } file_id = get_message_content_thumbnail_file_id(content, td_); if (being_uploaded_thumbnails_.erase(file_id) || file_id.is_valid()) { cancel_upload_file(file_id, "cancel_upload_message_content_files"); } } void MessagesManager::cancel_upload_file(FileId file_id, const char *source) { // send the request later so they doesn't interfere with other actions // for example merge, supposed to happen soon, can auto-cancel the upload LOG(INFO) << "Cancel upload of file " << file_id << " from " << source; send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_id); } void MessagesManager::cancel_send_message_query(DialogId dialog_id, Message *m) { CHECK(m != nullptr); CHECK(m->content != nullptr); CHECK(m->message_id.is_valid() || m->message_id.is_valid_scheduled()); CHECK(m->message_id.is_yet_unsent()); LOG(INFO) << "Cancel send message query for " << m->message_id; cancel_upload_message_content_files(m->content.get()); CHECK(m->edited_content == nullptr); if (!m->send_query_ref.empty()) { LOG(INFO) << "Cancel send query for " << m->message_id; cancel_query(m->send_query_ref); m->send_query_ref = NetQueryRef(); } if (m->send_message_log_event_id != 0) { LOG(INFO) << "Delete send message log event for " << m->message_id; binlog_erase(G()->td_db()->get_binlog(), m->send_message_log_event_id); m->send_message_log_event_id = 0; } update_replied_by_message_count(dialog_id, m, false); { auto it = replied_yet_unsent_messages_.find({dialog_id, m->message_id}); if (it != replied_yet_unsent_messages_.end()) { for (auto message_full_id : it->second) { auto reply_d = get_dialog(message_full_id.get_dialog_id()); CHECK(reply_d != nullptr); auto replied_m = get_message(reply_d, message_full_id.get_message_id()); CHECK(replied_m != nullptr); const auto *input_reply_to = get_message_input_reply_to(replied_m); CHECK(input_reply_to != nullptr); CHECK(input_reply_to->get_reply_message_full_id(reply_d->dialog_id) == MessageFullId(dialog_id, m->message_id)); set_message_reply(reply_d, replied_m, MessageInputReplyTo{replied_m->top_thread_message_id, DialogId(), FormattedText(), 0}, true); } replied_yet_unsent_messages_.erase(it); } } if (m->media_album_id != 0) { send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id, dialog_id, m->message_id, Status::OK()); } if (!m->message_id.is_scheduled() && G()->keep_media_order() && !m->is_copy) { auto queue_id = ChainId(dialog_id, m->content->get_type()).get(); if (queue_id & 1) { auto queue_it = yet_unsent_media_queues_.find(queue_id); if (queue_it != yet_unsent_media_queues_.end()) { auto &queue = queue_it->second.queue_; LOG(INFO) << "Delete " << m->message_id << " from queue " << queue_id; if (queue.erase(m->message_id) != 0) { if (queue.empty()) { yet_unsent_media_queues_.erase(queue_it); } else { // send later, because delete_all_dialog_messages can be called right now send_closure_later(actor_id(this), &MessagesManager::on_yet_unsent_media_queue_updated, dialog_id); } } } } } } void MessagesManager::cancel_send_deleted_message(DialogId dialog_id, Message *m, bool is_permanently_deleted) { CHECK(m != nullptr); if (m->message_id.is_yet_unsent()) { cancel_send_message_query(dialog_id, m); } else if (is_permanently_deleted || !m->message_id.is_scheduled()) { cancel_edit_message_media(dialog_id, m, "Message was deleted"); } } bool MessagesManager::is_message_auto_read(DialogId dialog_id, bool is_outgoing) const { switch (dialog_id.get_type()) { case DialogType::User: { auto user_id = dialog_id.get_user_id(); if (user_id == td_->contacts_manager_->get_my_id()) { return true; } if (is_outgoing && td_->contacts_manager_->is_user_bot(user_id) && !td_->contacts_manager_->is_user_support(user_id)) { return true; } return false; } case DialogType::Chat: // TODO auto_read message content and messages sent to group with bots only return false; case DialogType::Channel: return is_outgoing && is_broadcast_channel(dialog_id); case DialogType::SecretChat: return false; case DialogType::None: return false; default: UNREACHABLE(); return false; } } void MessagesManager::add_message_dependencies(Dependencies &dependencies, const Message *m) { auto is_bot = td_->auth_manager_->is_bot(); dependencies.add(m->sender_user_id); dependencies.add_dialog_and_dependencies(m->sender_dialog_id); m->replied_message_info.add_dependencies(dependencies, is_bot); dependencies.add_dialog_and_dependencies(m->reply_to_story_full_id.get_dialog_id()); dependencies.add_dialog_and_dependencies(m->real_forward_from_dialog_id); dependencies.add(m->via_bot_user_id); if (m->forward_info != nullptr) { m->forward_info->origin.add_dependencies(dependencies); dependencies.add_dialog_and_dependencies(m->forward_info->from_dialog_id); } for (const auto &replier_min_channel : m->reply_info.replier_min_channels_) { LOG(INFO) << "Add min replied " << replier_min_channel.first; td_->contacts_manager_->add_min_channel(replier_min_channel.first, replier_min_channel.second); } for (auto recent_replier_dialog_id : m->reply_info.recent_replier_dialog_ids_) { dependencies.add_message_sender_dependencies(recent_replier_dialog_id); } if (m->reactions != nullptr) { m->reactions->add_min_channels(td_); m->reactions->add_dependencies(dependencies); } add_message_content_dependencies(dependencies, m->content.get(), is_bot); add_reply_markup_dependencies(dependencies, m->reply_markup.get()); add_draft_message_dependencies(dependencies, m->thread_draft_message); } void MessagesManager::get_dialog_send_message_as_dialog_ids( DialogId dialog_id, Promise> &&promise, bool is_recursive) { TRY_STATUS_PROMISE(promise, G()->close_status()); const Dialog *d = get_dialog_force(dialog_id, "get_group_call_join_as"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access chat")); } if (!d->default_send_message_as_dialog_id.is_valid()) { return promise.set_value(td_api::make_object()); } CHECK(dialog_id.get_type() == DialogType::Channel); if (created_public_broadcasts_inited_) { auto senders = td_api::make_object(); if (!created_public_broadcasts_.empty()) { auto add_sender = [&senders, td = td_](DialogId dialog_id, bool needs_premium) { auto sender = get_message_sender_object_const(td, dialog_id, "add_sender"); senders->senders_.push_back(td_api::make_object(std::move(sender), needs_premium)); }; if (is_anonymous_administrator(dialog_id, nullptr)) { add_sender(dialog_id, false); } else { add_sender(get_my_dialog_id(), false); } struct Sender { ChannelId channel_id; bool needs_premium; }; std::multimap sorted_senders; bool is_premium = td_->option_manager_->get_option_boolean("is_premium"); auto linked_channel_id = td_->contacts_manager_->get_channel_linked_channel_id( dialog_id.get_channel_id(), "get_dialog_send_message_as_dialog_ids"); for (auto channel_id : created_public_broadcasts_) { int64 score = td_->contacts_manager_->get_channel_participant_count(channel_id); bool needs_premium = !is_premium && channel_id != linked_channel_id && !td_->contacts_manager_->get_channel_is_verified(channel_id); if (needs_premium) { score -= static_cast(1) << 40; } if (channel_id == linked_channel_id) { score += static_cast(1) << 32; } sorted_senders.emplace(-score, Sender{channel_id, needs_premium}); }; for (auto &sender : sorted_senders) { add_sender(DialogId(sender.second.channel_id), sender.second.needs_premium); } } return promise.set_value(std::move(senders)); } CHECK(!is_recursive); auto new_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, &MessagesManager::get_dialog_send_message_as_dialog_ids, dialog_id, std::move(promise), true); } }); td_->contacts_manager_->get_created_public_dialogs(PublicDialogType::HasUsername, std::move(new_promise), true); } void MessagesManager::set_dialog_default_send_message_as_dialog_id(DialogId dialog_id, DialogId message_sender_dialog_id, Promise &&promise) { Dialog *d = get_dialog_force(dialog_id, "set_dialog_default_send_message_as_dialog_id"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!d->default_send_message_as_dialog_id.is_valid()) { return promise.set_error(Status::Error(400, "Can't change message sender in the chat")); } // checked in on_update_dialog_default_send_message_as_dialog_id CHECK(dialog_id.get_type() == DialogType::Channel && !is_broadcast_channel(dialog_id)); if (!have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } bool is_anonymous = is_anonymous_administrator(dialog_id, nullptr); switch (message_sender_dialog_id.get_type()) { case DialogType::User: if (message_sender_dialog_id != DialogId(td_->contacts_manager_->get_my_id())) { return promise.set_error(Status::Error(400, "Can't send messages as another user")); } if (is_anonymous) { return promise.set_error(Status::Error(400, "Can't send messages as self")); } break; case DialogType::Chat: case DialogType::Channel: case DialogType::SecretChat: if (is_anonymous && dialog_id == message_sender_dialog_id) { break; } if (!is_broadcast_channel(message_sender_dialog_id) || td_->contacts_manager_->get_channel_first_username(message_sender_dialog_id.get_channel_id()).empty()) { return promise.set_error(Status::Error(400, "Message sender chat must be a public channel")); } break; default: return promise.set_error(Status::Error(400, "Invalid message sender specified")); } if (!have_input_peer(message_sender_dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access specified message sender chat")); } { auto it = set_typing_query_.find(dialog_id); if (it != set_typing_query_.end()) { if (!it->second.empty()) { cancel_query(it->second); } set_typing_query_.erase(it); } } td_->create_handler(std::move(promise))->send(dialog_id, message_sender_dialog_id); on_update_dialog_default_send_message_as_dialog_id(dialog_id, message_sender_dialog_id, true); } class MessagesManager::SendMessageLogEvent { public: DialogId dialog_id; const Message *m_in; unique_ptr message_out; SendMessageLogEvent() : dialog_id(), m_in(nullptr) { } SendMessageLogEvent(DialogId dialog_id, const Message *m) : dialog_id(dialog_id), m_in(m) { } template void store(StorerT &storer) const { td::store(dialog_id, storer); td::store(*m_in, storer); } template void parse(ParserT &parser) { td::parse(dialog_id, parser); td::parse(message_out, parser); } }; Result> MessagesManager::send_message( DialogId dialog_id, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, tl_object_ptr &&options, tl_object_ptr &&reply_markup, tl_object_ptr &&input_message_content) { if (input_message_content == nullptr) { return Status::Error(400, "Can't send message without content"); } Dialog *d = get_dialog_force(dialog_id, "send_message"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } auto input_reply_to = get_message_input_reply_to(d, top_thread_message_id, std::move(reply_to), false); if (input_message_content->get_id() == td_api::inputMessageForwarded::ID) { auto input_message = td_api::move_object_as(input_message_content); TRY_RESULT(copy_options, process_message_copy_options(dialog_id, std::move(input_message->copy_options_))); copy_options.input_reply_to = std::move(input_reply_to); TRY_RESULT_ASSIGN(copy_options.reply_markup, get_dialog_reply_markup(dialog_id, std::move(reply_markup))); return forward_message(dialog_id, top_thread_message_id, DialogId(input_message->from_chat_id_), MessageId(input_message->message_id_), std::move(options), input_message->in_game_share_, std::move(copy_options)); } TRY_STATUS(can_send_message(dialog_id)); TRY_RESULT(message_reply_markup, get_dialog_reply_markup(dialog_id, std::move(reply_markup))); TRY_RESULT(message_content, process_input_message_content(dialog_id, std::move(input_message_content))); TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options), true)); TRY_STATUS(can_use_message_send_options(message_send_options, message_content)); TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, input_reply_to)); // there must be no errors after get_message_to_send call auto content = dup_message_content(td_, dialog_id, message_content.content.get(), MessageContentDupType::Send, MessageCopyOptions()); bool need_update_dialog_pos = false; unique_ptr message; Message *m; if (message_send_options.only_preview) { message = create_message_to_send(d, top_thread_message_id, std::move(input_reply_to), message_send_options, std::move(content), message_content.invert_media, false, nullptr, message_content.via_bot_user_id.is_valid(), DialogId()); m = message.get(); } else { m = get_message_to_send(d, top_thread_message_id, std::move(input_reply_to), message_send_options, std::move(content), message_content.invert_media, &need_update_dialog_pos, false, nullptr, message_content.via_bot_user_id.is_valid()); } m->reply_markup = std::move(message_reply_markup); m->via_bot_user_id = message_content.via_bot_user_id; m->disable_web_page_preview = message_content.disable_web_page_preview; m->clear_draft = message_content.clear_draft; if (message_content.ttl > 0) { m->ttl = message_content.ttl; m->is_content_secret = is_secret_message_content(m->ttl, m->content->get_type()); } m->send_emoji = std::move(message_content.emoji); if (message_send_options.only_preview) { return get_message_object(dialog_id, m, "send_message"); } if (m->clear_draft) { if (top_thread_message_id.is_valid()) { set_dialog_draft_message(dialog_id, top_thread_message_id, nullptr).ignore(); } else { update_dialog_draft_message(d, nullptr, false, !need_update_dialog_pos); } } save_send_message_log_event(dialog_id, m); do_send_message(dialog_id, m); if (!td_->auth_manager_->is_bot()) { send_update_new_message(d, m); if (need_update_dialog_pos) { send_update_chat_last_message(d, "send_message"); } } return get_message_object(dialog_id, m, "send_message"); } Result MessagesManager::process_input_message_content( DialogId dialog_id, tl_object_ptr &&input_message_content, bool check_permissions) { if (input_message_content != nullptr && input_message_content->get_id() == td_api::inputMessageForwarded::ID) { // for sendMessageAlbum/editMessageMedia/addLocalMessage auto input_message = td_api::move_object_as(input_message_content); TRY_RESULT(copy_options, process_message_copy_options(dialog_id, std::move(input_message->copy_options_))); if (!copy_options.send_copy) { return Status::Error(400, "Can't use forwarded message"); } DialogId from_dialog_id(input_message->from_chat_id_); Dialog *from_dialog = get_dialog_force(from_dialog_id, "send_message copy"); if (from_dialog == nullptr) { return Status::Error(400, "Chat to copy message from not found"); } if (!have_input_peer(from_dialog_id, AccessRights::Read)) { return Status::Error(400, "Can't access the chat to copy message from"); } if (from_dialog_id.get_type() == DialogType::SecretChat) { return Status::Error(400, "Can't copy message from secret chats"); } MessageId message_id = get_persistent_message_id(from_dialog, MessageId(input_message->message_id_)); const Message *copied_message = get_message_force(from_dialog, message_id, "process_input_message_content"); if (copied_message == nullptr) { return Status::Error(400, "Can't find message to copy"); } if (!can_forward_message(from_dialog_id, copied_message)) { return Status::Error(400, "Can't copy message"); } if (!can_save_message(from_dialog_id, copied_message) && !td_->auth_manager_->is_bot()) { return Status::Error(400, "Message copying is restricted"); } unique_ptr content = dup_message_content(td_, dialog_id, copied_message->content.get(), MessageContentDupType::Copy, std::move(copy_options)); if (content == nullptr) { return Status::Error(400, "Can't copy message content"); } return InputMessageContent(std::move(content), get_message_disable_web_page_preview(copied_message), copied_message->invert_media, false, 0, UserId(), copied_message->send_emoji); } bool is_premium = td_->option_manager_->get_option_boolean("is_premium"); TRY_RESULT(content, get_input_message_content(dialog_id, std::move(input_message_content), td_, is_premium)); if (dialog_id != DialogId()) { TRY_STATUS(can_send_message_content(dialog_id, content.content.get(), false, check_permissions, td_)); } return std::move(content); } Result MessagesManager::process_message_copy_options( DialogId dialog_id, tl_object_ptr &&options) const { if (options == nullptr || !options->send_copy_) { return MessageCopyOptions(); } MessageCopyOptions result; result.send_copy = true; result.replace_caption = options->replace_caption_; if (result.replace_caption) { TRY_RESULT_ASSIGN(result.new_caption, get_formatted_text(td_, dialog_id, std::move(options->new_caption_), td_->auth_manager_->is_bot(), true, false, false)); } return std::move(result); } Result MessagesManager::process_message_send_options( DialogId dialog_id, tl_object_ptr &&options, bool allow_update_stickersets_order) const { MessageSendOptions result; if (options != nullptr) { result.disable_notification = options->disable_notification_; result.from_background = options->from_background_; if (allow_update_stickersets_order) { result.update_stickersets_order = options->update_order_of_installed_sticker_sets_; } result.protect_content = options->protect_content_; result.only_preview = options->only_preview_; TRY_RESULT_ASSIGN(result.schedule_date, get_message_schedule_date(std::move(options->scheduling_state_))); result.sending_id = options->sending_id_; } auto dialog_type = dialog_id.get_type(); if (result.schedule_date != 0) { if (dialog_type == DialogType::SecretChat) { return Status::Error(400, "Can't schedule messages in secret chats"); } if (td_->auth_manager_->is_bot()) { return Status::Error(400, "Bots can't send scheduled messages"); } } if (result.schedule_date == SCHEDULE_WHEN_ONLINE_DATE) { if (dialog_type != DialogType::User) { return Status::Error(400, "Messages can be scheduled till online only in private chats"); } if (dialog_id == get_my_dialog_id()) { return Status::Error(400, "Can't scheduled till online messages in chat with self"); } } if (result.protect_content && !td_->auth_manager_->is_bot()) { result.protect_content = false; } return result; } Status MessagesManager::can_use_message_send_options(const MessageSendOptions &options, const unique_ptr &content, int32 ttl) { if (options.schedule_date != 0) { if (ttl > 0) { return Status::Error(400, "Can't send scheduled self-destructing messages"); } if (content->get_type() == MessageContentType::LiveLocation) { return Status::Error(400, "Can't send scheduled live location messages"); } } return Status::OK(); } Status MessagesManager::can_use_message_send_options(const MessageSendOptions &options, const InputMessageContent &content) { return can_use_message_send_options(options, content.content, content.ttl); } Status MessagesManager::can_use_top_thread_message_id(Dialog *d, MessageId top_thread_message_id, const MessageInputReplyTo &input_reply_to) { if (top_thread_message_id == MessageId()) { return Status::OK(); } if (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server()) { return Status::Error(400, "Invalid message thread identifier specified"); } if (d->dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(d->dialog_id)) { return Status::Error(400, "Chat doesn't have threads"); } if (input_reply_to.get_story_full_id().is_valid()) { return Status::Error(400, "Can't send story replies to the thread"); } auto same_chat_reply_to_message_id = input_reply_to.get_same_chat_reply_to_message_id(); if (same_chat_reply_to_message_id.is_valid()) { const Message *reply_m = get_message_force(d, same_chat_reply_to_message_id, "can_use_top_thread_message_id 1"); if (reply_m != nullptr && top_thread_message_id != reply_m->top_thread_message_id) { if (reply_m->top_thread_message_id.is_valid() || reply_m->media_album_id == 0) { return Status::Error(400, "The message to be replied is not in the specified message thread"); } // if the message is in an album and not in the thread, it can be in the album of top_thread_message_id const Message *top_m = get_message_force(d, top_thread_message_id, "can_use_top_thread_message_id 2"); if (top_m != nullptr && (top_m->media_album_id != reply_m->media_album_id || top_m->top_thread_message_id != top_m->message_id)) { return Status::Error(400, "The message to be replied is not in the specified message thread root album"); } } } return Status::OK(); } int64 MessagesManager::generate_new_media_album_id() { int64 media_album_id = 0; do { media_album_id = Random::secure_int64(); } while (media_album_id >= 0 || pending_message_group_sends_.count(media_album_id) != 0); return media_album_id; } Result> MessagesManager::send_message_group( DialogId dialog_id, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, tl_object_ptr &&options, vector> &&input_message_contents) { if (input_message_contents.size() > MAX_GROUPED_MESSAGES) { return Status::Error(400, "Too many messages to send as an album"); } if (input_message_contents.empty()) { return Status::Error(400, "There are no messages to send"); } Dialog *d = get_dialog_force(dialog_id, "send_message_group"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } TRY_STATUS(can_send_message(dialog_id)); TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options), true)); vector message_contents; std::unordered_set message_content_types; for (auto &input_message_content : input_message_contents) { TRY_RESULT(message_content, process_input_message_content(dialog_id, std::move(input_message_content))); TRY_STATUS(can_use_message_send_options(message_send_options, message_content)); auto message_content_type = message_content.content->get_type(); if (!is_allowed_media_group_content(message_content_type)) { return Status::Error(400, "Invalid message content type"); } message_content_types.insert(message_content_type); message_contents.push_back(std::move(message_content)); } if (message_content_types.size() > 1) { for (auto message_content_type : message_content_types) { if (is_homogenous_media_group_content(message_content_type)) { return Status::Error(400, PSLICE() << message_content_type << " can't be mixed with other media types"); } } } auto input_reply_to = get_message_input_reply_to(d, top_thread_message_id, std::move(reply_to), false); TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, input_reply_to)); int64 media_album_id = 0; if (message_contents.size() > 1) { media_album_id = generate_new_media_album_id(); } // there must be no errors after get_message_to_send calls vector> result; bool need_update_dialog_pos = false; for (size_t i = 0; i < message_contents.size(); i++) { auto &message_content = message_contents[i]; unique_ptr message; Message *m; if (message_send_options.only_preview) { message = create_message_to_send(d, top_thread_message_id, std::move(input_reply_to), message_send_options, std::move(message_content.content), message_content.invert_media, i != 0, nullptr, false, DialogId()); m = message.get(); } else { m = get_message_to_send(d, top_thread_message_id, std::move(input_reply_to), message_send_options, dup_message_content(td_, dialog_id, message_content.content.get(), MessageContentDupType::Send, MessageCopyOptions()), message_content.invert_media, &need_update_dialog_pos, i != 0); } auto ttl = message_content.ttl; if (ttl > 0) { m->ttl = ttl; m->is_content_secret = is_secret_message_content(m->ttl, m->content->get_type()); } m->media_album_id = media_album_id; result.push_back(get_message_object(dialog_id, m, "send_message_group")); if (!message_send_options.only_preview) { save_send_message_log_event(dialog_id, m); do_send_message(dialog_id, m); if (!td_->auth_manager_->is_bot()) { send_update_new_message(d, m); } } } if (need_update_dialog_pos) { CHECK(!message_send_options.only_preview); send_update_chat_last_message(d, "send_message_group"); } return get_messages_object(-1, std::move(result), false); } void MessagesManager::save_send_message_log_event(DialogId dialog_id, const Message *m) { if (!G()->use_message_database()) { return; } CHECK(m != nullptr); LOG(INFO) << "Save " << MessageFullId(dialog_id, m->message_id) << " to binlog"; auto log_event = SendMessageLogEvent(dialog_id, m); CHECK(m->send_message_log_event_id == 0); m->send_message_log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SendMessage, get_log_event_storer(log_event)); } void MessagesManager::do_send_message(DialogId dialog_id, const Message *m, vector bad_parts) { bool is_edit = m->message_id.is_any_server(); LOG(INFO) << "Do " << (is_edit ? "edit" : "send") << ' ' << MessageFullId(dialog_id, m->message_id); bool is_secret = dialog_id.get_type() == DialogType::SecretChat; if (m->media_album_id != 0 && bad_parts.empty() && !is_secret && !is_edit) { auto &request = pending_message_group_sends_[m->media_album_id]; request.dialog_id = dialog_id; request.message_ids.push_back(m->message_id); request.is_finished.push_back(false); request.results.push_back(Status::OK()); } auto content = is_edit ? m->edited_content.get() : m->content.get(); CHECK(content != nullptr); auto content_type = content->get_type(); if (content_type == MessageContentType::Text) { CHECK(!is_edit); send_closure_later(actor_id(this), &MessagesManager::on_text_message_ready_to_send, dialog_id, m->message_id); return; } FileId file_id = get_message_content_any_file_id(content); // any_file_id, because it could be a photo sent by ID FileView file_view = td_->file_manager_->get_file_view(file_id); FileId thumbnail_file_id = get_message_content_thumbnail_file_id(content, td_); LOG(DEBUG) << "Need to send file " << file_id << " with thumbnail " << thumbnail_file_id; if (is_secret) { CHECK(!is_edit); auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()); auto secret_input_media = get_secret_input_media(content, td_, nullptr, BufferSlice(), layer); if (secret_input_media.empty()) { LOG(INFO) << "Ask to upload encrypted file " << file_id; CHECK(file_view.is_encrypted_secret()); CHECK(file_id.is_valid()); bool is_inserted = being_uploaded_files_ .emplace(file_id, std::make_pair(MessageFullId(dialog_id, m->message_id), thumbnail_file_id)) .second; CHECK(is_inserted); // need to call resume_upload synchronously to make upload process consistent with being_uploaded_files_ td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_media_callback_, 1, m->message_id.get()); } else { on_secret_message_media_uploaded(dialog_id, m, std::move(secret_input_media), file_id, thumbnail_file_id); } } else { auto input_media = get_input_media(content, td_, m->ttl, m->send_emoji, td_->auth_manager_->is_bot() && bad_parts.empty()); if (input_media == nullptr) { if (content_type == MessageContentType::Game || content_type == MessageContentType::Poll || content_type == MessageContentType::Story) { return; } if (get_main_file_type(file_view.get_type()) == FileType::Photo) { thumbnail_file_id = FileId(); } LOG(INFO) << "Ask to upload file " << file_id << " with bad parts " << bad_parts; CHECK(file_id.is_valid()); bool is_inserted = being_uploaded_files_ .emplace(file_id, std::make_pair(MessageFullId(dialog_id, m->message_id), thumbnail_file_id)) .second; CHECK(is_inserted); // need to call resume_upload synchronously to make upload process consistent with being_uploaded_files_ // and to send is_uploading_active == true in the updates td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_media_callback_, 1, m->message_id.get()); } else { on_message_media_uploaded(dialog_id, m, std::move(input_media), file_id, thumbnail_file_id); } } } void MessagesManager::on_message_media_uploaded(DialogId dialog_id, const Message *m, tl_object_ptr &&input_media, FileId file_id, FileId thumbnail_file_id) { if (G()->close_flag()) { return; } CHECK(m != nullptr); CHECK(input_media != nullptr); auto message_id = m->message_id; if (message_id.is_any_server()) { const FormattedText *caption = get_message_content_caption(m->edited_content.get()); auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), m->edited_reply_markup); bool was_uploaded = FileManager::extract_was_uploaded(input_media); bool was_thumbnail_uploaded = FileManager::extract_was_thumbnail_uploaded(input_media); LOG(INFO) << "Edit media from " << message_id << " in " << dialog_id; auto schedule_date = get_message_schedule_date(m); auto promise = PromiseCreator::lambda( [actor_id = actor_id(this), dialog_id, message_id, file_id, thumbnail_file_id, schedule_date, generation = m->edit_generation, was_uploaded, was_thumbnail_uploaded, file_reference = FileManager::extract_file_reference(input_media)](Result result) mutable { send_closure(actor_id, &MessagesManager::on_message_media_edited, dialog_id, message_id, file_id, thumbnail_file_id, was_uploaded, was_thumbnail_uploaded, std::move(file_reference), schedule_date, generation, std::move(result)); }); td_->create_handler(std::move(promise)) ->send(1 << 11, dialog_id, message_id, caption == nullptr ? "" : caption->text, get_input_message_entities(td_->contacts_manager_.get(), caption, "edit_message_media"), std::move(input_media), m->edited_invert_media, std::move(input_reply_markup), schedule_date); return; } if (m->media_album_id == 0) { send_closure_later( actor_id(this), &MessagesManager::on_media_message_ready_to_send, dialog_id, message_id, PromiseCreator::lambda([this, dialog_id, input_media = std::move(input_media), file_id, thumbnail_file_id](Result result) mutable { if (result.is_error() || G()->close_flag()) { return; } auto m = result.move_as_ok(); CHECK(m != nullptr); CHECK(input_media != nullptr); const FormattedText *caption = get_message_content_caption(m->content.get()); LOG(INFO) << "Send media from " << m->message_id << " in " << dialog_id; int64 random_id = begin_send_message(dialog_id, m); td_->create_handler()->send( file_id, thumbnail_file_id, get_message_flags(m), dialog_id, get_send_message_as_input_peer(m), *get_message_input_reply_to(m), m->top_thread_message_id, get_message_schedule_date(m), get_input_reply_markup(td_->contacts_manager_.get(), m->reply_markup), get_input_message_entities(td_->contacts_manager_.get(), caption, "on_message_media_uploaded"), caption == nullptr ? "" : caption->text, std::move(input_media), m->content->get_type(), m->is_copy, random_id, &m->send_query_ref); })); } else { switch (input_media->get_id()) { case telegram_api::inputMediaUploadedDocument::ID: static_cast(input_media.get())->flags_ |= telegram_api::inputMediaUploadedDocument::NOSOUND_VIDEO_MASK; // fallthrough case telegram_api::inputMediaUploadedPhoto::ID: case telegram_api::inputMediaDocumentExternal::ID: case telegram_api::inputMediaPhotoExternal::ID: LOG(INFO) << "Upload media from " << message_id << " in " << dialog_id; td_->create_handler()->send(dialog_id, message_id, file_id, thumbnail_file_id, std::move(input_media)); break; case telegram_api::inputMediaDocument::ID: case telegram_api::inputMediaPhoto::ID: send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id, dialog_id, message_id, Status::OK()); break; default: LOG(ERROR) << "Have wrong input media " << to_string(input_media); send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id, dialog_id, message_id, Status::Error(400, "Invalid input media")); } } } void MessagesManager::on_secret_message_media_uploaded(DialogId dialog_id, const Message *m, SecretInputMedia &&secret_input_media, FileId file_id, FileId thumbnail_file_id) { if (G()->close_flag()) { return; } CHECK(m != nullptr); CHECK(m->message_id.is_valid()); if (secret_input_media.empty()) { // the media can't be sent to the chat LOG(INFO) << "Can't send a media message to " << dialog_id; fail_send_message({dialog_id, m->message_id}, Status::Error(400, "The file can't be sent to the secret chat")); return; } /* if (m->media_album_id != 0) { switch (secret_input_media->input_file_->get_id()) { case telegram_api::inputEncryptedFileUploaded::ID: case telegram_api::inputEncryptedFileBigUploaded::ID: LOG(INFO) << "Upload media from " << m->message_id << " in " << dialog_id; return td_->create_handler()->send(dialog_id, m->message_id, std::move(secret_input_media)); case telegram_api::inputEncryptedFile::ID: return send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id, dialog_id, m->message_id, Status::OK()); default: LOG(ERROR) << "Have wrong secret input media " << to_string(secret_input_media->input_file_); return send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id, dialog_id, m->message_id, Status::Error(400, "Invalid input media")); } */ // TODO use file_id, thumbnail_file_id, was_uploaded, was_thumbnail_uploaded, // invalidate partial remote location for file_id in case of failed upload even message has already been deleted send_closure_later(actor_id(this), &MessagesManager::on_media_message_ready_to_send, dialog_id, m->message_id, PromiseCreator::lambda([this, dialog_id, secret_input_media = std::move(secret_input_media)]( Result result) mutable { if (result.is_error() || G()->close_flag()) { return; } auto m = result.move_as_ok(); CHECK(m != nullptr); CHECK(!secret_input_media.empty()); send_secret_message(dialog_id, m, std::move(secret_input_media)); })); } void MessagesManager::send_secret_message(DialogId dialog_id, const Message *m, SecretInputMedia media) { CHECK(dialog_id.get_type() == DialogType::SecretChat); int64 random_id = begin_send_message(dialog_id, m); auto text = get_message_content_text(m->content.get()); vector> entities; if (text != nullptr && !text->entities.empty()) { auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()); entities = get_input_secret_message_entities(text->entities, layer); } int32 flags = 0; if (m->reply_to_random_id != 0) { flags |= secret_api::decryptedMessage::REPLY_TO_RANDOM_ID_MASK; } if (m->via_bot_user_id.is_valid()) { flags |= secret_api::decryptedMessage::VIA_BOT_NAME_MASK; } if (!media.empty()) { flags |= secret_api::decryptedMessage::MEDIA_MASK; } if (!entities.empty()) { flags |= secret_api::decryptedMessage::ENTITIES_MASK; } if (m->media_album_id != 0) { CHECK(m->media_album_id < 0); flags |= secret_api::decryptedMessage::GROUPED_ID_MASK; } if (m->disable_notification) { flags |= secret_api::decryptedMessage::SILENT_MASK; } send_closure( td_->secret_chats_manager_, &SecretChatsManager::send_message, dialog_id.get_secret_chat_id(), make_tl_object( flags, false /*ignored*/, random_id, m->ttl, m->content->get_type() == MessageContentType::Text ? text->text : string(), std::move(media.decrypted_media_), std::move(entities), td_->contacts_manager_->get_user_first_username(m->via_bot_user_id), m->reply_to_random_id, -m->media_album_id), std::move(media.input_file_), Promise()); } void MessagesManager::on_upload_message_media_success(DialogId dialog_id, MessageId message_id, tl_object_ptr &&media) { Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); CHECK(message_id.is_valid() || message_id.is_valid_scheduled()); CHECK(message_id.is_yet_unsent()); Message *m = get_message(d, message_id); if (m == nullptr) { // message has already been deleted by the user or sent to inaccessible channel // don't need to send error to the user, because the message has already been deleted // and there is nothing to be deleted from the server LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat " << MessageFullId{dialog_id, message_id}; return; } if (!have_input_peer(dialog_id, AccessRights::Read)) { return; // the message should be deleted soon } auto caption = get_message_content_caption(m->content.get()); auto has_spoiler = get_message_content_has_spoiler(m->content.get()); auto content = get_message_content(td_, caption == nullptr ? FormattedText() : *caption, std::move(media), dialog_id, m->date, false, UserId(), nullptr, nullptr, "on_upload_message_media_success"); set_message_content_has_spoiler(content.get(), has_spoiler); bool is_content_changed = false; bool need_update = update_message_content(dialog_id, m, std::move(content), true, true, is_content_changed); if (need_update) { send_update_message_content(d, m, true, "on_upload_message_media_success"); } if (is_content_changed || need_update) { on_message_changed(d, m, need_update, "on_upload_message_media_success"); } auto input_media = get_input_media(m->content.get(), td_, m->ttl, m->send_emoji, true); Status result; if (input_media == nullptr) { result = Status::Error(400, "Failed to upload file"); } send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id, dialog_id, m->message_id, std::move(result)); } void MessagesManager::on_upload_message_media_file_parts_missing(DialogId dialog_id, MessageId message_id, vector &&bad_parts) { Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); Message *m = get_message(d, message_id); if (m == nullptr) { // message has already been deleted by the user or sent to inaccessible channel // don't need to send error to the user, because the message has already been deleted // and there is nothing to be deleted from the server LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat " << MessageFullId{dialog_id, message_id}; return; } if (!have_input_peer(dialog_id, AccessRights::Read)) { return; // the message should be deleted soon } CHECK(dialog_id.get_type() != DialogType::SecretChat); do_send_message(dialog_id, m, std::move(bad_parts)); } void MessagesManager::on_upload_message_media_fail(DialogId dialog_id, MessageId message_id, Status error) { Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); Message *m = get_message(d, message_id); if (m == nullptr) { // message has already been deleted by the user or sent to inaccessible channel // don't need to send error to the user, because the message has already been deleted // and there is nothing to be deleted from the server LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat " << MessageFullId{dialog_id, message_id}; return; } if (!have_input_peer(dialog_id, AccessRights::Read)) { return; // the message should be deleted soon } CHECK(dialog_id.get_type() != DialogType::SecretChat); send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id, dialog_id, m->message_id, std::move(error)); } void MessagesManager::on_upload_message_media_finished(int64 media_album_id, DialogId dialog_id, MessageId message_id, Status result) { CHECK(media_album_id < 0); auto it = pending_message_group_sends_.find(media_album_id); if (it == pending_message_group_sends_.end()) { // the group may be already sent or failed to be sent return; } auto &request = it->second; CHECK(request.dialog_id == dialog_id); auto message_it = std::find(request.message_ids.begin(), request.message_ids.end(), message_id); if (message_it == request.message_ids.end()) { // the message may be already deleted and the album is recreated without it CHECK(message_id.is_yet_unsent()); LOG_CHECK(get_message({dialog_id, message_id}) == nullptr) << dialog_id << ' ' << request.message_ids << ' ' << message_id << ' ' << request.finished_count << ' ' << request.is_finished << ' ' << request.results; return; } auto pos = static_cast(message_it - request.message_ids.begin()); if (request.is_finished[pos]) { LOG(INFO) << "Upload media of " << message_id << " in " << dialog_id << " from group " << media_album_id << " at pos " << pos << " was already finished"; return; } LOG(INFO) << "Finish to upload media of " << message_id << " in " << dialog_id << " from group " << media_album_id << " at pos " << pos << " with result " << result << " and previous finished_count = " << request.finished_count; request.results[pos] = std::move(result); request.is_finished[pos] = true; request.finished_count++; if (request.finished_count == request.message_ids.size() || request.results[pos].is_error()) { // must use send_closure_later if some messages may be being deleted now // but this function is called only through send_closure_later, so there should be no being deleted messages // we must use synchronous calls to keep the correct message order during copying of multiple messages // but "request" iterator can be invalidated by do_send_message_group, so it must not be used below auto message_ids = request.message_ids; for (auto request_message_id : message_ids) { LOG(INFO) << "Send on_media_message_ready_to_send for " << request_message_id << " in " << dialog_id; auto promise = PromiseCreator::lambda([this, media_album_id](Result result) { if (result.is_error() || G()->close_flag()) { return; } auto m = result.move_as_ok(); CHECK(m != nullptr); CHECK(m->media_album_id == media_album_id); do_send_message_group(media_album_id); // send_closure_later(actor_id, &MessagesManager::do_send_message_group, media_album_id); }); // send_closure_later(actor_id(this), &MessagesManager::on_media_message_ready_to_send, dialog_id, // request_message_id, std::move(promise)); on_media_message_ready_to_send(dialog_id, request_message_id, std::move(promise)); } } } void MessagesManager::do_send_message_group(int64 media_album_id) { if (G()->close_flag()) { return; } CHECK(media_album_id < 0); auto it = pending_message_group_sends_.find(media_album_id); if (it == pending_message_group_sends_.end()) { // the group may be already sent or failed to be sent return; } auto &request = it->second; auto dialog_id = request.dialog_id; Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); auto default_status = can_send_message(dialog_id); bool success = default_status.is_ok(); vector file_ids; vector random_ids; vector> input_single_media; tl_object_ptr as_input_peer; const MessageInputReplyTo *input_reply_to = nullptr; MessageId top_thread_message_id; int32 flags = 0; int32 schedule_date = 0; bool is_copy = false; for (size_t i = 0; i < request.message_ids.size(); i++) { auto *m = get_message(d, request.message_ids[i]); if (m == nullptr) { // skip deleted messages random_ids.push_back(0); continue; } input_reply_to = get_message_input_reply_to(m); top_thread_message_id = m->top_thread_message_id; flags = get_message_flags(m); schedule_date = get_message_schedule_date(m); is_copy = m->is_copy; as_input_peer = get_send_message_as_input_peer(m); file_ids.push_back(get_message_content_any_file_id(m->content.get())); random_ids.push_back(begin_send_message(dialog_id, m)); LOG(INFO) << "Have file " << file_ids.back() << " in " << m->message_id << " with result " << request.results[i] << " and is_finished = " << static_cast(request.is_finished[i]); if (request.results[i].is_error() || !request.is_finished[i]) { success = false; continue; } const FormattedText *caption = get_message_content_caption(m->content.get()); auto input_media = get_input_media(m->content.get(), td_, m->ttl, m->send_emoji, true); if (input_media == nullptr) { // TODO return CHECK auto file_id = get_message_content_any_file_id(m->content.get()); auto file_view = td_->file_manager_->get_file_view(file_id); bool has_remote = file_view.has_remote_location(); bool is_web = has_remote ? file_view.remote_location().is_web() : false; LOG(FATAL) << request.dialog_id << " " << request.finished_count << " " << i << " " << request.message_ids << " " << request.is_finished << " " << request.results << " " << m->ttl << " " << has_remote << " " << file_view.has_alive_remote_location() << " " << file_view.has_active_upload_remote_location() << " " << file_view.has_active_download_remote_location() << " " << file_view.is_encrypted() << " " << is_web << " " << file_view.has_url() << " " << to_string(get_message_message_content_object(dialog_id, m)); } auto entities = get_input_message_entities(td_->contacts_manager_.get(), caption, "do_send_message_group"); int32 input_single_media_flags = 0; if (!entities.empty()) { input_single_media_flags |= telegram_api::inputSingleMedia::ENTITIES_MASK; } input_single_media.push_back(make_tl_object( input_single_media_flags, std::move(input_media), random_ids.back(), caption == nullptr ? "" : caption->text, std::move(entities))); } if (!success) { if (default_status.is_ok()) { default_status = Status::Error(400, "Group send failed"); } for (size_t i = 0; i < random_ids.size(); i++) { if (random_ids[i] != 0) { on_send_message_fail(random_ids[i], request.results[i].is_error() ? std::move(request.results[i]) : default_status.clone()); } } pending_message_group_sends_.erase(it); return; } LOG_CHECK(request.finished_count == request.message_ids.size()) << request.finished_count << " " << request.message_ids.size(); pending_message_group_sends_.erase(it); LOG(INFO) << "Begin to send media group " << media_album_id << " to " << dialog_id; if (input_single_media.empty()) { LOG(INFO) << "Media group " << media_album_id << " from " << dialog_id << " is empty"; } CHECK(input_reply_to != nullptr); td_->create_handler()->send(flags, dialog_id, std::move(as_input_peer), *input_reply_to, top_thread_message_id, schedule_date, std::move(file_ids), std::move(input_single_media), is_copy); } void MessagesManager::on_text_message_ready_to_send(DialogId dialog_id, MessageId message_id) { if (G()->close_flag()) { return; } LOG(INFO) << "Ready to send " << message_id << " to " << dialog_id; auto m = get_message({dialog_id, message_id}); if (m == nullptr) { return; } CHECK(message_id.is_yet_unsent()); auto content = m->content.get(); CHECK(content != nullptr); if (dialog_id.get_type() == DialogType::SecretChat) { CHECK(!message_id.is_scheduled()); auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()); send_secret_message(dialog_id, m, get_secret_input_media(content, td_, nullptr, BufferSlice(), layer)); } else { const FormattedText *message_text = get_message_content_text(content); CHECK(message_text != nullptr); int64 random_id = begin_send_message(dialog_id, m); auto input_media = get_message_content_input_media_web_page(td_, content); if (input_media == nullptr) { td_->create_handler()->send( get_message_flags(m), dialog_id, get_send_message_as_input_peer(m), *get_message_input_reply_to(m), m->top_thread_message_id, get_message_schedule_date(m), get_input_reply_markup(td_->contacts_manager_.get(), m->reply_markup), get_input_message_entities(td_->contacts_manager_.get(), message_text, "do_send_message"), message_text->text, m->is_copy, random_id, &m->send_query_ref); } else { td_->create_handler()->send( FileId(), FileId(), get_message_flags(m), dialog_id, get_send_message_as_input_peer(m), *get_message_input_reply_to(m), m->top_thread_message_id, get_message_schedule_date(m), get_input_reply_markup(td_->contacts_manager_.get(), m->reply_markup), get_input_message_entities(td_->contacts_manager_.get(), message_text, "do_send_message"), message_text->text, std::move(input_media), MessageContentType::Text, m->is_copy, random_id, &m->send_query_ref); } } } void MessagesManager::on_media_message_ready_to_send(DialogId dialog_id, MessageId message_id, Promise &&promise) { LOG(INFO) << "Ready to send " << message_id << " to " << dialog_id; CHECK(promise); if (!G()->keep_media_order() || message_id.is_scheduled()) { auto m = get_message({dialog_id, message_id}); if (m != nullptr) { promise.set_value(std::move(m)); } return; } auto queue_id = ChainId(dialog_id, MessageContentType::Photo).get(); CHECK(queue_id & 1); auto &queue = yet_unsent_media_queues_[queue_id]; queue.dialog_id_ = dialog_id; auto it = queue.queue_.find(message_id); if (it == queue.queue_.end()) { if (queue.queue_.empty()) { yet_unsent_media_queues_.erase(queue_id); } LOG(INFO) << "Can't find " << message_id << " in the queue of " << dialog_id; auto m = get_message({dialog_id, message_id}); if (m != nullptr) { promise.set_value(std::move(m)); } return; } if (it->second) { promise.set_error(Status::Error(500, "Duplicate promise")); return; } it->second = std::move(promise); on_yet_unsent_media_queue_updated(dialog_id); } void MessagesManager::on_yet_unsent_media_queue_updated(DialogId dialog_id) { auto queue_id = ChainId(dialog_id, MessageContentType::Photo).get(); CHECK(queue_id & 1); while (true) { auto it = yet_unsent_media_queues_.find(queue_id); if (it == yet_unsent_media_queues_.end()) { return; } auto &queue = it->second.queue_; if (queue.empty()) { yet_unsent_media_queues_.erase(it); return; } auto first_it = queue.begin(); if (!first_it->second) { return; } auto m = get_message({dialog_id, first_it->first}); auto promise = std::move(first_it->second); queue.erase(first_it); LOG(INFO) << "Queue for " << dialog_id << " now has size " << queue.size(); // don't use it/queue/first_it after promise is called if (m != nullptr) { LOG(INFO) << "Can send " << MessageFullId{dialog_id, m->message_id}; promise.set_value(std::move(m)); } else { promise.set_error(Status::Error(400, "Message not found")); } } } Result MessagesManager::send_bot_start_message(UserId bot_user_id, DialogId dialog_id, const string ¶meter) { CHECK(!td_->auth_manager_->is_bot()); TRY_RESULT(bot_data, td_->contacts_manager_->get_bot_data(bot_user_id)); Dialog *d = get_dialog_force(dialog_id, "send_bot_start_message"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } bool is_chat_with_bot = false; switch (dialog_id.get_type()) { case DialogType::User: if (dialog_id.get_user_id() != bot_user_id) { return Status::Error(400, "Can't send start message to a private chat other than chat with the bot"); } is_chat_with_bot = true; break; case DialogType::Chat: { if (!bot_data.can_join_groups) { return Status::Error(400, "Bot can't join groups"); } auto chat_id = dialog_id.get_chat_id(); if (!td_->contacts_manager_->have_input_peer_chat(chat_id, AccessRights::Write)) { return Status::Error(400, "Can't access the chat"); } auto status = td_->contacts_manager_->get_chat_permissions(chat_id); if (!status.can_invite_users()) { return Status::Error(400, "Need administrator rights to invite a bot to the group chat"); } break; } case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); if (!td_->contacts_manager_->have_input_peer_channel(channel_id, AccessRights::Write)) { return Status::Error(400, "Can't access the chat"); } switch (td_->contacts_manager_->get_channel_type(channel_id)) { case ChannelType::Megagroup: if (!bot_data.can_join_groups) { return Status::Error(400, "The bot can't join groups"); } break; case ChannelType::Broadcast: return Status::Error(400, "Bots can't be invited to channel chats. Add them as administrators instead"); case ChannelType::Unknown: default: UNREACHABLE(); } auto status = td_->contacts_manager_->get_channel_permissions(channel_id); if (!status.can_invite_users()) { return Status::Error(400, "Need administrator rights to invite a bot to the supergroup chat"); } break; } case DialogType::SecretChat: return Status::Error(400, "Can't send bot start message to a secret chat"); case DialogType::None: default: UNREACHABLE(); } string text = "/start"; if (!is_chat_with_bot) { text += '@'; text += bot_data.username; } vector text_entities; text_entities.emplace_back(MessageEntity::Type::BotCommand, 0, narrow_cast(text.size())); bool need_update_dialog_pos = false; Message *m = get_message_to_send( d, MessageId(), MessageInputReplyTo(), MessageSendOptions(), create_text_message_content(text, std::move(text_entities), WebPageId(), false, false, false, string()), false, &need_update_dialog_pos); m->is_bot_start_message = true; send_update_new_message(d, m); if (need_update_dialog_pos) { send_update_chat_last_message(d, "send_bot_start_message"); } if (parameter.empty() && is_chat_with_bot) { save_send_message_log_event(dialog_id, m); do_send_message(dialog_id, m); } else { save_send_bot_start_message_log_event(bot_user_id, dialog_id, parameter, m); send_closure_later(actor_id(this), &MessagesManager::do_send_bot_start_message, bot_user_id, dialog_id, m->message_id, parameter); } return m->message_id; } class MessagesManager::SendBotStartMessageLogEvent { public: UserId bot_user_id; DialogId dialog_id; string parameter; const Message *m_in = nullptr; unique_ptr message_out; template void store(StorerT &storer) const { td::store(bot_user_id, storer); td::store(dialog_id, storer); td::store(parameter, storer); td::store(*m_in, storer); } template void parse(ParserT &parser) { td::parse(bot_user_id, parser); td::parse(dialog_id, parser); td::parse(parameter, parser); td::parse(message_out, parser); } }; void MessagesManager::save_send_bot_start_message_log_event(UserId bot_user_id, DialogId dialog_id, const string ¶meter, const Message *m) { if (!G()->use_message_database()) { return; } CHECK(m != nullptr); LOG(INFO) << "Save " << MessageFullId(dialog_id, m->message_id) << " to binlog"; SendBotStartMessageLogEvent log_event; log_event.bot_user_id = bot_user_id; log_event.dialog_id = dialog_id; log_event.parameter = parameter; log_event.m_in = m; CHECK(m->send_message_log_event_id == 0); m->send_message_log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SendBotStartMessage, get_log_event_storer(log_event)); } void MessagesManager::do_send_bot_start_message(UserId bot_user_id, DialogId dialog_id, MessageId message_id, const string ¶meter) { if (G()->close_flag()) { return; } LOG(INFO) << "Do send bot start " << MessageFullId(dialog_id, message_id) << " to bot " << bot_user_id; auto m = get_message({dialog_id, message_id}); if (m == nullptr) { return; } int64 random_id = begin_send_message(dialog_id, m); telegram_api::object_ptr input_peer = dialog_id.get_type() == DialogType::User ? make_tl_object() : get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { return on_send_message_fail(random_id, Status::Error(400, "Chat is not accessible")); } auto r_bot_input_user = td_->contacts_manager_->get_input_user(bot_user_id); if (r_bot_input_user.is_error()) { return on_send_message_fail(random_id, r_bot_input_user.move_as_error()); } m->send_query_ref = td_->create_handler()->send(r_bot_input_user.move_as_ok(), dialog_id, std::move(input_peer), parameter, random_id); } Result> MessagesManager::send_inline_query_result_message( DialogId dialog_id, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, tl_object_ptr &&options, int64 query_id, const string &result_id, bool hide_via_bot) { Dialog *d = get_dialog_force(dialog_id, "send_inline_query_result_message"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } TRY_STATUS(can_send_message(dialog_id)); TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options), false)); bool to_secret = false; switch (dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: // ok break; case DialogType::Channel: { auto channel_status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()); if (!channel_status.can_use_inline_bots()) { return Status::Error(400, "Can't use inline bots in the chat"); } break; } case DialogType::SecretChat: to_secret = true; // ok break; case DialogType::None: default: UNREACHABLE(); } const InlineMessageContent *content = td_->inline_queries_manager_->get_inline_message_content(query_id, result_id); if (content == nullptr) { return Status::Error(400, "Inline query result not found"); } auto input_reply_to = get_message_input_reply_to(d, top_thread_message_id, std::move(reply_to), false); TRY_STATUS(can_use_message_send_options(message_send_options, content->message_content, 0)); TRY_STATUS(can_send_message_content(dialog_id, content->message_content.get(), false, true, td_)); TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, input_reply_to)); auto message_content = dup_message_content(td_, dialog_id, content->message_content.get(), MessageContentDupType::SendViaBot, MessageCopyOptions()); bool need_update_dialog_pos = false; unique_ptr message; Message *m; if (message_send_options.only_preview) { message = create_message_to_send(d, top_thread_message_id, std::move(input_reply_to), message_send_options, std::move(message_content), content->invert_media, false, nullptr, true, DialogId()); m = message.get(); } else { m = get_message_to_send(d, top_thread_message_id, std::move(input_reply_to), message_send_options, std::move(message_content), content->invert_media, &need_update_dialog_pos, false, nullptr, true); } m->hide_via_bot = hide_via_bot; if (!hide_via_bot) { m->via_bot_user_id = td_->inline_queries_manager_->get_inline_bot_user_id(query_id); } if (!to_secret) { m->reply_markup = dup_reply_markup(content->message_reply_markup); } m->disable_web_page_preview = content->disable_web_page_preview; m->clear_draft = !hide_via_bot; if (message_send_options.only_preview) { return get_message_object(dialog_id, m, "send_inline_query_result_message"); } if (m->clear_draft) { if (top_thread_message_id.is_valid()) { set_dialog_draft_message(dialog_id, top_thread_message_id, nullptr).ignore(); } else { update_dialog_draft_message(d, nullptr, false, !need_update_dialog_pos); } } send_update_new_message(d, m); if (need_update_dialog_pos) { send_update_chat_last_message(d, "send_inline_query_result_message"); } if (to_secret) { save_send_message_log_event(dialog_id, m); do_send_message(dialog_id, m); } else { save_send_inline_query_result_message_log_event(dialog_id, m, query_id, result_id); send_closure_later(actor_id(this), &MessagesManager::do_send_inline_query_result_message, dialog_id, m->message_id, query_id, result_id); } return get_message_object(dialog_id, m, "send_inline_query_result_message"); } class MessagesManager::SendInlineQueryResultMessageLogEvent { public: DialogId dialog_id; int64 query_id; string result_id; const Message *m_in = nullptr; unique_ptr message_out; template void store(StorerT &storer) const { td::store(dialog_id, storer); td::store(query_id, storer); td::store(result_id, storer); td::store(*m_in, storer); } template void parse(ParserT &parser) { td::parse(dialog_id, parser); td::parse(query_id, parser); td::parse(result_id, parser); td::parse(message_out, parser); } }; void MessagesManager::save_send_inline_query_result_message_log_event(DialogId dialog_id, const Message *m, int64 query_id, const string &result_id) { if (!G()->use_message_database()) { return; } CHECK(m != nullptr); LOG(INFO) << "Save " << MessageFullId(dialog_id, m->message_id) << " to binlog"; SendInlineQueryResultMessageLogEvent log_event; log_event.dialog_id = dialog_id; log_event.query_id = query_id; log_event.result_id = result_id; log_event.m_in = m; CHECK(m->send_message_log_event_id == 0); m->send_message_log_event_id = binlog_add( G()->td_db()->get_binlog(), LogEvent::HandlerType::SendInlineQueryResultMessage, get_log_event_storer(log_event)); } void MessagesManager::do_send_inline_query_result_message(DialogId dialog_id, MessageId message_id, int64 query_id, const string &result_id) { if (G()->close_flag()) { return; } LOG(INFO) << "Do send inline query result " << MessageFullId(dialog_id, message_id); auto m = get_message({dialog_id, message_id}); if (m == nullptr) { return; } int64 random_id = begin_send_message(dialog_id, m); auto flags = get_message_flags(m); if (!m->via_bot_user_id.is_valid() || m->hide_via_bot) { flags |= telegram_api::messages_sendInlineBotResult::HIDE_VIA_MASK; } m->send_query_ref = td_->create_handler()->send( flags, dialog_id, get_send_message_as_input_peer(m), *get_message_input_reply_to(m), m->top_thread_message_id, get_message_schedule_date(m), random_id, query_id, result_id); } bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, bool is_editing, bool only_reply_markup) const { if (m == nullptr) { return false; } if (m->message_id.is_yet_unsent()) { return false; } if (m->message_id.is_local()) { return false; } if (m->forward_info != nullptr || m->had_forward_info) { return false; } if (m->had_reply_markup) { return false; } if (m->reply_markup != nullptr && m->reply_markup->type != ReplyMarkup::Type::InlineKeyboard) { return false; } auto my_id = td_->contacts_manager_->get_my_id(); if (m->via_bot_user_id.is_valid() && (m->via_bot_user_id != my_id || m->message_id.is_scheduled())) { return false; } bool is_bot = td_->auth_manager_->is_bot(); auto content_type = m->content->get_type(); DialogId my_dialog_id(my_id); bool has_edit_time_limit = !(is_bot && m->is_outgoing) && dialog_id != my_dialog_id && content_type != MessageContentType::Poll && content_type != MessageContentType::LiveLocation && !m->message_id.is_scheduled(); switch (dialog_id.get_type()) { case DialogType::User: if (!m->is_outgoing && dialog_id != my_dialog_id && !m->via_bot_user_id.is_valid()) { return false; } break; case DialogType::Chat: if (!m->is_outgoing && !m->via_bot_user_id.is_valid()) { return false; } break; case DialogType::Channel: { if (m->via_bot_user_id.is_valid()) { // outgoing via_bot messages can always be edited break; } auto channel_id = dialog_id.get_channel_id(); auto channel_status = td_->contacts_manager_->get_channel_permissions(channel_id); if (m->is_channel_post) { if (m->message_id.is_scheduled()) { if (!channel_status.can_post_messages()) { return false; } } else { if (!channel_status.can_edit_messages() && !(channel_status.can_post_messages() && m->is_outgoing)) { return false; } if (channel_status.can_edit_messages()) { has_edit_time_limit = false; } } if (is_bot && only_reply_markup) { has_edit_time_limit = false; } } else { if (!m->is_outgoing) { return false; } if (channel_status.can_pin_messages()) { has_edit_time_limit = false; } } break; } case DialogType::SecretChat: return false; case DialogType::None: default: UNREACHABLE(); return false; } if (has_edit_time_limit) { const int32 DEFAULT_EDIT_TIME_LIMIT = 2 * 86400; int64 edit_time_limit = td_->option_manager_->get_option_integer("edit_time_limit", DEFAULT_EDIT_TIME_LIMIT); if (G()->unix_time() - m->date - (is_editing ? 300 : 0) >= edit_time_limit) { return false; } } switch (content_type) { case MessageContentType::Animation: case MessageContentType::Audio: case MessageContentType::Document: case MessageContentType::Game: case MessageContentType::Photo: case MessageContentType::Text: case MessageContentType::Video: case MessageContentType::VoiceNote: return true; case MessageContentType::LiveLocation: { if (is_bot && only_reply_markup) { // there is no caption to edit, but bot can edit inline reply_markup return true; } return G()->unix_time() - m->date < get_message_content_live_location_period(m->content.get()); } case MessageContentType::Poll: { if (is_bot && only_reply_markup) { // there is no caption to edit, but bot can edit inline reply_markup return true; } if (m->message_id.is_scheduled()) { return false; } return !get_message_content_poll_is_closed(td_, m->content.get()); } case MessageContentType::Story: case MessageContentType::Contact: case MessageContentType::Dice: case MessageContentType::Giveaway: case MessageContentType::GiveawayWinners: case MessageContentType::Location: case MessageContentType::Sticker: case MessageContentType::Venue: case MessageContentType::VideoNote: // there is no caption to edit, but bot can edit inline reply_markup return is_bot && only_reply_markup; case MessageContentType::Invoice: case MessageContentType::Unsupported: case MessageContentType::ChatCreate: case MessageContentType::ChatChangeTitle: case MessageContentType::ChatChangePhoto: case MessageContentType::ChatDeletePhoto: case MessageContentType::ChatDeleteHistory: case MessageContentType::ChatAddUsers: case MessageContentType::ChatJoinedByLink: case MessageContentType::ChatDeleteUser: case MessageContentType::ChatMigrateTo: case MessageContentType::ChannelCreate: case MessageContentType::ChannelMigrateFrom: case MessageContentType::PinMessage: case MessageContentType::GameScore: case MessageContentType::ScreenshotTaken: case MessageContentType::ChatSetTtl: case MessageContentType::Call: case MessageContentType::PaymentSuccessful: case MessageContentType::ContactRegistered: case MessageContentType::ExpiredPhoto: case MessageContentType::ExpiredVideo: case MessageContentType::CustomServiceAction: case MessageContentType::WebsiteConnected: case MessageContentType::PassportDataSent: case MessageContentType::PassportDataReceived: case MessageContentType::ProximityAlertTriggered: case MessageContentType::GroupCall: case MessageContentType::InviteToGroupCall: case MessageContentType::ChatSetTheme: case MessageContentType::WebViewDataSent: case MessageContentType::WebViewDataReceived: case MessageContentType::GiftPremium: case MessageContentType::TopicCreate: case MessageContentType::TopicEdit: case MessageContentType::SuggestProfilePhoto: case MessageContentType::WriteAccessAllowed: case MessageContentType::RequestedDialog: case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: case MessageContentType::WriteAccessAllowedByRequest: case MessageContentType::GiftCode: case MessageContentType::GiveawayLaunch: case MessageContentType::GiveawayResults: return false; default: UNREACHABLE(); } return false; } bool MessagesManager::can_resend_message(const Message *m) const { if (m->send_error_code != 429 && m->send_error_message != "Message is too old to be re-sent automatically" && m->send_error_message != "SCHEDULE_TOO_MUCH" && m->send_error_message != "SEND_AS_PEER_INVALID" && m->send_error_message != "QUOTE_TEXT_INVALID" && m->send_error_message != "REPLY_MESSAGE_ID_INVALID") { return false; } if (m->is_bot_start_message) { return false; } if (m->forward_info != nullptr || m->real_forward_from_dialog_id.is_valid()) { // TODO implement resending of forwarded messages return false; } auto content_type = m->content->get_type(); if (m->via_bot_user_id.is_valid() || m->hide_via_bot) { // via bot message if (!can_have_input_media(td_, m->content.get(), false)) { return false; } // resend via_bot message as an ordinary message if error code is 429 // TODO support other error codes } if (content_type == MessageContentType::ChatSetTtl || content_type == MessageContentType::ScreenshotTaken) { // TODO implement resending of ChatSetTtl and ScreenshotTaken messages return false; } return true; } bool MessagesManager::is_group_dialog(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::Chat: return true; case DialogType::Channel: return td_->contacts_manager_->is_megagroup_channel(dialog_id.get_channel_id()); default: return false; } } bool MessagesManager::is_forum_channel(DialogId dialog_id) const { return dialog_id.get_type() == DialogType::Channel && td_->contacts_manager_->is_forum_channel(dialog_id.get_channel_id()); } bool MessagesManager::is_broadcast_channel(DialogId dialog_id) const { if (dialog_id.get_type() != DialogType::Channel) { return false; } return td_->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id()); } bool MessagesManager::is_deleted_secret_chat(DialogId dialog_id) const { return is_deleted_secret_chat(get_dialog(dialog_id)); } bool MessagesManager::is_deleted_secret_chat(const Dialog *d) const { if (d == nullptr) { return true; } if (d->dialog_id.get_type() != DialogType::SecretChat) { return false; } if (d->order != DEFAULT_ORDER || !d->messages.empty()) { return false; } auto state = td_->contacts_manager_->get_secret_chat_state(d->dialog_id.get_secret_chat_id()); if (state != SecretChatState::Closed) { return false; } return true; } int32 MessagesManager::get_message_schedule_date(const Message *m) { CHECK(m != nullptr); if (!m->message_id.is_scheduled()) { return 0; } if (m->edited_schedule_date != 0) { return m->edited_schedule_date; } return m->date; } DialogId MessagesManager::get_message_original_sender(const Message *m) { CHECK(m != nullptr); if (m->forward_info != nullptr) { auto forward_info = m->forward_info.get(); if (forward_info->is_imported) { return DialogId(); } return forward_info->origin.get_sender(); } return get_message_sender(m); } DialogId MessagesManager::get_message_sender(const Message *m) { CHECK(m != nullptr); return m->sender_dialog_id.is_valid() ? m->sender_dialog_id : DialogId(m->sender_user_id); } void MessagesManager::edit_message_text(MessageFullId message_full_id, tl_object_ptr &&reply_markup, tl_object_ptr &&input_message_content, Promise &&promise) { if (input_message_content == nullptr) { return promise.set_error(Status::Error(400, "Can't edit message without new content")); } int32 new_message_content_type = input_message_content->get_id(); if (new_message_content_type != td_api::inputMessageText::ID) { return promise.set_error(Status::Error(400, "Input message content type must be InputMessageText")); } auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "edit_message_text"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Edit)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } const Message *m = get_message_force(d, message_full_id.get_message_id(), "edit_message_text"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } if (!can_edit_message(dialog_id, m, true)) { return promise.set_error(Status::Error(400, "Message can't be edited")); } MessageContentType old_message_content_type = m->content->get_type(); if (old_message_content_type != MessageContentType::Text && old_message_content_type != MessageContentType::Game) { return promise.set_error(Status::Error(400, "There is no text in the message to edit")); } auto r_input_message_text = process_input_message_text(td_, dialog_id, std::move(input_message_content), td_->auth_manager_->is_bot()); if (r_input_message_text.is_error()) { return promise.set_error(r_input_message_text.move_as_error()); } const InputMessageText input_message_text = r_input_message_text.move_as_ok(); auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, has_message_sender_user_id(dialog_id, m)); if (r_new_reply_markup.is_error()) { return promise.set_error(r_new_reply_markup.move_as_error()); } auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok()); int32 flags = 0; if (input_message_text.disable_web_page_preview) { flags |= SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW; } td_->create_handler(std::move(promise)) ->send(flags, dialog_id, m->message_id, input_message_text.text.text, get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities, "edit_message_text"), input_message_text.get_input_media_web_page(), input_message_text.show_above_text, std::move(input_reply_markup), get_message_schedule_date(m)); } void MessagesManager::edit_message_live_location(MessageFullId message_full_id, tl_object_ptr &&reply_markup, tl_object_ptr &&input_location, int32 heading, int32 proximity_alert_radius, Promise &&promise) { auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "edit_message_live_location"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Edit)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } const Message *m = get_message_force(d, message_full_id.get_message_id(), "edit_message_live_location"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } if (!can_edit_message(dialog_id, m, true)) { return promise.set_error(Status::Error(400, "Message can't be edited")); } MessageContentType old_message_content_type = m->content->get_type(); if (old_message_content_type != MessageContentType::LiveLocation) { return promise.set_error(Status::Error(400, "There is no live location in the message to edit")); } if (m->message_id.is_scheduled()) { LOG(ERROR) << "Have " << message_full_id << " with live location"; return promise.set_error(Status::Error(400, "Can't edit live location in scheduled message")); } Location location(input_location); if (location.empty() && input_location != nullptr) { return promise.set_error(Status::Error(400, "Invalid location specified")); } auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, has_message_sender_user_id(dialog_id, m)); if (r_new_reply_markup.is_error()) { return promise.set_error(r_new_reply_markup.move_as_error()); } auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok()); int32 flags = 0; if (location.empty()) { flags |= telegram_api::inputMediaGeoLive::STOPPED_MASK; } if (heading != 0) { flags |= telegram_api::inputMediaGeoLive::HEADING_MASK; } flags |= telegram_api::inputMediaGeoLive::PROXIMITY_NOTIFICATION_RADIUS_MASK; auto input_media = telegram_api::make_object( flags, false /*ignored*/, location.get_input_geo_point(), heading, 0, proximity_alert_radius); td_->create_handler(std::move(promise)) ->send(0, dialog_id, m->message_id, string(), vector>(), std::move(input_media), false, std::move(input_reply_markup), get_message_schedule_date(m)); } void MessagesManager::cancel_edit_message_media(DialogId dialog_id, Message *m, Slice error_message) { if (m->edited_content == nullptr) { return; } cancel_upload_message_content_files(m->edited_content.get()); m->edited_content = nullptr; m->edited_invert_media = false; m->edited_reply_markup = nullptr; m->edit_generation = 0; m->edit_promise.set_error(Status::Error(400, error_message)); } void MessagesManager::on_message_media_edited(DialogId dialog_id, MessageId message_id, FileId file_id, FileId thumbnail_file_id, bool was_uploaded, bool was_thumbnail_uploaded, string file_reference, int32 schedule_date, uint64 generation, Result &&result) { // must not run getDifference CHECK(message_id.is_any_server()); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); auto m = get_message(d, message_id); if (m == nullptr || m->edit_generation != generation) { // message is already deleted or was edited again return; } CHECK(m->edited_content != nullptr); if (result.is_ok()) { // message content has already been replaced from updateEdit{Channel,}Message // need only merge files from edited_content with their uploaded counterparts // updateMessageContent was already sent and needs to be sent again, // only if 'i' and 't' sizes from edited_content were added to the photo auto pts = result.ok(); LOG(INFO) << "Successfully edited " << message_id << " in " << dialog_id << " with PTS = " << pts << " and last edit PTS = " << m->last_edit_pts; std::swap(m->content, m->edited_content); bool need_send_update_message_content = m->edited_content->get_type() == MessageContentType::Photo && m->content->get_type() == MessageContentType::Photo; bool need_merge_files = pts != 0 && pts == m->last_edit_pts; bool is_content_changed = false; bool need_update = update_message_content(dialog_id, m, std::move(m->edited_content), need_merge_files, true, is_content_changed); if (need_send_update_message_content) { if (need_update) { send_update_message_content(d, m, true, "on_message_media_edited"); } if (is_content_changed || need_update) { on_message_changed(d, m, need_update, "on_message_media_edited"); } } } else { LOG(INFO) << "Failed to edit " << message_id << " in " << dialog_id << ": " << result.error(); if (was_uploaded) { if (was_thumbnail_uploaded) { CHECK(thumbnail_file_id.is_valid()); // always delete partial remote location for the thumbnail, because it can't be reused anyway td_->file_manager_->delete_partial_remote_location(thumbnail_file_id); } CHECK(file_id.is_valid()); auto bad_parts = FileManager::get_missing_file_parts(result.error()); if (!bad_parts.empty()) { do_send_message(dialog_id, m, std::move(bad_parts)); return; } if (result.error().code() != 429 && result.error().code() < 500 && !G()->close_flag()) { td_->file_manager_->delete_partial_remote_location(file_id); } } else if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(result.error())) { if (file_id.is_valid()) { VLOG(file_references) << "Receive " << result.error() << " for " << file_id; td_->file_manager_->delete_file_reference(file_id, file_reference); do_send_message(dialog_id, m, {-1}); return; } else { LOG(ERROR) << "Receive file reference error, but have no file_id"; } } cancel_upload_message_content_files(m->edited_content.get()); if (dialog_id.get_type() != DialogType::SecretChat) { get_message_from_server({dialog_id, m->message_id}, Auto(), "on_message_media_edited"); } } if (m->edited_schedule_date == schedule_date) { m->edited_schedule_date = 0; } m->edited_content = nullptr; m->edited_invert_media = false; m->edited_reply_markup = nullptr; m->edit_generation = 0; if (result.is_ok()) { m->edit_promise.set_value(Unit()); } else { m->edit_promise.set_error(result.move_as_error()); } } void MessagesManager::edit_message_media(MessageFullId message_full_id, tl_object_ptr &&reply_markup, tl_object_ptr &&input_message_content, Promise &&promise) { if (input_message_content == nullptr) { return promise.set_error(Status::Error(400, "Can't edit message without new content")); } int32 new_message_content_type = input_message_content->get_id(); if (new_message_content_type != td_api::inputMessageAnimation::ID && new_message_content_type != td_api::inputMessageAudio::ID && new_message_content_type != td_api::inputMessageDocument::ID && new_message_content_type != td_api::inputMessagePhoto::ID && new_message_content_type != td_api::inputMessageVideo::ID) { return promise.set_error(Status::Error(400, "Unsupported input message content type")); } auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "edit_message_media"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Edit)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } Message *m = get_message_force(d, message_full_id.get_message_id(), "edit_message_media"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } if (!can_edit_message(dialog_id, m, true)) { return promise.set_error(Status::Error(400, "Message can't be edited")); } CHECK(m->message_id.is_any_server()); MessageContentType old_message_content_type = m->content->get_type(); if (old_message_content_type != MessageContentType::Animation && old_message_content_type != MessageContentType::Audio && old_message_content_type != MessageContentType::Document && old_message_content_type != MessageContentType::Photo && old_message_content_type != MessageContentType::Video) { return promise.set_error(Status::Error(400, "There is no media in the message to edit")); } if (m->ttl > 0) { return promise.set_error(Status::Error(400, "Can't edit media in self-destructing message")); } auto r_input_message_content = process_input_message_content(dialog_id, std::move(input_message_content)); if (r_input_message_content.is_error()) { return promise.set_error(r_input_message_content.move_as_error()); } InputMessageContent content = r_input_message_content.move_as_ok(); if (content.ttl > 0) { return promise.set_error(Status::Error(400, "Can't enable self-destruction for media")); } if (m->media_album_id != 0) { auto new_content_type = content.content->get_type(); if (old_message_content_type != new_content_type) { if (!is_allowed_media_group_content(new_content_type)) { return promise.set_error(Status::Error(400, "Message content type can't be used in an album")); } if (is_homogenous_media_group_content(old_message_content_type) || is_homogenous_media_group_content(new_content_type)) { return promise.set_error(Status::Error(400, "Can't change media type in the album")); } } } auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, has_message_sender_user_id(dialog_id, m)); if (r_new_reply_markup.is_error()) { return promise.set_error(r_new_reply_markup.move_as_error()); } cancel_edit_message_media(dialog_id, m, "Canceled by new editMessageMedia request"); m->edited_content = dup_message_content(td_, dialog_id, content.content.get(), MessageContentDupType::Send, MessageCopyOptions()); CHECK(m->edited_content != nullptr); m->edited_invert_media = content.invert_media; m->edited_reply_markup = r_new_reply_markup.move_as_ok(); m->edit_generation = ++current_message_edit_generation_; m->edit_promise = std::move(promise); do_send_message(dialog_id, m); } void MessagesManager::edit_message_caption(MessageFullId message_full_id, tl_object_ptr &&reply_markup, tl_object_ptr &&input_caption, Promise &&promise) { auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "edit_message_caption"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Edit)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } const Message *m = get_message_force(d, message_full_id.get_message_id(), "edit_message_caption"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } if (!can_edit_message(dialog_id, m, true)) { return promise.set_error(Status::Error(400, "Message can't be edited")); } if (!can_have_message_content_caption(m->content->get_type())) { return promise.set_error(Status::Error(400, "There is no caption in the message to edit")); } auto r_caption = get_formatted_text(td_, dialog_id, std::move(input_caption), td_->auth_manager_->is_bot(), true, false, false); if (r_caption.is_error()) { return promise.set_error(r_caption.move_as_error()); } auto caption = r_caption.move_as_ok(); auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, has_message_sender_user_id(dialog_id, m)); if (r_new_reply_markup.is_error()) { return promise.set_error(r_new_reply_markup.move_as_error()); } auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok()); td_->create_handler(std::move(promise)) ->send(1 << 11, dialog_id, m->message_id, caption.text, get_input_message_entities(td_->contacts_manager_.get(), caption.entities, "edit_message_caption"), nullptr, m->invert_media, std::move(input_reply_markup), get_message_schedule_date(m)); } void MessagesManager::edit_message_reply_markup(MessageFullId message_full_id, tl_object_ptr &&reply_markup, Promise &&promise) { CHECK(td_->auth_manager_->is_bot()); auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "edit_message_reply_markup"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Edit)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } const Message *m = get_message_force(d, message_full_id.get_message_id(), "edit_message_reply_markup"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } if (!can_edit_message(dialog_id, m, true, true)) { return promise.set_error(Status::Error(400, "Message can't be edited")); } auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, has_message_sender_user_id(dialog_id, m)); if (r_new_reply_markup.is_error()) { return promise.set_error(r_new_reply_markup.move_as_error()); } auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok()); td_->create_handler(std::move(promise)) ->send(0, dialog_id, m->message_id, string(), vector>(), nullptr, false, std::move(input_reply_markup), get_message_schedule_date(m)); } void MessagesManager::edit_inline_message_text(const string &inline_message_id, tl_object_ptr &&reply_markup, tl_object_ptr &&input_message_content, Promise &&promise) { CHECK(td_->auth_manager_->is_bot()); if (input_message_content == nullptr) { return promise.set_error(Status::Error(400, "Can't edit message without new content")); } int32 new_message_content_type = input_message_content->get_id(); if (new_message_content_type != td_api::inputMessageText::ID) { return promise.set_error(Status::Error(400, "Input message content type must be InputMessageText")); } auto r_input_message_text = process_input_message_text(td_, DialogId(), std::move(input_message_content), td_->auth_manager_->is_bot()); if (r_input_message_text.is_error()) { return promise.set_error(r_input_message_text.move_as_error()); } const InputMessageText input_message_text = r_input_message_text.move_as_ok(); auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true); if (r_new_reply_markup.is_error()) { return promise.set_error(r_new_reply_markup.move_as_error()); } auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id); if (input_bot_inline_message_id == nullptr) { return promise.set_error(Status::Error(400, "Invalid inline message identifier specified")); } int32 flags = 0; if (input_message_text.disable_web_page_preview) { flags |= SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW; } td_->create_handler(std::move(promise)) ->send(flags, std::move(input_bot_inline_message_id), input_message_text.text.text, get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities, "edit_inline_message_text"), input_message_text.get_input_media_web_page(), input_message_text.show_above_text, get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); } void MessagesManager::edit_inline_message_live_location(const string &inline_message_id, tl_object_ptr &&reply_markup, tl_object_ptr &&input_location, int32 heading, int32 proximity_alert_radius, Promise &&promise) { CHECK(td_->auth_manager_->is_bot()); auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true); if (r_new_reply_markup.is_error()) { return promise.set_error(r_new_reply_markup.move_as_error()); } auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id); if (input_bot_inline_message_id == nullptr) { return promise.set_error(Status::Error(400, "Invalid inline message identifier specified")); } Location location(input_location); if (location.empty() && input_location != nullptr) { return promise.set_error(Status::Error(400, "Invalid location specified")); } int32 flags = 0; if (location.empty()) { flags |= telegram_api::inputMediaGeoLive::STOPPED_MASK; } if (heading != 0) { flags |= telegram_api::inputMediaGeoLive::HEADING_MASK; } flags |= telegram_api::inputMediaGeoLive::PROXIMITY_NOTIFICATION_RADIUS_MASK; auto input_media = telegram_api::make_object( flags, false /*ignored*/, location.get_input_geo_point(), heading, 0, proximity_alert_radius); td_->create_handler(std::move(promise)) ->send(0, std::move(input_bot_inline_message_id), "", vector>(), std::move(input_media), false, get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); } void MessagesManager::edit_inline_message_media(const string &inline_message_id, tl_object_ptr &&reply_markup, tl_object_ptr &&input_message_content, Promise &&promise) { CHECK(td_->auth_manager_->is_bot()); if (input_message_content == nullptr) { return promise.set_error(Status::Error(400, "Can't edit message without new content")); } int32 new_message_content_type = input_message_content->get_id(); if (new_message_content_type != td_api::inputMessageAnimation::ID && new_message_content_type != td_api::inputMessageAudio::ID && new_message_content_type != td_api::inputMessageDocument::ID && new_message_content_type != td_api::inputMessagePhoto::ID && new_message_content_type != td_api::inputMessageVideo::ID) { return promise.set_error(Status::Error(400, "Unsupported input message content type")); } auto r_input_message_content = process_input_message_content(DialogId(), std::move(input_message_content)); if (r_input_message_content.is_error()) { return promise.set_error(r_input_message_content.move_as_error()); } InputMessageContent content = r_input_message_content.move_as_ok(); if (content.ttl > 0) { return promise.set_error(Status::Error(400, "Can't enable self-destruction for media")); } auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true); if (r_new_reply_markup.is_error()) { return promise.set_error(r_new_reply_markup.move_as_error()); } auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id); if (input_bot_inline_message_id == nullptr) { return promise.set_error(Status::Error(400, "Invalid inline message identifier specified")); } auto input_media = get_input_media(content.content.get(), td_, 0, string(), true); if (input_media == nullptr) { return promise.set_error(Status::Error(400, "Invalid message content specified")); } const FormattedText *caption = get_message_content_caption(content.content.get()); td_->create_handler(std::move(promise)) ->send(1 << 11, std::move(input_bot_inline_message_id), caption == nullptr ? "" : caption->text, get_input_message_entities(td_->contacts_manager_.get(), caption, "edit_inline_message_media"), std::move(input_media), content.invert_media, get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); } void MessagesManager::edit_inline_message_caption(const string &inline_message_id, tl_object_ptr &&reply_markup, tl_object_ptr &&input_caption, Promise &&promise) { CHECK(td_->auth_manager_->is_bot()); auto r_caption = get_formatted_text(td_, DialogId(), std::move(input_caption), td_->auth_manager_->is_bot(), true, false, false); if (r_caption.is_error()) { return promise.set_error(r_caption.move_as_error()); } auto caption = r_caption.move_as_ok(); auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true); if (r_new_reply_markup.is_error()) { return promise.set_error(r_new_reply_markup.move_as_error()); } auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id); if (input_bot_inline_message_id == nullptr) { return promise.set_error(Status::Error(400, "Invalid inline message identifier specified")); } td_->create_handler(std::move(promise)) ->send(1 << 11, std::move(input_bot_inline_message_id), caption.text, get_input_message_entities(td_->contacts_manager_.get(), caption.entities, "edit_inline_message_caption"), nullptr, false, get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); } void MessagesManager::edit_inline_message_reply_markup(const string &inline_message_id, tl_object_ptr &&reply_markup, Promise &&promise) { CHECK(td_->auth_manager_->is_bot()); auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true); if (r_new_reply_markup.is_error()) { return promise.set_error(r_new_reply_markup.move_as_error()); } auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id); if (input_bot_inline_message_id == nullptr) { return promise.set_error(Status::Error(400, "Invalid inline message identifier specified")); } td_->create_handler(std::move(promise)) ->send(0, std::move(input_bot_inline_message_id), string(), vector>(), nullptr, false, get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); } void MessagesManager::edit_message_scheduling_state( MessageFullId message_full_id, td_api::object_ptr &&scheduling_state, Promise &&promise) { auto r_schedule_date = get_message_schedule_date(std::move(scheduling_state)); if (r_schedule_date.is_error()) { return promise.set_error(r_schedule_date.move_as_error()); } auto schedule_date = r_schedule_date.move_as_ok(); auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "edit_message_scheduling_state"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Edit)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } Message *m = get_message_force(d, message_full_id.get_message_id(), "edit_message_scheduling_state"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } if (!m->message_id.is_scheduled()) { return promise.set_error(Status::Error(400, "Message is not scheduled")); } if (!m->message_id.is_scheduled_server()) { return promise.set_error(Status::Error(400, "Can't reschedule the message")); } if (get_message_schedule_date(m) == schedule_date) { return promise.set_value(Unit()); } m->edited_schedule_date = schedule_date; if (schedule_date > 0) { td_->create_handler(std::move(promise)) ->send(0, dialog_id, m->message_id, string(), vector>(), nullptr, false, nullptr, schedule_date); } else { td_->create_handler(std::move(promise))->send(dialog_id, m->message_id); } } bool MessagesManager::is_discussion_message(DialogId dialog_id, const Message *m) const { if (m == nullptr || m->forward_info == nullptr) { return false; } if (m->sender_user_id.is_valid()) { if (!td_->auth_manager_->is_bot() || m->sender_user_id != ContactsManager::get_service_notifications_user_id()) { return false; } } if (!m->forward_info->from_dialog_id.is_valid() || !m->forward_info->from_message_id.is_valid()) { return false; } if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id)) { return false; } if (m->forward_info->from_dialog_id == dialog_id) { return false; } if (m->forward_info->from_dialog_id.get_type() != DialogType::Channel) { return false; } return true; } bool MessagesManager::has_message_sender_user_id(DialogId dialog_id, const Message *m) const { if (!m->sender_user_id.is_valid()) { return false; } if (td_->auth_manager_->is_bot() && is_discussion_message(dialog_id, m)) { return false; } return true; } int32 MessagesManager::get_message_own_max_media_timestamp(const Message *m) const { auto duration = get_message_content_media_duration(m->content.get(), td_); return duration == 0 ? std::numeric_limits::max() : duration; } int32 MessagesManager::get_message_max_media_timestamp(const Message *m) { return m->max_own_media_timestamp >= 0 ? m->max_own_media_timestamp : m->max_reply_media_timestamp; } void MessagesManager::update_message_max_reply_media_timestamp(const Dialog *d, Message *m, bool need_send_update_message_content) const { if (td_->auth_manager_->is_bot()) { return; } auto new_max_reply_media_timestamp = -1; auto reply_message_full_id = m->replied_message_info.get_reply_message_full_id(d->dialog_id, false); auto reply_message_id = reply_message_full_id.get_message_id(); if (reply_message_id.is_valid() && !reply_message_id.is_yet_unsent()) { const auto *reply_d = reply_message_full_id.get_dialog_id() != d->dialog_id ? get_dialog(reply_message_full_id.get_dialog_id()) : d; if (reply_d == nullptr) { // replied message isn't loaded yet return; } auto replied_m = get_message(reply_d, reply_message_id); if (replied_m != nullptr) { new_max_reply_media_timestamp = get_message_own_max_media_timestamp(replied_m); } else if (!is_deleted_message(reply_d, reply_message_id) && reply_message_id > reply_d->last_clear_history_message_id && reply_message_id > reply_d->max_unavailable_message_id) { // replied message isn't deleted and isn't loaded yet return; } } else if (m->reply_to_story_full_id != StoryFullId()) { if (!td_->story_manager_->have_story(m->reply_to_story_full_id)) { if (!td_->story_manager_->is_inaccessible_story(m->reply_to_story_full_id)) { // replied story isn't loaded yet return; } } else { new_max_reply_media_timestamp = td_->story_manager_->get_story_duration(m->reply_to_story_full_id); } } if (m->max_reply_media_timestamp == new_max_reply_media_timestamp) { return; } LOG(INFO) << "Set max_reply_media_timestamp in " << m->message_id << " in " << d->dialog_id << " to " << new_max_reply_media_timestamp; auto old_max_media_timestamp = get_message_max_media_timestamp(m); m->max_reply_media_timestamp = new_max_reply_media_timestamp; auto new_max_media_timestamp = get_message_max_media_timestamp(m); if (need_send_update_message_content && old_max_media_timestamp != new_max_media_timestamp) { if (old_max_media_timestamp > new_max_media_timestamp) { std::swap(old_max_media_timestamp, new_max_media_timestamp); } if (has_media_timestamps(get_message_content_text(m->content.get()), old_max_media_timestamp + 1, new_max_media_timestamp)) { send_update_message_content_impl(d->dialog_id, m, "update_message_max_reply_media_timestamp"); } } } void MessagesManager::update_message_max_own_media_timestamp(const Dialog *d, Message *m) { if (td_->auth_manager_->is_bot()) { return; } auto new_max_own_media_timestamp = get_message_own_max_media_timestamp(m); if (m->max_own_media_timestamp == new_max_own_media_timestamp) { return; } LOG(INFO) << "Set max_own_media_timestamp in " << m->message_id << " in " << d->dialog_id << " to " << new_max_own_media_timestamp; m->max_own_media_timestamp = new_max_own_media_timestamp; update_message_max_reply_media_timestamp_in_replied_messages(d->dialog_id, m->message_id); } void MessagesManager::update_message_max_reply_media_timestamp_in_replied_messages(DialogId dialog_id, MessageId reply_to_message_id) { if (reply_to_message_id.is_scheduled()) { return; } CHECK(reply_to_message_id.is_valid()); if (reply_to_message_id.is_yet_unsent()) { return; } MessageFullId message_full_id{dialog_id, reply_to_message_id}; auto it = message_to_replied_media_timestamp_messages_.find(message_full_id); if (it == message_to_replied_media_timestamp_messages_.end()) { return; } LOG(INFO) << "Update max_reply_media_timestamp for replies of " << message_full_id; for (auto replied_message_full_id : it->second) { auto replied_dialog_id = replied_message_full_id.get_dialog_id(); Dialog *d = get_dialog(replied_dialog_id); auto m = get_message(d, replied_message_full_id.get_message_id()); CHECK(m != nullptr); CHECK(m->replied_message_info.get_reply_message_full_id(replied_dialog_id, false) == message_full_id); update_message_max_reply_media_timestamp(d, m, true); } } void MessagesManager::update_story_max_reply_media_timestamp_in_replied_messages(StoryFullId story_full_id) { auto it = story_to_replied_media_timestamp_messages_.find(story_full_id); if (it == story_to_replied_media_timestamp_messages_.end()) { return; } LOG(INFO) << "Update max_reply_media_timestamp for replies of " << story_full_id; for (auto replied_message_full_id : it->second) { auto replied_dialog_id = replied_message_full_id.get_dialog_id(); Dialog *d = get_dialog(replied_dialog_id); auto m = get_message(d, replied_message_full_id.get_message_id()); CHECK(m != nullptr); CHECK(m->reply_to_story_full_id == story_full_id); update_message_max_reply_media_timestamp(d, m, true); } } bool MessagesManager::can_register_message_reply(const Message *m) const { if (td_->auth_manager_->is_bot()) { return false; } auto reply_message_full_id = m->replied_message_info.get_reply_message_full_id(DialogId(), false); auto reply_message_id = reply_message_full_id.get_message_id(); if (reply_message_id.is_valid() && !reply_message_id.is_yet_unsent()) { return true; } if (m->reply_to_story_full_id.is_valid()) { return true; } return false; } void MessagesManager::register_message_reply(DialogId dialog_id, const Message *m) { m->replied_message_info.register_content(td_); if (!can_register_message_reply(m)) { return; } if (has_media_timestamps(get_message_content_text(m->content.get()), 0, std::numeric_limits::max())) { if (m->reply_to_story_full_id.is_valid()) { LOG(INFO) << "Register " << m->message_id << " in " << dialog_id << " as reply to " << m->reply_to_story_full_id; bool is_inserted = story_to_replied_media_timestamp_messages_[m->reply_to_story_full_id] .insert({dialog_id, m->message_id}) .second; CHECK(is_inserted); } else { auto message_full_id = m->replied_message_info.get_reply_message_full_id(dialog_id, false); LOG(INFO) << "Register " << m->message_id << " in " << dialog_id << " as reply to " << message_full_id; bool is_inserted = message_to_replied_media_timestamp_messages_[message_full_id].insert({dialog_id, m->message_id}).second; CHECK(is_inserted); } } } void MessagesManager::reregister_message_reply(DialogId dialog_id, const Message *m) { // reply itself wan't changed, so there is nothing to reregister if (!can_register_message_reply(m)) { return; } bool was_registered = false; if (m->reply_to_story_full_id.is_valid()) { auto it = story_to_replied_media_timestamp_messages_.find(m->reply_to_story_full_id); was_registered = it != story_to_replied_media_timestamp_messages_.end() && it->second.count({dialog_id, m->message_id}) > 0; } else { auto message_full_id = m->replied_message_info.get_reply_message_full_id(dialog_id, false); auto it = message_to_replied_media_timestamp_messages_.find(message_full_id); was_registered = it != message_to_replied_media_timestamp_messages_.end() && it->second.count({dialog_id, m->message_id}) > 0; } bool need_register = has_media_timestamps(get_message_content_text(m->content.get()), 0, std::numeric_limits::max()); if (was_registered == need_register) { return; } if (was_registered) { unregister_message_reply(dialog_id, m); } else { register_message_reply(dialog_id, m); } } void MessagesManager::unregister_message_reply(DialogId dialog_id, const Message *m) { m->replied_message_info.unregister_content(td_); if (!can_register_message_reply(m)) { return; } if (m->reply_to_story_full_id.is_valid()) { auto it = story_to_replied_media_timestamp_messages_.find(m->reply_to_story_full_id); if (it == story_to_replied_media_timestamp_messages_.end()) { return; } auto is_deleted = it->second.erase({dialog_id, m->message_id}) > 0; if (is_deleted) { LOG(INFO) << "Unregister " << m->message_id << " in " << dialog_id << " as reply to " << m->reply_to_story_full_id; if (it->second.empty()) { story_to_replied_media_timestamp_messages_.erase(it); } } } else { auto message_full_id = m->replied_message_info.get_reply_message_full_id(dialog_id, false); auto it = message_to_replied_media_timestamp_messages_.find(message_full_id); if (it == message_to_replied_media_timestamp_messages_.end()) { return; } auto is_deleted = it->second.erase({dialog_id, m->message_id}) > 0; if (is_deleted) { LOG(INFO) << "Unregister " << m->message_id << " in " << dialog_id << " as reply to " << message_full_id; if (it->second.empty()) { message_to_replied_media_timestamp_messages_.erase(it); } } } } bool MessagesManager::get_message_disable_web_page_preview(const Message *m) { if (m->content->get_type() != MessageContentType::Text) { return false; } if (has_message_content_web_page(m->content.get())) { return false; } return m->disable_web_page_preview; } int32 MessagesManager::get_message_flags(const Message *m) { int32 flags = 0; if (m->disable_web_page_preview) { flags |= SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW; } if (m->reply_markup != nullptr) { flags |= SEND_MESSAGE_FLAG_HAS_REPLY_MARKUP; } if (m->disable_notification) { flags |= SEND_MESSAGE_FLAG_DISABLE_NOTIFICATION; } if (m->from_background) { flags |= SEND_MESSAGE_FLAG_FROM_BACKGROUND; } if (m->clear_draft) { flags |= SEND_MESSAGE_FLAG_CLEAR_DRAFT; } if (m->message_id.is_scheduled()) { flags |= SEND_MESSAGE_FLAG_HAS_SCHEDULE_DATE; } if (m->noforwards) { flags |= SEND_MESSAGE_FLAG_NOFORWARDS; } if (m->update_stickersets_order) { flags |= SEND_MESSAGE_FLAG_UPDATE_STICKER_SETS_ORDER; } if (m->invert_media) { flags |= SEND_MESSAGE_FLAG_INVERT_MEDIA; } return flags; } tl_object_ptr MessagesManager::get_send_message_as_input_peer(const Message *m) const { if (!m->has_explicit_sender) { return nullptr; } return get_input_peer(get_message_sender(m), AccessRights::Write); } bool MessagesManager::can_set_game_score(MessageFullId message_full_id) const { return can_set_game_score(message_full_id.get_dialog_id(), get_message(message_full_id)); } bool MessagesManager::can_set_game_score(DialogId dialog_id, const Message *m) const { if (m == nullptr) { return false; } if (m->content->get_type() != MessageContentType::Game) { return false; } if (m->message_id.is_scheduled()) { return false; } if (m->message_id.is_yet_unsent()) { return false; } if (m->message_id.is_local()) { return false; } if (m->via_bot_user_id.is_valid() && m->via_bot_user_id != td_->contacts_manager_->get_my_id()) { return false; } if (!td_->auth_manager_->is_bot()) { return false; } if (m->reply_markup == nullptr || m->reply_markup->type != ReplyMarkup::Type::InlineKeyboard || m->reply_markup->inline_keyboard.empty()) { return false; } switch (dialog_id.get_type()) { case DialogType::User: if (!m->is_outgoing && dialog_id != get_my_dialog_id()) { return false; } break; case DialogType::Chat: if (!m->is_outgoing) { return false; } break; case DialogType::Channel: { if (m->via_bot_user_id.is_valid()) { // outgoing via_bot messages can always be edited break; } auto channel_id = dialog_id.get_channel_id(); auto channel_status = td_->contacts_manager_->get_channel_permissions(channel_id); if (m->is_channel_post) { if (!channel_status.can_edit_messages() && !(channel_status.can_post_messages() && m->is_outgoing)) { return false; } } else { if (!m->is_outgoing) { return false; } } break; } case DialogType::SecretChat: return false; case DialogType::None: default: UNREACHABLE(); return false; } return true; } unique_ptr MessagesManager::get_message_forward_info( tl_object_ptr &&forward_header) { if (forward_header == nullptr) { return nullptr; } auto date = forward_header->date_; if (date <= 0) { LOG(ERROR) << "Wrong date in message forward header: " << oneline(to_string(forward_header)); return nullptr; } DialogId from_dialog_id; MessageId from_message_id; if (forward_header->saved_from_peer_ != nullptr) { from_dialog_id = DialogId(forward_header->saved_from_peer_); from_message_id = MessageId(ServerMessageId(forward_header->saved_from_msg_id_)); if (!from_dialog_id.is_valid() || !from_message_id.is_valid()) { LOG(ERROR) << "Receive " << from_message_id << " in " << from_dialog_id << " in message forward header: " << oneline(to_string(forward_header)); from_dialog_id = DialogId(); from_message_id = MessageId(); } else { force_create_dialog(from_dialog_id, "get_message_forward_info", true); } } bool is_imported = forward_header->imported_; auto psa_type = std::move(forward_header->psa_type_); auto r_origin = MessageOrigin::get_message_origin(td_, std::move(forward_header)); if (r_origin.is_error()) { return nullptr; } return td::make_unique(r_origin.move_as_ok(), date, from_dialog_id, from_message_id, std::move(psa_type), is_imported); } td_api::object_ptr MessagesManager::get_message_forward_info_object( const unique_ptr &forward_info) const { if (forward_info == nullptr || forward_info->is_imported) { return nullptr; } return td_api::make_object( forward_info->origin.get_message_origin_object(td_), forward_info->date, forward_info->psa_type, get_chat_id_object(forward_info->from_dialog_id, "messageForwardInfo"), forward_info->from_message_id.get()); } td_api::object_ptr MessagesManager::get_message_import_info_object( const unique_ptr &forward_info) const { if (forward_info == nullptr || !forward_info->is_imported) { return nullptr; } return td_api::make_object(forward_info->origin.get_sender_name(), forward_info->date); } Result> MessagesManager::get_dialog_reply_markup( DialogId dialog_id, tl_object_ptr &&reply_markup_ptr) const { if (reply_markup_ptr == nullptr) { return nullptr; } auto dialog_type = dialog_id.get_type(); bool is_anonymous = is_anonymous_administrator(dialog_id, nullptr); bool only_inline_keyboard = is_anonymous; bool request_buttons_allowed = dialog_type == DialogType::User; bool switch_inline_buttons_allowed = !is_anonymous; TRY_RESULT(reply_markup, get_reply_markup(std::move(reply_markup_ptr), td_->auth_manager_->is_bot(), only_inline_keyboard, request_buttons_allowed, switch_inline_buttons_allowed)); if (reply_markup == nullptr) { return nullptr; } switch (dialog_type) { case DialogType::User: if (reply_markup->type != ReplyMarkup::Type::InlineKeyboard) { reply_markup->is_personal = false; } break; case DialogType::Channel: case DialogType::Chat: case DialogType::SecretChat: case DialogType::None: // nothing special break; default: UNREACHABLE(); } return std::move(reply_markup); } class MessagesManager::ForwardMessagesLogEvent { public: DialogId to_dialog_id; DialogId from_dialog_id; vector message_ids; vector messages_in; bool drop_author; bool drop_media_captions; vector> messages_out; template void store(StorerT &storer) const { BEGIN_STORE_FLAGS(); STORE_FLAG(drop_author); STORE_FLAG(drop_media_captions); END_STORE_FLAGS(); td::store(to_dialog_id, storer); td::store(from_dialog_id, storer); td::store(message_ids, storer); td::store(messages_in, storer); } template void parse(ParserT &parser) { if (parser.version() >= static_cast(Version::UseServerForwardAsCopy)) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(drop_author); PARSE_FLAG(drop_media_captions); END_PARSE_FLAGS(); } else { drop_author = false; drop_media_captions = false; } td::parse(to_dialog_id, parser); td::parse(from_dialog_id, parser); td::parse(message_ids, parser); td::parse(messages_out, parser); } }; uint64 MessagesManager::save_forward_messages_log_event(DialogId to_dialog_id, DialogId from_dialog_id, const vector &messages, const vector &message_ids, bool drop_author, bool drop_media_captions) { ForwardMessagesLogEvent log_event{to_dialog_id, from_dialog_id, message_ids, messages, drop_author, drop_media_captions, Auto()}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ForwardMessages, get_log_event_storer(log_event)); } void MessagesManager::do_forward_messages(DialogId to_dialog_id, DialogId from_dialog_id, const vector &messages, const vector &message_ids, bool drop_author, bool drop_media_captions, uint64 log_event_id) { if (G()->close_flag()) { return; } CHECK(messages.size() == message_ids.size()); if (messages.empty()) { return; } if (log_event_id == 0 && G()->use_message_database()) { log_event_id = save_forward_messages_log_event(to_dialog_id, from_dialog_id, messages, message_ids, drop_author, drop_media_captions); } auto schedule_date = get_message_schedule_date(messages[0]); auto as_input_peer = get_send_message_as_input_peer(messages[0]); int32 flags = 0; if (messages[0]->disable_notification) { flags |= SEND_MESSAGE_FLAG_DISABLE_NOTIFICATION; } if (messages[0]->from_background) { flags |= SEND_MESSAGE_FLAG_FROM_BACKGROUND; } if (messages[0]->in_game_share) { flags |= SEND_MESSAGE_FLAG_WITH_MY_SCORE; } if (schedule_date != 0) { flags |= SEND_MESSAGE_FLAG_HAS_SCHEDULE_DATE; } if (as_input_peer != nullptr) { flags |= SEND_MESSAGE_FLAG_HAS_SEND_AS; } if (messages[0]->noforwards) { flags |= SEND_MESSAGE_FLAG_NOFORWARDS; } if (drop_author) { flags |= telegram_api::messages_forwardMessages::DROP_AUTHOR_MASK; } if (drop_media_captions) { flags |= telegram_api::messages_forwardMessages::DROP_MEDIA_CAPTIONS_MASK; } vector random_ids = transform(messages, [this, to_dialog_id](const Message *m) { return begin_send_message(to_dialog_id, m); }); send_closure_later(actor_id(this), &MessagesManager::send_forward_message_query, flags, to_dialog_id, messages[0]->top_thread_message_id, from_dialog_id, std::move(as_input_peer), message_ids, std::move(random_ids), schedule_date, get_erase_log_event_promise(log_event_id)); } void MessagesManager::send_forward_message_query(int32 flags, DialogId to_dialog_id, MessageId top_thread_message_id, DialogId from_dialog_id, tl_object_ptr as_input_peer, vector message_ids, vector random_ids, int32 schedule_date, Promise promise) { td_->create_handler(std::move(promise)) ->send(flags, to_dialog_id, top_thread_message_id, from_dialog_id, std::move(as_input_peer), message_ids, std::move(random_ids), schedule_date); } Result> MessagesManager::forward_message( DialogId to_dialog_id, MessageId top_thread_message_id, DialogId from_dialog_id, MessageId message_id, tl_object_ptr &&options, bool in_game_share, MessageCopyOptions &©_options) { bool need_copy = copy_options.send_copy; vector all_copy_options; all_copy_options.push_back(std::move(copy_options)); TRY_RESULT(result, forward_messages(to_dialog_id, top_thread_message_id, from_dialog_id, {message_id}, std::move(options), in_game_share, std::move(all_copy_options))); CHECK(result->messages_.size() == 1); if (result->messages_[0] == nullptr) { return Status::Error(400, need_copy ? Slice("The message can't be copied") : Slice("The message can't be forwarded")); } return std::move(result->messages_[0]); } MessageOrigin MessagesManager::get_forwarded_message_origin(DialogId dialog_id, const Message *m) const { CHECK(m != nullptr); MessageOrigin origin; if (m->forward_info != nullptr) { origin = m->forward_info->origin; } else if (m->is_channel_post) { if (is_broadcast_channel(dialog_id)) { auto author_signature = m->sender_user_id.is_valid() ? td_->contacts_manager_->get_user_title(m->sender_user_id) : m->author_signature; origin = MessageOrigin{UserId(), dialog_id, m->message_id, std::move(author_signature), string()}; } else { LOG(ERROR) << "Don't know how to forward a channel post not from a channel"; } } else if (m->sender_user_id.is_valid() || m->sender_dialog_id.is_valid()) { auto author_signature = m->author_signature; origin = MessageOrigin{m->sender_user_id, m->sender_dialog_id, MessageId(), string(), std::move(author_signature)}; } else { LOG(ERROR) << "Don't know how to forward a non-channel post message without forward info and sender"; } origin.hide_sender_if_needed(td_); return origin; } unique_ptr MessagesManager::create_message_forward_info(DialogId from_dialog_id, DialogId to_dialog_id, const Message *m) const { auto content_type = m->content->get_type(); if (content_type == MessageContentType::Game || content_type == MessageContentType::Audio || content_type == MessageContentType::Story) { return nullptr; } auto my_dialog_id = get_my_dialog_id(); DialogId saved_from_dialog_id; MessageId saved_from_message_id; if (to_dialog_id == my_dialog_id) { saved_from_dialog_id = from_dialog_id; saved_from_message_id = m->message_id; } if (m->forward_info != nullptr) { auto forward_info = make_unique(*m->forward_info); forward_info->from_dialog_id = saved_from_dialog_id; forward_info->from_message_id = saved_from_message_id; if (!forward_info->is_imported) { forward_info->origin.hide_sender_if_needed(td_); } return forward_info; } if (from_dialog_id != my_dialog_id || content_type == MessageContentType::Dice) { auto origin = get_forwarded_message_origin(from_dialog_id, m); if (!origin.is_empty()) { return td::make_unique(std::move(origin), m->date, saved_from_dialog_id, saved_from_message_id, "", false); } } return nullptr; } void MessagesManager::fix_forwarded_message(Message *m, DialogId to_dialog_id, const Message *forwarded_message, int64 media_album_id, bool drop_author) const { if (m->content->get_type() == MessageContentType::Audio) { drop_author = true; } bool is_game = m->content->get_type() == MessageContentType::Game; if (!drop_author || is_game) { m->via_bot_user_id = forwarded_message->via_bot_user_id; } m->media_album_id = media_album_id; if (!drop_author && forwarded_message->view_count > 0 && m->forward_info != nullptr && m->view_count == 0 && !(m->message_id.is_scheduled() && is_broadcast_channel(to_dialog_id))) { m->view_count = forwarded_message->view_count; m->forward_count = forwarded_message->forward_count; m->interaction_info_update_date = G()->unix_time(); } if (m->content->get_type() == MessageContentType::Game) { // via_bot_user_id in games is present unless the message is sent by the bot if (m->via_bot_user_id == UserId()) { // if there is no via_bot_user_id, then the original message was sent by the game owner m->via_bot_user_id = forwarded_message->sender_user_id; } if (m->via_bot_user_id == td_->contacts_manager_->get_my_id()) { // if via_bot_user_id is the current bot user, then there should be m->via_bot_user_id = UserId(); } } if (forwarded_message->reply_markup != nullptr && forwarded_message->reply_markup->type == ReplyMarkup::Type::InlineKeyboard && to_dialog_id.get_type() != DialogType::SecretChat) { bool need_reply_markup = true; for (auto &row : forwarded_message->reply_markup->inline_keyboard) { for (auto &button : row) { if (button.type == InlineKeyboardButton::Type::Url || button.type == InlineKeyboardButton::Type::UrlAuth) { // ok continue; } if (m->via_bot_user_id.is_valid() && (button.type == InlineKeyboardButton::Type::SwitchInline || button.type == InlineKeyboardButton::Type::SwitchInlineCurrentDialog)) { // ok continue; } need_reply_markup = false; } } if (need_reply_markup) { m->reply_markup = dup_reply_markup(forwarded_message->reply_markup); for (auto &row : m->reply_markup->inline_keyboard) { for (auto &button : row) { if (button.type == InlineKeyboardButton::Type::SwitchInlineCurrentDialog) { button.type = InlineKeyboardButton::Type::SwitchInline; } if (!button.forward_text.empty()) { button.text = std::move(button.forward_text); button.forward_text.clear(); } } } } } } Result MessagesManager::get_forwarded_messages( DialogId to_dialog_id, MessageId top_thread_message_id, DialogId from_dialog_id, const vector &message_ids, tl_object_ptr &&options, bool in_game_share, vector &©_options) { CHECK(copy_options.size() == message_ids.size()); if (message_ids.size() > 100) { // TODO replace with const from config or implement mass-forward return Status::Error(400, "Too many messages to forward"); } if (message_ids.empty()) { return Status::Error(400, "There are no messages to forward"); } Dialog *from_dialog = get_dialog_force(from_dialog_id, "forward_messages from"); if (from_dialog == nullptr) { return Status::Error(400, "Chat to forward messages from not found"); } if (!have_input_peer(from_dialog_id, AccessRights::Read)) { return Status::Error(400, "Can't access the chat to forward messages from"); } if (from_dialog_id.get_type() == DialogType::SecretChat) { return Status::Error(400, "Can't forward messages from secret chats"); } if (get_dialog_has_protected_content(from_dialog_id)) { for (const auto ©_option : copy_options) { if (!copy_option.send_copy || !td_->auth_manager_->is_bot()) { return Status::Error(400, "Message has protected content and can't be forwarded"); } } } Dialog *to_dialog = get_dialog_force(to_dialog_id, "forward_messages to"); if (to_dialog == nullptr) { return Status::Error(400, "Chat to forward messages to not found"); } TRY_STATUS(can_send_message(to_dialog_id)); TRY_RESULT(message_send_options, process_message_send_options(to_dialog_id, std::move(options), false)); TRY_STATUS(can_use_top_thread_message_id(to_dialog, top_thread_message_id, MessageInputReplyTo())); { MessageId last_message_id; for (auto message_id : message_ids) { if (message_id.is_valid_scheduled()) { return Status::Error(400, "Can't forward scheduled messages"); } if (message_id.is_scheduled() || !message_id.is_valid()) { return Status::Error(400, "Invalid message identifier"); } if (message_id <= last_message_id) { return Status::Error(400, "Message identifiers must be in a strictly increasing order"); } last_message_id = message_id; } } bool to_secret = to_dialog_id.get_type() == DialogType::SecretChat; bool can_use_server_forward = true; for (auto ©_option : copy_options) { if (!copy_option.is_supported_server_side()) { can_use_server_forward = false; break; } } CHECK(can_use_server_forward || copy_options.size() == 1); if (to_secret) { can_use_server_forward = false; } ForwardedMessages result; result.to_dialog = to_dialog; result.from_dialog = from_dialog; result.message_send_options = message_send_options; auto &copied_messages = result.copied_messages; auto &forwarded_message_contents = result.forwarded_message_contents; result.drop_author = can_use_server_forward && copy_options[0].send_copy; result.drop_media_captions = can_use_server_forward && copy_options[0].replace_caption; std::unordered_map, Hash> new_copied_media_album_ids; std::unordered_map, Hash> new_forwarded_media_album_ids; for (size_t i = 0; i < message_ids.size(); i++) { MessageId message_id = get_persistent_message_id(from_dialog, message_ids[i]); const Message *forwarded_message = get_message_force(from_dialog, message_id, "get_forwarded_messages"); if (forwarded_message == nullptr) { LOG(INFO) << "Can't find " << message_id << " to forward"; continue; } CHECK(message_id.is_valid()); CHECK(message_id == forwarded_message->message_id); if (!can_forward_message(from_dialog_id, forwarded_message)) { LOG(INFO) << "Can't forward " << message_id; continue; } bool is_broken_server_copy = [&] { switch (forwarded_message->content->get_type()) { case MessageContentType::Dice: return true; default: return false; } }(); bool need_copy = !message_id.is_server() || to_secret || copy_options[i].send_copy; bool is_local_copy = need_copy && !(message_id.is_server() && can_use_server_forward && !is_broken_server_copy); if (!(need_copy && td_->auth_manager_->is_bot()) && !can_save_message(from_dialog_id, forwarded_message)) { LOG(INFO) << "Forward of " << message_id << " is restricted"; continue; } auto type = need_copy ? (is_local_copy ? MessageContentDupType::Copy : MessageContentDupType::ServerCopy) : MessageContentDupType::Forward; auto input_reply_to = std::move(copy_options[i].input_reply_to); auto reply_markup = std::move(copy_options[i].reply_markup); unique_ptr content = dup_message_content(td_, to_dialog_id, forwarded_message->content.get(), type, std::move(copy_options[i])); if (content == nullptr) { LOG(INFO) << "Can't forward content of " << message_id; continue; } auto can_send_status = can_send_message_content(to_dialog_id, content.get(), !is_local_copy, true, td_); if (can_send_status.is_error()) { LOG(INFO) << "Can't forward " << message_id << ": " << can_send_status.message(); continue; } auto can_use_options_status = can_use_message_send_options(message_send_options, content, 0); if (can_use_options_status.is_error()) { LOG(INFO) << "Can't forward " << message_id << ": " << can_send_status.message(); continue; } if (can_use_top_thread_message_id(to_dialog, top_thread_message_id, input_reply_to).is_error()) { LOG(INFO) << "Ignore invalid message thread ID " << top_thread_message_id; top_thread_message_id = MessageId(); } if (forwarded_message->media_album_id != 0) { auto &new_media_album_id = is_local_copy ? new_copied_media_album_ids[forwarded_message->media_album_id] : new_forwarded_media_album_ids[forwarded_message->media_album_id]; new_media_album_id.second++; if (new_media_album_id.second == 2) { // have at least 2 messages in the new album CHECK(new_media_album_id.first == 0); new_media_album_id.first = generate_new_media_album_id(); } if (new_media_album_id.second == MAX_GROUPED_MESSAGES + 1) { CHECK(new_media_album_id.first != 0); new_media_album_id.first = 0; // just in case } } if (is_local_copy) { auto original_reply_to_message_id = forwarded_message->replied_message_info.get_same_chat_reply_to_message_id(true); copied_messages.push_back( {std::move(content), std::move(input_reply_to), forwarded_message->message_id, original_reply_to_message_id, std::move(reply_markup), forwarded_message->media_album_id, get_message_disable_web_page_preview(forwarded_message), forwarded_message->invert_media, i}); } else { forwarded_message_contents.push_back( {std::move(content), forwarded_message->invert_media, forwarded_message->media_album_id, i}); } } result.top_thread_message_id = top_thread_message_id; if (2 <= forwarded_message_contents.size() && forwarded_message_contents.size() <= MAX_GROUPED_MESSAGES) { std::unordered_set message_content_types; std::unordered_set sender_dialog_ids; for (auto &message_content : forwarded_message_contents) { message_content_types.insert(message_content.content->get_type()); MessageId message_id = get_persistent_message_id(from_dialog, message_ids[message_content.index]); sender_dialog_ids.insert(get_message_original_sender(get_message(from_dialog, message_id))); } if (message_content_types.size() == 1 && is_homogenous_media_group_content(*message_content_types.begin()) && sender_dialog_ids.size() == 1 && *sender_dialog_ids.begin() != DialogId()) { new_forwarded_media_album_ids[0].first = generate_new_media_album_id(); for (auto &message : forwarded_message_contents) { message.media_album_id = 0; } } } for (auto &message : forwarded_message_contents) { message.media_album_id = new_forwarded_media_album_ids[message.media_album_id].first; } if (2 <= copied_messages.size() && copied_messages.size() <= MAX_GROUPED_MESSAGES) { std::unordered_set message_content_types; for (auto &copied_message : copied_messages) { message_content_types.insert(copied_message.content->get_type()); } if (message_content_types.size() == 1 && is_homogenous_media_group_content(*message_content_types.begin())) { new_copied_media_album_ids[0].first = generate_new_media_album_id(); for (auto &message : copied_messages) { message.media_album_id = 0; } } } for (auto &message : copied_messages) { message.media_album_id = new_copied_media_album_ids[message.media_album_id].first; } return std::move(result); } Result> MessagesManager::forward_messages( DialogId to_dialog_id, MessageId top_thread_message_id, DialogId from_dialog_id, vector message_ids, tl_object_ptr &&options, bool in_game_share, vector &©_options) { TRY_RESULT(forwarded_messages_info, get_forwarded_messages(to_dialog_id, top_thread_message_id, from_dialog_id, message_ids, std::move(options), in_game_share, std::move(copy_options))); auto from_dialog = forwarded_messages_info.from_dialog; auto to_dialog = forwarded_messages_info.to_dialog; const auto message_send_options = forwarded_messages_info.message_send_options; auto &copied_messages = forwarded_messages_info.copied_messages; auto &forwarded_message_contents = forwarded_messages_info.forwarded_message_contents; auto drop_author = forwarded_messages_info.drop_author; auto drop_media_captions = forwarded_messages_info.drop_media_captions; top_thread_message_id = forwarded_messages_info.top_thread_message_id; FlatHashMap forwarded_message_id_to_new_message_id; vector> result(message_ids.size()); vector forwarded_messages; vector forwarded_message_ids; bool need_update_dialog_pos = false; for (size_t j = 0; j < forwarded_message_contents.size(); j++) { MessageId message_id = get_persistent_message_id(from_dialog, message_ids[forwarded_message_contents[j].index]); const Message *forwarded_message = get_message(from_dialog, message_id); CHECK(forwarded_message != nullptr); auto content = std::move(forwarded_message_contents[j].content); auto forward_info = drop_author ? nullptr : create_message_forward_info(from_dialog_id, to_dialog_id, forwarded_message); MessageInputReplyTo input_reply_to; auto original_reply_to_message_id = forwarded_message->replied_message_info.get_same_chat_reply_to_message_id(true); if (original_reply_to_message_id.is_valid()) { auto it = forwarded_message_id_to_new_message_id.find(original_reply_to_message_id); if (it != forwarded_message_id_to_new_message_id.end()) { input_reply_to = forwarded_message->replied_message_info.get_input_reply_to(); input_reply_to.set_message_id(it->second); } } unique_ptr message; Message *m; if (message_send_options.only_preview) { message = create_message_to_send( to_dialog, top_thread_message_id, std::move(input_reply_to), message_send_options, std::move(content), forwarded_message_contents[j].invert_media, j + 1 != forwarded_message_contents.size(), std::move(forward_info), false, DialogId()); m = message.get(); } else { m = get_message_to_send(to_dialog, top_thread_message_id, std::move(input_reply_to), message_send_options, std::move(content), forwarded_message_contents[j].invert_media, &need_update_dialog_pos, j + 1 != forwarded_message_contents.size(), std::move(forward_info)); } fix_forwarded_message(m, to_dialog_id, forwarded_message, forwarded_message_contents[j].media_album_id, drop_author); m->in_game_share = in_game_share; m->real_forward_from_dialog_id = from_dialog_id; m->real_forward_from_message_id = message_id; forwarded_message_id_to_new_message_id.emplace(message_id, m->message_id); if (forwarded_message->replied_message_info.is_external()) { if (!message_send_options.only_preview) { unregister_message_reply(to_dialog_id, m); } m->replied_message_info = forwarded_message->replied_message_info.clone(td_); if (!message_send_options.only_preview) { register_message_reply(to_dialog_id, m); } } if (!message_send_options.only_preview) { if (!td_->auth_manager_->is_bot()) { send_update_new_message(to_dialog, m); } forwarded_messages.push_back(m); forwarded_message_ids.push_back(message_id); } result[forwarded_message_contents[j].index] = get_message_object(to_dialog_id, m, "forward_messages"); } if (!forwarded_messages.empty()) { CHECK(!message_send_options.only_preview); do_forward_messages(to_dialog_id, from_dialog_id, forwarded_messages, forwarded_message_ids, drop_author, drop_media_captions, 0); } bool is_secret = to_dialog_id.get_type() == DialogType::SecretChat; bool is_copy = !is_secret; bool need_invalidate_authentication_code = from_dialog_id == DialogId(ContactsManager::get_service_notifications_user_id()) && !td_->auth_manager_->is_bot(); vector authentication_codes; for (const auto &copied_message : copied_messages) { if (forwarded_message_id_to_new_message_id.count(copied_message.original_reply_to_message_id) > 0) { is_copy = true; break; } forwarded_message_id_to_new_message_id.emplace(copied_message.original_message_id, MessageId()); } for (auto &copied_message : copied_messages) { auto input_reply_to = std::move(copied_message.input_reply_to); if (!input_reply_to.is_valid() && copied_message.original_reply_to_message_id.is_valid() && is_secret) { auto it = forwarded_message_id_to_new_message_id.find(copied_message.original_reply_to_message_id); if (it != forwarded_message_id_to_new_message_id.end()) { input_reply_to = MessageInputReplyTo{it->second, DialogId(), FormattedText(), 0}; } } unique_ptr message; Message *m; if (message_send_options.only_preview) { message = create_message_to_send(to_dialog, top_thread_message_id, std::move(input_reply_to), message_send_options, std::move(copied_message.content), copied_message.invert_media, false, nullptr, is_copy, DialogId()); m = message.get(); } else { if (need_invalidate_authentication_code) { const Message *forwarded_message = get_message(from_dialog, copied_message.original_message_id); CHECK(forwarded_message != nullptr); extract_authentication_codes(from_dialog_id, forwarded_message, authentication_codes); } m = get_message_to_send(to_dialog, top_thread_message_id, std::move(input_reply_to), message_send_options, std::move(copied_message.content), copied_message.invert_media, &need_update_dialog_pos, false, nullptr, is_copy); } m->disable_web_page_preview = copied_message.disable_web_page_preview; m->media_album_id = copied_message.media_album_id; m->reply_markup = std::move(copied_message.reply_markup); forwarded_message_id_to_new_message_id[copied_message.original_message_id] = m->message_id; if (!message_send_options.only_preview) { save_send_message_log_event(to_dialog_id, m); do_send_message(to_dialog_id, m); if (!td_->auth_manager_->is_bot()) { send_update_new_message(to_dialog, m); } } result[copied_message.index] = get_message_object(to_dialog_id, m, "forward_messages"); } if (need_update_dialog_pos) { CHECK(!message_send_options.only_preview); send_update_chat_last_message(to_dialog, "forward_messages"); } if (!authentication_codes.empty()) { td_->account_manager_->invalidate_authentication_codes(std::move(authentication_codes)); } return get_messages_object(-1, std::move(result), false); } Result> MessagesManager::resend_messages(DialogId dialog_id, vector message_ids, td_api::object_ptr &"e) { if (message_ids.empty()) { return Status::Error(400, "There are no messages to resend"); } Dialog *d = get_dialog_force(dialog_id, "resend_messages"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } TRY_STATUS(can_send_message(dialog_id)); MessageId last_message_id; for (auto &message_id : message_ids) { message_id = get_persistent_message_id(d, message_id); const Message *m = get_message_force(d, message_id, "resend_messages"); if (m == nullptr) { return Status::Error(400, "Message not found"); } if (!m->is_failed_to_send) { return Status::Error(400, "Message is not failed to send"); } if (!can_resend_message(m)) { return Status::Error(400, "Message can't be re-sent"); } if (m->try_resend_at > Time::now()) { return Status::Error(400, "Message can't be re-sent yet"); } if (last_message_id != MessageId()) { if (m->message_id.is_scheduled() != last_message_id.is_scheduled()) { return Status::Error(400, "Messages must be all scheduled or ordinary"); } if (m->message_id <= last_message_id) { return Status::Error(400, "Message identifiers must be in a strictly increasing order"); } } last_message_id = m->message_id; } vector> new_contents(message_ids.size()); std::unordered_map, Hash> new_media_album_ids; for (size_t i = 0; i < message_ids.size(); i++) { MessageId message_id = message_ids[i]; const Message *m = get_message(d, message_id); CHECK(m != nullptr); unique_ptr content = dup_message_content(td_, dialog_id, m->content.get(), MessageContentDupType::Send, MessageCopyOptions()); if (content == nullptr) { LOG(INFO) << "Can't resend " << m->message_id; continue; } auto can_send_status = can_send_message_content(dialog_id, content.get(), false, true, td_); if (can_send_status.is_error()) { LOG(INFO) << "Can't resend " << m->message_id << ": " << can_send_status.message(); continue; } new_contents[i] = std::move(content); if (m->media_album_id != 0) { auto &new_media_album_id = new_media_album_ids[m->media_album_id]; new_media_album_id.second++; if (new_media_album_id.second == 2) { // have at least 2 messages in the new album CHECK(new_media_album_id.first == 0); new_media_album_id.first = generate_new_media_album_id(); } if (new_media_album_id.second == MAX_GROUPED_MESSAGES + 1) { CHECK(new_media_album_id.first != 0); new_media_album_id.first = 0; // just in case } } } vector result(message_ids.size()); bool need_update_dialog_pos = false; for (size_t i = 0; i < message_ids.size(); i++) { if (new_contents[i] == nullptr) { continue; } being_readded_message_id_ = {dialog_id, message_ids[i]}; auto message = delete_message(d, message_ids[i], true, &need_update_dialog_pos, "resend_messages"); CHECK(message != nullptr); send_update_delete_messages(dialog_id, {message->message_id.get()}, true); auto need_another_sender = message->send_error_code == 400 && message->send_error_message == CSlice("SEND_AS_PEER_INVALID"); auto need_another_reply_quote = message->send_error_code == 400 && message->send_error_message == CSlice("QUOTE_TEXT_INVALID"); auto need_drop_reply = message->send_error_code == 400 && message->send_error_message == CSlice("REPLY_MESSAGE_ID_INVALID"); if (need_another_reply_quote && message_ids.size() == 1 && quote != nullptr) { CHECK(message->input_reply_to.is_valid()); CHECK(message->input_reply_to.has_quote()); // checked in on_send_message_fail auto r_quote = get_formatted_text(td_, get_my_dialog_id(), std::move(quote->text_), td_->auth_manager_->is_bot(), true, true, true); if (r_quote.is_ok()) { message->input_reply_to.set_quote(r_quote.move_as_ok(), quote->position_); } } else if (need_drop_reply) { message->input_reply_to = {}; } MessageSendOptions options(message->disable_notification, message->from_background, message->update_stickersets_order, message->noforwards, false, get_message_schedule_date(message.get()), message->sending_id); Message *m = get_message_to_send(d, message->top_thread_message_id, std::move(message->input_reply_to), options, std::move(new_contents[i]), message->invert_media, &need_update_dialog_pos, false, nullptr, message->is_copy, need_another_sender ? DialogId() : get_message_sender(message.get())); m->reply_markup = std::move(message->reply_markup); // m->via_bot_user_id = message->via_bot_user_id; m->disable_web_page_preview = message->disable_web_page_preview; m->clear_draft = false; // never clear draft in resend m->ttl = message->ttl; m->is_content_secret = message->is_content_secret; m->media_album_id = new_media_album_ids[message->media_album_id].first; m->send_emoji = message->send_emoji; m->has_explicit_sender |= message->has_explicit_sender; save_send_message_log_event(dialog_id, m); do_send_message(dialog_id, m); send_update_new_message(d, m); result[i] = m->message_id; being_readded_message_id_ = MessageFullId(); } if (need_update_dialog_pos) { send_update_chat_last_message(d, "resend_messages"); } return result; } void MessagesManager::send_screenshot_taken_notification_message(Dialog *d) { LOG(INFO) << "Begin to send notification about taken screenshot in " << d->dialog_id; auto dialog_type = d->dialog_id.get_type(); if (dialog_type == DialogType::User) { bool need_update_dialog_pos = false; const Message *m = get_message_to_send(d, MessageId(), MessageInputReplyTo(), MessageSendOptions(), create_screenshot_taken_message_content(), false, &need_update_dialog_pos); do_send_screenshot_taken_notification_message(d->dialog_id, m, 0); send_update_new_message(d, m); if (need_update_dialog_pos) { send_update_chat_last_message(d, "send_screenshot_taken_notification_message"); } } else { CHECK(dialog_type == DialogType::SecretChat); send_closure(td_->secret_chats_manager_, &SecretChatsManager::notify_screenshot_taken, d->dialog_id.get_secret_chat_id(), Promise()); } } class MessagesManager::SendScreenshotTakenNotificationMessageLogEvent { public: DialogId dialog_id; const Message *m_in = nullptr; unique_ptr message_out; template void store(StorerT &storer) const { td::store(dialog_id, storer); td::store(*m_in, storer); } template void parse(ParserT &parser) { td::parse(dialog_id, parser); td::parse(message_out, parser); } }; uint64 MessagesManager::save_send_screenshot_taken_notification_message_log_event(DialogId dialog_id, const Message *m) { if (!G()->use_message_database()) { return 0; } CHECK(m != nullptr); LOG(INFO) << "Save " << MessageFullId(dialog_id, m->message_id) << " to binlog"; SendScreenshotTakenNotificationMessageLogEvent log_event; log_event.dialog_id = dialog_id; log_event.m_in = m; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SendScreenshotTakenNotificationMessage, get_log_event_storer(log_event)); } void MessagesManager::do_send_screenshot_taken_notification_message(DialogId dialog_id, const Message *m, uint64 log_event_id) { LOG(INFO) << "Do send screenshot taken notification " << MessageFullId(dialog_id, m->message_id); CHECK(dialog_id.get_type() == DialogType::User); if (log_event_id == 0) { log_event_id = save_send_screenshot_taken_notification_message_log_event(dialog_id, m); } int64 random_id = begin_send_message(dialog_id, m); td_->create_handler(get_erase_log_event_promise(log_event_id)) ->send(dialog_id, random_id); } void MessagesManager::share_dialogs_with_bot(MessageFullId message_full_id, int32 button_id, vector shared_dialog_ids, bool expect_user, bool only_check, Promise &&promise) { const Message *m = get_message_force(message_full_id, "share_dialog_with_bot"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } if (m->reply_markup == nullptr) { return promise.set_error(Status::Error(400, "Message has no buttons")); } CHECK(m->message_id.is_valid() && m->message_id.is_server()); TRY_STATUS_PROMISE(promise, m->reply_markup->check_shared_dialog_count(button_id, shared_dialog_ids.size())); for (auto shared_dialog_id : shared_dialog_ids) { if (shared_dialog_id.get_type() != DialogType::User) { if (!have_dialog_force(shared_dialog_id, "share_dialogs_with_bot")) { return promise.set_error(Status::Error(400, "Shared chat not found")); } } else { if (!expect_user) { return promise.set_error(Status::Error(400, "Wrong chat type")); } if (!td_->contacts_manager_->have_user(shared_dialog_id.get_user_id())) { return promise.set_error(Status::Error(400, "Shared user not found")); } } TRY_STATUS_PROMISE(promise, m->reply_markup->check_shared_dialog(td_, button_id, shared_dialog_id)); } if (only_check) { return promise.set_value(Unit()); } td_->create_handler(std::move(promise)) ->send(message_full_id, button_id, std::move(shared_dialog_ids)); } Result MessagesManager::add_local_message( DialogId dialog_id, td_api::object_ptr &&sender, td_api::object_ptr &&reply_to, bool disable_notification, tl_object_ptr &&input_message_content) { if (input_message_content == nullptr) { return Status::Error(400, "Can't add local message without content"); } Dialog *d = get_dialog_force(dialog_id, "add_local_message"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return Status::Error(400, "Can't access the chat"); } TRY_RESULT(message_content, process_input_message_content(dialog_id, std::move(input_message_content), false)); if (message_content.content->get_type() == MessageContentType::Poll) { return Status::Error(400, "Can't add local poll message"); } if (message_content.content->get_type() == MessageContentType::Game) { return Status::Error(400, "Can't add local game message"); } if (message_content.content->get_type() == MessageContentType::Dice) { return Status::Error(400, "Can't add local dice message"); } bool is_channel_post = is_broadcast_channel(dialog_id); UserId sender_user_id; DialogId sender_dialog_id; if (sender != nullptr) { TRY_RESULT_ASSIGN(sender_dialog_id, get_message_sender_dialog_id(td_, sender, true, false)); auto sender_dialog_type = sender_dialog_id.get_type(); if (sender_dialog_type == DialogType::User) { sender_user_id = sender_dialog_id.get_user_id(); sender_dialog_id = DialogId(); } else if (sender_dialog_type != DialogType::Channel) { return Status::Error(400, "Sender chat must be a supergroup or channel"); } } else if (is_channel_post) { sender_dialog_id = dialog_id; } else { return Status::Error(400, "The message must have a sender"); } if (is_channel_post && sender_user_id.is_valid()) { return Status::Error(400, "Channel post can't have user as a sender"); } if (is_channel_post && sender_dialog_id != dialog_id) { return Status::Error(400, "Channel post must have the channel as a sender"); } auto dialog_type = dialog_id.get_type(); auto my_id = td_->contacts_manager_->get_my_id(); if (sender_user_id != my_id) { if (dialog_type == DialogType::User && DialogId(sender_user_id) != dialog_id) { return Status::Error(400, "Wrong sender user"); } if (dialog_type == DialogType::SecretChat) { auto peer_user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (!peer_user_id.is_valid() || sender_user_id != peer_user_id) { return Status::Error(400, "Wrong sender user"); } } } auto input_reply_to = get_message_input_reply_to(d, MessageId(), std::move(reply_to), false); MessageId message_id = get_next_local_message_id(d); auto message = make_unique(); auto *m = message.get(); m->message_id = message_id; if (is_channel_post) { // sender of the post can be hidden if (td_->contacts_manager_->get_channel_sign_messages(dialog_id.get_channel_id())) { m->author_signature = td_->contacts_manager_->get_user_title(sender_user_id); } m->sender_dialog_id = sender_dialog_id; } else { m->sender_user_id = sender_user_id; m->sender_dialog_id = sender_dialog_id; } m->date = G()->unix_time(); m->replied_message_info = RepliedMessageInfo(td_, input_reply_to); m->reply_to_story_full_id = input_reply_to.get_story_full_id(); if (!message_id.is_scheduled()) { auto reply_to_message_id = input_reply_to.get_same_chat_reply_to_message_id(); const Message *reply_m = get_message(d, reply_to_message_id); if (reply_m != nullptr) { m->top_thread_message_id = reply_m->top_thread_message_id; if (m->top_thread_message_id.is_valid()) { m->is_topic_message = reply_m->is_topic_message; } } } m->is_channel_post = is_channel_post; m->is_outgoing = dialog_id != DialogId(my_id) && sender_user_id == my_id; m->disable_notification = disable_notification; m->from_background = false; m->update_stickersets_order = false; m->view_count = 0; m->forward_count = 0; m->content = std::move(message_content.content); m->invert_media = message_content.invert_media; m->disable_web_page_preview = message_content.disable_web_page_preview; m->clear_draft = message_content.clear_draft; if (dialog_type == DialogType::SecretChat) { m->ttl = min(td_->contacts_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id()), 0x7FFFFFFE); if (is_service_message_content(m->content->get_type())) { m->ttl = 0; } } else if (message_content.ttl > 0) { m->ttl = message_content.ttl; } m->is_content_secret = is_secret_message_content(m->ttl, m->content->get_type()); m->send_emoji = std::move(message_content.emoji); bool need_update = true; bool need_update_dialog_pos = false; auto result = add_message_to_dialog(d, std::move(message), false, true, &need_update, &need_update_dialog_pos, "add local message"); LOG_CHECK(result != nullptr) << message_id << " " << debug_add_message_to_dialog_fail_reason_; register_new_local_message_id(d, result); if (is_message_auto_read(dialog_id, result->is_outgoing)) { if (result->is_outgoing) { read_history_outbox(d, message_id); } else { read_history_inbox(d, message_id, 0, "add_local_message"); } } if (result->clear_draft) { update_dialog_draft_message(d, nullptr, false, !need_update_dialog_pos); } send_update_new_message(d, result); if (need_update_dialog_pos) { send_update_chat_last_message(d, "add_local_message"); } return message_id; } void MessagesManager::get_message_file_type(const string &message_file_head, Promise> &&promise) { td_->create_handler(std::move(promise))->send(message_file_head); } Status MessagesManager::can_import_messages(DialogId dialog_id) { if (!have_dialog_force(dialog_id, "can_import_messages")) { return Status::Error(400, "Chat not found"); } TRY_STATUS(can_send_message(dialog_id)); switch (dialog_id.get_type()) { case DialogType::User: if (!td_->contacts_manager_->is_user_contact(dialog_id.get_user_id(), true)) { return Status::Error(400, "User must be a mutual contact"); } break; case DialogType::Chat: return Status::Error(400, "Basic groups must be updagraded to supergroups first"); case DialogType::Channel: if (is_broadcast_channel(dialog_id)) { return Status::Error(400, "Can't import messages to channels"); } if (!td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).can_change_info_and_settings()) { return Status::Error(400, "Not enough rights to import messages"); } break; case DialogType::SecretChat: return Status::Error(400, "Can't import messages to secret chats"); case DialogType::None: default: UNREACHABLE(); } return Status::OK(); } void MessagesManager::get_message_import_confirmation_text(DialogId dialog_id, Promise &&promise) { TRY_STATUS_PROMISE(promise, can_import_messages(dialog_id)); td_->create_handler(std::move(promise))->send(dialog_id); } void MessagesManager::import_messages(DialogId dialog_id, const td_api::object_ptr &message_file, const vector> &attached_files, Promise &&promise) { TRY_STATUS_PROMISE(promise, can_import_messages(dialog_id)); TRY_RESULT_PROMISE(promise, file_id, td_->file_manager_->get_input_file_id(FileType::Document, message_file, dialog_id, false, false)); vector attached_file_ids; attached_file_ids.reserve(attached_files.size()); for (auto &attached_file : attached_files) { auto file_type = td_->file_manager_->guess_file_type(attached_file); if (file_type != FileType::Animation && file_type != FileType::Audio && file_type != FileType::Document && file_type != FileType::Photo && file_type != FileType::Sticker && file_type != FileType::Video && file_type != FileType::VoiceNote) { LOG(INFO) << "Skip attached file of type " << file_type; continue; } TRY_RESULT_PROMISE(promise, attached_file_id, td_->file_manager_->get_input_file_id(file_type, attached_file, dialog_id, false, false)); attached_file_ids.push_back(attached_file_id); } upload_imported_messages(dialog_id, td_->file_manager_->dup_file_id(file_id, "import_messages"), std::move(attached_file_ids), false, std::move(promise)); } void MessagesManager::upload_imported_messages(DialogId dialog_id, FileId file_id, vector attached_file_ids, bool is_reupload, Promise &&promise, vector bad_parts) { CHECK(file_id.is_valid()); LOG(INFO) << "Ask to upload imported messages file " << file_id; auto info = td::make_unique(dialog_id, std::move(attached_file_ids), is_reupload, std::move(promise)); bool is_inserted = being_uploaded_imported_messages_.emplace(file_id, std::move(info)).second; CHECK(is_inserted); // TODO use force_reupload if is_reupload td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_imported_messages_callback_, 1, 0, false, true); } void MessagesManager::start_import_messages(DialogId dialog_id, int64 import_id, vector &&attached_file_ids, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); TRY_STATUS_PROMISE(promise, can_send_message(dialog_id)); auto pending_message_import = make_unique(); pending_message_import->dialog_id = dialog_id; pending_message_import->import_id = import_id; pending_message_import->promise = std::move(promise); auto &multipromise = pending_message_import->upload_files_multipromise; int64 random_id; do { random_id = Random::secure_int64(); } while (random_id == 0 || pending_message_imports_.count(random_id) > 0); pending_message_imports_[random_id] = std::move(pending_message_import); multipromise.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), random_id](Result result) { send_closure_later(actor_id, &MessagesManager::on_imported_message_attachments_uploaded, random_id, std::move(result)); })); auto lock_promise = multipromise.get_promise(); for (auto attached_file_id : attached_file_ids) { upload_imported_message_attachment(dialog_id, import_id, td_->file_manager_->dup_file_id(attached_file_id, "start_import_messages"), false, multipromise.get_promise()); } lock_promise.set_value(Unit()); } void MessagesManager::upload_imported_message_attachment(DialogId dialog_id, int64 import_id, FileId file_id, bool is_reupload, Promise &&promise, vector bad_parts) { CHECK(file_id.is_valid()); LOG(INFO) << "Ask to upload imported message attached file " << file_id; auto info = td::make_unique(dialog_id, import_id, is_reupload, std::move(promise)); bool is_inserted = being_uploaded_imported_message_attachments_.emplace(file_id, std::move(info)).second; CHECK(is_inserted); // TODO use force_reupload if is_reupload td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_imported_message_attachment_callback_, 1, 0, false, true); } void MessagesManager::on_imported_message_attachments_uploaded(int64 random_id, Result &&result) { G()->ignore_result_if_closing(result); auto it = pending_message_imports_.find(random_id); CHECK(it != pending_message_imports_.end()); auto pending_message_import = std::move(it->second); CHECK(pending_message_import != nullptr); pending_message_imports_.erase(it); if (result.is_error()) { pending_message_import->promise.set_error(result.move_as_error()); return; } CHECK(pending_message_import->upload_files_multipromise.promise_count() == 0); auto promise = std::move(pending_message_import->promise); auto dialog_id = pending_message_import->dialog_id; TRY_STATUS_PROMISE(promise, can_send_message(dialog_id)); td_->create_handler(std::move(promise))->send(dialog_id, pending_message_import->import_id); } bool MessagesManager::on_update_message_id(int64 random_id, MessageId new_message_id, const char *source) { if (!new_message_id.is_valid() && !new_message_id.is_valid_scheduled()) { LOG(ERROR) << "Receive " << new_message_id << " in updateMessageId with random_id " << random_id << " from " << source; return false; } CHECK(new_message_id.is_any_server()); auto it = being_sent_messages_.find(random_id); if (it == being_sent_messages_.end()) { // update about a new message sent from other device or a service message LOG(INFO) << "Receive not sent outgoing " << new_message_id << " with random_id = " << random_id; return true; } auto dialog_id = it->second.get_dialog_id(); auto old_message_id = it->second.get_message_id(); being_sent_messages_.erase(it); if (!have_message_force({dialog_id, old_message_id}, "on_update_message_id")) { delete_sent_message_on_server(dialog_id, new_message_id, old_message_id); return true; } LOG(INFO) << "Save correspondence from " << new_message_id << " in " << dialog_id << " to " << old_message_id; CHECK(old_message_id.is_yet_unsent()); if (new_message_id.is_scheduled()) { update_scheduled_message_ids_[dialog_id][new_message_id.get_scheduled_server_message_id()] = old_message_id; } else { update_message_ids_[MessageFullId(dialog_id, new_message_id)] = old_message_id; } return true; } bool MessagesManager::on_get_dialog_error(DialogId dialog_id, const Status &status, const char *source) { if (status.message() == CSlice("BOT_METHOD_INVALID")) { LOG(ERROR) << "Receive BOT_METHOD_INVALID from " << source; return true; } if (G()->is_expected_error(status)) { return true; } if (status.message() == CSlice("SEND_AS_PEER_INVALID")) { reload_dialog_info_full(dialog_id, "SEND_AS_PEER_INVALID"); return true; } if (status.message() == CSlice("QUOTE_TEXT_INVALID") || status.message() == CSlice("REPLY_MESSAGE_ID_INVALID")) { return true; } switch (dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: case DialogType::SecretChat: // to be implemented if necessary break; case DialogType::Channel: return td_->contacts_manager_->on_get_channel_error(dialog_id.get_channel_id(), status, source); case DialogType::None: // to be implemented if necessary break; default: UNREACHABLE(); } return false; } bool MessagesManager::on_get_message_error(DialogId dialog_id, MessageId message_id, const Status &status, const char *source) { if (status.message() == "MSG_ID_INVALID" || status.message() == "MESSAGE_ID_INVALID" || status.message() == "DATA_INVALID") { get_message_from_server({dialog_id, message_id}, Promise(), source); return true; } return on_get_dialog_error(dialog_id, status, source); } void MessagesManager::on_dialog_updated(DialogId dialog_id, const char *source) { if (G()->use_message_database()) { LOG(INFO) << "Update " << dialog_id << " from " << source; pending_updated_dialog_timeout_.add_timeout_in(dialog_id.get(), MAX_SAVE_DIALOG_DELAY); } } void MessagesManager::send_update_new_message(const Dialog *d, const Message *m) { CHECK(d != nullptr); CHECK(m != nullptr); CHECK(d->is_update_new_chat_sent); send_closure( G()->td(), &Td::send_update, td_api::make_object(get_message_object(d->dialog_id, m, "send_update_new_message"))); } NotificationGroupInfo &MessagesManager::get_notification_group_info(Dialog *d, bool from_mentions) { CHECK(d != nullptr); auto notification_info = add_dialog_notification_info(d); return from_mentions ? notification_info->mention_notification_group_ : notification_info->message_notification_group_; } NotificationGroupInfo &MessagesManager::get_notification_group_info(Dialog *d, const Message *m) { return get_notification_group_info(d, is_from_mention_notification_group(m)); } NotificationGroupId MessagesManager::get_dialog_notification_group_id(DialogId dialog_id, NotificationGroupInfo &group_info) { CHECK(!td_->auth_manager_->is_bot()); if (!group_info.is_valid()) { NotificationGroupId next_notification_group_id; do { next_notification_group_id = td_->notification_manager_->get_next_notification_group_id(); if (!next_notification_group_id.is_valid()) { return NotificationGroupId(); } } while (td_->notification_manager_->have_group_force(next_notification_group_id)); group_info = NotificationGroupInfo(next_notification_group_id); VLOG(notifications) << "Assign " << next_notification_group_id << " to " << dialog_id; on_dialog_updated(dialog_id, "get_dialog_notification_group_id"); notification_group_id_to_dialog_id_.emplace(next_notification_group_id, dialog_id); if (running_get_channel_difference(dialog_id) || get_channel_difference_to_log_event_id_.count(dialog_id) != 0) { send_closure_later(G()->notification_manager(), &NotificationManager::before_get_chat_difference, next_notification_group_id); } } CHECK(group_info.is_valid()); // notification group must be preloaded to guarantee that there is no race between // get_message_notifications_from_database_force and new notifications added right now td_->notification_manager_->load_group_force(group_info.get_group_id()); return group_info.get_group_id(); } Result MessagesManager::get_message_push_notification_info( DialogId dialog_id, MessageId message_id, int64 random_id, UserId sender_user_id, DialogId sender_dialog_id, int32 date, bool is_from_scheduled, bool contains_mention, bool is_pinned, bool is_from_binlog) { if (!is_from_scheduled && dialog_id == get_my_dialog_id()) { return Status::Error("Ignore notification in chat with self"); } if (td_->auth_manager_->is_bot()) { return Status::Error("Ignore notification sent to bot"); } Dialog *d = get_dialog_force(dialog_id, "get_message_push_notification_info"); if (d == nullptr) { return Status::Error(406, "Ignore notification in unknown chat"); } if (sender_dialog_id.is_valid() && !have_dialog_force(sender_dialog_id, "get_message_push_notification_info")) { return Status::Error(406, "Ignore notification sent by unknown chat"); } if (is_from_scheduled && dialog_id != get_my_dialog_id() && td_->option_manager_->get_option_boolean("disable_sent_scheduled_message_notifications")) { return Status::Error("Ignore notification about sent scheduled message"); } bool is_new_pinned = is_pinned && message_id.is_valid() && (d->notification_info == nullptr || message_id > d->notification_info->max_push_notification_message_id_); CHECK(!message_id.is_scheduled()); if (message_id.is_valid()) { if (message_id <= d->last_new_message_id) { return Status::Error("Ignore notification about known message"); } if (!is_from_binlog && d->notification_info != nullptr) { if (message_id == d->notification_info->max_push_notification_message_id_) { return Status::Error("Ignore previously added message push notification"); } if (message_id < d->notification_info->max_push_notification_message_id_) { return Status::Error("Ignore out of order message push notification"); } } if (message_id <= d->last_read_inbox_message_id) { return Status::Error("Ignore notification about read message"); } if (message_id <= d->last_clear_history_message_id) { return Status::Error("Ignore notification about message from cleared chat history"); } if (is_deleted_message(d, message_id)) { return Status::Error("Ignore notification about deleted message"); } if (message_id <= d->max_unavailable_message_id) { return Status::Error("Ignore notification about unavailable message"); } } if (random_id != 0) { CHECK(dialog_id.get_type() == DialogType::SecretChat); if (get_message_id_by_random_id(d, random_id, "get_message_push_notification_info").is_valid()) { return Status::Error(406, "Ignore notification about known secret message"); } } if (is_pinned) { contains_mention = !is_dialog_pinned_message_notifications_disabled(d); } else if (contains_mention && is_dialog_mention_notifications_disabled(d)) { contains_mention = false; } if (dialog_id.get_type() == DialogType::User) { contains_mention = false; } DialogId settings_dialog_id = dialog_id; Dialog *settings_dialog = d; if (contains_mention) { auto real_sender_dialog_id = sender_dialog_id.is_valid() ? sender_dialog_id : DialogId(sender_user_id); if (real_sender_dialog_id.is_valid()) { settings_dialog_id = real_sender_dialog_id; settings_dialog = get_dialog_force(settings_dialog_id, "get_message_push_notification_info"); } } bool have_settings; int32 mute_until; std::tie(have_settings, mute_until) = get_dialog_mute_until(settings_dialog_id, settings_dialog); if (have_settings && mute_until > date) { if (is_new_pinned) { remove_dialog_pinned_message_notification(d, "get_message_push_notification_info"); } return Status::Error("Ignore notification in muted chat"); } if (is_dialog_message_notification_disabled(settings_dialog_id, date)) { if (is_new_pinned) { remove_dialog_pinned_message_notification(d, "get_message_push_notification_info"); } return Status::Error("Ignore notification in chat, because notifications are disabled in the chat"); } auto group_id = get_dialog_notification_group_id(dialog_id, get_notification_group_info(d, contains_mention)); if (!group_id.is_valid()) { return Status::Error("Can't assign notification group ID"); } if (message_id.is_valid()) { auto notification_info = add_dialog_notification_info(d); if (message_id > notification_info->max_push_notification_message_id_) { if (is_new_pinned) { set_dialog_pinned_message_notification(d, contains_mention ? message_id : MessageId(), "get_message_push_notification_info"); } notification_info->max_push_notification_message_id_ = message_id; on_dialog_updated(dialog_id, "set_max_push_notification_message_id"); } } MessagePushNotificationInfo result; result.group_id = group_id; result.group_type = contains_mention ? NotificationGroupType::Mentions : NotificationGroupType::Messages; result.settings_dialog_id = settings_dialog_id; return result; } NotificationId MessagesManager::get_next_notification_id(NotificationInfo *notification_info, NotificationGroupId notification_group_id, MessageId message_id) { CHECK(notification_info != nullptr); CHECK(!message_id.is_scheduled()); NotificationId notification_id; do { notification_id = td_->notification_manager_->get_next_notification_id(); if (!notification_id.is_valid()) { return NotificationId(); } } while (notification_info->notification_id_to_message_id_.count(notification_id) != 0 || notification_info->new_secret_chat_notification_id_ == notification_id || notification_info->message_notification_group_.is_used_notification_id(notification_id) || notification_info->mention_notification_group_.is_used_notification_id(notification_id)); // just in case if (message_id.is_valid()) { add_notification_id_to_message_id_correspondence(notification_info, notification_id, message_id); } return notification_id; } NotificationGroupFromDatabase MessagesManager::get_message_notification_group_force(NotificationGroupId group_id) { CHECK(!td_->auth_manager_->is_bot()); CHECK(group_id.is_valid()); Dialog *d = nullptr; auto it = notification_group_id_to_dialog_id_.find(group_id); if (it != notification_group_id_to_dialog_id_.end()) { d = get_dialog(it->second); CHECK(d != nullptr); } else if (G()->use_message_database()) { auto *dialog_db = G()->td_db()->get_dialog_db_sync(); dialog_db->begin_read_transaction().ensure(); auto r_value = dialog_db->get_notification_group(group_id); if (r_value.is_ok()) { VLOG(notifications) << "Loaded " << r_value.ok() << " from database by " << group_id; d = get_dialog_force(r_value.ok().dialog_id, "get_message_notification_group_force"); } else { LOG_CHECK(r_value.error().message() == "Not found") << r_value.error(); VLOG(notifications) << "Failed to load " << group_id << " from database"; } dialog_db->commit_transaction().ensure(); } if (d == nullptr || d->notification_info == nullptr) { return NotificationGroupFromDatabase(); } if (!is_dialog_notification_group_id(d, group_id)) { if (d->dialog_id.get_type() == DialogType::SecretChat && !d->notification_info->message_notification_group_.is_valid() && !d->notification_info->mention_notification_group_.is_valid()) { // the group was reused, but wasn't deleted from the database, trying to resave it auto &group_info = d->notification_info->message_notification_group_; group_info = NotificationGroupInfo(group_id); group_info.try_reuse(); save_dialog_to_database(d->dialog_id); group_info = NotificationGroupInfo(); } CHECK(is_dialog_notification_group_id(d, group_id)); } bool from_mentions = d->notification_info->mention_notification_group_.has_group_id(group_id); auto &group_info = get_notification_group_info(d, from_mentions); NotificationGroupFromDatabase result; VLOG(notifications) << "Found " << (from_mentions ? "Mentions " : "Messages ") << group_info.get_group_id() << '/' << d->dialog_id << " by " << group_id << " with " << d->unread_mention_count << " unread mentions, " << d->unread_reaction_count << " unread reactions, pinned " << d->notification_info->pinned_message_notification_message_id_ << ", new secret chat " << d->notification_info->new_secret_chat_notification_id_ << " and " << d->server_unread_count + d->local_unread_count << " unread messages"; result.dialog_id = d->dialog_id; result.total_count = get_dialog_pending_notification_count(d, from_mentions); auto pending_notification_count = from_mentions ? d->notification_info->pending_new_mention_notifications_.size() : d->notification_info->pending_new_message_notifications_.size(); result.total_count -= static_cast(pending_notification_count); if (result.total_count < 0) { LOG(ERROR) << "Total notification count is " << result.total_count << " in " << d->dialog_id << " with " << pending_notification_count << " pending new notifications"; result.total_count = 0; } if (d->notification_info->new_secret_chat_notification_id_.is_valid()) { CHECK(d->dialog_id.get_type() == DialogType::SecretChat); result.type = NotificationGroupType::SecretChat; result.notifications.emplace_back(d->notification_info->new_secret_chat_notification_id_, td_->contacts_manager_->get_secret_chat_date(d->dialog_id.get_secret_chat_id()), false, create_new_secret_chat_notification()); } else { result.type = from_mentions ? NotificationGroupType::Mentions : NotificationGroupType::Messages; result.notifications = get_message_notifications_from_database_force( d, from_mentions, static_cast(td_->notification_manager_->get_max_notification_group_size())); } int32 last_notification_date = 0; NotificationId last_notification_id; if (!result.notifications.empty()) { last_notification_date = result.notifications[0].date; last_notification_id = result.notifications[0].notification_id; } set_dialog_last_notification(d->dialog_id, group_info, last_notification_date, last_notification_id, "get_message_notification_group_force"); std::reverse(result.notifications.begin(), result.notifications.end()); return result; } bool MessagesManager::get_dialog_show_preview(const Dialog *d) const { CHECK(!td_->auth_manager_->is_bot()); CHECK(d != nullptr); if (d->notification_settings.use_default_show_preview) { auto scope = get_dialog_notification_setting_scope(d->dialog_id); return td_->notification_settings_manager_->get_scope_show_preview(scope); } else { return d->notification_settings.show_preview; } } bool MessagesManager::is_message_preview_enabled(const Dialog *d, const Message *m, bool from_mentions) { if (!get_dialog_show_preview(d)) { return false; } if (!from_mentions) { return true; } auto sender_dialog_id = get_message_sender(m); if (!sender_dialog_id.is_valid()) { return true; } d = get_dialog_force(sender_dialog_id, "is_message_preview_enabled"); if (d == nullptr) { auto scope = get_dialog_notification_setting_scope(sender_dialog_id); return td_->notification_settings_manager_->get_scope_show_preview(scope); } return get_dialog_show_preview(d); } bool MessagesManager::is_from_mention_notification_group(const Message *m) { CHECK(m != nullptr); return m->contains_mention && !m->is_mention_notification_disabled; } bool MessagesManager::is_message_notification_active(const Dialog *d, const Message *m) { CHECK(!m->message_id.is_scheduled()); if (d->notification_info == nullptr) { if (is_from_mention_notification_group(m)) { return m->contains_unread_mention; } else { return m->message_id > d->last_read_inbox_message_id; } } if (is_from_mention_notification_group(m)) { return !d->notification_info->mention_notification_group_.is_removed_notification(m->notification_id, m->message_id) && (m->contains_unread_mention || m->message_id == d->notification_info->pinned_message_notification_message_id_); } else { return !d->notification_info->message_notification_group_.is_removed_notification(m->notification_id, m->message_id) && m->message_id > d->last_read_inbox_message_id; } } void MessagesManager::try_add_pinned_message_notification(Dialog *d, vector &res, NotificationId max_notification_id, int32 limit) { CHECK(d != nullptr); if (d->notification_info == nullptr) { return; } auto message_id = d->notification_info->pinned_message_notification_message_id_; if (!message_id.is_valid() || message_id > d->last_new_message_id) { CHECK(!message_id.is_scheduled()); return; } auto m = get_message_force(d, message_id, "try_add_pinned_message_notification"); if (m != nullptr && !d->notification_info->mention_notification_group_.is_removed_notification(m->notification_id, m->message_id) && m->message_id > d->last_read_inbox_message_id && !is_dialog_pinned_message_notifications_disabled(d)) { if (m->notification_id.get() < max_notification_id.get()) { VLOG(notifications) << "Add " << m->notification_id << " about pinned " << message_id << " in " << d->dialog_id; auto pinned_message_id = get_message_content_pinned_message_id(m->content.get()); if (pinned_message_id.is_valid()) { get_message_force(d, pinned_message_id, "try_add_pinned_message_notification 2"); // preload pinned message } auto pos = res.size(); res.emplace_back(m->notification_id, m->date, m->disable_notification, create_new_message_notification(message_id, is_message_preview_enabled(d, m, true))); NotificationObjectId object_id(message_id); while (pos > 0 && res[pos - 1].type->get_object_id() < object_id) { std::swap(res[pos - 1], res[pos]); pos--; } if (pos > 0 && res[pos - 1].type->get_object_id() == object_id) { res.erase(res.begin() + pos); // notification was already there } if (res.size() > static_cast(limit)) { res.pop_back(); CHECK(res.size() == static_cast(limit)); } } } else { remove_dialog_pinned_message_notification(d, "try_add_pinned_message_notification"); } } vector MessagesManager::get_message_notifications_from_database_force(Dialog *d, bool from_mentions, int32 limit) { CHECK(d != nullptr); if (!G()->use_message_database() || td_->auth_manager_->is_bot()) { return {}; } auto &group_info = get_notification_group_info(d, from_mentions); auto from_notification_id = NotificationId::max(); auto from_message_id = MessageId::max(); vector res; if (!from_mentions && from_message_id <= d->last_read_inbox_message_id) { return res; } while (true) { auto messages = do_get_message_notifications_from_database_force(d, from_mentions, from_notification_id, from_message_id, limit); if (messages.empty()) { break; } bool is_found = false; VLOG(notifications) << "Loaded " << messages.size() << (from_mentions ? " mention" : "") << " messages with notifications from database in " << group_info.get_group_id() << '/' << d->dialog_id; for (auto &message : messages) { auto m = on_get_message_from_database(d, message, false, "get_message_notifications_from_database_force"); if (m == nullptr) { VLOG(notifications) << "Receive from database a broken message"; continue; } auto notification_id = m->notification_id.is_valid() ? m->notification_id : m->removed_notification_id; if (!notification_id.is_valid()) { if (from_mentions) { VLOG(notifications) << "Receive " << m->message_id << " with unread mention, but without notification"; is_found = false; } else { LOG(ERROR) << "Can't find notification identifier for " << m->message_id << " in " << d->dialog_id << " with from_mentions = " << from_mentions; } continue; } CHECK(m->message_id.is_valid()); bool is_correct = true; if (notification_id.get() >= from_notification_id.get()) { // possible if two messages have the same notification_id LOG(ERROR) << "Have nonmonotonic notification identifiers: " << d->dialog_id << " " << m->message_id << " " << notification_id << " " << from_message_id << " " << from_notification_id; is_correct = false; } else { from_notification_id = notification_id; is_found = true; } if (m->message_id >= from_message_id) { LOG(ERROR) << "Have nonmonotonic message identifiers: " << d->dialog_id << " " << m->message_id << " " << notification_id << " " << from_message_id << " " << from_notification_id; is_correct = false; } else { from_message_id = m->message_id; is_found = true; } if (group_info.is_removed_notification(notification_id, m->message_id) || (!from_mentions && m->message_id <= d->last_read_inbox_message_id)) { // if message still has notification_id, but it was removed, // then there will be no more messages with active notifications is_found = false; break; } if (!m->notification_id.is_valid()) { // notification_id can be empty if it is deleted in memory, but not in the database VLOG(notifications) << "Receive from database " << m->message_id << " with removed " << m->removed_notification_id; continue; } if (is_from_mention_notification_group(m) != from_mentions) { VLOG(notifications) << "Receive from database " << m->message_id << " with " << m->notification_id << " from another group"; continue; } if (!is_message_notification_active(d, m)) { CHECK(from_mentions); CHECK(!m->contains_unread_mention); // skip read mentions continue; } if (is_correct) { // skip mention messages returned among unread messages res.emplace_back( m->notification_id, m->date, m->disable_notification, create_new_message_notification(m->message_id, is_message_preview_enabled(d, m, from_mentions))); } else { remove_message_notification_id(d, m, true, false); on_message_changed(d, m, false, "get_message_notifications_from_database_force"); } } if (!res.empty() || !is_found) { break; } } if (from_mentions) { try_add_pinned_message_notification(d, res, NotificationId::max(), limit); } return res; } vector MessagesManager::do_get_message_notifications_from_database_force( Dialog *d, bool from_mentions, NotificationId from_notification_id, MessageId from_message_id, int32 limit) { CHECK(G()->use_message_database()); CHECK(!from_message_id.is_scheduled()); auto *db = G()->td_db()->get_message_db_sync(); if (!from_mentions) { CHECK(from_message_id > d->last_read_inbox_message_id); VLOG(notifications) << "Trying to load " << limit << " messages with notifications in " << d->dialog_id << " from " << from_notification_id; return db->get_messages_from_notification_id(d->dialog_id, from_notification_id, limit); } else { VLOG(notifications) << "Trying to load " << limit << " messages with unread mentions in " << d->dialog_id << " from " << from_message_id; // ignore first_db_message_id, notifications can be nonconsecutive MessageDbMessagesQuery db_query; db_query.dialog_id = d->dialog_id; db_query.filter = MessageSearchFilter::UnreadMention; db_query.from_message_id = from_message_id; db_query.offset = 0; db_query.limit = limit; return db->get_messages(db_query); } } vector MessagesManager::get_message_notification_group_keys_from_database( NotificationGroupKey from_group_key, int32 limit) { if (!G()->use_message_database()) { return {}; } VLOG(notifications) << "Trying to load " << limit << " message notification groups from database from " << from_group_key; auto *dialog_db = G()->td_db()->get_dialog_db_sync(); dialog_db->begin_read_transaction().ensure(); auto group_keys = dialog_db->get_notification_groups_by_last_notification_date(from_group_key, limit); vector result; for (auto &group_key : group_keys) { CHECK(group_key.group_id.is_valid()); CHECK(group_key.dialog_id.is_valid()); const Dialog *d = get_dialog_force(group_key.dialog_id, "get_message_notification_group_keys_from_database"); if (!is_dialog_notification_group_id(d, group_key.group_id)) { continue; } CHECK(d->dialog_id == group_key.dialog_id); CHECK(notification_group_id_to_dialog_id_[group_key.group_id] == d->dialog_id); VLOG(notifications) << "Loaded " << group_key << " from database"; result.push_back(group_key); } dialog_db->commit_transaction().ensure(); return result; } void MessagesManager::get_message_notifications_from_database(DialogId dialog_id, NotificationGroupId group_id, NotificationId from_notification_id, MessageId from_message_id, int32 limit, Promise> promise) { if (!G()->use_message_database()) { return promise.set_error(Status::Error(500, "There is no message database")); } if (td_->auth_manager_->is_bot()) { return promise.set_error(Status::Error(500, "Bots have no notifications")); } CHECK(dialog_id.is_valid()); CHECK(group_id.is_valid()); CHECK(!from_message_id.is_scheduled()); CHECK(limit > 0); auto d = get_dialog(dialog_id); CHECK(d != nullptr); if (!is_dialog_notification_group_id(d, group_id)) { return promise.set_value(vector()); } VLOG(notifications) << "Get " << limit << " message notifications from database in " << group_id << " from " << dialog_id << " from " << from_notification_id << "/" << from_message_id; bool from_mentions = d->notification_info->mention_notification_group_.has_group_id(group_id); if (d->notification_info->new_secret_chat_notification_id_.is_valid()) { CHECK(dialog_id.get_type() == DialogType::SecretChat); vector notifications; if (!from_mentions && d->notification_info->new_secret_chat_notification_id_.get() < from_notification_id.get()) { auto date = td_->contacts_manager_->get_secret_chat_date(dialog_id.get_secret_chat_id()); if (date <= 0) { remove_new_secret_chat_notification(d, true); } else { notifications.emplace_back(d->notification_info->new_secret_chat_notification_id_, date, false, create_new_secret_chat_notification()); } } return promise.set_value(std::move(notifications)); } do_get_message_notifications_from_database(d, from_mentions, from_notification_id, from_notification_id, from_message_id, limit, std::move(promise)); } void MessagesManager::do_get_message_notifications_from_database(Dialog *d, bool from_mentions, NotificationId initial_from_notification_id, NotificationId from_notification_id, MessageId from_message_id, int32 limit, Promise> promise) { CHECK(G()->use_message_database()); CHECK(!from_message_id.is_scheduled()); auto &group_info = get_notification_group_info(d, from_mentions); if (!group_info.is_valid() || group_info.is_removed_notification(from_notification_id, from_message_id) || (!from_mentions && from_message_id <= d->last_read_inbox_message_id)) { return promise.set_value(vector()); } auto dialog_id = d->dialog_id; auto new_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, from_mentions, initial_from_notification_id, limit, promise = std::move(promise)](Result> result) mutable { send_closure(actor_id, &MessagesManager::on_get_message_notifications_from_database, dialog_id, from_mentions, initial_from_notification_id, limit, std::move(result), std::move(promise)); }); auto *db = G()->td_db()->get_message_db_async(); if (!from_mentions) { VLOG(notifications) << "Trying to load " << limit << " messages with notifications in " << group_info.get_group_id() << '/' << dialog_id << " from " << from_notification_id; return db->get_messages_from_notification_id(d->dialog_id, from_notification_id, limit, std::move(new_promise)); } else { VLOG(notifications) << "Trying to load " << limit << " messages with unread mentions in " << group_info.get_group_id() << '/' << dialog_id << " from " << from_message_id; // ignore first_db_message_id, notifications can be nonconsecutive MessageDbMessagesQuery db_query; db_query.dialog_id = dialog_id; db_query.filter = MessageSearchFilter::UnreadMention; db_query.from_message_id = from_message_id; db_query.offset = 0; db_query.limit = limit; return db->get_messages(db_query, std::move(new_promise)); } } void MessagesManager::on_get_message_notifications_from_database(DialogId dialog_id, bool from_mentions, NotificationId initial_from_notification_id, int32 limit, Result> result, Promise> promise) { G()->ignore_result_if_closing(result); if (result.is_error()) { return promise.set_error(result.move_as_error()); } Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); auto &group_info = get_notification_group_info(d, from_mentions); if (!group_info.is_valid()) { return promise.set_error(Status::Error("Notification group was deleted")); } auto messages = result.move_as_ok(); vector res; res.reserve(messages.size()); NotificationId from_notification_id; MessageId from_message_id; VLOG(notifications) << "Loaded " << messages.size() << " messages with notifications in " << group_info.get_group_id() << '/' << dialog_id << " from database"; for (auto &message : messages) { auto m = on_get_message_from_database(d, message, false, "on_get_message_notifications_from_database"); if (m == nullptr) { VLOG(notifications) << "Receive from database a broken message"; continue; } auto notification_id = m->notification_id.is_valid() ? m->notification_id : m->removed_notification_id; if (!notification_id.is_valid()) { if (from_mentions) { VLOG(notifications) << "Receive " << m->message_id << " with unread mention, but without notification"; from_notification_id = NotificationId(); // stop requesting database } else { LOG(ERROR) << "Can't find notification identifier for " << m->message_id << " in " << d->dialog_id << " with from_mentions = " << from_mentions; } continue; } CHECK(m->message_id.is_valid()); bool is_correct = true; if (from_notification_id.is_valid() && notification_id.get() >= from_notification_id.get()) { LOG(ERROR) << "Receive " << m->message_id << "/" << notification_id << " after " << from_message_id << "/" << from_notification_id; is_correct = false; } else { from_notification_id = notification_id; } if (from_message_id.is_valid() && m->message_id >= from_message_id) { LOG(ERROR) << "Receive " << m->message_id << "/" << notification_id << " after " << from_message_id << "/" << from_notification_id; is_correct = false; } else { from_message_id = m->message_id; } if (group_info.is_removed_notification(notification_id, m->message_id) || (!from_mentions && m->message_id <= d->last_read_inbox_message_id)) { // if message still has notification_id, but it was removed via max_removed_notification_id, // or max_removed_message_id, or last_read_inbox_message_id, // then there will be no more messages with active notifications from_notification_id = NotificationId(); // stop requesting database break; } if (!m->notification_id.is_valid()) { // notification_id can be empty if it is deleted in memory, but not in the database VLOG(notifications) << "Receive from database " << m->message_id << " with removed " << m->removed_notification_id; continue; } if (is_from_mention_notification_group(m) != from_mentions) { VLOG(notifications) << "Receive from database " << m->message_id << " with " << m->notification_id << " from another category"; continue; } if (!is_message_notification_active(d, m)) { CHECK(from_mentions); CHECK(!m->contains_unread_mention); // skip read mentions continue; } if (is_correct) { // skip mention messages returned among unread messages CHECK(m->date > 0); res.emplace_back(m->notification_id, m->date, m->disable_notification, create_new_message_notification(m->message_id, is_message_preview_enabled(d, m, from_mentions))); } else { remove_message_notification_id(d, m, true, false); on_message_changed(d, m, false, "on_get_message_notifications_from_database"); } } if (!res.empty() || !from_notification_id.is_valid() || static_cast(limit) > messages.size()) { if (from_mentions) { try_add_pinned_message_notification(d, res, initial_from_notification_id, limit); } std::reverse(res.begin(), res.end()); return promise.set_value(std::move(res)); } // try again from adjusted from_notification_id and from_message_id do_get_message_notifications_from_database(d, from_mentions, initial_from_notification_id, from_notification_id, from_message_id, limit, std::move(promise)); } void MessagesManager::remove_message_notification(DialogId dialog_id, NotificationGroupId group_id, NotificationId notification_id) { Dialog *d = get_dialog_force(dialog_id, "remove_message_notification"); if (!is_dialog_notification_group_id(d, group_id)) { LOG(ERROR) << "There is no " << group_id << " in " << dialog_id; return; } if (notification_id == NotificationId::max() || !notification_id.is_valid()) { return; // there can be no notification with this ID } bool from_mentions = d->notification_info->mention_notification_group_.has_group_id(group_id); if (d->notification_info->new_secret_chat_notification_id_.is_valid()) { if (!from_mentions && d->notification_info->new_secret_chat_notification_id_ == notification_id) { return remove_new_secret_chat_notification(d, false); } return; } auto it = d->notification_info->notification_id_to_message_id_.find(notification_id); if (it != d->notification_info->notification_id_to_message_id_.end()) { auto m = get_message(d, it->second); CHECK(m != nullptr); CHECK(m->notification_id == notification_id); CHECK(!m->message_id.is_scheduled()); if (is_from_mention_notification_group(m) == from_mentions && is_message_notification_active(d, m)) { remove_message_notification_id(d, m, false, false); } return; } if (G()->use_message_database()) { G()->td_db()->get_message_db_async()->get_messages_from_notification_id( dialog_id, NotificationId(notification_id.get() + 1), 1, PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, from_mentions, notification_id](vector result) { send_closure(actor_id, &MessagesManager::do_remove_message_notification, dialog_id, from_mentions, notification_id, std::move(result)); })); } } void MessagesManager::remove_message_notifications_by_message_ids(DialogId dialog_id, const vector &message_ids) { VLOG(notifications) << "Trying to remove notification about " << message_ids << " in " << dialog_id; Dialog *d = get_dialog_force(dialog_id, "remove_message_notifications_by_message_ids"); if (d == nullptr || d->notification_info == nullptr) { return; } bool need_update_dialog_pos = false; vector deleted_message_ids; for (auto message_id : message_ids) { CHECK(!message_id.is_scheduled()); // can't remove just notification_id, because total_count will stay wrong after restart // delete whole message auto message = delete_message(d, message_id, true, &need_update_dialog_pos, "remove_message_notifications_by_message_ids"); if (message == nullptr) { LOG(INFO) << "Can't delete " << message_id << " because it is not found"; // call synchronously to remove them before ProcessPush returns td_->notification_manager_->remove_temporary_notification_by_object_id( d->notification_info->message_notification_group_.get_group_id(), message_id, true, "remove_message_notifications_by_message_ids"); td_->notification_manager_->remove_temporary_notification_by_object_id( d->notification_info->mention_notification_group_.get_group_id(), message_id, true, "remove_message_notifications_by_message_ids"); continue; } deleted_message_ids.push_back(message->message_id.get()); } if (need_update_dialog_pos) { send_update_chat_last_message(d, "remove_message_notifications_by_message_ids"); } send_update_delete_messages(dialog_id, std::move(deleted_message_ids), true); } void MessagesManager::do_remove_message_notification(DialogId dialog_id, bool from_mentions, NotificationId notification_id, vector result) { if (result.empty() || G()->close_flag()) { return; } CHECK(result.size() == 1); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); auto m = on_get_message_from_database(d, result[0], false, "do_remove_message_notification"); if (m != nullptr && m->notification_id == notification_id && is_from_mention_notification_group(m) == from_mentions && is_message_notification_active(d, m)) { remove_message_notification_id(d, m, false, false); } } void MessagesManager::remove_message_notifications(DialogId dialog_id, NotificationGroupId group_id, NotificationId max_notification_id, MessageId max_message_id) { Dialog *d = get_dialog_force(dialog_id, "remove_message_notifications"); if (!is_dialog_notification_group_id(d, group_id)) { LOG(ERROR) << "There is no " << group_id << " in " << dialog_id; return; } if (!max_notification_id.is_valid()) { return; } CHECK(!max_message_id.is_scheduled()); bool from_mentions = d->notification_info->mention_notification_group_.has_group_id(group_id); if (d->notification_info->new_secret_chat_notification_id_.is_valid()) { if (!from_mentions && d->notification_info->new_secret_chat_notification_id_.get() <= max_notification_id.get()) { return remove_new_secret_chat_notification(d, false); } return; } auto &group_info = get_notification_group_info(d, from_mentions); if (group_info.set_max_removed_notification_id(max_notification_id, max_message_id.get_prev_server_message_id(), "remove_message_notifications")) { on_dialog_updated(dialog_id, "remove_message_notifications"); } } int32 MessagesManager::get_dialog_pending_notification_count(const Dialog *d, bool from_mentions) const { CHECK(!td_->auth_manager_->is_bot()); CHECK(d != nullptr); auto notification_info = d->notification_info.get(); CHECK(notification_info != nullptr); if (from_mentions) { bool has_pinned_message = notification_info->pinned_message_notification_message_id_.is_valid() && notification_info->pinned_message_notification_message_id_ <= d->last_new_message_id; return d->unread_mention_count + static_cast(has_pinned_message); } else { if (notification_info->new_secret_chat_notification_id_.is_valid()) { return 1; } if (is_dialog_muted(d)) { return narrow_cast(notification_info->pending_new_message_notifications_.size()); // usually 0 } return d->server_unread_count + d->local_unread_count; } } void MessagesManager::update_dialog_mention_notification_count(const Dialog *d) { CHECK(d != nullptr); if (td_->auth_manager_->is_bot() || d->notification_info == nullptr || !d->notification_info->mention_notification_group_.is_valid()) { return; } auto total_count = get_dialog_pending_notification_count(d, true) - static_cast(d->notification_info->pending_new_mention_notifications_.size()); if (total_count < 0) { LOG(ERROR) << "Total mention notification count is " << total_count << " in " << d->dialog_id << " with " << d->notification_info->pending_new_mention_notifications_ << " pending new mention notifications"; total_count = 0; } send_closure_later(G()->notification_manager(), &NotificationManager::set_notification_total_count, d->notification_info->mention_notification_group_.get_group_id(), total_count); } bool MessagesManager::is_message_notification_disabled(const Dialog *d, const Message *m) const { CHECK(d != nullptr); CHECK(m != nullptr); if (!has_incoming_notification(d->dialog_id, m) || td_->auth_manager_->is_bot()) { return true; } if (m->is_from_scheduled && d->dialog_id != get_my_dialog_id() && td_->option_manager_->get_option_boolean("disable_sent_scheduled_message_notifications")) { return true; } if (m->forward_info != nullptr && m->forward_info->is_imported) { return true; } switch (m->content->get_type()) { case MessageContentType::ChatDeleteHistory: case MessageContentType::ChatMigrateTo: case MessageContentType::Unsupported: case MessageContentType::ExpiredPhoto: case MessageContentType::ExpiredVideo: case MessageContentType::PassportDataSent: case MessageContentType::PassportDataReceived: case MessageContentType::WebViewDataSent: case MessageContentType::WebViewDataReceived: case MessageContentType::GiveawayLaunch: VLOG(notifications) << "Disable notification for " << m->message_id << " in " << d->dialog_id << " with content of type " << m->content->get_type(); return true; case MessageContentType::ContactRegistered: if (m->disable_notification) { return true; } break; default: break; } return is_dialog_message_notification_disabled(d->dialog_id, m->date); } bool MessagesManager::is_dialog_message_notification_disabled(DialogId dialog_id, int32 message_date) const { switch (dialog_id.get_type()) { case DialogType::User: break; case DialogType::Chat: if (!td_->contacts_manager_->get_chat_is_active(dialog_id.get_chat_id())) { return true; } break; case DialogType::Channel: if (!td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).is_member() || message_date < td_->contacts_manager_->get_channel_date(dialog_id.get_channel_id())) { return true; } break; case DialogType::SecretChat: if (td_->contacts_manager_->get_secret_chat_state(dialog_id.get_secret_chat_id()) == SecretChatState::Closed) { return true; } break; case DialogType::None: default: UNREACHABLE(); } if (message_date < authorization_date_) { return true; } return false; } bool MessagesManager::may_need_message_notification(const Dialog *d, const Message *m) const { CHECK(d != nullptr); CHECK(m != nullptr); CHECK(m->message_id.is_valid()); if (is_message_notification_disabled(d, m)) { return false; } if (is_from_mention_notification_group(m)) { return true; } bool have_settings; int32 mute_until; std::tie(have_settings, mute_until) = get_dialog_mute_until(d->dialog_id, d); return !have_settings || mute_until <= m->date; } bool MessagesManager::add_new_message_notification(Dialog *d, Message *m, bool force) { CHECK(d != nullptr); CHECK(m != nullptr); CHECK(m->message_id.is_valid()); if (!force && d->notification_info != nullptr) { if (d->notification_info->message_notification_group_.is_valid()) { send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notifications, d->notification_info->message_notification_group_.get_group_id(), "add_new_message_notification 1"); } if (d->notification_info->mention_notification_group_.is_valid()) { send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notifications, d->notification_info->mention_notification_group_.get_group_id(), "add_new_message_notification 2"); } } CHECK(!m->notification_id.is_valid()); if (is_message_notification_disabled(d, m)) { return false; } auto from_mentions = is_from_mention_notification_group(m); bool is_pinned = m->content->get_type() == MessageContentType::PinMessage; bool is_active = from_mentions ? m->contains_unread_mention || is_pinned : m->message_id > d->last_read_inbox_message_id; if (is_active) { auto &group_info = get_notification_group_info(d, from_mentions); if (group_info.is_removed_object_id(m->message_id)) { is_active = false; } } if (!is_active) { VLOG(notifications) << "Disable inactive notification for " << m->message_id << " in " << d->dialog_id; if (is_pinned) { remove_dialog_pinned_message_notification(d, "add_new_message_notification"); } return false; } VLOG(notifications) << "Trying to " << (force ? "forcely " : "") << "add new message notification for " << m->message_id << " in " << d->dialog_id << (m->disable_notification ? " silently" : " with sound"); DialogId settings_dialog_id = d->dialog_id; Dialog *settings_dialog = d; if (is_from_mention_notification_group(m)) { // have a mention, so use notification settings from the dialog with the sender auto sender_dialog_id = get_message_sender(m); if (sender_dialog_id.is_valid()) { settings_dialog_id = sender_dialog_id; settings_dialog = get_dialog_force(settings_dialog_id, "add_new_message_notification"); } } bool have_settings; int32 mute_until; std::tie(have_settings, mute_until) = get_dialog_mute_until(settings_dialog_id, settings_dialog); if (mute_until > m->date && (have_settings || force)) { VLOG(notifications) << "Disable notification, because " << settings_dialog_id << " is muted"; if (is_pinned) { remove_dialog_pinned_message_notification(d, "add_new_message_notification"); } return false; } MessageId missing_pinned_message_id; if (is_pinned) { auto message_id = get_message_content_pinned_message_id(m->content.get()); if (message_id.is_valid() && !have_message_force(d, message_id, force ? "add_new_message_notification force" : "add_new_message_notification not force")) { missing_pinned_message_id = message_id; } } auto notification_info = add_dialog_notification_info(d); auto &pending_notifications = from_mentions ? notification_info->pending_new_mention_notifications_ : notification_info->pending_new_message_notifications_; if (!force && (!have_settings || !pending_notifications.empty() || missing_pinned_message_id.is_valid())) { VLOG(notifications) << "Delay new message notification for " << m->message_id << " in " << d->dialog_id << " with " << pending_notifications.size() << " already waiting messages"; if (pending_notifications.empty()) { VLOG(notifications) << "Create FlushPendingNewMessageNotificationsSleepActor for " << d->dialog_id; create_actor("FlushPendingNewMessageNotificationsSleepActor", 5.0, PromiseCreator::lambda([actor_id = actor_id(this), dialog_id = d->dialog_id, from_mentions](Result result) { VLOG(notifications) << "Pending notifications timeout in " << dialog_id << " has expired"; send_closure(actor_id, &MessagesManager::flush_pending_new_message_notifications, dialog_id, from_mentions, DialogId()); })) .release(); } auto last_settings_dialog_id = (pending_notifications.empty() ? DialogId() : pending_notifications.back().first); pending_notifications.emplace_back((have_settings ? DialogId() : settings_dialog_id), m->message_id); if (!have_settings && last_settings_dialog_id != settings_dialog_id) { VLOG(notifications) << "Fetch notification settings for " << settings_dialog_id; auto promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id = d->dialog_id, from_mentions, settings_dialog_id](Result result) { send_closure(actor_id, &MessagesManager::flush_pending_new_message_notifications, dialog_id, from_mentions, settings_dialog_id); }); reload_dialog_notification_settings(settings_dialog_id, std::move(promise), "add_new_message_notification"); } if (missing_pinned_message_id.is_valid()) { VLOG(notifications) << "Fetch pinned " << missing_pinned_message_id; auto promise = PromiseCreator::lambda( [actor_id = actor_id(this), dialog_id = d->dialog_id, from_mentions](Result result) { send_closure(actor_id, &MessagesManager::flush_pending_new_message_notifications, dialog_id, from_mentions, dialog_id); }); get_message_from_server({d->dialog_id, missing_pinned_message_id}, std::move(promise), "add_new_message_notification"); } return false; } LOG_IF(WARNING, !have_settings) << "Have no notification settings for " << settings_dialog_id << ", but forced to send notification about " << m->message_id << " in " << d->dialog_id; auto &group_info = get_notification_group_info(d, m); auto notification_group_id = get_dialog_notification_group_id(d->dialog_id, group_info); if (!notification_group_id.is_valid()) { return false; } // if !force, then add_message_to_dialog will add the correspondence m->notification_id = get_next_notification_id(notification_info, notification_group_id, force ? m->message_id : MessageId()); if (!m->notification_id.is_valid()) { return false; } set_dialog_last_notification_checked(d->dialog_id, group_info, m->date, m->notification_id, "add_new_message_notification 3"); if (is_pinned) { set_dialog_pinned_message_notification(d, from_mentions ? m->message_id : MessageId(), "add_new_message_notification"); } if (!m->notification_id.is_valid()) { // protection from accidental notification_id removal in set_dialog_pinned_message_notification return false; } VLOG(notifications) << "Create " << m->notification_id << " with " << m->message_id << " in " << group_info.get_group_id() << '/' << d->dialog_id; int32 min_delay_ms = 0; if (need_delay_message_content_notification(m->content.get(), td_->contacts_manager_->get_my_id())) { min_delay_ms = 3000; // 3 seconds } else if (td_->is_online() && d->open_count > 0) { min_delay_ms = 1000; // 1 second } auto ringtone_id = get_dialog_notification_ringtone_id(settings_dialog_id, settings_dialog); bool is_silent = m->disable_notification || m->message_id <= notification_info->max_push_notification_message_id_; send_closure_later(G()->notification_manager(), &NotificationManager::add_notification, notification_group_id, from_mentions ? NotificationGroupType::Mentions : NotificationGroupType::Messages, d->dialog_id, m->date, settings_dialog_id, m->disable_notification, is_silent ? 0 : ringtone_id, min_delay_ms, m->notification_id, create_new_message_notification(m->message_id, is_message_preview_enabled(d, m, from_mentions)), "add_new_message_notification"); return true; } void MessagesManager::flush_pending_new_message_notifications(DialogId dialog_id, bool from_mentions, DialogId settings_dialog_id) { // flush pending notifications even while closing CHECK(!td_->auth_manager_->is_bot()); auto d = get_dialog(dialog_id); CHECK(d != nullptr); if (d->notification_info == nullptr) { return; } auto &pending_notifications = from_mentions ? d->notification_info->pending_new_mention_notifications_ : d->notification_info->pending_new_message_notifications_; if (pending_notifications.empty()) { VLOG(notifications) << "Have no pending notifications in " << dialog_id << " to flush"; return; } for (auto &it : pending_notifications) { if (it.first == settings_dialog_id || !settings_dialog_id.is_valid()) { it.first = DialogId(); } } VLOG(notifications) << "Flush pending notifications in " << dialog_id << " because of received notification settings in " << settings_dialog_id; auto it = pending_notifications.begin(); while (it != pending_notifications.end() && it->first == DialogId()) { auto m = get_message(d, it->second); if (m == nullptr) { VLOG(notifications) << "Can't find " << it->second << " in " << dialog_id << " with pending notification"; } else if (add_new_message_notification(d, m, true)) { on_message_changed(d, m, false, "flush_pending_new_message_notifications"); } ++it; } if (it == pending_notifications.end()) { reset_to_empty(pending_notifications); } else { pending_notifications.erase(pending_notifications.begin(), it); } } void MessagesManager::remove_all_dialog_notifications(Dialog *d, bool from_mentions, const char *source) { // removes up to group_info.get_last_notification_id() CHECK(!td_->auth_manager_->is_bot()); if (d->notification_info == nullptr) { return; } NotificationGroupInfo &group_info = get_notification_group_info(d, from_mentions); if (group_info.is_valid() && group_info.get_last_notification_id().is_valid()) { auto last_notification_id = group_info.get_last_notification_id(); group_info.set_max_removed_notification_id(last_notification_id, d->notification_info->max_push_notification_message_id_, source); on_dialog_updated(d->dialog_id, source); if (!d->notification_info->pending_new_message_notifications_.empty()) { for (auto &it : d->notification_info->pending_new_message_notifications_) { it.first = DialogId(); } flush_pending_new_message_notifications(d->dialog_id, from_mentions, DialogId(UserId(static_cast(2)))); } // remove_message_notifications will be called by NotificationManager send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification_group, group_info.get_group_id(), last_notification_id, MessageId(), 0, true, Promise()); d->notification_info->new_secret_chat_notification_id_ = NotificationId(); set_dialog_last_notification(d->dialog_id, group_info, 0, NotificationId(), source); // just in case } } void MessagesManager::remove_message_dialog_notifications(Dialog *d, MessageId max_message_id, bool from_mentions, const char *source) { // removes up to max_message_id CHECK(!td_->auth_manager_->is_bot()); CHECK(!max_message_id.is_scheduled()); if (d->notification_info == nullptr) { return; } NotificationGroupInfo &group_info = get_notification_group_info(d, from_mentions); if (!group_info.is_valid()) { return; } VLOG(notifications) << "Remove message dialog notifications in " << group_info.get_group_id() << '/' << d->dialog_id << " up to " << max_message_id << " from " << source; if (!d->notification_info->pending_new_message_notifications_.empty()) { for (auto &it : d->notification_info->pending_new_message_notifications_) { if (it.second <= max_message_id) { it.first = DialogId(); } } flush_pending_new_message_notifications(d->dialog_id, from_mentions, DialogId(UserId(static_cast(3)))); } auto max_notification_message_id = max_message_id; if (d->last_message_id.is_valid() && max_notification_message_id >= d->last_message_id) { max_notification_message_id = d->last_message_id; set_dialog_last_notification(d->dialog_id, group_info, 0, NotificationId(), "remove_message_dialog_notifications 1"); } else if (max_notification_message_id == MessageId::max()) { max_notification_message_id = get_next_local_message_id(d); set_dialog_last_notification(d->dialog_id, group_info, 0, NotificationId(), "remove_message_dialog_notifications 2"); } else { LOG(FATAL) << "TODO support notification deletion up to " << max_message_id << " if it would be ever needed"; } send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification_group, group_info.get_group_id(), NotificationId(), max_notification_message_id, 0, true, Promise()); } void MessagesManager::send_update_message_send_succeeded(Dialog *d, MessageId old_message_id, const Message *m, bool *need_update_dialog_pos) { CHECK(m != nullptr); CHECK(d != nullptr); CHECK(d->is_update_new_chat_sent); if (!td_->auth_manager_->is_bot()) { yet_unsent_message_full_id_to_persistent_message_id_.emplace({d->dialog_id, old_message_id}, m->message_id); auto message = delete_message(d, m->message_id, false, need_update_dialog_pos, "send_update_message_send_succeeded"); if (message != nullptr) { send_update_delete_messages(d->dialog_id, {message->message_id.get()}, false); } } send_closure(G()->td(), &Td::send_update, td_api::make_object( get_message_object(d->dialog_id, m, "send_update_message_send_succeeded"), old_message_id.get())); } void MessagesManager::send_update_message_content(const Dialog *d, Message *m, bool is_message_in_dialog, const char *source) { CHECK(d != nullptr); CHECK(m != nullptr); if (is_message_in_dialog) { delete_bot_command_message_id(d->dialog_id, m->message_id); try_add_bot_command_message_id(d->dialog_id, m); reregister_message_reply(d->dialog_id, m); update_message_max_reply_media_timestamp(d, m, false); // because the message reply can be just registered update_message_max_own_media_timestamp(d, m); } send_update_message_content_impl(d->dialog_id, m, source); } void MessagesManager::send_update_message_content_impl(DialogId dialog_id, const Message *m, const char *source) const { CHECK(m != nullptr); if (!m->is_update_sent) { LOG(INFO) << "Skip updateMessageContent for " << m->message_id << " in " << dialog_id << " from " << source; return; } LOG(INFO) << "Send updateMessageContent for " << m->message_id << " in " << dialog_id << " from " << source; send_closure(G()->td(), &Td::send_update, td_api::make_object(get_chat_id_object(dialog_id, "updateMessageContent"), m->message_id.get(), get_message_message_content_object(dialog_id, m))); } void MessagesManager::send_update_message_edited(DialogId dialog_id, const Message *m) { CHECK(m != nullptr); cancel_dialog_action(dialog_id, m); auto edit_date = m->hide_edit_date ? 0 : m->edit_date; send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(dialog_id, "updateMessageEdited"), m->message_id.get(), edit_date, get_reply_markup_object(td_->contacts_manager_.get(), m->reply_markup))); } void MessagesManager::send_update_message_interaction_info(DialogId dialog_id, const Message *m) const { CHECK(m != nullptr); if (td_->auth_manager_->is_bot() || !m->is_update_sent) { return; } send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(dialog_id, "updateMessageInteractionInfo"), m->message_id.get(), get_message_interaction_info_object(dialog_id, m))); } void MessagesManager::send_update_message_unread_reactions(DialogId dialog_id, const Message *m, int32 unread_reaction_count) const { CHECK(m != nullptr); if (td_->auth_manager_->is_bot()) { return; } if (!m->is_update_sent) { LOG(INFO) << "Update unread reaction message count in " << dialog_id << " to " << unread_reaction_count; send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(dialog_id, "updateChatUnreadReactionCount"), unread_reaction_count)); return; } send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(dialog_id, "updateMessageUnreadReactions"), m->message_id.get(), get_unread_reactions_object(dialog_id, m), unread_reaction_count)); } void MessagesManager::send_update_message_live_location_viewed(MessageFullId message_full_id) { CHECK(get_message(message_full_id) != nullptr); send_closure(G()->td(), &Td::send_update, td_api::make_object(message_full_id.get_dialog_id().get(), message_full_id.get_message_id().get())); } void MessagesManager::send_update_delete_messages(DialogId dialog_id, vector &&message_ids, bool is_permanent) const { if (message_ids.empty()) { return; } LOG_CHECK(have_dialog(dialog_id)) << "Wrong " << dialog_id << " in send_update_delete_messages"; send_closure(G()->td(), &Td::send_update, td_api::make_object(get_chat_id_object(dialog_id, "updateDeleteMessages"), std::move(message_ids), is_permanent, false)); } void MessagesManager::send_update_new_chat(Dialog *d) { CHECK(d != nullptr); CHECK(d->messages.empty()); if ((d->dialog_id.get_type() == DialogType::User || d->dialog_id.get_type() == DialogType::SecretChat) && td_->auth_manager_->is_bot()) { (void)get_dialog_photo(d->dialog_id); // to apply pending user photo } d->is_update_new_chat_being_sent = true; auto chat_object = get_chat_object(d); bool has_action_bar = chat_object->action_bar_ != nullptr; bool has_background = chat_object->background_ != nullptr; bool has_theme = !chat_object->theme_name_.empty(); d->last_sent_has_scheduled_messages = chat_object->has_scheduled_messages_; send_closure(G()->td(), &Td::send_update, td_api::make_object(std::move(chat_object))); d->is_update_new_chat_sent = true; d->is_update_new_chat_being_sent = false; if (has_action_bar) { send_update_secret_chats_with_user_action_bar(d); } if (has_background) { send_update_secret_chats_with_user_background(d); } if (has_theme) { send_update_secret_chats_with_user_theme(d); } } bool MessagesManager::need_hide_dialog_draft_message(const Dialog *d) const { return get_dialog_view_as_topics(d) || can_send_message(d->dialog_id).is_error(); } void MessagesManager::send_update_chat_draft_message(const Dialog *d) { if (td_->auth_manager_->is_bot()) { // just in case return; } CHECK(d != nullptr); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_draft_message"; if (d->draft_message == nullptr || !need_hide_dialog_draft_message(d)) { send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateChatDraftMessage"), get_draft_message_object(td_, d->draft_message), get_chat_positions_object(d))); } } void MessagesManager::send_update_chat_last_message(Dialog *d, const char *source) { update_dialog_pos(d, source, false); send_update_chat_last_message_impl(d, source); } void MessagesManager::send_update_chat_last_message_impl(const Dialog *d, const char *source) const { if (td_->auth_manager_->is_bot()) { return; } CHECK(d != nullptr); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_last_message from " << source; LOG(INFO) << "Send updateChatLastMessage in " << d->dialog_id << " to " << d->last_message_id << " from " << source; const auto *m = get_message(d, d->last_message_id); auto message_object = get_message_object(d->dialog_id, m, "send_update_chat_last_message_impl"); auto positions_object = get_chat_positions_object(d); auto update = td_api::make_object(get_chat_id_object(d->dialog_id, "updateChatLastMessage"), std::move(message_object), std::move(positions_object)); send_closure(G()->td(), &Td::send_update, std::move(update)); } void MessagesManager::send_update_unread_message_count(DialogList &list, DialogId dialog_id, bool force, const char *source, bool from_database) { if (td_->auth_manager_->is_bot() || !G()->use_message_database()) { return; } auto dialog_list_id = list.dialog_list_id; CHECK(list.is_message_unread_count_inited_); if (list.unread_message_muted_count_ < 0 || list.unread_message_muted_count_ > list.unread_message_total_count_) { if (!list.need_unread_count_recalc_) { LOG(ERROR) << "Unread message count became invalid in " << dialog_list_id << ": " << list.unread_message_total_count_ << '/' << list.unread_message_total_count_ - list.unread_message_muted_count_ << " from " << source << " and " << dialog_id; } if (list.unread_message_muted_count_ < 0) { list.unread_message_muted_count_ = 0; } if (list.unread_message_muted_count_ > list.unread_message_total_count_) { list.unread_message_total_count_ = list.unread_message_muted_count_; } } if (!from_database) { LOG(INFO) << "Save unread message count in " << dialog_list_id; G()->td_db()->get_binlog_pmc()->set( PSTRING() << "unread_message_count" << dialog_list_id.get(), PSTRING() << list.unread_message_total_count_ << ' ' << list.unread_message_muted_count_); } int32 unread_unmuted_count = list.unread_message_total_count_ - list.unread_message_muted_count_; if (!force && running_get_difference_) { LOG(INFO) << "Postpone updateUnreadMessageCount in " << dialog_list_id << " to " << list.unread_message_total_count_ << '/' << unread_unmuted_count << " from " << source << " and " << dialog_id; postponed_unread_message_count_updates_.insert(dialog_list_id); } else { postponed_unread_message_count_updates_.erase(dialog_list_id); LOG(INFO) << "Send updateUnreadMessageCount in " << dialog_list_id << " to " << list.unread_message_total_count_ << '/' << unread_unmuted_count << " from " << source << " and " << dialog_id; send_closure(G()->td(), &Td::send_update, get_update_unread_message_count_object(list)); } } void MessagesManager::send_update_unread_chat_count(DialogList &list, DialogId dialog_id, bool force, const char *source, bool from_database) { if (td_->auth_manager_->is_bot() || !G()->use_message_database()) { return; } auto dialog_list_id = list.dialog_list_id; CHECK(list.is_dialog_unread_count_inited_); if (list.unread_dialog_muted_marked_count_ < 0 || list.unread_dialog_marked_count_ < list.unread_dialog_muted_marked_count_ || list.unread_dialog_muted_count_ < list.unread_dialog_muted_marked_count_ || list.unread_dialog_total_count_ + list.unread_dialog_muted_marked_count_ < list.unread_dialog_muted_count_ + list.unread_dialog_marked_count_) { if (!list.need_unread_count_recalc_) { LOG(ERROR) << "Unread chat count became invalid in " << dialog_list_id << ": " << list.unread_dialog_total_count_ << '/' << list.unread_dialog_total_count_ - list.unread_dialog_muted_count_ << '/' << list.unread_dialog_marked_count_ << '/' << list.unread_dialog_marked_count_ - list.unread_dialog_muted_marked_count_ << " from " << source << " and " << dialog_id; } if (list.unread_dialog_muted_marked_count_ < 0) { list.unread_dialog_muted_marked_count_ = 0; } if (list.unread_dialog_marked_count_ < list.unread_dialog_muted_marked_count_) { list.unread_dialog_marked_count_ = list.unread_dialog_muted_marked_count_; } if (list.unread_dialog_muted_count_ < list.unread_dialog_muted_marked_count_) { list.unread_dialog_muted_count_ = list.unread_dialog_muted_marked_count_; } if (list.unread_dialog_total_count_ + list.unread_dialog_muted_marked_count_ < list.unread_dialog_muted_count_ + list.unread_dialog_marked_count_) { list.unread_dialog_total_count_ = list.unread_dialog_muted_count_ + list.unread_dialog_marked_count_ - list.unread_dialog_muted_marked_count_; } } if (!from_database) { save_unread_chat_count(list); } bool need_postpone = !force && running_get_difference_; int32 unread_unmuted_count = list.unread_dialog_total_count_ - list.unread_dialog_muted_count_; int32 unread_unmuted_marked_count = list.unread_dialog_marked_count_ - list.unread_dialog_muted_marked_count_; LOG(INFO) << (need_postpone ? "Postpone" : "Send") << " updateUnreadChatCount in " << dialog_list_id << " to " << list.in_memory_dialog_total_count_ << '/' << list.server_dialog_total_count_ << '+' << list.secret_chat_total_count_ << '/' << list.unread_dialog_total_count_ << '/' << unread_unmuted_count << '/' << list.unread_dialog_marked_count_ << '/' << unread_unmuted_marked_count << " from " << source << " and " << dialog_id; if (need_postpone) { postponed_unread_chat_count_updates_.insert(dialog_list_id); } else { postponed_unread_chat_count_updates_.erase(dialog_list_id); send_closure(G()->td(), &Td::send_update, get_update_unread_chat_count_object(list)); } } void MessagesManager::save_unread_chat_count(const DialogList &list) { LOG(INFO) << "Save unread chat count in " << list.dialog_list_id; G()->td_db()->get_binlog_pmc()->set( PSTRING() << "unread_dialog_count" << list.dialog_list_id.get(), PSTRING() << list.unread_dialog_total_count_ << ' ' << list.unread_dialog_muted_count_ << ' ' << list.unread_dialog_marked_count_ << ' ' << list.unread_dialog_muted_marked_count_ << ' ' << list.server_dialog_total_count_ << ' ' << list.secret_chat_total_count_); } void MessagesManager::send_update_chat_read_inbox(const Dialog *d, bool force, const char *source) { if (td_->auth_manager_->is_bot()) { return; } CHECK(d != nullptr); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_read_inbox from " << source; if (!force && (running_get_difference_ || running_get_channel_difference(d->dialog_id) || get_channel_difference_to_log_event_id_.count(d->dialog_id) != 0 || (d->open_count > 0 && d->server_unread_count + d->local_unread_count > 0))) { LOG(INFO) << "Postpone updateChatReadInbox in " << d->dialog_id << "(" << get_dialog_title(d->dialog_id) << ") to " << d->server_unread_count << " + " << d->local_unread_count << " from " << source; postponed_chat_read_inbox_updates_.insert(d->dialog_id); if (d->open_count > 0) { send_update_chat_read_inbox_timeout_.add_timeout_in(d->dialog_id.get(), 0.1); } } else { postponed_chat_read_inbox_updates_.erase(d->dialog_id); LOG(INFO) << "Send updateChatReadInbox in " << d->dialog_id << "(" << get_dialog_title(d->dialog_id) << ") to " << d->server_unread_count << " + " << d->local_unread_count << " from " << source; send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateChatReadInbox"), d->last_read_inbox_message_id.get(), d->server_unread_count + d->local_unread_count)); } } void MessagesManager::send_update_chat_read_outbox(const Dialog *d) { if (td_->auth_manager_->is_bot()) { return; } CHECK(d != nullptr); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_read_outbox"; on_dialog_updated(d->dialog_id, "send_update_chat_read_outbox"); send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateChatReadOutbox"), d->last_read_outbox_message_id.get())); } void MessagesManager::send_update_chat_unread_mention_count(const Dialog *d) { if (td_->auth_manager_->is_bot()) { return; } CHECK(d != nullptr); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_unread_mention_count"; LOG(INFO) << "Update unread mention message count in " << d->dialog_id << " to " << d->unread_mention_count; on_dialog_updated(d->dialog_id, "send_update_chat_unread_mention_count"); send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateChatUnreadMentionCount"), d->unread_mention_count)); } void MessagesManager::send_update_chat_unread_reaction_count(const Dialog *d, const char *source) { if (td_->auth_manager_->is_bot()) { return; } CHECK(d != nullptr); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_unread_reaction_count from " << source; LOG(INFO) << "Update unread reaction message count in " << d->dialog_id << " to " << d->unread_reaction_count << " from " << source; on_dialog_updated(d->dialog_id, "send_update_chat_unread_reaction_count"); send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateChatUnreadReactionCount"), d->unread_reaction_count)); } void MessagesManager::send_update_chat_position(DialogListId dialog_list_id, const Dialog *d, const char *source) const { if (td_->auth_manager_->is_bot()) { return; } CHECK(d != nullptr); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_position"; LOG(INFO) << "Send updateChatPosition for " << d->dialog_id << " in " << dialog_list_id << " from " << source; auto position = get_chat_position_object(dialog_list_id, d); if (position == nullptr) { position = td_api::make_object(dialog_list_id.get_chat_list_object(), 0, false, nullptr); } send_closure(G()->td(), &Td::send_update, td_api::make_object(get_chat_id_object(d->dialog_id, "updateChatPosition"), std::move(position))); } void MessagesManager::send_update_chat_online_member_count(DialogId dialog_id, int32 online_member_count) const { if (td_->auth_manager_->is_bot()) { return; } send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(dialog_id, "updateChatOnlineMemberCount"), online_member_count)); } void MessagesManager::send_update_secret_chats_with_user_action_bar(const Dialog *d) const { if (td_->auth_manager_->is_bot()) { return; } if (d->dialog_id.get_type() != DialogType::User) { return; } td_->contacts_manager_->for_each_secret_chat_with_user( d->dialog_id.get_user_id(), [this, user_d = d](SecretChatId secret_chat_id) { DialogId dialog_id(secret_chat_id); auto secret_chat_d = get_dialog(dialog_id); // must not create the dialog if (secret_chat_d != nullptr && secret_chat_d->is_update_new_chat_sent) { send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(dialog_id, "updateChatActionBar"), get_chat_action_bar_object(user_d))); } }); } void MessagesManager::send_update_chat_action_bar(Dialog *d) { if (td_->auth_manager_->is_bot()) { return; } if (d->action_bar != nullptr && d->action_bar->is_empty()) { d->action_bar = nullptr; } CHECK(d != nullptr); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_action_bar"; on_dialog_updated(d->dialog_id, "send_update_chat_action_bar"); send_closure(G()->td(), &Td::send_update, td_api::make_object(get_chat_id_object(d->dialog_id, "updateChatActionBar"), get_chat_action_bar_object(d))); send_update_secret_chats_with_user_action_bar(d); } void MessagesManager::send_update_chat_available_reactions(const Dialog *d) { CHECK(d != nullptr); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_available_reactions"; auto available_reactions = get_dialog_active_reactions(d).get_chat_available_reactions_object(); send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateChatAvailableReactions"), std::move(available_reactions))); } void MessagesManager::send_update_secret_chats_with_user_background(const Dialog *d) const { if (td_->auth_manager_->is_bot()) { return; } if (d->dialog_id.get_type() != DialogType::User) { return; } td_->contacts_manager_->for_each_secret_chat_with_user( d->dialog_id.get_user_id(), [this, user_d = d](SecretChatId secret_chat_id) { DialogId dialog_id(secret_chat_id); auto secret_chat_d = get_dialog(dialog_id); // must not create the dialog if (secret_chat_d != nullptr && secret_chat_d->is_update_new_chat_sent) { send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(dialog_id, "updateChatBackground"), get_chat_background_object(user_d))); } }); } void MessagesManager::send_update_chat_background(const Dialog *d) { if (td_->auth_manager_->is_bot()) { return; } CHECK(d != nullptr); CHECK(d->dialog_id.get_type() != DialogType::SecretChat); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_background"; on_dialog_updated(d->dialog_id, "send_update_chat_background"); send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateChatBackground"), get_chat_background_object(d))); send_update_secret_chats_with_user_background(d); } void MessagesManager::send_update_secret_chats_with_user_theme(const Dialog *d) const { if (td_->auth_manager_->is_bot()) { return; } if (d->dialog_id.get_type() != DialogType::User) { return; } td_->contacts_manager_->for_each_secret_chat_with_user( d->dialog_id.get_user_id(), [this, user_d = d](SecretChatId secret_chat_id) { DialogId dialog_id(secret_chat_id); auto secret_chat_d = get_dialog(dialog_id); // must not create the dialog if (secret_chat_d != nullptr && secret_chat_d->is_update_new_chat_sent) { send_closure(G()->td(), &Td::send_update, td_api::make_object(get_chat_id_object(dialog_id, "updateChatTheme"), user_d->theme_name)); } }); } void MessagesManager::send_update_chat_theme(const Dialog *d) { if (td_->auth_manager_->is_bot()) { return; } CHECK(d != nullptr); CHECK(d->dialog_id.get_type() != DialogType::SecretChat); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_theme"; on_dialog_updated(d->dialog_id, "send_update_chat_theme"); send_closure( G()->td(), &Td::send_update, td_api::make_object(get_chat_id_object(d->dialog_id, "updateChatTheme"), d->theme_name)); send_update_secret_chats_with_user_theme(d); } void MessagesManager::send_update_chat_pending_join_requests(const Dialog *d) { if (td_->auth_manager_->is_bot()) { return; } CHECK(d != nullptr); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_pending_join_requests"; on_dialog_updated(d->dialog_id, "send_update_chat_pending_join_requests"); send_closure( G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateChatPendingJoinRequests"), get_chat_join_requests_info_object(d))); } void MessagesManager::send_update_chat_video_chat(const Dialog *d) { CHECK(d != nullptr); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_video_chat"; on_dialog_updated(d->dialog_id, "send_update_chat_video_chat"); send_closure(G()->td(), &Td::send_update, td_api::make_object(get_chat_id_object(d->dialog_id, "updateChatVideoChat"), get_video_chat_object(d))); } void MessagesManager::send_update_chat_message_sender(const Dialog *d) { CHECK(!td_->auth_manager_->is_bot()); CHECK(d != nullptr); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_message_sender"; send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateChatMessageSender"), get_default_message_sender_object(d))); } void MessagesManager::send_update_chat_message_auto_delete_time(const Dialog *d) { CHECK(d != nullptr); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_message_auto_delete_time"; on_dialog_updated(d->dialog_id, "send_update_chat_message_auto_delete_time"); send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateChatMessageAutoDeleteTime"), d->message_ttl.get_message_auto_delete_time_object())); } void MessagesManager::send_update_chat_has_scheduled_messages(Dialog *d, bool from_deletion) { if (td_->auth_manager_->is_bot()) { return; } if (!have_dialog_scheduled_messages_in_memory(d)) { if (d->has_scheduled_database_messages) { if (d->has_loaded_scheduled_messages_from_database) { set_dialog_has_scheduled_database_messages_impl(d, false); } else { CHECK(G()->use_message_database()); repair_dialog_scheduled_messages(d); } } if (d->has_scheduled_server_messages) { if (from_deletion && d->scheduled_messages_sync_generation > 0) { set_dialog_has_scheduled_server_messages(d, false); } else { d->last_repair_scheduled_messages_generation = 0; repair_dialog_scheduled_messages(d); } } } LOG(INFO) << "In " << d->dialog_id << " have scheduled messages on server = " << d->has_scheduled_server_messages << ", in database = " << d->has_scheduled_database_messages << " and in memory = " << have_dialog_scheduled_messages_in_memory(d) << "; was loaded from database = " << d->has_loaded_scheduled_messages_from_database; bool has_scheduled_messages = get_dialog_has_scheduled_messages(d); if (has_scheduled_messages == d->last_sent_has_scheduled_messages) { return; } d->last_sent_has_scheduled_messages = has_scheduled_messages; LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_has_scheduled_messages"; send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateChatHasScheduledMessages"), has_scheduled_messages)); } void MessagesManager::send_update_chat_action(DialogId dialog_id, MessageId top_thread_message_id, DialogId typing_dialog_id, const DialogAction &action) { if (td_->auth_manager_->is_bot()) { return; } LOG(DEBUG) << "Send " << action << " of " << typing_dialog_id << " in thread of " << top_thread_message_id << " in " << dialog_id; send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(dialog_id, "updateChatAction"), top_thread_message_id.get(), get_message_sender_object(td_, typing_dialog_id, "send_update_chat_action"), action.get_chat_action_object())); } void MessagesManager::on_send_message_get_quick_ack(int64 random_id) { auto it = being_sent_messages_.find(random_id); if (it == being_sent_messages_.end()) { LOG(ERROR) << "Receive quick ack about unknown message with random_id = " << random_id; return; } auto dialog_id = it->second.get_dialog_id(); auto message_id = it->second.get_message_id(); send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(dialog_id, "updateMessageSendAcknowledged"), message_id.get())); } bool MessagesManager::is_invalid_poll_message(const telegram_api::Message *message) { CHECK(message != nullptr); auto constructor_id = message->get_id(); if (constructor_id != telegram_api::message::ID) { return false; } auto media = static_cast(message)->media_.get(); if (media == nullptr || media->get_id() != telegram_api::messageMediaPoll::ID) { return false; } auto poll = static_cast(media)->poll_.get(); return !PollId(poll->id_).is_valid(); } void MessagesManager::check_send_message_result(int64 random_id, DialogId dialog_id, const telegram_api::Updates *updates_ptr, const char *source) { CHECK(updates_ptr != nullptr); CHECK(source != nullptr); auto sent_messages = UpdatesManager::get_new_messages(updates_ptr); auto sent_messages_random_ids = UpdatesManager::get_sent_messages_random_ids(updates_ptr); if (sent_messages.size() != 1u || sent_messages_random_ids.size() != 1u || *sent_messages_random_ids.begin() != random_id || DialogId::get_message_dialog_id(sent_messages[0].first) != dialog_id || is_invalid_poll_message(sent_messages[0].first)) { LOG(ERROR) << "Receive wrong result for sending message with random_id " << random_id << " from " << source << " to " << dialog_id << ": " << oneline(to_string(*updates_ptr)); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); if (dialog_id.get_type() == DialogType::Channel) { get_channel_difference(dialog_id, d->pts, 0, MessageId(), true, "check_send_message_result"); } else { td_->updates_manager_->schedule_get_difference("check_send_message_result"); } repair_dialog_scheduled_messages(d); } } void MessagesManager::update_reply_to_message_id(DialogId dialog_id, MessageId old_message_id, MessageId new_message_id, bool have_new_message, const char *source) { LOG(INFO) << "Update replies of " << MessageFullId{dialog_id, old_message_id} << " to " << new_message_id << " from " << source; auto it = replied_yet_unsent_messages_.find({dialog_id, old_message_id}); if (it == replied_yet_unsent_messages_.end()) { return; } CHECK(old_message_id.is_yet_unsent()); for (auto message_full_id : it->second) { auto reply_d = get_dialog(message_full_id.get_dialog_id()); CHECK(reply_d != nullptr); auto replied_m = get_message(reply_d, message_full_id.get_message_id()); CHECK(replied_m != nullptr); const auto *input_reply_to = get_message_input_reply_to(replied_m); CHECK(input_reply_to != nullptr); CHECK(input_reply_to->get_reply_message_full_id(reply_d->dialog_id) == MessageFullId(dialog_id, old_message_id)); update_message_reply_to_message_id(reply_d, replied_m, new_message_id, true); } if (have_new_message) { CHECK(!new_message_id.is_yet_unsent()); replied_by_yet_unsent_messages_[MessageFullId{dialog_id, new_message_id}] = static_cast(it->second.size()); } else { replied_by_yet_unsent_messages_.erase(MessageFullId{dialog_id, new_message_id}); } replied_yet_unsent_messages_.erase(it); } MessageFullId MessagesManager::on_send_message_success(int64 random_id, MessageId new_message_id, int32 date, int32 ttl_period, FileId new_file_id, const char *source) { CHECK(source != nullptr); // do not try to run getDifference from this function if (DROP_SEND_MESSAGE_UPDATES) { return {}; } if (!new_message_id.is_valid()) { LOG(ERROR) << "Receive " << new_message_id << " as sent message from " << source; on_send_message_fail( random_id, Status::Error(500, "Internal Server Error: receive invalid message identifier as sent message identifier")); return {}; } if (new_message_id.is_yet_unsent()) { LOG(ERROR) << "Receive " << new_message_id << " as sent message from " << source; on_send_message_fail(random_id, Status::Error(500, "Internal Server Error: receive yet unsent message as sent message")); return {}; } auto it = being_sent_messages_.find(random_id); if (it == being_sent_messages_.end()) { LOG(ERROR) << "Result from sendMessage for " << new_message_id << " with random_id " << random_id << " sent at " << date << " comes from " << source << " after updateNewMessageId, but was not discarded by PTS"; return {}; } auto dialog_id = it->second.get_dialog_id(); auto old_message_id = it->second.get_message_id(); if (new_message_id.is_local() && dialog_id.get_type() != DialogType::SecretChat) { LOG(ERROR) << "Receive " << new_message_id << " as sent message from " << source; on_send_message_fail(random_id, Status::Error(500, "Internal Server Error: receive local as sent message")); return {}; } being_sent_messages_.erase(it); // must be called before delete_message update_reply_to_message_id(dialog_id, old_message_id, new_message_id, true, "on_send_message_success"); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); bool need_update_dialog_pos = false; being_readded_message_id_ = {dialog_id, old_message_id}; auto sent_message = delete_message(d, old_message_id, false, &need_update_dialog_pos, source); if (sent_message == nullptr) { delete_sent_message_on_server(dialog_id, new_message_id, old_message_id); being_readded_message_id_ = MessageFullId(); return {}; } // imitation of update_message(d, sent_message.get(), std::move(new_message), false); if (date <= 0) { LOG(ERROR) << "Receive " << new_message_id << " in " << dialog_id << " with wrong date " << date << " from " << source; } else { LOG_CHECK(sent_message->date > 0) << old_message_id << ' ' << sent_message->message_id << ' ' << new_message_id << ' ' << sent_message->date << ' ' << date << ' ' << source; sent_message->date = date; CHECK(d->last_message_id != old_message_id); } sent_message->ttl_period = ttl_period; if (merge_message_content_file_id(td_, sent_message->content.get(), new_file_id)) { send_update_message_content(d, sent_message.get(), false, source); } if (old_message_id.is_valid() && new_message_id < old_message_id && !d->had_yet_unsent_message_id_overflow) { LOG(ERROR) << "Sent " << old_message_id << " to " << dialog_id << " as " << new_message_id; } const auto *input_reply_to = get_message_input_reply_to(sent_message.get()); if (input_reply_to != nullptr && input_reply_to->is_valid() && input_reply_to->get_reply_message_full_id(dialog_id).get_message_id().is_yet_unsent()) { set_message_reply(d, sent_message.get(), MessageInputReplyTo(), false); } sent_message->message_id = new_message_id; send_update_message_send_succeeded(d, old_message_id, sent_message.get(), &need_update_dialog_pos); bool need_update = true; Message *m = add_message_to_dialog(d, std::move(sent_message), false, true, &need_update, &need_update_dialog_pos, source); if (need_update_dialog_pos) { send_update_chat_last_message(d, source); } if (m == nullptr) { if (!(old_message_id.is_valid() && new_message_id < old_message_id) && !(ttl_period > 0 && date + ttl_period <= G()->server_time())) { // if message ID has decreased, which could happen if some messages were lost, // or the message has already been deleted after auto-delete timer expired, then the error is expected LOG(ERROR) << "Failed to add just sent " << old_message_id << " to " << dialog_id << " as " << new_message_id << " from " << source << ": " << debug_add_message_to_dialog_fail_reason_; } send_update_delete_messages(dialog_id, {new_message_id.get()}, true); being_readded_message_id_ = MessageFullId(); return {}; } try_add_active_live_location(dialog_id, m); update_reply_count_by_message(d, +1, m); update_forward_count(dialog_id, m); being_readded_message_id_ = MessageFullId(); return {dialog_id, new_message_id}; } void MessagesManager::on_send_message_file_parts_missing(int64 random_id, vector &&bad_parts) { auto it = being_sent_messages_.find(random_id); if (it == being_sent_messages_.end()) { // we can't receive fail more than once // but message can be successfully sent before LOG(INFO) << "Receive error for successfully sent message with random_id = " << random_id; return; } auto message_full_id = it->second; being_sent_messages_.erase(it); Message *m = get_message(message_full_id); if (m == nullptr) { // message has already been deleted by the user or sent to inaccessible channel // don't need to send error to the user, because the message has already been deleted // and there is nothing to be deleted from the server LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat " << message_full_id; return; } auto dialog_id = message_full_id.get_dialog_id(); if (dialog_id.get_type() == DialogType::SecretChat) { CHECK(!m->message_id.is_scheduled()); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); delete_random_id_to_message_id_correspondence(d, m->random_id, m->message_id); // need to change message random_id before resending m->random_id = generate_new_random_id(d); add_random_id_to_message_id_correspondence(d, m->random_id, m->message_id); auto log_event = SendMessageLogEvent(dialog_id, m); CHECK(m->send_message_log_event_id != 0); binlog_rewrite(G()->td_db()->get_binlog(), m->send_message_log_event_id, LogEvent::HandlerType::SendMessage, get_log_event_storer(log_event)); } do_send_message(dialog_id, m, std::move(bad_parts)); } void MessagesManager::on_send_message_file_reference_error(int64 random_id) { auto it = being_sent_messages_.find(random_id); if (it == being_sent_messages_.end()) { // we can't receive fail more than once // but message can be successfully sent before LOG(WARNING) << "Receive file reference invalid error about successfully sent message with random_id = " << random_id; return; } auto message_full_id = it->second; being_sent_messages_.erase(it); Message *m = get_message(message_full_id); if (m == nullptr) { // message has already been deleted by the user or sent to inaccessible channel // don't need to send error to the user, because the message has already been deleted // and there is nothing to be deleted from the server LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat " << message_full_id; return; } auto dialog_id = message_full_id.get_dialog_id(); if (dialog_id.get_type() == DialogType::SecretChat) { CHECK(!m->message_id.is_scheduled()); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); delete_random_id_to_message_id_correspondence(d, m->random_id, m->message_id); // need to change message random_id before resending m->random_id = generate_new_random_id(d); add_random_id_to_message_id_correspondence(d, m->random_id, m->message_id); auto log_event = SendMessageLogEvent(dialog_id, m); CHECK(m->send_message_log_event_id != 0); binlog_rewrite(G()->td_db()->get_binlog(), m->send_message_log_event_id, LogEvent::HandlerType::SendMessage, get_log_event_storer(log_event)); } do_send_message(dialog_id, m, {-1}); } void MessagesManager::on_send_media_group_file_reference_error(DialogId dialog_id, vector random_ids) { int64 media_album_id = 0; vector message_ids; vector messages; for (auto &random_id : random_ids) { auto it = being_sent_messages_.find(random_id); if (it == being_sent_messages_.end()) { // we can't receive fail more than once // but message can be successfully sent before LOG(ERROR) << "Receive file reference invalid error about successfully sent message with random_id = " << random_id; continue; } auto message_full_id = it->second; being_sent_messages_.erase(it); Message *m = get_message(message_full_id); if (m == nullptr) { // message has already been deleted by the user or sent to inaccessible channel // don't need to send error to the user, because the message has already been deleted // and there is nothing to be deleted from the server LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat " << message_full_id; continue; } CHECK(m->media_album_id != 0); CHECK(media_album_id == 0 || media_album_id == m->media_album_id); media_album_id = m->media_album_id; CHECK(dialog_id == message_full_id.get_dialog_id()); message_ids.push_back(m->message_id); messages.push_back(m); } CHECK(dialog_id.get_type() != DialogType::SecretChat); if (message_ids.empty()) { // all messages was deleted, nothing to do return; } auto &request = pending_message_group_sends_[media_album_id]; CHECK(!request.dialog_id.is_valid()); CHECK(request.finished_count == 0); CHECK(request.results.empty()); request.dialog_id = dialog_id; request.message_ids = std::move(message_ids); request.is_finished.resize(request.message_ids.size(), false); for (size_t i = 0; i < request.message_ids.size(); i++) { request.results.push_back(Status::OK()); } for (auto m : messages) { do_send_message(dialog_id, m, {-1}); } } void MessagesManager::on_send_message_fail(int64 random_id, Status error) { CHECK(error.is_error()); auto it = being_sent_messages_.find(random_id); if (it == being_sent_messages_.end()) { // we can't receive fail more than once // but message can be successfully sent before if (error.code() != NetQuery::Canceled) { LOG(ERROR) << "Receive error " << error << " about successfully sent message with random_id = " << random_id; } return; } auto message_full_id = it->second; being_sent_messages_.erase(it); Message *m = get_message(message_full_id); if (m == nullptr) { // message has already been deleted by the user or sent to inaccessible channel // don't need to send error to the user, because the message has already been deleted // and there is nothing to be deleted from the server LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat " << message_full_id; return; } LOG_IF(ERROR, error.code() == NetQuery::Canceled) << "Receive error " << error << " about sent message with random_id = " << random_id; auto dialog_id = message_full_id.get_dialog_id(); int error_code = error.code(); string error_message = error.message().str(); switch (error_code) { case 420: error_code = 429; LOG(ERROR) << "Receive error 420: " << error_message; break; case 429: // nothing special, error description has already been changed LOG_IF(ERROR, !begins_with(error_message, "Too Many Requests: retry after ")) << "Wrong error message: " << error_message; break; case 400: if (error.message() == "MESSAGE_TOO_LONG") { error_message = "Message is too long"; } else if (error.message() == "MEDIA_CAPTION_TOO_LONG") { error_message = "Message caption is too long"; } else if (error.message() == "INPUT_USER_DEACTIVATED") { error_code = 403; error_message = "User is deactivated"; } else if (error.message() == "USER_IS_BLOCKED") { error_code = 403; if (td_->auth_manager_->is_bot()) { switch (dialog_id.get_type()) { case DialogType::User: case DialogType::SecretChat: error_message = "Bot was blocked by the user"; break; case DialogType::Chat: case DialogType::Channel: error_message = "Bot was kicked from the chat"; break; case DialogType::None: default: UNREACHABLE(); } } else { switch (dialog_id.get_type()) { case DialogType::User: case DialogType::SecretChat: error_message = "User was blocked by the other user"; break; case DialogType::Chat: case DialogType::Channel: error_message = "User is not in the chat"; break; case DialogType::None: default: UNREACHABLE(); } } // TODO add check to send_message } else if (error.message() == "USER_IS_BOT") { if (td_->auth_manager_->is_bot() && (dialog_id.get_type() == DialogType::User || dialog_id.get_type() == DialogType::SecretChat)) { error_code = 403; if (td_->contacts_manager_->is_user_bot(dialog_id.get_user_id())) { error_message = "Bot can't send messages to bots"; } else { error_message = "Bot can't send messages to the user"; } // TODO move check to send_message } } else if (error.message() == "PEER_ID_INVALID") { error_code = 403; if (td_->auth_manager_->is_bot() && (dialog_id.get_type() == DialogType::User || dialog_id.get_type() == DialogType::SecretChat)) { error_message = "Bot can't initiate conversation with a user"; } } else if (error.message() == "WC_CONVERT_URL_INVALID" || error.message() == "EXTERNAL_URL_INVALID") { error_message = "Wrong HTTP URL specified"; } else if (error.message() == "WEBPAGE_CURL_FAILED") { error_message = "Failed to get HTTP URL content"; } else if (error.message() == "WEBPAGE_MEDIA_EMPTY") { error_message = "Wrong type of the web page content"; } else if (error.message() == "CHAT_FORWARDS_RESTRICTED") { error_message = "Message has protected content and can't be forwarded"; } else if (error.message() == "MEDIA_EMPTY") { auto content_type = m->content->get_type(); if (content_type == MessageContentType::Game) { error_message = "Wrong game short name specified"; } else if (content_type == MessageContentType::Invoice) { error_message = "Wrong invoice information specified"; } else if (content_type == MessageContentType::Poll) { error_message = "Wrong poll data specified"; } else if (content_type == MessageContentType::Contact) { error_message = "Wrong phone number specified"; } else if (content_type == MessageContentType::Story) { error_message = "Wrong story data specified"; } else { error_message = "Wrong file identifier/HTTP URL specified"; } } else if (error.message() == "PHOTO_EXT_INVALID") { error_message = "Photo has unsupported extension. Use one of .jpg, .jpeg, .gif, .png, .tif or .bmp"; } else if (error.message() == "QUOTE_TEXT_INVALID") { auto *input_reply_to = get_message_input_reply_to(m); if (input_reply_to != nullptr && !input_reply_to->is_empty() && input_reply_to->has_quote()) { auto reply_message_full_id = input_reply_to->get_reply_message_full_id(dialog_id); if (reply_message_full_id.get_message_id().is_valid()) { get_message_from_server(reply_message_full_id, Auto(), "QUOTE_TEXT_INVALID"); } } else { error_code = 500; error_message = "Unexpected QUOTE_TEXT_INVALID error"; } } else if (error.message() == "REPLY_MESSAGE_ID_INVALID") { auto *input_reply_to = get_message_input_reply_to(m); if (input_reply_to != nullptr && !input_reply_to->is_empty()) { auto reply_message_full_id = input_reply_to->get_reply_message_full_id(dialog_id); if (reply_message_full_id.get_message_id().is_valid()) { get_message_from_server(reply_message_full_id, Auto(), "REPLY_MESSAGE_ID_INVALID"); } } else { error_code = 500; error_message = "Unexpected REPLY_MESSAGE_ID_INVALID error"; } } break; case 403: if (error.message() == "MESSAGE_DELETE_FORBIDDEN") { error_code = 400; error_message = "Message can't be deleted"; } else if (error.message() == "CHAT_GUEST_SEND_FORBIDDEN") { error_code = 400; if (dialog_id.get_type() == DialogType::Channel) { td_->contacts_manager_->reload_channel(dialog_id.get_channel_id(), Promise(), "CHAT_GUEST_SEND_FORBIDDEN"); } } else if (error.message() != "CHANNEL_PUBLIC_GROUP_NA" && error.message() != "USER_IS_BLOCKED" && error.message() != "USER_BOT_INVALID" && error.message() != "USER_DELETED") { error_code = 400; } break; // TODO other codes default: break; } if (error.message() == "REPLY_MARKUP_INVALID") { if (m->reply_markup == nullptr) { LOG(ERROR) << "Receive " << error.message() << " for " << oneline(to_string(get_message_object(dialog_id, m, "on_send_message_fail"))); } else { LOG(ERROR) << "Receive " << error.message() << " for " << message_full_id << " with keyboard " << *m->reply_markup; } } if (error_code != 403 && !(error_code == 500 && G()->close_flag())) { LOG(WARNING) << "Failed to send " << message_full_id << " with the error " << error; } fail_send_message(message_full_id, error_code, error_message); } MessageId MessagesManager::get_next_message_id(Dialog *d, MessageType type) const { CHECK(d != nullptr); MessageId last_message_id; if (td_->auth_manager_->is_bot()) { last_message_id = max(d->last_assigned_message_id, d->max_unavailable_message_id); } else { last_message_id = std::max({d->last_message_id, d->last_new_message_id, d->last_database_message_id, d->last_assigned_message_id, d->last_clear_history_message_id, d->deleted_last_message_id, d->max_unavailable_message_id, d->max_added_message_id}); if (last_message_id < d->last_read_inbox_message_id && d->last_read_inbox_message_id < d->last_new_message_id.get_next_server_message_id()) { last_message_id = d->last_read_inbox_message_id; } if (last_message_id < d->last_read_outbox_message_id && d->last_read_outbox_message_id < d->last_new_message_id.get_next_server_message_id()) { last_message_id = d->last_read_outbox_message_id; } } d->last_assigned_message_id = last_message_id.get_next_message_id(type); if (d->last_assigned_message_id > MessageId::max()) { LOG(FATAL) << "Force restart because of message_id overflow: " << d->last_assigned_message_id; } CHECK(d->last_assigned_message_id.is_valid()); if (d->last_assigned_message_id.get_prev_server_message_id() != last_message_id.get_prev_server_message_id()) { d->had_yet_unsent_message_id_overflow = true; } return d->last_assigned_message_id; } MessageId MessagesManager::get_next_yet_unsent_message_id(Dialog *d) const { return get_next_message_id(d, MessageType::YetUnsent); } MessageId MessagesManager::get_next_local_message_id(Dialog *d) const { return get_next_message_id(d, MessageType::Local); } MessageId MessagesManager::get_next_yet_unsent_scheduled_message_id(Dialog *d, int32 date) { CHECK(date > 0); auto *scheduled_messages = add_dialog_scheduled_messages(d); MessageId message_id(ScheduledServerMessageId(1), date); for (const auto &it : d->scheduled_messages->scheduled_messages_) { if (it.first.get_scheduled_message_date() == date && it.first > message_id) { message_id = it.first; } }; auto &last_assigned_message_id = scheduled_messages->last_assigned_scheduled_message_id_[date]; if (last_assigned_message_id != MessageId() && last_assigned_message_id > message_id) { message_id = last_assigned_message_id; } last_assigned_message_id = message_id.get_next_message_id(MessageType::YetUnsent); return last_assigned_message_id; } void MessagesManager::fail_send_message(MessageFullId message_full_id, int32 error_code, const string &error_message) { if (error_code <= 0) { error_code = 500; } auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); MessageId old_message_id = message_full_id.get_message_id(); CHECK(old_message_id.is_valid() || old_message_id.is_valid_scheduled()); CHECK(old_message_id.is_yet_unsent()); update_reply_to_message_id(dialog_id, old_message_id, MessageId(), false, "fail_send_message"); bool need_update_dialog_pos = false; being_readded_message_id_ = message_full_id; auto message = delete_message(d, old_message_id, false, &need_update_dialog_pos, "fail send message"); if (message == nullptr) { // message has already been deleted by the user or sent to inaccessible channel // don't need to send update to the user, because the message has already been deleted // and there is nothing to be deleted from the server being_readded_message_id_ = MessageFullId(); return; } MessageId new_message_id = old_message_id.get_next_message_id(MessageType::Local); // trying to keep message position if (!old_message_id.is_scheduled()) { if (get_message_force(d, new_message_id, "fail_send_message") != nullptr || is_deleted_message(d, new_message_id) || new_message_id <= d->last_clear_history_message_id) { new_message_id = get_next_local_message_id(d); } else if (new_message_id > d->last_assigned_message_id) { d->last_assigned_message_id = new_message_id; } } else { while (get_message_force(d, new_message_id, "fail_send_message") != nullptr || is_deleted_message(d, new_message_id)) { new_message_id = new_message_id.get_next_message_id(MessageType::Local); } } message->message_id = new_message_id; if (old_message_id.is_scheduled()) { CHECK(message->message_id.is_valid_scheduled()); } else { CHECK(message->message_id.is_valid()); } if (message->forward_info == nullptr && message->view_count == 1) { message->view_count = 0; } message->is_failed_to_send = true; message->send_error_code = error_code; message->send_error_message = error_message; message->try_resend_at = 0.0; auto retry_after = Global::get_retry_after(error_code, error_message); if (retry_after > 0) { message->try_resend_at = Time::now() + retry_after; } update_failed_to_send_message_content(td_, message->content); bool need_update = false; Message *m = add_message_to_dialog(d, std::move(message), false, true, &need_update, &need_update_dialog_pos, "fail_send_message"); LOG_CHECK(m != nullptr) << "Failed to add failed to send " << new_message_id << " to " << dialog_id << " due to " << debug_add_message_to_dialog_fail_reason_; if (!m->message_id.is_scheduled()) { // add_message_to_dialog will not update counts, because need_update == false update_message_count_by_index(d, +1, m); update_reply_count_by_message(d, +1, m); // no-op because the message isn't server } register_new_local_message_id(d, m); LOG(INFO) << "Send updateMessageSendFailed for " << message_full_id; if (!td_->auth_manager_->is_bot()) { yet_unsent_message_full_id_to_persistent_message_id_.emplace({dialog_id, old_message_id}, m->message_id); } send_closure(G()->td(), &Td::send_update, td_api::make_object( get_message_object(dialog_id, m, "fail_send_message"), old_message_id.get(), td_api::make_object(error_code, error_message))); if (need_update_dialog_pos) { send_update_chat_last_message(d, "fail_send_message"); } being_readded_message_id_ = MessageFullId(); } void MessagesManager::fail_send_message(MessageFullId message_full_id, Status error) { fail_send_message(message_full_id, error.code(), error.message().str()); } void MessagesManager::fail_edit_message_media(MessageFullId message_full_id, Status &&error) { auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); MessageId message_id = message_full_id.get_message_id(); CHECK(message_id.is_any_server()); auto m = get_message(d, message_id); if (m == nullptr) { // message has already been deleted by the user or sent to inaccessible channel return; } CHECK(m->edited_content != nullptr); m->edit_promise.set_error(std::move(error)); cancel_edit_message_media(dialog_id, m, "Failed to edit message. MUST BE IGNORED"); } void MessagesManager::on_update_dialog_draft_message(DialogId dialog_id, MessageId top_thread_message_id, tl_object_ptr &&draft_message, bool force) { if (G()->close_flag()) { return; } if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive update of draft message in invalid " << dialog_id; return; } if (td_->auth_manager_->is_bot()) { if (draft_message != nullptr && draft_message->get_id() != telegram_api::draftMessageEmpty::ID) { LOG(ERROR) << "Receive update of draft message in " << dialog_id; } return; } auto d = get_dialog_force(dialog_id, "on_update_dialog_draft_message"); if (d == nullptr) { LOG(INFO) << "Ignore update chat draft in unknown " << dialog_id; if (draft_message != nullptr && draft_message->get_id() != telegram_api::draftMessageEmpty::ID) { if (!have_input_peer(dialog_id, AccessRights::Read)) { LOG(ERROR) << "Have no read access to " << dialog_id << " to repair chat draft message"; } else { send_get_dialog_query(dialog_id, Auto(), 0, "on_update_dialog_draft_message"); } } return; } if (top_thread_message_id.is_valid()) { // TODO update thread message draft return; } if (!force && draft_message != nullptr && draft_message->get_id() == telegram_api::draftMessage::ID) { auto *input_reply_to = static_cast(draft_message.get())->reply_to_.get(); if (input_reply_to != nullptr) { InputDialogId input_dialog_id; switch (input_reply_to->get_id()) { case telegram_api::inputReplyToStory::ID: { auto reply_to = static_cast(input_reply_to); input_dialog_id = InputDialogId(reply_to->user_id_); break; } case telegram_api::inputReplyToMessage::ID: { auto reply_to = static_cast(input_reply_to); if (reply_to->reply_to_peer_id_ != nullptr) { input_dialog_id = InputDialogId(reply_to->reply_to_peer_id_); } break; } default: UNREACHABLE(); } auto reply_in_dialog_id = input_dialog_id.get_dialog_id(); if (reply_in_dialog_id.is_valid() && !have_dialog_force(reply_in_dialog_id, "on_update_dialog_draft_message")) { td_->dialog_filter_manager_->load_input_dialog( input_dialog_id, [actor_id = actor_id(this), dialog_id, top_thread_message_id, draft_message = std::move(draft_message)](Unit) mutable { send_closure(actor_id, &MessagesManager::on_update_dialog_draft_message, dialog_id, top_thread_message_id, std::move(draft_message), true); }); return; } } } update_dialog_draft_message(d, get_draft_message(td_, std::move(draft_message)), true, true); } bool MessagesManager::update_dialog_draft_message(Dialog *d, unique_ptr &&draft_message, bool from_update, bool need_update_dialog_pos) { CHECK(d != nullptr); if (!td_->auth_manager_->is_bot() && need_update_draft_message(d->draft_message, draft_message, from_update)) { d->draft_message = std::move(draft_message); if (need_update_dialog_pos) { update_dialog_pos(d, "update_dialog_draft_message", false); } on_dialog_updated(d->dialog_id, "update_dialog_draft_message"); send_update_chat_draft_message(d); return true; } return false; } void MessagesManager::on_update_dialog_is_pinned(FolderId folder_id, DialogId dialog_id, bool is_pinned) { if (td_->auth_manager_->is_bot()) { // just in case return; } if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive pin of invalid " << dialog_id; return; } auto d = get_dialog_force(dialog_id, "on_update_dialog_is_pinned"); if (d == nullptr) { LOG(INFO) << "Can't apply updateDialogPinned in " << folder_id << " with unknown " << dialog_id; on_update_pinned_dialogs(folder_id); return; } if (d->order == DEFAULT_ORDER) { // the chat can't be pinned or is already unpinned // don't change it's folder_id LOG(INFO) << "Can't apply updateDialogPinned in " << folder_id << " with " << dialog_id; return; } auto *list = get_dialog_list(DialogListId(folder_id)); CHECK(list != nullptr); if (!list->are_pinned_dialogs_inited_) { return; } set_dialog_folder_id(d, folder_id); set_dialog_is_pinned(DialogListId(folder_id), d, is_pinned); } void MessagesManager::on_update_pinned_dialogs(FolderId folder_id) { if (td_->auth_manager_->is_bot()) { // just in case return; } // TODO log event + delete_log_event_promise auto *list = get_dialog_list(DialogListId(folder_id)); if (list == nullptr || !list->are_pinned_dialogs_inited_) { return; } // preload all pinned dialogs int32 limit = narrow_cast(list->pinned_dialogs_.size()) + (folder_id == FolderId::main() && sponsored_dialog_id_.is_valid() ? 1 : 0); get_dialogs_from_list(DialogListId(folder_id), limit, Auto()); reload_pinned_dialogs(DialogListId(folder_id), Auto()); } void MessagesManager::on_update_dialog_view_as_topics(const Dialog *d, bool old_view_as_topics) { auto new_view_as_topics = get_dialog_view_as_topics(d); if (old_view_as_topics == new_view_as_topics) { return; } LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in on_update_dialog_view_as_topics"; send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateChatViewAsTopics"), new_view_as_topics)); if (d->draft_message != nullptr && can_send_message(d->dialog_id).is_ok()) { // need_hide_dialog_draft_message has changed and there is draft message send_update_chat_draft_message(d); } // TODO update unread counters in chat lists } void MessagesManager::on_update_dialog_is_forum(DialogId dialog_id, bool is_forum) { if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive is_forum for invalid " << dialog_id; return; } auto d = get_dialog_force(dialog_id, "on_update_dialog_is_forum"); if (d == nullptr) { // nothing to do return; } set_dialog_is_forum(d, is_forum); } void MessagesManager::set_dialog_is_forum(Dialog *d, bool is_forum) { CHECK(d != nullptr); if (d->is_forum == is_forum) { return; } auto old_view_as_topics = get_dialog_view_as_topics(d); d->is_forum = is_forum; on_dialog_updated(d->dialog_id, "set_dialog_is_forum"); LOG(INFO) << "Set " << d->dialog_id << " is_forum to " << is_forum; on_update_dialog_view_as_topics(d, old_view_as_topics); } void MessagesManager::on_update_dialog_view_as_messages(DialogId dialog_id, bool view_as_messages) { if (td_->auth_manager_->is_bot()) { // just in case return; } if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive view_as_messages for invalid " << dialog_id; return; } auto d = get_dialog_force(dialog_id, "on_update_dialog_view_as_messages"); if (d == nullptr) { // nothing to do return; } set_dialog_view_as_messages(d, view_as_messages); } void MessagesManager::set_dialog_view_as_messages(Dialog *d, bool view_as_messages) { if (td_->auth_manager_->is_bot()) { // just in case return; } CHECK(d != nullptr); if (d->view_as_messages == view_as_messages) { if (!d->is_view_as_messages_inited) { d->is_view_as_messages_inited = true; on_dialog_updated(d->dialog_id, "set_dialog_view_as_messages"); } return; } auto old_view_as_topics = get_dialog_view_as_topics(d); d->view_as_messages = view_as_messages; d->is_view_as_messages_inited = true; on_dialog_updated(d->dialog_id, "set_dialog_view_as_messages"); LOG(INFO) << "Set " << d->dialog_id << " view_as_messages to " << view_as_messages; on_update_dialog_view_as_topics(d, old_view_as_topics); } void MessagesManager::on_update_dialog_is_marked_as_unread(DialogId dialog_id, bool is_marked_as_unread) { if (td_->auth_manager_->is_bot()) { // just in case return; } if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive marking as unread of invalid " << dialog_id; return; } auto d = get_dialog_force(dialog_id, "on_update_dialog_is_marked_as_unread"); if (d == nullptr) { // nothing to do return; } if (is_marked_as_unread == d->is_marked_as_unread) { return; } set_dialog_is_marked_as_unread(d, is_marked_as_unread); } void MessagesManager::set_dialog_is_marked_as_unread(Dialog *d, bool is_marked_as_unread) { if (td_->auth_manager_->is_bot()) { // just in case return; } CHECK(d != nullptr); CHECK(d->is_marked_as_unread != is_marked_as_unread); d->is_marked_as_unread = is_marked_as_unread; on_dialog_updated(d->dialog_id, "set_dialog_is_marked_as_unread"); LOG(INFO) << "Set " << d->dialog_id << " is marked as unread to " << is_marked_as_unread; LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in set_dialog_is_marked_as_unread"; send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateChatIsMarkedAsUnread"), is_marked_as_unread)); if (d->server_unread_count + d->local_unread_count == 0 && need_unread_counter(d->order)) { int32 delta = d->is_marked_as_unread ? 1 : -1; for (auto &list : get_dialog_lists(d)) { if (list.is_dialog_unread_count_inited_) { list.unread_dialog_total_count_ += delta; list.unread_dialog_marked_count_ += delta; if (is_dialog_muted(d)) { list.unread_dialog_muted_count_ += delta; list.unread_dialog_muted_marked_count_ += delta; } send_update_unread_chat_count(list, d->dialog_id, true, "set_dialog_is_marked_as_unread"); } } if (td_->dialog_filter_manager_->have_dialog_filters()) { update_dialog_lists(d, get_dialog_positions(d), true, false, "set_dialog_is_marked_as_unread"); } } } void MessagesManager::on_update_dialog_is_translatable(DialogId dialog_id, bool is_translatable) { if (td_->auth_manager_->is_bot()) { // just in case return; } if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive marking as unread of invalid " << dialog_id; return; } auto d = get_dialog_force(dialog_id, "on_update_dialog_is_translatable"); if (d == nullptr) { // nothing to do return; } if (is_translatable == d->is_translatable) { return; } set_dialog_is_translatable(d, is_translatable); } void MessagesManager::update_is_translatable(bool new_is_premium) { if (td_->auth_manager_->is_bot()) { return; } dialogs_.foreach([&](const DialogId &dialog_id, const unique_ptr &dialog) { if (dialog->is_translatable) { send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(dialog_id, "updateChatIsTranslatable"), new_is_premium)); } }); } void MessagesManager::set_dialog_is_translatable(Dialog *d, bool is_translatable) { if (td_->auth_manager_->is_bot()) { // just in case return; } CHECK(d != nullptr); CHECK(d->is_translatable != is_translatable); d->is_translatable = is_translatable; on_dialog_updated(d->dialog_id, "set_dialog_is_translatable"); LOG(INFO) << "Set " << d->dialog_id << " is translatable to " << is_translatable; LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in set_dialog_is_translatable"; bool is_premium = td_->option_manager_->get_option_boolean("is_premium"); if (is_premium) { send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateChatIsTranslatable"), is_translatable)); } } void MessagesManager::on_update_dialog_is_blocked(DialogId dialog_id, bool is_blocked, bool is_blocked_for_stories) { if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive pinned message in invalid " << dialog_id; return; } if (dialog_id.get_type() == DialogType::User) { td_->contacts_manager_->on_update_user_is_blocked(dialog_id.get_user_id(), is_blocked, is_blocked_for_stories); } auto d = get_dialog_force(dialog_id, "on_update_dialog_is_blocked"); if (d == nullptr) { // nothing to do return; } if (d->is_blocked == is_blocked && d->is_blocked_for_stories == is_blocked_for_stories) { if (!d->is_is_blocked_for_stories_inited) { CHECK(is_blocked_for_stories == false); d->is_is_blocked_inited = true; d->is_is_blocked_for_stories_inited = true; on_dialog_updated(dialog_id, "on_update_dialog_is_blocked"); } return; } set_dialog_is_blocked(d, is_blocked, is_blocked_for_stories); } void MessagesManager::set_dialog_is_blocked(Dialog *d, bool is_blocked, bool is_blocked_for_stories) { CHECK(d != nullptr); CHECK(d->is_blocked != is_blocked || d->is_blocked_for_stories != is_blocked_for_stories); d->is_blocked = is_blocked; d->is_blocked_for_stories = is_blocked_for_stories; d->is_is_blocked_inited = true; d->is_is_blocked_for_stories_inited = true; on_dialog_updated(d->dialog_id, "set_dialog_is_blocked"); LOG(INFO) << "Set " << d->dialog_id << " is_blocked to " << is_blocked << '/' << is_blocked_for_stories; LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in set_dialog_is_blocked"; auto block_list_id = BlockListId(d->is_blocked, d->is_blocked_for_stories); send_closure(G()->td(), &Td::send_update, td_api::make_object(get_chat_id_object(d->dialog_id, "updateChatBlockList"), block_list_id.get_block_list_object())); if (d->dialog_id.get_type() == DialogType::User) { td_->contacts_manager_->on_update_user_is_blocked(d->dialog_id.get_user_id(), is_blocked, is_blocked_for_stories); if (d->know_action_bar) { if (is_blocked) { if (d->action_bar != nullptr) { d->action_bar = nullptr; send_update_chat_action_bar(d); } } else { repair_dialog_action_bar(d, "on_dialog_user_is_blocked_updated"); } } td_->contacts_manager_->for_each_secret_chat_with_user( d->dialog_id.get_user_id(), [this, is_blocked, is_blocked_for_stories](SecretChatId secret_chat_id) { DialogId dialog_id(secret_chat_id); auto d = get_dialog(dialog_id); // must not create the dialog if (d != nullptr && d->is_update_new_chat_sent && (d->is_blocked != is_blocked || d->is_blocked_for_stories != is_blocked_for_stories)) { set_dialog_is_blocked(d, is_blocked, is_blocked_for_stories); } }); } } void MessagesManager::on_update_dialog_last_pinned_message_id(DialogId dialog_id, MessageId pinned_message_id) { if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive pinned message in invalid " << dialog_id; return; } if (!pinned_message_id.is_valid() && pinned_message_id != MessageId()) { LOG(ERROR) << "Receive as pinned message " << pinned_message_id; return; } auto d = get_dialog_force(dialog_id, "on_update_dialog_last_pinned_message_id"); if (d == nullptr) { // nothing to do return; } set_dialog_last_pinned_message_id(d, pinned_message_id); } void MessagesManager::set_dialog_last_pinned_message_id(Dialog *d, MessageId pinned_message_id) { CHECK(d != nullptr); Message *m = get_message_force(d, pinned_message_id, "set_dialog_last_pinned_message_id"); if (m != nullptr && update_message_is_pinned(d, m, true, "set_dialog_last_pinned_message_id")) { on_message_changed(d, m, true, "set_dialog_last_pinned_message_id"); } if (d->is_last_pinned_message_id_inited && d->last_pinned_message_id == pinned_message_id) { return; } d->last_pinned_message_id = pinned_message_id; d->is_last_pinned_message_id_inited = true; on_dialog_updated(d->dialog_id, "set_dialog_last_pinned_message_id"); LOG(INFO) << "Set " << d->dialog_id << " pinned message to " << pinned_message_id; } void MessagesManager::drop_dialog_last_pinned_message_id(Dialog *d) { d->last_pinned_message_id = MessageId(); d->is_last_pinned_message_id_inited = false; on_dialog_updated(d->dialog_id, "drop_dialog_last_pinned_message_id"); LOG(INFO) << "Drop " << d->dialog_id << " pinned message"; create_actor( "ReloadDialogFullInfoActor", 1.0, PromiseCreator::lambda([actor_id = actor_id(this), dialog_id = d->dialog_id](Result result) { send_closure(actor_id, &MessagesManager::reload_dialog_info_full, dialog_id, "drop_dialog_last_pinned_message_id"); })) .release(); } void MessagesManager::on_update_dialog_background(DialogId dialog_id, telegram_api::object_ptr &&wallpaper) { if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive background in invalid " << dialog_id; return; } if (td_->auth_manager_->is_bot()) { return; } auto d = get_dialog_force(dialog_id, "on_update_dialog_background"); if (d == nullptr) { // nothing to do return; } set_dialog_background(d, BackgroundInfo(td_, std::move(wallpaper))); } void MessagesManager::set_dialog_background(Dialog *d, BackgroundInfo &&background_info) { CHECK(d != nullptr); if (td_->auth_manager_->is_bot()) { return; } bool is_changed = d->background_info != background_info; if (!is_changed && d->is_background_inited) { return; } d->background_info = std::move(background_info); d->is_background_inited = true; if (is_changed) { LOG(INFO) << "Set " << d->dialog_id << " background to " << d->background_info; send_update_chat_background(d); } else { on_dialog_updated(d->dialog_id, "set_dialog_background"); } } void MessagesManager::on_update_dialog_theme_name(DialogId dialog_id, string theme_name) { if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive theme in invalid " << dialog_id; return; } if (td_->auth_manager_->is_bot()) { return; } auto d = get_dialog_force(dialog_id, "on_update_dialog_theme_name"); if (d == nullptr) { // nothing to do return; } set_dialog_theme_name(d, std::move(theme_name)); } void MessagesManager::set_dialog_theme_name(Dialog *d, string theme_name) { CHECK(d != nullptr); if (td_->auth_manager_->is_bot()) { return; } bool is_changed = d->theme_name != theme_name; if (!is_changed && d->is_theme_name_inited) { return; } d->theme_name = std::move(theme_name); d->is_theme_name_inited = true; if (is_changed) { LOG(INFO) << "Set " << d->dialog_id << " theme to \"" << d->theme_name << '"'; send_update_chat_theme(d); } else { on_dialog_updated(d->dialog_id, "set_dialog_theme_name"); } } void MessagesManager::drop_dialog_pending_join_requests(DialogId dialog_id) { CHECK(dialog_id.is_valid()); if (td_->auth_manager_->is_bot()) { return; } auto d = get_dialog(dialog_id); // called from update_chat/channel, must not create the dialog if (d != nullptr && d->is_update_new_chat_sent) { set_dialog_pending_join_requests(d, 0, {}); } } void MessagesManager::on_update_dialog_pending_join_requests(DialogId dialog_id, int32 pending_join_request_count, vector pending_requesters) { if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive pending join request count in invalid " << dialog_id; return; } if (td_->auth_manager_->is_bot()) { return; } auto d = get_dialog_force(dialog_id, "on_update_dialog_pending_join_request_count"); if (d == nullptr) { // nothing to do return; } set_dialog_pending_join_requests(d, pending_join_request_count, UserId::get_user_ids(pending_requesters)); } void MessagesManager::fix_pending_join_requests(DialogId dialog_id, int32 &pending_join_request_count, vector &pending_join_request_user_ids) const { td::remove_if(pending_join_request_user_ids, [](UserId user_id) { return !user_id.is_valid(); }); bool need_drop_pending_join_requests = [&] { if (pending_join_request_count < 0) { return true; } switch (dialog_id.get_type()) { case DialogType::User: case DialogType::SecretChat: return true; case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); auto status = td_->contacts_manager_->get_chat_status(chat_id); if (!status.can_manage_invite_links()) { return true; } break; } case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); auto status = td_->contacts_manager_->get_channel_permissions(channel_id); if (!status.can_manage_invite_links()) { return true; } break; } case DialogType::None: default: UNREACHABLE(); } return false; }(); if (need_drop_pending_join_requests) { pending_join_request_count = 0; pending_join_request_user_ids.clear(); } else if (static_cast(pending_join_request_count) < pending_join_request_user_ids.size()) { LOG(ERROR) << "Fix pending join request count from " << pending_join_request_count << " to " << pending_join_request_user_ids.size(); pending_join_request_count = narrow_cast(pending_join_request_user_ids.size()); } static constexpr size_t MAX_PENDING_JOIN_REQUESTS = 3; if (pending_join_request_user_ids.size() > MAX_PENDING_JOIN_REQUESTS) { pending_join_request_user_ids.resize(MAX_PENDING_JOIN_REQUESTS); } } void MessagesManager::set_dialog_pending_join_requests(Dialog *d, int32 pending_join_request_count, vector pending_join_request_user_ids) { if (td_->auth_manager_->is_bot()) { return; } CHECK(d != nullptr); fix_pending_join_requests(d->dialog_id, pending_join_request_count, pending_join_request_user_ids); if (d->pending_join_request_count == pending_join_request_count && d->pending_join_request_user_ids == pending_join_request_user_ids) { return; } d->pending_join_request_count = pending_join_request_count; d->pending_join_request_user_ids = std::move(pending_join_request_user_ids); send_update_chat_pending_join_requests(d); } void MessagesManager::repair_dialog_scheduled_messages(Dialog *d) { if (td_->auth_manager_->is_bot() || d->dialog_id.get_type() == DialogType::SecretChat) { return; } if (d->last_repair_scheduled_messages_generation == scheduled_messages_sync_generation_) { return; } d->last_repair_scheduled_messages_generation = scheduled_messages_sync_generation_; // TODO create log event auto dialog_id = d->dialog_id; LOG(INFO) << "Repair scheduled messages in " << dialog_id << " with generation " << d->last_repair_scheduled_messages_generation; get_dialog_scheduled_messages(dialog_id, false, true, PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](Unit) { send_closure(G()->messages_manager(), &MessagesManager::get_dialog_scheduled_messages, dialog_id, true, true, Promise()); })); } void MessagesManager::on_update_dialog_has_scheduled_server_messages(DialogId dialog_id, bool has_scheduled_server_messages) { CHECK(dialog_id.is_valid()); if (td_->auth_manager_->is_bot() || dialog_id.get_type() == DialogType::SecretChat) { return; } auto d = get_dialog_force(dialog_id, "on_update_dialog_has_scheduled_server_messages"); if (d == nullptr) { // nothing to do return; } LOG(INFO) << "Receive has_scheduled_server_messages = " << has_scheduled_server_messages << " in " << dialog_id; if (d->has_scheduled_server_messages != has_scheduled_server_messages) { set_dialog_has_scheduled_server_messages(d, has_scheduled_server_messages); } else if (has_scheduled_server_messages != (d->has_scheduled_database_messages || have_dialog_scheduled_messages_in_memory(d))) { repair_dialog_scheduled_messages(d); } } void MessagesManager::set_dialog_has_scheduled_server_messages(Dialog *d, bool has_scheduled_server_messages) { CHECK(d != nullptr); CHECK(d->has_scheduled_server_messages != has_scheduled_server_messages); d->has_scheduled_server_messages = has_scheduled_server_messages; repair_dialog_scheduled_messages(d); on_dialog_updated(d->dialog_id, "set_dialog_has_scheduled_server_messages"); LOG(INFO) << "Set " << d->dialog_id << " has_scheduled_server_messages to " << has_scheduled_server_messages; send_update_chat_has_scheduled_messages(d, false); } void MessagesManager::set_dialog_has_scheduled_database_messages(DialogId dialog_id, bool has_scheduled_database_messages) { if (G()->close_flag()) { return; } return set_dialog_has_scheduled_database_messages_impl(get_dialog(dialog_id), has_scheduled_database_messages); } void MessagesManager::set_dialog_has_scheduled_database_messages_impl(Dialog *d, bool has_scheduled_database_messages) { CHECK(d != nullptr); if (d->has_scheduled_database_messages == has_scheduled_database_messages) { return; } if (d->has_scheduled_database_messages && have_dialog_scheduled_messages_in_memory(d) && !d->scheduled_messages->scheduled_messages_.begin()->first.is_yet_unsent()) { // to prevent race between add_message_to_database and check of has_scheduled_database_messages return; } CHECK(G()->use_message_database()); d->has_scheduled_database_messages = has_scheduled_database_messages; on_dialog_updated(d->dialog_id, "set_dialog_has_scheduled_database_messages"); } void MessagesManager::on_update_dialog_folder_id(DialogId dialog_id, FolderId folder_id) { auto d = get_dialog_force(dialog_id, "on_update_dialog_folder_id"); if (d == nullptr) { // nothing to do return; } set_dialog_folder_id(d, folder_id); } void MessagesManager::set_dialog_folder_id(Dialog *d, FolderId folder_id) { if (td_->auth_manager_->is_bot()) { return; } CHECK(d != nullptr); if (d->folder_id == folder_id) { if (!d->is_folder_id_inited) { LOG(INFO) << "Folder of " << d->dialog_id << " is still " << folder_id; do_set_dialog_folder_id(d, folder_id); } return; } LOG(INFO) << "Change " << d->dialog_id << " folder from " << d->folder_id << " to " << folder_id; auto dialog_positions = get_dialog_positions(d); if (get_dialog_pinned_order(DialogListId(d->folder_id), d->dialog_id) != DEFAULT_ORDER) { set_dialog_is_pinned(DialogListId(d->folder_id), d, false, false); } DialogDate dialog_date(d->order, d->dialog_id); if (get_dialog_folder(d->folder_id)->ordered_dialogs_.erase(dialog_date) == 0) { LOG_IF(ERROR, d->order != DEFAULT_ORDER) << d->dialog_id << " not found in the chat list"; } do_set_dialog_folder_id(d, folder_id); get_dialog_folder(d->folder_id)->ordered_dialogs_.insert(dialog_date); update_dialog_lists(d, std::move(dialog_positions), true, false, "set_dialog_folder_id"); } void MessagesManager::do_set_dialog_folder_id(Dialog *d, FolderId folder_id) { CHECK(!td_->auth_manager_->is_bot()); bool is_changed = d->folder_id != folder_id; if (!is_changed && d->is_folder_id_inited) { return; } d->is_folder_id_inited = true; if (is_changed) { d->folder_id = folder_id; if (d->dialog_id.get_type() == DialogType::SecretChat) { // need to change action bar only for the secret chat and keep unarchive for the main chat auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id()); if (d->is_update_new_chat_sent && user_id.is_valid()) { const Dialog *user_d = get_dialog(DialogId(user_id)); if (user_d != nullptr && user_d->action_bar != nullptr && user_d->action_bar->can_unarchive()) { send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateChatActionBar"), get_chat_action_bar_object(d))); } } } else if (folder_id != FolderId::archive() && d->action_bar != nullptr && d->action_bar->on_dialog_unarchived()) { send_update_chat_action_bar(d); } } on_dialog_updated(d->dialog_id, "do_set_dialog_folder_id"); } void MessagesManager::on_update_dialog_group_call(DialogId dialog_id, bool has_active_group_call, bool is_group_call_empty, const char *source, bool force) { LOG(INFO) << "Update voice chat in " << dialog_id << " with has_active_voice_chat = " << has_active_group_call << " and is_voice_chat_empty = " << is_group_call_empty << " from " << source; CHECK(dialog_id.is_valid()); Dialog *d = get_dialog(dialog_id); // must not create the Dialog, because it is called from on_get_chat if (d == nullptr) { LOG(INFO) << "Can't find " << dialog_id; pending_dialog_group_call_updates_[dialog_id] = {has_active_group_call, is_group_call_empty}; return; } if (!has_active_group_call) { is_group_call_empty = false; } if (d->active_group_call_id.is_valid() && has_active_group_call && is_group_call_empty && (td_->group_call_manager_->is_group_call_being_joined(d->active_group_call_id) || td_->group_call_manager_->is_group_call_joined(d->active_group_call_id))) { LOG(INFO) << "Fix is_group_call_empty to false"; is_group_call_empty = false; } if (d->has_active_group_call == has_active_group_call && d->is_group_call_empty == is_group_call_empty) { return; } if (!force && d->active_group_call_id.is_valid() && has_active_group_call && td_->group_call_manager_->is_group_call_being_joined(d->active_group_call_id)) { LOG(INFO) << "Ignore update in a being joined group call"; return; } if (d->has_active_group_call && !has_active_group_call && d->active_group_call_id.is_valid()) { d->active_group_call_id = InputGroupCallId(); d->has_active_group_call = false; d->is_group_call_empty = false; send_update_chat_video_chat(d); } else if (d->has_active_group_call && has_active_group_call) { d->is_group_call_empty = is_group_call_empty; send_update_chat_video_chat(d); } else { d->has_active_group_call = has_active_group_call; d->is_group_call_empty = is_group_call_empty; on_dialog_updated(dialog_id, "on_update_dialog_group_call"); if (has_active_group_call && !d->active_group_call_id.is_valid() && !td_->auth_manager_->is_bot()) { repair_dialog_active_group_call_id(dialog_id); } } } void MessagesManager::on_update_dialog_group_call_id(DialogId dialog_id, InputGroupCallId input_group_call_id) { auto d = get_dialog_force(dialog_id, "on_update_dialog_group_call_id"); if (d == nullptr) { // nothing to do return; } if (d->active_group_call_id != input_group_call_id) { LOG(INFO) << "Update active group call in " << dialog_id << " to " << input_group_call_id; d->active_group_call_id = input_group_call_id; bool has_active_group_call = input_group_call_id.is_valid(); if (has_active_group_call != d->has_active_group_call) { d->has_active_group_call = has_active_group_call; if (!has_active_group_call) { d->is_group_call_empty = false; } } send_update_chat_video_chat(d); } } void MessagesManager::on_update_dialog_default_join_group_call_as_dialog_id(DialogId dialog_id, DialogId default_join_as_dialog_id, bool force) { auto d = get_dialog_force(dialog_id, "on_update_dialog_default_join_group_call_as_dialog_id"); if (d == nullptr) { // nothing to do return; } if (!force && d->active_group_call_id.is_valid() && td_->group_call_manager_->is_group_call_being_joined(d->active_group_call_id)) { LOG(INFO) << "Ignore default_join_as_dialog_id update in a being joined group call"; return; } if (default_join_as_dialog_id.is_valid()) { if (default_join_as_dialog_id.get_type() != DialogType::User) { force_create_dialog(default_join_as_dialog_id, "on_update_dialog_default_join_group_call_as_dialog_id"); } else if (!td_->contacts_manager_->have_user_force(default_join_as_dialog_id.get_user_id(), "on_update_dialog_default_join_group_call_as_dialog_id") || default_join_as_dialog_id != get_my_dialog_id()) { default_join_as_dialog_id = DialogId(); } } if (d->default_join_group_call_as_dialog_id != default_join_as_dialog_id) { d->default_join_group_call_as_dialog_id = default_join_as_dialog_id; send_update_chat_video_chat(d); } } void MessagesManager::on_update_dialog_default_send_message_as_dialog_id(DialogId dialog_id, DialogId default_send_as_dialog_id, bool force) { if (td_->auth_manager_->is_bot()) { // just in case return; } auto dialog_type = dialog_id.get_type(); if (dialog_type != DialogType::Channel || is_broadcast_channel(dialog_id)) { if (default_send_as_dialog_id != DialogId()) { LOG(ERROR) << "Receive message sender " << default_send_as_dialog_id << " in " << dialog_id; } return; } auto d = get_dialog_force(dialog_id, "on_update_dialog_default_send_message_as_dialog_id"); if (d == nullptr) { // nothing to do return; } if (!force) { // TODO ignore update if have being sent messages } if (default_send_as_dialog_id.is_valid()) { if (default_send_as_dialog_id.get_type() != DialogType::User) { force_create_dialog(default_send_as_dialog_id, "on_update_dialog_default_send_message_as_dialog_id"); } else if (!td_->contacts_manager_->have_user_force(default_send_as_dialog_id.get_user_id(), "on_update_dialog_default_send_message_as_dialog_id") || default_send_as_dialog_id != get_my_dialog_id()) { default_send_as_dialog_id = DialogId(); } } if (d->default_send_message_as_dialog_id != default_send_as_dialog_id) { if (force || default_send_as_dialog_id.is_valid() || (created_public_broadcasts_inited_ && created_public_broadcasts_.empty())) { LOG(INFO) << "Set message sender in " << dialog_id << " to " << default_send_as_dialog_id; d->need_drop_default_send_message_as_dialog_id = false; d->default_send_message_as_dialog_id = default_send_as_dialog_id; send_update_chat_message_sender(d); } else { LOG(INFO) << "Postpone removal of message sender in " << dialog_id; d->need_drop_default_send_message_as_dialog_id = true; } on_dialog_updated(d->dialog_id, "on_update_dialog_default_send_message_as_dialog_id"); } else if (default_send_as_dialog_id.is_valid() && d->need_drop_default_send_message_as_dialog_id) { LOG(INFO) << "Don't remove message sender in " << dialog_id; d->need_drop_default_send_message_as_dialog_id = false; on_dialog_updated(d->dialog_id, "on_update_dialog_default_send_message_as_dialog_id"); } } void MessagesManager::on_update_dialog_message_ttl(DialogId dialog_id, MessageTtl message_ttl) { auto d = get_dialog_force(dialog_id, "on_update_dialog_message_ttl"); if (d == nullptr) { // nothing to do return; } set_dialog_message_ttl(d, std::move(message_ttl)); } void MessagesManager::set_dialog_message_ttl(Dialog *d, MessageTtl message_ttl) { CHECK(d != nullptr); if (d->message_ttl != message_ttl) { d->message_ttl = message_ttl; d->is_message_ttl_inited = true; send_update_chat_message_auto_delete_time(d); } if (!d->is_message_ttl_inited) { d->is_message_ttl_inited = true; on_dialog_updated(d->dialog_id, "on_update_dialog_message_ttl"); } } void MessagesManager::on_create_new_dialog_success(int64 random_id, tl_object_ptr &&updates, DialogType expected_type, Promise &&promise) { auto sent_messages = UpdatesManager::get_new_messages(updates.get()); auto sent_messages_random_ids = UpdatesManager::get_sent_messages_random_ids(updates.get()); if (sent_messages.size() != 1u || sent_messages_random_ids.size() != 1u) { LOG(ERROR) << "Receive wrong result for create group or channel chat " << oneline(to_string(updates)); return on_create_new_dialog_fail(random_id, Status::Error(500, "Unsupported server response"), std::move(promise)); } auto *message = sent_messages.begin()->first; // int64 message_random_id = *sent_messages_random_ids.begin(); // TODO check that message_random_id equals random_id after messages_createChat will be updated if (sent_messages.begin()->second) { return on_create_new_dialog_fail(random_id, Status::Error(500, "Scheduled message received"), std::move(promise)); } auto dialog_id = DialogId::get_message_dialog_id(message); if (dialog_id.get_type() != expected_type) { return on_create_new_dialog_fail(random_id, Status::Error(500, "Chat of wrong type has been created"), std::move(promise)); } if (message->get_id() != telegram_api::messageService::ID) { return on_create_new_dialog_fail(random_id, Status::Error(500, "Invalid message received"), std::move(promise)); } auto action_id = static_cast(message)->action_->get_id(); if (action_id != telegram_api::messageActionChatCreate::ID && action_id != telegram_api::messageActionChannelCreate::ID) { return on_create_new_dialog_fail(random_id, Status::Error(500, "Invalid service message received"), std::move(promise)); } auto it = created_dialogs_.find(random_id); CHECK(it != created_dialogs_.end()); CHECK(it->second == DialogId()); it->second = dialog_id; const Dialog *d = get_dialog(dialog_id); if (d != nullptr && d->last_new_message_id.is_valid()) { // dialog have been already created and at least one non-temporary message was added, // i.e. we are not interested in the creation of dialog by searchMessages // then messages have already been added, so just set promise return promise.set_value(Unit()); } if (pending_created_dialogs_.count(dialog_id) == 0) { auto user_ids = td_->updates_manager_->extract_group_invite_privacy_forbidden_updates(updates); auto new_promise = PromiseCreator::lambda( [dialog_id, user_ids = std::move(user_ids), promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { return promise.set_error(result.move_as_error()); } promise.set_value(Unit()); if (!user_ids.empty()) { send_closure(G()->contacts_manager(), &ContactsManager::send_update_add_chat_members_privacy_forbidden, dialog_id, std::move(user_ids), "on_create_new_dialog_success"); } }); pending_created_dialogs_.emplace(dialog_id, std::move(new_promise)); } else { LOG(ERROR) << "Receive twice " << dialog_id << " as result of chat creation"; return on_create_new_dialog_fail(random_id, Status::Error(500, "Chat was created earlier"), std::move(promise)); } td_->updates_manager_->on_get_updates(std::move(updates), Promise()); } void MessagesManager::on_create_new_dialog_fail(int64 random_id, Status error, Promise &&promise) { LOG(INFO) << "Clean up creation of group or channel chat"; auto it = created_dialogs_.find(random_id); CHECK(it != created_dialogs_.end()); CHECK(it->second == DialogId()); created_dialogs_.erase(it); CHECK(error.is_error()); promise.set_error(std::move(error)); } void MessagesManager::on_dialog_bots_updated(DialogId dialog_id, vector bot_user_ids, bool from_database) { if (td_->auth_manager_->is_bot()) { return; } auto d = from_database ? get_dialog(dialog_id) : get_dialog_force(dialog_id, "on_dialog_bots_updated"); if (d == nullptr) { return; } bool has_bots = !bot_user_ids.empty(); if (!d->is_has_bots_inited || d->has_bots != has_bots) { set_dialog_has_bots(d, has_bots); on_dialog_updated(dialog_id, "on_dialog_bots_updated"); } if (d->reply_markup_message_id != MessageId()) { const Message *m = get_message_force(d, d->reply_markup_message_id, "on_dialog_bots_updated"); if (m == nullptr || (m->sender_user_id.is_valid() && !td::contains(bot_user_ids, m->sender_user_id))) { LOG(INFO) << "Remove reply markup in " << dialog_id << ", because bot " << (m == nullptr ? UserId() : m->sender_user_id) << " isn't a member of the chat"; set_dialog_reply_markup(d, MessageId()); } } } void MessagesManager::set_dialog_has_bots(Dialog *d, bool has_bots) { CHECK(d != nullptr); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in set_dialog_has_bots"; LOG(INFO) << "Set " << d->dialog_id << " has_bots to " << has_bots; auto old_skip_bot_commands = need_skip_bot_commands(d->dialog_id, nullptr); d->has_bots = has_bots; d->is_has_bots_inited = true; auto new_skip_bot_commands = need_skip_bot_commands(d->dialog_id, nullptr); if (old_skip_bot_commands != new_skip_bot_commands) { auto it = dialog_bot_command_message_ids_.find(d->dialog_id); if (it != dialog_bot_command_message_ids_.end()) { for (auto message_id : it->second.message_ids) { auto m = get_message(d, message_id); LOG_CHECK(m != nullptr) << d->dialog_id << ' ' << message_id; send_update_message_content_impl(d->dialog_id, m, "set_dialog_has_bots"); } } } } void MessagesManager::on_dialog_photo_updated(DialogId dialog_id) { auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog if (d != nullptr && d->is_update_new_chat_sent) { send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(dialog_id, "updateChatPhoto"), get_chat_photo_info_object(td_->file_manager_.get(), get_dialog_photo(dialog_id)))); } else if (d != nullptr && d->is_update_new_chat_being_sent) { const auto *photo = get_dialog_photo(dialog_id); if (photo == nullptr) { LOG(ERROR) << "Removed photo of " << dialog_id << " while the chat is being added"; } else { LOG(ERROR) << "Changed photo of " << dialog_id << " while the chat is being added to " << *photo; } } } void MessagesManager::on_dialog_accent_colors_updated(DialogId dialog_id) { auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog if (d != nullptr && d->is_update_new_chat_sent) { send_closure( G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(dialog_id, "updateChatAccentColors"), get_dialog_accent_color_id_object(dialog_id), get_dialog_background_custom_emoji_id(dialog_id).get(), get_dialog_profile_accent_color_id_object(dialog_id), get_dialog_profile_background_custom_emoji_id(dialog_id).get())); } } void MessagesManager::on_dialog_title_updated(DialogId dialog_id) { auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog if (d != nullptr) { update_dialogs_hints(d); if (d->is_update_new_chat_sent) { send_closure(G()->td(), &Td::send_update, td_api::make_object(dialog_id.get(), get_dialog_title(dialog_id))); } } } void MessagesManager::on_dialog_emoji_status_updated(DialogId dialog_id) { auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog if (d != nullptr && d->is_update_new_chat_sent) { send_closure( G()->td(), &Td::send_update, td_api::make_object(get_chat_id_object(dialog_id, "updateChatEmojiStatus"), get_dialog_emoji_status_object(dialog_id))); } } void MessagesManager::on_dialog_default_permissions_updated(DialogId dialog_id) { auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog if (d != nullptr && d->is_update_new_chat_sent) { send_closure(G()->td(), &Td::send_update, td_api::make_object( dialog_id.get(), get_dialog_default_permissions(dialog_id).get_chat_permissions_object())); } } void MessagesManager::on_dialog_has_protected_content_updated(DialogId dialog_id) { auto d = get_dialog(dialog_id); // called from update_chat, must not create the dialog if (d != nullptr && d->is_update_new_chat_sent) { send_closure(G()->td(), &Td::send_update, td_api::make_object( dialog_id.get(), get_dialog_has_protected_content(dialog_id))); } } void MessagesManager::on_dialog_user_is_contact_updated(DialogId dialog_id, bool is_contact) { CHECK(dialog_id.get_type() == DialogType::User); auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog if (d != nullptr && d->is_update_new_chat_sent) { if (d->know_action_bar) { if (is_contact) { if (d->action_bar != nullptr && d->action_bar->on_user_contact_added()) { send_update_chat_action_bar(d); } } else { repair_dialog_action_bar(d, "on_dialog_user_is_contact_updated"); } } if (td_->dialog_filter_manager_->have_dialog_filters() && d->order != DEFAULT_ORDER) { update_dialog_lists(d, get_dialog_positions(d), true, false, "on_dialog_user_is_contact_updated"); td_->contacts_manager_->for_each_secret_chat_with_user( d->dialog_id.get_user_id(), [this](SecretChatId secret_chat_id) { DialogId dialog_id(secret_chat_id); auto d = get_dialog(dialog_id); // must not create the dialog if (d != nullptr && d->is_update_new_chat_sent && d->order != DEFAULT_ORDER) { update_dialog_lists(d, get_dialog_positions(d), true, false, "on_dialog_user_is_contact_updated"); } }); } } } void MessagesManager::on_dialog_user_is_deleted_updated(DialogId dialog_id, bool is_deleted) { CHECK(dialog_id.get_type() == DialogType::User); auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog if (d != nullptr && d->is_update_new_chat_sent) { if (d->know_action_bar) { if (is_deleted) { if (d->action_bar != nullptr && d->action_bar->on_user_deleted()) { send_update_chat_action_bar(d); } } else { repair_dialog_action_bar(d, "on_dialog_user_is_deleted_updated"); } } if (td_->dialog_filter_manager_->have_dialog_filters() && d->order != DEFAULT_ORDER) { update_dialog_lists(d, get_dialog_positions(d), true, false, "on_dialog_user_is_deleted_updated"); td_->contacts_manager_->for_each_secret_chat_with_user( dialog_id.get_user_id(), [this](SecretChatId secret_chat_id) { DialogId dialog_id(secret_chat_id); auto d = get_dialog(dialog_id); // must not create the dialog if (d != nullptr && d->is_update_new_chat_sent && d->order != DEFAULT_ORDER) { update_dialog_lists(d, get_dialog_positions(d), true, false, "on_dialog_user_is_deleted_updated"); } }); } if (is_deleted && d->has_bots) { set_dialog_has_bots(d, false); td_->contacts_manager_->for_each_secret_chat_with_user( dialog_id.get_user_id(), [this](SecretChatId secret_chat_id) { DialogId dialog_id(secret_chat_id); auto d = get_dialog(dialog_id); // must not create the dialog if (d != nullptr && d->is_update_new_chat_sent && d->has_bots) { set_dialog_has_bots(d, false); } }); } } } void MessagesManager::on_dialog_linked_channel_updated(DialogId dialog_id, ChannelId old_linked_channel_id, ChannelId new_linked_channel_id) const { CHECK(dialog_id.get_type() == DialogType::Channel); if (td_->auth_manager_->is_bot() || !is_broadcast_channel(dialog_id)) { return; } auto d = get_dialog(dialog_id); // no need to create the dialog if (d == nullptr || !d->is_update_new_chat_sent) { return; } auto message_ids = find_dialog_messages(d, [old_linked_channel_id, new_linked_channel_id](const Message *m) { return !m->reply_info.is_empty() && m->reply_info.channel_id_.is_valid() && (m->reply_info.channel_id_ == old_linked_channel_id || m->reply_info.channel_id_ == new_linked_channel_id); }); LOG(INFO) << "Found discussion messages " << message_ids; for (auto message_id : message_ids) { send_update_message_interaction_info(dialog_id, get_message(d, message_id)); if (message_id == d->last_message_id) { send_update_chat_last_message_impl(d, "on_dialog_linked_channel_updated"); } } } void MessagesManager::send_resolve_dialog_username_query(const string &username, Promise &&promise) { td_->create_handler(std::move(promise))->send(username); } void MessagesManager::resolve_dialog(const string &username, ChannelId channel_id, Promise promise) { CHECK(username.empty() == channel_id.is_valid()); bool have_dialog = username.empty() ? td_->contacts_manager_->have_channel_force(channel_id, "resolve_dialog") : resolve_dialog_username(username).is_valid(); if (!have_dialog) { auto query_promise = PromiseCreator::lambda( [actor_id = actor_id(this), username, channel_id, promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { return promise.set_error(result.move_as_error()); } send_closure(actor_id, &MessagesManager::on_resolve_dialog, username, channel_id, std::move(promise)); }); if (username.empty()) { td_->contacts_manager_->reload_channel(channel_id, std::move(query_promise), "resolve_dialog"); } else { send_resolve_dialog_username_query(username, std::move(query_promise)); } return; } return on_resolve_dialog(username, channel_id, std::move(promise)); } void MessagesManager::on_resolve_dialog(const string &username, ChannelId channel_id, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); DialogId dialog_id; if (username.empty()) { if (!td_->contacts_manager_->have_channel(channel_id)) { return promise.set_error(Status::Error(500, "Chat info not found")); } dialog_id = DialogId(channel_id); force_create_dialog(dialog_id, "on_resolve_dialog"); } else { dialog_id = resolve_dialog_username(username); if (dialog_id.is_valid()) { force_create_dialog(dialog_id, "on_resolve_dialog", true); } } Dialog *d = get_dialog_force(dialog_id, "on_get_message_link_dialog"); if (d == nullptr) { return promise.set_error(Status::Error(500, "Chat not found")); } promise.set_value(std::move(dialog_id)); } DialogId MessagesManager::resolve_dialog_username(const string &username) const { auto cleaned_username = clean_username(username); auto resolved_username = resolved_usernames_.get(cleaned_username); if (resolved_username.dialog_id.is_valid()) { return resolved_username.dialog_id; } return inaccessible_resolved_usernames_.get(cleaned_username); } DialogId MessagesManager::search_public_dialog(const string &username_to_search, bool force, Promise &&promise) { string username = clean_username(username_to_search); if (username[0] == '@') { username = username.substr(1); } if (username.empty()) { promise.set_error(Status::Error(200, "Username is invalid")); return DialogId(); } DialogId dialog_id; auto resolved_username = resolved_usernames_.get(username); if (resolved_username.dialog_id.is_valid()) { if (resolved_username.expires_at < Time::now()) { send_resolve_dialog_username_query(username, Promise()); } dialog_id = resolved_username.dialog_id; } else { dialog_id = inaccessible_resolved_usernames_.get(username); } if (dialog_id.is_valid()) { if (have_input_peer(dialog_id, AccessRights::Read)) { if (!force && reload_voice_chat_on_search_usernames_.count(username)) { reload_voice_chat_on_search_usernames_.erase(username); if (dialog_id.get_type() == DialogType::Channel) { td_->contacts_manager_->reload_channel_full(dialog_id.get_channel_id(), std::move(promise), "search_public_dialog"); return DialogId(); } } if (td_->auth_manager_->is_bot()) { force_create_dialog(dialog_id, "search_public_dialog", true); } else { const Dialog *d = get_dialog_force(dialog_id, "search_public_dialog"); if (!is_dialog_inited(d)) { send_get_dialog_query(dialog_id, std::move(promise), 0, "search_public_dialog"); return DialogId(); } } promise.set_value(Unit()); return dialog_id; } else { // bot username maybe known despite there is no access_hash if (force || dialog_id.get_type() != DialogType::User) { force_create_dialog(dialog_id, "search_public_dialog", true); promise.set_value(Unit()); return dialog_id; } } } send_resolve_dialog_username_query(username, std::move(promise)); return DialogId(); } void MessagesManager::reload_voice_chat_on_search(const string &username) { if (!td_->auth_manager_->is_authorized()) { return; } auto cleaned_username = clean_username(username); if (!cleaned_username.empty()) { reload_voice_chat_on_search_usernames_.insert(cleaned_username); } } class MessagesManager::RegetDialogLogEvent { 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 MessagesManager::save_reget_dialog_log_event(DialogId dialog_id) { RegetDialogLogEvent log_event{dialog_id}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::RegetDialog, get_log_event_storer(log_event)); } void MessagesManager::send_get_dialog_query(DialogId dialog_id, Promise &&promise, uint64 log_event_id, const char *source) { TRY_STATUS_PROMISE(promise, G()->close_status()); if (td_->auth_manager_->is_bot() || dialog_id.get_type() == DialogType::SecretChat) { if (log_event_id != 0) { binlog_erase(G()->td_db()->get_binlog(), log_event_id); } return promise.set_error(Status::Error(500, "Wrong getDialog query")); } if (!have_input_peer(dialog_id, AccessRights::Read)) { if (log_event_id != 0) { binlog_erase(G()->td_db()->get_binlog(), log_event_id); } return promise.set_error(Status::Error(400, "Can't access the chat")); } auto &promises = get_dialog_queries_[dialog_id]; promises.push_back(std::move(promise)); if (promises.size() != 1) { if (log_event_id != 0) { LOG(INFO) << "Duplicate getDialog query for " << dialog_id << " from " << source; binlog_erase(G()->td_db()->get_binlog(), log_event_id); } // query has already been sent, just wait for the result return; } if (log_event_id == 0 && G()->use_message_database()) { log_event_id = save_reget_dialog_log_event(dialog_id); } if (log_event_id != 0) { auto result = get_dialog_query_log_event_id_.emplace(dialog_id, log_event_id); CHECK(result.second); } if (!G()->close_flag()) { LOG(INFO) << "Send get " << dialog_id << " query from " << source; td_->create_handler()->send(dialog_id); } else { // request will be sent after restart } } void MessagesManager::on_get_dialog_query_finished(DialogId dialog_id, Status &&status) { if (G()->close_flag()) { return; } LOG(INFO) << "Finished getting " << dialog_id << " with result " << status; auto it = get_dialog_queries_.find(dialog_id); CHECK(it != get_dialog_queries_.end()); CHECK(!it->second.empty()); auto promises = std::move(it->second); get_dialog_queries_.erase(it); auto log_event_it = get_dialog_query_log_event_id_.find(dialog_id); if (log_event_it != get_dialog_query_log_event_id_.end()) { if (!G()->close_flag()) { binlog_erase(G()->td_db()->get_binlog(), log_event_it->second); } get_dialog_query_log_event_id_.erase(log_event_it); } if (status.is_ok()) { set_promises(promises); } else { fail_promises(promises, std::move(status)); } } void MessagesManager::on_dialog_usernames_updated(DialogId dialog_id, const Usernames &old_usernames, const Usernames &new_usernames) { CHECK(dialog_id.is_valid()); LOG(INFO) << "Update usernames in " << dialog_id << " from " << old_usernames << " to " << new_usernames; message_embedding_codes_[0].erase(dialog_id); message_embedding_codes_[1].erase(dialog_id); for (auto &username : old_usernames.get_active_usernames()) { auto cleaned_username = clean_username(username); resolved_usernames_.erase(cleaned_username); inaccessible_resolved_usernames_.erase(cleaned_username); } on_dialog_usernames_received(dialog_id, new_usernames, false); } void MessagesManager::on_dialog_usernames_received(DialogId dialog_id, const Usernames &usernames, bool from_database) { if (!td_->auth_manager_->is_bot()) { const auto *d = get_dialog(dialog_id); if (d != nullptr) { update_dialogs_hints(d); } } for (auto &username : usernames.get_active_usernames()) { auto cleaned_username = clean_username(username); if (!cleaned_username.empty()) { resolved_usernames_[cleaned_username] = ResolvedUsername{dialog_id, Time::now() + (from_database ? 0 : USERNAME_CACHE_EXPIRE_TIME)}; } } } void MessagesManager::on_resolved_username(const string &username, DialogId dialog_id) { if (!dialog_id.is_valid()) { LOG(ERROR) << "Resolve username \"" << username << "\" to invalid " << dialog_id; return; } auto cleaned_username = clean_username(username); if (cleaned_username.empty()) { return; } auto resolved_username = resolved_usernames_.get(cleaned_username); if (resolved_username.dialog_id.is_valid()) { LOG_IF(ERROR, resolved_username.dialog_id != dialog_id) << "Resolve username \"" << username << "\" to " << dialog_id << ", but have it in " << resolved_username.dialog_id; return; } inaccessible_resolved_usernames_[cleaned_username] = dialog_id; } void MessagesManager::drop_username(const string &username) { auto cleaned_username = clean_username(username); if (cleaned_username.empty()) { return; } inaccessible_resolved_usernames_.erase(cleaned_username); auto resolved_username = resolved_usernames_.get(cleaned_username); if (resolved_username.dialog_id.is_valid()) { auto dialog_id = resolved_username.dialog_id; if (have_input_peer(dialog_id, AccessRights::Read)) { CHECK(dialog_id.get_type() != DialogType::SecretChat); send_get_dialog_query(dialog_id, Auto(), 0, "drop_username"); } resolved_usernames_.erase(cleaned_username); } } const DialogPhoto *MessagesManager::get_dialog_photo(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: return td_->contacts_manager_->get_user_dialog_photo(dialog_id.get_user_id()); case DialogType::Chat: return td_->contacts_manager_->get_chat_dialog_photo(dialog_id.get_chat_id()); case DialogType::Channel: return td_->contacts_manager_->get_channel_dialog_photo(dialog_id.get_channel_id()); case DialogType::SecretChat: return td_->contacts_manager_->get_secret_chat_dialog_photo(dialog_id.get_secret_chat_id()); case DialogType::None: default: UNREACHABLE(); return nullptr; } } int32 MessagesManager::get_dialog_accent_color_id_object(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: return td_->contacts_manager_->get_user_accent_color_id_object(dialog_id.get_user_id()); case DialogType::Chat: return td_->contacts_manager_->get_chat_accent_color_id_object(dialog_id.get_chat_id()); case DialogType::Channel: return td_->contacts_manager_->get_channel_accent_color_id_object(dialog_id.get_channel_id()); case DialogType::SecretChat: return td_->contacts_manager_->get_secret_chat_accent_color_id_object(dialog_id.get_secret_chat_id()); case DialogType::None: default: UNREACHABLE(); return 0; } } CustomEmojiId MessagesManager::get_dialog_background_custom_emoji_id(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: return td_->contacts_manager_->get_user_background_custom_emoji_id(dialog_id.get_user_id()); case DialogType::Chat: return td_->contacts_manager_->get_chat_background_custom_emoji_id(dialog_id.get_chat_id()); case DialogType::Channel: return td_->contacts_manager_->get_channel_background_custom_emoji_id(dialog_id.get_channel_id()); case DialogType::SecretChat: return td_->contacts_manager_->get_secret_chat_background_custom_emoji_id(dialog_id.get_secret_chat_id()); case DialogType::None: default: UNREACHABLE(); return CustomEmojiId(); } } int32 MessagesManager::get_dialog_profile_accent_color_id_object(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: return td_->contacts_manager_->get_user_profile_accent_color_id_object(dialog_id.get_user_id()); case DialogType::Chat: return td_->contacts_manager_->get_chat_profile_accent_color_id_object(dialog_id.get_chat_id()); case DialogType::Channel: return td_->contacts_manager_->get_channel_profile_accent_color_id_object(dialog_id.get_channel_id()); case DialogType::SecretChat: return td_->contacts_manager_->get_secret_chat_profile_accent_color_id_object(dialog_id.get_secret_chat_id()); case DialogType::None: default: UNREACHABLE(); return 0; } } CustomEmojiId MessagesManager::get_dialog_profile_background_custom_emoji_id(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: return td_->contacts_manager_->get_user_profile_background_custom_emoji_id(dialog_id.get_user_id()); case DialogType::Chat: return td_->contacts_manager_->get_chat_profile_background_custom_emoji_id(dialog_id.get_chat_id()); case DialogType::Channel: return td_->contacts_manager_->get_channel_profile_background_custom_emoji_id(dialog_id.get_channel_id()); case DialogType::SecretChat: return td_->contacts_manager_->get_secret_chat_profile_background_custom_emoji_id(dialog_id.get_secret_chat_id()); case DialogType::None: default: UNREACHABLE(); return CustomEmojiId(); } } string MessagesManager::get_dialog_title(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: return td_->contacts_manager_->get_user_title(dialog_id.get_user_id()); case DialogType::Chat: return td_->contacts_manager_->get_chat_title(dialog_id.get_chat_id()); case DialogType::Channel: return td_->contacts_manager_->get_channel_title(dialog_id.get_channel_id()); case DialogType::SecretChat: return td_->contacts_manager_->get_secret_chat_title(dialog_id.get_secret_chat_id()); case DialogType::None: default: UNREACHABLE(); return string(); } } RestrictedRights MessagesManager::get_dialog_default_permissions(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: return td_->contacts_manager_->get_user_default_permissions(dialog_id.get_user_id()); case DialogType::Chat: return td_->contacts_manager_->get_chat_default_permissions(dialog_id.get_chat_id()); case DialogType::Channel: return td_->contacts_manager_->get_channel_default_permissions(dialog_id.get_channel_id()); case DialogType::SecretChat: return td_->contacts_manager_->get_secret_chat_default_permissions(dialog_id.get_secret_chat_id()); case DialogType::None: default: UNREACHABLE(); return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, ChannelType::Unknown); } } td_api::object_ptr MessagesManager::get_dialog_emoji_status_object(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: return td_->contacts_manager_->get_user_emoji_status_object(dialog_id.get_user_id()); case DialogType::Chat: return td_->contacts_manager_->get_chat_emoji_status_object(dialog_id.get_chat_id()); case DialogType::Channel: return td_->contacts_manager_->get_channel_emoji_status_object(dialog_id.get_channel_id()); case DialogType::SecretChat: return td_->contacts_manager_->get_secret_chat_emoji_status_object(dialog_id.get_secret_chat_id()); case DialogType::None: default: UNREACHABLE(); return 0; } } bool MessagesManager::get_dialog_has_protected_content(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: return false; case DialogType::Chat: return td_->contacts_manager_->get_chat_has_protected_content(dialog_id.get_chat_id()); case DialogType::Channel: return td_->contacts_manager_->get_channel_has_protected_content(dialog_id.get_channel_id()); case DialogType::SecretChat: return false; case DialogType::None: default: UNREACHABLE(); return true; } } bool MessagesManager::get_dialog_view_as_topics(const Dialog *d) const { return !d->view_as_messages && d->is_forum; } bool MessagesManager::get_dialog_has_scheduled_messages(const Dialog *d) const { if (!have_input_peer(d->dialog_id, AccessRights::Read)) { return false; } if (is_broadcast_channel(d->dialog_id) && !td_->contacts_manager_->get_channel_status(d->dialog_id.get_channel_id()).can_post_messages()) { return false; } // TODO send updateChatHasScheduledMessage when can_post_messages changes return d->has_scheduled_server_messages || d->has_scheduled_database_messages || have_dialog_scheduled_messages_in_memory(d); } MessagesManager::DialogScheduledMessages *MessagesManager::add_dialog_scheduled_messages(Dialog *d) { if (d->scheduled_messages == nullptr) { d->scheduled_messages = make_unique(); } return d->scheduled_messages.get(); } MessagesManager::NotificationInfo *MessagesManager::add_dialog_notification_info(Dialog *d) { if (d->notification_info == nullptr) { d->notification_info = make_unique(); } return d->notification_info.get(); } bool MessagesManager::is_dialog_action_unneeded(DialogId dialog_id) const { if (is_anonymous_administrator(dialog_id, nullptr)) { return true; } auto dialog_type = dialog_id.get_type(); if (dialog_type == DialogType::User || dialog_type == DialogType::SecretChat) { UserId user_id = dialog_type == DialogType::User ? dialog_id.get_user_id() : td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (td_->contacts_manager_->is_user_deleted(user_id)) { return true; } if (td_->contacts_manager_->is_user_bot(user_id) && !td_->contacts_manager_->is_user_support(user_id)) { return true; } if (user_id == td_->contacts_manager_->get_my_id()) { return true; } if (!td_->auth_manager_->is_bot()) { if (td_->contacts_manager_->is_user_status_exact(user_id)) { if (!td_->contacts_manager_->is_user_online(user_id, 30)) { return true; } } else { // return true; } } } return false; } void MessagesManager::send_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, DialogAction action, Promise &&promise) { if (!have_dialog_force(dialog_id, "send_dialog_action")) { return promise.set_error(Status::Error(400, "Chat not found")); } if (top_thread_message_id != MessageId() && (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server())) { return promise.set_error(Status::Error(400, "Invalid message thread specified")); } if (is_forum_channel(dialog_id) && !top_thread_message_id.is_valid()) { top_thread_message_id = MessageId(ServerMessageId(1)); } tl_object_ptr input_peer; if (action == DialogAction::get_speaking_action()) { input_peer = get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return promise.set_error(Status::Error(400, "Have no access to the chat")); } } else { auto can_send_status = can_send_message(dialog_id); if (can_send_status.is_error()) { if (td_->auth_manager_->is_bot()) { return promise.set_error(std::move(can_send_status)); } return promise.set_value(Unit()); } if (is_dialog_action_unneeded(dialog_id)) { LOG(INFO) << "Skip unneeded " << action << " in " << dialog_id; return promise.set_value(Unit()); } input_peer = get_input_peer(dialog_id, AccessRights::Write); } if (dialog_id.get_type() == DialogType::SecretChat) { send_closure(G()->secret_chats_manager(), &SecretChatsManager::send_message_action, dialog_id.get_secret_chat_id(), action.get_secret_input_send_message_action()); promise.set_value(Unit()); return; } auto new_query_ref = td_->create_handler(std::move(promise)) ->send(dialog_id, std::move(input_peer), top_thread_message_id, action.get_input_send_message_action()); if (td_->auth_manager_->is_bot()) { return; } auto &query_ref = set_typing_query_[dialog_id]; if (!query_ref.empty()) { LOG(INFO) << "Cancel previous send chat action query"; cancel_query(query_ref); } query_ref = std::move(new_query_ref); } void MessagesManager::after_set_typing_query(DialogId dialog_id, int32 generation) { auto it = set_typing_query_.find(dialog_id); if (it != set_typing_query_.end() && (!it->second.is_alive() || it->second.generation() == generation)) { set_typing_query_.erase(it); } } void MessagesManager::on_send_dialog_action_timeout(DialogId dialog_id) { LOG(INFO) << "Receive send_chat_action timeout in " << dialog_id; Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); if (can_send_message(dialog_id).is_error()) { return; } auto queue_id = ChainId(dialog_id, MessageContentType::Photo).get(); CHECK(queue_id & 1); auto queue_it = yet_unsent_media_queues_.find(queue_id); if (queue_it == yet_unsent_media_queues_.end()) { return; } pending_send_dialog_action_timeout_.add_timeout_in(dialog_id.get(), 4.0); auto &queue = queue_it->second.queue_; CHECK(!queue.empty()); const Message *m = get_message(d, queue.begin()->first); if (m == nullptr) { return; } CHECK(m->message_id.is_yet_unsent()); if (m->forward_info != nullptr || m->had_forward_info || m->is_copy || m->message_id.is_scheduled() || m->sender_dialog_id.is_valid()) { return; } auto file_id = get_message_content_upload_file_id(m->content.get()); if (!file_id.is_valid()) { LOG(ERROR) << "Have no file in " << to_string(get_message_message_content_object(dialog_id, m)); return; } auto file_view = td_->file_manager_->get_file_view(file_id); if (!file_view.is_uploading()) { return; } int64 total_size = file_view.expected_size(); int64 uploaded_size = file_view.remote_size(); int32 progress = 0; if (total_size > 0 && uploaded_size > 0) { if (uploaded_size > total_size) { uploaded_size = total_size; // just in case } progress = static_cast(100 * uploaded_size / total_size); } DialogAction action = DialogAction::get_uploading_action(m->content->get_type(), progress); if (action == DialogAction()) { return; } LOG(INFO) << "Send " << action << " in " << dialog_id; send_dialog_action(dialog_id, m->top_thread_message_id, std::move(action), Promise()); } void MessagesManager::on_active_dialog_action_timeout(DialogId dialog_id) { LOG(DEBUG) << "Receive active dialog action timeout in " << dialog_id; auto actions_it = active_dialog_actions_.find(dialog_id); if (actions_it == active_dialog_actions_.end()) { return; } CHECK(!actions_it->second.empty()); auto now = Time::now(); DialogId prev_typing_dialog_id; while (actions_it->second[0].start_time + DIALOG_ACTION_TIMEOUT < now + 0.1) { CHECK(actions_it->second[0].typing_dialog_id != prev_typing_dialog_id); prev_typing_dialog_id = actions_it->second[0].typing_dialog_id; on_dialog_action(dialog_id, actions_it->second[0].top_thread_message_id, actions_it->second[0].typing_dialog_id, DialogAction(), 0); actions_it = active_dialog_actions_.find(dialog_id); if (actions_it == active_dialog_actions_.end()) { return; } CHECK(!actions_it->second.empty()); } LOG(DEBUG) << "Schedule next action timeout in " << dialog_id; active_dialog_action_timeout_.add_timeout_in(dialog_id.get(), actions_it->second[0].start_time + DIALOG_ACTION_TIMEOUT - now); } void MessagesManager::clear_active_dialog_actions(DialogId dialog_id) { LOG(DEBUG) << "Clear active dialog actions in " << dialog_id; auto actions_it = active_dialog_actions_.find(dialog_id); while (actions_it != active_dialog_actions_.end()) { CHECK(!actions_it->second.empty()); on_dialog_action(dialog_id, actions_it->second[0].top_thread_message_id, actions_it->second[0].typing_dialog_id, DialogAction(), 0); actions_it = active_dialog_actions_.find(dialog_id); } } void MessagesManager::get_dialog_filter_dialog_count(td_api::object_ptr filter, Promise &&promise) { TRY_RESULT_PROMISE(promise, dialog_filter, DialogFilter::create_dialog_filter(td_, DialogFilterId(), std::move(filter))); int32 total_count = 0; for (auto folder_id : dialog_filter->get_folder_ids()) { const auto &folder = *get_dialog_folder(folder_id); for (const auto &dialog_date : folder.ordered_dialogs_) { if (dialog_date.get_order() == DEFAULT_ORDER) { break; } auto dialog_id = dialog_date.get_dialog_id(); if (dialog_filter->need_dialog(td_, get_dialog_info_for_dialog_filter(get_dialog(dialog_id)))) { total_count++; } } } promise.set_value(std::move(total_count)); } void MessagesManager::add_dialog_list_for_dialog_filter(DialogFilterId dialog_filter_id) { DialogListId dialog_list_id(dialog_filter_id); CHECK(dialog_lists_.count(dialog_list_id) == 0); auto &list = add_dialog_list(dialog_list_id); auto folder_ids = get_dialog_list_folder_ids(list); CHECK(!folder_ids.empty()); for (auto folder_id : folder_ids) { auto *folder = get_dialog_folder(folder_id); CHECK(folder != nullptr); for (const auto &dialog_date : folder->ordered_dialogs_) { if (dialog_date.get_order() == DEFAULT_ORDER) { break; } auto dialog_id = dialog_date.get_dialog_id(); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); if (need_dialog_in_list(d, list)) { list.in_memory_dialog_total_count_++; add_dialog_to_list(d, dialog_list_id); } } } auto pinned_dialog_ids = td_->dialog_filter_manager_->get_pinned_dialog_ids(dialog_filter_id); for (const auto &dialog_id : reversed(pinned_dialog_ids)) { if (!dialog_id.is_valid()) { continue; } auto order = get_next_pinned_dialog_order(); list.pinned_dialogs_.emplace_back(order, dialog_id); list.pinned_dialog_id_orders_.emplace(dialog_id, order); } std::reverse(list.pinned_dialogs_.begin(), list.pinned_dialogs_.end()); list.are_pinned_dialogs_inited_ = true; update_list_last_pinned_dialog_date(list); update_list_last_dialog_date(list); } void MessagesManager::edit_dialog_list_for_dialog_filter(unique_ptr &old_dialog_filter, unique_ptr new_dialog_filter, bool &disable_get_dialog_filter, const char *source) { CHECK(old_dialog_filter != nullptr); CHECK(new_dialog_filter != nullptr); auto dialog_list_id = DialogListId(old_dialog_filter->get_dialog_filter_id()); auto *old_list_ptr = get_dialog_list(dialog_list_id); CHECK(old_list_ptr != nullptr); auto &old_list = *old_list_ptr; auto folder_ids = old_dialog_filter->get_folder_ids(); CHECK(!folder_ids.empty()); for (auto folder_id : new_dialog_filter->get_folder_ids()) { if (!td::contains(folder_ids, folder_id)) { folder_ids.push_back(folder_id); } } DialogList new_list; new_list.dialog_list_id = dialog_list_id; auto old_it = old_list.pinned_dialogs_.rbegin(); for (const auto &input_dialog_id : reversed(new_dialog_filter->get_pinned_input_dialog_ids())) { auto dialog_id = input_dialog_id.get_dialog_id(); if (!dialog_id.is_valid()) { continue; } while (old_it < old_list.pinned_dialogs_.rend()) { if (old_it->get_dialog_id() == dialog_id) { break; } ++old_it; } int64 order; if (old_it < old_list.pinned_dialogs_.rend()) { order = old_it->get_order(); ++old_it; } else { order = get_next_pinned_dialog_order(); } new_list.pinned_dialogs_.emplace_back(order, dialog_id); new_list.pinned_dialog_id_orders_.emplace(dialog_id, order); } std::reverse(new_list.pinned_dialogs_.begin(), new_list.pinned_dialogs_.end()); new_list.are_pinned_dialogs_inited_ = true; do_update_list_last_pinned_dialog_date(new_list); do_update_list_last_dialog_date(new_list, new_dialog_filter->get_folder_ids()); new_list.server_dialog_total_count_ = 0; new_list.secret_chat_total_count_ = 0; std::map updated_position_dialogs; for (auto folder_id : folder_ids) { auto *folder = get_dialog_folder(folder_id); CHECK(folder != nullptr); for (const auto &dialog_date : folder->ordered_dialogs_) { if (dialog_date.get_order() == DEFAULT_ORDER) { break; } auto dialog_id = dialog_date.get_dialog_id(); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); const DialogPositionInList old_position = get_dialog_position_in_list(old_list_ptr, d); // can't use get_dialog_position_in_list, because need_dialog_in_list calls get_dialog_filter DialogPositionInList new_position; if (new_dialog_filter->need_dialog(td_, get_dialog_info_for_dialog_filter(d))) { new_position.private_order = get_dialog_private_order(&new_list, d); if (new_position.private_order != 0) { new_position.public_order = DialogDate(new_position.private_order, dialog_id) <= new_list.list_last_dialog_date_ ? new_position.private_order : 0; new_position.is_pinned = get_dialog_pinned_order(&new_list, dialog_id) != DEFAULT_ORDER; new_position.is_sponsored = is_dialog_sponsored(d); } } if (need_send_update_chat_position(old_position, new_position)) { updated_position_dialogs.emplace(DialogDate(new_position.public_order, dialog_id), d); } bool was_in_list = old_position.private_order != 0; bool is_in_list = new_position.private_order != 0; if (is_in_list) { if (!was_in_list) { add_dialog_to_list(d, dialog_list_id); } new_list.in_memory_dialog_total_count_++; if (dialog_id.get_type() == DialogType::SecretChat) { new_list.secret_chat_total_count_++; } else { new_list.server_dialog_total_count_++; } auto unread_count = d->server_unread_count + d->local_unread_count; if (unread_count != 0) { new_list.unread_message_total_count_ += unread_count; if (is_dialog_muted(d)) { new_list.unread_message_muted_count_ += unread_count; } } if (unread_count != 0 || d->is_marked_as_unread) { new_list.unread_dialog_total_count_++; if (unread_count == 0 && d->is_marked_as_unread) { new_list.unread_dialog_marked_count_++; } if (is_dialog_muted(d)) { new_list.unread_dialog_muted_count_++; if (unread_count == 0 && d->is_marked_as_unread) { new_list.unread_dialog_muted_marked_count_++; } } } } else { if (was_in_list) { remove_dialog_from_list(d, dialog_list_id); } } } } if (new_list.list_last_dialog_date_ == MAX_DIALOG_DATE) { new_list.is_message_unread_count_inited_ = true; new_list.is_dialog_unread_count_inited_ = true; new_list.need_unread_count_recalc_ = false; } else { if (old_list.is_message_unread_count_inited_) { // can't stop sending updates new_list.is_message_unread_count_inited_ = true; } if (old_list.is_dialog_unread_count_inited_) { // can't stop sending updates new_list.is_dialog_unread_count_inited_ = true; } new_list.server_dialog_total_count_ = -1; new_list.secret_chat_total_count_ = -1; } bool need_update_unread_message_count = new_list.is_message_unread_count_inited_ && (old_list.unread_message_total_count_ != new_list.unread_message_total_count_ || old_list.unread_message_muted_count_ != new_list.unread_message_muted_count_ || !old_list.is_message_unread_count_inited_); bool need_update_unread_chat_count = new_list.is_dialog_unread_count_inited_ && (old_list.unread_dialog_total_count_ != new_list.unread_dialog_total_count_ || old_list.unread_dialog_muted_count_ != new_list.unread_dialog_muted_count_ || old_list.unread_dialog_marked_count_ != new_list.unread_dialog_marked_count_ || old_list.unread_dialog_muted_marked_count_ != new_list.unread_dialog_muted_marked_count_ || get_dialog_total_count(old_list) != get_dialog_total_count(new_list) || !old_list.is_dialog_unread_count_inited_); bool need_save_unread_chat_count = new_list.is_dialog_unread_count_inited_ && (old_list.server_dialog_total_count_ != new_list.server_dialog_total_count_ || old_list.secret_chat_total_count_ != new_list.secret_chat_total_count_); auto load_list_promises = std::move(old_list.load_list_queries_); old_list = std::move(new_list); old_dialog_filter = std::move(new_dialog_filter); disable_get_dialog_filter = false; if (need_update_unread_message_count) { send_update_unread_message_count(old_list, DialogId(), true, source); } if (need_update_unread_chat_count) { send_update_unread_chat_count(old_list, DialogId(), true, source); } else if (need_save_unread_chat_count) { save_unread_chat_count(old_list); } for (const auto &it : updated_position_dialogs) { send_update_chat_position(dialog_list_id, it.second, source); } if (old_list.need_unread_count_recalc_) { // repair unread count get_dialogs_from_list(dialog_list_id, static_cast(old_list.pinned_dialogs_.size() + 2), Auto()); } if (!load_list_promises.empty()) { LOG(INFO) << "Retry loading of chats in " << dialog_list_id; set_promises(load_list_promises); // try again } } void MessagesManager::delete_dialog_list_for_dialog_filter(DialogFilterId dialog_filter_id, const char *source) { auto dialog_list_id = DialogListId(dialog_filter_id); auto *list = get_dialog_list(dialog_list_id); CHECK(list != nullptr); auto folder_ids = get_dialog_list_folder_ids(*list); CHECK(!folder_ids.empty()); for (auto folder_id : folder_ids) { auto *folder = get_dialog_folder(folder_id); CHECK(folder != nullptr); for (const auto &dialog_date : folder->ordered_dialogs_) { if (dialog_date.get_order() == DEFAULT_ORDER) { break; } auto dialog_id = dialog_date.get_dialog_id(); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); const DialogPositionInList old_position = get_dialog_position_in_list(list, d); if (is_dialog_in_list(d, dialog_list_id)) { remove_dialog_from_list(d, dialog_list_id); if (old_position.public_order != 0) { send_update_chat_position(dialog_list_id, d, source); } } } } if (G()->use_message_database()) { postponed_unread_message_count_updates_.erase(dialog_list_id); postponed_unread_chat_count_updates_.erase(dialog_list_id); if (list->is_message_unread_count_inited_) { list->unread_message_total_count_ = 0; list->unread_message_muted_count_ = 0; send_update_unread_message_count(*list, DialogId(), true, source, true); G()->td_db()->get_binlog_pmc()->erase(PSTRING() << "unread_message_count" << dialog_list_id.get()); } if (list->is_dialog_unread_count_inited_) { list->unread_dialog_total_count_ = 0; list->unread_dialog_muted_count_ = 0; list->unread_dialog_marked_count_ = 0; list->unread_dialog_muted_marked_count_ = 0; list->in_memory_dialog_total_count_ = 0; list->server_dialog_total_count_ = 0; list->secret_chat_total_count_ = 0; send_update_unread_chat_count(*list, DialogId(), true, source, true); G()->td_db()->get_binlog_pmc()->erase(PSTRING() << "unread_dialog_count" << dialog_list_id.get()); } } fail_promises(list->load_list_queries_, Status::Error(400, "Chat list not found")); dialog_lists_.erase(dialog_list_id); } vector MessagesManager::get_dialog_lists_to_add_dialog(DialogId dialog_id) { vector result; if (can_add_dialog_to_filter(dialog_id).is_error()) { return result; } if (dialog_id != get_my_dialog_id() && dialog_id != DialogId(ContactsManager::get_service_notifications_user_id())) { result.push_back( DialogListId(get_dialog(dialog_id)->folder_id == FolderId::archive() ? FolderId::main() : FolderId::archive())); } for (auto dialog_filter_id : td_->dialog_filter_manager_->get_dialog_filters_to_add_dialog(dialog_id)) { result.push_back(DialogListId(dialog_filter_id)); } return result; } void MessagesManager::add_dialog_to_list(DialogId dialog_id, DialogListId dialog_list_id, Promise &&promise) { LOG(INFO) << "Receive addChatToList request to add " << dialog_id << " to " << dialog_list_id; CHECK(!td_->auth_manager_->is_bot()); Dialog *d = get_dialog_force(dialog_id, "add_dialog_to_list"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } if (d->order == DEFAULT_ORDER) { return promise.set_error(Status::Error(400, "Chat is not in a chat list")); } if (get_dialog_list(dialog_list_id) == nullptr) { return promise.set_error(Status::Error(400, "Chat list not found")); } if (dialog_list_id.is_filter()) { return promise.set_result( td_->dialog_filter_manager_->add_dialog(dialog_list_id.get_filter_id(), get_input_dialog_id(dialog_id))); } CHECK(dialog_list_id.is_folder()); auto folder_id = dialog_list_id.get_folder_id(); if (d->folder_id == folder_id) { return promise.set_value(Unit()); } if (folder_id == FolderId::archive() && (dialog_id == get_my_dialog_id() || dialog_id == DialogId(ContactsManager::get_service_notifications_user_id()))) { return promise.set_error(Status::Error(400, "Chat can't be archived")); } set_dialog_folder_id(d, folder_id); if (dialog_id.get_type() != DialogType::SecretChat) { set_dialog_folder_id_on_server(dialog_id, false); } promise.set_value(Unit()); } class MessagesManager::SetDialogFolderIdOnServerLogEvent { public: DialogId dialog_id_; FolderId folder_id_; template void store(StorerT &storer) const { td::store(dialog_id_, storer); td::store(folder_id_, storer); } template void parse(ParserT &parser) { td::parse(dialog_id_, parser); td::parse(folder_id_, parser); } }; void MessagesManager::set_dialog_folder_id_on_server(DialogId dialog_id, bool from_binlog) { auto d = get_dialog(dialog_id); CHECK(d != nullptr); if (!from_binlog && G()->use_message_database()) { SetDialogFolderIdOnServerLogEvent log_event; log_event.dialog_id_ = dialog_id; log_event.folder_id_ = d->folder_id; add_log_event(d->set_folder_id_log_event_id, get_log_event_storer(log_event), LogEvent::HandlerType::SetDialogFolderIdOnServer, "set chat folder"); } Promise promise; if (d->set_folder_id_log_event_id.log_event_id != 0) { d->set_folder_id_log_event_id.generation++; promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, generation = d->set_folder_id_log_event_id.generation](Result result) { if (!G()->close_flag()) { send_closure(actor_id, &MessagesManager::on_updated_dialog_folder_id, dialog_id, generation); } }); } // TODO do not send two queries simultaneously or use InvokeAfter td_->create_handler(std::move(promise))->send(dialog_id, d->folder_id); } void MessagesManager::on_updated_dialog_folder_id(DialogId dialog_id, uint64 generation) { auto d = get_dialog(dialog_id); CHECK(d != nullptr); delete_log_event(d->set_folder_id_log_event_id, generation, "set chat folder"); } void MessagesManager::set_dialog_photo(DialogId dialog_id, const tl_object_ptr &input_photo, Promise &&promise) { if (!have_dialog_force(dialog_id, "set_dialog_photo")) { return promise.set_error(Status::Error(400, "Chat not found")); } switch (dialog_id.get_type()) { case DialogType::User: return promise.set_error(Status::Error(400, "Can't change private chat photo")); case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); auto status = td_->contacts_manager_->get_chat_permissions(chat_id); if (!status.can_change_info_and_settings() || (td_->auth_manager_->is_bot() && !td_->contacts_manager_->is_appointed_chat_administrator(chat_id))) { return promise.set_error(Status::Error(400, "Not enough rights to change chat photo")); } break; } case DialogType::Channel: { auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()); if (!status.can_change_info_and_settings()) { return promise.set_error(Status::Error(400, "Not enough rights to change chat photo")); } break; } case DialogType::SecretChat: return promise.set_error(Status::Error(400, "Can't change secret chat photo")); case DialogType::None: default: UNREACHABLE(); } const td_api::object_ptr *input_file = nullptr; double main_frame_timestamp = 0.0; bool is_animation = false; if (input_photo != nullptr) { switch (input_photo->get_id()) { case td_api::inputChatPhotoPrevious::ID: { auto photo = static_cast(input_photo.get()); auto file_id = td_->contacts_manager_->get_profile_photo_file_id(photo->chat_photo_id_); if (!file_id.is_valid()) { return promise.set_error(Status::Error(400, "Unknown profile photo ID specified")); } auto file_view = td_->file_manager_->get_file_view(file_id); auto input_chat_photo = make_tl_object(file_view.main_remote_location().as_input_photo()); return send_edit_dialog_photo_query(dialog_id, file_id, std::move(input_chat_photo), std::move(promise)); } case td_api::inputChatPhotoStatic::ID: { auto photo = static_cast(input_photo.get()); input_file = &photo->photo_; break; } case td_api::inputChatPhotoAnimation::ID: { auto photo = static_cast(input_photo.get()); input_file = &photo->animation_; main_frame_timestamp = photo->main_frame_timestamp_; is_animation = true; break; } case td_api::inputChatPhotoSticker::ID: { auto photo = static_cast(input_photo.get()); TRY_RESULT_PROMISE(promise, sticker_photo_size, StickerPhotoSize::get_sticker_photo_size(td_, photo->sticker_)); int32 flags = telegram_api::inputChatUploadedPhoto::VIDEO_EMOJI_MARKUP_MASK; auto input_chat_photo = make_tl_object( flags, nullptr, nullptr, 0.0, sticker_photo_size->get_input_video_size_object(td_)); return send_edit_dialog_photo_query(dialog_id, FileId(), std::move(input_chat_photo), std::move(promise)); } default: UNREACHABLE(); break; } } if (input_file == nullptr) { send_edit_dialog_photo_query(dialog_id, FileId(), make_tl_object(), std::move(promise)); return; } const double MAX_ANIMATION_DURATION = 10.0; if (main_frame_timestamp < 0.0 || main_frame_timestamp > MAX_ANIMATION_DURATION) { return promise.set_error(Status::Error(400, "Wrong main frame timestamp specified")); } auto file_type = is_animation ? FileType::Animation : FileType::Photo; TRY_RESULT_PROMISE(promise, file_id, td_->file_manager_->get_input_file_id(file_type, *input_file, dialog_id, true, false)); if (!file_id.is_valid()) { send_edit_dialog_photo_query(dialog_id, FileId(), make_tl_object(), std::move(promise)); return; } upload_dialog_photo(dialog_id, td_->file_manager_->dup_file_id(file_id, "set_dialog_photo"), is_animation, main_frame_timestamp, false, std::move(promise)); } void MessagesManager::send_edit_dialog_photo_query(DialogId dialog_id, FileId file_id, tl_object_ptr &&input_chat_photo, Promise &&promise) { // TODO invoke after td_->create_handler(std::move(promise))->send(dialog_id, file_id, std::move(input_chat_photo)); } void MessagesManager::upload_dialog_photo(DialogId dialog_id, FileId file_id, bool is_animation, double main_frame_timestamp, bool is_reupload, Promise &&promise, vector bad_parts) { CHECK(file_id.is_valid()); LOG(INFO) << "Ask to upload chat photo " << file_id; bool is_inserted = being_uploaded_dialog_photos_ .emplace(file_id, UploadedDialogPhotoInfo{dialog_id, main_frame_timestamp, is_animation, is_reupload, std::move(promise)}) .second; CHECK(is_inserted); // TODO use force_reupload if is_reupload td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_dialog_photo_callback_, 32, 0); } void MessagesManager::set_dialog_accent_color(DialogId dialog_id, AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id, Promise &&promise) { if (!have_dialog_force(dialog_id, "set_dialog_accent_color")) { return promise.set_error(Status::Error(400, "Chat not found")); } switch (dialog_id.get_type()) { case DialogType::User: if (dialog_id == get_my_dialog_id()) { return td_->contacts_manager_->set_accent_color(accent_color_id, background_custom_emoji_id, std::move(promise)); } break; case DialogType::Chat: break; case DialogType::Channel: return td_->contacts_manager_->set_channel_accent_color(dialog_id.get_channel_id(), accent_color_id, background_custom_emoji_id, std::move(promise)); case DialogType::SecretChat: break; case DialogType::None: default: UNREACHABLE(); } promise.set_error(Status::Error(400, "Can't change accent color in the chat")); } void MessagesManager::set_dialog_profile_accent_color(DialogId dialog_id, AccentColorId profile_accent_color_id, CustomEmojiId profile_background_custom_emoji_id, Promise &&promise) { if (!have_dialog_force(dialog_id, "set_dialog_profile_accent_color")) { return promise.set_error(Status::Error(400, "Chat not found")); } switch (dialog_id.get_type()) { case DialogType::User: if (dialog_id == get_my_dialog_id()) { return td_->contacts_manager_->set_profile_accent_color(profile_accent_color_id, profile_background_custom_emoji_id, std::move(promise)); } break; case DialogType::Chat: break; case DialogType::Channel: return td_->contacts_manager_->set_channel_profile_accent_color( dialog_id.get_channel_id(), profile_accent_color_id, profile_background_custom_emoji_id, std::move(promise)); case DialogType::SecretChat: break; case DialogType::None: default: UNREACHABLE(); } promise.set_error(Status::Error(400, "Can't change profile accent color in the chat")); } void MessagesManager::set_dialog_title(DialogId dialog_id, const string &title, Promise &&promise) { if (!have_dialog_force(dialog_id, "set_dialog_title")) { return promise.set_error(Status::Error(400, "Chat not found")); } auto new_title = clean_name(title, MAX_TITLE_LENGTH); if (new_title.empty()) { return promise.set_error(Status::Error(400, "Title must be non-empty")); } switch (dialog_id.get_type()) { case DialogType::User: return promise.set_error(Status::Error(400, "Can't change private chat title")); case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); auto status = td_->contacts_manager_->get_chat_permissions(chat_id); if (!status.can_change_info_and_settings() || (td_->auth_manager_->is_bot() && !td_->contacts_manager_->is_appointed_chat_administrator(chat_id))) { return promise.set_error(Status::Error(400, "Not enough rights to change chat title")); } break; } case DialogType::Channel: { auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()); if (!status.can_change_info_and_settings()) { return promise.set_error(Status::Error(400, "Not enough rights to change chat title")); } break; } case DialogType::SecretChat: return promise.set_error(Status::Error(400, "Can't change secret chat title")); case DialogType::None: default: UNREACHABLE(); } // TODO this can be wrong if there were previous change title requests if (get_dialog_title(dialog_id) == new_title) { return promise.set_value(Unit()); } // TODO invoke after td_->create_handler(std::move(promise))->send(dialog_id, new_title); } void MessagesManager::set_dialog_available_reactions( DialogId dialog_id, td_api::object_ptr &&available_reactions_ptr, Promise &&promise) { Dialog *d = get_dialog_force(dialog_id, "set_dialog_available_reactions"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } ChatReactions available_reactions(std::move(available_reactions_ptr), !is_broadcast_channel(dialog_id)); auto active_reactions = get_active_reactions(available_reactions); if (active_reactions.reaction_types_.size() != available_reactions.reaction_types_.size()) { return promise.set_error(Status::Error(400, "Invalid reactions specified")); } available_reactions = std::move(active_reactions); switch (dialog_id.get_type()) { case DialogType::User: return promise.set_error(Status::Error(400, "Can't change private chat available reactions")); case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); auto status = td_->contacts_manager_->get_chat_permissions(chat_id); if (!status.can_change_info_and_settings() || (td_->auth_manager_->is_bot() && !td_->contacts_manager_->is_appointed_chat_administrator(chat_id))) { return promise.set_error(Status::Error(400, "Not enough rights to change chat available reactions")); } break; } case DialogType::Channel: { auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()); if (!status.can_change_info_and_settings()) { return promise.set_error(Status::Error(400, "Not enough rights to change chat available reactions")); } break; } case DialogType::SecretChat: return promise.set_error(Status::Error(400, "Can't change secret chat available reactions")); case DialogType::None: default: UNREACHABLE(); } bool is_changed = d->available_reactions != available_reactions; set_dialog_available_reactions(d, ChatReactions(available_reactions)); if (!is_changed) { return promise.set_value(Unit()); } // TODO invoke after td_->create_handler(std::move(promise)) ->send(dialog_id, std::move(available_reactions)); } void MessagesManager::set_dialog_message_ttl(DialogId dialog_id, int32 ttl, Promise &&promise) { if (ttl < 0) { return promise.set_error(Status::Error(400, "Message auto-delete time can't be negative")); } Dialog *d = get_dialog_force(dialog_id, "set_dialog_message_ttl"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Write)) { return promise.set_error(Status::Error(400, "Have no write access to the chat")); } switch (dialog_id.get_type()) { case DialogType::User: if (dialog_id == get_my_dialog_id() || dialog_id == DialogId(ContactsManager::get_service_notifications_user_id())) { return promise.set_error(Status::Error(400, "Message auto-delete time in the chat can't be changed")); } break; case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); auto status = td_->contacts_manager_->get_chat_permissions(chat_id); if (!status.can_change_info_and_settings()) { return promise.set_error( Status::Error(400, "Not enough rights to change message auto-delete time in the chat")); } break; } case DialogType::Channel: { auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()); if (!status.can_change_info_and_settings()) { return promise.set_error( Status::Error(400, "Not enough rights to change message auto-delete time in the chat")); } break; } case DialogType::SecretChat: break; case DialogType::None: default: UNREACHABLE(); } if (dialog_id.get_type() != DialogType::SecretChat) { // TODO invoke after td_->create_handler(std::move(promise))->send(dialog_id, ttl); } else { bool need_update_dialog_pos = false; Message *m = get_message_to_send(d, MessageId(), MessageInputReplyTo(), MessageSendOptions(), create_chat_set_ttl_message_content(ttl, UserId()), false, &need_update_dialog_pos); send_update_new_message(d, m); if (need_update_dialog_pos) { send_update_chat_last_message(d, "set_dialog_message_ttl"); } int64 random_id = begin_send_message(dialog_id, m); send_closure(td_->secret_chats_manager_, &SecretChatsManager::send_set_ttl_message, dialog_id.get_secret_chat_id(), ttl, random_id, std::move(promise)); } } void MessagesManager::set_dialog_emoji_status(DialogId dialog_id, const EmojiStatus &emoji_status, Promise &&promise) { if (!have_dialog_force(dialog_id, "set_dialog_emoji_status")) { return promise.set_error(Status::Error(400, "Chat not found")); } switch (dialog_id.get_type()) { case DialogType::User: if (dialog_id == get_my_dialog_id()) { return td_->contacts_manager_->set_emoji_status(emoji_status, std::move(promise)); } break; case DialogType::Chat: break; case DialogType::Channel: return td_->contacts_manager_->set_channel_emoji_status(dialog_id.get_channel_id(), emoji_status, std::move(promise)); case DialogType::SecretChat: break; case DialogType::None: default: UNREACHABLE(); } promise.set_error(Status::Error(400, "Can't change emoji status in the chat")); } void MessagesManager::set_dialog_permissions(DialogId dialog_id, const td_api::object_ptr &permissions, Promise &&promise) { if (!have_dialog_force(dialog_id, "set_dialog_permissions")) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Write)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } if (permissions == nullptr) { return promise.set_error(Status::Error(400, "New permissions must be non-empty")); } ChannelType channel_type = ChannelType::Unknown; switch (dialog_id.get_type()) { case DialogType::User: return promise.set_error(Status::Error(400, "Can't change private chat permissions")); case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); auto status = td_->contacts_manager_->get_chat_permissions(chat_id); if (!status.can_restrict_members()) { return promise.set_error(Status::Error(400, "Not enough rights to change chat permissions")); } break; } case DialogType::Channel: { if (is_broadcast_channel(dialog_id)) { return promise.set_error(Status::Error(400, "Can't change channel chat permissions")); } auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()); if (!status.can_restrict_members()) { return promise.set_error(Status::Error(400, "Not enough rights to change chat permissions")); } channel_type = ChannelType::Megagroup; break; } case DialogType::SecretChat: return promise.set_error(Status::Error(400, "Can't change secret chat permissions")); case DialogType::None: default: UNREACHABLE(); } RestrictedRights new_permissions(permissions, channel_type); // TODO this can be wrong if there were previous change permissions requests if (get_dialog_default_permissions(dialog_id) == new_permissions) { return promise.set_value(Unit()); } // TODO invoke after td_->create_handler(std::move(promise))->send(dialog_id, new_permissions); } void MessagesManager::toggle_dialog_has_protected_content(DialogId dialog_id, bool has_protected_content, Promise &&promise) { if (!have_dialog_force(dialog_id, "toggle_dialog_has_protected_content")) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } switch (dialog_id.get_type()) { case DialogType::User: case DialogType::SecretChat: return promise.set_error(Status::Error(400, "Can't restrict saving content in the chat")); case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); auto status = td_->contacts_manager_->get_chat_status(chat_id); if (!status.is_creator()) { return promise.set_error(Status::Error(400, "Only owner can restrict saving content")); } break; } case DialogType::Channel: { auto status = td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()); if (!status.is_creator()) { return promise.set_error(Status::Error(400, "Only owner can restrict saving content")); } break; } case DialogType::None: default: UNREACHABLE(); } // TODO this can be wrong if there were previous toggle_dialog_has_protected_content requests if (get_dialog_has_protected_content(dialog_id) == has_protected_content) { return promise.set_value(Unit()); } // TODO invoke after td_->create_handler(std::move(promise))->send(dialog_id, has_protected_content); } void MessagesManager::set_dialog_theme(DialogId dialog_id, const string &theme_name, Promise &&promise) { auto d = get_dialog_force(dialog_id, "set_dialog_theme"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!have_input_peer(dialog_id, AccessRights::Write)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } switch (dialog_id.get_type()) { case DialogType::User: break; case DialogType::Chat: case DialogType::Channel: return promise.set_error(Status::Error(400, "Can't change theme in the chat")); case DialogType::SecretChat: { auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (!user_id.is_valid()) { return promise.set_error(Status::Error(400, "Can't access the user")); } dialog_id = DialogId(user_id); break; } case DialogType::None: default: UNREACHABLE(); } // TODO this can be wrong if there were previous change theme requests if (get_dialog_theme_name(d) == theme_name) { return promise.set_value(Unit()); } // TODO invoke after td_->create_handler(std::move(promise))->send(dialog_id, theme_name); } void MessagesManager::set_dialog_description(DialogId dialog_id, const string &description, Promise &&promise) { if (!have_dialog_force(dialog_id, "set_dialog_description")) { return promise.set_error(Status::Error(400, "Chat not found")); } switch (dialog_id.get_type()) { case DialogType::User: return promise.set_error(Status::Error(400, "Can't change private chat description")); case DialogType::Chat: return td_->contacts_manager_->set_chat_description(dialog_id.get_chat_id(), description, std::move(promise)); case DialogType::Channel: return td_->contacts_manager_->set_channel_description(dialog_id.get_channel_id(), description, std::move(promise)); case DialogType::SecretChat: return promise.set_error(Status::Error(400, "Can't change secret chat description")); case DialogType::None: default: UNREACHABLE(); } } Status MessagesManager::can_pin_messages(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: break; case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); auto status = td_->contacts_manager_->get_chat_permissions(chat_id); if (!status.can_pin_messages() || (td_->auth_manager_->is_bot() && !td_->contacts_manager_->is_appointed_chat_administrator(chat_id))) { return Status::Error(400, "Not enough rights to manage pinned messages in the chat"); } break; } case DialogType::Channel: { auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()); bool can_pin = is_broadcast_channel(dialog_id) ? status.can_edit_messages() : status.can_pin_messages(); if (!can_pin) { return Status::Error(400, "Not enough rights to manage pinned messages in the chat"); } break; } case DialogType::SecretChat: return Status::Error(400, "Secret chats can't have pinned messages"); case DialogType::None: default: UNREACHABLE(); } if (!have_input_peer(dialog_id, AccessRights::Write)) { return Status::Error(400, "Not enough rights"); } return Status::OK(); } void MessagesManager::pin_dialog_message(DialogId dialog_id, MessageId message_id, bool disable_notification, bool only_for_self, bool is_unpin, Promise &&promise) { auto d = get_dialog_force(dialog_id, "pin_dialog_message"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } TRY_STATUS_PROMISE(promise, can_pin_messages(dialog_id)); const Message *m = get_message_force(d, message_id, "pin_dialog_message"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } if (message_id.is_scheduled()) { return promise.set_error(Status::Error(400, "Scheduled message can't be pinned")); } if (!message_id.is_server()) { return promise.set_error(Status::Error(400, "Message can't be pinned")); } if (is_service_message_content(m->content->get_type())) { return promise.set_error(Status::Error(400, "A service message can't be pinned")); } if (only_for_self && dialog_id.get_type() != DialogType::User) { return promise.set_error(Status::Error(400, "Messages can't be pinned only for self in the chat")); } // TODO log event td_->create_handler(std::move(promise)) ->send(dialog_id, message_id, is_unpin, disable_notification, only_for_self); } void MessagesManager::unpin_all_dialog_messages(DialogId dialog_id, MessageId top_thread_message_id, Promise &&promise) { auto d = get_dialog_force(dialog_id, "unpin_all_dialog_messages"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } TRY_STATUS_PROMISE(promise, can_pin_messages(dialog_id)); TRY_STATUS_PROMISE(promise, can_use_top_thread_message_id(d, top_thread_message_id, MessageInputReplyTo())); if (!td_->auth_manager_->is_bot()) { auto message_ids = find_dialog_messages(d, [top_thread_message_id](const Message *m) { return m->is_pinned && (!top_thread_message_id.is_valid() || (m->is_topic_message && m->top_thread_message_id == top_thread_message_id)); }); vector deleted_message_ids; for (auto message_id : message_ids) { auto m = get_message(d, message_id); CHECK(m != nullptr); m->is_pinned = false; send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateMessageIsPinned"), m->message_id.get(), m->is_pinned)); on_message_changed(d, m, true, "unpin_all_dialog_messages"); } } if (top_thread_message_id.is_valid()) { AffectedHistoryQuery query = [td = td_, top_thread_message_id](DialogId dialog_id, Promise &&query_promise) { td->create_handler(std::move(query_promise))->send(dialog_id, top_thread_message_id); }; run_affected_history_query_until_complete(dialog_id, std::move(query), true, std::move(promise)); return; } set_dialog_last_pinned_message_id(d, MessageId()); if (!td_->auth_manager_->is_bot() && d->message_count_by_index[message_search_filter_index(MessageSearchFilter::Pinned)] != 0) { d->message_count_by_index[message_search_filter_index(MessageSearchFilter::Pinned)] = 0; on_dialog_updated(dialog_id, "unpin_all_dialog_messages"); } unpin_all_dialog_messages_on_server(dialog_id, 0, std::move(promise)); } class MessagesManager::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 MessagesManager::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 MessagesManager::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, MessageId()); }; run_affected_history_query_until_complete(dialog_id, std::move(query), true, get_erase_log_event_promise(log_event_id, std::move(promise))); } MessagesManager::Message *MessagesManager::get_message(Dialog *d, MessageId message_id) { return const_cast(get_message(static_cast(d), message_id)); } const MessagesManager::Message *MessagesManager::get_message(const Dialog *d, MessageId message_id) { CHECK(d != nullptr); const Message *result = nullptr; if (message_id.is_scheduled()) { if (d->scheduled_messages != nullptr && message_id.is_valid_scheduled()) { if (message_id.is_scheduled_server()) { auto server_message_id = message_id.get_scheduled_server_message_id(); auto it = d->scheduled_messages->scheduled_message_date_.find(server_message_id); if (it != d->scheduled_messages->scheduled_message_date_.end()) { int32 date = it->second; message_id = MessageId(server_message_id, date); CHECK(message_id.is_scheduled_server()); } } auto it = d->scheduled_messages->scheduled_messages_.find(message_id); if (it != d->scheduled_messages->scheduled_messages_.end()) { result = it->second.get(); } } } else { result = d->messages.get_pointer(message_id); if (result != nullptr) { auto unix_time = G()->unix_time(); if (unix_time > result->last_access_date + 5) { result->last_access_date = unix_time; auto list_node = const_cast(static_cast(result)); list_node->remove(); d->message_lru_list.put_back(list_node); } } } LOG(INFO) << "Search for " << message_id << " in " << d->dialog_id << " found " << result; return result; } const MessagesManager::Message *MessagesManager::get_message_static(const Dialog *d, MessageId message_id) { return get_message(d, message_id); } MessagesManager::Message *MessagesManager::get_message_force(Dialog *d, MessageId message_id, const char *source) { if (!message_id.is_valid() && !message_id.is_valid_scheduled()) { return nullptr; } auto result = get_message(d, message_id); if (result != nullptr) { return result; } if (!G()->use_message_database() || message_id.is_yet_unsent() || is_deleted_message(d, message_id)) { return nullptr; } if (message_id.is_scheduled() && d->has_loaded_scheduled_messages_from_database) { return nullptr; } LOG(INFO) << "Trying to load " << MessageFullId{d->dialog_id, message_id} << " from database from " << source; auto r_value = G()->td_db()->get_message_db_sync()->get_message({d->dialog_id, message_id}); if (r_value.is_error()) { return nullptr; } return on_get_message_from_database(d, r_value.ok(), message_id.is_scheduled(), source); } MessagesManager::Message *MessagesManager::on_get_message_from_database(const MessageDbMessage &message, bool is_scheduled, const char *source) { if (message.data.empty()) { return nullptr; } auto dialog_id = message.dialog_id; Dialog *d = get_dialog_force(dialog_id, source); if (d == nullptr) { LOG(ERROR) << "Can't find " << dialog_id << ", but have a message from it from " << source; if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive message in invalid " << dialog_id << " from " << source; return nullptr; } bool is_valid_server_message_id = (is_scheduled ? message.message_id.is_valid_scheduled() && message.message_id.is_scheduled_server() : message.message_id.is_valid() && message.message_id.is_server()); if (is_valid_server_message_id && (dialog_id.get_type() == DialogType::User || dialog_id.get_type() == DialogType::Chat)) { get_message_from_server({dialog_id, message.message_id}, Auto(), "on_get_message_from_database 1"); } force_create_dialog(dialog_id, source); d = get_dialog_force(dialog_id, source); CHECK(d != nullptr); } return on_get_message_from_database(d, message.message_id, message.data, is_scheduled, source); } MessagesManager::Message *MessagesManager::on_get_message_from_database(Dialog *d, const MessageDbDialogMessage &message, bool is_scheduled, const char *source) { return on_get_message_from_database(d, message.message_id, message.data, is_scheduled, source); } MessagesManager::Message *MessagesManager::on_get_message_from_database(Dialog *d, MessageId expected_message_id, const BufferSlice &value, bool is_scheduled, const char *source) { if (value.empty()) { return nullptr; } auto message = parse_message(d, expected_message_id, value, is_scheduled); if (message == nullptr) { return nullptr; } CHECK(d != nullptr); auto dialog_id = d->dialog_id; if (!have_input_peer(dialog_id, AccessRights::Read)) { return nullptr; } auto old_message = get_message(d, message->message_id); if (old_message != nullptr) { // data in the database is always outdated, so return a message from the memory if (dialog_id.get_type() == DialogType::SecretChat) { CHECK(!is_scheduled); // just in case restore random_id to message_id corespondence // can be needed if there was newer unloaded message with the same random_id add_random_id_to_message_id_correspondence(d, old_message->random_id, old_message->message_id); } if (old_message->notification_id.is_valid() && !is_scheduled) { auto notification_info = add_dialog_notification_info(d); add_notification_id_to_message_id_correspondence(notification_info, old_message->notification_id, old_message->message_id); } return old_message; } Dependencies dependencies; add_message_dependencies(dependencies, message.get()); if (!dependencies.resolve_force(td_, "on_get_message_from_database") && dialog_id.get_type() != DialogType::SecretChat) { get_message_from_server({dialog_id, message->message_id}, Auto(), "on_get_message_from_database 2"); } bool need_update = false; bool need_update_dialog_pos = false; auto result = add_message_to_dialog(d, std::move(message), true, false, &need_update, &need_update_dialog_pos, source); if (need_update_dialog_pos) { LOG(ERROR) << "Need update chat position after loading of " << (result == nullptr ? MessageId() : result->message_id) << " in " << dialog_id << " from " << source; send_update_chat_last_message(d, source); } return result; } vector MessagesManager::on_get_messages_from_database(Dialog *d, vector &&messages, MessageId first_message_id, bool &have_error, const char *source) { vector result; if (!first_message_id.is_valid() || !have_input_peer(d->dialog_id, AccessRights::Read)) { return result; } bool need_update = false; bool need_update_dialog_pos = false; auto next_message_id = MessageId::max(); Dependencies dependencies; for (auto &message_slice : messages) { auto message = parse_message(d, message_slice.message_id, message_slice.data, false); if (message == nullptr) { have_error = true; break; } if (message->message_id >= next_message_id) { LOG(ERROR) << "Receive " << message->message_id << " after " << next_message_id << " from database in the history of " << d->dialog_id; have_error = true; break; } next_message_id = message->message_id; if (message->message_id < first_message_id) { break; } result.push_back(message->message_id); auto *m = get_message(d, message->message_id); if (m == nullptr) { m = add_message_to_dialog(d, std::move(message), true, false, &need_update, &need_update_dialog_pos, source); if (m != nullptr) { add_message_dependencies(dependencies, m); } } } dependencies.resolve_force(td_, source); if (need_update_dialog_pos) { LOG(ERROR) << "Need update chat position after loading of " << result << " in " << d->dialog_id << " from " << source; send_update_chat_last_message(d, source); } return result; } void MessagesManager::fix_new_message(const Dialog *d, Message *m, bool from_database) const { CHECK(d != nullptr); CHECK(m != nullptr); DialogId dialog_id = d->dialog_id; if (!has_message_sender_user_id(dialog_id, m) && !m->sender_dialog_id.is_valid()) { if (is_broadcast_channel(dialog_id)) { m->sender_dialog_id = dialog_id; } else { if (is_discussion_message(dialog_id, m)) { m->sender_dialog_id = m->forward_info->from_dialog_id; } else { LOG(ERROR) << "Failed to repair sender chat in " << m->message_id << " in " << dialog_id; } } } auto dialog_type = dialog_id.get_type(); if (m->sender_user_id == ContactsManager::get_anonymous_bot_user_id() && !m->sender_dialog_id.is_valid() && dialog_type == DialogType::Channel && !is_broadcast_channel(dialog_id)) { m->sender_user_id = UserId(); m->sender_dialog_id = dialog_id; } if (!from_database && m->message_id.is_valid()) { switch (dialog_type) { case DialogType::Chat: case DialogType::Channel: { m->available_reactions_generation = d->available_reactions_generation; break; } case DialogType::User: case DialogType::SecretChat: break; default: UNREACHABLE(); break; } m->history_generation = d->history_generation; } if (m->message_id.is_scheduled() && !m->message_id.is_yet_unsent()) { m->top_thread_message_id = MessageId(); } else if (m->top_thread_message_id.is_valid()) { if (is_broadcast_channel(dialog_id)) { m->top_thread_message_id = MessageId(); } } else { if (is_thread_message(dialog_id, m)) { m->top_thread_message_id = m->message_id; } } m->last_access_date = G()->unix_time(); if (!from_database && m->contains_mention) { CHECK(!td_->auth_manager_->is_bot()); auto message_content_type = m->content->get_type(); if (message_content_type == MessageContentType::PinMessage) { if (is_dialog_pinned_message_notifications_disabled(d) || !get_message_content_pinned_message_id(m->content.get()).is_valid()) { // treat message pin without pinned message as an ordinary message m->contains_mention = false; } } else if (is_dialog_mention_notifications_disabled(d)) { // disable mention notification m->is_mention_notification_disabled = true; } } if (m->contains_unread_mention && m->message_id <= d->last_read_all_mentions_message_id) { m->contains_unread_mention = false; } if (dialog_type == DialogType::Channel && !m->contains_unread_mention) { auto channel_read_media_period = td_->option_manager_->get_option_integer("channels_read_media_period", (G()->is_test_dc() ? 300 : 7 * 86400)); if (m->date < G()->unix_time() - channel_read_media_period) { update_opened_message_content(m->content.get()); } } if (m->reply_markup != nullptr && !m->reply_markup->is_personal && (m->reply_markup->type == ReplyMarkup::Type::ForceReply || m->reply_markup->type == ReplyMarkup::Type::RemoveKeyboard) && !td_->auth_manager_->is_bot()) { m->reply_markup = nullptr; } } void MessagesManager::remove_message_remove_keyboard_reply_markup(Message *m) const { CHECK(m != nullptr); if (m->reply_markup == nullptr || m->reply_markup->type != ReplyMarkup::Type::RemoveKeyboard || td_->auth_manager_->is_bot()) { return; } CHECK(m->reply_markup->is_personal); // otherwise it was removed in fix_new_message m->had_reply_markup = true; m->reply_markup = nullptr; } void MessagesManager::update_replied_by_message_count(DialogId dialog_id, const Message *m, bool is_add) { CHECK(m != nullptr); if (!m->message_id.is_yet_unsent()) { return; } const auto *input_reply_to = get_message_input_reply_to(m); if (input_reply_to == nullptr || input_reply_to->is_empty()) { return; } auto replied_message_full_id = input_reply_to->get_reply_message_full_id(dialog_id); auto replied_message_id = replied_message_full_id.get_message_id(); if (!replied_message_id.is_valid() && !replied_message_id.is_valid_scheduled()) { return; } if (!replied_message_id.is_yet_unsent()) { if (!replied_message_id.is_scheduled()) { if (is_add) { replied_by_yet_unsent_messages_[replied_message_full_id]++; } else { auto it = replied_by_yet_unsent_messages_.find(replied_message_full_id); CHECK(it != replied_by_yet_unsent_messages_.end()); it->second--; if (it->second == 0) { replied_by_yet_unsent_messages_.erase(it); } else { CHECK(it->second > 0); } } } } else { if (is_add) { replied_yet_unsent_messages_[replied_message_full_id].insert({dialog_id, m->message_id}); } else { auto it = replied_yet_unsent_messages_.find(replied_message_full_id); CHECK(it != replied_yet_unsent_messages_.end()); size_t erased_count = it->second.erase({dialog_id, m->message_id}); CHECK(erased_count > 0); if (it->second.empty()) { replied_yet_unsent_messages_.erase(it); } } } } void MessagesManager::add_message_to_dialog_message_list(const Message *m, Dialog *d, const bool from_database, const bool from_update, const bool need_update, bool *need_update_dialog_pos, const char *source) { CHECK(!td_->auth_manager_->is_bot()); auto dialog_id = d->dialog_id; auto dialog_type = dialog_id.get_type(); auto message_id = m->message_id; if (d->have_full_history && !from_database && !from_update && !message_id.is_local() && !message_id.is_yet_unsent()) { LOG(ERROR) << "Have full history in " << dialog_id << ", but receive unknown " << message_id << " with content of type " << m->content->get_type() << " from " << source << ". Last new is " << d->last_new_message_id << ", last is " << d->last_message_id << ", first database is " << d->first_database_message_id << ", last database is " << d->last_database_message_id << ", last read inbox is " << d->last_read_inbox_message_id << ", last read outbox is " << d->last_read_outbox_message_id << ", last read all mentions is " << d->last_read_all_mentions_message_id << ", last clear history date is " << d->last_clear_history_date << ", last clear history is " << d->last_clear_history_message_id << ", last delete is " << d->deleted_last_message_id << ", delete last message date is " << d->delete_last_message_date << ", have_full_history source = " << d->have_full_history_source; d->have_full_history = false; d->have_full_history_source = 0; on_dialog_updated(dialog_id, "drop have_full_history"); } if (d->is_empty) { d->is_empty = false; *need_update_dialog_pos = true; } if (!(d->have_full_history && from_update) && d->last_message_id.is_valid() && d->last_message_id < MessageId(ServerMessageId(1)) && message_id >= MessageId(ServerMessageId(1))) { set_dialog_last_message_id(d, MessageId(), "add_message_to_dialog_message_list"); set_dialog_first_database_message_id(d, MessageId(), "add_message_to_dialog_message_list"); set_dialog_last_database_message_id(d, MessageId(), source); d->have_full_history = false; d->have_full_history_source = 0; invalidate_message_indexes(d); d->local_unread_count = 0; // read all local messages. They will not be reachable anymore on_dialog_updated(dialog_id, "add gap to dialog"); send_update_chat_last_message(d, "add gap to dialog"); *need_update_dialog_pos = false; } if (from_update && !m->is_failed_to_send && message_id > d->last_new_message_id && !message_id.is_yet_unsent()) { if (dialog_type == DialogType::SecretChat || message_id.is_server()) { // can delete messages, therefore must be called before message attaching/adding set_dialog_last_new_message_id(d, message_id, "add_message_to_dialog_message_list"); } } auto old_last_message_id = d->last_message_id; if (need_update && message_id > d->last_read_inbox_message_id) { if (has_incoming_notification(dialog_id, m)) { int32 server_unread_count = d->server_unread_count; int32 local_unread_count = d->local_unread_count; if (message_id.is_server()) { server_unread_count++; } else { local_unread_count++; } set_dialog_last_read_inbox_message_id(d, MessageId::min(), server_unread_count, local_unread_count, false, source); } else { // if non-scheduled outgoing message has identifier one greater than last_read_inbox_message_id, // then definitely there are no unread incoming messages before it if (message_id.is_server() && d->last_read_inbox_message_id.is_valid() && d->last_read_inbox_message_id.is_server() && message_id == d->last_read_inbox_message_id.get_next_message_id(MessageType::Server)) { read_history_inbox(d, message_id, 0, "add_message_to_dialog_message_list"); } } } if (need_update && m->contains_unread_mention) { set_dialog_unread_mention_count(d, d->unread_mention_count + 1); send_update_chat_unread_mention_count(d); } if (need_update && has_unread_message_reactions(dialog_id, m)) { set_dialog_unread_reaction_count(d, d->unread_reaction_count + 1); send_update_chat_unread_reaction_count(d, "add_message_to_dialog_message_list"); } if (need_update) { update_message_count_by_index(d, +1, m); } if (from_update && message_id > d->last_message_id && message_id >= d->last_new_message_id) { set_dialog_last_message_id(d, message_id, "add_message_to_dialog_message_list", m); *need_update_dialog_pos = true; } if (from_update && !message_id.is_yet_unsent() && message_id >= d->last_new_message_id && (d->last_new_message_id.is_valid() || (message_id.is_local() && d->last_message_id.is_valid() && (message_id >= d->last_message_id || (d->last_database_message_id.is_valid() && message_id > d->last_database_message_id))))) { CHECK(message_id <= d->last_message_id); if (message_id > d->last_database_message_id) { set_dialog_last_database_message_id(d, message_id, "add_message_to_dialog_message_list"); if (!d->first_database_message_id.is_valid()) { set_dialog_first_database_message_id(d, message_id, "add_message_to_dialog_message_list"); try_restore_dialog_reply_markup(d, m); } } } if (!from_update && message_id.is_server() && d->last_message_id.is_valid() && message_id > d->last_message_id) { LOG(INFO) << "Receive " << message_id << ", which is newer than the last " << d->last_message_id << " not from update"; set_dialog_last_message_id(d, MessageId(), source); if (message_id > d->deleted_last_message_id) { d->delete_last_message_date = m->date; d->deleted_last_message_id = message_id; } set_dialog_first_database_message_id(d, MessageId(), source); set_dialog_last_database_message_id(d, MessageId(), source); d->have_full_history = false; d->have_full_history_source = 0; invalidate_message_indexes(d); on_dialog_updated(dialog_id, source); send_update_chat_last_message(d, source); *need_update_dialog_pos = false; on_dialog_updated(dialog_id, "do delete last message"); send_closure_later(actor_id(this), &MessagesManager::load_last_dialog_message_later, dialog_id); } d->ordered_messages.insert(message_id, from_update, old_last_message_id, source); } // keep synced with add_scheduled_message_to_dialog MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, unique_ptr message, const bool from_database, const bool from_update, bool *need_update, bool *need_update_dialog_pos, const char *source) { if (!message->message_id.is_valid()) { return add_scheduled_message_to_dialog(d, std::move(message), from_database, from_update, need_update, source); } CHECK(need_update != nullptr); CHECK(need_update_dialog_pos != nullptr); CHECK(source != nullptr); fix_new_message(d, message.get(), from_database); debug_add_message_to_dialog_fail_reason_ = "success"; DialogId dialog_id = d->dialog_id; MessageId message_id = message->message_id; LOG(INFO) << "Adding " << message_id << " of type " << message->content->get_type() << " to " << dialog_id << " from " << source << ". Last new is " << d->last_new_message_id << ", last is " << d->last_message_id << ", from_update = " << from_update; if (*need_update) { CHECK(from_update); } if (message_id <= d->last_clear_history_message_id) { LOG(INFO) << "Skip adding cleared " << message_id << " to " << dialog_id; if (from_database) { delete_message_from_database(d, message_id, message.get(), true, "cleared full history"); } debug_add_message_to_dialog_fail_reason_ = "cleared full history"; return nullptr; } if (is_deleted_message(d, message_id)) { LOG(INFO) << "Skip adding deleted " << message_id << " to " << dialog_id; debug_add_message_to_dialog_fail_reason_ = "adding deleted message"; return nullptr; } auto dialog_type = dialog_id.get_type(); if (from_update && !message->is_failed_to_send) { if (message_id <= d->last_new_message_id && dialog_type != DialogType::Channel && (message_id.is_server() || !td_->auth_manager_->is_bot())) { LOG(ERROR) << "New " << message_id << " in " << dialog_id << " from " << source << " has identifier less than last_new_message_id = " << d->last_new_message_id; } } if (!from_update && !message->is_failed_to_send) { MessageId max_message_id; if (message_id.is_server()) { if (d->being_added_message_id.is_valid()) { // if a too new message not from update has failed to preload before being_added_message_id was set, // then it should fail to load even after it is set and last_new_message_id has changed max_message_id = d->being_updated_last_new_message_id; } else { max_message_id = d->last_new_message_id; } } else if (message_id.is_local()) { if (d->being_added_message_id.is_valid()) { max_message_id = d->being_updated_last_database_message_id; } else { max_message_id = d->last_database_message_id; } } if (max_message_id != MessageId() && message_id > max_message_id) { if (!from_database) { LOG(ERROR) << "Ignore " << message_id << " in " << dialog_id << " received not through update from " << source << ". The maximum allowed is " << max_message_id << ", last is " << d->last_message_id << ", being added message is " << d->being_added_message_id << ", channel difference " << debug_channel_difference_dialog_ << " " << to_string(get_message_object(dialog_id, message.get(), "add_message_to_dialog")); if (need_channel_difference_to_add_message(dialog_id, nullptr)) { schedule_get_channel_difference(dialog_id, 0, MessageId(), 0.001, "add_message_to_dialog"); } } else { LOG(INFO) << "Ignore " << message_id << " in " << dialog_id << " received not through update from " << source; } debug_add_message_to_dialog_fail_reason_ = "too new message not from update"; return nullptr; } } if ((message_id.is_server() || (message_id.is_local() && dialog_type == DialogType::SecretChat)) && message_id <= d->max_unavailable_message_id) { LOG(INFO) << "Can't add an unavailable " << message_id << " to " << dialog_id; if (from_database) { delete_message_from_database(d, message_id, message.get(), true, "ignore unavailable message"); } debug_add_message_to_dialog_fail_reason_ = "ignore unavailable message"; return nullptr; } auto message_content_type = message->content->get_type(); if (message_content_type == MessageContentType::ChatDeleteHistory) { { auto old_message = delete_message(d, message_id, true, need_update_dialog_pos, "message chat delete history"); if (old_message != nullptr) { send_update_delete_messages(dialog_id, {old_message->message_id.get()}, true); } } int32 last_message_date = 0; if (d->last_message_id != MessageId()) { auto last_message = get_message(d, d->last_message_id); CHECK(last_message != nullptr); last_message_date = last_message->date - 1; } else { last_message_date = d->last_clear_history_date; } if (message->date > last_message_date) { set_dialog_last_clear_history_date(d, message->date, message_id, "update_last_clear_history_date"); *need_update_dialog_pos = true; } LOG(INFO) << "Process MessageChatDeleteHistory in " << message_id << " in " << dialog_id << " with date " << message->date << " from " << source; if (message_id > d->max_unavailable_message_id) { set_dialog_max_unavailable_message_id(dialog_id, message_id, false, "message chat delete history"); } CHECK(!from_database); debug_add_message_to_dialog_fail_reason_ = "skip adding MessageChatDeleteHistory"; return nullptr; } if (*need_update && message_id <= d->last_new_message_id && !td_->auth_manager_->is_bot()) { *need_update = false; } if (*need_update && d->reply_markup_message_id != MessageId()) { UserId bot_user_id; if (message->reply_markup != nullptr && message->reply_markup->type == ReplyMarkup::Type::RemoveKeyboard && message_id > d->reply_markup_message_id) { bot_user_id = message->sender_user_id; } else { bot_user_id = get_message_content_deleted_user_id(message->content.get()); // do not check for is_user_bot to allow deleted bots } if (bot_user_id.is_valid()) { const Message *old_message = get_message_force(d, d->reply_markup_message_id, "add_message_to_dialog 1"); if (old_message == nullptr || old_message->sender_user_id == bot_user_id) { set_dialog_reply_markup(d, MessageId()); } } } // must be after set_dialog_reply_markup(d, MessageId()), but before try_restore_dialog_reply_markup remove_message_remove_keyboard_reply_markup(message.get()); { Message *m = from_database ? get_message(d, message_id) : get_message_force(d, message_id, "add_message_to_dialog 2"); if (m != nullptr) { CHECK(m->message_id == message_id); CHECK(message->message_id == message_id); LOG(INFO) << "Adding already existing " << message_id << " in " << dialog_id << " from " << source; if (*need_update && !td_->auth_manager_->is_bot()) { *need_update = false; if (!G()->use_message_database()) { // can happen if the message is received first through getMessage in an unknown chat without // last_new_message_id and only after that received through getDifference or getChannelDifference if (d->last_new_message_id.is_valid()) { LOG(ERROR) << "Receive again " << (message->is_outgoing ? "outgoing" : "incoming") << (message->forward_info == nullptr ? " not" : "") << " forwarded " << message_id << " with content of type " << message_content_type << " in " << dialog_id << " from " << source << ", current last new is " << d->last_new_message_id << ", last is " << d->last_message_id; } } } if (!from_database && (from_update || message->edit_date >= m->edit_date)) { const int32 INDEX_MASK_MASK = ~(message_search_filter_index_mask(MessageSearchFilter::UnreadMention) | message_search_filter_index_mask(MessageSearchFilter::UnreadReaction)); auto old_index_mask = get_message_index_mask(dialog_id, m) & INDEX_MASK_MASK; bool was_deleted = delete_active_live_location(dialog_id, m); auto old_file_ids = get_message_file_ids(m); bool need_send_update = update_message(d, m, std::move(message), true); if (!need_send_update) { LOG(INFO) << message_id << " in " << dialog_id << " is not changed"; } auto new_index_mask = get_message_index_mask(dialog_id, m) & INDEX_MASK_MASK; if (was_deleted) { try_add_active_live_location(dialog_id, m); } change_message_files(dialog_id, m, old_file_ids); if (need_send_update) { on_message_notification_changed(d, m, source); } update_message_count_by_index(d, -1, old_index_mask & ~new_index_mask); update_message_count_by_index(d, +1, new_index_mask & ~old_index_mask); } return m; } } if (*need_update && may_need_message_notification(d, message.get())) { // notification group must be created here because it may force adding new messages from database // in get_message_notification_group_force get_dialog_notification_group_id(d->dialog_id, get_notification_group_info(d, message.get())); } if (*need_update || (!d->last_new_message_id.is_valid() && !message_id.is_yet_unsent() && from_update && !message->is_failed_to_send)) { auto pinned_message_id = get_message_content_pinned_message_id(message->content.get()); if (pinned_message_id.is_valid() && pinned_message_id < message_id && have_message_force(d, pinned_message_id, "preload pinned message")) { LOG(INFO) << "Preloaded pinned " << pinned_message_id << " from database"; } if (d->notification_info != nullptr && d->notification_info->pinned_message_notification_message_id_.is_valid() && d->notification_info->pinned_message_notification_message_id_ != message_id && have_message_force(d, d->notification_info->pinned_message_notification_message_id_, "preload previously pinned message")) { LOG(INFO) << "Preloaded previously pinned " << d->notification_info->pinned_message_notification_message_id_ << " from database"; } } if (from_update && !message->is_failed_to_send && message->top_thread_message_id.is_valid() && message->top_thread_message_id != message_id && message_id.is_server() && have_message_force(d, message->top_thread_message_id, "preload top reply message")) { LOG(INFO) << "Preloaded top thread " << message->top_thread_message_id << " from database"; Message *top_m = get_message(d, message->top_thread_message_id); CHECK(top_m != nullptr); if (is_active_message_reply_info(dialog_id, top_m->reply_info) && is_discussion_message(dialog_id, top_m)) { MessageFullId top_message_full_id{top_m->forward_info->from_dialog_id, top_m->forward_info->from_message_id}; if (have_message_force(top_message_full_id, "preload discussed message")) { LOG(INFO) << "Preloaded discussed " << top_message_full_id << " from database"; } } } // there must be no two recursive calls to add_message_to_dialog LOG_CHECK(!d->being_added_message_id.is_valid()) << d->dialog_id << " " << d->being_added_message_id << " " << message_id << " " << *need_update << " " << d->last_new_message_id << " " << source; LOG_CHECK(!d->being_deleted_message_id.is_valid()) << d->being_deleted_message_id << " " << message_id << " " << source; d->being_added_message_id = message_id; d->being_updated_last_new_message_id = d->last_new_message_id; d->being_updated_last_database_message_id = d->last_database_message_id; if (d->notification_info != nullptr && d->notification_info->new_secret_chat_notification_id_.is_valid()) { remove_new_secret_chat_notification(d, true); } if (message->message_id > d->max_added_message_id) { d->max_added_message_id = message->message_id; } if (d->open_count == 0 && !d->has_unload_timeout && is_message_unload_enabled() && (!d->messages.empty() || !from_database)) { LOG(INFO) << "Schedule unload of " << dialog_id; pending_unload_dialog_timeout_.add_timeout_in(dialog_id.get(), get_next_unload_dialog_delay(d)); d->has_unload_timeout = true; } if (message->ttl > 0 && message->ttl_expires_at != 0) { auto now = Time::now(); if (message->ttl_expires_at <= now) { if (dialog_type == DialogType::SecretChat) { LOG(INFO) << "Can't add " << message_id << " with expired " << (now - message->ttl_expires_at) << " seconds ago self-destruct timer to " << dialog_id << " from " << source; delete_message_from_database(d, message_id, message.get(), true, "delete self-destructed message"); debug_add_message_to_dialog_fail_reason_ = "delete self-destructed message"; d->being_added_message_id = MessageId(); return nullptr; } else { on_message_ttl_expired_impl(d, message.get(), false); message_content_type = message->content->get_type(); if (from_database) { on_message_changed(d, message.get(), false, "add expired message to dialog"); } } } else { ttl_register_message(dialog_id, message.get(), now); } } if (message->ttl_period > 0) { CHECK(dialog_type != DialogType::SecretChat); auto server_time = G()->server_time(); if (message->date + message->ttl_period <= server_time) { LOG(INFO) << "Can't add auto-deleted " << message_id << " sent at " << message->date << " with auto-delete time " << message->ttl_period << " to " << dialog_id << " from " << source; delete_message_from_database(d, message_id, message.get(), true, "delete auto-deleted message"); debug_add_message_to_dialog_fail_reason_ = "delete auto-deleted message"; d->being_added_message_id = MessageId(); return nullptr; } else { ttl_period_register_message(dialog_id, message.get(), server_time); } } if (!message->are_media_timestamp_entities_found) { message->are_media_timestamp_entities_found = true; if (from_database && !td_->auth_manager_->is_bot()) { // otherwise the entities were found in get_message_text auto text = get_message_content_text_mutable(message->content.get()); if (text != nullptr) { fix_formatted_text(text->text, text->entities, true, true, true, false, false).ensure(); // always call to save are_media_timestamp_entities_found flag on_message_changed(d, message.get(), false, "save media timestamp entities"); } } } LOG(INFO) << "Adding not found " << message_id << " to " << dialog_id << " from " << source; if (G()->keep_media_order() && message_id.is_yet_unsent() && !message->via_bot_user_id.is_valid() && !message->hide_via_bot && !message->is_copy) { auto queue_id = ChainId(dialog_id, message_content_type).get(); if (queue_id & 1) { LOG(INFO) << "Add " << message_id << " from " << source << " to queue " << queue_id; auto &queue = yet_unsent_media_queues_[queue_id]; queue.dialog_id_ = dialog_id; queue.queue_[message_id]; // reserve place for promise if (!td_->auth_manager_->is_bot()) { pending_send_dialog_action_timeout_.add_timeout_in(dialog_id.get(), 1.0); } } } if (!td_->auth_manager_->is_bot()) { if (*need_update) { // notification must be added before updating unread_count to have correct total notification count // in get_message_notification_group_force add_new_message_notification(d, message.get(), false); } else { if (from_database && message->notification_id.is_valid() && is_from_mention_notification_group(message.get()) && is_message_notification_active(d, message.get()) && is_dialog_mention_notifications_disabled(d) && d->notification_info != nullptr && message_id != d->notification_info->pinned_message_notification_message_id_) { auto notification_id = message->notification_id; VLOG(notifications) << "Remove mention " << notification_id << " in " << message_id << " in " << dialog_id; message->notification_id = NotificationId(); if (d->notification_info->mention_notification_group_.get_last_notification_id() == notification_id) { // last notification is deleted, need to find new last notification fix_dialog_last_notification_id(d, true, message_id); } send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification, d->notification_info->mention_notification_group_.get_group_id(), notification_id, false, false, Promise(), "remove disabled mention notification"); on_message_changed(d, message.get(), false, "remove_mention_notification"); } } } const Message *m = message.get(); if (!td_->auth_manager_->is_bot()) { add_message_to_dialog_message_list(m, d, from_database, from_update, *need_update, need_update_dialog_pos, source); } update_replied_by_message_count(dialog_id, m, true); if (!from_database && !m->message_id.is_yet_unsent()) { add_message_to_database(d, m, "add_message_to_dialog"); } if (from_update && !m->is_failed_to_send && dialog_type == DialogType::Channel) { auto now = max(G()->unix_time(), m->date); if (m->date < now - 2 * 86400 && Slice(source) == Slice("updateNewChannelMessage")) { // if the message is pretty old, we might have missed the update that the message has already been read repair_channel_server_unread_count(d); } if (m->date + 3600 >= now && m->is_outgoing) { auto channel_id = dialog_id.get_channel_id(); auto slow_mode_delay = td_->contacts_manager_->get_channel_slow_mode_delay(channel_id, "add_message_to_dialog"); auto status = td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()); if (m->date + slow_mode_delay > now && !status.is_administrator()) { td_->contacts_manager_->on_update_channel_slow_mode_next_send_date(channel_id, m->date + slow_mode_delay); } } if (m->date > now - 14 * 86400) { td_->contacts_manager_->remove_inactive_channel(dialog_id.get_channel_id()); } } if (message_content_type == MessageContentType::ContactRegistered && !d->has_contact_registered_message) { d->has_contact_registered_message = true; on_dialog_updated(dialog_id, "update_has_contact_registered_message"); } if (m->sender_user_id.is_valid()) { auto story_full_id = get_message_content_story_full_id(td_, m->content.get()); if (story_full_id.is_valid()) { td_->story_manager_->on_story_replied(story_full_id, m->sender_user_id); } if (m->reply_to_story_full_id.is_valid()) { td_->story_manager_->on_story_replied(m->reply_to_story_full_id, m->sender_user_id); } } reget_message_from_server_if_needed(dialog_id, m); add_message_file_sources(dialog_id, m); register_message_content(td_, m->content.get(), {dialog_id, m->message_id}, "add_message_to_dialog"); register_message_reply(dialog_id, m); if (*need_update && m->message_id.is_server()) { if (message_content_type == MessageContentType::PinMessage) { auto pinned_message_id = get_message_content_pinned_message_id(m->content.get()); if (d->is_last_pinned_message_id_inited && pinned_message_id > d->last_pinned_message_id) { set_dialog_last_pinned_message_id(d, pinned_message_id); } } if (message_content_type == MessageContentType::SetBackground) { set_dialog_background(d, get_message_content_my_background_info( m->content.get(), m->is_outgoing || dialog_id.get_type() != DialogType::User)); } if (message_content_type == MessageContentType::ChatSetTheme) { set_dialog_theme_name(d, get_message_content_theme_name(m->content.get())); } } if (from_update && !m->is_failed_to_send) { speculatively_update_active_group_call_id(d, m); speculatively_update_channel_participants(dialog_id, m); update_forum_topic_info_by_service_message_content(td_, m->content.get(), dialog_id, m->top_thread_message_id); update_sent_message_contents(dialog_id, m); update_used_hashtags(dialog_id, m); update_top_dialogs(dialog_id, m); cancel_dialog_action(dialog_id, m); update_has_outgoing_messages(dialog_id, m); if (!td_->auth_manager_->is_bot() && d->messages.empty() && !m->is_outgoing && dialog_id != get_my_dialog_id()) { switch (dialog_type) { case DialogType::User: td_->contacts_manager_->invalidate_user_full(dialog_id.get_user_id()); td_->contacts_manager_->reload_user_full(dialog_id.get_user_id(), Promise(), "add_message_to_dialog"); break; case DialogType::Chat: case DialogType::Channel: // nothing to do break; case DialogType::SecretChat: { auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (user_id.is_valid()) { td_->contacts_manager_->invalidate_user_full(user_id); td_->contacts_manager_->reload_user_full(user_id, Promise(), "add_message_to_dialog"); } break; } case DialogType::None: default: UNREACHABLE(); } } } if (m->is_topic_message) { td_->forum_topic_manager_->on_topic_message_count_changed(dialog_id, m->top_thread_message_id, +1); } Message *result_message = message.get(); d->messages.set(message_id, std::move(message)); d->message_lru_list.put_back(result_message); if (m->message_id.is_yet_unsent() && !m->message_id.is_scheduled() && m->top_thread_message_id.is_valid() && !td_->auth_manager_->is_bot()) { auto is_inserted = yet_unsent_thread_message_ids_[MessageFullId{dialog_id, m->top_thread_message_id}].insert(m->message_id).second; CHECK(is_inserted); } switch (dialog_type) { case DialogType::User: case DialogType::Chat: if (m->message_id.is_server()) { message_id_to_dialog_id_.set(m->message_id, dialog_id); } break; case DialogType::Channel: if (m->message_id.is_server() && !td_->auth_manager_->is_bot()) { td_->contacts_manager_->register_message_users({dialog_id, m->message_id}, get_message_user_ids(m)); td_->contacts_manager_->register_message_channels({dialog_id, m->message_id}, get_message_channel_ids(m)); } break; case DialogType::SecretChat: break; case DialogType::None: default: UNREACHABLE(); } if (m->notification_id.is_valid()) { auto notification_info = add_dialog_notification_info(d); add_notification_id_to_message_id_correspondence(notification_info, m->notification_id, m->message_id); } if (m->message_id.is_yet_unsent() || dialog_type == DialogType::SecretChat) { add_random_id_to_message_id_correspondence(d, m->random_id, m->message_id); } try_add_bot_command_message_id(dialog_id, m); // must be called after the message is added to correctly update replies update_message_max_reply_media_timestamp(d, result_message, false); update_message_max_own_media_timestamp(d, result_message); result_message->debug_source = source; d->being_added_message_id = MessageId(); added_message_count_++; return result_message; } MessagesManager::Message *MessagesManager::add_scheduled_message_to_dialog(Dialog *d, unique_ptr message, bool from_database, bool from_update, bool *need_update, const char *source) { CHECK(message != nullptr); CHECK(d != nullptr); CHECK(need_update != nullptr); CHECK(source != nullptr); DialogId dialog_id = d->dialog_id; MessageId message_id = message->message_id; CHECK(message_id.is_valid_scheduled()); CHECK(!message->notification_id.is_valid()); CHECK(!message->removed_notification_id.is_valid()); fix_new_message(d, message.get(), from_database); debug_add_message_to_dialog_fail_reason_ = "success"; LOG(INFO) << "Adding " << message_id << " of type " << message->content->get_type() << " to " << dialog_id << " from " << source; if (is_deleted_message(d, message_id)) { LOG(INFO) << "Skip adding deleted " << message_id << " to " << dialog_id << " from " << source; debug_add_message_to_dialog_fail_reason_ = "adding deleted scheduled message"; return nullptr; } if (dialog_id.get_type() == DialogType::SecretChat) { LOG(ERROR) << "Tried to add " << message_id << " to " << dialog_id << " from " << source; debug_add_message_to_dialog_fail_reason_ = "skip adding scheduled message to secret chat"; return nullptr; } if (message->ttl != 0 || message->ttl_expires_at != 0) { LOG(ERROR) << "Tried to add " << message_id << " with self-destruct timer " << message->ttl << '/' << message->ttl_expires_at << " to " << dialog_id << " from " << source; debug_add_message_to_dialog_fail_reason_ = "skip adding secret scheduled message"; return nullptr; } if (message->ttl_period != 0) { LOG(ERROR) << "Tried to add " << message_id << " with auto-delete timer " << message->ttl_period << " to " << dialog_id << " from " << source; debug_add_message_to_dialog_fail_reason_ = "skip adding auto-deleting scheduled message"; return nullptr; } if (td_->auth_manager_->is_bot()) { LOG(ERROR) << "Bot tried to add " << message_id << " to " << dialog_id << " from " << source; debug_add_message_to_dialog_fail_reason_ = "skip adding scheduled message by bot"; return nullptr; } auto message_content_type = message->content->get_type(); if (is_service_message_content(message_content_type) || message_content_type == MessageContentType::LiveLocation || message_content_type == MessageContentType::ExpiredPhoto || message_content_type == MessageContentType::ExpiredVideo) { LOG(ERROR) << "Tried to add " << message_id << " of type " << message_content_type << " to " << dialog_id << " from " << source; debug_add_message_to_dialog_fail_reason_ = "skip adding message of unexpected type"; return nullptr; } { Message *m = from_database ? get_message(d, message_id) : get_message_force(d, message_id, "add_scheduled_message_to_dialog"); if (m != nullptr) { auto old_message_id = m->message_id; LOG(INFO) << "Adding already existing " << old_message_id << " in " << dialog_id << " from " << source; message->message_id = old_message_id; if (!from_database) { auto old_file_ids = get_message_file_ids(m); update_message(d, m, std::move(message), true); change_message_files(dialog_id, m, old_file_ids); } if (old_message_id != message_id) { being_readded_message_id_ = {dialog_id, old_message_id}; message = do_delete_scheduled_message(d, old_message_id, false, "add_scheduled_message_to_dialog"); CHECK(message != nullptr); send_update_delete_messages(dialog_id, {message->message_id.get()}, false); message->message_id = message_id; from_database = false; } else { *need_update = false; return m; } } } LOG(INFO) << "Adding not found " << message_id << " to " << dialog_id << " from " << source; const Message *m = message.get(); update_replied_by_message_count(dialog_id, m, true); if (!from_database && !m->message_id.is_yet_unsent()) { add_message_to_database(d, m, "add_scheduled_message_to_dialog"); } reget_message_from_server_if_needed(dialog_id, m); add_message_file_sources(dialog_id, m); register_message_content(td_, m->content.get(), {dialog_id, m->message_id}, "add_scheduled_message_to_dialog"); if (m->message_id.is_yet_unsent()) { add_random_id_to_message_id_correspondence(d, m->random_id, m->message_id); } // must be called after register_message_content, which loads web page update_message_max_reply_media_timestamp(d, message.get(), false); update_message_max_own_media_timestamp(d, message.get()); register_message_reply(dialog_id, m); if (from_update && !m->is_failed_to_send) { update_sent_message_contents(dialog_id, m); update_used_hashtags(dialog_id, m); update_has_outgoing_messages(dialog_id, m); } if (m->is_topic_message) { td_->forum_topic_manager_->on_topic_message_count_changed(dialog_id, m->top_thread_message_id, +1); } auto *scheduled_messages = add_dialog_scheduled_messages(d); if (m->message_id.is_scheduled_server()) { auto is_inserted = scheduled_messages->scheduled_message_date_.emplace(m->message_id.get_scheduled_server_message_id(), m->date) .second; CHECK(is_inserted); } auto result_message = message.get(); auto is_inserted = scheduled_messages->scheduled_messages_.emplace(result_message->message_id, std::move(message)).second; CHECK(is_inserted); being_readded_message_id_ = MessageFullId(); return result_message; } void MessagesManager::register_new_local_message_id(Dialog *d, const Message *m) { if (m == nullptr) { return; } if (m->message_id.is_scheduled()) { return; } CHECK(m->message_id.is_local()); if (m->top_thread_message_id.is_valid() && m->top_thread_message_id != m->message_id) { Message *top_m = get_message_force(d, m->top_thread_message_id, "register_new_local_message_id"); if (top_m != nullptr && top_m->top_thread_message_id == top_m->message_id) { auto it = std::lower_bound(top_m->local_thread_message_ids.begin(), top_m->local_thread_message_ids.end(), m->message_id); if (it == top_m->local_thread_message_ids.end() || *it != m->message_id) { top_m->local_thread_message_ids.insert(it, m->message_id); if (top_m->local_thread_message_ids.size() >= 1000) { top_m->local_thread_message_ids.erase(top_m->local_thread_message_ids.begin()); } on_message_changed(d, top_m, false, "register_new_local_message_id"); } } } } void MessagesManager::on_message_changed(const Dialog *d, const Message *m, bool need_send_update, const char *source) { CHECK(d != nullptr); CHECK(m != nullptr); if (need_send_update && m->message_id == d->last_message_id) { send_update_chat_last_message_impl(d, source); } if (m->message_id == d->last_database_message_id) { on_dialog_updated(d->dialog_id, source); } if (!m->message_id.is_yet_unsent()) { add_message_to_database(d, m, source); } } void MessagesManager::on_message_notification_changed(Dialog *d, const Message *m, const char *source) { CHECK(d != nullptr); CHECK(m != nullptr); if (m->notification_id.is_valid() && is_message_notification_active(d, m)) { auto &group_info = get_notification_group_info(d, m); if (group_info.is_valid()) { send_closure_later(G()->notification_manager(), &NotificationManager::edit_notification, group_info.get_group_id(), m->notification_id, create_new_message_notification( m->message_id, is_message_preview_enabled(d, m, is_from_mention_notification_group(m)))); } } if (m->is_pinned && d->notification_info != nullptr && d->notification_info->pinned_message_notification_message_id_.is_valid() && d->notification_info->mention_notification_group_.is_valid()) { auto pinned_message = get_message_force(d, d->notification_info->pinned_message_notification_message_id_, "after update_message"); if (pinned_message != nullptr && pinned_message->notification_id.is_valid() && is_message_notification_active(d, pinned_message) && get_message_content_pinned_message_id(pinned_message->content.get()) == m->message_id) { send_closure_later( G()->notification_manager(), &NotificationManager::edit_notification, d->notification_info->mention_notification_group_.get_group_id(), pinned_message->notification_id, create_new_message_notification(pinned_message->message_id, is_message_preview_enabled(d, m, true))); } } } void MessagesManager::add_message_to_database(const Dialog *d, const Message *m, const char *source) { if (!G()->use_message_database()) { return; } CHECK(d != nullptr); CHECK(m != nullptr); MessageId message_id = m->message_id; if (message_id.is_scheduled()) { LOG(INFO) << "Add " << MessageFullId(d->dialog_id, message_id) << " to database from " << source; set_dialog_has_scheduled_database_messages(d->dialog_id, true); G()->td_db()->get_message_db_async()->add_scheduled_message({d->dialog_id, message_id}, log_event_store(*m), Auto()); // TODO Promise return; } LOG_CHECK(message_id.is_server() || message_id.is_local()) << source; LOG(INFO) << "Add " << MessageFullId(d->dialog_id, message_id) << " to database from " << source; ServerMessageId unique_message_id; int64 random_id = 0; int64 search_id = 0; string text; switch (d->dialog_id.get_type()) { case DialogType::User: case DialogType::Chat: if (message_id.is_server()) { unique_message_id = message_id.get_server_message_id(); } // FOR DEBUG // text = get_message_search_text(m); // if (!text.empty()) { // search_id = (static_cast(m->date) << 32) | static_cast(Random::secure_int32()); // } break; case DialogType::Channel: break; case DialogType::SecretChat: random_id = m->random_id; text = get_message_search_text(m); if (!text.empty()) { search_id = (static_cast(m->date) << 32) | static_cast(m->random_id); } break; case DialogType::None: default: UNREACHABLE(); } int32 ttl_expires_at = 0; if (m->ttl_expires_at != 0) { ttl_expires_at = static_cast(m->ttl_expires_at - Time::now() + G()->server_time()) + 1; } if (m->ttl_period != 0 && (ttl_expires_at == 0 || m->date + m->ttl_period < ttl_expires_at)) { ttl_expires_at = m->date + m->ttl_period; } G()->td_db()->get_message_db_async()->add_message({d->dialog_id, message_id}, unique_message_id, get_message_sender(m), random_id, ttl_expires_at, get_message_index_mask(d->dialog_id, m), search_id, text, m->notification_id, m->top_thread_message_id, log_event_store(*m), Auto()); // TODO Promise } void MessagesManager::delete_all_dialog_notifications(Dialog *d, MessageId max_message_id, const char *source) { CHECK(d != nullptr); if (d->notification_info == nullptr) { return; } if (d->notification_info->new_secret_chat_notification_id_.is_valid()) { remove_new_secret_chat_notification(d, true); } if (d->notification_info->pinned_message_notification_message_id_.is_valid() && d->notification_info->pinned_message_notification_message_id_ <= max_message_id) { remove_dialog_pinned_message_notification(d, source); } remove_message_dialog_notifications(d, max_message_id, false, source); remove_message_dialog_notifications(d, max_message_id, true, source); } void MessagesManager::delete_all_dialog_messages_from_database(Dialog *d, MessageId max_message_id, const char *source) { CHECK(d != nullptr); CHECK(max_message_id.is_valid()); if (!G()->use_message_database()) { return; } auto dialog_id = d->dialog_id; LOG(INFO) << "Delete all messages in " << dialog_id << " from database up to " << max_message_id << " from " << source; /* if (dialog_id.get_type() == DialogType::User && max_message_id.is_server()) { bool need_save = false; int i = 0; for (auto &first_message_id : calls_db_state_.first_calls_database_message_id_by_index) { if (first_message_id <= max_message_id) { first_message_id = max_message_id.get_next_server_message_id(); calls_db_state_.message_count_by_index[i] = -1; need_save = true; } i++; } if (need_save) { save_calls_db_state(); } } */ G()->td_db()->get_message_db_async()->delete_all_dialog_messages(dialog_id, max_message_id, Auto()); // TODO Promise } class MessagesManager::DeleteMessageLogEvent { public: LogEvent::Id id_{0}; MessageFullId message_full_id_; std::vector file_ids_; template void store(StorerT &storer) const { bool has_file_ids = !file_ids_.empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_file_ids); END_STORE_FLAGS(); td::store(message_full_id_, storer); if (has_file_ids) { td::store(file_ids_, storer); } } template void parse(ParserT &parser) { bool has_file_ids; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_file_ids); END_PARSE_FLAGS(); td::parse(message_full_id_, parser); if (has_file_ids) { td::parse(file_ids_, parser); } } }; void MessagesManager::delete_message_files(DialogId dialog_id, const Message *m) const { for (auto file_id : get_message_file_ids(m)) { if (need_delete_file({dialog_id, m->message_id}, file_id)) { send_closure(G()->file_manager(), &FileManager::delete_file, file_id, Promise(), "delete_message_files"); } } } bool MessagesManager::need_delete_file(MessageFullId message_full_id, FileId file_id) const { if (being_readded_message_id_ == message_full_id || td_->auth_manager_->is_bot()) { return false; } auto main_file_id = td_->file_manager_->get_file_view(file_id).get_main_file_id(); auto message_full_ids = td_->file_reference_manager_->get_some_message_file_sources(main_file_id); LOG(INFO) << "Receive " << message_full_ids << " as sources for file " << main_file_id << "/" << file_id << " from " << message_full_id; for (const auto &other_full_messsage_id : message_full_ids) { if (other_full_messsage_id != message_full_id) { return false; } } return true; } bool MessagesManager::need_delete_message_files(DialogId dialog_id, const Message *m) const { if (m == nullptr || td_->auth_manager_->is_bot()) { return false; } auto dialog_type = dialog_id.get_type(); if (!m->message_id.is_scheduled() && !m->message_id.is_server() && dialog_type != DialogType::SecretChat) { return false; } if (being_readded_message_id_ == MessageFullId{dialog_id, m->message_id}) { return false; } if (m->forward_info != nullptr && m->forward_info->from_dialog_id.is_valid() && m->forward_info->from_message_id.is_valid()) { // this function must not try to load the message, because it can be called from // do_delete_message or add_scheduled_message_to_dialog const Message *old_m = get_message({m->forward_info->from_dialog_id, m->forward_info->from_message_id}); if (old_m != nullptr && get_message_file_ids(old_m) == get_message_file_ids(m)) { return false; } } return true; } void MessagesManager::delete_message_from_database(Dialog *d, MessageId message_id, const Message *m, bool is_permanently_deleted, const char *source) { CHECK(d != nullptr); if (!message_id.is_valid() && !message_id.is_valid_scheduled()) { return; } if (message_id.is_yet_unsent()) { return; } if (m != nullptr && !m->message_id.is_scheduled() && m->message_id.is_local() && m->top_thread_message_id.is_valid() && m->top_thread_message_id != m->message_id) { // must not load the message from the database Message *top_m = get_message(d, m->top_thread_message_id); if (top_m != nullptr && top_m->top_thread_message_id == top_m->message_id) { auto it = std::lower_bound(top_m->local_thread_message_ids.begin(), top_m->local_thread_message_ids.end(), m->message_id); if (it != top_m->local_thread_message_ids.end() && *it == m->message_id) { top_m->local_thread_message_ids.erase(it); on_message_changed(d, top_m, false, "delete_message_from_database"); } } } if (is_permanently_deleted) { if (message_id.is_scheduled() && message_id.is_valid_scheduled() && message_id.is_scheduled_server()) { add_dialog_scheduled_messages(d)->deleted_scheduled_server_message_ids_.insert( message_id.get_scheduled_server_message_id()); } else { // don't store failed to send message identifiers for bots to reduce memory usage if (m == nullptr || !td_->auth_manager_->is_bot() || !m->is_failed_to_send) { d->deleted_message_ids.insert(message_id); send_closure_later(actor_id(this), &MessagesManager::update_message_max_reply_media_timestamp_in_replied_messages, d->dialog_id, message_id); } } if (message_id.is_any_server()) { auto old_message_id = find_old_message_id(d->dialog_id, message_id); if (old_message_id.is_valid()) { bool have_old_message = get_message(d, old_message_id) != nullptr; LOG(WARNING) << "Sent " << MessageFullId{d->dialog_id, message_id} << " was deleted before it was received. Have old " << old_message_id << " = " << have_old_message; send_closure_later(actor_id(this), &MessagesManager::delete_messages, d->dialog_id, vector{old_message_id}, false, Promise()); delete_update_message_id(d->dialog_id, message_id); } } } if (m != nullptr && m->notification_id.is_valid()) { CHECK(!message_id.is_scheduled()); auto from_mentions = is_from_mention_notification_group(m); auto &group_info = get_notification_group_info(d, from_mentions); if (group_info.is_valid()) { if (group_info.get_last_notification_id() == m->notification_id) { // last notification is deleted, need to find new last notification fix_dialog_last_notification_id(d, from_mentions, m->message_id); } if (is_message_notification_active(d, m)) { send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification, group_info.get_group_id(), m->notification_id, true, false, Promise(), "delete_message_from_database"); } } } else if (!message_id.is_scheduled() && message_id > d->last_new_message_id && d->notification_info != nullptr) { send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_object_id, d->notification_info->message_notification_group_.get_group_id(), message_id, false, "delete_message_from_database"); send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_object_id, d->notification_info->mention_notification_group_.get_group_id(), message_id, false, "delete_message_from_database"); } auto need_delete_files = need_delete_message_files(d->dialog_id, m); if (need_delete_files) { delete_message_files(d->dialog_id, m); } if (G()->use_message_database()) { DeleteMessageLogEvent log_event; log_event.message_full_id_ = {d->dialog_id, message_id}; if (need_delete_files) { log_event.file_ids_ = get_message_file_ids(m); } do_delete_message_log_event(log_event); } on_message_deleted_from_database(d, m, source); } void MessagesManager::do_delete_message_log_event(const DeleteMessageLogEvent &log_event) const { CHECK(G()->use_message_database()); Promise db_promise; if (!log_event.file_ids_.empty()) { auto log_event_id = log_event.id_; if (log_event_id == 0) { log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteMessage, get_log_event_storer(log_event)); } MultiPromiseActorSafe mpas{"DeleteMessageMultiPromiseActor"}; mpas.add_promise( PromiseCreator::lambda([log_event_id, context_weak_ptr = get_context_weak_ptr()](Result result) { auto context = context_weak_ptr.lock(); if (result.is_error() || context == nullptr) { return; } CHECK(context->get_id() == Global::ID); auto global = static_cast(context.get()); if (global->close_flag()) { return; } binlog_erase(global->td_db()->get_binlog(), log_event_id); })); auto lock = mpas.get_promise(); for (auto file_id : log_event.file_ids_) { if (need_delete_file(log_event.message_full_id_, file_id)) { send_closure(G()->file_manager(), &FileManager::delete_file, file_id, mpas.get_promise(), "do_delete_message_log_event"); } } db_promise = mpas.get_promise(); lock.set_value(Unit()); } // message may not exist in the dialog LOG(INFO) << "Delete " << log_event.message_full_id_ << " from database"; G()->td_db()->get_message_db_async()->delete_message(log_event.message_full_id_, std::move(db_promise)); } int64 MessagesManager::get_message_reply_to_random_id(const Dialog *d, const Message *m) const { auto same_chat_reply_to_message_id = m->replied_message_info.get_same_chat_reply_to_message_id(false); if (same_chat_reply_to_message_id != MessageId() && m->message_id.is_yet_unsent() && (d->dialog_id.get_type() == DialogType::SecretChat || same_chat_reply_to_message_id.is_yet_unsent())) { auto *replied_m = get_message(d, same_chat_reply_to_message_id); if (replied_m != nullptr) { return replied_m->random_id; } } return 0; } bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr new_message, bool is_message_in_dialog) { CHECK(d != nullptr); CHECK(old_message != nullptr); CHECK(new_message != nullptr); LOG_CHECK(old_message->message_id == new_message->message_id) << d->dialog_id << ' ' << old_message->message_id << ' ' << new_message->message_id << ' ' << is_message_in_dialog; DialogId dialog_id = d->dialog_id; MessageId message_id = old_message->message_id; auto old_content_type = old_message->content->get_type(); auto new_content_type = new_message->content->get_type(); bool is_scheduled = message_id.is_scheduled(); bool need_send_update = false; bool is_new_available = new_content_type != MessageContentType::ChatDeleteHistory && new_message->restriction_reasons.empty(); bool replace_legacy = (old_message->legacy_layer != 0 && (new_message->legacy_layer == 0 || old_message->legacy_layer < new_message->legacy_layer)) || old_content_type == MessageContentType::Unsupported; bool was_visible_message_reply_info = is_visible_message_reply_info(dialog_id, old_message); if (old_message->date != new_message->date) { if (new_message->date > 0 && (is_scheduled || !is_message_in_dialog)) { if (!(is_scheduled || message_id.is_yet_unsent() || (message_id.is_server() && message_id.get_server_message_id().get() == 1) || old_content_type == MessageContentType::ChannelMigrateFrom || old_content_type == MessageContentType::ChannelCreate)) { LOG(ERROR) << "Date has changed for " << message_id << " in " << dialog_id << " from " << old_message->date << " to " << new_message->date << ", message content type is " << old_content_type << '/' << new_content_type; } CHECK(old_message->date > 0); LOG(DEBUG) << "Message date has changed from " << old_message->date << " to " << new_message->date; old_message->date = new_message->date; if (is_scheduled && message_id.is_scheduled_server()) { CHECK(d->scheduled_messages != nullptr); CHECK(is_message_in_dialog); int32 &date = d->scheduled_messages->scheduled_message_date_[message_id.get_scheduled_server_message_id()]; CHECK(date != 0); date = new_message->date; } need_send_update = true; } else { LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with date changed from " << old_message->date << " to " << new_message->date << ", message content type is " << old_content_type << '/' << new_content_type; } } if (old_message->date == old_message->edited_schedule_date) { old_message->edited_schedule_date = 0; } bool is_edited = false; int32 old_shown_edit_date = old_message->hide_edit_date ? 0 : old_message->edit_date; if (old_message->edit_date != new_message->edit_date) { if (new_message->edit_date > 0) { if (new_message->edit_date > old_message->edit_date) { LOG(DEBUG) << "Message edit date has changed from " << old_message->edit_date << " to " << new_message->edit_date; old_message->edit_date = new_message->edit_date; } } else { LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " of type " << old_content_type << "/" << new_content_type << " with wrong edit date " << new_message->edit_date << ", old edit date = " << old_message->edit_date; } } if (old_message->author_signature != new_message->author_signature) { LOG(DEBUG) << "Author signature has changed for " << message_id << " in " << dialog_id << " sent by " << old_message->sender_user_id << "/" << new_message->sender_user_id << " or " << old_message->sender_dialog_id << "/" << new_message->sender_dialog_id << " from " << old_message->author_signature << " to " << new_message->author_signature; old_message->author_signature = std::move(new_message->author_signature); need_send_update = true; } if (old_message->sender_user_id != new_message->sender_user_id) { // there can be race for sent signed posts or changed anonymous flag LOG_IF(ERROR, old_message->sender_user_id != UserId() && new_message->sender_user_id != UserId()) << message_id << " in " << dialog_id << " has changed sender from " << old_message->sender_user_id << " to " << new_message->sender_user_id << ", message content type is " << old_content_type << '/' << new_content_type; LOG_IF(WARNING, (new_message->sender_user_id.is_valid() || old_message->author_signature.empty()) && !old_message->sender_dialog_id.is_valid() && !new_message->sender_dialog_id.is_valid()) << "Update message sender from " << old_message->sender_user_id << " to " << new_message->sender_user_id << " in " << dialog_id; LOG(DEBUG) << "Change message sender"; old_message->sender_user_id = new_message->sender_user_id; need_send_update = true; } if (old_message->sender_dialog_id != new_message->sender_dialog_id) { // there can be race for changed anonymous flag LOG_IF(ERROR, old_message->sender_dialog_id != DialogId() && new_message->sender_dialog_id != DialogId()) << message_id << " in " << dialog_id << " has changed sender from " << old_message->sender_dialog_id << " to " << new_message->sender_dialog_id << ", message content type is " << old_content_type << '/' << new_content_type; LOG(DEBUG) << "Change message sender"; old_message->sender_dialog_id = new_message->sender_dialog_id; need_send_update = true; } if (old_message->forward_info == nullptr) { if (new_message->forward_info != nullptr) { if (!replace_legacy) { LOG(ERROR) << message_id << " in " << dialog_id << " has received forward info " << *new_message->forward_info << ", really forwarded from " << old_message->real_forward_from_message_id << " in " << old_message->real_forward_from_dialog_id << ", message content type is " << old_content_type << '/' << new_content_type; } else { LOG(DEBUG) << "Message forward has changed to " << *new_message->forward_info; } old_message->forward_info = std::move(new_message->forward_info); need_send_update = true; } } else { if (new_message->forward_info != nullptr) { if (*old_message->forward_info != *new_message->forward_info) { bool need_warning = [&] { if (replace_legacy) { return false; } if (old_message->forward_info->is_imported || new_message->forward_info->is_imported) { return true; } if (!is_scheduled && !message_id.is_yet_unsent()) { return true; } // yet unsent or scheduled messages can change sender name or author signature when being sent return !old_message->forward_info->origin.has_sender_signature() && !new_message->forward_info->origin.has_sender_signature(); }(); if (need_warning) { LOG(ERROR) << message_id << " in " << dialog_id << " has changed forward info from " << *old_message->forward_info << " to " << *new_message->forward_info << ", really forwarded from " << old_message->real_forward_from_message_id << " in " << old_message->real_forward_from_dialog_id << ", message content type is " << old_content_type << '/' << new_content_type; } else { LOG(DEBUG) << "Message forward info has changed from " << *old_message->forward_info << " to " << *new_message->forward_info; } old_message->forward_info = std::move(new_message->forward_info); need_send_update = true; } } else if (is_new_available) { LOG(ERROR) << message_id << " in " << dialog_id << " sent by " << old_message->sender_user_id << "/" << old_message->sender_dialog_id << " has lost forward info " << *old_message->forward_info << ", really forwarded from " << old_message->real_forward_from_message_id << " in " << old_message->real_forward_from_dialog_id << ", message content type is " << old_content_type << '/' << new_content_type; old_message->forward_info = nullptr; need_send_update = true; } } if (old_message->had_forward_info != new_message->had_forward_info) { LOG(DEBUG) << "Message had_forward_info has changed from " << old_message->had_forward_info << " to " << new_message->had_forward_info; old_message->had_forward_info = new_message->had_forward_info; } if (old_message->notification_id != new_message->notification_id) { CHECK(!is_scheduled); if (old_message->notification_id.is_valid()) { if (new_message->notification_id.is_valid()) { LOG(ERROR) << "Notification identifier for " << message_id << " in " << dialog_id << " has tried to change from " << old_message->notification_id << " to " << new_message->notification_id << ", message content type is " << old_content_type << '/' << new_content_type; } } else { CHECK(new_message->notification_id.is_valid()); auto notification_info = add_dialog_notification_info(d); add_notification_id_to_message_id_correspondence(notification_info, new_message->notification_id, message_id); old_message->notification_id = new_message->notification_id; } } if (new_message->is_mention_notification_disabled) { // is_mention_notification_disabled must not be changed, because the message notification will not // be moved to the other group // old_message->is_mention_notification_disabled = true; } if (old_message->ttl_period != new_message->ttl_period) { if (old_message->ttl_period != 0 || !message_id.is_yet_unsent()) { LOG(ERROR) << message_id << " in " << dialog_id << " has changed auto-delete timer from " << old_message->ttl_period << " to " << new_message->ttl_period; } else { LOG(DEBUG) << "Change message auto-delete timer"; old_message->ttl_period = new_message->ttl_period; need_send_update = true; } } const bool is_replied_message_info_changed = old_message->replied_message_info != new_message->replied_message_info; const bool is_top_thread_message_id_changed = old_message->top_thread_message_id != new_message->top_thread_message_id; const bool is_is_topic_message_changed = old_message->is_topic_message != new_message->is_topic_message; if (is_replied_message_info_changed || is_top_thread_message_id_changed || is_is_topic_message_changed || old_message->reply_to_story_full_id != new_message->reply_to_story_full_id) { if (!replace_legacy && is_new_available) { if (is_replied_message_info_changed) { LOG(INFO) << "Update replied message of " << MessageFullId{dialog_id, message_id} << " from " << old_message->replied_message_info << " to " << new_message->replied_message_info; auto is_reply_to_deleted_message = [&](const RepliedMessageInfo &replied_message_info) { auto reply_message_full_id = replied_message_info.get_reply_message_full_id(dialog_id, false); auto *d = get_dialog(reply_message_full_id.get_dialog_id()); if (d == nullptr) { return false; } return is_deleted_message(d, reply_message_full_id.get_message_id()); }; if (RepliedMessageInfo::need_reply_changed_warning( td_, old_message->replied_message_info, new_message->replied_message_info, old_message->top_thread_message_id, message_id.is_yet_unsent() && !is_message_in_dialog, is_reply_to_deleted_message)) { LOG(ERROR) << message_id << " in " << dialog_id << " has changed replied message from " << old_message->replied_message_info << " to " << new_message->replied_message_info << ", message content type is " << old_content_type << '/' << new_content_type; } } if (is_top_thread_message_id_changed) { if ((new_message->top_thread_message_id != MessageId() && old_message->top_thread_message_id != MessageId()) || is_message_in_dialog) { LOG(ERROR) << message_id << " in " << dialog_id << " has changed message thread from " << old_message->top_thread_message_id << " to " << new_message->top_thread_message_id << ", message content type is " << old_content_type << '/' << new_content_type; } else { LOG(INFO) << "Update message thread of " << MessageFullId{dialog_id, message_id} << " from " << old_message->top_thread_message_id << " to " << new_message->top_thread_message_id; } } if (is_is_topic_message_changed) { if (!message_id.is_yet_unsent()) { LOG(ERROR) << message_id << " in " << dialog_id << " has changed is_topic_message to " << new_message->is_topic_message; } else { LOG(INFO) << "Update is_topic_message of " << MessageFullId{dialog_id, message_id} << " from " << old_message->is_topic_message << " to " << new_message->is_topic_message; } } if (old_message->reply_to_story_full_id != new_message->reply_to_story_full_id) { if (!message_id.is_yet_unsent() || new_message->reply_to_story_full_id.is_valid() || is_message_in_dialog) { LOG(ERROR) << message_id << " in " << dialog_id << " has changed replied story from " << old_message->reply_to_story_full_id << " to " << new_message->reply_to_story_full_id << ", message content type is " << old_content_type << '/' << new_content_type; } else { LOG(INFO) << "Update replied story of " << MessageFullId{dialog_id, message_id} << " from " << old_message->reply_to_story_full_id << " to " << new_message->reply_to_story_full_id; } } } if ((is_top_thread_message_id_changed || is_is_topic_message_changed) && is_message_in_dialog && old_message->is_topic_message && old_message->top_thread_message_id != MessageId()) { td_->forum_topic_manager_->on_topic_message_count_changed(dialog_id, old_message->top_thread_message_id, -1); } if (is_message_in_dialog) { unregister_message_reply(d->dialog_id, old_message); } old_message->replied_message_info = std::move(new_message->replied_message_info); old_message->reply_to_story_full_id = new_message->reply_to_story_full_id; old_message->top_thread_message_id = new_message->top_thread_message_id; old_message->is_topic_message = new_message->is_topic_message; old_message->reply_to_random_id = get_message_reply_to_random_id(d, old_message); if (is_message_in_dialog) { register_message_reply(d->dialog_id, old_message); } update_message_max_reply_media_timestamp(d, old_message, is_message_in_dialog); if ((is_top_thread_message_id_changed || is_is_topic_message_changed) && is_message_in_dialog && old_message->is_topic_message && old_message->top_thread_message_id != MessageId()) { td_->forum_topic_manager_->on_topic_message_count_changed(dialog_id, old_message->top_thread_message_id, +1); } need_send_update = true; } if (old_message->via_bot_user_id != new_message->via_bot_user_id) { if ((!message_id.is_yet_unsent() || old_message->via_bot_user_id.is_valid()) && is_new_available && !replace_legacy) { LOG(ERROR) << message_id << " in " << dialog_id << " has changed inline bot from " << old_message->via_bot_user_id << " to " << new_message->via_bot_user_id << ", message content type is " << old_content_type << '/' << new_content_type; } LOG(DEBUG) << "Change message via_bot from " << old_message->via_bot_user_id << " to " << new_message->via_bot_user_id; old_message->via_bot_user_id = new_message->via_bot_user_id; need_send_update = true; if (old_message->hide_via_bot && old_message->via_bot_user_id.is_valid()) { // wrongly set hide_via_bot old_message->hide_via_bot = false; } } if (old_message->is_outgoing != new_message->is_outgoing && is_new_available) { if (!replace_legacy && !(message_id.is_scheduled() && dialog_id == get_my_dialog_id())) { LOG(ERROR) << message_id << " in " << dialog_id << " has changed is_outgoing from " << old_message->is_outgoing << " to " << new_message->is_outgoing << ", message content type is " << old_content_type << '/' << new_content_type; if (new_message->is_outgoing) { old_message->is_outgoing = new_message->is_outgoing; need_send_update = true; } } else { LOG(DEBUG) << "Message is_outgoing has changed from " << old_message->is_outgoing << " to " << new_message->is_outgoing; old_message->is_outgoing = new_message->is_outgoing; need_send_update = true; } } LOG_IF(ERROR, old_message->is_channel_post != new_message->is_channel_post) << message_id << " in " << dialog_id << " has changed is_channel_post from " << old_message->is_channel_post << " to " << new_message->is_channel_post << ", message content type is " << old_content_type << '/' << new_content_type; if (old_message->contains_mention != new_message->contains_mention) { if (old_message->edit_date == 0 && is_new_available && new_content_type != MessageContentType::PinMessage && new_content_type != MessageContentType::ExpiredPhoto && new_content_type != MessageContentType::ExpiredVideo && !replace_legacy) { LOG(ERROR) << message_id << " in " << dialog_id << " has changed contains_mention from " << old_message->contains_mention << " to " << new_message->contains_mention << ", is_outgoing = " << old_message->is_outgoing << ", message content type is " << old_content_type << '/' << new_content_type; } // contains_mention flag shouldn't be changed, because the message will not be added to unread mention list // and we are unable to show/hide message notification // old_message->contains_mention = new_message->contains_mention; // need_send_update = true; } if (old_message->disable_notification != new_message->disable_notification) { LOG_IF(ERROR, old_message->edit_date == 0 && is_new_available && !replace_legacy) << "Disable_notification has changed from " << old_message->disable_notification << " to " << new_message->disable_notification << ". Old message: " << to_string(get_message_object(dialog_id, old_message, "update_message")) << ". New message: " << to_string(get_message_object(dialog_id, new_message.get(), "update_message")); // disable_notification flag shouldn't be changed, because we are unable to show/hide message notification // old_message->disable_notification = new_message->disable_notification; // need_send_update = true; } if (old_message->noforwards != new_message->noforwards) { LOG(DEBUG) << "Message can_be_saved has changed from " << !old_message->noforwards << " to " << !old_message->noforwards; old_message->noforwards = new_message->noforwards; need_send_update = true; } if (old_message->restriction_reasons != new_message->restriction_reasons) { LOG(DEBUG) << "Message restriction_reasons have changed from " << old_message->restriction_reasons << " to " << old_message->restriction_reasons; old_message->restriction_reasons = std::move(new_message->restriction_reasons); need_send_update = true; } if (old_message->legacy_layer != new_message->legacy_layer) { old_message->legacy_layer = new_message->legacy_layer; } if (old_message->media_album_id != new_message->media_album_id && (old_message->media_album_id == 0 || (new_message->media_album_id == 0 && message_id.is_yet_unsent()) || td_->auth_manager_->is_bot())) { old_message->media_album_id = new_message->media_album_id; LOG(DEBUG) << "Update message media_album_id"; need_send_update = true; } if (old_message->hide_edit_date != new_message->hide_edit_date) { old_message->hide_edit_date = new_message->hide_edit_date; } int32 new_shown_edit_date = old_message->hide_edit_date ? 0 : old_message->edit_date; if (new_shown_edit_date != old_shown_edit_date) { LOG(DEBUG) << "Message edit_date has changed"; is_edited = true; need_send_update = true; } if (old_message->is_from_scheduled != new_message->is_from_scheduled) { // is_from_scheduled flag shouldn't be changed, because we are unable to show/hide message notification // old_message->is_from_scheduled = new_message->is_from_scheduled; } if (old_message->edit_date > 0) { // inline keyboard can be edited bool reply_markup_changed = ((old_message->reply_markup == nullptr) != (new_message->reply_markup == nullptr)) || (old_message->reply_markup != nullptr && *old_message->reply_markup != *new_message->reply_markup); if (reply_markup_changed) { if (d->reply_markup_message_id == message_id && !td_->auth_manager_->is_bot() && new_message->reply_markup == nullptr) { set_dialog_reply_markup(d, MessageId()); } LOG(DEBUG) << "Update message reply keyboard"; old_message->reply_markup = std::move(new_message->reply_markup); is_edited = true; need_send_update = true; } old_message->had_reply_markup = false; } else { if (old_message->reply_markup == nullptr) { if (new_message->reply_markup != nullptr) { // MessageGame and MessageInvoice reply markup can be generated server side // some forwards retain their reply markup if (old_content_type != MessageContentType::Game && old_content_type != MessageContentType::Invoice && need_message_changed_warning(old_message) && !replace_legacy) { LOG(ERROR) << message_id << " in " << dialog_id << " has received reply markup " << *new_message->reply_markup << ", message content type is " << old_content_type << '/' << new_content_type; } else { LOG(DEBUG) << "Add message reply keyboard"; } old_message->had_reply_markup = false; old_message->reply_markup = std::move(new_message->reply_markup); need_send_update = true; } } else { if (new_message->reply_markup != nullptr) { if (replace_legacy || (message_id.is_yet_unsent() && old_message->reply_markup->type == ReplyMarkup::Type::InlineKeyboard && new_message->reply_markup->type == ReplyMarkup::Type::InlineKeyboard)) { // allow the server to update inline keyboard for sent messages // this is needed to get correct button_id for UrlAuth buttons old_message->had_reply_markup = false; old_message->reply_markup = std::move(new_message->reply_markup); need_send_update = true; } else if (need_message_changed_warning(old_message) && is_new_available) { LOG_IF(WARNING, *old_message->reply_markup != *new_message->reply_markup) << message_id << " in " << dialog_id << " has changed reply_markup from " << *old_message->reply_markup << " to " << *new_message->reply_markup; } } else { // if the message is not accessible anymore, then we don't need a warning if (need_message_changed_warning(old_message) && is_new_available) { LOG(ERROR) << message_id << " in " << dialog_id << " sent by " << old_message->sender_user_id << "/" << old_message->sender_dialog_id << " has lost reply markup " << *old_message->reply_markup << ". Old message: " << to_string(get_message_object(dialog_id, old_message, "update_message")) << ". New message: " << to_string(get_message_object(dialog_id, new_message.get(), "update_message")); } } } } if (old_message->last_access_date < new_message->last_access_date) { old_message->last_access_date = new_message->last_access_date; } if (old_message->history_generation < new_message->history_generation) { old_message->history_generation = new_message->history_generation; } if (new_message->is_update_sent || is_edited) { old_message->is_update_sent = true; } if (!is_scheduled && update_message_is_pinned(d, old_message, new_message->is_pinned, "update_message")) { need_send_update = true; } if (!is_scheduled && update_message_contains_unread_mention(d, old_message, new_message->contains_unread_mention, "update_message")) { need_send_update = true; } // update_message_interaction_info must be called after top_thread_message_id is updated if (update_message_interaction_info(d, old_message, new_message->view_count, new_message->forward_count, true, std::move(new_message->reply_info), true, std::move(new_message->reactions), "update_message")) { need_send_update = true; } bool is_content_changed = false; if (update_message_content(dialog_id, old_message, std::move(new_message->content), message_id.is_yet_unsent() && new_message->edit_date == 0, is_message_in_dialog, is_content_changed) || old_message->invert_media != new_message->invert_media || old_message->disable_web_page_preview != new_message->disable_web_page_preview) { old_message->disable_web_page_preview = new_message->disable_web_page_preview; old_message->invert_media = new_message->invert_media; send_update_message_content(d, old_message, is_message_in_dialog, "update_message"); need_send_update = true; } if (was_visible_message_reply_info && !is_visible_message_reply_info(dialog_id, old_message)) { send_update_message_interaction_info(dialog_id, old_message); } if (is_edited && !td_->auth_manager_->is_bot()) { send_update_message_edited(dialog_id, old_message); } if (!was_visible_message_reply_info && is_visible_message_reply_info(dialog_id, old_message)) { send_update_message_interaction_info(dialog_id, old_message); } on_message_changed(d, old_message, need_send_update, "update_message"); return need_send_update; } bool MessagesManager::need_message_changed_warning(const Message *old_message) { if (old_message->edit_date > 0) { // message was edited return false; } if (old_message->message_id.is_yet_unsent() && (old_message->forward_info != nullptr || old_message->had_forward_info || old_message->real_forward_from_dialog_id.is_valid())) { // original message may be edited return false; } if (old_message->ttl > 0) { // message can expire return false; } if (!old_message->restriction_reasons.empty()) { return false; } return true; } bool MessagesManager::update_message_content(DialogId dialog_id, Message *old_message, unique_ptr new_content, bool need_merge_files, bool is_message_in_dialog, bool &is_content_changed) { is_content_changed = false; bool need_update = false; unique_ptr &old_content = old_message->content; MessageContentType old_content_type = old_content->get_type(); MessageContentType new_content_type = new_content->get_type(); auto old_file_id = get_message_content_any_file_id(old_content.get()); bool need_finish_upload = old_file_id.is_valid() && need_merge_files; if (old_content_type != new_content_type) { if (old_message->ttl > 0 && old_message->ttl_expires_at > 0 && ((new_content_type == MessageContentType::ExpiredPhoto && old_content_type == MessageContentType::Photo) || (new_content_type == MessageContentType::ExpiredVideo && old_content_type == MessageContentType::Video))) { LOG(INFO) << "Do not apply expired message content early"; } else { need_update = true; LOG(INFO) << "Message content has changed type from " << old_content_type << " to " << new_content_type; old_message->is_content_secret = is_secret_message_content(old_message->ttl, new_content->get_type()); } if (need_merge_files && old_file_id.is_valid()) { auto new_file_id = get_message_content_any_file_id(new_content.get()); if (new_file_id.is_valid()) { FileView old_file_view = td_->file_manager_->get_file_view(old_file_id); FileView new_file_view = td_->file_manager_->get_file_view(new_file_id); // if file type has changed, but file size remains the same, we are trying to update local location of the new // file with the old local location if (old_file_view.has_local_location() && !new_file_view.has_local_location() && old_file_view.size() != 0 && old_file_view.size() == new_file_view.size()) { auto old_file_type = old_file_view.get_type(); auto new_file_type = new_file_view.get_type(); if (is_document_file_type(old_file_type) && is_document_file_type(new_file_type)) { auto &old_location = old_file_view.local_location(); auto r_file_id = td_->file_manager_->register_local( FullLocalFileLocation(new_file_type, old_location.path_, old_location.mtime_nsec_), dialog_id, old_file_view.size()); if (r_file_id.is_ok()) { LOG_STATUS(td_->file_manager_->merge(new_file_id, r_file_id.ok())); } } } } } } else { merge_message_contents(td_, old_content.get(), new_content.get(), need_message_changed_warning(old_message), dialog_id, need_merge_files, is_content_changed, need_update); compare_message_contents(td_, old_content.get(), new_content.get(), is_content_changed, need_update); } if (need_finish_upload) { // the file is likely to be already merged with a server file, but if not we need to // cancel file upload of the main file to allow next upload with the same file to succeed cancel_upload_file(old_file_id, "update_message_content"); } if (is_content_changed || need_update) { if (is_message_in_dialog) { reregister_message_content(td_, old_content.get(), new_content.get(), {dialog_id, old_message->message_id}, "update_message_content"); } old_content = std::move(new_content); old_message->last_edit_pts = 0; update_message_content_file_id_remote(old_content.get(), old_file_id); } else { update_message_content_file_id_remote(old_content.get(), get_message_content_any_file_id(new_content.get())); } if (is_content_changed && !need_update) { LOG(INFO) << "Content of " << old_message->message_id << " in " << dialog_id << " has changed"; } if (need_update) { auto file_ids = get_message_content_file_ids(old_content.get(), td_); if (!file_ids.empty()) { auto file_source_id = get_message_file_source_id(MessageFullId(dialog_id, old_message->message_id)); if (file_source_id.is_valid()) { auto search_text = get_message_search_text(old_message); for (auto file_id : file_ids) { auto file_view = td_->file_manager_->get_file_view(file_id); send_closure(td_->download_manager_actor_, &DownloadManager::change_search_text, file_view.get_main_file_id(), file_source_id, search_text); } } } } return need_update; } MessagesManager::Dialog *MessagesManager::get_dialog_by_message_id(MessageId message_id) { CHECK(message_id.is_valid() && message_id.is_server()); auto dialog_id = message_id_to_dialog_id_.get(message_id); if (dialog_id == DialogId()) { if (G()->use_message_database()) { auto r_value = G()->td_db()->get_message_db_sync()->get_message_by_unique_message_id(message_id.get_server_message_id()); if (r_value.is_ok()) { Message *m = on_get_message_from_database(r_value.ok(), false, "get_dialog_by_message_id"); if (m != nullptr) { dialog_id = r_value.ok().dialog_id; CHECK(m->message_id == message_id); LOG_CHECK(message_id_to_dialog_id_.get(message_id) == dialog_id) << message_id << ' ' << dialog_id << ' ' << message_id_to_dialog_id_.get(message_id) << ' ' << m->debug_source; Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); return d; } } } LOG(INFO) << "Can't find the chat by " << message_id; return nullptr; } return get_dialog(dialog_id); } MessageId MessagesManager::get_message_id_by_random_id(Dialog *d, int64 random_id, const char *source) { CHECK(d != nullptr); if (random_id == 0) { return MessageId(); } auto it = d->random_id_to_message_id.find(random_id); if (it == d->random_id_to_message_id.end()) { if (G()->use_message_database() && d->dialog_id.get_type() == DialogType::SecretChat && random_id != 0) { auto r_value = G()->td_db()->get_message_db_sync()->get_message_by_random_id(d->dialog_id, random_id); if (r_value.is_ok()) { debug_add_message_to_dialog_fail_reason_ = "not called"; Message *m = on_get_message_from_database(d, r_value.ok(), false, "get_message_id_by_random_id"); if (m != nullptr) { LOG_CHECK(m->random_id == random_id) << random_id << " " << m->random_id << " " << d->random_id_to_message_id[random_id] << " " << d->random_id_to_message_id[m->random_id] << " " << m->message_id << " " << source << " " << get_message(d, m->message_id) << " " << m << " " << debug_add_message_to_dialog_fail_reason_; LOG_CHECK(d->random_id_to_message_id.count(random_id)) << source << " " << random_id << " " << m->message_id << " " << m->is_failed_to_send << " " << m->is_outgoing << " " << get_message(d, m->message_id) << " " << m << " " << debug_add_message_to_dialog_fail_reason_; LOG_CHECK(d->random_id_to_message_id[random_id] == m->message_id) << source << " " << random_id << " " << d->random_id_to_message_id[random_id] << " " << m->message_id << " " << m->is_failed_to_send << " " << m->is_outgoing << " " << get_message(d, m->message_id) << " " << m << " " << debug_add_message_to_dialog_fail_reason_; LOG(INFO) << "Found " << MessageFullId{d->dialog_id, m->message_id} << " by random_id " << random_id << " from " << source; return m->message_id; } } } LOG(INFO) << "Found no message by random_id " << random_id << " from " << source; return MessageId(); } LOG(INFO) << "Found " << MessageFullId{d->dialog_id, it->second} << " by random_id " << random_id << " from " << source; return it->second; } void MessagesManager::try_update_dialog_pos(DialogId dialog_id) { Dialog *d = get_dialog(dialog_id); if (d != nullptr && d->is_update_new_chat_sent) { update_dialog_pos(d, "try_update_dialog_pos"); } } void MessagesManager::force_create_dialog(DialogId dialog_id, const char *source, bool expect_no_access, bool force_update_dialog_pos) { LOG_CHECK(dialog_id.is_valid()) << source; LOG_CHECK(is_inited_) << dialog_id << ' ' << source << ' ' << expect_no_access << ' ' << force_update_dialog_pos; Dialog *d = get_dialog_force(dialog_id, source); if (d == nullptr) { LOG(INFO) << "Force create " << dialog_id << " from " << source; if (loaded_dialogs_.count(dialog_id) > 0) { LOG(INFO) << "Skip creation of " << dialog_id << ", because it is being loaded now"; return; } d = add_dialog(dialog_id, source); update_dialog_pos(d, source); if (dialog_id.get_type() == DialogType::SecretChat && !d->notification_settings.is_synchronized && td_->contacts_manager_->get_secret_chat_state(dialog_id.get_secret_chat_id()) != SecretChatState::Closed) { // secret chat is being created // let's copy notification settings from main chat if available VLOG(notifications) << "Create new secret " << dialog_id << " from " << source; auto secret_chat_id = dialog_id.get_secret_chat_id(); { auto user_id = td_->contacts_manager_->get_secret_chat_user_id(secret_chat_id); Dialog *user_d = get_dialog_force(DialogId(user_id), source); if (user_d != nullptr && user_d->notification_settings.is_synchronized) { VLOG(notifications) << "Copy notification settings from " << user_d->dialog_id << " to " << dialog_id; auto user_settings = &user_d->notification_settings; auto new_notification_settings = DialogNotificationSettings( user_settings->use_default_mute_until, user_settings->mute_until, dup_notification_sound(user_settings->sound), true /*use_default_show_preview*/, false /*show_preview*/, user_settings->use_default_mute_stories, user_settings->mute_stories, dup_notification_sound(user_settings->story_sound), user_settings->use_default_hide_story_sender, user_settings->hide_story_sender, user_settings->silent_send_message, true, false, true, false); new_notification_settings.is_secret_chat_show_preview_fixed = true; update_dialog_notification_settings(dialog_id, &d->notification_settings, std::move(new_notification_settings)); } else { d->notification_settings.is_synchronized = true; } } if (G()->use_message_database() && !td_->auth_manager_->is_bot() && !td_->contacts_manager_->get_secret_chat_is_outbound(secret_chat_id)) { auto notification_info = add_dialog_notification_info(d); auto notification_group_id = get_dialog_notification_group_id(dialog_id, notification_info->message_notification_group_); if (notification_group_id.is_valid()) { if (notification_info->new_secret_chat_notification_id_.is_valid()) { LOG(ERROR) << "Found previously created " << notification_info->new_secret_chat_notification_id_ << " in " << d->dialog_id << ", when creating it from " << source; } else { notification_info->new_secret_chat_notification_id_ = get_next_notification_id(notification_info, notification_group_id, MessageId()); if (notification_info->new_secret_chat_notification_id_.is_valid()) { auto date = td_->contacts_manager_->get_secret_chat_date(secret_chat_id); set_dialog_last_notification_checked(dialog_id, notification_info->message_notification_group_, date, notification_info->new_secret_chat_notification_id_, "add_new_secret_chat"); VLOG(notifications) << "Create " << notification_info->new_secret_chat_notification_id_ << " with " << secret_chat_id; auto ringtone_id = get_dialog_notification_ringtone_id(dialog_id, d); send_closure_later(G()->notification_manager(), &NotificationManager::add_notification, notification_group_id, NotificationGroupType::SecretChat, dialog_id, date, dialog_id, false, ringtone_id, 0, notification_info->new_secret_chat_notification_id_, create_new_secret_chat_notification(), "add_new_secret_chat_notification"); } } } } } if (!have_input_peer(dialog_id, AccessRights::Read)) { if (!have_dialog_info(dialog_id)) { if (expect_no_access && dialog_id.get_type() == DialogType::Channel && td_->contacts_manager_->have_min_channel(dialog_id.get_channel_id())) { LOG(INFO) << "Created " << dialog_id << " for min-channel from " << source; } else { LOG(ERROR) << "Forced to create unknown " << dialog_id << " from " << source; } } else if (!expect_no_access) { LOG(ERROR) << "Have no access to " << dialog_id << " received from " << source << ", but forced to create it"; } } } else if (force_update_dialog_pos) { update_dialog_pos(d, "force update chat position"); } } MessagesManager::Dialog *MessagesManager::add_dialog_for_new_message(DialogId dialog_id, bool have_last_message, bool *need_update_dialog_pos, const char *source) { if (have_last_message) { CHECK(!being_added_by_new_message_dialog_id_.is_valid()); being_added_by_new_message_dialog_id_ = dialog_id; } auto *d = add_dialog(dialog_id, source); CHECK(d != nullptr); being_added_by_new_message_dialog_id_ = DialogId(); *need_update_dialog_pos = true; return d; } MessagesManager::Dialog *MessagesManager::add_dialog(DialogId dialog_id, const char *source) { LOG(DEBUG) << "Creating " << dialog_id << " from " << source; CHECK(!have_dialog(dialog_id)); LOG_CHECK(dialog_id.is_valid()) << source; if (G()->use_message_database() && failed_to_load_dialogs_.count(dialog_id) == 0) { auto r_value = G()->td_db()->get_dialog_db_sync()->get_dialog(dialog_id); if (r_value.is_ok()) { LOG(INFO) << "Synchronously loaded " << dialog_id << " from database from " << source; return add_new_dialog(parse_dialog(dialog_id, r_value.ok(), source), true, source); } } auto dialog = make_unique(); dialog->dialog_id = dialog_id; invalidate_message_indexes(dialog.get()); return add_new_dialog(std::move(dialog), false, source); } MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr &&dialog, bool is_loaded_from_database, const char *source) { Dialog *d = dialog.get(); auto dialog_id = d->dialog_id; LOG_CHECK(is_inited_) << dialog_id << ' ' << is_loaded_from_database << ' ' << source; LOG_CHECK(!have_dialog(dialog_id)) << dialog_id << ' ' << is_loaded_from_database << ' ' << source; switch (dialog_id.get_type()) { case DialogType::User: if (dialog_id == get_my_dialog_id() && d->last_read_inbox_message_id == MessageId::max() && d->last_read_outbox_message_id == MessageId::max()) { d->last_read_inbox_message_id = d->last_new_message_id; d->last_read_outbox_message_id = d->last_new_message_id; } d->has_bots = dialog_id.get_user_id() != ContactsManager::get_replies_bot_user_id() && td_->contacts_manager_->is_user_bot(dialog_id.get_user_id()); d->is_has_bots_inited = true; d->is_available_reactions_inited = true; d->is_view_as_messages_inited = true; break; case DialogType::Chat: d->is_is_blocked_inited = true; d->is_is_blocked_for_stories_inited = true; d->is_background_inited = true; d->is_view_as_messages_inited = true; break; case DialogType::Channel: { if (td_->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id())) { d->last_read_outbox_message_id = MessageId::max(); d->is_last_read_outbox_message_id_inited = true; } auto pts = load_channel_pts(dialog_id); if (pts > 0) { d->pts = pts; } break; } case DialogType::SecretChat: if (d->last_new_message_id.get() <= MessageId::min().get()) { LOG(INFO) << "Set " << dialog_id << " last new message in add_new_dialog from " << source; d->last_new_message_id = MessageId::min().get_next_message_id(MessageType::Local); } if (!d->notification_settings.is_secret_chat_show_preview_fixed) { d->notification_settings.use_default_show_preview = true; d->notification_settings.show_preview = false; d->notification_settings.is_secret_chat_show_preview_fixed = true; on_dialog_updated(dialog_id, "fix secret chat show preview"); } d->have_full_history = true; d->have_full_history_source = 4; d->need_restore_reply_markup = false; d->is_last_read_inbox_message_id_inited = true; d->is_last_read_outbox_message_id_inited = true; d->is_last_pinned_message_id_inited = true; d->is_background_inited = true; d->is_theme_name_inited = true; d->is_is_blocked_inited = true; d->is_is_blocked_for_stories_inited = true; d->is_view_as_messages_inited = true; if (!d->is_folder_id_inited && !td_->auth_manager_->is_bot()) { do_set_dialog_folder_id( d, td_->contacts_manager_->get_secret_chat_initial_folder_id(dialog_id.get_secret_chat_id())); } d->message_ttl = MessageTtl(td_->contacts_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id())); d->is_message_ttl_inited = true; d->has_bots = td_->contacts_manager_->is_user_bot( td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id())); d->is_has_bots_inited = true; d->is_available_reactions_inited = true; d->has_loaded_scheduled_messages_from_database = true; break; case DialogType::None: default: UNREACHABLE(); } if (!is_loaded_from_database) { on_dialog_updated(dialog_id, "add_new_dialog"); } if (td_->auth_manager_->is_bot()) { d->notification_settings.is_synchronized = true; } if (is_channel_difference_finished_.erase(dialog_id)) { d->is_channel_difference_finished = true; } auto draft_message = std::move(d->draft_message); d->draft_message = nullptr; unique_ptr last_database_message; if (!d->messages.empty()) { d->messages.foreach([&](const MessageId &message_id, unique_ptr &message) { CHECK(last_database_message == nullptr); last_database_message = std::move(message); }); d->messages = {}; } auto last_database_message_id = d->last_database_message_id; d->last_database_message_id = MessageId(); if (td_->auth_manager_->is_bot()) { draft_message = nullptr; last_database_message = nullptr; d->first_database_message_id = MessageId(); last_database_message_id = MessageId(); d->reply_markup_message_id = MessageId(); d->last_assigned_message_id = MessageId(ServerMessageId(1)); d->last_new_message_id = MessageId(); d->last_clear_history_date = 0; d->last_clear_history_message_id = MessageId(); d->order = DEFAULT_ORDER; } int64 order = d->order; d->order = DEFAULT_ORDER; int32 last_clear_history_date = d->last_clear_history_date; MessageId last_clear_history_message_id = d->last_clear_history_message_id; d->last_clear_history_date = 0; d->last_clear_history_message_id = MessageId(); DialogId default_join_group_call_as_dialog_id = d->default_join_group_call_as_dialog_id; if (default_join_group_call_as_dialog_id != dialog_id && default_join_group_call_as_dialog_id.get_type() != DialogType::User && !have_dialog(default_join_group_call_as_dialog_id)) { d->default_join_group_call_as_dialog_id = DialogId(); } DialogId default_send_message_as_dialog_id = d->default_send_message_as_dialog_id; bool need_drop_default_send_message_as_dialog_id = d->need_drop_default_send_message_as_dialog_id; if (default_send_message_as_dialog_id != dialog_id && default_send_message_as_dialog_id.get_type() != DialogType::User && !have_dialog(default_send_message_as_dialog_id)) { d->need_drop_default_send_message_as_dialog_id = false; d->default_send_message_as_dialog_id = DialogId(); } if (d->notification_info != nullptr) { if (d->notification_info->message_notification_group_.is_valid()) { notification_group_id_to_dialog_id_.emplace(d->notification_info->message_notification_group_.get_group_id(), dialog_id); } if (d->notification_info->mention_notification_group_.is_valid()) { notification_group_id_to_dialog_id_.emplace(d->notification_info->mention_notification_group_.get_group_id(), dialog_id); } } if (pending_dialog_group_call_updates_.count(dialog_id) > 0) { auto it = pending_dialog_group_call_updates_.find(dialog_id); bool has_active_group_call = it->second.first; bool is_group_call_empty = it->second.second; pending_dialog_group_call_updates_.erase(it); if (d->has_active_group_call != has_active_group_call || d->is_group_call_empty != is_group_call_empty) { if (!has_active_group_call) { d->active_group_call_id = InputGroupCallId(); } d->has_active_group_call = has_active_group_call; d->is_group_call_empty = is_group_call_empty; on_dialog_updated(dialog_id, "pending update_dialog_group_call"); } } fix_pending_join_requests(dialog_id, d->pending_join_request_count, d->pending_join_request_user_ids); if (!is_loaded_from_database) { CHECK(order == DEFAULT_ORDER); CHECK(draft_message == nullptr); CHECK(last_database_message == nullptr); } CHECK(!have_dialog(dialog_id)); dialogs_.set(dialog_id, std::move(dialog)); CHECK(!being_added_new_dialog_id_.is_valid()); being_added_new_dialog_id_ = dialog_id; loaded_dialogs_.erase(dialog_id); failed_to_load_dialogs_.erase(dialog_id); fix_dialog_action_bar(d, d->action_bar.get()); send_update_new_chat(d); being_added_new_dialog_id_ = DialogId(); CHECK(d->messages.empty()); fix_new_dialog(d, std::move(draft_message), std::move(last_database_message), last_database_message_id, order, last_clear_history_date, last_clear_history_message_id, default_join_group_call_as_dialog_id, default_send_message_as_dialog_id, need_drop_default_send_message_as_dialog_id, is_loaded_from_database, source); return d; } void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&draft_message, unique_ptr &&last_database_message, MessageId last_database_message_id, int64 order, int32 last_clear_history_date, MessageId last_clear_history_message_id, DialogId default_join_group_call_as_dialog_id, DialogId default_send_message_as_dialog_id, bool need_drop_default_send_message_as_dialog_id, bool is_loaded_from_database, const char *source) { CHECK(d != nullptr); auto dialog_id = d->dialog_id; auto dialog_type = dialog_id.get_type(); if (!td_->auth_manager_->is_bot() && dialog_type == DialogType::SecretChat) { auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (user_id.is_valid()) { force_create_dialog(DialogId(user_id), "add chat with user to load/store action_bar and is_blocked"); Dialog *user_d = get_dialog_force(DialogId(user_id), "fix_new_dialog"); if (user_d != nullptr && (d->is_blocked != user_d->is_blocked || d->is_blocked_for_stories != user_d->is_blocked_for_stories)) { set_dialog_is_blocked(d, user_d->is_blocked, user_d->is_blocked_for_stories); } } } if (d->is_empty && !d->have_full_history) { LOG(ERROR) << "Drop invalid flag is_empty"; d->is_empty = false; } if (being_added_dialog_id_ != dialog_id && !td_->auth_manager_->is_bot() && !is_dialog_inited(d) && dialog_type != DialogType::SecretChat && have_input_peer(dialog_id, AccessRights::Read)) { // asynchronously get dialog from the server send_get_dialog_query(dialog_id, Auto(), 0, "fix_new_dialog 20"); } if (being_added_dialog_id_ != dialog_id && !d->is_is_blocked_for_stories_inited && !td_->auth_manager_->is_bot()) { // asynchronously get is_blocked_for_stories from the server reload_dialog_info_full(dialog_id, "fix_new_dialog init is_blocked_for_stories"); } else if (being_added_dialog_id_ != dialog_id && !d->is_has_bots_inited && !td_->auth_manager_->is_bot()) { // asynchronously get has_bots from the server reload_dialog_info_full(dialog_id, "fix_new_dialog init has_bots"); } else if (being_added_dialog_id_ != dialog_id && !d->is_background_inited && !td_->auth_manager_->is_bot()) { // asynchronously get dialog background from the server reload_dialog_info_full(dialog_id, "fix_new_dialog init background"); } else if (being_added_dialog_id_ != dialog_id && !d->is_theme_name_inited && !td_->auth_manager_->is_bot()) { // asynchronously get dialog theme identifier from the server reload_dialog_info_full(dialog_id, "fix_new_dialog init theme_name"); } else if (being_added_dialog_id_ != dialog_id && !d->is_last_pinned_message_id_inited && !td_->auth_manager_->is_bot()) { // asynchronously get dialog pinned message from the server get_dialog_pinned_message(dialog_id, Auto()); } else if (being_added_dialog_id_ != dialog_id && !d->is_folder_id_inited && !td_->auth_manager_->is_bot() && order != DEFAULT_ORDER) { // asynchronously get dialog folder identifier from the server reload_dialog_info_full(dialog_id, "fix_new_dialog init folder_id"); } else if (!d->is_message_ttl_inited && !td_->auth_manager_->is_bot() && have_input_peer(dialog_id, AccessRights::Write)) { // asynchronously get dialog message auto-delete timer from the server reload_dialog_info_full(dialog_id, "fix_new_dialog init message_auto_delete_time"); } else if (being_added_dialog_id_ != dialog_id && !d->is_available_reactions_inited && !td_->auth_manager_->is_bot()) { // asynchronously get dialog available reactions from the server reload_dialog_info_full(dialog_id, "fix_new_dialog init available_reactions"); } else if (being_added_dialog_id_ != dialog_id && !d->is_view_as_messages_inited && is_forum_channel(dialog_id) && !td_->auth_manager_->is_bot()) { // asynchronously get view_as_messages from the server reload_dialog_info_full(dialog_id, "fix_new_dialog init view_as_messages"); } if ((!d->know_action_bar || d->need_repair_action_bar) && !td_->auth_manager_->is_bot() && dialog_type != DialogType::SecretChat && dialog_id != get_my_dialog_id() && have_input_peer(dialog_id, AccessRights::Read)) { // asynchronously get action bar from the server reget_dialog_action_bar(dialog_id, "fix_new_dialog", false); } if (d->has_active_group_call && !d->active_group_call_id.is_valid() && !td_->auth_manager_->is_bot()) { repair_dialog_active_group_call_id(dialog_id); } set_dialog_is_forum(d, is_forum_channel(dialog_id)); if (d->notification_settings.is_synchronized && !d->notification_settings.is_use_default_fixed && have_input_peer(dialog_id, AccessRights::Read) && !td_->auth_manager_->is_bot()) { LOG(INFO) << "Reget notification settings of " << dialog_id; if (dialog_type == DialogType::SecretChat) { d->notification_settings.is_use_default_fixed = true; on_dialog_updated(dialog_id, "reget notification settings"); } else { td_->notification_settings_manager_->send_get_dialog_notification_settings_query(dialog_id, MessageId(), Promise()); } } if (td_->auth_manager_->is_bot() || d->notification_settings.use_default_mute_until || d->notification_settings.mute_until <= G()->unix_time()) { d->notification_settings.mute_until = 0; } else { schedule_dialog_unmute(dialog_id, false, d->notification_settings.mute_until, G()->unix_time()); } if (d->notification_info != nullptr && d->notification_info->pinned_message_notification_message_id_.is_valid()) { auto pinned_message_id = d->notification_info->pinned_message_notification_message_id_; if (!d->notification_info->mention_notification_group_.is_valid()) { LOG(ERROR) << "Have pinned message notification in " << pinned_message_id << " in " << dialog_id << ", but there is no mention notification group"; d->notification_info->pinned_message_notification_message_id_ = MessageId(); on_dialog_updated(dialog_id, "fix pinned message notification"); } else if (is_dialog_pinned_message_notifications_disabled(d) || pinned_message_id <= d->last_read_inbox_message_id || d->notification_info->mention_notification_group_.is_removed_object_id(pinned_message_id)) { VLOG(notifications) << "Remove disabled pinned message notification in " << pinned_message_id << " in " << dialog_id; send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_object_id, d->notification_info->mention_notification_group_.get_group_id(), pinned_message_id, true, "fix pinned message notification"); d->notification_info->pinned_message_notification_message_id_ = MessageId(); on_dialog_updated(dialog_id, "fix pinned message notification 2"); } } { auto it = pending_add_dialog_dependent_dialogs_.find(dialog_id); if (it != pending_add_dialog_dependent_dialogs_.end()) { auto pending_dialog_ids = std::move(it->second); pending_add_dialog_dependent_dialogs_.erase(it); for (auto &pending_dialog_id : pending_dialog_ids) { auto &add_dialog_data = pending_add_dialog_data_[pending_dialog_id]; CHECK(add_dialog_data.dependent_dialog_count_ > 0); add_dialog_data.dependent_dialog_count_--; if (add_dialog_data.dependent_dialog_count_ == 0) { LOG(INFO) << "Add postponed last database message in " << pending_dialog_id; add_pending_dialog_data(get_dialog(pending_dialog_id), std::move(add_dialog_data.last_message_), std::move(add_dialog_data.draft_message_)); pending_add_dialog_data_.erase(pending_dialog_id); } } } } { auto it = pending_add_default_join_group_call_as_dialog_id_.find(dialog_id); if (it != pending_add_default_join_group_call_as_dialog_id_.end()) { auto pending_dialog_ids = std::move(it->second); pending_add_default_join_group_call_as_dialog_id_.erase(it); for (auto &pending_dialog_id : pending_dialog_ids) { on_update_dialog_default_join_group_call_as_dialog_id(pending_dialog_id, dialog_id, false); } } } { auto it = pending_add_default_send_message_as_dialog_id_.find(dialog_id); if (it != pending_add_default_send_message_as_dialog_id_.end()) { auto pending_dialog_ids = std::move(it->second); pending_add_default_send_message_as_dialog_id_.erase(it); for (auto &pending_dialog_id : pending_dialog_ids) { Dialog *pending_d = get_dialog(pending_dialog_id.first); CHECK(pending_d != nullptr); if (!pending_d->default_send_message_as_dialog_id.is_valid()) { LOG(INFO) << "Set postponed message sender in " << pending_dialog_id << " to " << dialog_id; pending_d->need_drop_default_send_message_as_dialog_id = pending_dialog_id.second; pending_d->default_send_message_as_dialog_id = dialog_id; send_update_chat_message_sender(pending_d); } } } } if (dialog_id != being_added_dialog_id_) { set_dialog_last_clear_history_date(d, last_clear_history_date, last_clear_history_message_id, "fix_new_dialog 8", is_loaded_from_database); set_dialog_order(d, order, false, is_loaded_from_database, "fix_new_dialog 9"); } if (dialog_type != DialogType::SecretChat && d->last_new_message_id.is_valid() && !d->last_new_message_id.is_server()) { // fix wrong last_new_message_id d->last_new_message_id = d->last_new_message_id.get_prev_server_message_id(); on_dialog_updated(dialog_id, "fix_new_dialog 10"); } bool need_get_history = true; // add last database message to dialog MessageId last_message_id; if (last_database_message != nullptr) { need_get_history = false; last_message_id = last_database_message->message_id; } else if (last_database_message_id.is_valid()) { last_message_id = last_database_message_id; } if (!d->last_new_message_id.is_valid() && d->last_new_message_id != MessageId()) { LOG(ERROR) << "Drop invalid last_new_message_id " << d->last_new_message_id << " in " << dialog_id; d->last_new_message_id = MessageId(); } if (last_message_id.is_valid()) { if ((last_message_id.is_server() || dialog_type == DialogType::SecretChat) && !last_message_id.is_yet_unsent() && !d->last_new_message_id.is_valid()) { LOG(ERROR) << "Bugfixing wrong last_new_message_id to " << last_message_id << " in " << dialog_id; // must be called before set_dialog_first_database_message_id and set_dialog_last_database_message_id set_dialog_last_new_message_id(d, last_message_id, "fix_new_dialog 1"); } if (!d->first_database_message_id.is_valid() || d->first_database_message_id > last_message_id) { LOG(ERROR) << "Bugfixing wrong first_database_message_id from " << d->first_database_message_id << " to " << last_message_id << " in " << dialog_id; set_dialog_first_database_message_id(d, last_message_id, "fix_new_dialog 2"); } set_dialog_last_database_message_id(d, last_message_id, "fix_new_dialog 3", is_loaded_from_database); } else if (d->first_database_message_id.is_valid()) { // ensure that first_database_message_id <= last_database_message_id if (d->first_database_message_id <= d->last_new_message_id) { set_dialog_last_database_message_id(d, d->last_new_message_id, "fix_new_dialog 4"); } else { // can't fix last_database_message_id, drop first_database_message_id; it shouldn't happen anyway set_dialog_first_database_message_id(d, MessageId(), "fix_new_dialog 5"); } } d->debug_first_database_message_id = d->first_database_message_id; d->debug_last_database_message_id = d->last_database_message_id; d->debug_last_new_message_id = d->last_new_message_id; if (last_database_message != nullptr || draft_message != nullptr) { Dependencies dependencies; if (last_database_message != nullptr) { add_message_dependencies(dependencies, last_database_message.get()); } if (draft_message != nullptr) { draft_message->add_dependencies(dependencies); } int32 dependent_dialog_count = 0; for (const auto &other_dialog_id : dependencies.get_dialog_ids()) { if (other_dialog_id.is_valid() && !have_dialog(other_dialog_id)) { LOG(INFO) << "Postpone adding of data in " << dialog_id << " because of cyclic dependency with " << other_dialog_id; pending_add_dialog_dependent_dialogs_[other_dialog_id].push_back(dialog_id); dependent_dialog_count++; } }; auto pending_order = DEFAULT_ORDER; if (last_database_message != nullptr) { auto last_message_order = get_dialog_order(last_message_id, last_database_message->date); if (last_message_order > pending_order) { pending_order = last_message_order; } } if (draft_message != nullptr && !need_hide_dialog_draft_message(d)) { int64 draft_order = get_dialog_order(MessageId(), draft_message->get_date()); if (draft_order > pending_order) { pending_order = draft_order; } } if (dependent_dialog_count == 0) { if (!add_pending_dialog_data(d, std::move(last_database_message), std::move(draft_message))) { // failed to add last message; keep the current position and get history from the database d->pending_order = pending_order; } } else { // can't add data immediately, because need to notify first about adding of dependent dialogs d->pending_order = pending_order; pending_add_dialog_data_[dialog_id] = {dependent_dialog_count, std::move(last_database_message), std::move(draft_message)}; } } else if (last_database_message_id.is_valid()) { auto date = DialogDate(order, dialog_id).get_date(); if (date < MIN_PINNED_DIALOG_DATE) { d->pending_order = get_dialog_order(last_message_id, date); } } if (default_join_group_call_as_dialog_id != d->default_join_group_call_as_dialog_id) { CHECK(default_join_group_call_as_dialog_id.is_valid()); CHECK(default_join_group_call_as_dialog_id.get_type() != DialogType::User); CHECK(!d->default_join_group_call_as_dialog_id.is_valid()); if (!have_dialog(default_join_group_call_as_dialog_id)) { LOG(INFO) << "Postpone adding of default join voice chat as " << default_join_group_call_as_dialog_id << " in " << dialog_id; pending_add_default_join_group_call_as_dialog_id_[default_join_group_call_as_dialog_id].push_back(dialog_id); } else { on_update_dialog_default_join_group_call_as_dialog_id(dialog_id, default_join_group_call_as_dialog_id, false); } } if (default_send_message_as_dialog_id != d->default_send_message_as_dialog_id) { CHECK(default_send_message_as_dialog_id.is_valid()); CHECK(default_send_message_as_dialog_id.get_type() != DialogType::User); CHECK(!d->default_send_message_as_dialog_id.is_valid()); if (!have_dialog(default_send_message_as_dialog_id)) { LOG(INFO) << "Postpone setting of message sender " << default_send_message_as_dialog_id << " in " << dialog_id; pending_add_default_send_message_as_dialog_id_[default_send_message_as_dialog_id].emplace_back( dialog_id, need_drop_default_send_message_as_dialog_id); } else { LOG(INFO) << "Set message sender in " << dialog_id << " to " << default_send_message_as_dialog_id; d->need_drop_default_send_message_as_dialog_id = need_drop_default_send_message_as_dialog_id; d->default_send_message_as_dialog_id = default_send_message_as_dialog_id; send_update_chat_message_sender(d); } } switch (dialog_type) { case DialogType::User: break; case DialogType::Chat: if (d->last_read_inbox_message_id < d->last_read_outbox_message_id) { LOG(INFO) << "Last read outbox message is " << d->last_read_outbox_message_id << " in " << dialog_id << ", but last read inbox message is " << d->last_read_inbox_message_id; // can't fix last_read_inbox_message_id by last_read_outbox_message_id because last_read_outbox_message_id is // just a message identifier not less than an identifier of last read outgoing message and less than // an identifier of first unread outgoing message, so it may not point to the outgoing message // read_history_inbox(d, d->last_read_outbox_message_id, d->server_unread_count, "fix_new_dialog 6"); } break; case DialogType::Channel: break; case DialogType::SecretChat: break; case DialogType::None: default: UNREACHABLE(); } if (d->delete_last_message_date != 0) { if (d->last_message_id.is_valid()) { LOG(ERROR) << "Last " << d->deleted_last_message_id << " in " << dialog_id << " was deleted at " << d->delete_last_message_date << ", but have last " << d->last_message_id; d->delete_last_message_date = 0; d->deleted_last_message_id = MessageId(); d->is_last_message_deleted_locally = false; on_dialog_updated(dialog_id, "update_delete_last_message_date"); } else { need_get_history = true; } } if (!G()->use_message_database()) { d->has_loaded_scheduled_messages_from_database = true; } if (dialog_id != being_added_dialog_id_) { update_dialog_pos(d, "fix_new_dialog 7", true, is_loaded_from_database); } if (is_loaded_from_database && d->order != order && order < MAX_ORDINARY_DIALOG_ORDER && !td_->contacts_manager_->is_dialog_info_received_from_server(dialog_id) && !d->had_last_yet_unsent_message && !td_->auth_manager_->is_bot()) { LOG(ERROR) << dialog_id << " has order " << d->order << " instead of saved to database order " << order; } LOG(INFO) << "Loaded " << dialog_id << " with last new " << d->last_new_message_id << ", first database " << d->first_database_message_id << ", last database " << d->last_database_message_id << ", last " << d->last_message_id << " with order " << d->order; if (d->notification_info != nullptr) { VLOG(notifications) << "Have " << dialog_id << " with message " << d->notification_info->message_notification_group_ << " and new secret chat " << d->notification_info->new_secret_chat_notification_id_; VLOG(notifications) << "Have " << dialog_id << " with mention " << d->notification_info->mention_notification_group_ << " and pinned " << d->notification_info->pinned_message_notification_message_id_; VLOG(notifications) << "In " << dialog_id << " have last_read_inbox_message_id = " << d->last_read_inbox_message_id << ", last_new_message_id = " << d->last_new_message_id << ", max_push_notification_message_id = " << d->notification_info->max_push_notification_message_id_; } LOG_CHECK(d->messages.calc_size() <= 1) << d->messages.calc_size() << ' ' << last_message_id << ' ' << d->last_message_id << ' ' << d->last_database_message_id << ' ' << d->debug_set_dialog_last_database_message_id << ' ' << is_loaded_from_database << ' ' << source << ' ' << being_added_dialog_id_ << ' ' << being_added_new_dialog_id_ << ' ' << dialog_id << ' ' << d->is_channel_difference_finished << ' ' << debug_last_get_channel_difference_dialog_id_ << ' ' << debug_last_get_channel_difference_source_ << ' ' << G()->use_message_database(); // must be after update_dialog_pos, because uses d->order // must be after checks that dialog has at most one message, because read_history_inbox can load // pinned message to remove notification about it if (d->pending_read_channel_inbox_pts != 0 && !td_->auth_manager_->is_bot() && have_input_peer(dialog_id, AccessRights::Read) && need_unread_counter(d->order)) { if (d->pts == d->pending_read_channel_inbox_pts) { d->pending_read_channel_inbox_pts = 0; read_history_inbox(d, d->pending_read_channel_inbox_max_message_id, d->pending_read_channel_inbox_server_unread_count, "fix_new_dialog 12"); on_dialog_updated(dialog_id, "fix_new_dialog 13"); } else if (d->pts > d->pending_read_channel_inbox_pts) { d->need_repair_channel_server_unread_count = true; d->pending_read_channel_inbox_pts = 0; on_dialog_updated(dialog_id, "fix_new_dialog 14"); } else { schedule_get_channel_difference(dialog_id, d->pending_read_channel_inbox_pts, MessageId(), 0.001, source); } } else { d->pending_read_channel_inbox_pts = 0; } if (need_get_history && !td_->auth_manager_->is_bot() && dialog_id != being_added_dialog_id_ && dialog_id != being_added_by_new_message_dialog_id_ && (d->order != DEFAULT_ORDER || is_dialog_sponsored(d))) { load_last_dialog_message(d, "fix_new_dialog"); } if (d->need_repair_server_unread_count && need_unread_counter(d->order)) { CHECK(dialog_type != DialogType::SecretChat); repair_server_unread_count(dialog_id, d->server_unread_count, "fix_new_dialog"); } if (dialog_type == DialogType::Channel && need_unread_counter(d->order) && d->server_unread_count > 0 && !td_->auth_manager_->is_bot() && td_->option_manager_->get_option_integer("since_last_open") >= 2 * 86400) { d->need_repair_channel_server_unread_count = true; } if (d->need_repair_channel_server_unread_count) { repair_channel_server_unread_count(d); } if (d->need_repair_unread_reaction_count) { repair_dialog_unread_reaction_count(d, Promise(), "fix_new_dialog"); } if (d->need_repair_unread_mention_count) { repair_dialog_unread_mention_count(d, "fix_new_dialog"); } } bool MessagesManager::add_pending_dialog_data(Dialog *d, unique_ptr &&last_database_message, unique_ptr &&draft_message) { CHECK(d != nullptr); CHECK(last_database_message != nullptr || draft_message != nullptr); CHECK(!td_->auth_manager_->is_bot()); bool need_update_dialog_pos = false; bool was_added_last_message = false; if (last_database_message != nullptr) { auto dialog_id = d->dialog_id; auto message_id = last_database_message->message_id; CHECK(message_id.is_valid()); LOG_CHECK(d->last_database_message_id == message_id) << message_id << " " << d->last_database_message_id << " " << d->debug_set_dialog_last_database_message_id; const Message *m = nullptr; if (have_input_peer(dialog_id, AccessRights::Read)) { bool need_update = false; m = add_message_to_dialog(d, std::move(last_database_message), true, false, &need_update, &need_update_dialog_pos, "add_pending_dialog_data 1"); if (need_update_dialog_pos) { LOG(ERROR) << "Need to update pos in " << dialog_id; } } if (m != nullptr) { set_dialog_last_message_id(d, m->message_id, "add_pending_dialog_data 2", m); send_update_chat_last_message(d, "add_pending_dialog_data 3"); was_added_last_message = true; } else { on_dialog_updated(dialog_id, "add_pending_dialog_data 4"); // resave without last database message if (!td_->auth_manager_->is_bot() && dialog_id != being_added_dialog_id_ && dialog_id != being_added_by_new_message_dialog_id_ && (d->order != DEFAULT_ORDER || is_dialog_sponsored(d))) { load_last_dialog_message(d, "add_pending_dialog_data 5"); } } } if (draft_message != nullptr) { d->draft_message = std::move(draft_message); need_update_dialog_pos = true; send_update_chat_draft_message(d); } if (d->pending_order != DEFAULT_ORDER) { d->pending_order = DEFAULT_ORDER; need_update_dialog_pos = true; } if (need_update_dialog_pos) { update_dialog_pos(d, "add_pending_dialog_data 6"); } return was_added_last_message; } void MessagesManager::update_dialogs_hints(const Dialog *d) { if (!td_->auth_manager_->is_bot() && d->order != DEFAULT_ORDER) { dialogs_hints_.add(-d->dialog_id.get(), td_->contacts_manager_->get_dialog_search_text(d->dialog_id)); } } void MessagesManager::update_dialogs_hints_rating(const Dialog *d) { if (td_->auth_manager_->is_bot()) { return; } if (d->order == DEFAULT_ORDER) { LOG(INFO) << "Remove " << d->dialog_id << " from chats search"; dialogs_hints_.remove(-d->dialog_id.get()); } else { LOG(INFO) << "Change position of " << d->dialog_id << " in chats search"; dialogs_hints_.set_rating(-d->dialog_id.get(), -get_dialog_base_order(d)); } } int64 MessagesManager::get_dialog_order(MessageId message_id, int32 message_date) { CHECK(!message_id.is_scheduled()); return (static_cast(message_date) << 32) + message_id.get_prev_server_message_id().get_server_message_id().get(); } bool MessagesManager::is_dialog_sponsored(const Dialog *d) const { return d->order == DEFAULT_ORDER && d->dialog_id == sponsored_dialog_id_; } int64 MessagesManager::get_dialog_base_order(const Dialog *d) const { if (td_->auth_manager_->is_bot()) { return 0; // to not call get_dialog_list } if (is_dialog_sponsored(d)) { return SPONSORED_DIALOG_ORDER; } if (d->order == DEFAULT_ORDER) { return 0; } auto pinned_order = get_dialog_pinned_order(DialogListId(FolderId::main()), d->dialog_id); if (pinned_order != DEFAULT_ORDER) { return pinned_order; } return d->order; } int64 MessagesManager::get_dialog_private_order(const DialogList *list, const Dialog *d) const { if (list == nullptr || td_->auth_manager_->is_bot()) { return 0; } if (is_dialog_sponsored(d) && list->dialog_list_id == DialogListId(FolderId::main())) { return SPONSORED_DIALOG_ORDER; } if (d->order == DEFAULT_ORDER) { return 0; } auto pinned_order = get_dialog_pinned_order(list, d->dialog_id); if (pinned_order != DEFAULT_ORDER) { return pinned_order; } return d->order; } td_api::object_ptr MessagesManager::get_chat_position_object(DialogListId dialog_list_id, const Dialog *d) const { if (td_->auth_manager_->is_bot()) { return nullptr; } auto *list = get_dialog_list(dialog_list_id); if (list == nullptr) { return nullptr; } auto position = get_dialog_position_in_list(list, d); if (position.public_order == 0) { return nullptr; } auto chat_source = position.is_sponsored ? sponsored_dialog_source_.get_chat_source_object() : nullptr; return td_api::make_object(dialog_list_id.get_chat_list_object(), position.public_order, position.is_pinned, std::move(chat_source)); } vector> MessagesManager::get_chat_positions_object(const Dialog *d) const { vector> positions; if (!td_->auth_manager_->is_bot()) { for (auto dialog_list_id : get_dialog_list_ids(d)) { auto position = get_chat_position_object(dialog_list_id, d); if (position != nullptr) { positions.push_back(std::move(position)); } } if (is_dialog_sponsored(d)) { CHECK(positions.empty()); positions.push_back(get_chat_position_object(DialogListId(FolderId::main()), d)); } } return positions; } int64 MessagesManager::get_next_pinned_dialog_order() { current_pinned_dialog_order_++; LOG(INFO) << "Assign pinned_order = " << current_pinned_dialog_order_; return current_pinned_dialog_order_; } bool MessagesManager::is_removed_from_dialog_list(const Dialog *d) const { switch (d->dialog_id.get_type()) { case DialogType::User: break; case DialogType::Chat: return !td_->contacts_manager_->get_chat_is_active(d->dialog_id.get_chat_id()); case DialogType::Channel: return !td_->contacts_manager_->get_channel_status(d->dialog_id.get_channel_id()).is_member(); case DialogType::SecretChat: break; case DialogType::None: default: UNREACHABLE(); break; } return false; } void MessagesManager::update_dialog_pos(Dialog *d, const char *source, bool need_send_update, bool is_loaded_from_database) { if (td_->auth_manager_->is_bot()) { return; } CHECK(d != nullptr); LOG(INFO) << "Trying to update " << d->dialog_id << " order from " << source; int64 new_order = DEFAULT_ORDER; if (!is_removed_from_dialog_list(d)) { if (d->last_message_id != MessageId()) { auto m = get_message(d, d->last_message_id); CHECK(m != nullptr); LOG(INFO) << "Last message at " << m->date << " found"; int64 last_message_order = get_dialog_order(m->message_id, m->date); if (last_message_order > new_order) { new_order = last_message_order; } } else if (d->delete_last_message_date > 0) { LOG(INFO) << "Deleted last " << d->deleted_last_message_id << " at " << d->delete_last_message_date << " found"; int64 delete_order = get_dialog_order(d->deleted_last_message_id, d->delete_last_message_date); if (delete_order > new_order) { new_order = delete_order; } } else if (d->last_clear_history_date > 0) { LOG(INFO) << "Clear history at " << d->last_clear_history_date << " found"; int64 clear_order = get_dialog_order(d->last_clear_history_message_id, d->last_clear_history_date); if (clear_order > new_order) { new_order = clear_order; } } if (d->pending_order != DEFAULT_ORDER) { LOG(INFO) << "Pending order " << d->pending_order << " found"; if (d->pending_order > new_order) { new_order = d->pending_order; } } if (d->draft_message != nullptr && !need_hide_dialog_draft_message(d)) { auto draft_message_date = d->draft_message->get_date(); LOG(INFO) << "Draft message at " << draft_message_date << " found"; int64 draft_order = get_dialog_order(MessageId(), draft_message_date); if (draft_order > new_order) { new_order = draft_order; } } switch (d->dialog_id.get_type()) { case DialogType::Chat: { auto chat_id = d->dialog_id.get_chat_id(); auto date = td_->contacts_manager_->get_chat_date(chat_id); LOG(INFO) << "Creation at " << date << " found"; int64 join_order = get_dialog_order(MessageId(), date); if (join_order > new_order && td_->contacts_manager_->get_chat_status(chat_id).is_member()) { new_order = join_order; } break; } case DialogType::Channel: { auto date = td_->contacts_manager_->get_channel_date(d->dialog_id.get_channel_id()); LOG(INFO) << "Join at " << date << " found"; int64 join_order = get_dialog_order(MessageId(), date); if (join_order > new_order) { new_order = join_order; } break; } case DialogType::SecretChat: { auto date = td_->contacts_manager_->get_secret_chat_date(d->dialog_id.get_secret_chat_id()); if (date != 0 && !is_deleted_secret_chat(d)) { LOG(INFO) << "Creation at " << date << " found"; int64 creation_order = get_dialog_order(MessageId(), date); if (creation_order > new_order) { new_order = creation_order; } } break; } default: break; } if (new_order == DEFAULT_ORDER && !d->is_empty) { LOG(INFO) << "There are no known messages in the chat, just leave it where it is"; new_order = d->order; load_last_dialog_message(d, source); } } if (set_dialog_order(d, new_order, need_send_update, is_loaded_from_database, source)) { on_dialog_updated(d->dialog_id, "update_dialog_pos"); } } bool MessagesManager::set_dialog_order(Dialog *d, int64 new_order, bool need_send_update, bool is_loaded_from_database, const char *source) { if (td_->auth_manager_->is_bot()) { return false; } CHECK(d != nullptr); DialogId dialog_id = d->dialog_id; DialogDate old_date(d->order, dialog_id); DialogDate new_date(new_order, dialog_id); if (old_date == new_date) { LOG(INFO) << "Order of " << d->dialog_id << " from " << d->folder_id << " is still " << new_order << " from " << source; } else { LOG(INFO) << "Update order of " << dialog_id << " from " << d->folder_id << " from " << d->order << " to " << new_order << " from " << source; } auto folder_ptr = get_dialog_folder(d->folder_id); LOG_CHECK(folder_ptr != nullptr) << is_inited_ << ' ' << G()->close_flag() << ' ' << dialog_id << ' ' << d->folder_id << ' ' << is_loaded_from_database << ' ' << td_->auth_manager_->is_authorized() << ' ' << td_->auth_manager_->was_authorized() << ' ' << source; auto &folder = *folder_ptr; if (old_date == new_date) { if (new_order == DEFAULT_ORDER) { // first addition of a new left dialog if (folder.ordered_dialogs_.insert(new_date).second) { for (const auto &dialog_list : dialog_lists_) { if (get_dialog_pinned_order(&dialog_list.second, d->dialog_id) != DEFAULT_ORDER) { set_dialog_is_pinned(dialog_list.first, d, false); } } } } return false; } auto dialog_positions = get_dialog_positions(d); if (folder.ordered_dialogs_.erase(old_date) == 0) { LOG_IF(ERROR, d->order != DEFAULT_ORDER) << dialog_id << " not found in the chat list from " << source; } folder.ordered_dialogs_.insert(new_date); bool is_added = (d->order == DEFAULT_ORDER); bool is_removed = (new_order == DEFAULT_ORDER); d->order = new_order; if (is_added) { update_dialogs_hints(d); } update_dialogs_hints_rating(d); update_dialog_lists(d, std::move(dialog_positions), need_send_update, is_loaded_from_database, source); if (!is_loaded_from_database) { auto dialog_type = dialog_id.get_type(); if (dialog_type == DialogType::Channel && is_added && being_added_dialog_id_ != dialog_id) { repair_channel_server_unread_count(d); schedule_get_channel_difference(dialog_id, 0, MessageId(), 0.001, source); } if (dialog_type == DialogType::Channel && is_removed) { remove_all_dialog_notifications(d, false, source); remove_all_dialog_notifications(d, true, source); clear_active_dialog_actions(dialog_id); } } return true; } void MessagesManager::update_dialog_lists( Dialog *d, std::unordered_map &&old_positions, bool need_send_update, bool is_loaded_from_database, const char *source) { if (td_->auth_manager_->is_bot()) { return; } CHECK(d != nullptr); auto dialog_id = d->dialog_id; if (being_added_dialog_id_ == dialog_id) { // do not try to update dialog lists, while the dialog isn't inited return; } LOG(INFO) << "Update lists of " << dialog_id << " from " << source; if (d->order == DEFAULT_ORDER) { for (auto &old_position : old_positions) { if (old_position.second.is_pinned) { set_dialog_is_pinned(old_position.first, d, false, false); } } if (d->folder_id != FolderId::main()) { LOG(INFO) << "Change folder of " << dialog_id << " to " << FolderId::main(); DialogDate dialog_date(d->order, dialog_id); get_dialog_folder(d->folder_id)->ordered_dialogs_.erase(dialog_date); do_set_dialog_folder_id(d, FolderId::main()); get_dialog_folder(d->folder_id)->ordered_dialogs_.insert(dialog_date); } } for (auto &dialog_list : dialog_lists_) { auto dialog_list_id = dialog_list.first; auto &list = dialog_list.second; const DialogPositionInList &old_position = old_positions[dialog_list_id]; const DialogPositionInList new_position = get_dialog_position_in_list(&list, d, true); // sponsored chat is never "in list" bool was_in_list = old_position.order != DEFAULT_ORDER && old_position.private_order != 0; bool is_in_list = new_position.order != DEFAULT_ORDER && new_position.private_order != 0; CHECK(was_in_list == is_dialog_in_list(d, dialog_list_id)); LOG(DEBUG) << "Update position of " << dialog_id << " in " << dialog_list_id << " from " << old_position << " to " << new_position; bool need_update_unread_chat_count = false; if (was_in_list != is_in_list) { const int32 delta = was_in_list ? -1 : 1; list.in_memory_dialog_total_count_ += delta; if (!is_loaded_from_database) { int32 &total_count = dialog_id.get_type() == DialogType::SecretChat ? list.secret_chat_total_count_ : list.server_dialog_total_count_; if (total_count != -1) { total_count += delta; if (total_count < 0) { LOG(ERROR) << "Total chat count in " << dialog_list_id << " became negative after removing " << dialog_id; total_count = 0; } } } if (!is_loaded_from_database) { need_update_unread_chat_count = list.is_dialog_unread_count_inited_ && old_position.total_dialog_count != get_dialog_total_count(list); auto unread_count = d->server_unread_count + d->local_unread_count; const char *change_source = was_in_list ? "on_dialog_remove" : "on_dialog_add"; if (unread_count != 0 && list.is_message_unread_count_inited_) { unread_count *= delta; list.unread_message_total_count_ += unread_count; if (is_dialog_muted(d)) { list.unread_message_muted_count_ += unread_count; } send_update_unread_message_count(list, dialog_id, true, change_source); } if ((unread_count != 0 || d->is_marked_as_unread) && list.is_dialog_unread_count_inited_) { list.unread_dialog_total_count_ += delta; if (unread_count == 0 && d->is_marked_as_unread) { list.unread_dialog_marked_count_ += delta; } if (is_dialog_muted(d)) { list.unread_dialog_muted_count_ += delta; if (unread_count == 0 && d->is_marked_as_unread) { list.unread_dialog_muted_marked_count_ += delta; } } need_update_unread_chat_count = true; } if (need_update_unread_chat_count) { send_update_unread_chat_count(list, dialog_id, true, change_source); } } if (was_in_list) { remove_dialog_from_list(d, dialog_list_id); } else { add_dialog_to_list(d, dialog_list_id); } } if (!need_update_unread_chat_count && list.is_dialog_unread_count_inited_ && old_position.total_dialog_count != get_dialog_total_count(list)) { send_update_unread_chat_count(list, dialog_id, true, "changed total count"); } if (need_send_update && need_send_update_chat_position(old_position, new_position)) { send_update_chat_position(dialog_list_id, d, source); } if (!is_loaded_from_database && !old_position.is_sponsored && new_position.is_sponsored) { // a chat is sponsored only if user isn't a chat member remove_all_dialog_notifications(d, false, "update_dialog_lists 3"); remove_all_dialog_notifications(d, true, "update_dialog_lists 4"); } } } void MessagesManager::update_last_dialog_date(FolderId folder_id) { CHECK(!td_->auth_manager_->is_bot()); auto *folder = get_dialog_folder(folder_id); CHECK(folder != nullptr); auto old_last_dialog_date = folder->folder_last_dialog_date_; folder->folder_last_dialog_date_ = folder->last_server_dialog_date_; CHECK(old_last_dialog_date <= folder->folder_last_dialog_date_); LOG(INFO) << "Update last dialog date in " << folder_id << " from " << old_last_dialog_date << " to " << folder->folder_last_dialog_date_; LOG(INFO) << "Know about " << folder->ordered_dialogs_.size() << " chats"; if (old_last_dialog_date != folder->folder_last_dialog_date_) { for (auto &dialog_list : dialog_lists_) { update_list_last_pinned_dialog_date(dialog_list.second); update_list_last_dialog_date(dialog_list.second); } } if (G()->use_message_database() && folder->last_database_server_dialog_date_ < folder->last_server_dialog_date_) { auto last_server_dialog_date_string = PSTRING() << folder->last_server_dialog_date_.get_order() << ' ' << folder->last_server_dialog_date_.get_dialog_id().get(); G()->td_db()->get_binlog_pmc()->set(PSTRING() << "last_server_dialog_date" << folder_id.get(), last_server_dialog_date_string); LOG(INFO) << "Save last server dialog date " << folder->last_server_dialog_date_; folder->last_database_server_dialog_date_ = folder->last_server_dialog_date_; folder->last_loaded_database_dialog_date_ = folder->last_server_dialog_date_; } } // must not call get_dialog_filter bool MessagesManager::do_update_list_last_pinned_dialog_date(DialogList &list) const { CHECK(!td_->auth_manager_->is_bot()); if (list.last_pinned_dialog_date_ == MAX_DIALOG_DATE) { return false; } if (!list.are_pinned_dialogs_inited_) { return false; } DialogDate max_dialog_date = MIN_DIALOG_DATE; for (const auto &pinned_dialog : list.pinned_dialogs_) { if (!have_dialog(pinned_dialog.get_dialog_id())) { break; } max_dialog_date = pinned_dialog; } if (list.pinned_dialogs_.empty() || max_dialog_date == list.pinned_dialogs_.back()) { max_dialog_date = MAX_DIALOG_DATE; } if (list.last_pinned_dialog_date_ < max_dialog_date) { LOG(INFO) << "Update last pinned dialog date in " << list.dialog_list_id << " from " << list.last_pinned_dialog_date_ << " to " << max_dialog_date; list.last_pinned_dialog_date_ = max_dialog_date; return true; } return false; } void MessagesManager::update_list_last_pinned_dialog_date(DialogList &list) { CHECK(!td_->auth_manager_->is_bot()); if (do_update_list_last_pinned_dialog_date(list)) { update_list_last_dialog_date(list); } } // must not call get_dialog_filter bool MessagesManager::do_update_list_last_dialog_date(DialogList &list, const vector &folder_ids) const { CHECK(!td_->auth_manager_->is_bot()); auto new_last_dialog_date = list.last_pinned_dialog_date_; for (auto folder_id : folder_ids) { const auto &folder = *get_dialog_folder(folder_id); if (folder.folder_last_dialog_date_ < new_last_dialog_date) { new_last_dialog_date = folder.folder_last_dialog_date_; } } if (list.list_last_dialog_date_ != new_last_dialog_date) { auto old_last_dialog_date = list.list_last_dialog_date_; LOG(INFO) << "Update last dialog date in " << list.dialog_list_id << " from " << old_last_dialog_date << " to " << new_last_dialog_date; LOG_CHECK(old_last_dialog_date < new_last_dialog_date) << list.dialog_list_id << " " << old_last_dialog_date << " " << new_last_dialog_date << " " << get_dialog_list_folder_ids(list) << " " << list.last_pinned_dialog_date_ << " " << get_dialog_folder(FolderId::main())->folder_last_dialog_date_ << " " << get_dialog_folder(FolderId::archive())->folder_last_dialog_date_ << " " << list.load_list_queries_.size() << " " << list.pinned_dialogs_; list.list_last_dialog_date_ = new_last_dialog_date; return true; } return false; } void MessagesManager::update_list_last_dialog_date(DialogList &list) { CHECK(!td_->auth_manager_->is_bot()); auto old_dialog_total_count = get_dialog_total_count(list); auto old_last_dialog_date = list.list_last_dialog_date_; if (!do_update_list_last_dialog_date(list, get_dialog_list_folder_ids(list))) { LOG(INFO) << "Don't need to update last dialog date in " << list.dialog_list_id; return; } for (auto it = std::upper_bound(list.pinned_dialogs_.begin(), list.pinned_dialogs_.end(), old_last_dialog_date); it != list.pinned_dialogs_.end() && *it <= list.list_last_dialog_date_; ++it) { auto dialog_id = it->get_dialog_id(); auto d = get_dialog(dialog_id); CHECK(d != nullptr); send_update_chat_position(list.dialog_list_id, d, "update_list_last_dialog_date"); } bool is_list_further_loaded = list.list_last_dialog_date_ == MAX_DIALOG_DATE; for (auto folder_id : get_dialog_list_folder_ids(list)) { const auto &folder = *get_dialog_folder(folder_id); for (auto it = folder.ordered_dialogs_.upper_bound(old_last_dialog_date); it != folder.ordered_dialogs_.end() && *it <= folder.folder_last_dialog_date_; ++it) { if (it->get_order() == DEFAULT_ORDER) { break; } auto dialog_id = it->get_dialog_id(); if (get_dialog_pinned_order(&list, dialog_id) == DEFAULT_ORDER) { auto d = get_dialog(dialog_id); CHECK(d != nullptr); if (is_dialog_in_list(d, list.dialog_list_id)) { send_update_chat_position(list.dialog_list_id, d, "update_list_last_dialog_date 2"); is_list_further_loaded = true; } } } } if (list.list_last_dialog_date_ == MAX_DIALOG_DATE) { recalc_unread_count(list.dialog_list_id, old_dialog_total_count, true); } LOG(INFO) << "After updating last dialog date in " << list.dialog_list_id << " to " << list.list_last_dialog_date_ << " have is_list_further_loaded == " << is_list_further_loaded << " and " << list.load_list_queries_.size() << " pending load list queries"; if (is_list_further_loaded && !list.load_list_queries_.empty()) { set_promises(list.load_list_queries_); } } MessagesManager::Dialog *MessagesManager::get_dialog(DialogId dialog_id) { return dialogs_.get_pointer(dialog_id); } const MessagesManager::Dialog *MessagesManager::get_dialog(DialogId dialog_id) const { return dialogs_.get_pointer(dialog_id); } bool MessagesManager::have_dialog_force(DialogId dialog_id, const char *source) { return loaded_dialogs_.count(dialog_id) > 0 || get_dialog_force(dialog_id, source) != nullptr; } MessagesManager::Dialog *MessagesManager::get_dialog_force(DialogId dialog_id, const char *source) { init(); Dialog *d = get_dialog(dialog_id); if (d != nullptr) { LOG_CHECK(d->dialog_id == dialog_id) << d->dialog_id << ' ' << dialog_id; return d; } if (!dialog_id.is_valid() || !G()->use_message_database() || loaded_dialogs_.count(dialog_id) > 0 || failed_to_load_dialogs_.count(dialog_id) > 0) { return nullptr; } auto r_value = G()->td_db()->get_dialog_db_sync()->get_dialog(dialog_id); if (r_value.is_ok()) { LOG(INFO) << "Loaded " << dialog_id << " from database from " << source; d = on_load_dialog_from_database(dialog_id, r_value.move_as_ok(), source); LOG_CHECK(d == nullptr || d->dialog_id == dialog_id) << d->dialog_id << ' ' << dialog_id; } else { failed_to_load_dialogs_.insert(dialog_id); LOG(INFO) << "Failed to load " << dialog_id << " from database from " << source << ": " << r_value.error().message(); } return d; } unique_ptr MessagesManager::parse_dialog(DialogId dialog_id, const BufferSlice &value, const char *source) { LOG(INFO) << "Loaded " << dialog_id << " of size " << value.size() << " from database from " << source; CHECK(dialog_id.is_valid()); auto dialog = make_unique(); Dialog *d = dialog.get(); d->dialog_id = dialog_id; invalidate_message_indexes(d); // must initialize indexes, because some of them could be not parsed loaded_dialogs_.insert(dialog_id); auto status = log_event_parse(*d, value.as_slice()); if (status.is_error() || !d->dialog_id.is_valid() || d->dialog_id != dialog_id) { // can't happen unless database is broken, but has been seen in the wild // if dialog_id is invalid, we can't repair the dialog LOG_CHECK(dialog_id.is_valid()) << "Can't repair " << dialog_id << ' ' << d->dialog_id << ' ' << status << ' ' << source << ' ' << format::as_hex_dump<4>(value.as_slice()); LOG(ERROR) << "Repair broken " << dialog_id << ": " << status << ' ' << format::as_hex_dump<4>(value.as_slice()); // just clean all known data about the dialog dialog = make_unique(); d = dialog.get(); d->dialog_id = dialog_id; invalidate_message_indexes(d); // and try to reget it from the server if possible have_dialog_info_force(dialog_id, "parse_dialog"); if (have_input_peer(dialog_id, AccessRights::Read)) { if (dialog_id.get_type() != DialogType::SecretChat) { send_get_dialog_query(dialog_id, Auto(), 0, source); } } else { LOG(ERROR) << "Can't repair unknown " << dialog_id << " from " << source; } } CHECK(dialog_id == d->dialog_id); Dependencies dependencies; dependencies.add_dialog_dependencies(dialog_id); if (d->default_join_group_call_as_dialog_id != dialog_id) { dependencies.add_message_sender_dependencies(d->default_join_group_call_as_dialog_id); } if (d->default_send_message_as_dialog_id != dialog_id) { dependencies.add_message_sender_dependencies(d->default_send_message_as_dialog_id); } d->messages.foreach([&](const MessageId &message_id, const unique_ptr &message) { add_message_dependencies(dependencies, message.get()); }); add_draft_message_dependencies(dependencies, d->draft_message); for (auto user_id : d->pending_join_request_user_ids) { dependencies.add(user_id); } if (!dependencies.resolve_force(td_, source)) { send_get_dialog_query(dialog_id, Auto(), 0, source); } if (td_->auth_manager_->is_bot()) { if (d->unread_mention_count > 0) { set_dialog_unread_mention_count(d, 0); } if (d->unread_reaction_count > 0) { set_dialog_unread_reaction_count(d, 0); } } auto dialog_type = d->dialog_id.get_type(); switch (dialog_type) { case DialogType::Chat: case DialogType::Channel: if (get_active_reactions(d->available_reactions).empty() != ((d->available_reactions_generation & 1) == 1)) { set_dialog_next_available_reactions_generation(d, d->available_reactions_generation); } break; case DialogType::User: case DialogType::SecretChat: default: break; } if (!d->need_drop_default_send_message_as_dialog_id && d->default_send_message_as_dialog_id.is_valid() && dialog_type == DialogType::Channel && !td_->contacts_manager_->is_channel_public(dialog_id.get_channel_id()) && !td_->contacts_manager_->get_channel_has_linked_channel(dialog_id.get_channel_id())) { LOG(INFO) << "Drop message sender in " << dialog_id; d->need_drop_default_send_message_as_dialog_id = true; } return dialog; } MessagesManager::Dialog *MessagesManager::on_load_dialog_from_database(DialogId dialog_id, BufferSlice &&value, const char *source) { CHECK(G()->use_message_database()); if (!dialog_id.is_valid()) { // hack LogEventParser dialog_id_parser(value.as_slice()); int32 flags; parse(flags, dialog_id_parser); parse(dialog_id, dialog_id_parser); if (!dialog_id.is_valid()) { LOG(ERROR) << "Failed to parse dialog_id from blob. Database is broken"; return nullptr; } } auto old_d = get_dialog(dialog_id); if (old_d != nullptr) { return old_d; } LOG(INFO) << "Add new " << dialog_id << " from database from " << source; return add_new_dialog(parse_dialog(dialog_id, value, source), true, source); } vector MessagesManager::get_dialog_list_folder_ids(const DialogList &list) const { CHECK(!td_->auth_manager_->is_bot()); if (list.dialog_list_id.is_folder()) { return {list.dialog_list_id.get_folder_id()}; } if (list.dialog_list_id.is_filter()) { return td_->dialog_filter_manager_->get_dialog_filter_folder_ids(list.dialog_list_id.get_filter_id()); } UNREACHABLE(); return {}; } bool MessagesManager::has_dialogs_from_folder(const DialogList &list, const DialogFolder &folder) const { CHECK(!td_->auth_manager_->is_bot()); if (list.dialog_list_id.is_folder()) { return list.dialog_list_id.get_folder_id() == folder.folder_id; } if (list.dialog_list_id.is_filter()) { return td::contains(td_->dialog_filter_manager_->get_dialog_filter_folder_ids(list.dialog_list_id.get_filter_id()), folder.folder_id); } UNREACHABLE(); return false; } bool MessagesManager::is_dialog_in_list(const Dialog *d, DialogListId dialog_list_id) { return td::contains(d->dialog_list_ids, dialog_list_id); } void MessagesManager::add_dialog_to_list(Dialog *d, DialogListId dialog_list_id) { LOG(INFO) << "Add " << d->dialog_id << " to " << dialog_list_id; CHECK(!is_dialog_in_list(d, dialog_list_id)); d->dialog_list_ids.push_back(dialog_list_id); } void MessagesManager::remove_dialog_from_list(Dialog *d, DialogListId dialog_list_id) { LOG(INFO) << "Remove " << d->dialog_id << " from " << dialog_list_id; bool is_removed = td::remove(d->dialog_list_ids, dialog_list_id); CHECK(is_removed); } DialogFilterDialogInfo MessagesManager::get_dialog_info_for_dialog_filter(const Dialog *d) const { CHECK(d != nullptr); CHECK(d->order != DEFAULT_ORDER); DialogFilterDialogInfo dialog_info; dialog_info.dialog_id_ = d->dialog_id; dialog_info.folder_id_ = d->folder_id; dialog_info.has_unread_mentions_ = d->unread_mention_count != 0 && !is_dialog_mention_notifications_disabled(d); dialog_info.is_muted_ = is_dialog_muted(d); dialog_info.has_unread_messages_ = d->server_unread_count + d->local_unread_count != 0 || d->is_marked_as_unread; return dialog_info; } bool MessagesManager::is_dialog_in_dialog_list(DialogId dialog_id) const { CHECK(!td_->auth_manager_->is_bot()); const Dialog *d = get_dialog(dialog_id); return d != nullptr && d->order != DEFAULT_ORDER; } bool MessagesManager::need_dialog_in_list(const Dialog *d, const DialogList &list) const { CHECK(!td_->auth_manager_->is_bot()); if (d->order == DEFAULT_ORDER) { return false; } if (list.dialog_list_id.is_folder()) { return d->folder_id == list.dialog_list_id.get_folder_id(); } if (list.dialog_list_id.is_filter()) { return td_->dialog_filter_manager_->need_dialog_in_filter(list.dialog_list_id.get_filter_id(), get_dialog_info_for_dialog_filter(d)); } UNREACHABLE(); return false; } bool MessagesManager::need_send_update_chat_position(const DialogPositionInList &old_position, const DialogPositionInList &new_position) { if (old_position.public_order != new_position.public_order) { return true; } if (old_position.public_order == 0) { return false; } return old_position.is_pinned != new_position.is_pinned || old_position.is_sponsored != new_position.is_sponsored; } MessagesManager::DialogPositionInList MessagesManager::get_dialog_position_in_list(const DialogList *list, const Dialog *d, bool actual) const { CHECK(!td_->auth_manager_->is_bot()); CHECK(list != nullptr); CHECK(d != nullptr); DialogPositionInList position; position.order = d->order; if (is_dialog_sponsored(d) || (actual ? need_dialog_in_list(d, *list) : is_dialog_in_list(d, list->dialog_list_id))) { position.private_order = get_dialog_private_order(list, d); } if (position.private_order != 0) { position.public_order = DialogDate(position.private_order, d->dialog_id) <= list->list_last_dialog_date_ ? position.private_order : 0; position.is_pinned = get_dialog_pinned_order(list, d->dialog_id) != DEFAULT_ORDER; position.is_sponsored = is_dialog_sponsored(d); } position.total_dialog_count = get_dialog_total_count(*list); return position; } std::unordered_map MessagesManager::get_dialog_positions(const Dialog *d) const { CHECK(d != nullptr); std::unordered_map positions; if (!td_->auth_manager_->is_bot()) { for (const auto &dialog_list : dialog_lists_) { positions.emplace(dialog_list.first, get_dialog_position_in_list(&dialog_list.second, d)); } } return positions; } vector MessagesManager::get_dialog_list_ids(const Dialog *d) { return d->dialog_list_ids; } MessagesManager::DialogListView MessagesManager::get_dialog_lists(const Dialog *d) { return DialogListView(this, get_dialog_list_ids(d)); } MessagesManager::DialogList &MessagesManager::add_dialog_list(DialogListId dialog_list_id) { CHECK(!td_->auth_manager_->is_bot()); if (dialog_lists_.count(dialog_list_id) == 0) { LOG(INFO) << "Create " << dialog_list_id; } auto &list = dialog_lists_[dialog_list_id]; list.dialog_list_id = dialog_list_id; return list; } MessagesManager::DialogList *MessagesManager::get_dialog_list(DialogListId dialog_list_id) { CHECK(!td_->auth_manager_->is_bot()); auto it = dialog_lists_.find(dialog_list_id); if (it == dialog_lists_.end()) { return nullptr; } return &it->second; } const MessagesManager::DialogList *MessagesManager::get_dialog_list(DialogListId dialog_list_id) const { CHECK(!td_->auth_manager_->is_bot()); auto it = dialog_lists_.find(dialog_list_id); if (it == dialog_lists_.end()) { return nullptr; } return &it->second; } MessagesManager::DialogFolder *MessagesManager::get_dialog_folder(FolderId folder_id) { CHECK(!td_->auth_manager_->is_bot()); auto it = dialog_folders_.find(folder_id); if (it == dialog_folders_.end()) { return nullptr; } return &it->second; } const MessagesManager::DialogFolder *MessagesManager::get_dialog_folder(FolderId folder_id) const { CHECK(!td_->auth_manager_->is_bot()); auto it = dialog_folders_.find(folder_id); if (it == dialog_folders_.end()) { return nullptr; } return &it->second; } string MessagesManager::get_channel_pts_key(DialogId dialog_id) { CHECK(dialog_id.get_type() == DialogType::Channel); auto channel_id = dialog_id.get_channel_id(); return PSTRING() << "ch.p" << channel_id.get(); } int32 MessagesManager::load_channel_pts(DialogId dialog_id) const { if (td_->ignore_background_updates() || !have_input_peer(dialog_id, AccessRights::Read)) { G()->td_db()->get_binlog_pmc()->erase(get_channel_pts_key(dialog_id)); // just in case return 0; } auto pts = to_integer(G()->td_db()->get_binlog_pmc()->get(get_channel_pts_key(dialog_id))); LOG(INFO) << "Load " << dialog_id << " PTS = " << pts; return pts; } void MessagesManager::set_channel_pts(Dialog *d, int32 new_pts, const char *source) { CHECK(d != nullptr); CHECK(d->dialog_id.get_type() == DialogType::Channel); LOG_IF(ERROR, running_get_channel_difference(d->dialog_id)) << "Set PTS of " << d->dialog_id << " to " << new_pts << " from " << source << " while running getChannelDifference"; // TODO delete_first_messages support in channels if (new_pts == std::numeric_limits::max()) { LOG(ERROR) << "Update " << d->dialog_id << " PTS to -1 from " << source; G()->td_db()->get_binlog_pmc()->erase(get_channel_pts_key(d->dialog_id)); d->pts = std::numeric_limits::max(); if (d->pending_read_channel_inbox_pts != 0) { d->pending_read_channel_inbox_pts = 0; } return; } if (new_pts > d->pts || (0 < new_pts && new_pts < d->pts - 99999)) { // PTS can only go up or drop cardinally if (new_pts < d->pts - 99999) { LOG(WARNING) << "PTS of " << d->dialog_id << " decreases from " << d->pts << " to " << new_pts << " from " << source; } else { LOG(INFO) << "Update " << d->dialog_id << " PTS to " << new_pts << " from " << source; } d->pts = new_pts; if (d->pending_read_channel_inbox_pts != 0 && d->pending_read_channel_inbox_pts <= d->pts) { auto pts = d->pending_read_channel_inbox_pts; d->pending_read_channel_inbox_pts = 0; on_dialog_updated(d->dialog_id, "set_channel_pts"); if (d->pts == pts) { read_history_inbox(d, d->pending_read_channel_inbox_max_message_id, d->pending_read_channel_inbox_server_unread_count, "set_channel_pts"); } else if (d->pts > pts) { repair_channel_server_unread_count(d); } } if (!td_->ignore_background_updates() && have_input_peer(d->dialog_id, AccessRights::Read)) { G()->td_db()->get_binlog_pmc()->set(get_channel_pts_key(d->dialog_id), to_string(new_pts)); } } else if (new_pts < d->pts) { LOG(ERROR) << "Receive wrong PTS " << new_pts << " in " << d->dialog_id << " from " << source << ". Current PTS is " << d->pts; } } bool MessagesManager::need_channel_difference_to_add_message(DialogId dialog_id, const tl_object_ptr &message_ptr) { if (dialog_id.get_type() != DialogType::Channel || !have_input_peer(dialog_id, AccessRights::Read) || dialog_id == debug_channel_difference_dialog_ || td_->auth_manager_->is_bot()) { return false; } if (message_ptr == nullptr) { return true; } if (DialogId::get_message_dialog_id(message_ptr) != dialog_id) { return false; } Dialog *d = get_dialog_force(dialog_id, "need_channel_difference_to_add_message"); if (d == nullptr) { LOG(DEBUG) << "Can't find " << dialog_id; return load_channel_pts(dialog_id) > 0 && !is_channel_difference_finished_.count(dialog_id); } if (d->last_new_message_id == MessageId()) { LOG(DEBUG) << "Can't find last message in " << dialog_id; return d->pts > 0 && !d->is_channel_difference_finished; } auto message_id = MessageId::get_message_id(message_ptr, false); LOG(DEBUG) << "Check ability to add " << message_id << " to " << dialog_id; return message_id > d->last_new_message_id; } void MessagesManager::run_after_channel_difference(DialogId dialog_id, MessageId expected_max_message_id, Promise &&promise, const char *source) { CHECK(dialog_id.get_type() == DialogType::Channel); CHECK(have_input_peer(dialog_id, AccessRights::Read)); run_after_get_channel_difference_[dialog_id].push_back(std::move(promise)); const Dialog *d = get_dialog(dialog_id); get_channel_difference(dialog_id, d == nullptr ? load_channel_pts(dialog_id) : d->pts, 0, expected_max_message_id, true, source); } bool MessagesManager::running_get_channel_difference(DialogId dialog_id) const { return active_get_channel_differences_.count(dialog_id) > 0; } void MessagesManager::on_channel_get_difference_timeout(DialogId dialog_id) { if (G()->close_flag()) { return; } CHECK(dialog_id.get_type() == DialogType::Channel); auto d = get_dialog(dialog_id); CHECK(d != nullptr); get_channel_difference(dialog_id, d->pts, 0, MessageId(), true, "on_channel_get_difference_timeout"); } class MessagesManager::GetChannelDifferenceLogEvent { public: ChannelId channel_id; int64 access_hash; GetChannelDifferenceLogEvent() : channel_id(), access_hash() { } GetChannelDifferenceLogEvent(ChannelId channel_id, int64 access_hash) : channel_id(channel_id), access_hash(access_hash) { } template void store(StorerT &storer) const { td::store(channel_id, storer); td::store(access_hash, storer); } template void parse(ParserT &parser) { td::parse(channel_id, parser); td::parse(access_hash, parser); } }; void MessagesManager::update_expected_channel_pts(DialogId dialog_id, int32 expected_pts) { if (expected_pts <= 0) { return; } CHECK(dialog_id.is_valid()); auto &old_pts = expected_channel_pts_[dialog_id]; if (old_pts < expected_pts) { old_pts = expected_pts; } } void MessagesManager::update_expected_channel_max_message_id(DialogId dialog_id, MessageId expected_max_message_id) { if (expected_max_message_id == MessageId() || td_->auth_manager_->is_bot()) { return; } CHECK(dialog_id.is_valid()); auto &old_max_message_id = expected_channel_max_message_id_[dialog_id]; if (old_max_message_id < expected_max_message_id) { old_max_message_id = expected_max_message_id; } } void MessagesManager::schedule_get_channel_difference(DialogId dialog_id, int32 expected_pts, MessageId expected_max_message_id, double delay, const char *source) { LOG(INFO) << "Schedule getDifference in " << dialog_id << " from " << source; update_expected_channel_pts(dialog_id, expected_pts); update_expected_channel_max_message_id(dialog_id, expected_max_message_id); channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), delay); } void MessagesManager::get_channel_difference(DialogId dialog_id, int32 pts, int32 expected_pts, MessageId expected_max_message_id, bool force, const char *source, bool is_old) { update_expected_channel_pts(dialog_id, expected_pts); update_expected_channel_max_message_id(dialog_id, expected_max_message_id); if (channel_get_difference_retry_timeout_.has_timeout(dialog_id.get())) { LOG(INFO) << "Skip running channels.getDifference for " << dialog_id << " from " << source << " because it is scheduled for later time"; return; } LOG_CHECK(dialog_id.get_type() == DialogType::Channel) << dialog_id << " " << source; if (active_get_channel_differences_.count(dialog_id)) { LOG(INFO) << "Skip running channels.getDifference for " << dialog_id << " from " << source << " because it has already been run"; return; } debug_last_get_channel_difference_dialog_id_ = dialog_id; debug_last_get_channel_difference_source_ = source; auto input_channel = td_->contacts_manager_->get_input_channel(dialog_id.get_channel_id()); if (input_channel == nullptr) { LOG(ERROR) << "Skip running channels.getDifference for " << dialog_id << " from " << source << " because the channel is unknown"; after_get_channel_difference(dialog_id, false); return; } if (!have_input_peer(dialog_id, AccessRights::Read)) { LOG(INFO) << "Skip running channels.getDifference for " << dialog_id << " from " << source << " because have no read access to it"; after_get_channel_difference(dialog_id, false); return; } if (force && get_channel_difference_to_log_event_id_.count(dialog_id) == 0 && !td_->ignore_background_updates() && input_channel->get_id() == telegram_api::inputChannel::ID) { auto channel_id = dialog_id.get_channel_id(); auto access_hash = static_cast(*input_channel).access_hash_; auto log_event = GetChannelDifferenceLogEvent(channel_id, access_hash); auto log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::GetChannelDifference, get_log_event_storer(log_event)); get_channel_difference_to_log_event_id_.emplace(dialog_id, log_event_id); } return do_get_channel_difference(dialog_id, pts, force, std::move(input_channel), is_old, source); } void MessagesManager::do_get_channel_difference(DialogId dialog_id, int32 pts, bool force, tl_object_ptr &&input_channel, bool is_old, const char *source) { auto inserted = active_get_channel_differences_.emplace(dialog_id, source); if (!inserted.second) { LOG(INFO) << "Skip running channels.getDifference for " << dialog_id << " from " << source << " because it has already been run"; return; } // must work even we know nothing about the dialog // can be called multiple times before after_get_channel_difference const Dialog *d = get_dialog(dialog_id); if (d != nullptr && d->notification_info != nullptr) { if (d->notification_info->message_notification_group_.is_valid()) { send_closure_later(G()->notification_manager(), &NotificationManager::before_get_chat_difference, d->notification_info->message_notification_group_.get_group_id()); } if (d->notification_info->mention_notification_group_.is_valid()) { send_closure_later(G()->notification_manager(), &NotificationManager::before_get_chat_difference, d->notification_info->mention_notification_group_.get_group_id()); } } int32 limit = td_->auth_manager_->is_bot() && !is_old ? MAX_BOT_CHANNEL_DIFFERENCE : MAX_CHANNEL_DIFFERENCE; if (pts <= 0) { pts = 1; limit = MIN_CHANNEL_DIFFERENCE; } pending_get_channel_differences_.push( td::make_unique(dialog_id, pts, limit, force, std::move(input_channel), source)); process_pending_get_channel_differences(); } void MessagesManager::process_pending_get_channel_differences() { static constexpr int32 MAX_CONCURRENT_GET_CHANNEL_DIFFERENCES = 10; if (pending_get_channel_differences_.empty() || get_channel_difference_count_ >= MAX_CONCURRENT_GET_CHANNEL_DIFFERENCES) { return; } get_channel_difference_count_++; auto query = std::move(pending_get_channel_differences_.front()); pending_get_channel_differences_.pop(); LOG(INFO) << "-----BEGIN GET CHANNEL DIFFERENCE----- for " << query->dialog_id_ << " with PTS " << query->pts_ << " and limit " << query->limit_ << " from " << query->source_; td_->create_handler()->send(query->dialog_id_, std::move(query->input_channel_), query->pts_, query->limit_, query->force_); } void MessagesManager::process_get_channel_difference_updates( DialogId dialog_id, int32 new_pts, vector> &&new_messages, vector> &&other_updates) { LOG(INFO) << "In get channel difference for " << dialog_id << " receive " << new_messages.size() << " messages and " << other_updates.size() << " other updates"; CHECK(!debug_channel_difference_dialog_.is_valid()); debug_channel_difference_dialog_ = dialog_id; // identifiers of edited and deleted messages FlatHashSet changed_message_ids; for (auto &update_ptr : other_updates) { bool is_good_update = true; switch (update_ptr->get_id()) { case telegram_api::updateMessageID::ID: { // in channels.getDifference updateMessageID can't be received for scheduled messages auto sent_message_update = move_tl_object_as(update_ptr); on_update_message_id(sent_message_update->random_id_, MessageId(ServerMessageId(sent_message_update->id_)), "get_channel_difference"); update_ptr = nullptr; break; } case telegram_api::updateDeleteChannelMessages::ID: { auto *update = static_cast(update_ptr.get()); if (DialogId(ChannelId(update->channel_id_)) != dialog_id) { is_good_update = false; } else { for (auto &message : update->messages_) { changed_message_ids.insert(MessageId(ServerMessageId(message))); } } break; } case telegram_api::updateEditChannelMessage::ID: { auto *update = static_cast(update_ptr.get()); auto message_full_id = MessageFullId::get_message_full_id(update->message_, false); if (message_full_id.get_dialog_id() != dialog_id) { is_good_update = false; } else { changed_message_ids.insert(message_full_id.get_message_id()); } break; } case telegram_api::updatePinnedChannelMessages::ID: { auto *update = static_cast(update_ptr.get()); if (DialogId(ChannelId(update->channel_id_)) != dialog_id) { is_good_update = false; } break; } default: is_good_update = false; break; } if (!is_good_update) { LOG(ERROR) << "Receive wrong update in channelDifference of " << dialog_id << ": " << to_string(update_ptr); update_ptr = nullptr; } } auto is_edited_message = [](const tl_object_ptr &message) { if (message->get_id() != telegram_api::message::ID) { return false; } return static_cast(message.get())->edit_date_ > 0; }; for (auto &message : new_messages) { if (is_edited_message(message)) { auto message_id = MessageId::get_message_id(message, false); if (message_id.is_valid()) { changed_message_ids.insert(message_id); } } } // extract awaited sent messages, which were edited or deleted after that auto postponed_updates_it = postponed_channel_updates_.find(dialog_id); struct AwaitedMessage { tl_object_ptr message; Promise promise; }; std::map awaited_messages; if (postponed_updates_it != postponed_channel_updates_.end()) { auto &updates = postponed_updates_it->second; while (!updates.empty()) { auto it = updates.begin(); auto update_pts = it->second.pts; if (update_pts > new_pts) { break; } auto update = std::move(it->second.update); auto promise = std::move(it->second.promise); updates.erase(it); if (update->get_id() == telegram_api::updateNewChannelMessage::ID) { auto update_new_channel_message = static_cast(update.get()); auto message_id = MessageId::get_message_id(update_new_channel_message->message_, false); MessageFullId message_full_id(dialog_id, message_id); if (update_message_ids_.count(message_full_id) > 0 && changed_message_ids.count(message_id) > 0) { changed_message_ids.erase(message_id); AwaitedMessage awaited_message; awaited_message.message = std::move(update_new_channel_message->message_); awaited_message.promise = std::move(promise); awaited_messages.emplace(message_id, std::move(awaited_message)); continue; } } LOG(INFO) << "Skip to be applied from getChannelDifference " << to_string(update); promise.set_value(Unit()); } if (updates.empty()) { postponed_channel_updates_.erase(postponed_updates_it); } } // if last message is pretty old, we might have missed the update bool need_repair_unread_count = !new_messages.empty() && get_message_date(new_messages[0]) < G()->unix_time() - 2 * 86400; auto it = awaited_messages.begin(); for (auto &message : new_messages) { auto message_id = MessageId::get_message_id(message, false); while (it != awaited_messages.end() && it->first < message_id) { on_get_message(std::move(it->second.message), true, true, false, "postponed channel update"); it->second.promise.set_value(Unit()); ++it; } Promise promise; if (it != awaited_messages.end() && it->first == message_id) { if (is_edited_message(message)) { // the new message is edited, apply postponed one and move this to updateEditChannelMessage other_updates.push_back(make_tl_object(std::move(message), new_pts, 0)); message = std::move(it->second.message); promise = std::move(it->second.promise); } else { it->second.promise.set_value(Unit()); } ++it; } on_get_message(std::move(message), true, true, false, "get channel difference"); promise.set_value(Unit()); } while (it != awaited_messages.end()) { on_get_message(std::move(it->second.message), true, true, false, "postponed channel update 2"); it->second.promise.set_value(Unit()); ++it; } for (auto &update : other_updates) { if (update != nullptr) { process_channel_update(std::move(update)); } } LOG_CHECK(!running_get_channel_difference(dialog_id)) << '"' << active_get_channel_differences_[dialog_id] << '"'; if (need_repair_unread_count) { repair_channel_server_unread_count(get_dialog(dialog_id)); } CHECK(debug_channel_difference_dialog_ == dialog_id); debug_channel_difference_dialog_ = DialogId(); } void MessagesManager::on_get_channel_dialog(DialogId dialog_id, MessageId last_message_id, MessageId read_inbox_max_message_id, int32 server_unread_count, int32 unread_mention_count, int32 unread_reaction_count, MessageId read_outbox_max_message_id, vector> &&messages) { FlatHashMap, MessageFullIdHash> message_full_id_to_message; for (auto &message : messages) { auto message_id = MessageId::get_message_id(message, false); if (!message_id.is_valid()) { continue; } auto message_dialog_id = DialogId::get_message_dialog_id(message); if (!message_dialog_id.is_valid()) { message_dialog_id = dialog_id; } auto message_full_id = MessageFullId(message_dialog_id, message_id); message_full_id_to_message[message_full_id] = std::move(message); } MessageFullId last_message_full_id(dialog_id, last_message_id); if (last_message_id.is_valid()) { if (message_full_id_to_message.count(last_message_full_id) == 0) { LOG(ERROR) << "Last " << last_message_id << " in " << dialog_id << " not found. Have:"; for (auto &message : message_full_id_to_message) { LOG(ERROR) << to_string(message.second); } return; } } CHECK(!last_message_id.is_scheduled()); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); // There are many ways of handling a gap in a channel: // 1) Delete all known messages in the chat, begin from scratch. It is easy to implement, but suddenly disappearing // messages looks awful for the user. // 2) Save all messages loaded in the memory until application restart, but delete all messages from the database. // Messages left in the memory must be lazily updated using calls to getHistory. It looks much smoothly for the // user, he will need to redownload messages only after client restart. Unsynchronized messages left in the // memory shouldn't be saved to database, results of getHistory and getMessage must be used to update state of // deleted and edited messages left in the memory. // 3) Save all messages loaded in the memory and stored in the database without saving that some messages form // continuous ranges. Messages in the database will be excluded from results of getChatHistory and // searchChatMessages after application restart and will be available only through getMessage. // Every message should still be checked using getHistory. It has more disadvantages over 2) than advantages. // 4) Save all messages with saving all data about continuous message ranges. Messages from the database may be used // as results of getChatHistory and (if implemented continuous ranges support for searching shared media) // searchChatMessages. The messages should still be lazily checked using getHistory, but they are still available // offline. It is the best way for gaps support, but it is pretty hard to implement correctly. // It should be also noted that some messages like outgoing live location messages shouldn't be deleted. if (is_message_unload_enabled()) { if (d->open_count == 0) { unload_dialog(dialog_id, 0); } else { d->need_unload_on_close = true; } } if (last_message_id > d->last_new_message_id && !td_->auth_manager_->is_bot()) { // TODO properly support last_message_id <= d->last_new_message_id set_dialog_first_database_message_id(d, MessageId(), "on_get_channel_dialog 6"); set_dialog_last_database_message_id(d, MessageId(), "on_get_channel_dialog 7"); d->have_full_history = false; d->have_full_history_source = 0; d->is_empty = false; } invalidate_message_indexes(d); // d->history_generation++; on_dialog_updated(dialog_id, "on_get_channel_dialog 10"); // TODO properly support last_message_id <= d->last_new_message_id if (last_message_id > d->last_new_message_id && !td_->auth_manager_->is_bot()) { // if last message is really a new message if (!d->last_new_message_id.is_valid() && last_message_id <= d->max_added_message_id) { auto prev_message_id = MessageId(ServerMessageId(last_message_id.get_server_message_id().get() - 1)); delete_all_dialog_notifications(d, prev_message_id, "on_get_channel_dialog 14"); remove_dialog_newer_messages(d, prev_message_id, "on_get_channel_dialog 15"); } d->last_new_message_id = MessageId(); set_dialog_last_message_id(d, MessageId(), "on_get_channel_dialog 20"); send_update_chat_last_message(d, "on_get_channel_dialog 30"); MessageFullId added_message_full_id; if (last_message_full_id.get_message_id().is_valid()) { last_message_full_id = on_get_message(std::move(message_full_id_to_message[last_message_full_id]), true, true, false, "channel difference too long"); } if (added_message_full_id.get_message_id().is_valid()) { if (added_message_full_id.get_message_id() == d->last_new_message_id) { CHECK(last_message_full_id == added_message_full_id); if (!td_->auth_manager_->is_bot()) { CHECK(d->last_message_id == d->last_new_message_id); } } else { LOG(ERROR) << added_message_full_id << " doesn't became last new message, which is " << d->last_new_message_id; } } else if (last_message_id > d->last_new_message_id) { set_dialog_last_new_message_id(d, last_message_id, "on_get_channel_dialog 40"); // skip updates about some messages } } if (d->last_read_inbox_message_id.is_valid() && !d->last_read_inbox_message_id.is_server() && read_inbox_max_message_id == d->last_read_inbox_message_id.get_prev_server_message_id()) { read_inbox_max_message_id = d->last_read_inbox_message_id; } if (d->server_unread_count != server_unread_count || d->last_read_inbox_message_id != read_inbox_max_message_id) { set_dialog_last_read_inbox_message_id(d, read_inbox_max_message_id, server_unread_count, d->local_unread_count, false, "on_get_channel_dialog 50"); } if (d->unread_mention_count != unread_mention_count && !td_->auth_manager_->is_bot()) { set_dialog_unread_mention_count(d, unread_mention_count); update_dialog_mention_notification_count(d); send_update_chat_unread_mention_count(d); } if (d->unread_reaction_count != unread_reaction_count && !td_->auth_manager_->is_bot()) { set_dialog_unread_reaction_count(d, unread_reaction_count); // update_dialog_mention_notification_count(d); send_update_chat_unread_reaction_count(d, "on_get_channel_dialog 60"); } if (d->last_read_outbox_message_id != read_outbox_max_message_id) { set_dialog_last_read_outbox_message_id(d, read_outbox_max_message_id); } } void MessagesManager::retry_get_channel_difference_later(DialogId dialog_id) { auto &delay = channel_get_difference_retry_timeouts_[dialog_id]; if (delay == 0) { delay = 1; } schedule_get_channel_difference(dialog_id, 0, MessageId(), Random::fast(delay * 800, delay * 1200) * 1e-3, "retry_get_channel_difference_later"); delay *= 2; if (delay > 60) { delay = Random::fast(60, 80); } } void MessagesManager::on_get_channel_difference(DialogId dialog_id, int32 request_pts, int32 request_limit, tl_object_ptr &&difference_ptr, Status &&status) { get_channel_difference_count_--; CHECK(get_channel_difference_count_ >= 0); process_pending_get_channel_differences(); LOG(INFO) << "----- END GET CHANNEL DIFFERENCE----- for " << dialog_id; auto it = active_get_channel_differences_.find(dialog_id); CHECK(it != active_get_channel_differences_.end()); string source = std::move(it->second); active_get_channel_differences_.erase(it); auto d = get_dialog_force(dialog_id, "on_get_channel_difference"); if (difference_ptr == nullptr) { CHECK(status.is_error()); bool have_access = have_input_peer(dialog_id, AccessRights::Read) && status.message() != "CHANNEL_INVALID"; if (have_access) { if (d == nullptr) { force_create_dialog(dialog_id, "on_get_channel_difference failed"); d = get_dialog(dialog_id); if (d == nullptr) { return after_get_channel_difference(dialog_id, false); } } retry_get_channel_difference_later(dialog_id); } else { after_get_channel_difference(dialog_id, false); } return; } CHECK(status.is_ok()); LOG(INFO) << "Receive result of getChannelDifference for " << dialog_id << " with PTS = " << request_pts << " and limit = " << request_limit << " from " << source << ": " << to_string(difference_ptr); bool have_new_messages = false; switch (difference_ptr->get_id()) { case telegram_api::updates_channelDifferenceEmpty::ID: if (d == nullptr) { // no need to create the dialog channel_get_difference_retry_timeouts_.erase(dialog_id); after_get_channel_difference(dialog_id, true); return; } break; case telegram_api::updates_channelDifference::ID: { auto difference = static_cast(difference_ptr.get()); have_new_messages = !difference->new_messages_.empty(); td_->contacts_manager_->on_get_users(std::move(difference->users_), "updates.channelDifference"); td_->contacts_manager_->on_get_chats(std::move(difference->chats_), "updates.channelDifference"); for (const auto &message : difference->new_messages_) { if (is_invalid_poll_message(message.get())) { LOG(ERROR) << "Receive invalid poll message in updates.channelDifference: " << oneline(to_string(message)); if (channel_get_difference_retry_timeouts_[dialog_id] <= 2) { return retry_get_channel_difference_later(dialog_id); } } } break; } case telegram_api::updates_channelDifferenceTooLong::ID: { auto difference = static_cast(difference_ptr.get()); have_new_messages = difference->dialog_->get_id() == telegram_api::dialog::ID && !difference->messages_.empty(); td_->contacts_manager_->on_get_users(std::move(difference->users_), "updates.channelDifferenceTooLong"); td_->contacts_manager_->on_get_chats(std::move(difference->chats_), "updates.channelDifferenceTooLong"); break; } default: UNREACHABLE(); } channel_get_difference_retry_timeouts_.erase(dialog_id); bool need_update_dialog_pos = false; if (d == nullptr) { d = add_dialog_for_new_message(dialog_id, have_new_messages, &need_update_dialog_pos, "on_get_channel_difference"); } int32 cur_pts = d->pts <= 0 ? 1 : d->pts; LOG_IF(ERROR, cur_pts != request_pts) << "Channel PTS has changed from " << request_pts << " to " << d->pts << " in " << dialog_id << " during getChannelDifference from " << source; bool is_final = true; bool is_old = false; int32 timeout = 0; switch (difference_ptr->get_id()) { case telegram_api::updates_channelDifferenceEmpty::ID: { auto difference = move_tl_object_as(difference_ptr); int32 flags = difference->flags_; is_final = (flags & CHANNEL_DIFFERENCE_FLAG_IS_FINAL) != 0; LOG_IF(ERROR, !is_final) << "Receive channelDifferenceEmpty as result of getChannelDifference from " << source << " with PTS = " << request_pts << " and limit = " << request_limit << " in " << dialog_id << ", but it is not final"; if (flags & CHANNEL_DIFFERENCE_FLAG_HAS_TIMEOUT) { timeout = difference->timeout_; } // bots can receive channelDifferenceEmpty with PTS bigger than known PTS // also, this can happen for deleted channels if (request_pts != difference->pts_ && !td_->auth_manager_->is_bot() && have_input_peer(dialog_id, AccessRights::Read)) { LOG(ERROR) << "Receive channelDifferenceEmpty as result of getChannelDifference from " << source << " with PTS = " << request_pts << " and limit = " << request_limit << " in " << dialog_id << ", but PTS has changed to " << difference->pts_; } set_channel_pts(d, difference->pts_, "channel difference empty"); break; } case telegram_api::updates_channelDifference::ID: { auto difference = move_tl_object_as(difference_ptr); int32 flags = difference->flags_; is_final = (flags & CHANNEL_DIFFERENCE_FLAG_IS_FINAL) != 0; if (flags & CHANNEL_DIFFERENCE_FLAG_HAS_TIMEOUT) { timeout = difference->timeout_; } auto new_pts = difference->pts_; if (request_pts >= new_pts && request_pts > 1 && (request_pts > new_pts || !td_->auth_manager_->is_bot())) { LOG(ERROR) << "Receive channelDifference as result of getChannelDifference from " << source << " with PTS = " << request_pts << " and limit = " << request_limit << " in " << dialog_id << ", but PTS has changed from " << d->pts << " to " << new_pts << ". Difference: " << oneline(to_string(difference)); new_pts = request_pts + 1; } if (difference->new_messages_.size() > 1) { // check that new messages are received in increasing message_id order MessageId cur_message_id; for (const auto &message : difference->new_messages_) { auto message_id = MessageId::get_message_id(message, false); if (message_id <= cur_message_id) { LOG(ERROR) << "Receive " << cur_message_id << " after " << message_id << " in channelDifference of " << dialog_id << " from " << source << " with PTS " << request_pts << " and limit " << request_limit << ": " << to_string(difference); after_get_channel_difference(dialog_id, false); return; } cur_message_id = message_id; } } if (!is_final && !difference->new_messages_.empty() && td_->auth_manager_->is_bot()) { auto date = get_message_date(difference->new_messages_.back()); if (0 < date && date < G()->unix_time() - 2 * 86400) { is_old = true; } } process_get_channel_difference_updates(dialog_id, new_pts, std::move(difference->new_messages_), std::move(difference->other_updates_)); set_channel_pts(d, new_pts, "channel difference"); break; } case telegram_api::updates_channelDifferenceTooLong::ID: { auto difference = move_tl_object_as(difference_ptr); telegram_api::dialog *dialog = nullptr; switch (difference->dialog_->get_id()) { case telegram_api::dialog::ID: dialog = static_cast(difference->dialog_.get()); break; case telegram_api::dialogFolder::ID: return after_get_channel_difference(dialog_id, false); default: UNREACHABLE(); return; } CHECK(dialog != nullptr); if ((dialog->flags_ & telegram_api::dialog::PTS_MASK) == 0) { LOG(ERROR) << "Receive " << dialog_id << " without PTS"; return after_get_channel_difference(dialog_id, false); } int32 flags = difference->flags_; is_final = (flags & CHANNEL_DIFFERENCE_FLAG_IS_FINAL) != 0; if (flags & CHANNEL_DIFFERENCE_FLAG_HAS_TIMEOUT) { timeout = difference->timeout_; } auto new_pts = dialog->pts_; if (request_pts > new_pts - request_limit) { LOG(ERROR) << "Receive channelDifferenceTooLong as result of getChannelDifference from " << source << " with PTS = " << request_pts << " and limit = " << request_limit << " in " << dialog_id << ", but PTS has changed from " << d->pts << " to " << new_pts << ". Difference: " << oneline(to_string(difference)); if (request_pts >= new_pts) { new_pts = request_pts + 1; } } set_dialog_folder_id(d, FolderId(dialog->folder_id_)); set_dialog_view_as_messages(d, dialog->view_forum_as_messages_); on_update_dialog_notify_settings(dialog_id, std::move(dialog->notify_settings_), "updates.channelDifferenceTooLong"); bool is_marked_as_unread = dialog->unread_mark_; if (is_marked_as_unread != d->is_marked_as_unread) { set_dialog_is_marked_as_unread(d, is_marked_as_unread); } update_dialog_draft_message(d, get_draft_message(td_, std::move(dialog->draft_)), true, false); on_get_channel_dialog(dialog_id, MessageId(ServerMessageId(dialog->top_message_)), MessageId(ServerMessageId(dialog->read_inbox_max_id_)), dialog->unread_count_, dialog->unread_mentions_count_, dialog->unread_reactions_count_, MessageId(ServerMessageId(dialog->read_outbox_max_id_)), std::move(difference->messages_)); update_dialog_pos(d, "updates.channelDifferenceTooLong"); if (!td_->auth_manager_->is_bot()) { // set is_pinned only after updating chat position to ensure that order is initialized bool is_pinned = (dialog->flags_ & DIALOG_FLAG_IS_PINNED) != 0; bool was_pinned = is_dialog_pinned(DialogListId(d->folder_id), dialog_id); if (is_pinned != was_pinned) { set_dialog_is_pinned(DialogListId(d->folder_id), d, is_pinned); } } set_channel_pts(d, new_pts, "channel difference too long"); break; } default: UNREACHABLE(); } if (need_update_dialog_pos) { update_dialog_pos(d, "on_get_channel_difference"); } if (!is_final) { LOG_IF(ERROR, timeout > 0) << "Have timeout in non-final ChannelDifference in " << dialog_id; get_channel_difference(dialog_id, d->pts, 0, MessageId(), true, "on_get_channel_difference", is_old); return; } LOG_IF(ERROR, timeout == 0) << "Have no timeout in final ChannelDifference in " << dialog_id; if (timeout > 0 && d->open_count > 0) { channel_get_difference_timeout_.add_timeout_in(dialog_id.get(), timeout); } after_get_channel_difference(dialog_id, true); } void MessagesManager::after_get_channel_difference(DialogId dialog_id, bool success) { LOG(INFO) << "After " << (success ? "" : "un") << "successful get channel difference in " << dialog_id; LOG_CHECK(!running_get_channel_difference(dialog_id)) << '"' << active_get_channel_differences_[dialog_id] << '"'; auto log_event_it = get_channel_difference_to_log_event_id_.find(dialog_id); if (log_event_it != get_channel_difference_to_log_event_id_.end()) { if (!G()->close_flag()) { binlog_erase(G()->td_db()->get_binlog(), log_event_it->second); } get_channel_difference_to_log_event_id_.erase(log_event_it); } auto d = get_dialog(dialog_id); bool have_access = have_input_peer(dialog_id, AccessRights::Read); auto pts = d != nullptr ? d->pts : load_channel_pts(dialog_id); auto postponed_updates_it = postponed_channel_updates_.find(dialog_id); if (postponed_updates_it != postponed_channel_updates_.end()) { auto &updates = postponed_updates_it->second; LOG(INFO) << "Begin to apply " << updates.size() << " postponed channel updates"; while (!updates.empty()) { auto it = updates.begin(); auto update = std::move(it->second.update); auto update_pts = it->second.pts; auto update_pts_count = it->second.pts_count; auto promise = std::move(it->second.promise); updates.erase(it); auto old_size = updates.size(); auto update_id = update->get_id(); if (have_access) { add_pending_channel_update(dialog_id, std::move(update), update_pts, update_pts_count, std::move(promise), "apply postponed channel updates", true); } else { promise.set_value(Unit()); } if (updates.size() != old_size || running_get_channel_difference(dialog_id)) { if (success && update_pts - 10000 < pts && update_pts_count == 1) { // if getChannelDifference was successful and update PTS is near channel PTS, // we hope that the update eventually can be applied LOG(INFO) << "Can't apply postponed channel updates"; } else { // otherwise protect from getChannelDifference repeating calls by dropping postponed updates LOG(WARNING) << "Failed to apply postponed updates of type " << update_id << " in " << dialog_id << " with PTS " << pts << ", update PTS is " << update_pts << ", update PTS count is " << update_pts_count; vector> update_promises; for (auto &postponed_update : updates) { update_promises.push_back(std::move(postponed_update.second.promise)); } updates.clear(); for (auto &update_promise : update_promises) { update_promise.set_value(Unit()); } } break; } } if (updates.empty()) { postponed_channel_updates_.erase(postponed_updates_it); } LOG(INFO) << "Finish to apply postponed channel updates"; } if (d != nullptr) { d->is_channel_difference_finished = true; if (d->notification_info != nullptr) { if (d->notification_info->message_notification_group_.is_valid()) { send_closure_later(G()->notification_manager(), &NotificationManager::after_get_chat_difference, d->notification_info->message_notification_group_.get_group_id()); } if (d->notification_info->mention_notification_group_.is_valid()) { send_closure_later(G()->notification_manager(), &NotificationManager::after_get_chat_difference, d->notification_info->mention_notification_group_.get_group_id()); } } } else { is_channel_difference_finished_.insert(dialog_id); } if (postponed_chat_read_inbox_updates_.erase(dialog_id) > 0) { send_update_chat_read_inbox(d, true, "after_get_channel_difference"); } auto promise_it = run_after_get_channel_difference_.find(dialog_id); if (promise_it != run_after_get_channel_difference_.end()) { auto promises = std::move(promise_it->second); run_after_get_channel_difference_.erase(promise_it); set_promises(promises); } auto on_get_dialogs_it = pending_channel_on_get_dialogs_.find(dialog_id); if (on_get_dialogs_it != pending_channel_on_get_dialogs_.end()) { LOG(INFO) << "Apply postponed results of channel getDialogs for " << dialog_id; PendingOnGetDialogs res = std::move(on_get_dialogs_it->second); pending_channel_on_get_dialogs_.erase(on_get_dialogs_it); on_get_dialogs(res.folder_id, std::move(res.dialogs), res.total_count, std::move(res.messages), std::move(res.promise)); } if (d != nullptr && !td_->auth_manager_->is_bot() && have_access && !d->last_message_id.is_valid() && !d->is_empty && (d->order != DEFAULT_ORDER || is_dialog_sponsored(d))) { load_last_dialog_message(d, "after_get_channel_difference"); } auto expected_channel_pts_it = expected_channel_pts_.find(dialog_id); if (expected_channel_pts_it != expected_channel_pts_.end()) { if (success && d != nullptr && expected_channel_pts_it->second > pts) { schedule_get_channel_difference(dialog_id, 0, MessageId(), 1.0, "after_get_channel_difference"); } expected_channel_pts_.erase(expected_channel_pts_it); } auto expected_channel_max_message_id_it = expected_channel_max_message_id_.find(dialog_id); if (expected_channel_max_message_id_it != expected_channel_max_message_id_.end()) { if (success && d != nullptr && expected_channel_max_message_id_it->second > d->last_new_message_id) { schedule_get_channel_difference(dialog_id, 0, MessageId(), 1.0, "after_get_channel_difference 2"); } expected_channel_max_message_id_.erase(expected_channel_max_message_id_it); } } void MessagesManager::reget_message_from_server_if_needed(DialogId dialog_id, const Message *m) { if (!m->message_id.is_any_server() || dialog_id.get_type() == DialogType::SecretChat) { return; } if (need_reget_message_content(m->content.get()) || (m->legacy_layer != 0 && m->legacy_layer < MTPROTO_LAYER) || m->reply_info.need_reget(td_) || m->replied_message_info.need_reget()) { MessageFullId message_full_id{dialog_id, m->message_id}; LOG(INFO) << "Reget from server " << message_full_id; get_message_from_server(message_full_id, Auto(), "reget_message_from_server_if_needed"); } } void MessagesManager::speculatively_update_active_group_call_id(Dialog *d, const Message *m) { CHECK(m != nullptr); if (!m->message_id.is_any_server() || m->content->get_type() != MessageContentType::GroupCall) { return; } InputGroupCallId input_group_call_id; bool is_ended; std::tie(input_group_call_id, is_ended) = get_message_content_group_call_info(m->content.get()); d->has_expected_active_group_call_id = true; if (is_ended) { d->expected_active_group_call_id = InputGroupCallId(); if (d->active_group_call_id == input_group_call_id) { on_update_dialog_group_call_id(d->dialog_id, InputGroupCallId()); } } else { d->expected_active_group_call_id = input_group_call_id; if (d->active_group_call_id != input_group_call_id && !td_->auth_manager_->is_bot()) { repair_dialog_active_group_call_id(d->dialog_id); } } } void MessagesManager::speculatively_update_channel_participants(DialogId dialog_id, const Message *m) { CHECK(m != nullptr); if (!m->message_id.is_any_server() || dialog_id.get_type() != DialogType::Channel || !m->sender_user_id.is_valid()) { return; } auto channel_id = dialog_id.get_channel_id(); UserId my_user_id(td_->contacts_manager_->get_my_id()); bool by_me = m->sender_user_id == my_user_id; switch (m->content->get_type()) { case MessageContentType::ChatAddUsers: send_closure_later(G()->contacts_manager(), &ContactsManager::speculative_add_channel_participants, channel_id, get_message_content_added_user_ids(m->content.get()), m->sender_user_id, m->date, by_me); break; case MessageContentType::ChatJoinedByLink: send_closure_later(G()->contacts_manager(), &ContactsManager::speculative_add_channel_participants, channel_id, vector{m->sender_user_id}, m->sender_user_id, m->date, by_me); break; case MessageContentType::ChatDeleteUser: send_closure_later(G()->contacts_manager(), &ContactsManager::speculative_delete_channel_participant, channel_id, get_message_content_deleted_user_id(m->content.get()), by_me); break; default: break; } } void MessagesManager::update_sent_message_contents(DialogId dialog_id, const Message *m) { CHECK(m != nullptr); if (td_->auth_manager_->is_bot() || (!m->is_outgoing && dialog_id != get_my_dialog_id()) || dialog_id.get_type() == DialogType::SecretChat || m->message_id.is_local() || m->forward_info != nullptr || m->had_forward_info) { return; } on_sent_message_content(td_, m->content.get()); } void MessagesManager::update_used_hashtags(DialogId dialog_id, const Message *m) { CHECK(m != nullptr); if (td_->auth_manager_->is_bot() || (!m->is_outgoing && dialog_id != get_my_dialog_id()) || m->via_bot_user_id.is_valid() || m->hide_via_bot || m->forward_info != nullptr || m->had_forward_info) { return; } ::td::update_used_hashtags(td_, m->content.get()); } void MessagesManager::update_top_dialogs(DialogId dialog_id, const Message *m) { CHECK(m != nullptr); auto dialog_type = dialog_id.get_type(); if (td_->auth_manager_->is_bot() || (!m->is_outgoing && dialog_id != get_my_dialog_id()) || dialog_type == DialogType::SecretChat || !m->message_id.is_any_server()) { return; } bool is_forward = m->forward_info != nullptr || m->had_forward_info; if (m->via_bot_user_id.is_valid() && !is_forward) { // forwarded game messages can't be distinguished from sent via bot game messages, so increase rating anyway on_dialog_used(TopDialogCategory::BotInline, DialogId(m->via_bot_user_id), m->date); } if (is_forward) { auto &last_forward_date = last_outgoing_forwarded_message_date_[dialog_id]; if (last_forward_date < m->date) { TopDialogCategory category = dialog_type == DialogType::User ? TopDialogCategory::ForwardUsers : TopDialogCategory::ForwardChats; on_dialog_used(category, dialog_id, m->date); last_forward_date = m->date; } } TopDialogCategory category = TopDialogCategory::Size; switch (dialog_type) { case DialogType::User: { if (td_->contacts_manager_->is_user_bot(dialog_id.get_user_id())) { category = TopDialogCategory::BotPM; } else { category = TopDialogCategory::Correspondent; } break; } case DialogType::Chat: category = TopDialogCategory::Group; break; case DialogType::Channel: switch (td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id())) { case ChannelType::Broadcast: category = TopDialogCategory::Channel; break; case ChannelType::Megagroup: category = TopDialogCategory::Group; break; case ChannelType::Unknown: break; default: UNREACHABLE(); break; } break; case DialogType::SecretChat: case DialogType::None: default: UNREACHABLE(); } if (category != TopDialogCategory::Size) { on_dialog_used(category, dialog_id, m->date); } } void MessagesManager::update_forward_count(DialogId dialog_id, const Message *m) { if (td_->auth_manager_->is_bot() || m->forward_info == nullptr) { return; } auto origin_message_full_id = m->forward_info->origin.get_message_full_id(); if (!origin_message_full_id.get_message_id().is_valid()) { return; } if (is_discussion_message(dialog_id, m) && origin_message_full_id == MessageFullId(m->forward_info->from_dialog_id, m->forward_info->from_message_id)) { return; } update_forward_count(origin_message_full_id, m->date); } void MessagesManager::update_forward_count(MessageFullId message_full_id, int32 update_date) { CHECK(!td_->auth_manager_->is_bot()); auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); Message *m = get_message_force(d, message_full_id.get_message_id(), "update_forward_count"); if (m != nullptr && !m->message_id.is_scheduled() && m->message_id.is_server() && m->view_count > 0 && m->interaction_info_update_date < update_date) { if (m->forward_count == 0) { m->forward_count++; send_update_message_interaction_info(dialog_id, m); on_message_changed(d, m, true, "update_forward_count"); } if (pending_message_views_[dialog_id].message_ids_.insert(m->message_id).second) { pending_message_views_timeout_.add_timeout_in(dialog_id.get(), 0.0); } } } void MessagesManager::update_has_outgoing_messages(DialogId dialog_id, const Message *m) { CHECK(m != nullptr); if (td_->auth_manager_->is_bot() || (!m->is_outgoing && dialog_id != get_my_dialog_id())) { return; } Dialog *d = nullptr; switch (dialog_id.get_type()) { case DialogType::User: d = get_dialog(dialog_id); break; case DialogType::Chat: case DialogType::Channel: break; case DialogType::SecretChat: { auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (user_id.is_valid()) { d = get_dialog_force(DialogId(user_id), "update_has_outgoing_messages"); } break; } default: UNREACHABLE(); } if (d == nullptr || d->has_outgoing_messages) { return; } d->has_outgoing_messages = true; on_dialog_updated(dialog_id, "update_has_outgoing_messages"); if (d->action_bar != nullptr && d->action_bar->on_outgoing_message()) { send_update_chat_action_bar(d); } } void MessagesManager::set_message_reply(const Dialog *d, Message *m, MessageInputReplyTo input_reply_to, bool is_message_in_dialog) { LOG(INFO) << "Update replied message of " << MessageFullId{d->dialog_id, m->message_id} << " from " << m->replied_message_info << " to " << input_reply_to; if (is_message_in_dialog) { unregister_message_reply(d->dialog_id, m); } m->replied_message_info = RepliedMessageInfo(td_, input_reply_to); m->reply_to_story_full_id = StoryFullId(); m->reply_to_random_id = get_message_reply_to_random_id(d, m); if (!m->message_id.is_any_server()) { m->input_reply_to = std::move(input_reply_to); } if (is_message_in_dialog) { register_message_reply(d->dialog_id, m); } update_message_max_reply_media_timestamp(d, m, is_message_in_dialog); } void MessagesManager::update_message_reply_to_message_id(const Dialog *d, Message *m, MessageId reply_to_message_id, bool is_message_in_dialog) { LOG(INFO) << "Update identifier of replied message of " << MessageFullId{d->dialog_id, m->message_id} << " from " << m->replied_message_info << " to " << reply_to_message_id; if (is_message_in_dialog) { unregister_message_reply(d->dialog_id, m); } m->replied_message_info.set_message_id(reply_to_message_id); if (!m->message_id.is_any_server()) { m->input_reply_to.set_message_id(reply_to_message_id); } if (is_message_in_dialog) { register_message_reply(d->dialog_id, m); } } void MessagesManager::restore_message_reply_to_message_id(Dialog *d, Message *m) { const auto *input_reply_to = get_message_input_reply_to(m); CHECK(input_reply_to != nullptr); if (input_reply_to->is_empty()) { return; } auto replied_message_full_id = input_reply_to->get_reply_message_full_id(d->dialog_id); auto replied_message_id = replied_message_full_id.get_message_id(); if (replied_message_id == MessageId() || !replied_message_id.is_yet_unsent()) { return; } CHECK(replied_message_full_id.get_dialog_id() == d->dialog_id); auto message_id = get_message_id_by_random_id(d, m->reply_to_random_id, "restore_message_reply_to_message_id"); if (message_id.is_valid() || message_id.is_valid_scheduled()) { update_message_reply_to_message_id(d, m, message_id, false); } else { set_message_reply(d, m, MessageInputReplyTo{m->top_thread_message_id, DialogId(), FormattedText(), 0}, false); } } MessagesManager::Message *MessagesManager::continue_send_message(DialogId dialog_id, unique_ptr &&message, bool *need_update_dialog_pos, uint64 log_event_id) { CHECK(log_event_id != 0); CHECK(message != nullptr); CHECK(message->content != nullptr); Dialog *d = get_dialog_force(dialog_id, "continue_send_message"); if (d == nullptr) { LOG(ERROR) << "Can't find " << dialog_id << " to continue send a message"; binlog_erase(G()->td_db()->get_binlog(), log_event_id); return nullptr; } if (!have_input_peer(dialog_id, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), log_event_id); return nullptr; } LOG(INFO) << "Continue to send " << message->message_id << " to " << dialog_id << " initially sent at " << message->send_date << " from binlog"; d->was_opened = true; auto now = G()->unix_time(); if (message->message_id.is_scheduled()) { message->message_id = get_next_yet_unsent_scheduled_message_id(d, message->date); } else { message->message_id = get_next_yet_unsent_message_id(d); message->date = now; } restore_message_reply_to_message_id(d, message.get()); bool need_update = false; auto result_message = add_message_to_dialog(d, std::move(message), false, true, &need_update, need_update_dialog_pos, "continue_send_message"); CHECK(result_message != nullptr); if (result_message->message_id.is_scheduled()) { send_update_chat_has_scheduled_messages(d, false); } auto can_send_status = can_send_message(dialog_id); if (can_send_status.is_ok() && result_message->send_date < now - MAX_RESEND_DELAY && dialog_id != get_my_dialog_id()) { can_send_status = Status::Error(400, "Message is too old to be re-sent automatically"); } if (can_send_status.is_error()) { LOG(INFO) << "Can't continue to send a message to " << dialog_id << ": " << can_send_status; send_update_new_message(d, result_message); if (*need_update_dialog_pos) { send_update_chat_last_message(d, "continue_send_message"); } fail_send_message({dialog_id, result_message->message_id}, std::move(can_send_status)); return nullptr; } return result_message; } void MessagesManager::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::SendMessage: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } SendMessageLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id; auto message = std::move(log_event.message_out); message->send_message_log_event_id = event.id_; if (message->content->get_type() == MessageContentType::Unsupported) { LOG(ERROR) << "Message content is invalid: " << format::as_hex_dump<4>(event.get_data()); binlog_erase(G()->td_db()->get_binlog(), event.id_); continue; } Dependencies dependencies; dependencies.add_dialog_dependencies(dialog_id); add_message_dependencies(dependencies, message.get()); dependencies.resolve_force(td_, "SendMessageLogEvent"); message->content = dup_message_content(td_, dialog_id, message->content.get(), MessageContentDupType::Send, MessageCopyOptions()); bool need_update_dialog_pos = false; auto result_message = continue_send_message(dialog_id, std::move(message), &need_update_dialog_pos, event.id_); if (result_message != nullptr) { // uses send_closure_later internally do_send_message(dialog_id, result_message); Dialog *d = get_dialog(dialog_id); send_update_new_message(d, result_message); if (need_update_dialog_pos) { send_update_chat_last_message(d, "SendMessageLogEvent"); } } break; } case LogEvent::HandlerType::SendBotStartMessage: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } SendBotStartMessageLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id; auto message = std::move(log_event.message_out); message->send_message_log_event_id = event.id_; CHECK(message->content->get_type() == MessageContentType::Text); auto bot_user_id = log_event.bot_user_id; Dependencies dependencies; dependencies.add(bot_user_id); dependencies.add_dialog_and_dependencies(dialog_id); add_message_dependencies(dependencies, message.get()); if (!dependencies.resolve_force(td_, "SendBotStartMessageLogEvent")) { binlog_erase(G()->td_db()->get_binlog(), event.id_); continue; } bool need_update_dialog_pos = false; auto result_message = continue_send_message(dialog_id, std::move(message), &need_update_dialog_pos, event.id_); if (result_message != nullptr) { send_closure_later(actor_id(this), &MessagesManager::do_send_bot_start_message, bot_user_id, dialog_id, result_message->message_id, log_event.parameter); Dialog *d = get_dialog(dialog_id); send_update_new_message(d, result_message); if (need_update_dialog_pos) { send_update_chat_last_message(d, "SendBotStartMessageLogEvent"); } } break; } case LogEvent::HandlerType::SendInlineQueryResultMessage: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } SendInlineQueryResultMessageLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id; auto message = std::move(log_event.message_out); message->send_message_log_event_id = event.id_; if (message->content->get_type() == MessageContentType::Unsupported) { LOG(ERROR) << "Message content is invalid: " << format::as_hex_dump<4>(event.get_data()); binlog_erase(G()->td_db()->get_binlog(), event.id_); continue; } Dependencies dependencies; dependencies.add_dialog_dependencies(dialog_id); add_message_dependencies(dependencies, message.get()); dependencies.resolve_force(td_, "SendInlineQueryResultMessageLogEvent"); message->content = dup_message_content(td_, dialog_id, message->content.get(), MessageContentDupType::SendViaBot, MessageCopyOptions()); bool need_update_dialog_pos = false; auto result_message = continue_send_message(dialog_id, std::move(message), &need_update_dialog_pos, event.id_); if (result_message != nullptr) { send_closure_later(actor_id(this), &MessagesManager::do_send_inline_query_result_message, dialog_id, result_message->message_id, log_event.query_id, log_event.result_id); Dialog *d = get_dialog(dialog_id); send_update_new_message(d, result_message); if (need_update_dialog_pos) { send_update_chat_last_message(d, "SendInlineQueryResultMessageLogEvent"); } } break; } case LogEvent::HandlerType::SendScreenshotTakenNotificationMessage: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } SendScreenshotTakenNotificationMessageLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id; auto message = std::move(log_event.message_out); message->send_message_log_event_id = 0; // to not allow event deletion by message deletion CHECK(message->content->get_type() == MessageContentType::ScreenshotTaken); Dependencies dependencies; dependencies.add_dialog_dependencies(dialog_id); add_message_dependencies(dependencies, message.get()); dependencies.resolve_force(td_, "SendScreenshotTakenNotificationMessageLogEvent"); bool need_update_dialog_pos = false; auto result_message = continue_send_message(dialog_id, std::move(message), &need_update_dialog_pos, event.id_); if (result_message != nullptr) { // order with other messages isn't kept do_send_screenshot_taken_notification_message(dialog_id, result_message, event.id_); Dialog *d = get_dialog(dialog_id); send_update_new_message(d, result_message); if (need_update_dialog_pos) { send_update_chat_last_message(d, "SendScreenshotTakenNotificationMessageLogEvent"); } } break; } case LogEvent::HandlerType::ForwardMessages: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); continue; } ForwardMessagesLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto to_dialog_id = log_event.to_dialog_id; auto from_dialog_id = log_event.from_dialog_id; auto messages = std::move(log_event.messages_out); Dependencies dependencies; dependencies.add_dialog_and_dependencies(to_dialog_id); dependencies.add_dialog_and_dependencies(from_dialog_id); for (auto &message : messages) { add_message_dependencies(dependencies, message.get()); } if (!dependencies.resolve_force(td_, "ForwardMessagesLogEvent")) { binlog_erase(G()->td_db()->get_binlog(), event.id_); continue; } Dialog *to_dialog = get_dialog(to_dialog_id); CHECK(to_dialog != nullptr); to_dialog->was_opened = true; auto now = G()->unix_time(); if (!have_input_peer(from_dialog_id, AccessRights::Read) || can_send_message(to_dialog_id).is_error() || messages.empty() || (messages[0]->send_date < now - MAX_RESEND_DELAY && to_dialog_id != get_my_dialog_id())) { LOG(WARNING) << "Can't continue forwarding " << messages.size() << " message(s) to " << to_dialog_id; binlog_erase(G()->td_db()->get_binlog(), event.id_); continue; } LOG(INFO) << "Continue to forward " << messages.size() << " message(s) to " << to_dialog_id << " from binlog"; bool need_update = false; bool need_update_dialog_pos = false; vector forwarded_messages; for (auto &message : messages) { if (message->message_id.is_scheduled()) { message->message_id = get_next_yet_unsent_scheduled_message_id(to_dialog, message->date); } else { message->message_id = get_next_yet_unsent_message_id(to_dialog); message->date = now; } message->content = dup_message_content(td_, to_dialog_id, message->content.get(), MessageContentDupType::Forward, MessageCopyOptions()); CHECK(message->content != nullptr); restore_message_reply_to_message_id(to_dialog, message.get()); forwarded_messages.push_back(add_message_to_dialog(to_dialog, std::move(message), false, true, &need_update, &need_update_dialog_pos, "forward message again")); send_update_new_message(to_dialog, forwarded_messages.back()); } send_update_chat_has_scheduled_messages(to_dialog, false); if (need_update_dialog_pos) { send_update_chat_last_message(to_dialog, "on_reforward_message"); } do_forward_messages(to_dialog_id, from_dialog_id, forwarded_messages, log_event.message_ids, log_event.drop_author, log_event.drop_media_captions, event.id_); break; } case LogEvent::HandlerType::DeleteMessage: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } DeleteMessageLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); log_event.id_ = event.id_; Dialog *d = get_dialog_force(log_event.message_full_id_.get_dialog_id(), "DeleteMessageLogEvent"); if (d != nullptr) { auto message_id = log_event.message_full_id_.get_message_id(); if (message_id.is_valid_scheduled() && message_id.is_scheduled_server()) { add_dialog_scheduled_messages(d)->deleted_scheduled_server_message_ids_.insert( message_id.get_scheduled_server_message_id()); } else if (message_id != MessageId()) { d->deleted_message_ids.insert(message_id); } } do_delete_message_log_event(log_event); 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_; Dialog *d = get_dialog_force(dialog_id, "DeleteMessagesOnServerLogEvent"); if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } for (auto message_id : log_event.message_ids_) { CHECK(message_id.is_valid()); d->deleted_message_ids.insert(message_id); } 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_; Dialog *d = get_dialog_force(dialog_id, "DeleteScheduledMessagesOnServerLogEvent"); if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } for (auto message_id : log_event.message_ids_) { CHECK(message_id.is_scheduled_server()); add_dialog_scheduled_messages(d)->deleted_scheduled_server_message_ids_.insert( message_id.get_scheduled_server_message_id()); } delete_scheduled_messages_on_server(dialog_id, std::move(log_event.message_ids_), 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_; Dialog *d = get_dialog_force(dialog_id, "DeleteDialogHistoryOnServerLogEvent"); if (d == nullptr || !have_input_peer(dialog_id, 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::DeleteTopicHistoryOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } DeleteTopicHistoryOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "DeleteTopicHistoryOnServerLogEvent"); if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } delete_topic_history_on_server(dialog_id, log_event.top_thread_message_id_, 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::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::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") || !have_input_peer(sender_dialog_id, 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::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_; Dialog *d = get_dialog_force(dialog_id, "DeleteDialogMessagesByDateOnServerLogEvent"); if (d == nullptr || !have_input_peer(dialog_id, 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::ReadHistoryOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } ReadHistoryOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "ReadHistoryOnServerLogEvent"); if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } auto &log_event_id = read_history_log_event_ids_[dialog_id][0]; if (log_event_id.log_event_id != 0) { // we need only the latest read history event binlog_erase(G()->td_db()->get_binlog(), log_event_id.log_event_id); } log_event_id.log_event_id = event.id_; read_history_on_server_impl(d, log_event.max_message_id_); break; } case LogEvent::HandlerType::ReadHistoryInSecretChat: { ReadHistoryInSecretChatLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; CHECK(dialog_id.get_type() == DialogType::SecretChat); Dependencies dependencies; dependencies.add_dialog_and_dependencies(dialog_id); if (!dependencies.resolve_force(td_, "ReadHistoryInSecretChat") || !have_input_peer(dialog_id, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } auto &log_event_id = read_history_log_event_ids_[dialog_id][0]; if (log_event_id.log_event_id != 0) { // we need only the latest read history event binlog_erase(G()->td_db()->get_binlog(), log_event_id.log_event_id); } log_event_id.log_event_id = event.id_; Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); d->last_read_inbox_message_date = log_event.max_date_; read_history_on_server_impl(d, MessageId()); break; } case LogEvent::HandlerType::ReadMessageThreadHistoryOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } ReadMessageThreadHistoryOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "ReadMessageThreadHistoryOnServerLogEvent"); if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } auto top_thread_message_id = log_event.top_thread_message_id_; auto &log_event_id = read_history_log_event_ids_[dialog_id][top_thread_message_id.get()]; if (log_event_id.log_event_id != 0) { // we need only the latest read history event binlog_erase(G()->td_db()->get_binlog(), log_event_id.log_event_id); } log_event_id.log_event_id = event.id_; read_message_thread_history_on_server_impl(d, top_thread_message_id, log_event.max_message_id_); 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_; Dialog *d = get_dialog_force(dialog_id, "ReadMessageContentsOnServerLogEvent"); if (d == nullptr || !have_input_peer(dialog_id, 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::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_; Dialog *d = get_dialog_force(dialog_id, "ReadAllDialogMentionsOnServerLogEvent"); if (d == nullptr || !have_input_peer(dialog_id, 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_; Dialog *d = get_dialog_force(dialog_id, "ReadAllDialogReactionsOnServerLogEvent"); if (d == nullptr || !have_input_peer(dialog_id, 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::ToggleDialogIsPinnedOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } ToggleDialogIsPinnedOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "ToggleDialogIsPinnedOnServerLogEvent"); if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } toggle_dialog_is_pinned_on_server(dialog_id, log_event.is_pinned_, event.id_); break; } case LogEvent::HandlerType::ReorderPinnedDialogsOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } ReorderPinnedDialogsOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); vector dialog_ids; for (auto &dialog_id : log_event.dialog_ids_) { Dialog *d = get_dialog_force(dialog_id, "ReorderPinnedDialogsOnServerLogEvent"); if (d != nullptr && have_input_peer(dialog_id, AccessRights::Read)) { dialog_ids.push_back(dialog_id); } } if (dialog_ids.empty()) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } reorder_pinned_dialogs_on_server(log_event.folder_id_, dialog_ids, event.id_); break; } case LogEvent::HandlerType::ToggleDialogViewAsMessagesOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } ToggleDialogViewAsMessagesOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; if (!have_dialog_force(dialog_id, "ToggleDialogViewAsMessagesOnServerLogEvent") || !have_input_peer(dialog_id, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } toggle_dialog_view_as_messages_on_server(dialog_id, log_event.view_as_messages_, event.id_); break; } case LogEvent::HandlerType::ToggleDialogIsTranslatableOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } ToggleDialogIsTranslatableOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; if (!have_dialog_force(dialog_id, "ToggleDialogIsTranslatableOnServerLogEvent") || !have_input_peer(dialog_id, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } toggle_dialog_is_translatable_on_server(dialog_id, log_event.is_translatable_, event.id_); break; } case LogEvent::HandlerType::ToggleDialogIsMarkedAsUnreadOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } ToggleDialogIsMarkedAsUnreadOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; if (!have_dialog_force(dialog_id, "ToggleDialogIsMarkedAsUnreadOnServerLogEvent") || !have_input_peer(dialog_id, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } toggle_dialog_is_marked_as_unread_on_server(dialog_id, log_event.is_marked_as_unread_, event.id_); break; } case LogEvent::HandlerType::ToggleDialogIsBlockedOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } ToggleDialogIsBlockedOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; if (dialog_id.get_type() == DialogType::SecretChat || !have_dialog_info_force(dialog_id, "ToggleDialogIsBlockedOnServerLogEvent") || !have_input_peer(dialog_id, AccessRights::Know)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } toggle_dialog_is_blocked_on_server(dialog_id, log_event.is_blocked_, log_event.is_blocked_for_stories_, event.id_); break; } case LogEvent::HandlerType::SaveDialogDraftMessageOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } SaveDialogDraftMessageOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "SaveDialogDraftMessageOnServerLogEvent"); if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Write)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } d->save_draft_message_log_event_id.log_event_id = event.id_; save_dialog_draft_message_on_server(dialog_id); break; } case LogEvent::HandlerType::UpdateDialogNotificationSettingsOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } UpdateDialogNotificationSettingsOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "UpdateDialogNotificationSettingsOnServerLogEvent"); if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } d->save_notification_settings_log_event_id.log_event_id = event.id_; update_dialog_notification_settings_on_server(dialog_id, true); break; } case LogEvent::HandlerType::ResetAllNotificationSettingsOnServer: { ResetAllNotificationSettingsOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); reset_all_notification_settings_on_server(event.id_); break; } case LogEvent::HandlerType::ToggleDialogReportSpamStateOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } ToggleDialogReportSpamStateOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "ToggleDialogReportSpamStateOnServerLogEvent"); if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } toggle_dialog_report_spam_state_on_server(dialog_id, log_event.is_spam_dialog_, event.id_, Promise()); break; } case LogEvent::HandlerType::SetDialogFolderIdOnServer: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } SetDialogFolderIdOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "SetDialogFolderIdOnServerLogEvent"); if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } d->set_folder_id_log_event_id.log_event_id = event.id_; set_dialog_folder_id(d, log_event.folder_id_); set_dialog_folder_id_on_server(dialog_id, true); break; } case LogEvent::HandlerType::RegetDialog: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } RegetDialogLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; Dependencies dependencies; dependencies.add_dialog_and_dependencies(dialog_id); dependencies.resolve_force(td_, "RegetDialogLogEvent", true); // dialog itself may not exist if (!have_input_peer(dialog_id, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } send_get_dialog_query(dialog_id, Auto(), event.id_, "RegetDialogLogEvent"); 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; } case LogEvent::HandlerType::GetChannelDifference: { if (td_->ignore_background_updates()) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } GetChannelDifferenceLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); DialogId dialog_id(log_event.channel_id); if (dialog_id.get_type() != DialogType::Channel) { LOG(ERROR) << "Trying to run GetChannelDifference in " << dialog_id; binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } LOG(INFO) << "Continue to run getChannelDifference in " << dialog_id; get_channel_difference_to_log_event_id_.emplace(dialog_id, event.id_); do_get_channel_difference( dialog_id, load_channel_pts(dialog_id), true, telegram_api::make_object(log_event.channel_id.get(), log_event.access_hash), false, "LogEvent::HandlerType::GetChannelDifference"); break; } default: LOG(FATAL) << "Unsupported log event type " << event.type_; } } } Status MessagesManager::add_recently_found_dialog(DialogId dialog_id) { if (!have_dialog_force(dialog_id, "add_recently_found_dialog")) { return Status::Error(400, "Chat not found"); } recently_found_dialogs_.add_dialog(dialog_id); return Status::OK(); } Status MessagesManager::remove_recently_found_dialog(DialogId dialog_id) { if (!have_dialog_force(dialog_id, "remove_recently_found_dialog")) { return Status::Error(400, "Chat not found"); } recently_found_dialogs_.remove_dialog(dialog_id); return Status::OK(); } void MessagesManager::clear_recently_found_dialogs() { recently_found_dialogs_.clear_dialogs(); } void MessagesManager::suffix_load_loop(const Dialog *d, SuffixLoadQueries *queries) { CHECK(queries != nullptr); if (queries->suffix_load_has_query_) { return; } if (queries->suffix_load_queries_.empty()) { return; } CHECK(!queries->suffix_load_done_); CHECK(d != nullptr); auto dialog_id = d->dialog_id; auto from_message_id = queries->suffix_load_first_message_id_; LOG(INFO) << "Send suffix load query in " << dialog_id << " from " << from_message_id; auto promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](Result result) { send_closure(actor_id, &MessagesManager::suffix_load_query_ready, dialog_id); }); queries->suffix_load_has_query_ = true; queries->suffix_load_query_message_id_ = from_message_id; get_history_impl(d, from_message_id, -1, MAX_GET_HISTORY, true, true, std::move(promise), "suffix_load_loop"); } void MessagesManager::suffix_load_update_first_message_id(const Dialog *d, SuffixLoadQueries *queries) { CHECK(d != nullptr); CHECK(queries != nullptr); if (!queries->suffix_load_first_message_id_.is_valid()) { if (!d->last_message_id.is_valid()) { return; } queries->suffix_load_first_message_id_ = d->last_message_id; } auto it = d->ordered_messages.get_const_iterator(queries->suffix_load_first_message_id_); CHECK(*it != nullptr); CHECK((*it)->get_message_id() == queries->suffix_load_first_message_id_); while (true) { --it; if (*it == nullptr) { break; } queries->suffix_load_first_message_id_ = (*it)->get_message_id(); } } void MessagesManager::suffix_load_query_ready(DialogId dialog_id) { auto *queries = dialog_suffix_load_queries_[dialog_id].get(); CHECK(queries != nullptr); CHECK(queries->suffix_load_has_query_); LOG(INFO) << "Finished suffix load query in " << dialog_id; auto *d = get_dialog(dialog_id); bool is_unchanged = queries->suffix_load_first_message_id_ == queries->suffix_load_query_message_id_; suffix_load_update_first_message_id(d, queries); if (is_unchanged && queries->suffix_load_first_message_id_ == queries->suffix_load_query_message_id_) { LOG(INFO) << "Finished suffix load in " << dialog_id; queries->suffix_load_done_ = true; } queries->suffix_load_has_query_ = false; // Remove ready queries auto *m = get_message_force(d, queries->suffix_load_first_message_id_, "suffix_load_query_ready"); auto ready_it = std::partition(queries->suffix_load_queries_.begin(), queries->suffix_load_queries_.end(), [&](auto &value) { return !(queries->suffix_load_done_ || value.second(m)); }); for (auto it = ready_it; it != queries->suffix_load_queries_.end(); ++it) { it->first.set_value(Unit()); } queries->suffix_load_queries_.erase(ready_it, queries->suffix_load_queries_.end()); suffix_load_loop(d, queries); } void MessagesManager::suffix_load_add_query(Dialog *d, std::pair, std::function> query) { CHECK(!td_->auth_manager_->is_bot()); auto &queries = dialog_suffix_load_queries_[d->dialog_id]; if (queries == nullptr) { queries = make_unique(); } suffix_load_update_first_message_id(d, queries.get()); auto *m = get_message_force(d, queries->suffix_load_first_message_id_, "suffix_load_add_query"); if (queries->suffix_load_done_ || query.second(m)) { query.first.set_value(Unit()); } else { queries->suffix_load_queries_.emplace_back(std::move(query)); suffix_load_loop(d, queries.get()); } } void MessagesManager::suffix_load_till_date(Dialog *d, int32 date, Promise promise) { LOG(INFO) << "Load suffix of " << d->dialog_id << " till date " << date; auto condition = [date](const Message *m) { return m != nullptr && m->date < date; }; suffix_load_add_query(d, std::make_pair(std::move(promise), std::move(condition))); } void MessagesManager::suffix_load_till_message_id(Dialog *d, MessageId message_id, Promise promise) { LOG(INFO) << "Load suffix of " << d->dialog_id << " till " << message_id; auto condition = [message_id](const Message *m) { return m != nullptr && m->message_id < message_id; }; suffix_load_add_query(d, std::make_pair(std::move(promise), std::move(condition))); } void MessagesManager::set_poll_answer(MessageFullId message_full_id, vector &&option_ids, Promise &&promise) { auto m = get_message_force(message_full_id, "set_poll_answer"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } if (!have_input_peer(message_full_id.get_dialog_id(), AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } if (m->content->get_type() != MessageContentType::Poll) { return promise.set_error(Status::Error(400, "Message is not a poll")); } if (m->message_id.is_scheduled()) { return promise.set_error(Status::Error(400, "Can't answer polls from scheduled messages")); } if (!m->message_id.is_server()) { return promise.set_error(Status::Error(400, "Poll can't be answered")); } set_message_content_poll_answer(td_, m->content.get(), message_full_id, std::move(option_ids), std::move(promise)); } void MessagesManager::get_poll_voters(MessageFullId message_full_id, int32 option_id, int32 offset, int32 limit, Promise> &&promise) { auto m = get_message_force(message_full_id, "get_poll_voters"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } if (!have_input_peer(message_full_id.get_dialog_id(), AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } if (m->content->get_type() != MessageContentType::Poll) { return promise.set_error(Status::Error(400, "Message is not a poll")); } if (m->message_id.is_scheduled()) { return promise.set_error(Status::Error(400, "Can't get poll results from scheduled messages")); } if (!m->message_id.is_server()) { return promise.set_error(Status::Error(400, "Poll results can't be received")); } get_message_content_poll_voters(td_, m->content.get(), message_full_id, option_id, offset, limit, std::move(promise)); } void MessagesManager::stop_poll(MessageFullId message_full_id, td_api::object_ptr &&reply_markup, Promise &&promise) { auto m = get_message_force(message_full_id, "stop_poll"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } if (!have_input_peer(message_full_id.get_dialog_id(), AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } if (m->content->get_type() != MessageContentType::Poll) { return promise.set_error(Status::Error(400, "Message is not a poll")); } if (get_message_content_poll_is_closed(td_, m->content.get())) { return promise.set_error(Status::Error(400, "Poll has already been closed")); } if (!can_edit_message(message_full_id.get_dialog_id(), m, true)) { return promise.set_error(Status::Error(400, "Poll can't be stopped")); } if (m->message_id.is_scheduled()) { return promise.set_error(Status::Error(400, "Can't stop polls from scheduled messages")); } if (!m->message_id.is_server()) { return promise.set_error(Status::Error(400, "Poll can't be stopped")); } auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, has_message_sender_user_id(message_full_id.get_dialog_id(), m)); if (r_new_reply_markup.is_error()) { return promise.set_error(r_new_reply_markup.move_as_error()); } stop_message_content_poll(td_, m->content.get(), message_full_id, r_new_reply_markup.move_as_ok(), std::move(promise)); } Result MessagesManager::get_invoice_message_id(MessageFullId message_full_id) { auto m = get_message_force(message_full_id, "get_invoice_message_id"); if (m == nullptr) { return Status::Error(400, "Message not found"); } if (m->content->get_type() != MessageContentType::Invoice) { return Status::Error(400, "Message has no invoice"); } if (m->message_id.is_scheduled()) { return Status::Error(400, "Wrong scheduled message identifier"); } if (!m->message_id.is_server()) { return Status::Error(400, "Wrong message identifier"); } if (m->reply_markup == nullptr || m->reply_markup->inline_keyboard.empty() || m->reply_markup->inline_keyboard[0].empty() || m->reply_markup->inline_keyboard[0][0].type != InlineKeyboardButton::Type::Buy) { return Status::Error(400, "Message has no Pay button"); } return m->message_id.get_server_message_id(); } Result MessagesManager::get_payment_successful_message_id(MessageFullId message_full_id) { auto m = get_message_force(message_full_id, "get_payment_successful_message_id"); if (m == nullptr) { return Status::Error(400, "Message not found"); } if (m->content->get_type() != MessageContentType::PaymentSuccessful) { return Status::Error(400, "Message has wrong type"); } if (m->message_id.is_scheduled()) { return Status::Error(400, "Wrong scheduled message identifier"); } if (!m->message_id.is_server()) { return Status::Error(400, "Wrong message identifier"); } return m->message_id.get_server_message_id(); } Result MessagesManager::get_giveaway_message_id(MessageFullId message_full_id) { auto m = get_message_force(message_full_id, "get_giveaway_message_id"); if (m == nullptr) { return Status::Error(400, "Message not found"); } switch (m->content->get_type()) { case MessageContentType::Giveaway: case MessageContentType::GiveawayWinners: // ok break; default: return Status::Error(400, "Message has wrong type"); } if (m->message_id.is_scheduled()) { return Status::Error(400, "Wrong scheduled message identifier"); } if (!m->message_id.is_server()) { return Status::Error(400, "Wrong message identifier"); } return m->message_id.get_server_message_id(); } void MessagesManager::remove_sponsored_dialog() { set_sponsored_dialog(DialogId(), DialogSource()); } void MessagesManager::on_get_sponsored_dialog(tl_object_ptr peer, DialogSource source, vector> users, vector> chats) { CHECK(peer != nullptr); td_->contacts_manager_->on_get_users(std::move(users), "on_get_sponsored_dialog"); td_->contacts_manager_->on_get_chats(std::move(chats), "on_get_sponsored_dialog"); set_sponsored_dialog(DialogId(peer), std::move(source)); } void MessagesManager::add_sponsored_dialog(const Dialog *d, DialogSource source) { if (td_->auth_manager_->is_bot()) { return; } CHECK(!sponsored_dialog_id_.is_valid()); sponsored_dialog_id_ = d->dialog_id; sponsored_dialog_source_ = std::move(source); // update last_pinned_dialog_date in any case, because all chats before SPONSORED_DIALOG_ORDER are known auto dialog_list_id = DialogListId(FolderId::main()); auto *list = get_dialog_list(dialog_list_id); CHECK(list != nullptr); DialogDate max_dialog_date(SPONSORED_DIALOG_ORDER, d->dialog_id); if (list->last_pinned_dialog_date_ < max_dialog_date) { list->last_pinned_dialog_date_ = max_dialog_date; update_list_last_dialog_date(*list); } if (is_dialog_sponsored(d)) { send_update_chat_position(dialog_list_id, d, "add_sponsored_dialog"); // the sponsored dialog must not be saved there } } void MessagesManager::save_sponsored_dialog() { if (!G()->use_message_database()) { return; } LOG(INFO) << "Save sponsored " << sponsored_dialog_id_ << " with source " << sponsored_dialog_source_; if (sponsored_dialog_id_.is_valid()) { G()->td_db()->get_binlog_pmc()->set( "sponsored_dialog_id", PSTRING() << sponsored_dialog_id_.get() << ' ' << sponsored_dialog_source_.DialogSource::serialize()); } else { G()->td_db()->get_binlog_pmc()->erase("sponsored_dialog_id"); } } void MessagesManager::set_sponsored_dialog(DialogId dialog_id, DialogSource source) { if (td_->auth_manager_->is_bot()) { return; } LOG(INFO) << "Change sponsored chat from " << sponsored_dialog_id_ << " to " << dialog_id; if (removed_sponsored_dialog_id_.is_valid() && dialog_id == removed_sponsored_dialog_id_) { return; } if (sponsored_dialog_id_ == dialog_id) { if (sponsored_dialog_source_ != source) { CHECK(sponsored_dialog_id_.is_valid()); sponsored_dialog_source_ = std::move(source); const Dialog *d = get_dialog(sponsored_dialog_id_); CHECK(d != nullptr); send_update_chat_position(DialogListId(FolderId::main()), d, "set_sponsored_dialog"); save_sponsored_dialog(); } return; } bool need_update_total_chat_count = false; if (sponsored_dialog_id_.is_valid()) { const Dialog *d = get_dialog(sponsored_dialog_id_); CHECK(d != nullptr); bool was_sponsored = is_dialog_sponsored(d); sponsored_dialog_id_ = DialogId(); sponsored_dialog_source_ = DialogSource(); if (was_sponsored) { send_update_chat_position(DialogListId(FolderId::main()), d, "set_sponsored_dialog 2"); need_update_total_chat_count = true; } } if (dialog_id.is_valid()) { force_create_dialog(dialog_id, "set_sponsored_dialog_id"); const Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); add_sponsored_dialog(d, std::move(source)); if (is_dialog_sponsored(d)) { need_update_total_chat_count = !need_update_total_chat_count; } } if (need_update_total_chat_count) { auto dialog_list_id = DialogListId(FolderId::main()); auto *list = get_dialog_list(dialog_list_id); CHECK(list != nullptr); if (list->is_dialog_unread_count_inited_) { send_update_unread_chat_count(*list, DialogId(), true, "set_sponsored_dialog_id"); } } save_sponsored_dialog(); } td_api::object_ptr MessagesManager::get_update_unread_message_count_object( const DialogList &list) const { CHECK(!td_->auth_manager_->is_bot()); CHECK(list.is_message_unread_count_inited_); int32 unread_count = list.unread_message_total_count_; int32 unread_unmuted_count = list.unread_message_total_count_ - list.unread_message_muted_count_; CHECK(unread_count >= 0); CHECK(unread_unmuted_count >= 0); return td_api::make_object(list.dialog_list_id.get_chat_list_object(), unread_count, unread_unmuted_count); } td_api::object_ptr MessagesManager::get_update_unread_chat_count_object( const DialogList &list) const { CHECK(!td_->auth_manager_->is_bot()); CHECK(list.is_dialog_unread_count_inited_); int32 unread_count = list.unread_dialog_total_count_; int32 unread_unmuted_count = unread_count - list.unread_dialog_muted_count_; int32 unread_marked_count = list.unread_dialog_marked_count_; int32 unread_unmuted_marked_count = unread_marked_count - list.unread_dialog_muted_marked_count_; CHECK(unread_count >= 0); CHECK(unread_unmuted_count >= 0); CHECK(unread_marked_count >= 0); CHECK(unread_unmuted_marked_count >= 0); return td_api::make_object( list.dialog_list_id.get_chat_list_object(), get_dialog_total_count(list), unread_count, unread_unmuted_count, unread_marked_count, unread_unmuted_marked_count); } void MessagesManager::get_current_state(vector> &updates) const { if (!td_->auth_manager_->is_bot()) { if (G()->use_message_database()) { for (const auto &it : dialog_lists_) { auto &list = it.second; if (list.is_message_unread_count_inited_) { updates.push_back(get_update_unread_message_count_object(list)); } if (list.is_dialog_unread_count_inited_) { updates.push_back(get_update_unread_chat_count_object(list)); } } } } vector> last_message_updates; dialogs_.foreach([&](const DialogId &dialog_id, const unique_ptr &dialog) { const Dialog *d = dialog.get(); auto update = td_api::make_object(get_chat_object(d)); if (update->chat_->last_message_ != nullptr) { last_message_updates.push_back(td_api::make_object( get_chat_id_object(dialog_id, "updateChatLastMessage"), std::move(update->chat_->last_message_), get_chat_positions_object(d))); } updates.push_back(std::move(update)); if (d->open_count > 0) { auto info_it = dialog_online_member_counts_.find(dialog_id); if (info_it != dialog_online_member_counts_.end() && info_it->second.is_update_sent) { updates.push_back(td_api::make_object( get_chat_id_object(dialog_id, "updateChatOnlineMemberCount"), info_it->second.online_member_count)); } } }); append(updates, std::move(last_message_updates)); } void MessagesManager::add_message_file_to_downloads(MessageFullId message_full_id, FileId file_id, int32 priority, Promise> promise) { auto m = get_message_force(message_full_id, "add_message_file_to_downloads"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } auto file_view = td_->file_manager_->get_file_view(file_id); if (file_view.empty()) { return promise.set_error(Status::Error(400, "File not found")); } file_id = file_view.get_main_file_id(); bool is_found = false; for (auto message_file_id : get_message_file_ids(m)) { auto message_file_view = td_->file_manager_->get_file_view(message_file_id); CHECK(!message_file_view.empty()); if (message_file_view.get_main_file_id() == file_id) { is_found = true; } } if (!is_found) { return promise.set_error(Status::Error(400, "Message has no specified file")); } if (m->message_id.is_yet_unsent()) { return promise.set_error(Status::Error(400, "Yet unsent messages can't be added to Downloads")); } auto search_text = get_message_search_text(m); auto file_source_id = get_message_file_source_id(message_full_id, true); CHECK(file_source_id.is_valid()); send_closure(td_->download_manager_actor_, &DownloadManager::add_file, file_id, file_source_id, std::move(search_text), static_cast(priority), std::move(promise)); } void MessagesManager::get_message_file_search_text(MessageFullId message_full_id, string unique_file_id, Promise promise) { auto m = get_message_force(message_full_id, "get_message_file_search_text"); if (m == nullptr) { return promise.set_error(Status::Error(200, "Message not found")); } for (auto file_id : get_message_file_ids(m)) { auto file_view = td_->file_manager_->get_file_view(file_id); CHECK(!file_view.empty()); if (file_view.get_unique_file_id() == unique_file_id) { return promise.set_value(get_message_search_text(m)); } } return promise.set_error(Status::Error(200, "File not found")); } } // namespace td