Request sponsored peers in global search.

This commit is contained in:
John Preston 2025-03-20 10:26:39 +04:00
parent a2a16f3f23
commit 7f2545a9ef
6 changed files with 300 additions and 128 deletions

View file

@ -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<Main::Session*> session, Type type)
: _session(session)
, _type(type) {
}
PeerSearch::~PeerSearch() {
clear();
}
void PeerSearch::request(
const QString &query,
Fn<void(PeerSearchResult)> 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

View file

@ -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<PeerData*> peer;
QByteArray randomId;
QString sponsorInfo;
QString additionalInfo;
};
struct PeerSearchResult {
QString query;
std::vector<not_null<PeerData*>> my;
std::vector<not_null<PeerData*>> peers;
std::vector<SponsoredSearchResult> sponsored;
};
class PeerSearch final {
public:
enum class Type {
WithSponsored,
JustPeers,
};
PeerSearch(not_null<Main::Session*> session, Type type);
~PeerSearch();
enum class RequestType {
CacheOnly,
CacheOrRemote,
};
void request(
const QString &query,
Fn<void(PeerSearchResult)> 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<Main::Session*> _session;
const Type _type;
QString _query;
Fn<void(PeerSearchResult)> _callback;
base::flat_map<QString, CacheEntry> _cache;
base::flat_map<mtpRequestId, QString> _peerRequests;
base::flat_map<mtpRequestId, QString> _sponsoredRequests;
};
} // namespace Api

View file

@ -243,7 +243,9 @@ struct InnerWidget::HashtagResult {
struct InnerWidget::PeerSearchResult {
explicit PeerSearchResult(not_null<PeerData*> peer) : peer(peer) {
}
not_null<PeerData*> peer;
std::unique_ptr<Api::SponsoredSearchResult> 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<MTPPeer> &my,
const QVector<MTPPeer> &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<not_null<PeerData*>>();
for (const auto &sponsored : result.sponsored) {
_peerSearchResults.push_back(
std::make_unique<PeerSearchResult>(sponsored.peer));
_peerSearchResults.back()->sponsored
= std::make_unique<Api::SponsoredSearchResult>(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<PeerSearchResult>(
peer));
} else {
LOG(("API Error: "
"user %1 was not loaded in InnerWidget::peopleReceived()"
).arg(peerFromMTP(mtpPeer).value));
}
_peerSearchResults.push_back(
std::make_unique<PeerSearchResult>(peer));
}
refresh();
}

View file

@ -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<MTPPeer> &my,
const QVector<MTPPeer> &result);
void peerSearchReceived(Api::PeerSearchResult result);
[[nodiscard]] FilterId filterId() const;

View file

@ -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<MTPPeer>(0),
MTP_vector<MTPPeer>(0),
MTP_vector<MTPChat>(0),
MTP_vector<MTPUser>(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();
}

View file

@ -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<SearchProcessState*> 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<Ui::RpWidget*> 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<SearchProcessState*> process);
void peerSearchFailed(const MTP::Error &error, mtpRequestId requestId);
void searchApplyEmpty(
SearchRequestType type,
not_null<SearchProcessState*> 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<QString, MTPcontacts_Found> _peerSearchCache;
base::flat_map<mtpRequestId, QString> _peerSearchQueries;
QPixmap _widthAnimationCache;