diff --git a/Telegram/SourceFiles/api/api_peer_search.cpp b/Telegram/SourceFiles/api/api_peer_search.cpp new file mode 100644 index 0000000000..4d7db9ccc8 --- /dev/null +++ b/Telegram/SourceFiles/api/api_peer_search.cpp @@ -0,0 +1,169 @@ +/* +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 "api/api_peer_search.h" + +#include "api/api_single_message_search.h" +#include "apiwrap.h" +#include "data/data_session.h" +#include "dialogs/ui/chat_search_in.h" // IsHashOrCashtagSearchQuery +#include "main/main_session.h" + +namespace Api { +namespace { + +constexpr auto kMinSponsoredQueryLength = 4; + +} // namespace + +PeerSearch::PeerSearch(not_null session, Type type) +: _session(session) +, _type(type) { +} + +PeerSearch::~PeerSearch() { + clear(); +} + +void PeerSearch::request( + const QString &query, + Fn callback, + RequestType type) { + using namespace Dialogs; + _query = Api::ConvertPeerSearchQuery(query); + _callback = callback; + if (_query.isEmpty() + || IsHashOrCashtagSearchQuery(_query) != HashOrCashtag::None) { + finish(PeerSearchResult{}); + return; + } + auto &cache = _cache[_query]; + if (cache.peersReady && cache.sponsoredReady) { + finish(cache.result); + return; + } else if (type == RequestType::CacheOnly) { + _callback = nullptr; + return; + } else if (cache.requested) { + return; + } + cache.requested = true; + cache.result.query = _query; + if (_query.size() < kMinSponsoredQueryLength) { + cache.sponsoredReady = true; + } else if (_type == Type::WithSponsored) { + requestSponsored(); + } + requestPeers(); +} + +void PeerSearch::requestPeers() { + const auto requestId = _session->api().request(MTPcontacts_Search( + MTP_string(_query), + MTP_int(SearchPeopleLimit) + )).done([=](const MTPcontacts_Found &result, mtpRequestId requestId) { + const auto &data = result.data(); + _session->data().processUsers(data.vusers()); + _session->data().processChats(data.vchats()); + auto parsed = PeerSearchResult(); + parsed.my.reserve(data.vmy_results().v.size()); + for (const auto &id : data.vmy_results().v) { + const auto peerId = peerFromMTP(id); + parsed.my.push_back(_session->data().peer(peerId)); + } + parsed.peers.reserve(data.vresults().v.size()); + for (const auto &id : data.vresults().v) { + const auto peerId = peerFromMTP(id); + parsed.peers.push_back(_session->data().peer(peerId)); + } + finishPeers(requestId, std::move(parsed)); + }).fail([=](const MTP::Error &error, mtpRequestId requestId) { + finishPeers(requestId, PeerSearchResult{}); + }).send(); + _peerRequests.emplace(requestId, _query); +} + +void PeerSearch::requestSponsored() { + const auto requestId = _session->api().request( + MTPcontacts_GetSponsoredPeers(MTP_string(_query)) + ).done([=]( + const MTPcontacts_SponsoredPeers &result, + mtpRequestId requestId) { + result.match([&](const MTPDcontacts_sponsoredPeersEmpty &) { + finishSponsored(requestId, PeerSearchResult{}); + }, [&](const MTPDcontacts_sponsoredPeers &data) { + _session->data().processUsers(data.vusers()); + _session->data().processChats(data.vchats()); + auto parsed = PeerSearchResult(); + parsed.sponsored.reserve(data.vpeers().v.size()); + for (const auto &peer : data.vpeers().v) { + const auto &data = peer.data(); + const auto peerId = peerFromMTP(data.vpeer()); + parsed.sponsored.push_back({ + .peer = _session->data().peer(peerId), + .randomId = data.vrandom_id().v, + .sponsorInfo = qs(data.vsponsor_info().value_or_empty()), + .additionalInfo = qs( + data.vadditional_info().value_or_empty()), + }); + } + finishSponsored(requestId, std::move(parsed)); + }); + }).fail([=](const MTP::Error &error, mtpRequestId requestId) { + finishSponsored(requestId, PeerSearchResult{}); + }).send(); + _sponsoredRequests.emplace(requestId, _query); +} + +void PeerSearch::finishPeers( + mtpRequestId requestId, + PeerSearchResult result) { + const auto query = _peerRequests.take(requestId); + Assert(query.has_value()); + + auto &cache = _cache[*query]; + cache.peersReady = true; + cache.result.my = std::move(result.my); + cache.result.peers = std::move(result.peers); + if (cache.sponsoredReady && _query == *query) { + finish(cache.result); + } +} + +void PeerSearch::finishSponsored( + mtpRequestId requestId, + PeerSearchResult result) { + const auto query = _sponsoredRequests.take(requestId); + Assert(query.has_value()); + + auto &cache = _cache[*query]; + cache.sponsoredReady = true; + cache.result.sponsored = std::move(result.sponsored); + if (cache.peersReady && _query == *query) { + finish(cache.result); + } +} + +void PeerSearch::finish(PeerSearchResult result) { + if (const auto onstack = base::take(_callback)) { + onstack(std::move(result)); + } +} + +void PeerSearch::clear() { + _query = QString(); + _callback = nullptr; + _cache.clear(); + for (const auto &[requestId, query] : base::take(_peerRequests)) { + _session->api().request(requestId).cancel(); + } + for (const auto &[requestId, query] : base::take(_sponsoredRequests)) { + _session->api().request(requestId).cancel(); + } +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_peer_search.h b/Telegram/SourceFiles/api/api_peer_search.h new file mode 100644 index 0000000000..e8f9f0253c --- /dev/null +++ b/Telegram/SourceFiles/api/api_peer_search.h @@ -0,0 +1,76 @@ +/* +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 +*/ +#pragma once + +namespace Main { +class Session; +} // namespace Main + +namespace Api { + +struct SponsoredSearchResult { + not_null peer; + QByteArray randomId; + QString sponsorInfo; + QString additionalInfo; +}; + +struct PeerSearchResult { + QString query; + std::vector> my; + std::vector> peers; + std::vector sponsored; +}; + +class PeerSearch final { +public: + enum class Type { + WithSponsored, + JustPeers, + }; + PeerSearch(not_null session, Type type); + ~PeerSearch(); + + enum class RequestType { + CacheOnly, + CacheOrRemote, + }; + void request( + const QString &query, + Fn callback, + RequestType type = RequestType::CacheOrRemote); + void clear(); + +private: + struct CacheEntry { + PeerSearchResult result; + bool requested = false; + bool peersReady = false; + bool sponsoredReady = false; + }; + + void requestPeers(); + void requestSponsored(); + + void finish(PeerSearchResult result); + void finishPeers(mtpRequestId requestId, PeerSearchResult result); + void finishSponsored(mtpRequestId requestId, PeerSearchResult result); + + const not_null _session; + const Type _type; + + QString _query; + Fn _callback; + + base::flat_map _cache; + base::flat_map _peerRequests; + base::flat_map _sponsoredRequests; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index e2aa2c5a43..5b2a201379 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -243,7 +243,9 @@ struct InnerWidget::HashtagResult { struct InnerWidget::PeerSearchResult { explicit PeerSearchResult(not_null peer) : peer(peer) { } + not_null peer; + std::unique_ptr sponsored; mutable Ui::Text::String name; mutable Ui::PeerBadge badge; BasicRow row; @@ -3671,40 +3673,36 @@ void InnerWidget::searchReceived( refresh(); } -void InnerWidget::peerSearchReceived( - const QString &query, - const QVector &my, - const QVector &result) { +void InnerWidget::peerSearchReceived(Api::PeerSearchResult result) { if (_state != WidgetState::Filtered) { return; } - _peerSearchQuery = query.toLower().trimmed(); + _peerSearchQuery = result.query.toLower().trimmed(); _peerSearchResults.clear(); - _peerSearchResults.reserve(result.size()); - for (const auto &mtpPeer : my) { - if (const auto peer = session().data().peerLoaded(peerFromMTP(mtpPeer))) { - appendToFiltered(peer->owner().history(peer)); - } else { - LOG(("API Error: " - "user %1 was not loaded in InnerWidget::peopleReceived()" - ).arg(peerFromMTP(mtpPeer).value)); - } + _peerSearchResults.reserve(result.peers.size() + + result.sponsored.size()); + for (const auto &peer : result.my) { + appendToFiltered(peer->owner().history(peer)); } - for (const auto &mtpPeer : result) { - if (const auto peer = session().data().peerLoaded(peerFromMTP(mtpPeer))) { - if (const auto history = peer->owner().historyLoaded(peer)) { - if (history->inChatList()) { - continue; // skip existing chats - } + auto added = base::flat_set>(); + for (const auto &sponsored : result.sponsored) { + _peerSearchResults.push_back( + std::make_unique(sponsored.peer)); + _peerSearchResults.back()->sponsored + = std::make_unique(sponsored); + added.emplace(sponsored.peer); + } + for (const auto &peer : result.peers) { + if (added.contains(peer)) { + continue; + } else if (const auto history = peer->owner().historyLoaded(peer)) { + if (history->inChatList()) { + continue; // skip existing chats } - _peerSearchResults.push_back(std::make_unique( - peer)); - } else { - LOG(("API Error: " - "user %1 was not loaded in InnerWidget::peopleReceived()" - ).arg(peerFromMTP(mtpPeer).value)); } + _peerSearchResults.push_back( + std::make_unique(peer)); } refresh(); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 44d1f8d497..0c602845f9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -22,6 +22,10 @@ namespace style { struct DialogRow; } // namespace style +namespace Api { +struct PeerSearchResult; +} // namespace Api + namespace MTP { class Error; } // namespace MTP @@ -126,10 +130,7 @@ public: HistoryItem *inject, SearchRequestType type, int fullCount); - void peerSearchReceived( - const QString &query, - const QVector &my, - const QVector &result); + void peerSearchReceived(Api::PeerSearchResult result); [[nodiscard]] FilterId filterId() const; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 5886a8318c..acd99764db 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -365,6 +365,7 @@ Widget::Widget( _storiesContents.events() | rpl::flatten_latest()) : nullptr) , _searchTimer([=] { search(); }) +, _peerSearch(&controller->session(), Api::PeerSearch::Type::WithSponsored) , _singleMessageSearch(&controller->session()) { const auto makeChildListShown = [](PeerId peerId, float64 shown) { return InnerWidget::ChildListShown{ peerId, shown }; @@ -1730,7 +1731,7 @@ void Widget::changeOpenedSubsection( change(); refreshTopBars(); updateControlsVisibility(true); - _peerSearchRequest = 0; + _peerSearch.clear(); _api.request(base::take(_topicSearchRequest)).cancel(); if (animated == anim::type::normal) { if (_connecting) { @@ -2434,10 +2435,9 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) { { .posts = true, .start = true }, &_postsProcess); } - _api.request(base::take(_peerSearchRequest)).cancel(); - _peerSearchQuery = QString(); - peerSearchApplyEmpty(0); + _peerSearch.clear(); _api.request(base::take(_topicSearchRequest)).cancel(); + peerSearchReceived({}); return true; } else if (inCache) { const auto success = _singleMessageSearch.lookup(query, [=] { @@ -2542,35 +2542,18 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) { } else { _inner->searchRequested(false); } - const auto peerQuery = Api::ConvertPeerSearchQuery(query); - if (searchForPeersRequired(peerQuery)) { - if (inCache) { - auto i = _peerSearchCache.find(peerQuery); - if (i != _peerSearchCache.end()) { - _peerSearchQuery = peerQuery; - _peerSearchRequest = 0; - peerSearchReceived(i->second, 0); - } - } else if (_peerSearchQuery != peerQuery) { - _peerSearchQuery = peerQuery; - _peerSearchFull = false; - _peerSearchRequest = _api.request(MTPcontacts_Search( - MTP_string(_peerSearchQuery), - MTP_int(SearchPeopleLimit) - )).done([=]( - const MTPcontacts_Found &result, - mtpRequestId requestId) { - peerSearchReceived(result, requestId); - }).fail([=](const MTP::Error &error, mtpRequestId requestId) { - peerSearchFailed(error, requestId); - }).send(); - _peerSearchQueries.emplace(_peerSearchRequest, _peerSearchQuery); - } + if (peerSearchRequired()) { + const auto requestType = inCache + ? Api::PeerSearch::RequestType::CacheOnly + : Api::PeerSearch::RequestType::CacheOrRemote; + _peerSearch.request(query, [=](Api::PeerSearchResult result) { + peerSearchReceived(result); + }, requestType); } else { - _api.request(base::take(_peerSearchRequest)).cancel(); - _peerSearchQuery = peerQuery; - peerSearchApplyEmpty(0); + _peerSearch.clear(); + peerSearchReceived({}); } + const auto peerQuery = Api::ConvertPeerSearchQuery(query); if (searchForTopicsRequired(peerQuery)) { if (inCache) { if (_topicSearchQuery != peerQuery) { @@ -2589,11 +2572,8 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) { return result; } -bool Widget::searchForPeersRequired(const QString &query) const { - return _searchState.filterChatsList() - && !_openedForum - && !query.isEmpty() - && (IsHashOrCashtagSearchQuery(query) == HashOrCashtag::None); +bool Widget::peerSearchRequired() const { + return _searchState.filterChatsList() && !_openedForum; } bool Widget::searchForTopicsRequired(const QString &query) const { @@ -2993,31 +2973,10 @@ void Widget::searchReceived( update(); } -void Widget::peerSearchReceived( - const MTPcontacts_Found &result, - mtpRequestId requestId) { - const auto state = _inner->state(); - auto q = _peerSearchQuery; - if (state == WidgetState::Filtered) { - auto i = _peerSearchQueries.find(requestId); - if (i != _peerSearchQueries.end()) { - _peerSearchCache[i->second] = result; - _peerSearchQueries.erase(i); - } - } - if (_peerSearchRequest == requestId) { - switch (result.type()) { - case mtpc_contacts_found: { - auto &d = result.c_contacts_found(); - session().data().processUsers(d.vusers()); - session().data().processChats(d.vchats()); - _inner->peerSearchReceived(q, d.vmy_results().v, d.vresults().v); - } break; - } - - _peerSearchRequest = 0; - listScrollUpdated(); - } +void Widget::peerSearchReceived(Api::PeerSearchResult result) { + _inner->peerSearchReceived(std::move(result)); + listScrollUpdated(); + update(); } void Widget::searchApplyEmpty( @@ -3033,17 +2992,6 @@ void Widget::searchApplyEmpty( process); } -void Widget::peerSearchApplyEmpty(mtpRequestId id) { - _peerSearchFull = true; - peerSearchReceived( - MTP_contacts_found( - MTP_vector(0), - MTP_vector(0), - MTP_vector(0), - MTP_vector(0)), - id); -} - void Widget::searchFailed( SearchRequestType type, const MTP::Error &error, @@ -3056,13 +3004,6 @@ void Widget::searchFailed( } } -void Widget::peerSearchFailed(const MTP::Error &error, mtpRequestId id) { - if (_peerSearchRequest == id) { - _peerSearchRequest = 0; - _peerSearchFull = true; - } -} - void Widget::dragEnterEvent(QDragEnterEvent *e) { using namespace Storage; @@ -3487,12 +3428,7 @@ bool Widget::applySearchState(SearchState state) { clearSearchCache(searchCleared); } if (state.query.isEmpty()) { - _peerSearchCache.clear(); - const auto queries = base::take(_peerSearchQueries); - for (const auto &[requestId, query] : queries) { - _api.request(requestId).cancel(); - } - _peerSearchQuery = QString(); + _peerSearch.clear(); } if (_searchState.query != currentSearchQuery()) { @@ -3550,8 +3486,8 @@ void Widget::clearSearchCache(bool clearPosts) { _topicSearchQuery = QString(); _topicSearchOffsetDate = 0; _topicSearchOffsetId = _topicSearchOffsetTopicId = 0; - _api.request(base::take(_peerSearchRequest)).cancel(); _api.request(base::take(_topicSearchRequest)).cancel(); + _peerSearch.clear(); cancelSearchRequest(); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 531b59b38a..7d231d3c77 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "api/api_peer_search.h" #include "base/timer.h" #include "dialogs/dialogs_key.h" #include "window/section_widget.h" @@ -188,9 +189,7 @@ private: const MTPmessages_Messages &result, not_null process, bool cacheResults = false); - void peerSearchReceived( - const MTPcontacts_Found &result, - mtpRequestId requestId); + void peerSearchReceived(Api::PeerSearchResult result); void escape(); void submit(); void cancelSearchRequest(); @@ -213,7 +212,7 @@ private: void collectStoriesUserpicsViews(Data::StorySourcesList list); void storiesToggleExplicitExpand(bool expand); void trackScroll(not_null widget); - [[nodiscard]] bool searchForPeersRequired(const QString &query) const; + [[nodiscard]] bool peerSearchRequired() const; [[nodiscard]] bool searchForTopicsRequired(const QString &query) const; // Child list may be unable to set specific search state. @@ -267,11 +266,9 @@ private: SearchRequestType type, const MTP::Error &error, not_null process); - void peerSearchFailed(const MTP::Error &error, mtpRequestId requestId); void searchApplyEmpty( SearchRequestType type, not_null process); - void peerSearchApplyEmpty(mtpRequestId id); void updateForceDisplayWide(); void scrollToDefault(bool verytop = false); @@ -373,10 +370,6 @@ private: base::Timer _searchTimer; - QString _peerSearchQuery; - bool _peerSearchFull = false; - mtpRequestId _peerSearchRequest = 0; - QString _topicSearchQuery; TimeId _topicSearchOffsetDate = 0; MsgId _topicSearchOffsetId = 0; @@ -399,9 +392,8 @@ private: SearchProcessState _postsProcess; int _historiesRequest = 0; // Not real mtpRequestId. + Api::PeerSearch _peerSearch; Api::SingleMessageSearch _singleMessageSearch; - base::flat_map _peerSearchCache; - base::flat_map _peerSearchQueries; QPixmap _widthAnimationCache;