From fbea859a117fdc818726a1b12d9f822f2b5e69a0 Mon Sep 17 00:00:00 2001 From: levlam Date: Tue, 7 Feb 2023 21:08:54 +0300 Subject: [PATCH] Add MessageSource to viewMessages. --- CMakeLists.txt | 2 + td/generate/scheme/td_api.tl | 36 ++++- td/telegram/MessageSource.cpp | 67 ++++++++++ td/telegram/MessageSource.h | 33 +++++ td/telegram/MessagesManager.cpp | 225 +++++++++++++++++++++----------- td/telegram/MessagesManager.h | 3 +- td/telegram/Td.cpp | 5 +- td/telegram/cli.cpp | 6 +- 8 files changed, 295 insertions(+), 82 deletions(-) create mode 100644 td/telegram/MessageSource.cpp create mode 100644 td/telegram/MessageSource.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2104d8b4f..a03edea07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -392,6 +392,7 @@ set(TDLIB_SOURCE td/telegram/MessageSender.cpp td/telegram/MessagesInfo.cpp td/telegram/MessagesManager.cpp + td/telegram/MessageSource.cpp td/telegram/MessageThreadDb.cpp td/telegram/MessageTtl.cpp td/telegram/misc.cpp @@ -644,6 +645,7 @@ set(TDLIB_SOURCE td/telegram/MessageSender.h td/telegram/MessagesInfo.h td/telegram/MessagesManager.h + td/telegram/MessageSource.h td/telegram/MessageThreadDb.h td/telegram/MessageThreadInfo.h td/telegram/MessageTtl.h diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index f391642ce..6e9d9092c 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -1139,6 +1139,36 @@ messageCalendarDay total_count:int32 message:message = MessageCalendarDay; messageCalendar total_count:int32 days:vector = MessageCalendar; +//@class MessageSource @description Describes source of a message + +//@description The message is from a chat history +messageSourceChatHistory = MessageSource; + +//@description The message is from a message thread history +messageSourceMessageThreadHistory = MessageSource; + +//@description The message is from a forum topic history +messageSourceForumTopicHistory = MessageSource; + +//@description The message is from chat, message thread or forum topic history preview +messageSourceHistoryPreview = MessageSource; + +//@description The message is from a chat list or a forum topic list +messageSourceChatList = MessageSource; + +//@description The message is from search results, including file downloads, local file list, outgoing document messages, calendar +messageSourceSearch = MessageSource; + +//@description The message is from a chat event log +messageSourceChatEventLog = MessageSource; + +//@description The message is from a notification +messageSourceNotification = MessageSource; + +//@description The message is from some other source +messageSourceOther = MessageSource; + + //@description Describes a sponsored message //@message_id Message identifier; unique for the chat to which the sponsored message belongs among both ordinary and sponsored messages //@is_recommended True, if the message needs to be labeled as "recommended" instead of "sponsored" @@ -6571,10 +6601,10 @@ closeChat chat_id:int53 = Ok; //@description Informs TDLib that messages are being viewed by the user. Sponsored messages must be marked as viewed only when the entire text of the message is shown on the screen (excluding the button). //-Many useful activities depend on whether the messages are currently being viewed or not (e.g., marking messages as read, incrementing a view counter, updating a view counter, removing deleted messages in supergroups and channels) //@chat_id Chat identifier -//@message_thread_id If not 0, a message thread identifier in which the messages are being viewed //@message_ids The identifiers of the messages being viewed -//@force_read Pass true to mark as read the specified messages even the chat is closed -viewMessages chat_id:int53 message_thread_id:int53 message_ids:vector force_read:Bool = Ok; +//@source Source of the message view +//@force_read Pass true to mark as read the specified messages even the chat is closed; pass null to guess the source based on chat open state +viewMessages chat_id:int53 message_ids:vector source:MessageSource force_read:Bool = Ok; //@description Informs TDLib that the message content has been opened (e.g., the user has opened a photo, video, document, location or venue, or has listened to an audio file or voice note message). //-An updateMessageContentOpened update will be generated if something has changed diff --git a/td/telegram/MessageSource.cpp b/td/telegram/MessageSource.cpp new file mode 100644 index 000000000..1e75ab0d4 --- /dev/null +++ b/td/telegram/MessageSource.cpp @@ -0,0 +1,67 @@ +// +// 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/MessageSource.h" + +namespace td { + +StringBuilder &operator<<(StringBuilder &string_builder, MessageSource source) { + switch (source) { + case MessageSource::Auto: + return string_builder << "Auto"; + case MessageSource::DialogHistory: + return string_builder << "ChatHistory"; + case MessageSource::MessageThreadHistory: + return string_builder << "MessageThreadHistory"; + case MessageSource::ForumTopicHistory: + return string_builder << "ForumTopicHistory"; + case MessageSource::HistoryPreview: + return string_builder << "HistoryPreview"; + case MessageSource::DialogList: + return string_builder << "DialogList"; + case MessageSource::Search: + return string_builder << "Search"; + case MessageSource::DialogEventLog: + return string_builder << "DialogEventLog"; + case MessageSource::Notification: + return string_builder << "Notification"; + case MessageSource::Other: + return string_builder << "Other"; + default: + UNREACHABLE(); + } +} + +MessageSource get_message_source(const td_api::object_ptr &source) { + if (source == nullptr) { + return MessageSource::Auto; + } + switch (source->get_id()) { + case td_api::messageSourceChatHistory::ID: + return MessageSource::DialogHistory; + case td_api::messageSourceMessageThreadHistory::ID: + return MessageSource::MessageThreadHistory; + case td_api::messageSourceForumTopicHistory::ID: + return MessageSource::ForumTopicHistory; + case td_api::messageSourceHistoryPreview::ID: + return MessageSource::HistoryPreview; + case td_api::messageSourceChatList::ID: + return MessageSource::DialogList; + case td_api::messageSourceSearch::ID: + return MessageSource::Search; + case td_api::messageSourceChatEventLog::ID: + return MessageSource::DialogEventLog; + case td_api::messageSourceNotification::ID: + return MessageSource::Notification; + case td_api::messageSourceOther::ID: + return MessageSource::Other; + default: + UNREACHABLE(); + return MessageSource::Other; + } +} + +} // namespace td diff --git a/td/telegram/MessageSource.h b/td/telegram/MessageSource.h new file mode 100644 index 000000000..778fd5871 --- /dev/null +++ b/td/telegram/MessageSource.h @@ -0,0 +1,33 @@ +// +// 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) +// +#pragma once + +#include "td/telegram/td_api.h" + +#include "td/utils/common.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +enum class MessageSource : int32 { + Auto, + DialogHistory, + MessageThreadHistory, + ForumTopicHistory, + HistoryPreview, + DialogList, + Search, + DialogEventLog, + Notification, + Other +}; + +StringBuilder &operator<<(StringBuilder &string_builder, MessageSource source); + +MessageSource get_message_source(const td_api::object_ptr &source); + +} // namespace td diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index f7a65baf7..6fc1b3f86 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -21490,56 +21490,133 @@ DialogId MessagesManager::get_my_dialog_id() const { return DialogId(td_->contacts_manager_->get_my_id()); } -Status MessagesManager::view_messages(DialogId dialog_id, MessageId top_thread_message_id, - const vector &message_ids, bool force_read) { +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"); } - for (auto message_id : message_ids) { - if (!message_id.is_valid() && !message_id.is_valid_scheduled()) { - if (message_id.is_valid_sponsored()) { - if (d->is_opened) { - td_->sponsored_message_manager_->view_sponsored_message(dialog_id, message_id); - } - continue; - } - return Status::Error(400, "Invalid message identifier"); - } - } if (!have_input_peer(dialog_id, AccessRights::Read)) { return Status::Error(400, "Can't access the chat"); } - MessageId max_thread_message_id; - if (top_thread_message_id != MessageId()) { - if (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server()) { - return Status::Error(400, "Invalid message thread ID specified"); + if (source == MessageSource::Auto) { + if (d->is_opened) { + source = MessageSource::DialogHistory; + } else { + source = MessageSource::Other; } - if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id)) { - return Status::Error(400, "There are no message threads in the chat"); + } + 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; + + // 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 { + return Status::Error(400, "Can't view the message from the specified source"); + } + } + return Status::Error(400, "Invalid message identifier"); } - const auto *top_m = get_message_force(d, top_thread_message_id, "view_messages 6"); - if (top_m != nullptr && !top_m->reply_info.is_comment_) { - max_thread_message_id = top_m->reply_info.max_message_id_; + message_ids[pos++] = message_id; + } + message_ids.resize(pos); + if (message_ids.empty()) { + // nothing 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_content_file_ids(m->content.get(), td_); + for (auto file_id : file_ids) { + td_->file_manager_->check_local_location_async(file_id, true); + } + } + } + + if (source == MessageSource::DialogEventLog) { + // nothing more to do + return Status::OK(); + } + + // get information about thread of the messages + MessageId top_thread_message_id; + MessageId max_thread_message_id; + if (source == MessageSource::MessageThreadHistory) { + if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id)) { + return Status::Error(400, "There are no message threads in the chat"); + } + + for (auto message_id : message_ids) { + auto *m = get_message_force(d, message_id, "view_messages 1"); + if (m != nullptr) { + if (top_thread_message_id.is_valid()) { + if (m->top_thread_message_id != top_thread_message_id) { + return Status::Error(400, "All messages must be from the same message thread"); + } + } else { + if (!m->top_thread_message_id.is_valid()) { + return Status::Error(400, "Messages must be from a message thread"); + } + top_thread_message_id = m->top_thread_message_id; + 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; + } + } } } - bool need_read = force_read || d->is_opened; MessageId max_message_id; // max server or local viewed message_id vector read_content_message_ids; vector new_viewed_message_ids; vector viewed_reaction_message_ids; for (auto message_id : message_ids) { - if (!message_id.is_valid()) { - continue; - } - - auto *m = get_message_force(d, message_id, "view_messages 1"); + auto *m = get_message_force(d, message_id, "view_messages 4"); if (m != nullptr) { - if (m->message_id.is_server() && m->view_count > 0) { + if (m->message_id.is_server() && m->view_count > 0 && need_update_view_count) { d->pending_viewed_message_ids.insert(m->message_id); } @@ -21554,20 +21631,20 @@ Status MessagesManager::view_messages(DialogId dialog_id, MessageId top_thread_m if (need_read && message_content_type != MessageContentType::VoiceNote && message_content_type != MessageContentType::VideoNote && - update_message_contains_unread_mention(d, m, false, "view_messages")) { + 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"); + on_message_changed(d, m, true, "view_messages 6"); } - if (need_read && remove_message_unread_reactions(d, m, "view_messages")) { + if (need_read && remove_message_unread_reactions(d, m, "view_messages 7")) { CHECK(m->message_id.is_server()); read_content_message_ids.push_back(m->message_id); - on_message_changed(d, m, true, "view_messages"); + on_message_changed(d, m, true, "view_messages 8"); } auto file_source_id = full_message_id_to_file_source_id_.get({dialog_id, m->message_id}); - if (file_source_id.is_valid()) { + 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)) { @@ -21595,11 +21672,6 @@ Status MessagesManager::view_messages(DialogId dialog_id, MessageId top_thread_m view_id = ++info->current_view_id; info->recently_viewed_messages[view_id] = message_id; } - - auto file_ids = get_message_content_file_ids(m->content.get(), td_); - for (auto file_id : file_ids) { - td_->file_manager_->check_local_location_async(file_id, true); - } } else if (!message_id.is_yet_unsent() && message_id > max_message_id) { if (message_id <= d->max_notification_message_id || message_id <= d->last_new_message_id || message_id <= max_thread_message_id) { @@ -21609,7 +21681,7 @@ Status MessagesManager::view_messages(DialogId dialog_id, MessageId top_thread_m } if (!d->pending_viewed_message_ids.empty()) { pending_message_views_timeout_.add_timeout_in(dialog_id.get(), MAX_MESSAGE_VIEW_DELAY); - d->increment_view_counter |= d->is_opened; + d->increment_view_counter |= is_dialog_history; } if (!read_content_message_ids.empty()) { read_message_contents_on_server(dialog_id, std::move(read_content_message_ids), 0, Auto()); @@ -21637,15 +21709,15 @@ Status MessagesManager::view_messages(DialogId dialog_id, MessageId top_thread_m return Status::OK(); } - if (top_thread_message_id.is_valid() && max_message_id.is_valid()) { + if (source == MessageSource::MessageThreadHistory && top_thread_message_id.is_valid() && max_message_id.is_valid()) { 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 2"); + 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 3"); + on_message_changed(d, top_m, true, "view_messages 10"); } max_thread_message_id = top_m->reply_info.max_message_id_; @@ -21654,14 +21726,14 @@ Status MessagesManager::view_messages(DialogId dialog_id, MessageId top_thread_m 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 4"); + 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 5"); + 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_; @@ -21678,35 +21750,42 @@ Status MessagesManager::view_messages(DialogId dialog_id, MessageId top_thread_m return Status::OK(); } - - if (max_message_id > d->last_read_inbox_message_id) { - 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 (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(dialog_id.get(), 0); - } - read_history_inbox(d->dialog_id, last_read_message_id, -1, "view_messages"); - 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); - } + if (source == MessageSource::ForumTopicHistory) { + // TODO read forum topic history + return Status::OK(); } - if (d->is_marked_as_unread) { - set_dialog_is_marked_as_unread(d, false); + + if (source == MessageSource::DialogHistory) { + if (max_message_id > d->last_read_inbox_message_id) { + 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 (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(dialog_id.get(), 0); + } + read_history_inbox(d->dialog_id, last_read_message_id, -1, "view_messages 13"); + 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); + } + } + if (d->is_marked_as_unread) { + set_dialog_is_marked_as_unread(d, false); + } + return Status::OK(); } return Status::OK(); diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index 669aec812..ef4234b45 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -38,6 +38,7 @@ #include "td/telegram/MessageReplyInfo.h" #include "td/telegram/MessageSearchFilter.h" #include "td/telegram/MessagesInfo.h" +#include "td/telegram/MessageSource.h" #include "td/telegram/MessageThreadInfo.h" #include "td/telegram/MessageTtl.h" #include "td/telegram/net/DcId.h" @@ -716,7 +717,7 @@ class MessagesManager final : public Actor { Status close_dialog(DialogId dialog_id) TD_WARN_UNUSED_RESULT; - Status view_messages(DialogId dialog_id, MessageId top_thread_message_id, const vector &message_ids, + Status view_messages(DialogId dialog_id, vector message_ids, MessageSource source, bool force_read) TD_WARN_UNUSED_RESULT; void finish_get_message_views(DialogId dialog_id, const vector &message_ids); diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index cec1f9d2f..8c751afe7 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -75,6 +75,7 @@ #include "td/telegram/MessageSearchFilter.h" #include "td/telegram/MessageSender.h" #include "td/telegram/MessagesManager.h" +#include "td/telegram/MessageSource.h" #include "td/telegram/MessageThreadInfo.h" #include "td/telegram/MessageTtl.h" #include "td/telegram/misc.h" @@ -5095,8 +5096,8 @@ void Td::on_request(uint64 id, const td_api::closeChat &request) { void Td::on_request(uint64 id, const td_api::viewMessages &request) { CHECK_IS_USER(); answer_ok_query( - id, messages_manager_->view_messages(DialogId(request.chat_id_), MessageId(request.message_thread_id_), - MessageId::get_message_ids(request.message_ids_), request.force_read_)); + id, messages_manager_->view_messages(DialogId(request.chat_id_), MessageId::get_message_ids(request.message_ids_), + get_message_source(request.source_), request.force_read_)); } void Td::on_request(uint64 id, const td_api::openMessageContent &request) { diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index fe1d625ac..953344ab8 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -5010,14 +5010,14 @@ class CliClient final : public Actor { send_request(td_api::make_object(hashtag)); } else if (op == "view" || op == "viewt") { ChatId chat_id; - MessageThreadId message_thread_id; string message_ids; get_args(args, chat_id, message_ids); + td_api::object_ptr source; if (op == "viewt") { - get_args(message_ids, message_thread_id, message_ids); + source = td_api::make_object(); } send_request( - td_api::make_object(chat_id, message_thread_id, as_message_ids(message_ids), true)); + td_api::make_object(chat_id, as_message_ids(message_ids), std::move(source), true)); } else if (op == "omc") { ChatId chat_id; MessageId message_id;