diff --git a/td/telegram/ContactsManager.cpp b/td/telegram/ContactsManager.cpp index 88a3004b1..f9a0d9dee 100644 --- a/td/telegram/ContactsManager.cpp +++ b/td/telegram/ContactsManager.cpp @@ -3453,6 +3453,9 @@ ContactsManager::ContactsManager(Td *td, ActorShared<> parent) : td_(td), parent invite_link_info_expire_timeout_.set_callback(on_invite_link_info_expire_timeout_callback); invite_link_info_expire_timeout_.set_callback_data(static_cast(this)); + + channel_participant_cache_timeout_.set_callback(on_channel_participant_cache_timeout_callback); + channel_participant_cache_timeout_.set_callback_data(static_cast(this)); } ContactsManager::~ContactsManager() = default; @@ -3614,6 +3617,44 @@ void ContactsManager::on_invite_link_info_expire_timeout(DialogId dialog_id) { remove_dialog_access_by_invite_link(dialog_id); } +void ContactsManager::on_channel_participant_cache_timeout_callback(void *contacts_manager_ptr, int64 channel_id_long) { + if (G()->close_flag()) { + return; + } + + auto contacts_manager = static_cast(contacts_manager_ptr); + send_closure_later(contacts_manager->actor_id(contacts_manager), + &ContactsManager::on_channel_participant_cache_timeout, + ChannelId(narrow_cast(channel_id_long))); +} + +void ContactsManager::on_channel_participant_cache_timeout(ChannelId channel_id) { + if (G()->close_flag()) { + return; + } + + auto channel_participants_it = channel_participants_.find(channel_id); + if (channel_participants_it == channel_participants_.end()) { + return; + } + + auto &participants = channel_participants_it->second.participants_; + auto min_access_date = G()->unix_time() - CHANNEL_PARTICIPANT_CACHE_TIME; + for (auto it = participants.begin(); it != participants.end();) { + if (it->second.last_access_date_ < min_access_date) { + it = participants.erase(it); + } else { + ++it; + } + } + + if (participants.empty()) { + channel_participants_.erase(channel_participants_it); + } else { + channel_participant_cache_timeout_.set_timeout_in(channel_id.get(), CHANNEL_PARTICIPANT_CACHE_TIME); + } +} + template void ContactsManager::BotInfo::store(StorerT &storer) const { using td::store; @@ -11749,6 +11790,11 @@ void ContactsManager::on_get_channel_participants( on_update_channel_bot_user_ids(channel_id, std::move(bot_user_ids)); } } + if (have_channel_participant_cache(channel_id)) { + for (const auto &participant : result) { + add_channel_participant_to_cache(channel_id, participant, false); + } + } if (participant_count != -1 || administrator_count != -1) { auto channel_full = get_channel_full_force(channel_id, "on_get_channel_participants_success"); @@ -11803,6 +11849,46 @@ void ContactsManager::on_get_channel_participants( promise.set_value(DialogParticipants{total_count, std::move(result)}); } +bool ContactsManager::have_channel_participant_cache(ChannelId channel_id) const { + if (!td_->auth_manager_->is_bot()) { + return false; + } + auto c = get_channel(channel_id); + return c != nullptr && c->status.is_administrator(); +} + +void ContactsManager::add_channel_participant_to_cache(ChannelId channel_id, + const DialogParticipant &dialog_participant, + bool allow_replace) { + auto &participants = channel_participants_[channel_id]; + if (participants.participants_.empty()) { + channel_participant_cache_timeout_.set_timeout_in(channel_id.get(), CHANNEL_PARTICIPANT_CACHE_TIME); + } + auto &participant_info = participants.participants_[dialog_participant.dialog_id]; + if (participant_info.last_access_date_ > 0 && !allow_replace) { + return; + } + participant_info.participant_ = dialog_participant; + participant_info.last_access_date_ = G()->unix_time(); +} + +const DialogParticipant *ContactsManager::get_channel_participant_from_cache(ChannelId channel_id, + DialogId participant_dialog_id) const { + auto channel_participants_it = channel_participants_.find(channel_id); + if (channel_participants_it == channel_participants_.end()) { + return nullptr; + } + + auto &participants = channel_participants_it->second.participants_; + CHECK(!participants.empty()); + auto it = participants.find(participant_dialog_id); + if (it != participants.end()) { + it->second.last_access_date_ = G()->unix_time(); + return &it->second.participant_; + } + return nullptr; +} + bool ContactsManager::speculative_add_count(int32 &count, int32 delta_count, int32 min_count) { auto new_count = count + delta_count; if (new_count < min_count) { @@ -13103,6 +13189,9 @@ void ContactsManager::on_channel_status_changed(const Channel *c, ChannelId chan send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_group_call_rights, DialogId(channel_id)); } + if (td_->auth_manager_->is_bot() && old_status.is_administrator() && !new_status.is_administrator()) { + channel_participants_.erase(channel_id); + } // must not load ChannelFull, because must not change the Channel CHECK(have_channel_full == (get_channel_full(channel_id) != nullptr)); @@ -13408,6 +13497,13 @@ void ContactsManager::on_update_channel_participant(ChannelId channel_id, UserId return; } + if (old_dialog_participant.dialog_id == DialogId(get_my_id()) && old_dialog_participant.status.is_administrator() && + !new_dialog_participant.status.is_administrator()) { + channel_participants_.erase(channel_id); + } else if (have_channel_participant_cache(channel_id)) { + add_channel_participant_to_cache(channel_id, new_dialog_participant, true); + } + send_update_chat_member(DialogId(channel_id), user_id, date, invite_link, old_dialog_participant, new_dialog_participant); } @@ -14930,6 +15026,14 @@ DialogParticipant ContactsManager::get_channel_participant(ChannelId channel_id, return DialogParticipant(); } + if (have_channel_participant_cache(channel_id)) { + auto *participant = get_channel_participant_from_cache(channel_id, participant_dialog_id); + if (participant != nullptr) { + promise.set_value(Unit()); + return *participant; + } + } + if (!td_->auth_manager_->is_bot() && participant_dialog_id.get_type() == DialogType::User && is_user_bot(participant_dialog_id.get_user_id())) { auto user_id = participant_dialog_id.get_user_id(); @@ -14953,24 +15057,26 @@ DialogParticipant ContactsManager::get_channel_participant(ChannelId channel_id, LOG(DEBUG) << "Get info about " << participant_dialog_id << " membership in the " << channel_id << " with random_id " << random_id; - auto on_result_promise = PromiseCreator::lambda([actor_id = actor_id(this), random_id, promise = std::move(promise)]( - Result r_dialog_participant) mutable { - send_closure(actor_id, &ContactsManager::on_get_channel_participant, random_id, std::move(r_dialog_participant), - std::move(promise)); - }); + auto on_result_promise = + PromiseCreator::lambda([actor_id = actor_id(this), channel_id, random_id, + promise = std::move(promise)](Result r_dialog_participant) mutable { + send_closure(actor_id, &ContactsManager::on_get_channel_participant, channel_id, random_id, + std::move(r_dialog_participant), std::move(promise)); + }); td_->create_handler(std::move(on_result_promise)) ->send(channel_id, participant_dialog_id, std::move(input_peer)); return DialogParticipant(); } -void ContactsManager::on_get_channel_participant(int64 random_id, Result r_dialog_participant, +void ContactsManager::on_get_channel_participant(ChannelId channel_id, int64 random_id, + Result r_dialog_participant, Promise &&promise) { if (G()->close_flag()) { return promise.set_error(Status::Error(500, "Request aborted")); } - LOG(INFO) << "Receive a member of a channel with random_id " << random_id; + LOG(INFO) << "Receive a member of a channel " << channel_id << " with random_id " << random_id; auto it = received_channel_participant_.find(random_id); CHECK(it != received_channel_participant_.end()); @@ -14980,6 +15086,9 @@ void ContactsManager::on_get_channel_participant(int64 random_id, Resultsecond = r_dialog_participant.move_as_ok(); + if (have_channel_participant_cache(channel_id)) { + add_channel_participant_to_cache(channel_id, it->second, false); + } promise.set_value(Unit()); } } diff --git a/td/telegram/ContactsManager.h b/td/telegram/ContactsManager.h index dcadb4de7..1bac89767 100644 --- a/td/telegram/ContactsManager.h +++ b/td/telegram/ContactsManager.h @@ -979,6 +979,8 @@ class ContactsManager : public Actor { static constexpr size_t MAX_BIO_LENGTH = 70; // server side limit static constexpr int32 MAX_GET_CHANNEL_PARTICIPANTS = 200; // server side limit + static constexpr int32 CHANNEL_PARTICIPANT_CACHE_TIME = 1800; // some reasonable limit + static constexpr int32 USER_FLAG_HAS_ACCESS_HASH = 1 << 0; static constexpr int32 USER_FLAG_HAS_FIRST_NAME = 1 << 1; static constexpr int32 USER_FLAG_HAS_LAST_NAME = 1 << 2; @@ -1493,7 +1495,7 @@ class ContactsManager : public Actor { void delete_chat_participant(ChatId chat_id, UserId user_id, bool revoke_messages, Promise &&promise); - void on_get_channel_participant(int64 random_id, Result r_dialog_participant, + void on_get_channel_participant(ChannelId channel_id, int64 random_id, Result r_dialog_participant, Promise &&promise); void search_chat_participants(ChatId chat_id, const string &query, int32 limit, DialogParticipantsFilter filter, @@ -1511,6 +1513,14 @@ class ContactsManager : public Actor { tl_object_ptr &&channel_participants, Promise &&promise); + bool have_channel_participant_cache(ChannelId channel_id) const; + + void add_channel_participant_to_cache(ChannelId channel_id, const DialogParticipant &dialog_participant, + bool allow_replace); + + const DialogParticipant *get_channel_participant_from_cache(ChannelId channel_id, + DialogId participant_dialog_id) const; + void change_channel_participant_status_impl(ChannelId channel_id, DialogId participant_dialog_id, DialogParticipantStatus status, DialogParticipantStatus old_status, Promise &&promise); @@ -1553,6 +1563,8 @@ class ContactsManager : public Actor { static void on_invite_link_info_expire_timeout_callback(void *contacts_manager_ptr, int64 dialog_id_long); + static void on_channel_participant_cache_timeout_callback(void *contacts_manager_ptr, int64 channel_id_long); + void on_user_online_timeout(UserId user_id); void on_channel_unban_timeout(ChannelId channel_id); @@ -1563,6 +1575,8 @@ class ContactsManager : public Actor { void on_invite_link_info_expire_timeout(DialogId dialog_id); + void on_channel_participant_cache_timeout(ChannelId channel_id); + void tear_down() override; Td *td_; @@ -1667,6 +1681,17 @@ class ContactsManager : public Actor { std::unordered_map, ChannelIdHash> cached_channel_participants_; + // bot-administrators only + struct ChannelParticipantInfo { + DialogParticipant participant_; + + mutable int32 last_access_date_ = 0; + }; + struct ChannelParticipants { + std::unordered_map participants_; + }; + std::unordered_map channel_participants_; + bool are_contacts_loaded_ = false; int32 next_contacts_sync_date_ = 0; Hints contacts_hints_; // search contacts by first name, last name and username @@ -1710,6 +1735,7 @@ class ContactsManager : public Actor { MultiTimeout user_nearby_timeout_{"UserNearbyTimeout"}; MultiTimeout slow_mode_delay_timeout_{"SlowModeDelayTimeout"}; MultiTimeout invite_link_info_expire_timeout_{"InviteLinkInfoExpireTimeout"}; + MultiTimeout channel_participant_cache_timeout_{"ChannelParticipantCacheTimeout"}; }; } // namespace td