diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 5ddc7b4293..e637796956 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -129,6 +129,8 @@ PRIVATE api/api_invite_links.h api/api_media.cpp api/api_media.h + api/api_messages_search.cpp + api/api_messages_search.h api/api_peer_photo.cpp api/api_peer_photo.h api/api_polls.cpp diff --git a/Telegram/SourceFiles/api/api_messages_search.cpp b/Telegram/SourceFiles/api/api_messages_search.cpp new file mode 100644 index 0000000000..4cbfe665e2 --- /dev/null +++ b/Telegram/SourceFiles/api/api_messages_search.cpp @@ -0,0 +1,173 @@ +/* +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_peer.h" +#include "data/data_session.h" +#include "history/history.h" +#include "history/history_item.h" +#include "main/main_session.h" + +namespace Api { + +namespace { + +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; +} + +} // namespace + +MessagesSearch::MessagesSearch( + not_null session, + not_null history) +: _session(session) +, _history(history) +, _api(&session->mtp()) { +} + +void MessagesSearch::searchMessages(const QString &query, PeerData *from) { + _query = query; + _from = from; + _offsetId = {}; + searchRequest(); +} + +void MessagesSearch::searchMore() { + if (_searchInHistoryRequest || _requestId) { + return; + } + searchRequest(); +} + +void MessagesSearch::searchRequest() { + const auto nextToken = _query + + QString::number(_from ? _from->id.value : 0); + auto callback = [=](Fn finish) { + const auto flags = _from + ? MTP_flags(MTPmessages_Search::Flag::f_from_id) + : MTP_flags(0); + _requestId = _api.request(MTPmessages_Search( + flags, + _history->peer->input, + MTP_string(_query), + (_from + ? _from->input + : MTP_inputPeerEmpty()), + 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(SearchPerPage), + MTP_int(0), // max_id + MTP_int(0), // min_id + MTP_long(0) // hash + )).done([=](const MTPmessages_Messages &result, mtpRequestId id) { + _searchInHistoryRequest = 0; + searchReceived(result, id, nextToken); + finish(); + }).fail([=](const MTP::Error &error, mtpRequestId id) { + _searchInHistoryRequest = 0; + + if (error.type() == u"SEARCH_QUERY_EMPTY"_q) { + _messagesFounds.fire({ 0, MessageIdsList(), nextToken }); + } else if (_requestId == id) { + _requestId = 0; + } + + finish(); + }).send(); + return _requestId; + }; + _searchInHistoryRequest = _session->data().histories().sendRequest( + _history, + Data::Histories::RequestType::History, + std::move(callback)); +} + +void MessagesSearch::searchReceived( + const MTPmessages_Messages &result, + mtpRequestId requestId, + const QString &nextToken) { + if (requestId != _requestId) { + return; + } + auto &owner = _session->data(); + 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 (const auto channel = _history->peer->asChannel()) { + channel->ptsReceived(data.vpts().v); + } else { + LOG(("API Error: " + "received messages.channelMessages when no channel " + "was passed!")); + } + 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_messagesNotModified &data) { + return FoundMessages{}; + }); + _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 diff --git a/Telegram/SourceFiles/api/api_messages_search.h b/Telegram/SourceFiles/api/api_messages_search.h new file mode 100644 index 0000000000..9e1a9d4ca8 --- /dev/null +++ b/Telegram/SourceFiles/api/api_messages_search.h @@ -0,0 +1,61 @@ +/* +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 + +#include "mtproto/sender.h" + +class HistoryItem; +class History; +class PeerData; + +namespace Main { +class Session; +} // namespace Main + +namespace Api { + +struct FoundMessages { + int total = -1; + MessageIdsList messages; + QString nextToken; +}; + +class MessagesSearch final { +public: + explicit MessagesSearch( + not_null session, + not_null history); + + void searchMessages(const QString &query, PeerData *from); + void searchMore(); + + [[nodiscard]] rpl::producer messagesFounds() const; + +private: + void searchRequest(); + void searchReceived( + const MTPmessages_Messages &result, + mtpRequestId requestId, + const QString &nextToken); + + const not_null _session; + const not_null _history; + MTP::Sender _api; + + QString _query; + PeerData *_from = nullptr; + MsgId _offsetId; + + int _searchInHistoryRequest = 0; // Not real mtpRequestId. + mtpRequestId _requestId = 0; + + rpl::event_stream _messagesFounds; + +}; + +} // namespace Api