/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/peers/add_participants_box.h" #include "api/api_chat_participants.h" #include "boxes/peers/edit_participant_box.h" #include "boxes/peers/edit_peer_type_box.h" #include "ui/boxes/confirm_box.h" #include "boxes/max_invite_box.h" #include "lang/lang_keys.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" #include "data/data_session.h" #include "data/data_folder.h" #include "data/data_changes.h" #include "history/history.h" #include "dialogs/dialogs_indexed_list.h" #include "ui/widgets/buttons.h" #include "ui/wrap/padding_wrap.h" #include "base/unixtime.h" #include "main/main_session.h" #include "mtproto/mtproto_config.h" #include "window/window_session_controller.h" #include "info/profile/info_profile_icon.h" #include "apiwrap.h" #include "styles/style_boxes.h" namespace { constexpr auto kParticipantsFirstPageCount = 16; constexpr auto kParticipantsPerPage = 200; base::flat_set> GetAlreadyInFromPeer(PeerData *peer) { if (!peer) { return {}; } if (const auto chat = peer->asChat()) { return chat->participants; } else if (const auto channel = peer->asChannel()) { if (channel->isMegagroup()) { const auto &participants = channel->mgInfo->lastParticipants; return { participants.cbegin(), participants.cend() }; } } return {}; } } // namespace AddParticipantsBoxController::AddParticipantsBoxController( not_null session) : ContactsBoxController(session) { } AddParticipantsBoxController::AddParticipantsBoxController( not_null peer) : AddParticipantsBoxController( peer, GetAlreadyInFromPeer(peer)) { } AddParticipantsBoxController::AddParticipantsBoxController( not_null peer, base::flat_set> &&alreadyIn) : ContactsBoxController(&peer->session()) , _peer(peer) , _alreadyIn(std::move(alreadyIn)) { if (needsInviteLinkButton()) { setStyleOverrides(&st::peerListWithInviteViaLink); } subscribeToMigration(); } void AddParticipantsBoxController::subscribeToMigration() { Expects(_peer != nullptr); SubscribeToMigration( _peer, lifetime(), [=](not_null channel) { _peer = channel; }); } void AddParticipantsBoxController::rowClicked(not_null row) { const auto &serverConfig = session().serverConfig(); auto count = fullCount(); auto limit = _peer && (_peer->isChat() || _peer->isMegagroup()) ? serverConfig.megagroupSizeMax : serverConfig.chatSizeMax; if (count < limit || row->checked()) { delegate()->peerListSetRowChecked(row, !row->checked()); updateTitle(); } else if (const auto channel = _peer ? _peer->asChannel() : nullptr) { if (!_peer->isMegagroup()) { showBox(Box(_peer->asChannel())); } } else if (count >= serverConfig.chatSizeMax && count < serverConfig.megagroupSizeMax) { showBox(Ui::MakeInformBox(tr::lng_profile_add_more_after_create())); } } void AddParticipantsBoxController::itemDeselectedHook( not_null peer) { updateTitle(); } void AddParticipantsBoxController::prepareViewHook() { updateTitle(); } int AddParticipantsBoxController::alreadyInCount() const { if (!_peer) { return 1; // self } if (const auto chat = _peer->asChat()) { return qMax(chat->count, 1); } else if (const auto channel = _peer->asChannel()) { return qMax(channel->membersCount(), int(_alreadyIn.size())); } Unexpected("User in AddParticipantsBoxController::alreadyInCount"); } bool AddParticipantsBoxController::isAlreadyIn( not_null user) const { if (!_peer) { return false; } if (const auto chat = _peer->asChat()) { return _alreadyIn.contains(user) || chat->participants.contains(user); } else if (const auto channel = _peer->asChannel()) { return _alreadyIn.contains(user) || (channel->isMegagroup() && base::contains(channel->mgInfo->lastParticipants, user)); } Unexpected("User in AddParticipantsBoxController::isAlreadyIn"); } int AddParticipantsBoxController::fullCount() const { return alreadyInCount() + delegate()->peerListSelectedRowsCount(); } std::unique_ptr AddParticipantsBoxController::createRow( not_null user) { if (user->isSelf()) { return nullptr; } auto result = std::make_unique(user); if (isAlreadyIn(user)) { result->setDisabledState(PeerListRow::State::DisabledChecked); } return result; } void AddParticipantsBoxController::updateTitle() { const auto additional = (_peer && _peer->isChannel() && !_peer->isMegagroup()) ? QString() : qsl("%1 / %2" ).arg(fullCount() ).arg(session().serverConfig().megagroupSizeMax); delegate()->peerListSetTitle(tr::lng_profile_add_participant()); delegate()->peerListSetAdditionalTitle(rpl::single(additional)); addInviteLinkButton(); } bool AddParticipantsBoxController::needsInviteLinkButton() { if (!_peer) { return false; } else if (const auto channel = _peer->asChannel()) { return channel->canHaveInviteLink(); } return _peer->asChat()->canHaveInviteLink(); } QPointer AddParticipantsBoxController::showBox( object_ptr box) const { const auto weak = Ui::MakeWeak(box.data()); delegate()->peerListShowBox(std::move(box), Ui::LayerOption::KeepOther); return weak; } void AddParticipantsBoxController::addInviteLinkButton() { if (!needsInviteLinkButton()) { return; } auto button = object_ptr>( nullptr, object_ptr( nullptr, tr::lng_profile_add_via_link(), st::inviteViaLinkButton), style::margins(0, st::membersMarginTop, 0, 0)); const auto icon = Ui::CreateChild( button->entity(), st::inviteViaLinkIcon, QPoint()); button->entity()->heightValue( ) | rpl::start_with_next([=](int height) { icon->moveToLeft( st::inviteViaLinkIconPosition.x(), (height - st::inviteViaLinkIcon.height()) / 2); }, icon->lifetime()); button->entity()->setClickedCallback([=] { showBox(Box(_peer)); }); button->entity()->events( ) | rpl::filter([=](not_null e) { return (e->type() == QEvent::Enter); }) | rpl::start_with_next([=] { delegate()->peerListMouseLeftGeometry(); }, button->lifetime()); delegate()->peerListSetAboveWidget(std::move(button)); } bool AddParticipantsBoxController::inviteSelectedUsers( not_null box) const { Expects(_peer != nullptr); const auto rows = box->collectSelectedRows(); const auto users = ranges::views::all( rows ) | ranges::views::transform([](not_null peer) { Expects(peer->isUser()); Expects(!peer->isSelf()); return not_null(peer->asUser()); }) | ranges::to_vector; if (users.empty()) { return false; } _peer->session().api().chatParticipants().add(_peer, users); return true; } void AddParticipantsBoxController::Start( not_null navigation, not_null chat) { auto controller = std::make_unique(chat); const auto weak = controller.get(); auto initBox = [=](not_null box) { box->addButton(tr::lng_participant_invite(), [=] { if (weak->inviteSelectedUsers(box)) { navigation->parentController()->showPeerHistory( chat, Window::SectionShow::Way::ClearStack, ShowAtTheEndMsgId); } }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); }; Window::Show(navigation).showBox( Box(std::move(controller), std::move(initBox)), Ui::LayerOption::KeepOther); } void AddParticipantsBoxController::Start( not_null navigation, not_null channel, base::flat_set> &&alreadyIn, bool justCreated) { auto controller = std::make_unique( channel, std::move(alreadyIn)); const auto weak = controller.get(); auto initBox = [=](not_null box) { box->addButton(tr::lng_participant_invite(), [=] { if (weak->inviteSelectedUsers(box)) { if (channel->isMegagroup()) { navigation->parentController()->showPeerHistory( channel, Window::SectionShow::Way::ClearStack, ShowAtTheEndMsgId); } else { box->closeBox(); } } }); box->addButton( justCreated ? tr::lng_create_group_skip() : tr::lng_cancel(), [=] { box->closeBox(); }); if (justCreated) { box->boxClosing() | rpl::start_with_next([=] { auto params = Window::SectionShow(); params.activation = anim::activation::background; navigation->parentController()->showPeerHistory( channel, params, ShowAtTheEndMsgId); }, box->lifetime()); } }; Window::Show(navigation).showBox( Box(std::move(controller), std::move(initBox)), Ui::LayerOption::KeepOther); } void AddParticipantsBoxController::Start( not_null navigation, not_null channel, base::flat_set> &&alreadyIn) { Start(navigation, channel, std::move(alreadyIn), false); } void AddParticipantsBoxController::Start( not_null navigation, not_null channel) { Start(navigation, channel, {}, true); } AddSpecialBoxController::AddSpecialBoxController( not_null peer, Role role, AdminDoneCallback adminDoneCallback, BannedDoneCallback bannedDoneCallback) : PeerListController(std::make_unique( peer, &_additional)) , _peer(peer) , _api(&_peer->session().mtp()) , _role(role) , _additional(peer, Role::Members) , _adminDoneCallback(std::move(adminDoneCallback)) , _bannedDoneCallback(std::move(bannedDoneCallback)) { subscribeToMigration(); } Main::Session &AddSpecialBoxController::session() const { return _peer->session(); } void AddSpecialBoxController::subscribeToMigration() { const auto chat = _peer->asChat(); if (!chat) { return; } SubscribeToMigration( chat, lifetime(), [=](not_null channel) { migrate(chat, channel); }); } void AddSpecialBoxController::migrate( not_null chat, not_null channel) { _peer = channel; _additional.migrate(chat, channel); } QPointer AddSpecialBoxController::showBox( object_ptr box) const { const auto weak = Ui::MakeWeak(box.data()); delegate()->peerListShowBox(std::move(box), Ui::LayerOption::KeepOther); return weak; } std::unique_ptr AddSpecialBoxController::createSearchRow( not_null peer) { if (_excludeSelf && peer->isSelf()) { return nullptr; } if (const auto user = peer->asUser()) { return createRow(user); } return nullptr; } void AddSpecialBoxController::prepare() { delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); auto title = [&] { switch (_role) { case Role::Members: return tr::lng_profile_participants_section(); case Role::Admins: return tr::lng_channel_add_admin(); case Role::Restricted: return tr::lng_channel_add_exception(); case Role::Kicked: return tr::lng_channel_add_removed(); } Unexpected("Role in AddSpecialBoxController::prepare()"); }(); delegate()->peerListSetTitle(std::move(title)); setDescriptionText(tr::lng_contacts_loading(tr::now)); setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now)); if (const auto chat = _peer->asChat()) { prepareChatRows(chat); } else { loadMoreRows(); } delegate()->peerListRefreshRows(); } void AddSpecialBoxController::prepareChatRows(not_null chat) { _onlineSorter = std::make_unique( chat, delegate()); rebuildChatRows(chat); if (!delegate()->peerListFullRowsCount()) { chat->updateFullForced(); } using UpdateFlag = Data::PeerUpdate::Flag; chat->session().changes().peerUpdates( chat, UpdateFlag::Members | UpdateFlag::Admins ) | rpl::start_with_next([=](const Data::PeerUpdate &update) { _additional.fillFromPeer(); if (update.flags & UpdateFlag::Members) { rebuildChatRows(chat); } }, lifetime()); } void AddSpecialBoxController::rebuildChatRows(not_null chat) { if (chat->participants.empty()) { // We get such updates often // (when participants list was invalidated). //while (delegate()->peerListFullRowsCount() > 0) { // delegate()->peerListRemoveRow( // delegate()->peerListRowAt(0)); //} return; } auto &participants = chat->participants; auto count = delegate()->peerListFullRowsCount(); for (auto i = 0; i != count;) { auto row = delegate()->peerListRowAt(i); Assert(row->peer()->isUser()); auto user = row->peer()->asUser(); if (participants.contains(user)) { ++i; } else { delegate()->peerListRemoveRow(row); --count; } } for (const auto &user : participants) { if (auto row = createRow(user)) { delegate()->peerListAppendRow(std::move(row)); } } _onlineSorter->sort(); delegate()->peerListRefreshRows(); setDescriptionText(QString()); } void AddSpecialBoxController::loadMoreRows() { if (searchController() && searchController()->loadMoreRows()) { return; } else if (!_peer->isChannel() || _loadRequestId || _allLoaded) { return; } // First query is small and fast, next loads a lot of rows. const auto perPage = (_offset > 0) ? kParticipantsPerPage : kParticipantsFirstPageCount; const auto participantsHash = uint64(0); const auto channel = _peer->asChannel(); _loadRequestId = _api.request(MTPchannels_GetParticipants( channel->inputChannel, MTP_channelParticipantsRecent(), MTP_int(_offset), MTP_int(perPage), MTP_long(participantsHash) )).done([=](const MTPchannels_ChannelParticipants &result) { _loadRequestId = 0; result.match([&](const MTPDchannels_channelParticipants &data) { const auto &[availableCount, list] = Api::ChatParticipants::Parse( channel, data); for (const auto &data : list) { if (const auto participant = _additional.applyParticipant( data)) { appendRow(participant); } } if (const auto size = list.size()) { _offset += size; } else { // To be sure - wait for a whole empty result list. _allLoaded = true; } }, [&](const MTPDchannels_channelParticipantsNotModified &) { LOG(("API Error: channels.channelParticipantsNotModified received!")); }); if (delegate()->peerListFullRowsCount() > 0) { setDescriptionText(QString()); } else if (_allLoaded) { setDescriptionText(tr::lng_blocked_list_not_found(tr::now)); } delegate()->peerListRefreshRows(); }).fail([this] { _loadRequestId = 0; }).send(); } void AddSpecialBoxController::rowClicked(not_null row) { const auto participant = row->peer(); const auto user = participant->asUser(); switch (_role) { case Role::Admins: Assert(user != nullptr); return showAdmin(user); case Role::Restricted: Assert(user != nullptr); return showRestricted(user); case Role::Kicked: return kickUser(participant); } Unexpected("Role in AddSpecialBoxController::rowClicked()"); } template bool AddSpecialBoxController::checkInfoLoaded( not_null participant, Callback callback) { if (_additional.infoLoaded(participant)) { return true; } // We don't know what this user status is in the group. const auto channel = _peer->asChannel(); _api.request(MTPchannels_GetParticipant( channel->inputChannel, participant->input )).done([=](const MTPchannels_ChannelParticipant &result) { result.match([&](const MTPDchannels_channelParticipant &data) { channel->owner().processUsers(data.vusers()); _additional.applyParticipant( Api::ChatParticipant(data.vparticipant(), channel)); }); callback(); }).fail([=] { _additional.setExternal(participant); callback(); }).send(); return false; } void AddSpecialBoxController::showAdmin( not_null user, bool sure) { if (!checkInfoLoaded(user, [=] { showAdmin(user); })) { return; } _editBox = nullptr; if (_editParticipantBox) { _editParticipantBox->closeBox(); } const auto chat = _peer->asChat(); const auto channel = _peer->asChannel(); const auto showAdminSure = crl::guard(this, [=] { showAdmin(user, true); }); // Check restrictions. const auto canAddMembers = chat ? chat->canAddMembers() : channel->canAddMembers(); const auto canBanMembers = chat ? chat->canBanMembers() : channel->canBanMembers(); const auto adminRights = _additional.adminRights(user); if (adminRights.has_value()) { // The user is already an admin. } else if (_additional.isKicked(user)) { // The user is banned. if (canAddMembers) { if (canBanMembers) { if (!sure) { _editBox = showBox( Ui::MakeConfirmBox({ tr::lng_sure_add_admin_unremove(), showAdminSure })); return; } } else { showBox( Ui::MakeInformBox(tr::lng_error_cant_add_admin_unban())); return; } } else { showBox(Ui::MakeInformBox(tr::lng_error_cant_add_admin_invite())); return; } } else if (_additional.restrictedRights(user).has_value()) { // The user is restricted. if (canBanMembers) { if (!sure) { _editBox = showBox( Ui::MakeConfirmBox({ tr::lng_sure_add_admin_unremove(), showAdminSure })); return; } } else { showBox(Ui::MakeInformBox(tr::lng_error_cant_add_admin_unban())); return; } } else if (_additional.isExternal(user)) { // The user is not in the group yet. if (canAddMembers) { if (!sure) { auto text = ((_peer->isChat() || _peer->isMegagroup()) ? tr::lng_sure_add_admin_invite : tr::lng_sure_add_admin_invite_channel)(); _editBox = showBox( Ui::MakeConfirmBox({ std::move(text), showAdminSure })); return; } } else { showBox(Ui::MakeInformBox(tr::lng_error_cant_add_admin_invite())); return; } } // Finally show the admin. const auto currentRights = adminRights ? *adminRights : ChatAdminRightsInfo(); auto box = Box( _peer, user, currentRights, _additional.adminRank(user)); if (_additional.canAddOrEditAdmin(user)) { const auto done = crl::guard(this, [=]( ChatAdminRightsInfo newRights, const QString &rank) { editAdminDone(user, newRights, rank); }); const auto fail = crl::guard(this, [=] { if (_editParticipantBox) { _editParticipantBox->closeBox(); } }); box->setSaveCallback(SaveAdminCallback(_peer, user, done, fail)); } _editParticipantBox = showBox(std::move(box)); } void AddSpecialBoxController::editAdminDone( not_null user, ChatAdminRightsInfo rights, const QString &rank) { if (_editParticipantBox) { _editParticipantBox->closeBox(); } _additional.applyAdminLocally(user, rights, rank); if (const auto callback = _adminDoneCallback) { callback(user, rights, rank); } } void AddSpecialBoxController::showRestricted( not_null user, bool sure) { if (!checkInfoLoaded(user, [=] { showRestricted(user); })) { return; } _editBox = nullptr; if (_editParticipantBox) { _editParticipantBox->closeBox(); } const auto showRestrictedSure = crl::guard(this, [=] { showRestricted(user, true); }); // Check restrictions. const auto restrictedRights = _additional.restrictedRights(user); if (restrictedRights.has_value()) { // The user is already banned or restricted. } else if (_additional.adminRights(user).has_value() || _additional.isCreator(user)) { // The user is an admin or creator. if (!_additional.isCreator(user) && _additional.canEditAdmin(user)) { if (!sure) { _editBox = showBox( Ui::MakeConfirmBox({ tr::lng_sure_ban_admin(), showRestrictedSure })); return; } } else { showBox(Ui::MakeInformBox(tr::lng_error_cant_ban_admin())); return; } } // Finally edit the restricted. const auto currentRights = restrictedRights ? *restrictedRights : ChatRestrictionsInfo(); auto box = Box( _peer, user, _additional.adminRights(user).has_value(), currentRights); if (_additional.canRestrictParticipant(user)) { const auto done = crl::guard(this, [=]( ChatRestrictionsInfo newRights) { editRestrictedDone(user, newRights); }); const auto fail = crl::guard(this, [=] { if (_editParticipantBox) { _editParticipantBox->closeBox(); } }); box->setSaveCallback( SaveRestrictedCallback(_peer, user, done, fail)); } _editParticipantBox = showBox(std::move(box)); } void AddSpecialBoxController::editRestrictedDone( not_null participant, ChatRestrictionsInfo rights) { if (_editParticipantBox) { _editParticipantBox->closeBox(); } _additional.applyBannedLocally(participant, rights); if (const auto callback = _bannedDoneCallback) { callback(participant, rights); } } void AddSpecialBoxController::kickUser( not_null participant, bool sure) { if (!checkInfoLoaded(participant, [=] { kickUser(participant); })) { return; } const auto kickUserSure = crl::guard(this, [=] { kickUser(participant, true); }); // Check restrictions. const auto user = participant->asUser(); if (user && (_additional.adminRights(user).has_value() || (_additional.isCreator(user)))) { // The user is an admin or creator. if (!_additional.isCreator(user) && _additional.canEditAdmin(user)) { if (!sure) { _editBox = showBox( Ui::MakeConfirmBox({ tr::lng_sure_ban_admin(), kickUserSure })); return; } } else { showBox(Ui::MakeInformBox(tr::lng_error_cant_ban_admin())); return; } } // Finally kick him. if (!sure) { const auto text = ((_peer->isChat() || _peer->isMegagroup()) ? tr::lng_profile_sure_kick : tr::lng_profile_sure_kick_channel)( tr::now, lt_user, participant->name); _editBox = showBox(Ui::MakeConfirmBox({ text, kickUserSure })); return; } const auto restrictedRights = _additional.restrictedRights(participant); const auto currentRights = restrictedRights ? *restrictedRights : ChatRestrictionsInfo(); const auto done = crl::guard(this, [=]( ChatRestrictionsInfo newRights) { editRestrictedDone(participant, newRights); }); const auto fail = crl::guard(this, [=] { _editBox = nullptr; }); const auto callback = SaveRestrictedCallback( _peer, participant, done, fail); callback(currentRights, ChannelData::KickedRestrictedRights(participant)); } bool AddSpecialBoxController::appendRow(not_null participant) { if (delegate()->peerListFindRow(participant->id.value) || (_excludeSelf && participant->isSelf())) { return false; } delegate()->peerListAppendRow(createRow(participant)); return true; } bool AddSpecialBoxController::prependRow(not_null user) { if (delegate()->peerListFindRow(user->id.value)) { return false; } delegate()->peerListPrependRow(createRow(user)); return true; } std::unique_ptr AddSpecialBoxController::createRow( not_null participant) const { return std::make_unique(participant); } AddSpecialBoxSearchController::AddSpecialBoxSearchController( not_null peer, not_null additional) : _peer(peer) , _additional(additional) , _api(&_peer->session().mtp()) , _timer([=] { searchOnServer(); }) { subscribeToMigration(); } void AddSpecialBoxSearchController::subscribeToMigration() { SubscribeToMigration( _peer, lifetime(), [=](not_null channel) { _peer = channel; }); } void AddSpecialBoxSearchController::searchQuery(const QString &query) { if (_query != query) { _query = query; _offset = 0; _requestId = 0; _participantsLoaded = false; _chatsContactsAdded = false; _chatMembersAdded = false; _globalLoaded = false; if (!_query.isEmpty() && !searchParticipantsInCache()) { _timer.callOnce(AutoSearchTimeout); } else { _timer.cancel(); } } } void AddSpecialBoxSearchController::searchOnServer() { Expects(!_query.isEmpty()); loadMoreRows(); } bool AddSpecialBoxSearchController::isLoading() { return _timer.isActive() || _requestId; } bool AddSpecialBoxSearchController::searchParticipantsInCache() { const auto i = _participantsCache.find(_query); if (i != _participantsCache.cend()) { _requestId = 0; searchParticipantsDone( _requestId, i->second.result, i->second.requestedCount); return true; } return false; } bool AddSpecialBoxSearchController::searchGlobalInCache() { auto it = _globalCache.find(_query); if (it != _globalCache.cend()) { _requestId = 0; searchGlobalDone(_requestId, it->second); return true; } return false; } bool AddSpecialBoxSearchController::loadMoreRows() { if (_query.isEmpty()) { return false; } if (_globalLoaded) { return true; } if (_participantsLoaded || _chatMembersAdded) { if (!_chatsContactsAdded) { addChatsContacts(); } if (!isLoading() && !searchGlobalInCache()) { requestGlobal(); } } else if (const auto chat = _peer->asChat()) { if (!_chatMembersAdded) { addChatMembers(chat); } } else if (!isLoading()) { requestParticipants(); } return true; } void AddSpecialBoxSearchController::requestParticipants() { Expects(_peer->isChannel()); // For search we request a lot of rows from the first query. // (because we've waited for search request by timer already, // so we don't expect it to be fast, but we want to fill cache). const auto perPage = kParticipantsPerPage; const auto participantsHash = uint64(0); const auto channel = _peer->asChannel(); _requestId = _api.request(MTPchannels_GetParticipants( channel->inputChannel, MTP_channelParticipantsSearch(MTP_string(_query)), MTP_int(_offset), MTP_int(perPage), MTP_long(participantsHash) )).done([=]( const MTPchannels_ChannelParticipants &result, mtpRequestId requestId) { searchParticipantsDone(requestId, result, perPage); }).fail([=](const MTP::Error &error, mtpRequestId requestId) { if (_requestId == requestId) { _requestId = 0; _participantsLoaded = true; loadMoreRows(); delegate()->peerListSearchRefreshRows(); } }).send(); auto entry = Query(); entry.text = _query; entry.offset = _offset; _participantsQueries.emplace(_requestId, entry); } void AddSpecialBoxSearchController::searchParticipantsDone( mtpRequestId requestId, const MTPchannels_ChannelParticipants &result, int requestedCount) { Expects(_peer->isChannel()); const auto channel = _peer->asChannel(); auto query = _query; if (requestId) { const auto addToCache = [&] { auto it = _participantsQueries.find(requestId); if (it != _participantsQueries.cend()) { query = it->second.text; if (it->second.offset == 0) { auto &entry = _participantsCache[query]; entry.result = result; entry.requestedCount = requestedCount; } _participantsQueries.erase(it); } }; result.match([&](const MTPDchannels_channelParticipants &data) { Api::ChatParticipants::Parse(channel, data); addToCache(); }, [&](const MTPDchannels_channelParticipantsNotModified &) { LOG(("API Error: " "channels.channelParticipantsNotModified received!")); }); } if (_requestId != requestId) { return; } _requestId = 0; result.match([&](const MTPDchannels_channelParticipants &data) { const auto &list = data.vparticipants().v; if (list.size() < requestedCount) { // We want cache to have full information about a query with // small results count (that we don't need the second request). // So we don't wait for empty list unlike the non-search case. _participantsLoaded = true; if (list.empty() && _offset == 0) { // No results, request global search immediately. loadMoreRows(); } } for (const auto &data : list) { if (const auto user = _additional->applyParticipant( Api::ChatParticipant(data, channel))) { delegate()->peerListSearchAddRow(user); } } _offset += list.size(); }, [&](const MTPDchannels_channelParticipantsNotModified &) { _participantsLoaded = true; }); delegate()->peerListSearchRefreshRows(); } void AddSpecialBoxSearchController::requestGlobal() { if (_query.isEmpty()) { _globalLoaded = true; return; } auto perPage = SearchPeopleLimit; _requestId = _api.request(MTPcontacts_Search( MTP_string(_query), MTP_int(perPage) )).done([=](const MTPcontacts_Found &result, mtpRequestId requestId) { searchGlobalDone(requestId, result); }).fail([=](const MTP::Error &error, mtpRequestId requestId) { if (_requestId == requestId) { _requestId = 0; _globalLoaded = true; delegate()->peerListSearchRefreshRows(); } }).send(); _globalQueries.emplace(_requestId, _query); } void AddSpecialBoxSearchController::searchGlobalDone( mtpRequestId requestId, const MTPcontacts_Found &result) { Expects(result.type() == mtpc_contacts_found); auto &found = result.c_contacts_found(); auto query = _query; if (requestId) { _peer->owner().processUsers(found.vusers()); _peer->owner().processChats(found.vchats()); auto it = _globalQueries.find(requestId); if (it != _globalQueries.cend()) { query = it->second; _globalCache[query] = result; _globalQueries.erase(it); } } const auto feedList = [&](const MTPVector &list) { for (const auto &mtpPeer : list.v) { const auto peerId = peerFromMTP(mtpPeer); if (const auto peer = _peer->owner().peerLoaded(peerId)) { if (const auto user = peer->asUser()) { _additional->checkForLoaded(user); delegate()->peerListSearchAddRow(user); } } } }; if (_requestId == requestId) { _requestId = 0; _globalLoaded = true; feedList(found.vmy_results()); feedList(found.vresults()); delegate()->peerListSearchRefreshRows(); } } void AddSpecialBoxSearchController::addChatMembers( not_null chat) { if (chat->participants.empty()) { return; } _chatMembersAdded = true; const auto wordList = TextUtilities::PrepareSearchWords(_query); if (wordList.empty()) { return; } const auto allWordsAreFound = [&]( const base::flat_set &nameWords) { const auto hasNamePartStartingWith = [&](const QString &word) { for (const auto &nameWord : nameWords) { if (nameWord.startsWith(word)) { return true; } } return false; }; for (const auto &word : wordList) { if (!hasNamePartStartingWith(word)) { return false; } } return true; }; for (const auto &user : chat->participants) { if (allWordsAreFound(user->nameWords())) { delegate()->peerListSearchAddRow(user); } } delegate()->peerListSearchRefreshRows(); } void AddSpecialBoxSearchController::addChatsContacts() { _chatsContactsAdded = true; const auto wordList = TextUtilities::PrepareSearchWords(_query); if (wordList.empty()) { return; } const auto allWordsAreFound = [&]( const base::flat_set &nameWords) { const auto hasNamePartStartingWith = [&](const QString &word) { for (const auto &nameWord : nameWords) { if (nameWord.startsWith(word)) { return true; } } return false; }; for (const auto &word : wordList) { if (!hasNamePartStartingWith(word)) { return false; } } return true; }; const auto getSmallestIndex = [&](not_null list) -> const Dialogs::List* { if (list->empty()) { return nullptr; } auto result = (const Dialogs::List*)nullptr; for (const auto &word : wordList) { const auto found = list->filtered(word[0]); if (!found || found->empty()) { return nullptr; } if (!result || result->size() > found->size()) { result = found; } } return result; }; const auto filterAndAppend = [&](not_null list) { const auto index = getSmallestIndex(list); if (!index) { return; } for (const auto &row : *index) { if (const auto history = row->history()) { if (const auto user = history->peer->asUser()) { if (allWordsAreFound(user->nameWords())) { delegate()->peerListSearchAddRow(user); } } } } }; filterAndAppend(_peer->owner().chatsList()->indexed()); const auto id = Data::Folder::kId; if (const auto folder = _peer->owner().folderLoaded(id)) { filterAndAppend(folder->chatsList()->indexed()); } filterAndAppend(_peer->owner().contactsNoChatsList()); delegate()->peerListSearchRefreshRows(); }