// // 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/DialogFilterManager.h" #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" #include "td/telegram/ContactsManager.h" #include "td/telegram/DialogFilter.h" #include "td/telegram/DialogFilter.hpp" #include "td/telegram/DialogFilterInviteLink.h" #include "td/telegram/Global.h" #include "td/telegram/LinkManager.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/OptionManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" #include "td/telegram/Version.h" #include "td/actor/MultiPromise.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" #include "td/utils/FlatHashMap.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/Random.h" #include "td/utils/tl_helpers.h" #include <unordered_set> namespace td { class GetDialogFiltersQuery final : public Td::ResultHandler { Promise<vector<tl_object_ptr<telegram_api::DialogFilter>>> promise_; public: explicit GetDialogFiltersQuery(Promise<vector<tl_object_ptr<telegram_api::DialogFilter>>> &&promise) : promise_(std::move(promise)) { } void send() { send_query(G()->net_query_creator().create(telegram_api::messages_getDialogFilters())); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result<telegram_api::messages_getDialogFilters>(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } promise_.set_value(result_ptr.move_as_ok()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class UpdateDialogFilterQuery final : public Td::ResultHandler { Promise<Unit> promise_; public: explicit UpdateDialogFilterQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) { } void send(DialogFilterId dialog_filter_id, telegram_api::object_ptr<telegram_api::DialogFilter> filter) { int32 flags = 0; if (filter != nullptr) { flags |= telegram_api::messages_updateDialogFilter::FILTER_MASK; } send_query(G()->net_query_creator().create( telegram_api::messages_updateDialogFilter(flags, dialog_filter_id.get(), std::move(filter)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result<telegram_api::messages_updateDialogFilter>(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } LOG(INFO) << "Receive result for UpdateDialogFilterQuery: " << result_ptr.ok(); promise_.set_value(Unit()); } void on_error(Status status) final { LOG(ERROR) << "Receive error for UpdateDialogFilterQuery: " << status; promise_.set_error(std::move(status)); } }; class UpdateDialogFiltersOrderQuery final : public Td::ResultHandler { Promise<Unit> promise_; public: explicit UpdateDialogFiltersOrderQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) { } void send(const vector<DialogFilterId> &dialog_filter_ids, int32 main_dialog_list_position) { auto filter_ids = transform(dialog_filter_ids, [](auto dialog_filter_id) { return dialog_filter_id.get(); }); CHECK(0 <= main_dialog_list_position); CHECK(main_dialog_list_position <= static_cast<int32>(filter_ids.size())); filter_ids.insert(filter_ids.begin() + main_dialog_list_position, 0); send_query(G()->net_query_creator().create(telegram_api::messages_updateDialogFiltersOrder(std::move(filter_ids)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result<telegram_api::messages_updateDialogFiltersOrder>(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } LOG(INFO) << "Receive result for UpdateDialogFiltersOrderQuery: " << result_ptr.ok(); promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class ExportChatlistInviteQuery final : public Td::ResultHandler { Promise<td_api::object_ptr<td_api::chatFolderInviteLink>> promise_; public: explicit ExportChatlistInviteQuery(Promise<td_api::object_ptr<td_api::chatFolderInviteLink>> &&promise) : promise_(std::move(promise)) { } void send(DialogFilterId dialog_filter_id, const string &title, vector<tl_object_ptr<telegram_api::InputPeer>> &&input_peers) { send_query(G()->net_query_creator().create(telegram_api::chatlists_exportChatlistInvite( dialog_filter_id.get_input_chatlist(), title, std::move(input_peers)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result<telegram_api::chatlists_exportChatlistInvite>(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 ExportChatlistInviteQuery: " << to_string(ptr); td_->dialog_filter_manager_->on_get_dialog_filter(std::move(ptr->filter_)); promise_.set_value(DialogFilterInviteLink(td_, std::move(ptr->invite_)).get_chat_folder_invite_link_object(td_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetExportedChatlistInvitesQuery final : public Td::ResultHandler { Promise<td_api::object_ptr<td_api::chatFolderInviteLinks>> promise_; DialogFilterId dialog_filter_id_; public: explicit GetExportedChatlistInvitesQuery(Promise<td_api::object_ptr<td_api::chatFolderInviteLinks>> &&promise) : promise_(std::move(promise)) { } void send(DialogFilterId dialog_filter_id) { dialog_filter_id_ = dialog_filter_id; send_query(G()->net_query_creator().create( telegram_api::chatlists_getExportedInvites(dialog_filter_id.get_input_chatlist()))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result<telegram_api::chatlists_getExportedInvites>(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 GetExportedChatlistInvitesQuery: " << to_string(ptr); td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetExportedChatlistInvitesQuery"); td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetExportedChatlistInvitesQuery"); auto result = td_api::make_object<td_api::chatFolderInviteLinks>(); for (auto &invite : ptr->invites_) { result->invite_links_.push_back( DialogFilterInviteLink(td_, std::move(invite)).get_chat_folder_invite_link_object(td_)); } td_->dialog_filter_manager_->set_dialog_filter_has_my_invite_links(dialog_filter_id_, !result->invite_links_.empty()); promise_.set_value(std::move(result)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class EditExportedChatlistInviteQuery final : public Td::ResultHandler { Promise<td_api::object_ptr<td_api::chatFolderInviteLink>> promise_; public: explicit EditExportedChatlistInviteQuery(Promise<td_api::object_ptr<td_api::chatFolderInviteLink>> &&promise) : promise_(std::move(promise)) { } void send(DialogFilterId dialog_filter_id, const string &slug, const string &title, vector<tl_object_ptr<telegram_api::InputPeer>> &&input_peers) { int32 flags = telegram_api::chatlists_editExportedInvite::TITLE_MASK | telegram_api::chatlists_editExportedInvite::PEERS_MASK; send_query(G()->net_query_creator().create(telegram_api::chatlists_editExportedInvite( flags, dialog_filter_id.get_input_chatlist(), slug, title, std::move(input_peers)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result<telegram_api::chatlists_editExportedInvite>(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 EditExportedChatlistInviteQuery: " << to_string(ptr); promise_.set_value(DialogFilterInviteLink(td_, std::move(ptr)).get_chat_folder_invite_link_object(td_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class DeleteExportedChatlistInviteQuery final : public Td::ResultHandler { Promise<Unit> promise_; public: explicit DeleteExportedChatlistInviteQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) { } void send(DialogFilterId dialog_filter_id, const string &slug) { send_query(G()->net_query_creator().create( telegram_api::chatlists_deleteExportedInvite(dialog_filter_id.get_input_chatlist(), slug))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result<telegram_api::chatlists_deleteExportedInvite>(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 DeleteExportedChatlistInviteQuery: " << ptr; promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class LeaveChatlistQuery final : public Td::ResultHandler { Promise<Unit> promise_; public: explicit LeaveChatlistQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) { } void send(DialogFilterId dialog_filter_id) { send_query(G()->net_query_creator().create(telegram_api::chatlists_leaveChatlist( dialog_filter_id.get_input_chatlist(), vector<telegram_api::object_ptr<telegram_api::InputPeer>>()))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result<telegram_api::chatlists_leaveChatlist>(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 LeaveChatlistQuery: " << to_string(ptr); // must be set before updates are processed to drop are_dialog_filters_being_synchronized_ promise_.set_value(Unit()); td_->updates_manager_->on_get_updates(std::move(ptr), Promise<Unit>()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetLeaveChatlistSuggestionsQuery final : public Td::ResultHandler { Promise<vector<telegram_api::object_ptr<telegram_api::Peer>>> promise_; public: explicit GetLeaveChatlistSuggestionsQuery(Promise<vector<telegram_api::object_ptr<telegram_api::Peer>>> &&promise) : promise_(std::move(promise)) { } void send(DialogFilterId dialog_filter_id) { send_query(G()->net_query_creator().create( telegram_api::chatlists_getLeaveChatlistSuggestions(dialog_filter_id.get_input_chatlist()))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result<telegram_api::chatlists_getLeaveChatlistSuggestions>(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 GetLeaveChatlistSuggestionsQuery: " << to_string(ptr); promise_.set_value(std::move(ptr)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class CheckChatlistInviteQuery final : public Td::ResultHandler { Promise<td_api::object_ptr<td_api::chatFolderInviteLinkInfo>> promise_; string invite_link_; public: explicit CheckChatlistInviteQuery(Promise<td_api::object_ptr<td_api::chatFolderInviteLinkInfo>> &&promise) : promise_(std::move(promise)) { } void send(const string &invite_link) { invite_link_ = invite_link; send_query(G()->net_query_creator().create( telegram_api::chatlists_checkChatlistInvite(LinkManager::get_dialog_filter_invite_link_slug(invite_link_)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result<telegram_api::chatlists_checkChatlistInvite>(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } td_->dialog_filter_manager_->on_get_chatlist_invite(invite_link_, result_ptr.move_as_ok(), std::move(promise_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class JoinChatlistInviteQuery final : public Td::ResultHandler { Promise<Unit> promise_; public: explicit JoinChatlistInviteQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) { } void send(const string &invite_link, vector<DialogId> dialog_ids) { send_query(G()->net_query_creator().create(telegram_api::chatlists_joinChatlistInvite( LinkManager::get_dialog_filter_invite_link_slug(invite_link), td_->messages_manager_->get_input_peers(dialog_ids, AccessRights::Know)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result<telegram_api::chatlists_joinChatlistInvite>(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 JoinChatlistInviteQuery: " << 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 GetChatlistUpdatesQuery final : public Td::ResultHandler { Promise<td_api::object_ptr<td_api::chats>> promise_; public: explicit GetChatlistUpdatesQuery(Promise<td_api::object_ptr<td_api::chats>> &&promise) : promise_(std::move(promise)) { } void send(DialogFilterId dialog_filter_id) { send_query(G()->net_query_creator().create( telegram_api::chatlists_getChatlistUpdates(dialog_filter_id.get_input_chatlist()))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result<telegram_api::chatlists_getChatlistUpdates>(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 GetChatlistUpdatesQuery: " << to_string(ptr); td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetChatlistUpdatesQuery"); td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetChatlistUpdatesQuery"); auto missing_dialog_ids = td_->messages_manager_->get_peers_dialog_ids(std::move(ptr->missing_peers_), true); promise_.set_value(td_->messages_manager_->get_chats_object(-1, missing_dialog_ids, "GetChatlistUpdatesQuery")); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class JoinChatlistUpdatesQuery final : public Td::ResultHandler { Promise<Unit> promise_; public: explicit JoinChatlistUpdatesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) { } void send(DialogFilterId dialog_filter_id, vector<DialogId> dialog_ids) { send_query(G()->net_query_creator().create(telegram_api::chatlists_joinChatlistUpdates( dialog_filter_id.get_input_chatlist(), td_->messages_manager_->get_input_peers(dialog_ids, AccessRights::Know)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result<telegram_api::chatlists_joinChatlistUpdates>(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 JoinChatlistUpdatesQuery: " << 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 HideChatlistUpdatesQuery final : public Td::ResultHandler { Promise<Unit> promise_; public: explicit HideChatlistUpdatesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) { } void send(DialogFilterId dialog_filter_id) { send_query(G()->net_query_creator().create( telegram_api::chatlists_hideChatlistUpdates(dialog_filter_id.get_input_chatlist()))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result<telegram_api::chatlists_hideChatlistUpdates>(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 HideChatlistUpdatesQuery: " << ptr; promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetDialogsQuery final : public Td::ResultHandler { Promise<Unit> promise_; bool is_single_ = false; public: explicit GetDialogsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) { } void send(vector<InputDialogId> input_dialog_ids) { CHECK(!input_dialog_ids.empty()); CHECK(input_dialog_ids.size() <= 100); is_single_ = input_dialog_ids.size() == 1; auto input_dialog_peers = InputDialogId::get_input_dialog_peers(input_dialog_ids); CHECK(input_dialog_peers.size() == input_dialog_ids.size()); send_query(G()->net_query_creator().create(telegram_api::messages_getPeerDialogs(std::move(input_dialog_peers)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result<telegram_api::messages_getPeerDialogs>(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 GetDialogsQuery: " << to_string(result); td_->contacts_manager_->on_get_users(std::move(result->users_), "GetDialogsQuery"); td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetDialogsQuery"); td_->messages_manager_->on_get_dialogs(FolderId(), std::move(result->dialogs_), -1, std::move(result->messages_), std::move(promise_)); } void on_error(Status status) final { if (is_single_ && status.code() == 400) { return promise_.set_value(Unit()); } promise_.set_error(std::move(status)); } }; class GetSuggestedDialogFiltersQuery final : public Td::ResultHandler { Promise<vector<telegram_api::object_ptr<telegram_api::dialogFilterSuggested>>> promise_; public: explicit GetSuggestedDialogFiltersQuery( Promise<vector<telegram_api::object_ptr<telegram_api::dialogFilterSuggested>>> &&promise) : promise_(std::move(promise)) { } void send() { send_query(G()->net_query_creator().create(telegram_api::messages_getSuggestedDialogFilters())); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result<telegram_api::messages_getSuggestedDialogFilters>(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } promise_.set_value(result_ptr.move_as_ok()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; DialogFilterManager::DialogFilterManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { } DialogFilterManager::~DialogFilterManager() = default; void DialogFilterManager::hangup() { fail_promises(dialog_filter_reload_queries_, Global::request_aborted_error()); stop(); } void DialogFilterManager::tear_down() { parent_.reset(); } class DialogFilterManager::DialogFiltersLogEvent { public: int32 server_main_dialog_list_position = 0; int32 main_dialog_list_position = 0; int32 updated_date = 0; const vector<unique_ptr<DialogFilter>> *server_dialog_filters_in; const vector<unique_ptr<DialogFilter>> *dialog_filters_in; vector<unique_ptr<DialogFilter>> server_dialog_filters_out; vector<unique_ptr<DialogFilter>> dialog_filters_out; template <class StorerT> void store(StorerT &storer) const { bool has_server_dialog_filters = !server_dialog_filters_in->empty(); bool has_dialog_filters = !dialog_filters_in->empty(); bool has_server_main_dialog_list_position = server_main_dialog_list_position != 0; bool has_main_dialog_list_position = main_dialog_list_position != 0; BEGIN_STORE_FLAGS(); STORE_FLAG(has_server_dialog_filters); STORE_FLAG(has_dialog_filters); STORE_FLAG(has_server_main_dialog_list_position); STORE_FLAG(has_main_dialog_list_position); END_STORE_FLAGS(); td::store(updated_date, storer); if (has_server_dialog_filters) { td::store(*server_dialog_filters_in, storer); } if (has_dialog_filters) { td::store(*dialog_filters_in, storer); } if (has_server_main_dialog_list_position) { td::store(server_main_dialog_list_position, storer); } if (has_main_dialog_list_position) { td::store(main_dialog_list_position, storer); } } template <class ParserT> void parse(ParserT &parser) { bool has_server_dialog_filters = true; bool has_dialog_filters = true; bool has_server_main_dialog_list_position = false; bool has_main_dialog_list_position = false; if (parser.version() >= static_cast<int32>(Version::AddMainDialogListPosition)) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_server_dialog_filters); PARSE_FLAG(has_dialog_filters); PARSE_FLAG(has_server_main_dialog_list_position); PARSE_FLAG(has_main_dialog_list_position); END_PARSE_FLAGS(); } td::parse(updated_date, parser); if (has_server_dialog_filters) { td::parse(server_dialog_filters_out, parser); } if (has_dialog_filters) { td::parse(dialog_filters_out, parser); } if (has_server_main_dialog_list_position) { td::parse(server_main_dialog_list_position, parser); } if (has_main_dialog_list_position) { td::parse(main_dialog_list_position, parser); } } }; void DialogFilterManager::init() { if (is_inited_) { return; } is_inited_ = true; bool is_authorized = td_->auth_manager_->is_authorized(); bool was_authorized_user = td_->auth_manager_->was_authorized() && !td_->auth_manager_->is_bot(); if (is_authorized && td_->auth_manager_->is_bot()) { disable_get_dialog_filter_ = true; } if (was_authorized_user) { auto dialog_filters = G()->td_db()->get_binlog_pmc()->get("dialog_filters"); if (!dialog_filters.empty()) { DialogFiltersLogEvent log_event; if (log_event_parse(log_event, dialog_filters).is_ok()) { server_main_dialog_list_position_ = log_event.server_main_dialog_list_position; main_dialog_list_position_ = log_event.main_dialog_list_position; if (!td_->option_manager_->get_option_boolean("is_premium") && (server_main_dialog_list_position_ != 0 || main_dialog_list_position_ != 0)) { LOG(INFO) << "Ignore main chat list position " << server_main_dialog_list_position_ << '/' << main_dialog_list_position_; server_main_dialog_list_position_ = 0; main_dialog_list_position_ = 0; } dialog_filters_updated_date_ = td_->ignore_background_updates() ? 0 : log_event.updated_date; std::unordered_set<DialogFilterId, DialogFilterIdHash> server_dialog_filter_ids; for (auto &dialog_filter : log_event.server_dialog_filters_out) { if (dialog_filter->get_dialog_filter_id().is_valid() && server_dialog_filter_ids.insert(dialog_filter->get_dialog_filter_id()).second) { server_dialog_filters_.push_back(std::move(dialog_filter)); } } for (auto &dialog_filter : log_event.dialog_filters_out) { add_dialog_filter(std::move(dialog_filter), false, "binlog"); } LOG(INFO) << "Loaded server chat folders " << DialogFilter::get_dialog_filter_ids(server_dialog_filters_, server_main_dialog_list_position_) << " and local chat folders " << DialogFilter::get_dialog_filter_ids(dialog_filters_, main_dialog_list_position_); } else { LOG(ERROR) << "Failed to parse chat folders from binlog"; } } send_update_chat_folders(); // always send updateChatFolders if (is_authorized) { if (need_synchronize_dialog_filters()) { reload_dialog_filters(); } else { auto cache_time = get_dialog_filters_cache_time(); schedule_dialog_filters_reload(cache_time - max(0, G()->unix_time() - dialog_filters_updated_date_)); } } } } void DialogFilterManager::on_authorization_success() { CHECK(td_->auth_manager_->is_authorized()); if (td_->auth_manager_->is_bot()) { disable_get_dialog_filter_ = true; return; } reload_dialog_filters(); } void DialogFilterManager::on_update_dialog_filters(Promise<Unit> &&promise) { if (td_->auth_manager_->is_bot()) { // just in case return promise.set_value(Unit()); } schedule_reload_dialog_filters(std::move(promise)); } void DialogFilterManager::schedule_reload_dialog_filters(Promise<Unit> &&promise) { schedule_dialog_filters_reload(0.0); dialog_filter_reload_queries_.push_back(std::move(promise)); } bool DialogFilterManager::have_dialog_filters() const { return !dialog_filters_.empty(); } vector<FolderId> DialogFilterManager::get_dialog_filter_folder_ids(DialogFilterId dialog_filter_id) const { const auto *dialog_filter = get_dialog_filter(dialog_filter_id); CHECK(dialog_filter != nullptr); return dialog_filter->get_folder_ids(); } vector<DialogFilterId> DialogFilterManager::get_dialog_filters_to_add_dialog(DialogId dialog_id) const { vector<DialogFilterId> result; for (const auto &dialog_filter : dialog_filters_) { if (dialog_filter->can_include_dialog(dialog_id)) { result.push_back(dialog_filter->get_dialog_filter_id()); } } return result; } bool DialogFilterManager::need_dialog_in_filter(DialogFilterId dialog_filter_id, const DialogFilterDialogInfo &dialog_info) const { const auto *dialog_filter = get_dialog_filter(dialog_filter_id); CHECK(dialog_filter != nullptr); return dialog_filter->need_dialog(td_, dialog_info); } bool DialogFilterManager::is_dialog_pinned(DialogFilterId dialog_filter_id, DialogId dialog_id) const { const auto *dialog_filter = get_dialog_filter(dialog_filter_id); return dialog_filter != nullptr && dialog_filter->is_dialog_pinned(dialog_id); } const vector<InputDialogId> &DialogFilterManager::get_pinned_input_dialog_ids(DialogFilterId dialog_filter_id) const { auto *dialog_filter = get_dialog_filter(dialog_filter_id); CHECK(dialog_filter != nullptr); return dialog_filter->get_pinned_input_dialog_ids(); } vector<DialogId> DialogFilterManager::get_pinned_dialog_ids(DialogFilterId dialog_filter_id) const { auto *dialog_filter = get_dialog_filter(dialog_filter_id); if (dialog_filter == nullptr) { return {}; } return InputDialogId::get_dialog_ids(dialog_filter->get_pinned_input_dialog_ids()); } Status DialogFilterManager::set_dialog_is_pinned(DialogFilterId dialog_filter_id, InputDialogId input_dialog_id, bool is_pinned) { CHECK(is_update_chat_folders_sent_); auto old_dialog_filter = get_dialog_filter(dialog_filter_id); CHECK(old_dialog_filter != nullptr); auto new_dialog_filter = make_unique<DialogFilter>(*old_dialog_filter); new_dialog_filter->set_dialog_is_pinned(input_dialog_id, is_pinned); TRY_STATUS(new_dialog_filter->check_limits()); new_dialog_filter->sort_input_dialog_ids(td_, "set_dialog_is_pinned"); do_edit_dialog_filter(std::move(new_dialog_filter), input_dialog_id.get_dialog_id().get_type() != DialogType::SecretChat, "set_dialog_is_pinned"); return Status::OK(); } Status DialogFilterManager::set_pinned_dialog_ids(DialogFilterId dialog_filter_id, vector<InputDialogId> input_dialog_ids, bool need_synchronize) { CHECK(is_update_chat_folders_sent_); auto old_dialog_filter = get_dialog_filter(dialog_filter_id); CHECK(old_dialog_filter != nullptr); auto new_dialog_filter = make_unique<DialogFilter>(*old_dialog_filter); new_dialog_filter->set_pinned_dialog_ids(std::move(input_dialog_ids)); TRY_STATUS(new_dialog_filter->check_limits()); new_dialog_filter->sort_input_dialog_ids(td_, "set_pinned_dialog_ids"); do_edit_dialog_filter(std::move(new_dialog_filter), need_synchronize, "set_pinned_dialog_ids"); return Status::OK(); } Status DialogFilterManager::add_dialog(DialogFilterId dialog_filter_id, InputDialogId input_dialog_id) { CHECK(is_update_chat_folders_sent_); auto old_dialog_filter = get_dialog_filter(dialog_filter_id); CHECK(old_dialog_filter != nullptr); if (old_dialog_filter->is_dialog_included(input_dialog_id.get_dialog_id())) { return Status::OK(); } auto new_dialog_filter = make_unique<DialogFilter>(*old_dialog_filter); new_dialog_filter->include_dialog(input_dialog_id); TRY_STATUS(new_dialog_filter->check_limits()); new_dialog_filter->sort_input_dialog_ids(td_, "add_dialog"); do_edit_dialog_filter(std::move(new_dialog_filter), input_dialog_id.get_dialog_id().get_type() != DialogType::SecretChat, "add_dialog"); return Status::OK(); } bool DialogFilterManager::is_recommended_dialog_filter(const DialogFilter *dialog_filter) { for (const auto &recommended_dialog_filter : recommended_dialog_filters_) { if (DialogFilter::are_similar(*recommended_dialog_filter.dialog_filter, *dialog_filter)) { return true; } } return false; } td_api::object_ptr<td_api::chatFolder> DialogFilterManager::get_chat_folder_object(DialogFilterId dialog_filter_id) { CHECK(!td_->auth_manager_->is_bot()); auto dialog_filter = get_dialog_filter(dialog_filter_id); if (dialog_filter == nullptr) { return nullptr; } return get_chat_folder_object(dialog_filter); } td_api::object_ptr<td_api::chatFolder> DialogFilterManager::get_chat_folder_object(const DialogFilter *dialog_filter) { DialogFilterId dialog_filter_id = dialog_filter->get_dialog_filter_id(); vector<DialogId> left_dialog_ids; vector<DialogId> unknown_dialog_ids; dialog_filter->for_each_dialog([&](const InputDialogId &input_dialog_id) { auto dialog_id = input_dialog_id.get_dialog_id(); if (td_->messages_manager_->is_dialog_in_dialog_list(dialog_id)) { return; } if (td_->messages_manager_->have_dialog(dialog_id)) { LOG(INFO) << "Skip nonjoined " << dialog_id << " from " << dialog_filter_id; unknown_dialog_ids.push_back(dialog_id); left_dialog_ids.push_back(dialog_id); } else { // possible if the chat folder has just been edited from another device LOG(ERROR) << "Can't find " << dialog_id << " from " << dialog_filter_id; unknown_dialog_ids.push_back(dialog_id); } }); auto result = dialog_filter->get_chat_folder_object(unknown_dialog_ids); if (dialog_filter_id.is_valid()) { delete_dialogs_from_filter(dialog_filter, std::move(left_dialog_ids), "get_chat_folder_object"); } return result; } void DialogFilterManager::get_recommended_dialog_filters( Promise<td_api::object_ptr<td_api::recommendedChatFolders>> &&promise) { CHECK(!td_->auth_manager_->is_bot()); auto query_promise = PromiseCreator::lambda( [actor_id = actor_id(this), promise = std::move(promise)]( Result<vector<telegram_api::object_ptr<telegram_api::dialogFilterSuggested>>> result) mutable { send_closure(actor_id, &DialogFilterManager::on_get_recommended_dialog_filters, std::move(result), std::move(promise)); }); td_->create_handler<GetSuggestedDialogFiltersQuery>(std::move(query_promise))->send(); } void DialogFilterManager::on_get_recommended_dialog_filters( Result<vector<telegram_api::object_ptr<telegram_api::dialogFilterSuggested>>> result, Promise<td_api::object_ptr<td_api::recommendedChatFolders>> &&promise) { if (result.is_error()) { return promise.set_error(result.move_as_error()); } CHECK(!td_->auth_manager_->is_bot()); auto suggested_filters = result.move_as_ok(); MultiPromiseActorSafe mpas{"LoadRecommendedFiltersMultiPromiseActor"}; mpas.add_promise(Promise<Unit>()); auto lock = mpas.get_promise(); vector<RecommendedDialogFilter> filters; for (auto &suggested_filter : suggested_filters) { RecommendedDialogFilter recommended_dialog_filter; recommended_dialog_filter.dialog_filter = DialogFilter::get_dialog_filter(std::move(suggested_filter->filter_), false); if (recommended_dialog_filter.dialog_filter == nullptr) { continue; } load_dialog_filter(recommended_dialog_filter.dialog_filter.get(), mpas.get_promise()); recommended_dialog_filter.description = std::move(suggested_filter->description_); filters.push_back(std::move(recommended_dialog_filter)); } mpas.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), filters = std::move(filters), promise = std::move(promise)](Result<Unit> &&result) mutable { send_closure(actor_id, &DialogFilterManager::on_load_recommended_dialog_filters, std::move(result), std::move(filters), std::move(promise)); })); lock.set_value(Unit()); } void DialogFilterManager::on_load_recommended_dialog_filters( Result<Unit> &&result, vector<RecommendedDialogFilter> &&filters, Promise<td_api::object_ptr<td_api::recommendedChatFolders>> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); if (result.is_error()) { return promise.set_error(result.move_as_error()); } CHECK(!td_->auth_manager_->is_bot()); auto chat_folders = transform(filters, [this](const RecommendedDialogFilter &recommended_dialog_filter) { return td_api::make_object<td_api::recommendedChatFolder>( get_chat_folder_object(recommended_dialog_filter.dialog_filter.get()), recommended_dialog_filter.description); }); recommended_dialog_filters_ = std::move(filters); promise.set_value(td_api::make_object<td_api::recommendedChatFolders>(std::move(chat_folders))); } void DialogFilterManager::get_dialog_filter(DialogFilterId dialog_filter_id, Promise<td_api::object_ptr<td_api::chatFolder>> &&promise) { CHECK(!td_->auth_manager_->is_bot()); if (!dialog_filter_id.is_valid()) { return promise.set_error(Status::Error(400, "Invalid chat folder identifier specified")); } auto dialog_filter = get_dialog_filter(dialog_filter_id); if (dialog_filter == nullptr) { return promise.set_value(nullptr); } auto load_promise = PromiseCreator::lambda( [actor_id = actor_id(this), dialog_filter_id, promise = std::move(promise)](Result<Unit> &&result) mutable { if (result.is_error()) { return promise.set_error(result.move_as_error()); } send_closure(actor_id, &DialogFilterManager::on_load_dialog_filter, dialog_filter_id, std::move(promise)); }); load_dialog_filter(dialog_filter, std::move(load_promise)); } void DialogFilterManager::on_load_dialog_filter(DialogFilterId dialog_filter_id, Promise<td_api::object_ptr<td_api::chatFolder>> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); promise.set_value(get_chat_folder_object(dialog_filter_id)); } void DialogFilterManager::load_dialog_filter(const DialogFilter *dialog_filter, Promise<Unit> &&promise) { CHECK(!td_->auth_manager_->is_bot()); vector<InputDialogId> needed_dialog_ids; dialog_filter->for_each_dialog([&](const InputDialogId &input_dialog_id) { if (!td_->messages_manager_->have_dialog(input_dialog_id.get_dialog_id())) { needed_dialog_ids.push_back(input_dialog_id); } }); vector<InputDialogId> input_dialog_ids; for (const auto &input_dialog_id : needed_dialog_ids) { auto dialog_id = input_dialog_id.get_dialog_id(); // TODO load dialogs asynchronously if (!td_->messages_manager_->have_dialog_force(dialog_id, "load_dialog_filter")) { if (dialog_id.get_type() == DialogType::SecretChat) { if (td_->messages_manager_->have_dialog_info_force(dialog_id)) { td_->messages_manager_->force_create_dialog(dialog_id, "load_dialog_filter"); } } else { input_dialog_ids.push_back(input_dialog_id); } } } if (!input_dialog_ids.empty()) { return load_dialog_filter_dialogs(dialog_filter->get_dialog_filter_id(), std::move(input_dialog_ids), std::move(promise)); } promise.set_value(Unit()); } void DialogFilterManager::load_dialog_filter_dialogs(DialogFilterId dialog_filter_id, vector<InputDialogId> &&input_dialog_ids, Promise<Unit> &&promise) { const size_t MAX_SLICE_SIZE = 100; // server side limit MultiPromiseActorSafe mpas{"GetFilterDialogsOnServerMultiPromiseActor"}; mpas.add_promise(std::move(promise)); auto lock = mpas.get_promise(); for (size_t i = 0; i < input_dialog_ids.size(); i += MAX_SLICE_SIZE) { auto end_i = i + MAX_SLICE_SIZE; auto end = end_i < input_dialog_ids.size() ? input_dialog_ids.begin() + end_i : input_dialog_ids.end(); vector<InputDialogId> slice_input_dialog_ids = {input_dialog_ids.begin() + i, end}; auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_filter_id, dialog_ids = InputDialogId::get_dialog_ids(slice_input_dialog_ids), promise = mpas.get_promise()](Result<Unit> &&result) mutable { if (result.is_error()) { return promise.set_error(result.move_as_error()); } send_closure(actor_id, &DialogFilterManager::on_load_dialog_filter_dialogs, dialog_filter_id, std::move(dialog_ids), std::move(promise)); }); td_->create_handler<GetDialogsQuery>(std::move(query_promise))->send(std::move(slice_input_dialog_ids)); } lock.set_value(Unit()); } void DialogFilterManager::on_load_dialog_filter_dialogs(DialogFilterId dialog_filter_id, vector<DialogId> &&dialog_ids, Promise<Unit> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); td::remove_if(dialog_ids, [messages_manager = td_->messages_manager_.get()](DialogId dialog_id) { return messages_manager->have_dialog_force(dialog_id, "on_load_dialog_filter_dialogs"); }); if (dialog_ids.empty()) { LOG(INFO) << "All chats from " << dialog_filter_id << " were loaded"; return promise.set_value(Unit()); } LOG(INFO) << "Failed to load chats " << dialog_ids << " from " << dialog_filter_id; auto old_dialog_filter = get_dialog_filter(dialog_filter_id); if (old_dialog_filter == nullptr) { return promise.set_value(Unit()); } CHECK(is_update_chat_folders_sent_); delete_dialogs_from_filter(old_dialog_filter, std::move(dialog_ids), "on_load_dialog_filter_dialogs"); promise.set_value(Unit()); } void DialogFilterManager::delete_dialogs_from_filter(const DialogFilter *dialog_filter, vector<DialogId> &&dialog_ids, const char *source) { if (dialog_ids.empty()) { return; } bool was_valid = dialog_filter->check_limits().is_ok(); auto new_dialog_filter = td::make_unique<DialogFilter>(*dialog_filter); for (auto dialog_id : dialog_ids) { new_dialog_filter->remove_dialog_id(dialog_id); } if (new_dialog_filter->is_empty(false)) { delete_dialog_filter(dialog_filter->get_dialog_filter_id(), vector<DialogId>(), Promise<Unit>()); return; } CHECK(!was_valid || new_dialog_filter->check_limits().is_ok()); if (*new_dialog_filter != *dialog_filter) { LOG(INFO) << "Update " << *dialog_filter << " to " << *new_dialog_filter; do_edit_dialog_filter(std::move(new_dialog_filter), true, "delete_dialogs_from_filter"); } } const DialogFilter *DialogFilterManager::get_server_dialog_filter(DialogFilterId dialog_filter_id) const { CHECK(!disable_get_dialog_filter_); for (const auto &dialog_filter : server_dialog_filters_) { if (dialog_filter->get_dialog_filter_id() == dialog_filter_id) { return dialog_filter.get(); } } return nullptr; } DialogFilter *DialogFilterManager::get_dialog_filter(DialogFilterId dialog_filter_id) { CHECK(!disable_get_dialog_filter_); for (auto &dialog_filter : dialog_filters_) { if (dialog_filter->get_dialog_filter_id() == dialog_filter_id) { return dialog_filter.get(); } } return nullptr; } const DialogFilter *DialogFilterManager::get_dialog_filter(DialogFilterId dialog_filter_id) const { CHECK(!disable_get_dialog_filter_); for (const auto &dialog_filter : dialog_filters_) { if (dialog_filter->get_dialog_filter_id() == dialog_filter_id) { return dialog_filter.get(); } } return nullptr; } int32 DialogFilterManager::get_server_main_dialog_list_position() const { int32 current_position = 0; int32 current_server_position = 0; if (current_position == main_dialog_list_position_) { return current_server_position; } for (const auto &dialog_filter : dialog_filters_) { current_position++; if (!dialog_filter->is_empty(true)) { current_server_position++; } if (current_position == main_dialog_list_position_) { return current_server_position; } } LOG(WARNING) << "Failed to find server position for " << main_dialog_list_position_ << " in chat folders"; return current_server_position; } double DialogFilterManager::get_dialog_filters_cache_time() { return DIALOG_FILTERS_CACHE_TIME * 0.0001 * Random::fast(9000, 11000); } void DialogFilterManager::schedule_dialog_filters_reload(double timeout) { if (td_->auth_manager_->is_bot()) { // just in case return; } if (timeout <= 0) { timeout = 0.0; if (dialog_filters_updated_date_ != 0) { dialog_filters_updated_date_ = 0; save_dialog_filters(); } } LOG(INFO) << "Schedule reload of chat folders in " << timeout; reload_dialog_filters_timeout_.set_callback(std::move(DialogFilterManager::on_reload_dialog_filters_timeout)); reload_dialog_filters_timeout_.set_callback_data(static_cast<void *>(this)); reload_dialog_filters_timeout_.set_timeout_in(timeout); } void DialogFilterManager::on_reload_dialog_filters_timeout(void *dialog_filter_manager_ptr) { if (G()->close_flag()) { return; } auto dialog_filter_manager = static_cast<DialogFilterManager *>(dialog_filter_manager_ptr); send_closure_later(dialog_filter_manager->actor_id(dialog_filter_manager), &DialogFilterManager::reload_dialog_filters); } void DialogFilterManager::reload_dialog_filters() { if (G()->close_flag()) { return; } CHECK(!td_->auth_manager_->is_bot()); if (are_dialog_filters_being_synchronized_ || are_dialog_filters_being_reloaded_) { need_dialog_filters_reload_ = true; return; } LOG(INFO) << "Reload chat folders from server"; are_dialog_filters_being_reloaded_ = true; need_dialog_filters_reload_ = false; auto promise = PromiseCreator::lambda( [actor_id = actor_id(this)](Result<vector<telegram_api::object_ptr<telegram_api::DialogFilter>>> r_filters) { send_closure(actor_id, &DialogFilterManager::on_get_dialog_filters, std::move(r_filters), false); }); td_->create_handler<GetDialogFiltersQuery>(std::move(promise))->send(); } void DialogFilterManager::on_get_dialog_filter(telegram_api::object_ptr<telegram_api::DialogFilter> filter) { CHECK(!td_->auth_manager_->is_bot()); auto new_server_filter = DialogFilter::get_dialog_filter(std::move(filter), true); if (new_server_filter == nullptr) { return; } new_server_filter->sort_input_dialog_ids(td_, "on_get_dialog_filter 1"); auto dialog_filter_id = new_server_filter->get_dialog_filter_id(); auto old_filter = get_dialog_filter(dialog_filter_id); if (old_filter == nullptr) { return; } bool is_server_changed = false; bool is_changed = false; for (auto &old_server_filter : server_dialog_filters_) { if (old_server_filter->get_dialog_filter_id() == dialog_filter_id && *new_server_filter != *old_server_filter) { if (!DialogFilter::are_equivalent(*old_filter, *new_server_filter)) { auto new_filter = DialogFilter::merge_dialog_filter_changes(old_filter, old_server_filter.get(), new_server_filter.get()); new_filter->sort_input_dialog_ids(td_, "on_get_dialog_filter"); if (*new_filter != *old_filter) { is_changed = true; edit_dialog_filter(std::move(new_filter), "on_get_dialog_filter"); } } is_server_changed = true; old_server_filter = std::move(new_server_filter); break; } } if (!is_server_changed) { return; } if (is_changed || !is_update_chat_folders_sent_) { send_update_chat_folders(); } schedule_dialog_filters_reload(get_dialog_filters_cache_time()); save_dialog_filters(); if (need_synchronize_dialog_filters()) { synchronize_dialog_filters(); } } void DialogFilterManager::on_get_dialog_filters( Result<vector<telegram_api::object_ptr<telegram_api::DialogFilter>>> r_filters, bool dummy) { if (G()->close_flag()) { return; } are_dialog_filters_being_reloaded_ = false; CHECK(!td_->auth_manager_->is_bot()); auto promises = std::move(dialog_filter_reload_queries_); dialog_filter_reload_queries_.clear(); if (r_filters.is_error()) { if (!G()->is_expected_error(r_filters.error())) { LOG(WARNING) << "Receive error " << r_filters.error() << " for GetDialogFiltersQuery"; } set_promises(promises); // ignore error need_dialog_filters_reload_ = false; schedule_dialog_filters_reload(Random::fast(60, 5 * 60)); return; } auto filters = r_filters.move_as_ok(); vector<unique_ptr<DialogFilter>> new_server_dialog_filters; LOG(INFO) << "Receive chat folders from server: " << to_string(filters); std::unordered_set<DialogFilterId, DialogFilterIdHash> new_dialog_filter_ids; int32 server_main_dialog_list_position = -1; int32 position = 0; for (auto &filter : filters) { if (filter->get_id() == telegram_api::dialogFilterDefault::ID) { if (server_main_dialog_list_position == -1) { server_main_dialog_list_position = position; } else { LOG(ERROR) << "Receive duplicate dialogFilterDefault"; } continue; } auto dialog_filter = DialogFilter::get_dialog_filter(std::move(filter), true); if (dialog_filter == nullptr) { continue; } if (!new_dialog_filter_ids.insert(dialog_filter->get_dialog_filter_id()).second) { LOG(ERROR) << "Receive duplicate " << dialog_filter->get_dialog_filter_id(); continue; } dialog_filter->sort_input_dialog_ids(td_, "on_get_dialog_filters 1"); new_server_dialog_filters.push_back(std::move(dialog_filter)); position++; } if (server_main_dialog_list_position == -1) { LOG(ERROR) << "Receive no dialogFilterDefault"; server_main_dialog_list_position = 0; } if (server_main_dialog_list_position != 0 && !td_->option_manager_->get_option_boolean("is_premium")) { LOG(INFO) << "Ignore server main chat list position " << server_main_dialog_list_position; server_main_dialog_list_position = 0; } bool is_changed = false; dialog_filters_updated_date_ = G()->unix_time(); if (server_dialog_filters_ != new_server_dialog_filters) { LOG(INFO) << "Change server chat folders from " << DialogFilter::get_dialog_filter_ids(server_dialog_filters_, server_main_dialog_list_position_) << " to " << DialogFilter::get_dialog_filter_ids(new_server_dialog_filters, server_main_dialog_list_position); FlatHashMap<DialogFilterId, const DialogFilter *, DialogFilterIdHash> old_server_dialog_filters; for (const auto &dialog_filter : server_dialog_filters_) { old_server_dialog_filters.emplace(dialog_filter->get_dialog_filter_id(), dialog_filter.get()); } for (const auto &new_server_filter : new_server_dialog_filters) { auto dialog_filter_id = new_server_filter->get_dialog_filter_id(); auto old_filter = get_dialog_filter(dialog_filter_id); auto it = old_server_dialog_filters.find(dialog_filter_id); if (it != old_server_dialog_filters.end()) { auto old_server_filter = it->second; if (*new_server_filter != *old_server_filter) { if (old_filter == nullptr) { // the filter was deleted, don't need to edit it } else { if (DialogFilter::are_equivalent(*old_filter, *new_server_filter)) { // fast path // the filter was edited from this client, nothing to do } else { auto new_filter = DialogFilter::merge_dialog_filter_changes(old_filter, old_server_filter, new_server_filter.get()); new_filter->sort_input_dialog_ids(td_, "on_get_dialog_filters"); if (*new_filter != *old_filter) { is_changed = true; edit_dialog_filter(std::move(new_filter), "on_get_dialog_filters"); } } } } old_server_dialog_filters.erase(it); } else { if (old_filter == nullptr) { // the filter was added from another client is_changed = true; add_dialog_filter(make_unique<DialogFilter>(*new_server_filter), false, "on_get_dialog_filters"); } else { // the filter was added from this client // after that it could be added from another client, or edited from this client, or edited from another client // prefer local value, so do nothing // effectively, ignore edits from other clients, if didn't receive UpdateDialogFilterQuery response } } } vector<DialogFilterId> left_old_server_dialog_filter_ids; for (const auto &dialog_filter : server_dialog_filters_) { if (old_server_dialog_filters.count(dialog_filter->get_dialog_filter_id()) == 0) { left_old_server_dialog_filter_ids.push_back(dialog_filter->get_dialog_filter_id()); } } LOG(INFO) << "Still existing server chat folders: " << left_old_server_dialog_filter_ids; for (auto &old_server_filter : old_server_dialog_filters) { auto dialog_filter_id = old_server_filter.first; // deleted filter auto old_filter = get_dialog_filter(dialog_filter_id); if (old_filter == nullptr) { // the filter was deleted from this client, nothing to do } else { // the filter was deleted from another client // ignore edits done from the current client and just delete the filter is_changed = true; do_delete_dialog_filter(dialog_filter_id, "on_get_dialog_filters"); } } bool is_order_changed = [&] { vector<DialogFilterId> new_server_dialog_filter_ids = DialogFilter::get_dialog_filter_ids(new_server_dialog_filters, -1); CHECK(new_server_dialog_filter_ids.size() >= left_old_server_dialog_filter_ids.size()); new_server_dialog_filter_ids.resize(left_old_server_dialog_filter_ids.size()); return new_server_dialog_filter_ids != left_old_server_dialog_filter_ids; }(); if (is_order_changed) { // if order is changed from this and other clients, prefer order from another client vector<DialogFilterId> new_dialog_filter_order; for (const auto &new_server_filter : new_server_dialog_filters) { auto dialog_filter_id = new_server_filter->get_dialog_filter_id(); if (get_dialog_filter(dialog_filter_id) != nullptr) { new_dialog_filter_order.push_back(dialog_filter_id); } } is_changed = true; DialogFilter::set_dialog_filters_order(dialog_filters_, new_dialog_filter_order); } server_dialog_filters_ = std::move(new_server_dialog_filters); } if (server_main_dialog_list_position_ != server_main_dialog_list_position) { server_main_dialog_list_position_ = server_main_dialog_list_position; int32 main_dialog_list_position = -1; if (server_main_dialog_list_position == 0) { main_dialog_list_position = 0; } else { int32 current_position = 0; int32 current_server_position = 0; for (const auto &dialog_filter : dialog_filters_) { current_position++; if (!dialog_filter->is_empty(true)) { current_server_position++; } if (current_server_position == server_main_dialog_list_position) { main_dialog_list_position = current_position; } } if (main_dialog_list_position == -1) { LOG(INFO) << "Failed to find server position " << server_main_dialog_list_position << " in chat folders"; main_dialog_list_position = static_cast<int32>(dialog_filters_.size()); } } if (main_dialog_list_position != main_dialog_list_position_) { LOG(INFO) << "Change main chat list position from " << main_dialog_list_position_ << " to " << main_dialog_list_position; main_dialog_list_position_ = main_dialog_list_position; is_changed = true; } } if (is_changed || !is_update_chat_folders_sent_) { send_update_chat_folders(); } schedule_dialog_filters_reload(get_dialog_filters_cache_time()); save_dialog_filters(); if (need_synchronize_dialog_filters()) { synchronize_dialog_filters(); } set_promises(promises); } bool DialogFilterManager::need_synchronize_dialog_filters() const { CHECK(!td_->auth_manager_->is_bot()); size_t server_dialog_filter_count = 0; vector<DialogFilterId> dialog_filter_ids; for (const auto &dialog_filter : dialog_filters_) { if (dialog_filter->is_empty(true)) { continue; } server_dialog_filter_count++; auto server_dialog_filter = get_server_dialog_filter(dialog_filter->get_dialog_filter_id()); if (server_dialog_filter == nullptr || !DialogFilter::are_equivalent(*server_dialog_filter, *dialog_filter)) { // need update dialog filter on server return true; } dialog_filter_ids.push_back(dialog_filter->get_dialog_filter_id()); } if (server_dialog_filter_count != server_dialog_filters_.size()) { // need delete dialog filter on server return true; } if (dialog_filter_ids != DialogFilter::get_dialog_filter_ids(server_dialog_filters_, -1)) { // need reorder dialog filters on server return true; } if (get_server_main_dialog_list_position() != server_main_dialog_list_position_) { // need reorder main chat list on server return true; } return false; } void DialogFilterManager::synchronize_dialog_filters() { if (G()->close_flag()) { return; } CHECK(!td_->auth_manager_->is_bot()); if (are_dialog_filters_being_synchronized_ || are_dialog_filters_being_reloaded_) { return; } if (need_dialog_filters_reload_) { return reload_dialog_filters(); } if (!need_synchronize_dialog_filters()) { // reload filters to repair their order if the server added new filter to the beginning of the list return reload_dialog_filters(); } LOG(INFO) << "Synchronize chat folder changes with server having local " << DialogFilter::get_dialog_filter_ids(dialog_filters_, main_dialog_list_position_) << " and server " << DialogFilter::get_dialog_filter_ids(server_dialog_filters_, server_main_dialog_list_position_); for (const auto &server_dialog_filter : server_dialog_filters_) { if (get_dialog_filter(server_dialog_filter->get_dialog_filter_id()) == nullptr) { return delete_dialog_filter_on_server(server_dialog_filter->get_dialog_filter_id(), server_dialog_filter->is_shareable()); } } vector<DialogFilterId> dialog_filter_ids; for (const auto &dialog_filter : dialog_filters_) { if (dialog_filter->is_empty(true)) { continue; } auto server_dialog_filter = get_server_dialog_filter(dialog_filter->get_dialog_filter_id()); if (server_dialog_filter == nullptr || !DialogFilter::are_equivalent(*server_dialog_filter, *dialog_filter)) { return update_dialog_filter_on_server(make_unique<DialogFilter>(*dialog_filter)); } dialog_filter_ids.push_back(dialog_filter->get_dialog_filter_id()); } auto server_main_dialog_list_position = get_server_main_dialog_list_position(); if (dialog_filter_ids != DialogFilter::get_dialog_filter_ids(server_dialog_filters_, -1) || server_main_dialog_list_position != server_main_dialog_list_position_) { return reorder_dialog_filters_on_server(std::move(dialog_filter_ids), server_main_dialog_list_position); } UNREACHABLE(); } void DialogFilterManager::send_update_chat_folders() { if (td_->auth_manager_->is_bot()) { return; } is_update_chat_folders_sent_ = true; send_closure(G()->td(), &Td::send_update, get_update_chat_folders_object()); } td_api::object_ptr<td_api::updateChatFolders> DialogFilterManager::get_update_chat_folders_object() const { CHECK(!td_->auth_manager_->is_bot()); auto update = td_api::make_object<td_api::updateChatFolders>(); for (const auto &dialog_filter : dialog_filters_) { update->chat_folders_.push_back(dialog_filter->get_chat_folder_info_object()); } update->main_chat_list_position_ = main_dialog_list_position_; return update; } void DialogFilterManager::create_dialog_filter(td_api::object_ptr<td_api::chatFolder> filter, Promise<td_api::object_ptr<td_api::chatFolderInfo>> &&promise) { auto max_dialog_filters = clamp(td_->option_manager_->get_option_integer("chat_folder_count_max"), static_cast<int64>(0), static_cast<int64>(100)); if (dialog_filters_.size() >= narrow_cast<size_t>(max_dialog_filters)) { return promise.set_error(Status::Error(400, "The maximum number of chat folders exceeded")); } if (!is_update_chat_folders_sent_) { return promise.set_error(Status::Error(400, "Chat folders are not synchronized yet")); } DialogFilterId dialog_filter_id; do { auto min_id = static_cast<int>(DialogFilterId::min().get()); auto max_id = static_cast<int>(DialogFilterId::max().get()); dialog_filter_id = DialogFilterId(static_cast<int32>(Random::fast(min_id, max_id))); } while (get_dialog_filter(dialog_filter_id) != nullptr || get_server_dialog_filter(dialog_filter_id) != nullptr); TRY_RESULT_PROMISE(promise, dialog_filter, DialogFilter::create_dialog_filter(td_, dialog_filter_id, std::move(filter))); if (dialog_filter->is_shareable()) { return promise.set_error(Status::Error(400, "Can't create shareable folder")); } auto chat_folder_info = dialog_filter->get_chat_folder_info_object(); bool at_beginning = is_recommended_dialog_filter(dialog_filter.get()); add_dialog_filter(std::move(dialog_filter), at_beginning, "create_dialog_filter"); if (at_beginning && main_dialog_list_position_ != 0) { main_dialog_list_position_++; } save_dialog_filters(); send_update_chat_folders(); synchronize_dialog_filters(); promise.set_value(std::move(chat_folder_info)); } void DialogFilterManager::edit_dialog_filter(DialogFilterId dialog_filter_id, td_api::object_ptr<td_api::chatFolder> filter, Promise<td_api::object_ptr<td_api::chatFolderInfo>> &&promise) { CHECK(!td_->auth_manager_->is_bot()); auto old_dialog_filter = get_dialog_filter(dialog_filter_id); if (old_dialog_filter == nullptr) { return promise.set_error(Status::Error(400, "Chat folder not found")); } CHECK(is_update_chat_folders_sent_); TRY_RESULT_PROMISE(promise, new_dialog_filter, DialogFilter::create_dialog_filter(td_, dialog_filter_id, std::move(filter))); if (new_dialog_filter->is_shareable() != old_dialog_filter->is_shareable()) { return promise.set_error(Status::Error(400, "Can't convert a shareable folder to a non-shareable")); } new_dialog_filter->update_from(*old_dialog_filter); auto chat_folder_info = new_dialog_filter->get_chat_folder_info_object(); if (*new_dialog_filter == *old_dialog_filter) { return promise.set_value(std::move(chat_folder_info)); } do_edit_dialog_filter(std::move(new_dialog_filter), true, "edit_dialog_filter"); promise.set_value(std::move(chat_folder_info)); } void DialogFilterManager::do_edit_dialog_filter(unique_ptr<DialogFilter> &&filter, bool need_synchronize, const char *source) { edit_dialog_filter(std::move(filter), source); save_dialog_filters(); send_update_chat_folders(); if (need_synchronize) { synchronize_dialog_filters(); } } void DialogFilterManager::update_dialog_filter_on_server(unique_ptr<DialogFilter> &&dialog_filter) { CHECK(!td_->auth_manager_->is_bot()); CHECK(dialog_filter != nullptr); are_dialog_filters_being_synchronized_ = true; dialog_filter->remove_secret_chat_dialog_ids(); auto dialog_filter_id = dialog_filter->get_dialog_filter_id(); auto input_dialog_filter = dialog_filter->get_input_dialog_filter(); auto promise = PromiseCreator::lambda( [actor_id = actor_id(this), dialog_filter = std::move(dialog_filter)](Result<Unit> result) mutable { send_closure(actor_id, &DialogFilterManager::on_update_dialog_filter, std::move(dialog_filter), result.is_error() ? result.move_as_error() : Status::OK()); }); td_->create_handler<UpdateDialogFilterQuery>(std::move(promise)) ->send(dialog_filter_id, std::move(input_dialog_filter)); } void DialogFilterManager::on_update_dialog_filter(unique_ptr<DialogFilter> dialog_filter, Status result) { CHECK(!td_->auth_manager_->is_bot()); if (result.is_error()) { // TODO rollback dialog_filters_ changes if error isn't 429 } else { bool is_edited = false; for (auto &server_dialog_filter : server_dialog_filters_) { if (server_dialog_filter->get_dialog_filter_id() == dialog_filter->get_dialog_filter_id()) { if (*server_dialog_filter != *dialog_filter) { server_dialog_filter = std::move(dialog_filter); } is_edited = true; break; } } if (!is_edited) { bool at_beginning = is_recommended_dialog_filter(dialog_filter.get()); if (at_beginning) { server_dialog_filters_.insert(server_dialog_filters_.begin(), std::move(dialog_filter)); } else { server_dialog_filters_.push_back(std::move(dialog_filter)); } if (at_beginning && server_main_dialog_list_position_ != 0) { server_main_dialog_list_position_++; } } save_dialog_filters(); } are_dialog_filters_being_synchronized_ = false; synchronize_dialog_filters(); } void DialogFilterManager::delete_dialog_filter(DialogFilterId dialog_filter_id, vector<DialogId> leave_dialog_ids, Promise<Unit> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); CHECK(!td_->auth_manager_->is_bot()); auto dialog_filter = get_dialog_filter(dialog_filter_id); if (dialog_filter == nullptr) { return promise.set_value(Unit()); } for (auto &leave_dialog_id : leave_dialog_ids) { if (!dialog_filter->is_dialog_included(leave_dialog_id)) { return promise.set_error(Status::Error(400, "The chat doesn't included in the folder")); } } if (!leave_dialog_ids.empty()) { MultiPromiseActorSafe mpas{"LeaveDialogsMultiPromiseActor"}; mpas.add_promise(PromiseCreator::lambda( [actor_id = actor_id(this), dialog_filter_id, promise = std::move(promise)](Result<Unit> &&result) mutable { if (result.is_error()) { return promise.set_error(result.move_as_error()); } send_closure(actor_id, &DialogFilterManager::delete_dialog_filter, dialog_filter_id, vector<DialogId>(), std::move(promise)); })); auto lock = mpas.get_promise(); for (auto &leave_dialog_id : leave_dialog_ids) { td_->contacts_manager_->leave_dialog(leave_dialog_id, mpas.get_promise()); } lock.set_value(Unit()); return; } int32 position = do_delete_dialog_filter(dialog_filter_id, "delete_dialog_filter"); if (main_dialog_list_position_ > position) { main_dialog_list_position_--; } save_dialog_filters(); send_update_chat_folders(); synchronize_dialog_filters(); promise.set_value(Unit()); } void DialogFilterManager::delete_dialog_filter_on_server(DialogFilterId dialog_filter_id, bool is_shareable) { CHECK(!td_->auth_manager_->is_bot()); are_dialog_filters_being_synchronized_ = true; auto promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_filter_id](Result<Unit> result) { send_closure(actor_id, &DialogFilterManager::on_delete_dialog_filter, dialog_filter_id, result.is_error() ? result.move_as_error() : Status::OK()); }); if (is_shareable) { td_->create_handler<LeaveChatlistQuery>(std::move(promise))->send(dialog_filter_id); } else { td_->create_handler<UpdateDialogFilterQuery>(std::move(promise))->send(dialog_filter_id, nullptr); } } void DialogFilterManager::on_delete_dialog_filter(DialogFilterId dialog_filter_id, Status result) { CHECK(!td_->auth_manager_->is_bot()); if (result.is_error()) { // TODO rollback dialog_filters_ changes if error isn't 429 } else { for (auto it = server_dialog_filters_.begin(); it != server_dialog_filters_.end(); ++it) { if ((*it)->get_dialog_filter_id() == dialog_filter_id) { server_dialog_filters_.erase(it); save_dialog_filters(); break; } } } are_dialog_filters_being_synchronized_ = false; synchronize_dialog_filters(); } void DialogFilterManager::get_leave_dialog_filter_suggestions(DialogFilterId dialog_filter_id, Promise<td_api::object_ptr<td_api::chats>> &&promise) { auto dialog_filter = get_dialog_filter(dialog_filter_id); if (dialog_filter == nullptr) { return promise.set_error(Status::Error(400, "Chat folder not found")); } if (!dialog_filter->is_shareable()) { return promise.set_value(td_api::make_object<td_api::chats>()); } auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_filter_id, promise = std::move(promise)]( Result<vector<telegram_api::object_ptr<telegram_api::Peer>>> &&result) mutable { if (result.is_error()) { return promise.set_error(result.move_as_error()); } send_closure(actor_id, &DialogFilterManager::on_get_leave_dialog_filter_suggestions, dialog_filter_id, result.move_as_ok(), std::move(promise)); }); td_->create_handler<GetLeaveChatlistSuggestionsQuery>(std::move(query_promise))->send(dialog_filter_id); } void DialogFilterManager::on_get_leave_dialog_filter_suggestions( DialogFilterId dialog_filter_id, vector<telegram_api::object_ptr<telegram_api::Peer>> peers, Promise<td_api::object_ptr<td_api::chats>> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); auto dialog_filter = get_dialog_filter(dialog_filter_id); if (dialog_filter == nullptr) { return promise.set_error(Status::Error(400, "Chat folder not found")); } if (!dialog_filter->is_shareable()) { return promise.set_value(td_api::make_object<td_api::chats>()); } auto dialog_ids = td_->messages_manager_->get_peers_dialog_ids(std::move(peers)); td::remove_if(dialog_ids, [&](DialogId dialog_id) { return !dialog_filter->is_dialog_included(dialog_id); }); promise.set_value(td_->messages_manager_->get_chats_object(-1, dialog_ids, "on_get_leave_dialog_filter_suggestions")); } void DialogFilterManager::reorder_dialog_filters(vector<DialogFilterId> dialog_filter_ids, int32 main_dialog_list_position, Promise<Unit> &&promise) { CHECK(!td_->auth_manager_->is_bot()); for (auto dialog_filter_id : dialog_filter_ids) { auto dialog_filter = get_dialog_filter(dialog_filter_id); if (dialog_filter == nullptr) { return promise.set_error(Status::Error(400, "Chat folder not found")); } } std::unordered_set<DialogFilterId, DialogFilterIdHash> new_dialog_filter_ids_set(dialog_filter_ids.begin(), dialog_filter_ids.end()); if (new_dialog_filter_ids_set.size() != dialog_filter_ids.size()) { return promise.set_error(Status::Error(400, "Duplicate chat folders in the new list")); } if (main_dialog_list_position < 0 || main_dialog_list_position > static_cast<int32>(dialog_filters_.size())) { return promise.set_error(Status::Error(400, "Invalid main chat list position specified")); } if (!td_->option_manager_->get_option_boolean("is_premium")) { main_dialog_list_position = 0; } if (DialogFilter::set_dialog_filters_order(dialog_filters_, dialog_filter_ids) || main_dialog_list_position != main_dialog_list_position_) { main_dialog_list_position_ = main_dialog_list_position; save_dialog_filters(); send_update_chat_folders(); synchronize_dialog_filters(); } promise.set_value(Unit()); } void DialogFilterManager::reorder_dialog_filters_on_server(vector<DialogFilterId> dialog_filter_ids, int32 main_dialog_list_position) { CHECK(!td_->auth_manager_->is_bot()); are_dialog_filters_being_synchronized_ = true; auto promise = PromiseCreator::lambda( [actor_id = actor_id(this), dialog_filter_ids, main_dialog_list_position](Result<Unit> result) mutable { send_closure(actor_id, &DialogFilterManager::on_reorder_dialog_filters, std::move(dialog_filter_ids), main_dialog_list_position, result.is_error() ? result.move_as_error() : Status::OK()); }); td_->create_handler<UpdateDialogFiltersOrderQuery>(std::move(promise)) ->send(dialog_filter_ids, main_dialog_list_position); } void DialogFilterManager::on_reorder_dialog_filters(vector<DialogFilterId> dialog_filter_ids, int32 main_dialog_list_position, Status result) { CHECK(!td_->auth_manager_->is_bot()); if (result.is_error()) { // TODO rollback dialog_filters_ changes if error isn't 429 } else { if (DialogFilter::set_dialog_filters_order(server_dialog_filters_, std::move(dialog_filter_ids)) || server_main_dialog_list_position_ != main_dialog_list_position) { server_main_dialog_list_position_ = main_dialog_list_position; save_dialog_filters(); } } are_dialog_filters_being_synchronized_ = false; synchronize_dialog_filters(); } void DialogFilterManager::add_dialog_filter(unique_ptr<DialogFilter> dialog_filter, bool at_beginning, const char *source) { if (td_->auth_manager_->is_bot()) { // just in case return; } CHECK(dialog_filter != nullptr); auto dialog_filter_id = dialog_filter->get_dialog_filter_id(); LOG(INFO) << "Add " << dialog_filter_id << " from " << source; CHECK(get_dialog_filter(dialog_filter_id) == nullptr); if (at_beginning) { dialog_filters_.insert(dialog_filters_.begin(), std::move(dialog_filter)); } else { dialog_filters_.push_back(std::move(dialog_filter)); } td_->messages_manager_->add_dialog_list_for_dialog_filter(dialog_filter_id); } void DialogFilterManager::edit_dialog_filter(unique_ptr<DialogFilter> new_dialog_filter, const char *source) { if (td_->auth_manager_->is_bot()) { // just in case return; } CHECK(new_dialog_filter != nullptr); LOG(INFO) << "Edit " << new_dialog_filter->get_dialog_filter_id() << " from " << source; for (auto &old_dialog_filter : dialog_filters_) { if (old_dialog_filter->get_dialog_filter_id() == new_dialog_filter->get_dialog_filter_id()) { CHECK(*old_dialog_filter != *new_dialog_filter); disable_get_dialog_filter_ = true; // to ensure crash if get_dialog_filter is called td_->messages_manager_->edit_dialog_list_for_dialog_filter(old_dialog_filter, std::move(new_dialog_filter), disable_get_dialog_filter_, source); return; } } UNREACHABLE(); } int32 DialogFilterManager::do_delete_dialog_filter(DialogFilterId dialog_filter_id, const char *source) { if (td_->auth_manager_->is_bot()) { // just in case return -1; } LOG(INFO) << "Delete " << dialog_filter_id << " from " << source; for (auto it = dialog_filters_.begin(); it != dialog_filters_.end(); ++it) { if ((*it)->get_dialog_filter_id() == dialog_filter_id) { auto position = static_cast<int32>(it - dialog_filters_.begin()); td_->messages_manager_->delete_dialog_list_for_dialog_filter(dialog_filter_id, source); dialog_filters_.erase(it); return position; } } UNREACHABLE(); return -1; } void DialogFilterManager::save_dialog_filters() { if (td_->auth_manager_->is_bot()) { return; } DialogFiltersLogEvent log_event; log_event.server_main_dialog_list_position = server_main_dialog_list_position_; log_event.main_dialog_list_position = main_dialog_list_position_; log_event.updated_date = dialog_filters_updated_date_; log_event.server_dialog_filters_in = &server_dialog_filters_; log_event.dialog_filters_in = &dialog_filters_; LOG(INFO) << "Save server chat folders " << DialogFilter::get_dialog_filter_ids(server_dialog_filters_, server_main_dialog_list_position_) << " and local chat folders " << DialogFilter::get_dialog_filter_ids(dialog_filters_, main_dialog_list_position_); G()->td_db()->get_binlog_pmc()->set("dialog_filters", log_event_store(log_event).as_slice().str()); } void DialogFilterManager::get_dialogs_for_dialog_filter_invite_link( DialogFilterId dialog_filter_id, Promise<td_api::object_ptr<td_api::chats>> promise) { auto dialog_filter = get_dialog_filter(dialog_filter_id); if (dialog_filter == nullptr) { return promise.set_error(Status::Error(400, "Chat folder not found")); } auto load_promise = PromiseCreator::lambda( [actor_id = actor_id(this), dialog_filter_id, promise = std::move(promise)](Result<Unit> &&result) mutable { if (result.is_error()) { return promise.set_error(result.move_as_error()); } send_closure(actor_id, &DialogFilterManager::do_get_dialogs_for_dialog_filter_invite_link, dialog_filter_id, std::move(promise)); }); load_dialog_filter(dialog_filter, std::move(load_promise)); } void DialogFilterManager::do_get_dialogs_for_dialog_filter_invite_link( DialogFilterId dialog_filter_id, Promise<td_api::object_ptr<td_api::chats>> promise) { auto dialog_filter = get_dialog_filter(dialog_filter_id); if (dialog_filter == nullptr) { return promise.set_error(Status::Error(400, "Chat folder not found")); } promise.set_value(td_->messages_manager_->get_chats_object(-1, dialog_filter->get_dialogs_for_invite_link(td_), "do_get_dialogs_for_dialog_filter_invite_link")); } void DialogFilterManager::create_dialog_filter_invite_link( DialogFilterId dialog_filter_id, string invite_link_name, vector<DialogId> dialog_ids, Promise<td_api::object_ptr<td_api::chatFolderInviteLink>> promise) { auto dialog_filter = get_dialog_filter(dialog_filter_id); if (dialog_filter == nullptr) { return promise.set_error(Status::Error(400, "Chat folder not found")); } vector<tl_object_ptr<telegram_api::InputPeer>> input_peers; input_peers.reserve(dialog_ids.size()); for (auto &dialog_id : dialog_ids) { if (!td_->messages_manager_->have_dialog_force(dialog_id, "create_dialog_filter_invite_link")) { return promise.set_error(Status::Error(400, "Chat not found")); } auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { return promise.set_error(Status::Error(400, "Have no access to the chat")); } input_peers.push_back(std::move(input_peer)); } if (input_peers.empty()) { return promise.set_error(Status::Error(400, "At least one chat must be included")); } td_->create_handler<ExportChatlistInviteQuery>(std::move(promise)) ->send(dialog_filter_id, invite_link_name, std::move(input_peers)); } void DialogFilterManager::get_dialog_filter_invite_links( DialogFilterId dialog_filter_id, Promise<td_api::object_ptr<td_api::chatFolderInviteLinks>> promise) { auto dialog_filter = get_dialog_filter(dialog_filter_id); if (dialog_filter == nullptr) { return promise.set_error(Status::Error(400, "Chat folder not found")); } if (!dialog_filter->is_shareable()) { return promise.set_value(td_api::make_object<td_api::chatFolderInviteLinks>()); } td_->create_handler<GetExportedChatlistInvitesQuery>(std::move(promise))->send(dialog_filter_id); } void DialogFilterManager::edit_dialog_filter_invite_link( DialogFilterId dialog_filter_id, string invite_link, string invite_link_name, vector<DialogId> dialog_ids, Promise<td_api::object_ptr<td_api::chatFolderInviteLink>> promise) { auto dialog_filter = get_dialog_filter(dialog_filter_id); if (dialog_filter == nullptr) { return promise.set_error(Status::Error(400, "Chat folder not found")); } vector<tl_object_ptr<telegram_api::InputPeer>> input_peers; input_peers.reserve(dialog_ids.size()); for (auto &dialog_id : dialog_ids) { if (!td_->messages_manager_->have_dialog_force(dialog_id, "edit_dialog_filter_invite_link")) { return promise.set_error(Status::Error(400, "Chat not found")); } auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { return promise.set_error(Status::Error(400, "Have no access to the chat")); } input_peers.push_back(std::move(input_peer)); } if (input_peers.empty()) { return promise.set_error(Status::Error(400, "At least one chat must be included")); } td_->create_handler<EditExportedChatlistInviteQuery>(std::move(promise)) ->send(dialog_filter_id, invite_link, invite_link_name, std::move(input_peers)); } void DialogFilterManager::delete_dialog_filter_invite_link(DialogFilterId dialog_filter_id, string invite_link, Promise<Unit> promise) { auto dialog_filter = get_dialog_filter(dialog_filter_id); if (dialog_filter == nullptr) { return promise.set_error(Status::Error(400, "Chat folder not found")); } td_->create_handler<DeleteExportedChatlistInviteQuery>(std::move(promise))->send(dialog_filter_id, invite_link); } void DialogFilterManager::check_dialog_filter_invite_link( const string &invite_link, Promise<td_api::object_ptr<td_api::chatFolderInviteLinkInfo>> &&promise) { if (!DialogFilterInviteLink::is_valid_invite_link(invite_link)) { return promise.set_error(Status::Error(400, "Wrong invite link")); } CHECK(!invite_link.empty()); td_->create_handler<CheckChatlistInviteQuery>(std::move(promise))->send(invite_link); } void DialogFilterManager::on_get_chatlist_invite( const string &invite_link, telegram_api::object_ptr<telegram_api::chatlists_ChatlistInvite> &&invite_ptr, Promise<td_api::object_ptr<td_api::chatFolderInviteLinkInfo>> &&promise) { CHECK(invite_ptr != nullptr); LOG(INFO) << "Receive information about chat folder invite link " << invite_link << ": " << to_string(invite_ptr); td_api::object_ptr<td_api::chatFolderInfo> info; vector<telegram_api::object_ptr<telegram_api::Peer>> missing_peers; vector<telegram_api::object_ptr<telegram_api::Peer>> already_peers; vector<telegram_api::object_ptr<telegram_api::Chat>> chats; vector<telegram_api::object_ptr<telegram_api::User>> users; switch (invite_ptr->get_id()) { case telegram_api::chatlists_chatlistInviteAlready::ID: { auto invite = move_tl_object_as<telegram_api::chatlists_chatlistInviteAlready>(invite_ptr); DialogFilterId dialog_filter_id = DialogFilterId(invite->filter_id_); if (!dialog_filter_id.is_valid()) { return promise.set_error(Status::Error(500, "Receive invalid chat folder identifier")); } auto dialog_filter = get_dialog_filter(dialog_filter_id); if (dialog_filter == nullptr) { reload_dialog_filters(); return promise.set_error(Status::Error(500, "Receive unknown chat folder")); } info = dialog_filter->get_chat_folder_info_object(); missing_peers = std::move(invite->missing_peers_); already_peers = std::move(invite->already_peers_); chats = std::move(invite->chats_); users = std::move(invite->users_); break; } case telegram_api::chatlists_chatlistInvite::ID: { auto invite = move_tl_object_as<telegram_api::chatlists_chatlistInvite>(invite_ptr); auto icon_name = DialogFilter::get_icon_name_by_emoji(invite->emoticon_); if (icon_name.empty()) { icon_name = "Custom"; } info = td_api::make_object<td_api::chatFolderInfo>( 0, invite->title_, td_api::make_object<td_api::chatFolderIcon>(icon_name), true, false); missing_peers = std::move(invite->peers_); chats = std::move(invite->chats_); users = std::move(invite->users_); break; } default: UNREACHABLE(); break; } td_->contacts_manager_->on_get_users(std::move(users), "on_get_chatlist_invite"); td_->contacts_manager_->on_get_chats(std::move(chats), "on_get_chatlist_invite"); auto missing_dialog_ids = td_->messages_manager_->get_peers_dialog_ids(std::move(missing_peers), true); auto already_dialog_ids = td_->messages_manager_->get_peers_dialog_ids(std::move(already_peers)); promise.set_value(td_api::make_object<td_api::chatFolderInviteLinkInfo>( std::move(info), td_->messages_manager_->get_chat_ids_object(missing_dialog_ids, "chatFolderInviteLinkInfo 1"), td_->messages_manager_->get_chat_ids_object(already_dialog_ids, "chatFolderInviteLinkInfo 1"))); } void DialogFilterManager::add_dialog_filter_by_invite_link(const string &invite_link, vector<DialogId> dialog_ids, Promise<Unit> &&promise) { if (!DialogFilterInviteLink::is_valid_invite_link(invite_link)) { return promise.set_error(Status::Error(400, "Wrong invite link")); } for (auto dialog_id : dialog_ids) { if (!td_->messages_manager_->have_dialog_force(dialog_id, "add_dialog_filter_by_invite_link")) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Know)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } } CHECK(!invite_link.empty()); td_->create_handler<JoinChatlistInviteQuery>(std::move(promise))->send(invite_link, std::move(dialog_ids)); } void DialogFilterManager::get_dialog_filter_new_chats(DialogFilterId dialog_filter_id, Promise<td_api::object_ptr<td_api::chats>> &&promise) { auto dialog_filter = get_dialog_filter(dialog_filter_id); if (dialog_filter == nullptr) { return promise.set_error(Status::Error(400, "Chat folder not found")); } if (!dialog_filter->is_shareable()) { return promise.set_value(td_api::make_object<td_api::chats>()); } td_->create_handler<GetChatlistUpdatesQuery>(std::move(promise))->send(dialog_filter_id); } void DialogFilterManager::add_dialog_filter_new_chats(DialogFilterId dialog_filter_id, vector<DialogId> dialog_ids, Promise<Unit> &&promise) { auto dialog_filter = get_dialog_filter(dialog_filter_id); if (dialog_filter == nullptr) { return promise.set_error(Status::Error(400, "Chat folder not found")); } if (!dialog_filter->is_shareable()) { return promise.set_error(Status::Error(400, "Chat folder must be shareable")); } for (auto dialog_id : dialog_ids) { if (!td_->messages_manager_->have_dialog_force(dialog_id, "add_dialog_filter_new_chats")) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Know)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } } if (dialog_ids.empty()) { td_->create_handler<HideChatlistUpdatesQuery>(std::move(promise))->send(dialog_filter_id); } else { td_->create_handler<JoinChatlistUpdatesQuery>(std::move(promise))->send(dialog_filter_id, std::move(dialog_ids)); } } void DialogFilterManager::set_dialog_filter_has_my_invite_links(DialogFilterId dialog_filter_id, bool has_my_invite_links) { auto dialog_filter = get_dialog_filter(dialog_filter_id); if (dialog_filter == nullptr) { return; } dialog_filter->set_has_my_invite_links(has_my_invite_links); } void DialogFilterManager::get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const { if (have_dialog_filters()) { updates.push_back(get_update_chat_folders_object()); } } } // namespace td