From db3f673deb9cd575bcd4167f856e5a46882b4c6d Mon Sep 17 00:00:00 2001 From: levlam Date: Mon, 22 Apr 2024 20:11:48 +0300 Subject: [PATCH] Add td_api::getRecommendedChats. --- td/generate/scheme/td_api.tl | 3 + td/telegram/ChannelRecommendationManager.cpp | 149 ++++++++++++++++++- td/telegram/ChannelRecommendationManager.h | 20 +++ td/telegram/Td.cpp | 6 + td/telegram/Td.h | 2 + td/telegram/cli.cpp | 2 + 6 files changed, 179 insertions(+), 3 deletions(-) diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index a90c538dc..baa29eac2 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -7652,6 +7652,9 @@ searchChatsOnServer query:string limit:int32 = Chats; //@location Current user location searchChatsNearby location:location = ChatsNearby; +//@description Returns a list of channel chats recommended to the current user +getRecommendedChats = Chats; + //@description Returns a list of chats similar to the given chat @chat_id Identifier of the target chat; must be an identifier of a channel chat getChatSimilarChats chat_id:int53 = Chats; diff --git a/td/telegram/ChannelRecommendationManager.cpp b/td/telegram/ChannelRecommendationManager.cpp index a75c6cf2f..c8d0048e9 100644 --- a/td/telegram/ChannelRecommendationManager.cpp +++ b/td/telegram/ChannelRecommendationManager.cpp @@ -42,8 +42,11 @@ class GetChannelRecommendationsQuery final : public Td::ResultHandler { channel_id_ = channel_id; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - int32 flags = telegram_api::channels_getChannelRecommendations::CHANNEL_MASK; + CHECK(!channel_id.is_valid() || input_channel != nullptr); + int32 flags = 0; + if (input_channel != nullptr) { + flags |= telegram_api::channels_getChannelRecommendations::CHANNEL_MASK; + } send_query(G()->net_query_creator().create( telegram_api::channels_getChannelRecommendations(flags, std::move(input_channel)))); } @@ -73,7 +76,9 @@ class GetChannelRecommendationsQuery final : public Td::ResultHandler { } void on_error(Status status) final { - td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetChannelRecommendationsQuery"); + if (channel_id_.is_valid()) { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetChannelRecommendationsQuery"); + } promise_.set_error(std::move(status)); } }; @@ -152,6 +157,144 @@ bool ChannelRecommendationManager::are_suitable_recommended_dialogs( return true; } +void ChannelRecommendationManager::get_recommended_channels(Promise> &&promise) { + bool use_database = true; + if (are_recommended_channels_inited_) { + if (are_suitable_recommended_dialogs(recommended_channels_)) { + auto next_reload_time = recommended_channels_.next_reload_time_; + promise.set_value(td_->dialog_manager_->get_chats_object( + recommended_channels_.total_count_, recommended_channels_.dialog_ids_, "get_recommended_channels")); + if (next_reload_time > Time::now()) { + return; + } + promise = {}; + } else { + LOG(INFO) << "Drop cache for recommended chats"; + are_recommended_channels_inited_ = false; + if (G()->use_message_database()) { + G()->td_db()->get_sqlite_pmc()->erase(get_recommended_channels_database_key(), Auto()); + } + } + use_database = false; + } + load_recommended_channels(use_database, std::move(promise)); +} + +string ChannelRecommendationManager::get_recommended_channels_database_key() { + return "recommended_channels"; +} + +void ChannelRecommendationManager::load_recommended_channels(bool use_database, + Promise> &&promise) { + get_recommended_channels_queries_.push_back(std::move(promise)); + if (get_recommended_channels_queries_.size() == 1) { + if (G()->use_message_database() && use_database) { + G()->td_db()->get_sqlite_pmc()->get( + get_recommended_channels_database_key(), PromiseCreator::lambda([actor_id = actor_id(this)](string value) { + send_closure(actor_id, &ChannelRecommendationManager::on_load_recommended_channels_from_database, + std::move(value)); + })); + } else { + reload_recommended_channels(); + } + } +} + +void ChannelRecommendationManager::fail_load_recommended_channels_queries(Status &&error) { + CHECK(!get_recommended_channels_queries_.empty()); + fail_promises(get_recommended_channels_queries_, std::move(error)); +} + +void ChannelRecommendationManager::finish_load_recommended_channels_queries(int32 total_count, + vector dialog_ids) { + are_recommended_channels_inited_ = true; + auto promises = std::move(get_recommended_channels_queries_); + CHECK(!promises.empty()); + for (auto &promise : promises) { + if (promise) { + promise.set_value( + td_->dialog_manager_->get_chats_object(total_count, dialog_ids, "finish_load_recommended_channels_queries")); + } + } +} + +void ChannelRecommendationManager::on_load_recommended_channels_from_database(string value) { + if (G()->close_flag()) { + return fail_load_recommended_channels_queries(G()->close_status()); + } + + if (value.empty()) { + return reload_recommended_channels(); + } + if (log_event_parse(recommended_channels_, value).is_error()) { + recommended_channels_ = {}; + G()->td_db()->get_sqlite_pmc()->erase(get_recommended_channels_database_key(), Auto()); + return reload_recommended_channels(); + } + Dependencies dependencies; + for (auto dialog_id : recommended_channels_.dialog_ids_) { + dependencies.add_dialog_and_dependencies(dialog_id); + } + if (!dependencies.resolve_force(td_, "on_load_recommended_channels_from_database") || + !are_suitable_recommended_dialogs(recommended_channels_)) { + recommended_channels_ = {}; + G()->td_db()->get_sqlite_pmc()->erase(get_recommended_channels_database_key(), Auto()); + return reload_recommended_channels(); + } + + auto next_reload_time = recommended_channels_.next_reload_time_; + finish_load_recommended_channels_queries(recommended_channels_.total_count_, recommended_channels_.dialog_ids_); + + if (next_reload_time <= Time::now()) { + load_recommended_channels(false, Auto()); + } +} + +void ChannelRecommendationManager::reload_recommended_channels() { + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this)](Result>>> &&result) { + send_closure(actor_id, &ChannelRecommendationManager::on_get_recommended_channels, std::move(result)); + }); + td_->create_handler(std::move(query_promise))->send(ChannelId()); +} + +void ChannelRecommendationManager::on_get_recommended_channels( + Result>>> &&r_chats) { + G()->ignore_result_if_closing(r_chats); + + if (r_chats.is_error()) { + return fail_load_recommended_channels_queries(r_chats.move_as_error()); + } + + auto chats = r_chats.move_as_ok(); + auto total_count = chats.first; + auto channel_ids = td_->chat_manager_->get_channel_ids(std::move(chats.second), "on_get_recommended_channels"); + vector dialog_ids; + if (total_count < static_cast(channel_ids.size())) { + LOG(ERROR) << "Receive total_count = " << total_count << " and " << channel_ids.size() << " recommended chats"; + total_count = static_cast(channel_ids.size()); + } + for (auto recommended_channel_id : channel_ids) { + auto recommended_dialog_id = DialogId(recommended_channel_id); + td_->dialog_manager_->force_create_dialog(recommended_dialog_id, "on_get_recommended_channels"); + if (is_suitable_recommended_channel(recommended_channel_id)) { + dialog_ids.push_back(recommended_dialog_id); + } else { + total_count--; + } + } + recommended_channels_.total_count_ = total_count; + recommended_channels_.dialog_ids_ = dialog_ids; + recommended_channels_.next_reload_time_ = Time::now() + CHANNEL_RECOMMENDATIONS_CACHE_TIME; + + if (G()->use_message_database()) { + G()->td_db()->get_sqlite_pmc()->set(get_recommended_channels_database_key(), + log_event_store(recommended_channels_).as_slice().str(), Promise()); + } + + finish_load_recommended_channels_queries(total_count, std::move(dialog_ids)); +} + void ChannelRecommendationManager::get_channel_recommendations( DialogId dialog_id, bool return_local, Promise> &&chats_promise, Promise> &&count_promise) { diff --git a/td/telegram/ChannelRecommendationManager.h b/td/telegram/ChannelRecommendationManager.h index 4fb431ce4..e21a1487f 100644 --- a/td/telegram/ChannelRecommendationManager.h +++ b/td/telegram/ChannelRecommendationManager.h @@ -28,6 +28,8 @@ class ChannelRecommendationManager final : public Actor { public: ChannelRecommendationManager(Td *td, ActorShared<> parent); + void get_recommended_channels(Promise> &&promise); + void get_channel_recommendations(DialogId dialog_id, bool return_local, Promise> &&chats_promise, Promise> &&count_promise); @@ -57,6 +59,20 @@ class ChannelRecommendationManager final : public Actor { bool are_suitable_recommended_dialogs(const RecommendedDialogs &recommended_dialogs) const; + static string get_recommended_channels_database_key(); + + void load_recommended_channels(bool use_database, Promise> &&promise); + + void fail_load_recommended_channels_queries(Status &&error); + + void finish_load_recommended_channels_queries(int32 total_count, vector dialog_ids); + + void on_load_recommended_channels_from_database(string value); + + void reload_recommended_channels(); + + void on_get_recommended_channels(Result>>> &&r_chats); + static string get_channel_recommendations_database_key(ChannelId channel_id); void load_channel_recommendations(ChannelId channel_id, bool use_database, bool return_local, @@ -81,6 +97,10 @@ class ChannelRecommendationManager final : public Actor { FlatHashMap>>, ChannelIdHash> get_channel_recommendation_count_queries_[2]; + RecommendedDialogs recommended_channels_; + vector>> get_recommended_channels_queries_; + bool are_recommended_channels_inited_ = false; + Td *td_; ActorShared<> parent_; }; diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 6dc2c9978..b2fadb4dc 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -5087,6 +5087,12 @@ void Td::on_request(uint64 id, const td_api::clearAutosaveSettingsExceptions &re autosave_manager_->clear_autosave_settings_exceptions(std::move(promise)); } +void Td::on_request(uint64 id, const td_api::getRecommendedChats &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + channel_recommendation_manager_->get_recommended_channels(std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::getChatSimilarChats &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); diff --git a/td/telegram/Td.h b/td/telegram/Td.h index ecf6754fc..43a1cf047 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -689,6 +689,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::clearAutosaveSettingsExceptions &request); + void on_request(uint64 id, const td_api::getRecommendedChats &request); + void on_request(uint64 id, const td_api::getChatSimilarChats &request); void on_request(uint64 id, const td_api::getChatSimilarChatCount &request); diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index a2cd3b44a..dbe0b8295 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -5490,6 +5490,8 @@ class CliClient final : public Actor { ChatId chat_id; get_args(args, chat_id); send_request(td_api::make_object(chat_id)); + } else if (op == "grc") { + send_request(td_api::make_object()); } else if (op == "gcsc") { ChatId chat_id; get_args(args, chat_id);