From b751acc3aec242568ef6505d0be26d2effc63978 Mon Sep 17 00:00:00 2001 From: levlam Date: Wed, 30 Sep 2020 04:26:39 +0300 Subject: [PATCH] Add td_api::getMessageThreadHistory. GitOrigin-RevId: c5aaa396b1fcdd6704302296e407989ff19be0a0 --- SplitSource.php | 1 + td/generate/scheme/td_api.tl | 11 +- td/generate/scheme/td_api.tlo | Bin 182756 -> 182992 bytes td/telegram/MessagesManager.cpp | 198 +++++++++++++++++++++++++++++++- td/telegram/MessagesManager.h | 9 ++ td/telegram/Td.cpp | 39 +++++++ td/telegram/Td.h | 2 + td/telegram/cli.cpp | 10 +- 8 files changed, 267 insertions(+), 3 deletions(-) diff --git a/SplitSource.php b/SplitSource.php index 3316e64ba..605ee0121 100644 --- a/SplitSource.php +++ b/SplitSource.php @@ -156,6 +156,7 @@ function split_file($file, $chunks, $undo) { '(?[A-Z][A-Za-z]*) : public (Td::ResultHandler|NetActor|Request)|'. '(CREATE_REQUEST|CREATE_NO_ARGS_REQUEST)[(](?[A-Z][A-Za-z]*)|'. '(?complete_pending_preauthentication_requests)|'. + '(?get_message_history_slice)|'. '(Up|Down)load[a-zA-Z]*C(?allback)|(up|down)load_[a-z_]*_c(?allback)_|'. '(?lazy_to_json)|'. '(?LogEvent)[^sA]|'. diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index f5697a06a..45e1794b8 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -3624,10 +3624,19 @@ getGroupsInCommon user_id:int32 offset_chat_id:int53 limit:int32 = Chats; //@chat_id Chat identifier //@from_message_id Identifier of the message starting from which history must be fetched; use 0 to get results from the last message //@offset Specify 0 to get results from exactly the from_message_id or a negative offset up to 99 to get additionally some newer messages -//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater or equal to -offset. Fewer messages may be returned than specified by the limit, even if the end of the message history has not been reached +//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater than or equal to -offset. Fewer messages may be returned than specified by the limit, even if the end of the message history has not been reached //@only_local If true, returns only messages that are available locally without sending network requests getChatHistory chat_id:int53 from_message_id:int53 offset:int32 limit:int32 only_local:Bool = Messages; +//@description Returns messages in a message thread of a message. Can be used only if message.can_get_message_thread == true. Message thread of a channel message is in the channel's linked supergroup. +//-The messages are returned in a reverse chronological order (i.e., in order of decreasing message_id). For optimal performance the number of returned messages is chosen by the library +//@chat_id Chat identifier +//@message_id Message identifier, which thread history needs to be returned +//@from_message_id Identifier of the message starting from which history must be fetched; use 0 to get results from the last message +//@offset Specify 0 to get results from exactly the from_message_id or a negative offset up to 99 to get additionally some newer messages +//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater than or equal to -offset. Fewer messages may be returned than specified by the limit, even if the end of the message thread history has not been reached +getMessageThreadHistory chat_id:int53 message_id:int53 from_message_id:int53 offset:int32 limit:int32 = Messages; + //@description Deletes all messages in the chat. Use Chat.can_be_deleted_only_for_self and Chat.can_be_deleted_for_all_users fields to find whether and how the method can be applied to the chat //@chat_id Chat identifier @remove_from_chat_list Pass true if the chat should be removed from the chat list @revoke Pass true to try to delete chat history for all users deleteChatHistory chat_id:int53 remove_from_chat_list:Bool revoke:Bool = Ok; diff --git a/td/generate/scheme/td_api.tlo b/td/generate/scheme/td_api.tlo index 49e29a707f6e6cc825722a750d7b3d2d71a79e97..6097739befb5c5ef609c18f66a48423b7851173d 100644 GIT binary patch delta 76 zcmV-S0JHz(lMB$53xI?Hv;qVQ0fe^&3Idx|94OQ^low}ZbWLS*b75y?RA_Q#VPr^Y ib98TVd6&RN0x*}b!vQ3h@I?Y-mw=K27`LEb0z_&v+#Hwy delta 27 jcmcaGmHWwL?uHh|Elez2jLq9wxtL~$ZtqE8a>)V!jOhtW diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index d5e74fc09..b4622fdd1 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -19330,7 +19330,7 @@ tl_object_ptr MessagesManager::get_dialog_history(DialogId dia return nullptr; } if (offset < -limit) { - promise.set_error(Status::Error(5, "Parameter offset must not be less than -limit")); + promise.set_error(Status::Error(5, "Parameter offset must be greater than or equal to -limit")); return nullptr; } bool is_limit_increased = false; @@ -19728,6 +19728,202 @@ void MessagesManager::on_read_history_finished(DialogId dialog_id, MessageId top } } +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(3, "Parameter limit must be positive")); + return {}; + } + if (limit > MAX_GET_HISTORY) { + limit = MAX_GET_HISTORY; + } + if (offset > 0) { + promise.set_error(Status::Error(5, "Parameter offset must be non-positive")); + return {}; + } + if (offset <= -MAX_GET_HISTORY) { + promise.set_error(Status::Error(5, "Parameter offset must be greater than -100")); + return {}; + } + if (offset < -limit) { + promise.set_error(Status::Error(5, "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); + if (d == nullptr) { + promise.set_error(Status::Error(6, "Chat not found")); + return {}; + } + if (!have_input_peer(dialog_id, AccessRights::Read)) { + promise.set_error(Status::Error(5, "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(3, "Parameter from_message_id must be identifier of the chat message or 0")); + return {}; + } + + DialogId message_thread_dialog_id; + MessageId top_thread_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 {}; + } + + if (m->reply_info.is_comment) { + if (!is_active_message_reply_info(dialog_id, m->reply_info)) { + promise.set_error(Status::Error(400, "Message has no comments")); + return {}; + } + if (!m->linked_top_thread_message_id.is_valid()) { + 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 {}; + } + message_thread_dialog_id = DialogId(m->reply_info.channel_id); + top_thread_message_id = m->linked_top_thread_message_id; + } else { + if (!m->top_thread_message_id.is_valid()) { + promise.set_error(Status::Error(400, "Message has no thread")); + return {}; + } + message_thread_dialog_id = dialog_id; + top_thread_message_id = m->top_thread_message_id; + } + } + CHECK(top_thread_message_id.is_valid()); + + 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.second); + 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 != message_thread_dialog_id) { + promise.set_error(Status::Error(500, "Receive messages in an unexpected chat")); + return {}; + } + + auto yet_unsent_it = d->yet_unsent_thread_message_ids.find(top_thread_message_id); + if (yet_unsent_it != d->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_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_.find(random_id) != found_dialog_messages_.end()); + found_dialog_messages_[random_id]; // reserve place for result + + td_->create_handler(std::move(promise)) + ->send(dialog_id, string(), UserId(), nullptr, from_message_id.get_next_server_message_id(), offset, limit, + MessageSearchFilter::Empty, message_id, random_id); + return {}; +} + std::pair> MessagesManager::search_dialog_messages( DialogId dialog_id, const string &query, UserId sender_user_id, MessageId from_message_id, int32 offset, int32 limit, MessageSearchFilter filter, MessageId top_thread_message_id, int64 &random_id, bool use_db, diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index cfb8aaa1e..0b75aac2e 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -701,6 +701,11 @@ class MessagesManager : public Actor { int32 limit, int left_tries, bool only_local, Promise &&promise); + std::pair> get_message_thread_history(DialogId dialog_id, MessageId message_id, + MessageId from_message_id, int32 offset, + int32 limit, int64 &random_id, + Promise &&promise); + std::pair> search_dialog_messages(DialogId dialog_id, const string &query, UserId sender_user_id, MessageId from_message_id, int32 offset, int32 limit, MessageSearchFilter filter, @@ -1987,6 +1992,10 @@ class MessagesManager : public Actor { void on_update_dialog_online_member_count_timeout(DialogId dialog_id); + template + vector get_message_history_slice(const T &begin, It it, const T &end, MessageId from_message_id, + int32 offset, int32 limit); + void preload_newer_messages(const Dialog *d, MessageId max_message_id); void preload_older_messages(const Dialog *d, MessageId min_message_id); diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index a287cd18c..122679327 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -1398,6 +1398,39 @@ class GetChatHistoryRequest : public RequestActor<> { } }; +class GetMessageThreadHistoryRequest : public RequestActor<> { + DialogId dialog_id_; + MessageId message_id_; + MessageId from_message_id_; + int32 offset_; + int32 limit_; + int64 random_id_; + + std::pair> messages_; + + void do_run(Promise &&promise) override { + messages_ = td->messages_manager_->get_message_thread_history(dialog_id_, message_id_, from_message_id_, offset_, + limit_, random_id_, std::move(promise)); + } + + void do_send_result() override { + send_result(td->messages_manager_->get_messages_object(-1, messages_.first, messages_.second)); + } + + public: + GetMessageThreadHistoryRequest(ActorShared td, uint64 request_id, int64 dialog_id, int64 message_id, + int64 from_message_id, int32 offset, int32 limit) + : RequestActor(std::move(td), request_id) + , dialog_id_(dialog_id) + , message_id_(message_id) + , from_message_id_(from_message_id) + , offset_(offset) + , limit_(limit) + , random_id_(0) { + set_tries(3); + } +}; + class SearchChatMessagesRequest : public RequestActor<> { DialogId dialog_id_; string query_; @@ -5488,6 +5521,12 @@ void Td::on_request(uint64 id, const td_api::deleteChatHistory &request) { std::move(promise)); } +void Td::on_request(uint64 id, const td_api::getMessageThreadHistory &request) { + CHECK_IS_USER(); + CREATE_REQUEST(GetMessageThreadHistoryRequest, request.chat_id_, request.message_id_, request.from_message_id_, + request.offset_, request.limit_); +} + void Td::on_request(uint64 id, td_api::searchChatMessages &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.query_); diff --git a/td/telegram/Td.h b/td/telegram/Td.h index 37a6d7cd9..ebd81da43 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -572,6 +572,8 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, const td_api::deleteChatHistory &request); + void on_request(uint64 id, const td_api::getMessageThreadHistory &request); + void on_request(uint64 id, td_api::searchChatMessages &request); void on_request(uint64 id, td_api::searchSecretMessages &request); diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 9cadf07e3..dfd970179 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -1816,13 +1816,17 @@ class CliClient final : public Actor { } send_request(td_api::make_object(as_user_id(user_id), as_chat_id(offset_chat_id), to_integer(limit))); - } else if (op == "gh" || op == "GetHistory" || op == "ghl") { + } else if (op == "gh" || op == "GetHistory" || op == "ghl" || op == "gmth") { string chat_id; + string thread_message_id; string from_message_id; string offset; string limit; std::tie(chat_id, args) = split(args); + if (op == "gmth") { + std::tie(thread_message_id, args) = split(args); + } std::tie(from_message_id, args) = split(args); if (from_message_id.empty()) { from_message_id = "0"; @@ -1837,6 +1841,10 @@ class CliClient final : public Actor { } if (!args.empty()) { LOG(ERROR) << "Wrong parameters to function getChatHistory specified"; + } else if (op == "gmth") { + send_request(td_api::make_object( + as_chat_id(chat_id), as_message_id(thread_message_id), as_message_id(from_message_id), + to_integer(offset), to_integer(limit))); } else { send_request(td_api::make_object(as_chat_id(chat_id), as_message_id(from_message_id), to_integer(offset), to_integer(limit),