diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index f344441af..d12667e42 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -819,6 +819,12 @@ messagePosition position:int32 message_id:int53 date:int32 = MessagePosition; //@description Contains a list of message positions @total_count Total count of messages found @positions List of message positions messagePositions total_count:int32 positions:vector = MessagePositions; +//@description Contains information about found messages sent in a specific day @total_count Total number of found messages sent in the day @message First message sent in the day +messageCalendarDay total_count:int32 message:message = MessageCalendarDay; + +//@description Contains information about found messages, splitted by days @total_count Total number of found messages @utc_time_offset UTC time offset in seconds, used to split messages by days @days Information about messages sent +messageCalendar total_count:int32 utc_time_offset:int32 days:vector = MessageCalendar; + //@description Describes a sponsored message @id Unique sponsored message identifier @sponsor_chat_id Chat identifier //@link An internal link to be opened when the sponsored message is clicked; may be null. If null, the sponsor chat needs to be opened instead @content Content of the message @@ -4299,6 +4305,12 @@ getChatMessageByDate chat_id:int53 date:int32 = Message; //@limit The expected number of message positions to be returned. A smaller number of positions can be returned, if there are not enough appropriate messages getChatSparseMessagePositions chat_id:int53 filter:SearchMessagesFilter from_message_id:int53 limit:int32 = MessagePositions; +//@description Returns information about the next messages of the specified type in the chat splitted by days. Returns the results in reverse chronological order. Can return partial result for the last returned day +//@chat_id Identifier of the chat in which to return information about messages +//@filter Filter for message content. Filters searchMessagesFilterEmpty, searchMessagesFilterCall, searchMessagesFilterMissedCall, searchMessagesFilterMention, searchMessagesFilterUnreadMention and searchMessagesFilterFailedToSend are unsupported in this function +//@from_message_id The message identifier from which to return information about messages; use 0 to get results from the last message +getChatMessageCalendar chat_id:int53 filter:SearchMessagesFilter from_message_id:int53 = MessageCalendar; + //@description Returns approximate number of messages of the specified type in the chat @chat_id Identifier of the chat in which to count messages @filter Filter for message content; searchMessagesFilterEmpty is unsupported in this function @return_local If true, returns count that is available locally without sending network requests, returning -1 if the number of messages is unknown getChatMessageCount chat_id:int53 filter:SearchMessagesFilter return_local:Bool = Count; diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index 409d3dbb9..4fa96eb22 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -2167,6 +2167,70 @@ class ReadDiscussionQuery final : public Td::ResultHandler { } }; +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(uint64 id, BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, 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"); + + MessagesManager::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)); + } + })); + } + + void on_error(uint64 id, Status status) final { + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "SearchMessagesQuery"); + td->messages_manager_->on_failed_get_message_search_result_calendar(dialog_id_, random_id_); + promise_.set_error(std::move(status)); + } +}; + class SearchMessagesQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; @@ -9693,6 +9757,71 @@ void MessagesManager::on_failed_public_dialogs_search(const string &query, Statu } } +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_full_message_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, + false, false, false, "on_get_message_search_result_calendar"); + if (new_full_message_id == FullMessageId()) { + total_count--; + continue; + } + + if (new_full_message_id.get_dialog_id() != dialog_id) { + LOG(ERROR) << "Receive " << new_full_message_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, Clocks::tz_offset(), 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, @@ -21220,7 +21349,7 @@ std::pair> MessagesManager::get_message_thread_histo 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 the chat message or 0")); + promise.set_error(Status::Error(400, "Parameter from_message_id must be identifier of a chat message or 0")); return {}; } @@ -21339,6 +21468,95 @@ std::pair> MessagesManager::get_message_thread_histo 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_.find(random_id) != found_dialog_message_calendars_.end()); + found_dialog_message_calendars_[random_id]; // reserve place for result + + if (filter == MessageSearchFilter::Empty || filter == MessageSearchFilter::Call || + filter == MessageSearchFilter::MissedCall || filter == MessageSearchFilter::Mention || + filter == MessageSearchFilter::UnreadMention) { + promise.set_error(Status::Error(400, "The filter is not supported")); + return {}; + } + + // Trying to use database + if (use_db && G()->parameters().use_message_db) { + 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) { + // TODO + } + } + 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, "Search messages is not supported")); + } + return {}; +} + std::pair> 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, @@ -21381,7 +21599,7 @@ std::pair> MessagesManager::search_dialog_messages( } if (!from_message_id.is_valid() && from_message_id != MessageId()) { - promise.set_error(Status::Error(400, "Parameter from_message_id must be identifier of the chat message or 0")); + 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(); @@ -21569,7 +21787,7 @@ std::pair> MessagesManager::search_call_messages(Me } if (!from_message_id.is_valid() && from_message_id != MessageId()) { - promise.set_error(Status::Error(400, "Parameter from_message_id must be identifier of the chat message or 0")); + 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(); diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index 3ce4089d7..1ff7a089a 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -205,6 +205,13 @@ class MessagesManager final : public Actor { vector> &&peers); void on_failed_public_dialogs_search(const string &query, Status &&error); + void 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); + void on_failed_get_message_search_result_calendar(DialogId dialog_id, int64 random_id); + void 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, @@ -695,6 +702,10 @@ class MessagesManager final : public Actor { int32 limit, int64 &random_id, Promise &&promise); + td_api::object_ptr get_dialog_message_calendar(DialogId dialog_id, MessageId from_message_id, + MessageSearchFilter filter, int64 &random_id, + bool use_db, Promise &&promise); + std::pair> search_dialog_messages(DialogId dialog_id, const string &query, const td_api::object_ptr &sender, MessageId from_message_id, int32 offset, int32 limit, @@ -3324,6 +3335,7 @@ class MessagesManager final : public Actor { std::unordered_map get_dialog_message_by_date_results_; + std::unordered_map> found_dialog_message_calendars_; std::unordered_map>> found_dialog_messages_; // random_id -> [total_count, [message_id]...] std::unordered_map found_dialog_messages_dialog_id_; // random_id -> dialog_id diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index ab5076352..5056cc716 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -1349,6 +1349,35 @@ class GetMessageThreadHistoryRequest final : public RequestActor<> { } }; +class GetChatMessageCalendarRequest final : public RequestActor<> { + DialogId dialog_id_; + MessageId from_message_id_; + MessageSearchFilter filter_; + int64 random_id_; + + td_api::object_ptr calendar_; + + void do_run(Promise &&promise) final { + calendar_ = td->messages_manager_->get_dialog_message_calendar(dialog_id_, from_message_id_, filter_, random_id_, + get_tries() == 3, std::move(promise)); + } + + void do_send_result() final { + send_result(std::move(calendar_)); + } + + public: + GetChatMessageCalendarRequest(ActorShared td, uint64 request_id, int64 dialog_id, int64 from_message_id, + tl_object_ptr filter) + : RequestActor(std::move(td), request_id) + , dialog_id_(dialog_id) + , from_message_id_(from_message_id) + , filter_(get_message_search_filter(filter)) + , random_id_(0) { + set_tries(3); + } +}; + class SearchChatMessagesRequest final : public RequestActor<> { DialogId dialog_id_; string query_; @@ -5321,6 +5350,11 @@ void Td::on_request(uint64 id, const td_api::getMessageThreadHistory &request) { request.offset_, request.limit_); } +void Td::on_request(uint64 id, td_api::getChatMessageCalendar &request) { + CHECK_IS_USER(); + CREATE_REQUEST(GetChatMessageCalendarRequest, request.chat_id_, request.from_message_id_, std::move(request.filter_)); +} + 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 90eb04802..9d1be0fd3 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -612,6 +612,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::getMessageThreadHistory &request); + void on_request(uint64 id, td_api::getChatMessageCalendar &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 b4fc390b3..bc69d2917 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -2097,6 +2097,13 @@ class CliClient final : public Actor { string limit; get_args(args, chat_id, limit); send_request(td_api::make_object(as_chat_id(chat_id), as_limit(limit))); + } else if (op == "gcmca") { + string chat_id; + string filter; + string from_message_id; + get_args(args, chat_id, filter, from_message_id); + send_request(td_api::make_object( + as_chat_id(chat_id), as_search_messages_filter(filter), as_message_id(from_message_id))); } else if (op == "SearchAudio" || op == "SearchDocument" || op == "SearchPhoto" || op == "SearchChatPhoto") { string chat_id; string offset_message_id;