diff --git a/td/telegram/ContactsManager.cpp b/td/telegram/ContactsManager.cpp index 9b8e934de..996169315 100644 --- a/td/telegram/ContactsManager.cpp +++ b/td/telegram/ContactsManager.cpp @@ -3141,78 +3141,6 @@ class GetChannelParticipantsQuery final : public Td::ResultHandler { } }; -class GetChannelAdministratorsQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - - public: - explicit GetChannelAdministratorsQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, int64 hash) { - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - if (input_channel == nullptr) { - return promise_.set_error(Status::Error(400, "Supergroup not found")); - } - - hash = 0; // to load even only ranks or creator changed - - channel_id_ = channel_id; - send_query(G()->net_query_creator().create(telegram_api::channels_getParticipants( - std::move(input_channel), telegram_api::make_object(), 0, - std::numeric_limits::max(), hash))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto participants_ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for GetChannelAdministratorsQuery: " << to_string(participants_ptr); - switch (participants_ptr->get_id()) { - case telegram_api::channels_channelParticipants::ID: { - auto participants = telegram_api::move_object_as(participants_ptr); - td_->contacts_manager_->on_get_users(std::move(participants->users_), "GetChannelAdministratorsQuery"); - td_->contacts_manager_->on_get_chats(std::move(participants->chats_), "GetChannelAdministratorsQuery"); - - auto channel_type = td_->contacts_manager_->get_channel_type(channel_id_); - vector administrators; - administrators.reserve(participants->participants_.size()); - for (auto &participant : participants->participants_) { - DialogParticipant dialog_participant(std::move(participant), channel_type); - if (!dialog_participant.is_valid() || !dialog_participant.status_.is_administrator() || - dialog_participant.dialog_id_.get_type() != DialogType::User) { - LOG(ERROR) << "Receive " << dialog_participant << " as an administrator of " << channel_id_; - continue; - } - administrators.emplace_back(dialog_participant.dialog_id_.get_user_id(), - dialog_participant.status_.get_rank(), dialog_participant.status_.is_creator()); - } - - td_->contacts_manager_->on_update_channel_administrator_count(channel_id_, - narrow_cast(administrators.size())); - td_->contacts_manager_->on_update_dialog_administrators(DialogId(channel_id_), std::move(administrators), true, - false); - - break; - } - case telegram_api::channels_channelParticipantsNotModified::ID: - break; - default: - UNREACHABLE(); - } - - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelAdministratorsQuery"); - promise_.set_error(std::move(status)); - } -}; - class GetSupportUserQuery final : public Td::ResultHandler { Promise promise_; @@ -3393,9 +3321,9 @@ ContactsManager::~ContactsManager() { Scheduler::instance()->destroy_on_scheduler( G()->get_gc_scheduler_id(), loaded_from_database_users_, unavailable_user_fulls_, loaded_from_database_chats_, unavailable_chat_fulls_, loaded_from_database_channels_, unavailable_channel_fulls_, - loaded_from_database_secret_chats_, dialog_administrators_, user_online_member_dialogs_, - cached_channel_participants_, resolved_phone_numbers_, channel_participants_, all_imported_contacts_, - linked_channel_ids_, restricted_user_ids_, restricted_channel_ids_); + loaded_from_database_secret_chats_, user_online_member_dialogs_, cached_channel_participants_, + resolved_phone_numbers_, channel_participants_, all_imported_contacts_, linked_channel_ids_, restricted_user_ids_, + restricted_channel_ids_); } void ContactsManager::start_up() { @@ -12174,8 +12102,8 @@ void ContactsManager::update_chat_full(ChatFull *chat_full, ChatId chat_id, cons return !td::contains(bot_user_ids, commands.get_bot_user_id()); }); - on_update_dialog_administrators(DialogId(chat_id), std::move(administrators), chat_full->version != -1, - from_database); + td_->dialog_participant_manager_->on_update_dialog_administrators(DialogId(chat_id), std::move(administrators), + chat_full->version != -1, from_database); send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(chat_id), std::move(bot_user_ids), from_database); @@ -14468,7 +14396,8 @@ void ContactsManager::on_get_channel_participants( } } if (filter.is_administrators() || filter.is_recent()) { - on_update_dialog_administrators(DialogId(channel_id), std::move(administrators), true, false); + td_->dialog_participant_manager_->on_update_dialog_administrators(DialogId(channel_id), std::move(administrators), + true, false); } if (filter.is_bots() || is_full_recent) { on_update_channel_bot_user_ids(channel_id, std::move(bot_user_ids)); @@ -14757,41 +14686,8 @@ void ContactsManager::speculative_add_channel_user(ChannelId channel_id, UserId update_channel(c, channel_id); } - if (new_status.is_administrator() != old_status.is_administrator() || - new_status.get_rank() != old_status.get_rank()) { - DialogId dialog_id(channel_id); - auto administrators_it = dialog_administrators_.find(dialog_id); - if (administrators_it != dialog_administrators_.end()) { - auto administrators = administrators_it->second; - if (new_status.is_administrator()) { - bool is_found = false; - for (auto &administrator : administrators) { - if (administrator.get_user_id() == user_id) { - is_found = true; - if (administrator.get_rank() != new_status.get_rank() || - administrator.is_creator() != new_status.is_creator()) { - administrator = DialogAdministrator(user_id, new_status.get_rank(), new_status.is_creator()); - on_update_dialog_administrators(dialog_id, std::move(administrators), true, false); - } - break; - } - } - if (!is_found) { - administrators.emplace_back(user_id, new_status.get_rank(), new_status.is_creator()); - on_update_dialog_administrators(dialog_id, std::move(administrators), true, false); - } - } else { - size_t i = 0; - while (i != administrators.size() && administrators[i].get_user_id() != user_id) { - i++; - } - if (i != administrators.size()) { - administrators.erase(administrators.begin() + i); - on_update_dialog_administrators(dialog_id, std::move(administrators), true, false); - } - } - } - } + td_->dialog_participant_manager_->speculative_update_dialog_administrators(DialogId(channel_id), user_id, new_status, + old_status); auto it = cached_channel_participants_.find(channel_id); if (it != cached_channel_participants_.end()) { @@ -15952,7 +15848,7 @@ void ContactsManager::on_channel_status_changed(Channel *c, ChannelId channel_id c->is_creator_changed = true; send_get_channel_full_query(nullptr, channel_id, Auto(), "update channel owner"); - reload_dialog_administrators(DialogId(channel_id), {}, Auto()); + td_->dialog_participant_manager_->reload_dialog_administrators(DialogId(channel_id), {}, Auto()); remove_dialog_suggested_action(SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)}); } @@ -18250,105 +18146,6 @@ void ContactsManager::get_channel_participants(ChannelId channel_id, ->send(channel_id, participant_filter, offset, limit); } -td_api::object_ptr ContactsManager::get_chat_administrators_object( - const vector &dialog_administrators) { - auto administrator_objects = transform(dialog_administrators, [this](const DialogAdministrator &administrator) { - return administrator.get_chat_administrator_object(this); - }); - return td_api::make_object(std::move(administrator_objects)); -} - -void ContactsManager::get_dialog_administrators(DialogId dialog_id, - Promise> &&promise) { - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "get_dialog_administrators")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - - switch (dialog_id.get_type()) { - case DialogType::User: - case DialogType::SecretChat: - return promise.set_value(td_api::make_object()); - case DialogType::Chat: - case DialogType::Channel: - break; - case DialogType::None: - default: - UNREACHABLE(); - return; - } - - auto it = dialog_administrators_.find(dialog_id); - if (it != dialog_administrators_.end()) { - reload_dialog_administrators(dialog_id, it->second, Auto()); // update administrators cache - return promise.set_value(get_chat_administrators_object(it->second)); - } - - if (G()->use_chat_info_database()) { - LOG(INFO) << "Load administrators of " << dialog_id << " from database"; - G()->td_db()->get_sqlite_pmc()->get(get_dialog_administrators_database_key(dialog_id), - PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, - promise = std::move(promise)](string value) mutable { - send_closure(actor_id, - &ContactsManager::on_load_dialog_administrators_from_database, - dialog_id, std::move(value), std::move(promise)); - })); - return; - } - - reload_dialog_administrators(dialog_id, {}, std::move(promise)); -} - -string ContactsManager::get_dialog_administrators_database_key(DialogId dialog_id) { - return PSTRING() << "adm" << (-dialog_id.get()); -} - -void ContactsManager::on_load_dialog_administrators_from_database( - DialogId dialog_id, string value, Promise> &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - - if (value.empty()) { - return reload_dialog_administrators(dialog_id, {}, std::move(promise)); - } - - vector administrators; - if (log_event_parse(administrators, value).is_error()) { - return reload_dialog_administrators(dialog_id, {}, std::move(promise)); - } - - LOG(INFO) << "Successfully loaded " << administrators.size() << " administrators in " << dialog_id - << " from database"; - - MultiPromiseActorSafe load_users_multipromise{"LoadUsersMultiPromiseActor"}; - load_users_multipromise.add_promise( - PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, administrators, - promise = std::move(promise)](Result result) mutable { - send_closure(actor_id, &ContactsManager::on_load_administrator_users_finished, dialog_id, - std::move(administrators), std::move(result), std::move(promise)); - })); - - auto lock_promise = load_users_multipromise.get_promise(); - - for (auto &administrator : administrators) { - get_user(administrator.get_user_id(), 3, load_users_multipromise.get_promise()); - } - - lock_promise.set_value(Unit()); -} - -void ContactsManager::on_load_administrator_users_finished( - DialogId dialog_id, vector administrators, Result<> result, - Promise> &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - - if (result.is_error()) { - return reload_dialog_administrators(dialog_id, {}, std::move(promise)); - } - - auto it = dialog_administrators_.emplace(dialog_id, std::move(administrators)).first; - reload_dialog_administrators(dialog_id, it->second, Auto()); // update administrators cache - promise.set_value(get_chat_administrators_object(it->second)); -} - void ContactsManager::on_update_channel_administrator_count(ChannelId channel_id, int32 administrator_count) { auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_administrator_count"); if (channel_full != nullptr && channel_full->administrator_count != administrator_count) { @@ -18370,89 +18167,6 @@ void ContactsManager::on_update_channel_administrator_count(ChannelId channel_id } } -void ContactsManager::on_update_dialog_administrators(DialogId dialog_id, vector &&administrators, - bool have_access, bool from_database) { - LOG(INFO) << "Update administrators in " << dialog_id << " to " << format::as_array(administrators); - if (have_access) { - CHECK(dialog_id.is_valid()); - std::sort(administrators.begin(), administrators.end(), - [](const DialogAdministrator &lhs, const DialogAdministrator &rhs) { - return lhs.get_user_id().get() < rhs.get_user_id().get(); - }); - - auto it = dialog_administrators_.find(dialog_id); - if (it != dialog_administrators_.end()) { - if (it->second == administrators) { - return; - } - it->second = std::move(administrators); - } else { - it = dialog_administrators_.emplace(dialog_id, std::move(administrators)).first; - } - - if (G()->use_chat_info_database() && !from_database) { - LOG(INFO) << "Save administrators of " << dialog_id << " to database"; - G()->td_db()->get_sqlite_pmc()->set(get_dialog_administrators_database_key(dialog_id), - log_event_store(it->second).as_slice().str(), Auto()); - } - } else { - dialog_administrators_.erase(dialog_id); - if (G()->use_chat_info_database()) { - G()->td_db()->get_sqlite_pmc()->erase(get_dialog_administrators_database_key(dialog_id), Auto()); - } - } -} - -void ContactsManager::reload_dialog_administrators(DialogId dialog_id, - const vector &dialog_administrators, - Promise> &&promise) { - auto dialog_type = dialog_id.get_type(); - if (dialog_type == DialogType::Chat && !get_chat_permissions(dialog_id.get_chat_id()).is_member()) { - return promise.set_value(td_api::make_object()); - } - auto query_promise = PromiseCreator::lambda( - [actor_id = actor_id(this), dialog_id, promise = std::move(promise)](Result &&result) mutable { - if (promise) { - if (result.is_ok()) { - send_closure(actor_id, &ContactsManager::on_reload_dialog_administrators, dialog_id, std::move(promise)); - } else { - promise.set_error(result.move_as_error()); - } - } - }); - switch (dialog_type) { - case DialogType::Chat: - load_chat_full(dialog_id.get_chat_id(), false, std::move(query_promise), "reload_dialog_administrators"); - break; - case DialogType::Channel: { - auto channel_id = dialog_id.get_channel_id(); - if (is_broadcast_channel(channel_id) && !get_channel_status(channel_id).is_administrator()) { - return query_promise.set_error(Status::Error(400, "Administrator list is inaccessible")); - } - auto hash = get_vector_hash(transform(dialog_administrators, [](const DialogAdministrator &administrator) { - return static_cast(administrator.get_user_id().get()); - })); - td_->create_handler(std::move(query_promise))->send(channel_id, hash); - break; - } - default: - UNREACHABLE(); - } -} - -void ContactsManager::on_reload_dialog_administrators( - DialogId dialog_id, Promise> &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - - auto it = dialog_administrators_.find(dialog_id); - if (it != dialog_administrators_.end()) { - return promise.set_value(get_chat_administrators_object(it->second)); - } - - LOG(ERROR) << "Failed to load administrators in " << dialog_id; - promise.set_error(Status::Error(500, "Failed to find chat administrators")); -} - void ContactsManager::on_get_chat_empty(telegram_api::chatEmpty &chat, const char *source) { ChatId chat_id(chat.id_); if (!chat_id.is_valid()) { diff --git a/td/telegram/ContactsManager.h b/td/telegram/ContactsManager.h index ef790e63c..f8b6a8188 100644 --- a/td/telegram/ContactsManager.h +++ b/td/telegram/ContactsManager.h @@ -287,9 +287,6 @@ class ContactsManager final : public Actor { void on_update_bot_menu_button(UserId bot_user_id, tl_object_ptr &&bot_menu_button); - void on_update_dialog_administrators(DialogId dialog_id, vector &&administrators, - bool have_access, bool from_database); - void speculative_add_channel_participants(ChannelId channel_id, const vector &added_user_ids, UserId inviter_user_id, int32 date, bool by_me); @@ -684,8 +681,6 @@ class ContactsManager final : public Actor { void search_dialog_participants(DialogId dialog_id, const string &query, int32 limit, DialogParticipantFilter filter, Promise &&promise); - void get_dialog_administrators(DialogId dialog_id, Promise> &&promise); - void get_channel_participants(ChannelId channel_id, tl_object_ptr &&filter, string additional_query, int32 offset, int32 limit, int32 additional_limit, Promise &&promise); @@ -1776,24 +1771,6 @@ class ContactsManager final : public Actor { void finish_get_channel_participant(ChannelId channel_id, DialogParticipant &&dialog_participant, Promise &&promise); - td_api::object_ptr get_chat_administrators_object( - const vector &dialog_administrators); - - static string get_dialog_administrators_database_key(DialogId dialog_id); - - void on_load_dialog_administrators_from_database(DialogId dialog_id, string value, - Promise> &&promise); - - void on_load_administrator_users_finished(DialogId dialog_id, vector administrators, - Result<> result, - Promise> &&promise); - - void reload_dialog_administrators(DialogId dialog_id, const vector &dialog_administrators, - Promise> &&promise); - - void on_reload_dialog_administrators(DialogId dialog_id, - Promise> &&promise); - void remove_dialog_suggested_action(SuggestedAction action); void on_dismiss_suggested_action(SuggestedAction action, Result &&result); @@ -2022,8 +1999,6 @@ class ContactsManager final : public Actor { QueryCombiner get_user_full_queries_{"GetUserFullCombiner", 2.0}; QueryCombiner get_chat_full_queries_{"GetChatFullCombiner", 2.0}; - FlatHashMap, DialogIdHash> dialog_administrators_; - FlatHashMap, DialogIdHash> dialog_suggested_actions_; FlatHashMap>, DialogIdHash> dismiss_suggested_action_queries_; diff --git a/td/telegram/DialogParticipantManager.cpp b/td/telegram/DialogParticipantManager.cpp index c87d661bb..e0c7dd8e5 100644 --- a/td/telegram/DialogParticipantManager.cpp +++ b/td/telegram/DialogParticipantManager.cpp @@ -10,15 +10,23 @@ #include "td/telegram/ContactsManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" +#include "td/telegram/logevent/LogEvent.h" #include "td/telegram/MessagesManager.h" +#include "td/telegram/misc.h" #include "td/telegram/Td.h" +#include "td/telegram/TdDb.h" #include "td/telegram/UpdatesManager.h" +#include "td/db/SqliteKeyValueAsync.h" + +#include "td/utils/algorithm.h" #include "td/utils/buffer.h" #include "td/utils/logging.h" #include "td/utils/Status.h" #include "td/utils/Time.h" +#include + namespace td { class GetOnlinesQuery final : public Td::ResultHandler { @@ -220,11 +228,87 @@ class HideAllChatJoinRequestsQuery final : public Td::ResultHandler { } }; +class GetChannelAdministratorsQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + + public: + explicit GetChannelAdministratorsQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, int64 hash) { + auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + if (input_channel == nullptr) { + return promise_.set_error(Status::Error(400, "Supergroup not found")); + } + + hash = 0; // to load even only ranks or creator changed + + channel_id_ = channel_id; + send_query(G()->net_query_creator().create(telegram_api::channels_getParticipants( + std::move(input_channel), telegram_api::make_object(), 0, + std::numeric_limits::max(), hash))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto participants_ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetChannelAdministratorsQuery: " << to_string(participants_ptr); + switch (participants_ptr->get_id()) { + case telegram_api::channels_channelParticipants::ID: { + auto participants = telegram_api::move_object_as(participants_ptr); + td_->contacts_manager_->on_get_users(std::move(participants->users_), "GetChannelAdministratorsQuery"); + td_->contacts_manager_->on_get_chats(std::move(participants->chats_), "GetChannelAdministratorsQuery"); + + auto channel_type = td_->contacts_manager_->get_channel_type(channel_id_); + vector administrators; + administrators.reserve(participants->participants_.size()); + for (auto &participant : participants->participants_) { + DialogParticipant dialog_participant(std::move(participant), channel_type); + if (!dialog_participant.is_valid() || !dialog_participant.status_.is_administrator() || + dialog_participant.dialog_id_.get_type() != DialogType::User) { + LOG(ERROR) << "Receive " << dialog_participant << " as an administrator of " << channel_id_; + continue; + } + administrators.emplace_back(dialog_participant.dialog_id_.get_user_id(), + dialog_participant.status_.get_rank(), dialog_participant.status_.is_creator()); + } + + td_->contacts_manager_->on_update_channel_administrator_count(channel_id_, + narrow_cast(administrators.size())); + td_->dialog_participant_manager_->on_update_dialog_administrators(DialogId(channel_id_), + std::move(administrators), true, false); + + break; + } + case telegram_api::channels_channelParticipantsNotModified::ID: + break; + default: + UNREACHABLE(); + } + + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelAdministratorsQuery"); + promise_.set_error(std::move(status)); + } +}; + DialogParticipantManager::DialogParticipantManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { update_dialog_online_member_count_timeout_.set_callback(on_update_dialog_online_member_count_timeout_callback); update_dialog_online_member_count_timeout_.set_callback_data(static_cast(this)); } +DialogParticipantManager::~DialogParticipantManager() { + Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), dialog_administrators_); +} + void DialogParticipantManager::tear_down() { parent_.reset(); } @@ -458,4 +542,232 @@ void DialogParticipantManager::process_dialog_join_requests(DialogId dialog_id, td_->create_handler(std::move(promise))->send(dialog_id, invite_link, approve); } +void DialogParticipantManager::speculative_update_dialog_administrators(DialogId dialog_id, UserId user_id, + const DialogParticipantStatus &new_status, + const DialogParticipantStatus &old_status) { + if (new_status.is_administrator() == old_status.is_administrator() && + new_status.get_rank() == old_status.get_rank()) { + return; + } + auto it = dialog_administrators_.find(dialog_id); + if (it == dialog_administrators_.end()) { + return; + } + auto administrators = it->second; + if (new_status.is_administrator()) { + bool is_found = false; + for (auto &administrator : administrators) { + if (administrator.get_user_id() == user_id) { + is_found = true; + if (administrator.get_rank() != new_status.get_rank() || + administrator.is_creator() != new_status.is_creator()) { + administrator = DialogAdministrator(user_id, new_status.get_rank(), new_status.is_creator()); + on_update_dialog_administrators(dialog_id, std::move(administrators), true, false); + } + break; + } + } + if (!is_found) { + administrators.emplace_back(user_id, new_status.get_rank(), new_status.is_creator()); + on_update_dialog_administrators(dialog_id, std::move(administrators), true, false); + } + } else { + size_t i = 0; + while (i != administrators.size() && administrators[i].get_user_id() != user_id) { + i++; + } + if (i != administrators.size()) { + administrators.erase(administrators.begin() + i); + on_update_dialog_administrators(dialog_id, std::move(administrators), true, false); + } + } +} + +td_api::object_ptr DialogParticipantManager::get_chat_administrators_object( + const vector &dialog_administrators) { + auto administrator_objects = transform(dialog_administrators, [this](const DialogAdministrator &administrator) { + return administrator.get_chat_administrator_object(td_->contacts_manager_.get()); + }); + return td_api::make_object(std::move(administrator_objects)); +} + +void DialogParticipantManager::get_dialog_administrators( + DialogId dialog_id, Promise> &&promise) { + if (!td_->dialog_manager_->have_dialog_force(dialog_id, "get_dialog_administrators")) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + + switch (dialog_id.get_type()) { + case DialogType::User: + case DialogType::SecretChat: + return promise.set_value(td_api::make_object()); + case DialogType::Chat: + case DialogType::Channel: + break; + case DialogType::None: + default: + UNREACHABLE(); + return; + } + + auto it = dialog_administrators_.find(dialog_id); + if (it != dialog_administrators_.end()) { + reload_dialog_administrators(dialog_id, it->second, Auto()); // update administrators cache + return promise.set_value(get_chat_administrators_object(it->second)); + } + + if (G()->use_chat_info_database()) { + LOG(INFO) << "Load administrators of " << dialog_id << " from database"; + G()->td_db()->get_sqlite_pmc()->get( + get_dialog_administrators_database_key(dialog_id), + PromiseCreator::lambda( + [actor_id = actor_id(this), dialog_id, promise = std::move(promise)](string value) mutable { + send_closure(actor_id, &DialogParticipantManager::on_load_dialog_administrators_from_database, dialog_id, + std::move(value), std::move(promise)); + })); + return; + } + + reload_dialog_administrators(dialog_id, {}, std::move(promise)); +} + +string DialogParticipantManager::get_dialog_administrators_database_key(DialogId dialog_id) { + return PSTRING() << "adm" << (-dialog_id.get()); +} + +void DialogParticipantManager::on_load_dialog_administrators_from_database( + DialogId dialog_id, string value, Promise> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + if (value.empty()) { + return reload_dialog_administrators(dialog_id, {}, std::move(promise)); + } + + vector administrators; + if (log_event_parse(administrators, value).is_error()) { + return reload_dialog_administrators(dialog_id, {}, std::move(promise)); + } + + LOG(INFO) << "Successfully loaded " << administrators.size() << " administrators in " << dialog_id + << " from database"; + + MultiPromiseActorSafe load_users_multipromise{"LoadUsersMultiPromiseActor"}; + load_users_multipromise.add_promise( + PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, administrators, + promise = std::move(promise)](Result result) mutable { + send_closure(actor_id, &DialogParticipantManager::on_load_administrator_users_finished, dialog_id, + std::move(administrators), std::move(result), std::move(promise)); + })); + + auto lock_promise = load_users_multipromise.get_promise(); + + for (auto &administrator : administrators) { + td_->contacts_manager_->get_user(administrator.get_user_id(), 3, load_users_multipromise.get_promise()); + } + + lock_promise.set_value(Unit()); +} + +void DialogParticipantManager::on_load_administrator_users_finished( + DialogId dialog_id, vector administrators, Result<> result, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + if (result.is_error()) { + return reload_dialog_administrators(dialog_id, {}, std::move(promise)); + } + + auto it = dialog_administrators_.emplace(dialog_id, std::move(administrators)).first; + reload_dialog_administrators(dialog_id, it->second, Auto()); // update administrators cache + promise.set_value(get_chat_administrators_object(it->second)); +} + +void DialogParticipantManager::on_update_dialog_administrators(DialogId dialog_id, + vector &&administrators, + bool have_access, bool from_database) { + LOG(INFO) << "Update administrators in " << dialog_id << " to " << format::as_array(administrators); + if (have_access) { + CHECK(dialog_id.is_valid()); + std::sort(administrators.begin(), administrators.end(), + [](const DialogAdministrator &lhs, const DialogAdministrator &rhs) { + return lhs.get_user_id().get() < rhs.get_user_id().get(); + }); + + auto it = dialog_administrators_.find(dialog_id); + if (it != dialog_administrators_.end()) { + if (it->second == administrators) { + return; + } + it->second = std::move(administrators); + } else { + it = dialog_administrators_.emplace(dialog_id, std::move(administrators)).first; + } + + if (G()->use_chat_info_database() && !from_database) { + LOG(INFO) << "Save administrators of " << dialog_id << " to database"; + G()->td_db()->get_sqlite_pmc()->set(get_dialog_administrators_database_key(dialog_id), + log_event_store(it->second).as_slice().str(), Auto()); + } + } else { + dialog_administrators_.erase(dialog_id); + if (G()->use_chat_info_database()) { + G()->td_db()->get_sqlite_pmc()->erase(get_dialog_administrators_database_key(dialog_id), Auto()); + } + } +} + +void DialogParticipantManager::reload_dialog_administrators( + DialogId dialog_id, const vector &dialog_administrators, + Promise> &&promise) { + auto dialog_type = dialog_id.get_type(); + if (dialog_type == DialogType::Chat && + !td_->contacts_manager_->get_chat_permissions(dialog_id.get_chat_id()).is_member()) { + return promise.set_value(td_api::make_object()); + } + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), dialog_id, promise = std::move(promise)](Result &&result) mutable { + if (promise) { + if (result.is_ok()) { + send_closure(actor_id, &DialogParticipantManager::on_reload_dialog_administrators, dialog_id, + std::move(promise)); + } else { + promise.set_error(result.move_as_error()); + } + } + }); + switch (dialog_type) { + case DialogType::Chat: + td_->contacts_manager_->load_chat_full(dialog_id.get_chat_id(), false, std::move(query_promise), + "reload_dialog_administrators"); + break; + case DialogType::Channel: { + auto channel_id = dialog_id.get_channel_id(); + if (td_->contacts_manager_->is_broadcast_channel(channel_id) && + !td_->contacts_manager_->get_channel_status(channel_id).is_administrator()) { + return query_promise.set_error(Status::Error(400, "Administrator list is inaccessible")); + } + auto hash = get_vector_hash(transform(dialog_administrators, [](const DialogAdministrator &administrator) { + return static_cast(administrator.get_user_id().get()); + })); + td_->create_handler(std::move(query_promise))->send(channel_id, hash); + break; + } + default: + UNREACHABLE(); + } +} + +void DialogParticipantManager::on_reload_dialog_administrators( + DialogId dialog_id, Promise> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + auto it = dialog_administrators_.find(dialog_id); + if (it != dialog_administrators_.end()) { + return promise.set_value(get_chat_administrators_object(it->second)); + } + + LOG(ERROR) << "Failed to load administrators in " << dialog_id; + promise.set_error(Status::Error(500, "Failed to find chat administrators")); +} + } // namespace td diff --git a/td/telegram/DialogParticipantManager.h b/td/telegram/DialogParticipantManager.h index 97f6bd449..0d2d1455f 100644 --- a/td/telegram/DialogParticipantManager.h +++ b/td/telegram/DialogParticipantManager.h @@ -6,7 +6,9 @@ // #pragma once +#include "td/telegram/DialogAdministrator.h" #include "td/telegram/DialogId.h" +#include "td/telegram/DialogParticipant.h" #include "td/telegram/td_api.h" #include "td/actor/actor.h" @@ -24,6 +26,11 @@ class Td; class DialogParticipantManager final : public Actor { public: DialogParticipantManager(Td *td, ActorShared<> parent); + DialogParticipantManager(const DialogParticipantManager &) = delete; + DialogParticipantManager &operator=(const DialogParticipantManager &) = delete; + DialogParticipantManager(DialogParticipantManager &&) = delete; + DialogParticipantManager &operator=(DialogParticipantManager &&) = delete; + ~DialogParticipantManager() final; static constexpr int32 ONLINE_MEMBER_COUNT_CACHE_EXPIRE_TIME = 30 * 60; @@ -42,6 +49,18 @@ class DialogParticipantManager final : public Actor { void process_dialog_join_requests(DialogId dialog_id, const string &invite_link, bool approve, Promise &&promise); + void speculative_update_dialog_administrators(DialogId dialog_id, UserId user_id, + const DialogParticipantStatus &new_status, + const DialogParticipantStatus &old_status); + + void on_update_dialog_administrators(DialogId dialog_id, vector &&administrators, + bool have_access, bool from_database); + + void get_dialog_administrators(DialogId dialog_id, Promise> &&promise); + + void reload_dialog_administrators(DialogId dialog_id, const vector &dialog_administrators, + Promise> &&promise); + void get_current_state(vector> &updates) const; private: @@ -61,6 +80,21 @@ class DialogParticipantManager final : public Actor { Status can_manage_dialog_join_requests(DialogId dialog_id); + td_api::object_ptr get_chat_administrators_object( + const vector &dialog_administrators); + + static string get_dialog_administrators_database_key(DialogId dialog_id); + + void on_load_dialog_administrators_from_database(DialogId dialog_id, string value, + Promise> &&promise); + + void on_load_administrator_users_finished(DialogId dialog_id, vector administrators, + Result result, + Promise> &&promise); + + void on_reload_dialog_administrators(DialogId dialog_id, + Promise> &&promise); + struct OnlineMemberCountInfo { int32 online_member_count = 0; double update_time = 0; @@ -70,6 +104,8 @@ class DialogParticipantManager final : public Actor { MultiTimeout update_dialog_online_member_count_timeout_{"UpdateDialogOnlineMemberCountTimeout"}; + FlatHashMap, DialogIdHash> dialog_administrators_; + Td *td_; ActorShared<> parent_; }; diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index f81a91c08..c42d3a237 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -7009,7 +7009,7 @@ void Td::on_request(uint64 id, td_api::searchChatMembers &request) { void Td::on_request(uint64 id, const td_api::getChatAdministrators &request) { CREATE_REQUEST_PROMISE(); - contacts_manager_->get_dialog_administrators(DialogId(request.chat_id_), std::move(promise)); + dialog_participant_manager_->get_dialog_administrators(DialogId(request.chat_id_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::replacePrimaryChatInviteLink &request) {