diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index 54a56d40b..e097ad43f 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -340,6 +340,24 @@ chatMember user_id:int32 inviter_user_id:int32 joined_chat_date:int32 status:Cha chatMembers total_count:int32 members:vector = ChatMembers; +//@class ChatMembersFilter @description Specifies the kind of chat members to return in searchChatMembers + +//@description Returns the creator and administrators +chatMembersFilterAdministrators = ChatMembersFilter; + +//@description Returns all chat members, including restricted chat members +chatMembersFilterMembers = ChatMembersFilter; + +//@description Returns users under certain restrictions in the chat; can be used only by administrators in a supergroup +chatMembersFilterRestricted = ChatMembersFilter; + +//@description Returns users banned from the chat; can be used only by administrators in a supergroup or in a channel +chatMembersFilterBanned = ChatMembersFilter; + +//@description Returns bot members of the chat +chatMembersFilterBots = ChatMembersFilter; + + //@class SupergroupMembersFilter @description Specifies the kind of chat members to return in getSupergroupMembers //@description Returns recently active users in reverse chronological order @@ -2724,8 +2742,8 @@ setChatMemberStatus chat_id:int53 user_id:int32 status:ChatMemberStatus = Ok; //@description Returns information about a single member of a chat @chat_id Chat identifier @user_id User identifier getChatMember chat_id:int53 user_id:int32 = ChatMember; -//@description Searches for a specified query in the first name, last name and username of the members of a specified chat. Requires administrator rights in channels @chat_id Chat identifier @query Query to search for @limit The maximum number of users to be returned -searchChatMembers chat_id:int53 query:string limit:int32 = ChatMembers; +//@description Searches for a specified query in the first name, last name and username of the members of a specified chat. Requires administrator rights in channels @chat_id Chat identifier @query Query to search for @limit The maximum number of users to be returned The type of users to return. By default, chatMembersFilterMembers +searchChatMembers chat_id:int53 query:string limit:int32 filter:ChatMembersFilter = ChatMembers; //@description Returns a list of users who are administrators of the chat @chat_id Chat identifier getChatAdministrators chat_id:int53 = Users; diff --git a/td/generate/scheme/td_api.tlo b/td/generate/scheme/td_api.tlo index 0fbac693f..db80463ff 100644 Binary files a/td/generate/scheme/td_api.tlo and b/td/generate/scheme/td_api.tlo differ diff --git a/td/telegram/ContactsManager.cpp b/td/telegram/ContactsManager.cpp index 59d8d3fa2..89dff8bfa 100644 --- a/td/telegram/ContactsManager.cpp +++ b/td/telegram/ContactsManager.cpp @@ -8816,6 +8816,7 @@ DialogParticipant ContactsManager::get_chat_participant(ChatId chat_id, UserId u std::pair> ContactsManager::search_chat_participants(ChatId chat_id, const string &query, int32 limit, + DialogParticipantsFilter filter, bool force, Promise &&promise) { if (limit < 0) { @@ -8835,7 +8836,31 @@ std::pair> ContactsManager::search_chat_partici return {}; } - auto user_ids = transform(chat_full->participants, [](const auto &participant) { return participant.user_id; }); + auto is_dialog_participant_suitable = [this](const DialogParticipant &participant, DialogParticipantsFilter filter) { + switch (filter) { + case DialogParticipantsFilter::Administrators: + return participant.status.is_administrator(); + case DialogParticipantsFilter::Members: + return participant.status.is_member(); // should be always true + case DialogParticipantsFilter::Restricted: + return participant.status.is_restricted(); // should be always false + case DialogParticipantsFilter::Banned: + return participant.status.is_banned(); // should be always false + case DialogParticipantsFilter::Bots: + return is_user_bot(participant.user_id); + default: + UNREACHABLE(); + return false; + } + }; + + vector user_ids; + for (auto &participant : chat_full->participants) { + if (is_dialog_participant_suitable(participant, filter)) { + user_ids.push_back(participant.user_id); + } + } + int32 total_count; std::tie(total_count, user_ids) = search_among_users(user_ids, query, limit); return {total_count, transform(user_ids, [&](UserId user_id) { return *get_chat_participant(chat_full, user_id); })}; @@ -8902,8 +8927,8 @@ DialogParticipant ContactsManager::get_channel_participant(ChannelId channel_id, } std::pair> ContactsManager::get_channel_participants( - ChannelId channel_id, const tl_object_ptr &filter, int32 offset, int32 limit, - int64 &random_id, bool force, Promise &&promise) { + ChannelId channel_id, const tl_object_ptr &filter, const string &additional_query, + int32 offset, int32 limit, int32 additional_limit, int64 &random_id, bool force, Promise &&promise) { if (random_id != 0) { // request has already been sent before auto it = received_channel_participants_.find(random_id); @@ -8911,6 +8936,25 @@ std::pair> ContactsManager::get_channel_partici auto result = std::move(it->second); received_channel_participants_.erase(it); promise.set_value(Unit()); + + if (additional_query.empty()) { + return result; + } + + auto user_ids = transform(result.second, [](const auto &participant) { return participant.user_id; }); + std::pair> result_user_ids = search_among_users(user_ids, additional_query, additional_limit); + + result.first = result_user_ids.first; + std::unordered_set result_user_ids_set(result_user_ids.second.begin(), + result_user_ids.second.end()); + auto all_participants = std::move(result.second); + result.second.clear(); + for (auto &participant : all_participants) { + if (result_user_ids_set.count(participant.user_id)) { + result.second.push_back(std::move(participant)); + result_user_ids_set.erase(participant.user_id); + } + } return result; } diff --git a/td/telegram/ContactsManager.h b/td/telegram/ContactsManager.h index 17ffd93f1..74084c96f 100644 --- a/td/telegram/ContactsManager.h +++ b/td/telegram/ContactsManager.h @@ -377,14 +377,16 @@ class ContactsManager : public Actor { DialogParticipant get_chat_participant(ChatId chat_id, UserId user_id, bool force, Promise &&promise); std::pair> search_chat_participants(ChatId chat_id, const string &query, int32 limit, - bool force, Promise &&promise); + DialogParticipantsFilter filter, bool force, + Promise &&promise); DialogParticipant get_channel_participant(ChannelId channel_id, UserId user_id, int64 &random_id, bool force, Promise &&promise); std::pair> get_channel_participants( - ChannelId channel_id, const tl_object_ptr &filter, int32 offset, int32 limit, - int64 &random_id, bool force, Promise &&promise); + ChannelId channel_id, const tl_object_ptr &filter, + const string &additional_query, int32 offset, int32 limit, int32 additional_limit, int64 &random_id, bool force, + Promise &&promise); DialogParticipant get_dialog_participant(ChannelId channel_id, tl_object_ptr &&participant_ptr) const; diff --git a/td/telegram/DialogParticipant.cpp b/td/telegram/DialogParticipant.cpp index 016509a35..d524e0530 100644 --- a/td/telegram/DialogParticipant.cpp +++ b/td/telegram/DialogParticipant.cpp @@ -425,4 +425,25 @@ ChannelParticipantsFilter::ChannelParticipantsFilter(const tl_object_ptr &filter) { + if (filter == nullptr) { + return DialogParticipantsFilter::Members; + } + switch (filter->get_id()) { + case td_api::chatMembersFilterAdministrators::ID: + return DialogParticipantsFilter::Administrators; + case td_api::chatMembersFilterMembers::ID: + return DialogParticipantsFilter::Members; + case td_api::chatMembersFilterRestricted::ID: + return DialogParticipantsFilter::Restricted; + case td_api::chatMembersFilterBanned::ID: + return DialogParticipantsFilter::Banned; + case td_api::chatMembersFilterBots::ID: + return DialogParticipantsFilter::Bots; + default: + UNREACHABLE(); + return DialogParticipantsFilter::Members; + } +} + } // namespace td diff --git a/td/telegram/DialogParticipant.h b/td/telegram/DialogParticipant.h index 5bb3d22bb..158e966cd 100644 --- a/td/telegram/DialogParticipant.h +++ b/td/telegram/DialogParticipant.h @@ -252,6 +252,10 @@ class ChannelParticipantsFilter { } }; +enum class DialogParticipantsFilter : int32 { Administrators, Members, Restricted, Banned, Bots }; + +DialogParticipantsFilter get_dialog_participants_filter(const tl_object_ptr &filter); + DialogParticipantStatus get_dialog_participant_status(const tl_object_ptr &status); DialogParticipantStatus get_dialog_participant_status( diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index 08c9c9d02..49e42cf96 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -21562,11 +21562,33 @@ DialogParticipant MessagesManager::get_dialog_participant(DialogId dialog_id, Us return DialogParticipant(); } -std::pair> MessagesManager::search_private_chat_participants(UserId my_user_id, - UserId peer_user_id, - const string &query, - int32 limit) const { - auto result = td_->contacts_manager_->search_among_users({my_user_id, peer_user_id}, query, limit); +std::pair> MessagesManager::search_private_chat_participants( + UserId my_user_id, UserId peer_user_id, const string &query, int32 limit, DialogParticipantsFilter filter) const { + vector user_ids; + switch (filter) { + case DialogParticipantsFilter::Administrators: + break; + case DialogParticipantsFilter::Members: + user_ids.push_back(my_user_id); + user_ids.push_back(peer_user_id); + break; + case DialogParticipantsFilter::Restricted: + break; + case DialogParticipantsFilter::Banned: + break; + case DialogParticipantsFilter::Bots: + if (td_->auth_manager_->is_bot()) { + user_ids.push_back(my_user_id); + } + if (td_->contacts_manager_->is_user_bot(peer_user_id)) { + user_ids.push_back(peer_user_id); + } + break; + default: + UNREACHABLE(); + } + + auto result = td_->contacts_manager_->search_among_users(user_ids, query, limit); return {result.first, transform(result.second, [&](UserId user_id) { return DialogParticipant(user_id, user_id == my_user_id ? peer_user_id : my_user_id, 0, DialogParticipantStatus::Member()); @@ -21574,7 +21596,8 @@ std::pair> MessagesManager::search_private_chat } std::pair> MessagesManager::search_dialog_participants( - DialogId dialog_id, const string &query, int32 limit, int64 &random_id, bool force, Promise &&promise) { + DialogId dialog_id, const string &query, int32 limit, DialogParticipantsFilter filter, int64 &random_id, bool force, + Promise &&promise) { LOG(INFO) << "Receive SearchChatMembers request to search for " << query << " in " << dialog_id; if (!have_dialog_force(dialog_id)) { promise.set_error(Status::Error(3, "Chat not found")); @@ -21589,19 +21612,49 @@ std::pair> MessagesManager::search_dialog_parti case DialogType::User: promise.set_value(Unit()); return search_private_chat_participants(td_->contacts_manager_->get_my_id("search_dialog_participants"), - dialog_id.get_user_id(), query, limit); + dialog_id.get_user_id(), query, limit, filter); case DialogType::Chat: - return td_->contacts_manager_->search_chat_participants(dialog_id.get_chat_id(), query, limit, force, + return td_->contacts_manager_->search_chat_participants(dialog_id.get_chat_id(), query, limit, filter, force, std::move(promise)); - case DialogType::Channel: - return td_->contacts_manager_->get_channel_participants( - dialog_id.get_channel_id(), td_api::make_object(query), 0, limit, - random_id, force, std::move(promise)); + case DialogType::Channel: { + tl_object_ptr request_filter; + string additional_query; + int32 additional_limit = 0; + switch (filter) { + case DialogParticipantsFilter::Administrators: + request_filter = td_api::make_object(); + additional_query = query; + additional_limit = limit; + limit = 100; + break; + case DialogParticipantsFilter::Members: + request_filter = td_api::make_object(query); + break; + case DialogParticipantsFilter::Restricted: + request_filter = td_api::make_object(query); + break; + case DialogParticipantsFilter::Banned: + request_filter = td_api::make_object(query); + break; + case DialogParticipantsFilter::Bots: + request_filter = td_api::make_object(); + additional_query = query; + additional_limit = limit; + limit = 100; + break; + default: + UNREACHABLE(); + } + + return td_->contacts_manager_->get_channel_participants(dialog_id.get_channel_id(), request_filter, + additional_query, 0, limit, additional_limit, random_id, + force, std::move(promise)); + } case DialogType::SecretChat: { promise.set_value(Unit()); auto peer_user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); return search_private_chat_participants(td_->contacts_manager_->get_my_id("search_dialog_participants"), - peer_user_id, query, limit); + peer_user_id, query, limit, filter); } case DialogType::None: default: diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index 729e1e2f0..0a8c4b513 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -1171,7 +1171,8 @@ class MessagesManager : public Actor { Promise &&promise); std::pair> search_dialog_participants(DialogId dialog_id, const string &query, - int32 limit, int64 &random_id, bool force, + int32 limit, DialogParticipantsFilter filter, + int64 &random_id, bool force, Promise &&promise); vector get_dialog_administrators(DialogId dialog_id, int left_tries, Promise &&promise); @@ -2397,7 +2398,8 @@ class MessagesManager : public Actor { void update_dialogs_hints_rating(const Dialog *d); std::pair> search_private_chat_participants(UserId my_user_id, UserId peer_user_id, - const string &query, int32 limit) const; + const string &query, int32 limit, + DialogParticipantsFilter filter) const; static unique_ptr *find_message(unique_ptr *v, MessageId message_id); static const unique_ptr *find_message(const unique_ptr *v, MessageId message_id); diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 84c8f2588..3a6707aca 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -6,8 +6,6 @@ // #include "td/telegram/Td.h" -#include "td/db/binlog/BinlogEvent.h" - #include "td/telegram/net/ConnectionCreator.h" #include "td/telegram/net/DcId.h" #include "td/telegram/net/MtprotoHeader.h" @@ -71,6 +69,8 @@ #include "td/actor/actor.h" #include "td/actor/PromiseFuture.h" +#include "td/db/binlog/BinlogEvent.h" + #include "td/mtproto/utils.h" // for create_storer, fetch_result, etc, TODO #include "td/utils/buffer.h" @@ -1850,12 +1850,13 @@ class SearchChatMembersRequest : public RequestActor<> { DialogId dialog_id_; string query_; int32 limit_; + DialogParticipantsFilter filter_; int64 random_id_ = 0; std::pair> participants_; void do_run(Promise &&promise) override { - participants_ = td->messages_manager_->search_dialog_participants(dialog_id_, query_, limit_, random_id_, + participants_ = td->messages_manager_->search_dialog_participants(dialog_id_, query_, limit_, filter_, random_id_, get_tries() < 3, std::move(promise)); } @@ -1871,8 +1872,13 @@ class SearchChatMembersRequest : public RequestActor<> { } public: - SearchChatMembersRequest(ActorShared td, uint64 request_id, int64 dialog_id, string &&query, int32 limit) - : RequestActor(std::move(td), request_id), dialog_id_(dialog_id), query_(std::move(query)), limit_(limit) { + SearchChatMembersRequest(ActorShared td, uint64 request_id, int64 dialog_id, string &&query, int32 limit, + DialogParticipantsFilter filter) + : RequestActor(std::move(td), request_id) + , dialog_id_(dialog_id) + , query_(std::move(query)) + , limit_(limit) + , filter_(filter) { set_tries(3); } }; @@ -2158,8 +2164,8 @@ class GetSupergroupMembersRequest : public RequestActor<> { std::pair> participants_; void do_run(Promise &&promise) override { - participants_ = td->contacts_manager_->get_channel_participants(channel_id_, filter_, offset_, limit_, random_id_, - get_tries() < 3, std::move(promise)); + participants_ = td->contacts_manager_->get_channel_participants(channel_id_, filter_, string(), offset_, limit_, -1, + random_id_, get_tries() < 3, std::move(promise)); } void do_send_result() override { @@ -5476,7 +5482,8 @@ void Td::on_request(uint64 id, const td_api::getChatMember &request) { void Td::on_request(uint64 id, td_api::searchChatMembers &request) { CLEAN_INPUT_STRING(request.query_); - CREATE_REQUEST(SearchChatMembersRequest, request.chat_id_, std::move(request.query_), request.limit_); + CREATE_REQUEST(SearchChatMembersRequest, request.chat_id_, std::move(request.query_), request.limit_, + get_dialog_participants_filter(request.filter_)); } void Td::on_request(uint64 id, td_api::getChatAdministrators &request) { diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index b40b399d7..9c3d76e13 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -923,6 +923,30 @@ class CliClient final : public Actor { return nullptr; } + static tl_object_ptr get_chat_members_filter(MutableSlice filter) { + filter = trim(filter); + to_lower_inplace(filter); + if (filter == "a" || filter == "admin" || filter == "administrators") { + return make_tl_object(); + } + if (filter == "b" || filter == "banned") { + return make_tl_object(); + } + if (filter == "bot" || filter == "bots") { + return make_tl_object(); + } + if (filter == "m" || filter == "members") { + return make_tl_object(); + } + if (filter == "r" || filter == "rest" || filter == "restricted") { + return make_tl_object(); + } + if (!filter.empty()) { + LOG(ERROR) << "Unsupported chat member filter " << filter; + } + return nullptr; + } + tl_object_ptr get_top_chat_category(MutableSlice category) { category = trim(category); to_lower_inplace(category); @@ -1969,10 +1993,13 @@ class CliClient final : public Actor { string chat_id; string limit; string query; + string filter; std::tie(chat_id, args) = split(args); - std::tie(limit, query) = split(args); - send_request(make_tl_object(as_chat_id(chat_id), query, to_integer(limit))); + std::tie(limit, args) = split(args); + std::tie(query, filter) = split(args); + send_request(make_tl_object(as_chat_id(chat_id), query, to_integer(limit), + get_chat_members_filter(filter))); } else if (op == "gcm") { string chat_id; string user_id;