/* 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_messages_search.h" #include "apiwrap.h" #include "data/data_channel.h" #include "data/data_histories.h" #include "data/data_message_reaction_id.h" #include "data/data_peer.h" #include "data/data_session.h" #include "history/history.h" #include "history/history_item.h" #include "main/main_session.h" namespace Api { namespace { constexpr auto kSearchPerPage = 50; [[nodiscard]] MessageIdsList HistoryItemsFromTL( not_null data, const QVector &messages) { auto result = MessageIdsList(); for (const auto &message : messages) { const auto peerId = PeerFromMessage(message); if (const auto peer = data->peerLoaded(peerId)) { if (const auto lastDate = DateFromMessage(message)) { const auto item = data->addNewMessage( message, MessageFlags(), NewMessageType::Existing); result.push_back(item->fullId()); } } else { LOG(("API Error: a search results with not loaded peer %1" ).arg(peerId.value)); } } return result; } [[nodiscard]] QString RequestToToken( const MessagesSearch::Request &request) { auto result = request.query; if (request.from) { result += '\n' + QString::number(request.from->id.value); } for (const auto &tag : request.tags) { result += '\n'; if (const auto customId = tag.custom()) { result += u"custom"_q + QString::number(customId); } else { result += u"emoji"_q + tag.emoji(); } } return result; } } // namespace MessagesSearch::MessagesSearch(not_null history) : _history(history) { } MessagesSearch::~MessagesSearch() { _history->owner().histories().cancelRequest( base::take(_searchInHistoryRequest)); } void MessagesSearch::searchMessages(Request request) { _request = std::move(request); _offsetId = {}; searchRequest(); } void MessagesSearch::searchMore() { if (_searchInHistoryRequest || _requestId) { return; } searchRequest(); } void MessagesSearch::searchRequest() { const auto nextToken = RequestToToken(_request); if (!_offsetId) { const auto it = _cacheOfStartByToken.find(nextToken); if (it != end(_cacheOfStartByToken)) { _requestId = 0; searchReceived(it->second, _requestId, nextToken); return; } } auto callback = [=](Fn finish) { using Flag = MTPmessages_Search::Flag; const auto from = _request.from; const auto fromPeer = _history->peer->isUser() ? nullptr : from; const auto savedPeer = _history->peer->isSelf() ? from : nullptr; _requestId = _history->session().api().request(MTPmessages_Search( MTP_flags((fromPeer ? Flag::f_from_id : Flag()) | (savedPeer ? Flag::f_saved_peer_id : Flag()) | (_request.tags.empty() ? Flag() : Flag::f_saved_reaction)), _history->peer->input, MTP_string(_request.query), (fromPeer ? fromPeer->input : MTP_inputPeerEmpty()), (savedPeer ? savedPeer->input : MTP_inputPeerEmpty()), MTP_vector_from_range(_request.tags | ranges::views::transform( Data::ReactionToMTP )), MTPint(), // top_msg_id MTP_inputMessagesFilterEmpty(), MTP_int(0), // min_date MTP_int(0), // max_date MTP_int(_offsetId), // offset_id MTP_int(0), // add_offset MTP_int(kSearchPerPage), MTP_int(0), // max_id MTP_int(0), // min_id MTP_long(0) // hash )).done([=](const TLMessages &result, mtpRequestId id) { _searchInHistoryRequest = 0; searchReceived(result, id, nextToken); finish(); }).fail([=](const MTP::Error &error, mtpRequestId id) { _searchInHistoryRequest = 0; if (_requestId == id) { _requestId = 0; } if (error.type() == u"SEARCH_QUERY_EMPTY"_q) { _messagesFounds.fire({ 0, MessageIdsList(), nextToken }); } finish(); }).send(); return _requestId; }; _searchInHistoryRequest = _history->owner().histories().sendRequest( _history, Data::Histories::RequestType::History, std::move(callback)); } void MessagesSearch::searchReceived( const TLMessages &result, mtpRequestId requestId, const QString &nextToken) { if (requestId != _requestId) { return; } auto &owner = _history->owner(); auto found = result.match([&](const MTPDmessages_messages &data) { if (_requestId != 0) { // Don't apply cached data! owner.processUsers(data.vusers()); owner.processChats(data.vchats()); } auto items = HistoryItemsFromTL(&owner, data.vmessages().v); const auto total = int(data.vmessages().v.size()); return FoundMessages{ total, std::move(items), nextToken }; }, [&](const MTPDmessages_messagesSlice &data) { if (_requestId != 0) { // Don't apply cached data! owner.processUsers(data.vusers()); owner.processChats(data.vchats()); } auto items = HistoryItemsFromTL(&owner, data.vmessages().v); // data.vnext_rate() is used only in global search. const auto total = int(data.vcount().v); return FoundMessages{ total, std::move(items), nextToken }; }, [&](const MTPDmessages_channelMessages &data) { if (_requestId != 0) { // Don't apply cached data! owner.processUsers(data.vusers()); owner.processChats(data.vchats()); } if (const auto channel = _history->peer->asChannel()) { channel->ptsReceived(data.vpts().v); if (_requestId != 0) { // Don't apply cached data! channel->processTopics(data.vtopics()); } } else { LOG(("API Error: " "received messages.channelMessages when no channel " "was passed!")); } auto items = HistoryItemsFromTL(&owner, data.vmessages().v); const auto total = int(data.vcount().v); return FoundMessages{ total, std::move(items), nextToken }; }, [](const MTPDmessages_messagesNotModified &data) { return FoundMessages{}; }); if (!_offsetId) { _cacheOfStartByToken.emplace(nextToken, result); } _requestId = 0; _offsetId = found.messages.empty() ? MsgId() : found.messages.back().msg; _messagesFounds.fire(std::move(found)); } rpl::producer MessagesSearch::messagesFounds() const { return _messagesFounds.events(); } } // namespace Api