mirror of https://github.com/procxx/kepka.git
Search single messages by message link.
This commit is contained in:
parent
230dd29af5
commit
10e28913ca
|
@ -0,0 +1,219 @@
|
||||||
|
/*
|
||||||
|
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_single_message_search.h"
|
||||||
|
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_channel.h"
|
||||||
|
#include "data/data_search_controller.h"
|
||||||
|
#include "core/local_url_handlers.h"
|
||||||
|
#include "history/history_item.h"
|
||||||
|
#include "base/qthelp_url.h"
|
||||||
|
#include "apiwrap.h"
|
||||||
|
|
||||||
|
namespace Api {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using Key = details::SingleMessageSearchKey;
|
||||||
|
|
||||||
|
Key ExtractKey(const QString &query) {
|
||||||
|
const auto trimmed = query.trimmed();
|
||||||
|
const auto local = Core::TryConvertUrlToLocal(query);
|
||||||
|
const auto check = local.isEmpty() ? trimmed : local;
|
||||||
|
const auto parse = [&] {
|
||||||
|
const auto delimeter = check.indexOf('?');
|
||||||
|
return (delimeter > 0)
|
||||||
|
? qthelp::url_parse_params(
|
||||||
|
check.mid(delimeter + 1),
|
||||||
|
qthelp::UrlParamNameTransform::ToLower)
|
||||||
|
: QMap<QString, QString>();
|
||||||
|
};
|
||||||
|
if (check.startsWith(qstr("tg://privatepost"), Qt::CaseInsensitive)) {
|
||||||
|
const auto params = parse();
|
||||||
|
const auto channel = params.value("channel");
|
||||||
|
const auto post = params.value("post").toInt();
|
||||||
|
return (channel.toInt() && post) ? Key{ channel, post } : Key();
|
||||||
|
} else if (check.startsWith(qstr("tg://resolve"), Qt::CaseInsensitive)) {
|
||||||
|
const auto params = parse();
|
||||||
|
const auto domain = params.value("domain");
|
||||||
|
const auto post = params.value("post").toInt();
|
||||||
|
return (!domain.isEmpty() && post) ? Key{ domain, post } : Key();
|
||||||
|
}
|
||||||
|
return Key();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
SingleMessageSearch::SingleMessageSearch(not_null<Main::Session*> session)
|
||||||
|
: _session(session) {
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleMessageSearch::~SingleMessageSearch() {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleMessageSearch::clear() {
|
||||||
|
_cache.clear();
|
||||||
|
_requestKey = Key();
|
||||||
|
_session->api().request(base::take(_requestId)).cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<HistoryItem*> SingleMessageSearch::lookup(
|
||||||
|
const QString &query,
|
||||||
|
Fn<void()> ready) {
|
||||||
|
const auto key = ExtractKey(query);
|
||||||
|
if (!key) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const auto i = _cache.find(key);
|
||||||
|
if (i != end(_cache)) {
|
||||||
|
return _session->data().message(i->second);
|
||||||
|
}
|
||||||
|
if (!(_requestKey == key)) {
|
||||||
|
_session->api().request(base::take(_requestId)).cancel();
|
||||||
|
_requestKey = key;
|
||||||
|
}
|
||||||
|
return performLookup(ready);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<HistoryItem*> SingleMessageSearch::performLookupByChannel(
|
||||||
|
not_null<ChannelData*> channel,
|
||||||
|
Fn<void()> ready) {
|
||||||
|
Expects(!_requestKey.empty());
|
||||||
|
|
||||||
|
const auto postId = _requestKey.postId;
|
||||||
|
if (const auto item = _session->data().message(channel, postId)) {
|
||||||
|
_cache.emplace(_requestKey, item->fullId());
|
||||||
|
return item;
|
||||||
|
} else if (!ready) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto fail = [=] {
|
||||||
|
_cache.emplace(_requestKey, FullMsgId());
|
||||||
|
ready();
|
||||||
|
};
|
||||||
|
_requestId = _session->api().request(MTPchannels_GetMessages(
|
||||||
|
channel->inputChannel,
|
||||||
|
MTP_vector<MTPInputMessage>(1, MTP_inputMessageID(MTP_int(postId)))
|
||||||
|
)).done([=](const MTPmessages_Messages &result) {
|
||||||
|
const auto received = Api::ParseSearchResult(
|
||||||
|
channel,
|
||||||
|
Storage::SharedMediaType::kCount,
|
||||||
|
postId,
|
||||||
|
Data::LoadDirection::Around,
|
||||||
|
result);
|
||||||
|
if (!received.messageIds.empty()
|
||||||
|
&& received.messageIds.front() == postId) {
|
||||||
|
_cache.emplace(
|
||||||
|
_requestKey,
|
||||||
|
FullMsgId(channel->bareId(), postId));
|
||||||
|
ready();
|
||||||
|
} else {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}).fail([=](const RPCError &error) {
|
||||||
|
fail();
|
||||||
|
}).send();
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<HistoryItem*> SingleMessageSearch::performLookupById(
|
||||||
|
ChannelId channelId,
|
||||||
|
Fn<void()> ready) {
|
||||||
|
Expects(!_requestKey.empty());
|
||||||
|
|
||||||
|
if (const auto channel = _session->data().channelLoaded(channelId)) {
|
||||||
|
return performLookupByChannel(channel, ready);
|
||||||
|
} else if (!ready) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto fail = [=] {
|
||||||
|
_cache.emplace(_requestKey, FullMsgId());
|
||||||
|
ready();
|
||||||
|
};
|
||||||
|
_requestId = _session->api().request(MTPchannels_GetChannels(
|
||||||
|
MTP_vector<MTPInputChannel>(
|
||||||
|
1,
|
||||||
|
MTP_inputChannel(MTP_int(channelId), MTP_long(0)))
|
||||||
|
)).done([=](const MTPmessages_Chats &result) {
|
||||||
|
result.match([&](const auto &data) {
|
||||||
|
const auto peer = _session->data().processChats(data.vchats());
|
||||||
|
if (peer && peer->id == peerFromChannel(channelId)) {
|
||||||
|
if (performLookupByChannel(peer->asChannel(), ready)) {
|
||||||
|
ready();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).fail([=](const RPCError &error) {
|
||||||
|
fail();
|
||||||
|
}).send();
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<HistoryItem*> SingleMessageSearch::performLookupByUsername(
|
||||||
|
const QString &username,
|
||||||
|
Fn<void()> ready) {
|
||||||
|
Expects(!_requestKey.empty());
|
||||||
|
|
||||||
|
if (const auto peer = _session->data().peerByUsername(username)) {
|
||||||
|
if (const auto channel = peer->asChannel()) {
|
||||||
|
return performLookupByChannel(channel, ready);
|
||||||
|
}
|
||||||
|
_cache.emplace(_requestKey, FullMsgId());
|
||||||
|
return nullptr;
|
||||||
|
} else if (!ready) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto fail = [=] {
|
||||||
|
_cache.emplace(_requestKey, FullMsgId());
|
||||||
|
ready();
|
||||||
|
};
|
||||||
|
_requestId = _session->api().request(MTPcontacts_ResolveUsername(
|
||||||
|
MTP_string(username)
|
||||||
|
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
||||||
|
result.match([&](const MTPDcontacts_resolvedPeer &data) {
|
||||||
|
_session->data().processUsers(data.vusers());
|
||||||
|
_session->data().processChats(data.vchats());
|
||||||
|
const auto peerId = peerFromMTP(data.vpeer());
|
||||||
|
const auto peer = peerId
|
||||||
|
? _session->data().peerLoaded(peerId)
|
||||||
|
: nullptr;
|
||||||
|
if (const auto channel = peer ? peer->asChannel() : nullptr) {
|
||||||
|
if (performLookupByChannel(channel, ready)) {
|
||||||
|
ready();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).fail([=](const RPCError &error) {
|
||||||
|
fail();
|
||||||
|
}).send();
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<HistoryItem*> SingleMessageSearch::performLookup(
|
||||||
|
Fn<void()> ready) {
|
||||||
|
Expects(!_requestKey.empty());
|
||||||
|
|
||||||
|
if (!_requestKey.domainOrId[0].isDigit()) {
|
||||||
|
return performLookupByUsername(_requestKey.domainOrId, ready);
|
||||||
|
}
|
||||||
|
const auto channelId = _requestKey.domainOrId.toInt();
|
||||||
|
return performLookupById(channelId, ready);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Api
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
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 {
|
||||||
|
namespace details {
|
||||||
|
|
||||||
|
struct SingleMessageSearchKey {
|
||||||
|
QString domainOrId;
|
||||||
|
MsgId postId = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] bool empty() const {
|
||||||
|
return domainOrId.isEmpty() || !postId;
|
||||||
|
}
|
||||||
|
[[nodiscard]] explicit operator bool() const {
|
||||||
|
return !empty();
|
||||||
|
}
|
||||||
|
[[nodiscard]] bool operator<(const SingleMessageSearchKey &other) const {
|
||||||
|
return std::tie(domainOrId, postId)
|
||||||
|
< std::tie(other.domainOrId, other.postId);
|
||||||
|
}
|
||||||
|
[[nodiscard]] bool operator==(
|
||||||
|
const SingleMessageSearchKey &other) const {
|
||||||
|
return std::tie(domainOrId, postId)
|
||||||
|
== std::tie(other.domainOrId, other.postId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace details
|
||||||
|
|
||||||
|
class SingleMessageSearch {
|
||||||
|
public:
|
||||||
|
explicit SingleMessageSearch(not_null<Main::Session*> session);
|
||||||
|
~SingleMessageSearch();
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
// If 'ready' callback is empty, the result must not be 'nullopt'.
|
||||||
|
[[nodiscard]] std::optional<HistoryItem*> lookup(
|
||||||
|
const QString &query,
|
||||||
|
Fn<void()> ready = nullptr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
using Key = details::SingleMessageSearchKey;
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<HistoryItem*> performLookup(
|
||||||
|
Fn<void()> ready);
|
||||||
|
[[nodiscard]] std::optional<HistoryItem*> performLookupById(
|
||||||
|
ChannelId channelId,
|
||||||
|
Fn<void()> ready);
|
||||||
|
[[nodiscard]] std::optional<HistoryItem*> performLookupByUsername(
|
||||||
|
const QString &username,
|
||||||
|
Fn<void()> ready);
|
||||||
|
[[nodiscard]] std::optional<HistoryItem*> performLookupByChannel(
|
||||||
|
not_null<ChannelData*> channel,
|
||||||
|
Fn<void()> ready);
|
||||||
|
|
||||||
|
const not_null<Main::Session*> _session;
|
||||||
|
std::map<Key, FullMsgId> _cache;
|
||||||
|
mtpRequestId _requestId = 0;
|
||||||
|
Key _requestKey;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Api
|
|
@ -26,58 +26,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
QString tryConvertUrlToLocal(QString url) {
|
|
||||||
if (url.size() > 8192) {
|
|
||||||
url = url.mid(0, 8192);
|
|
||||||
}
|
|
||||||
|
|
||||||
using namespace qthelp;
|
|
||||||
auto matchOptions = RegExOption::CaseInsensitive;
|
|
||||||
auto telegramMeMatch = regex_match(qsl("^https?://(www\\.)?(telegram\\.(me|dog)|t\\.me)/(.+)$"), url, matchOptions);
|
|
||||||
if (telegramMeMatch) {
|
|
||||||
auto query = telegramMeMatch->capturedRef(4);
|
|
||||||
if (auto joinChatMatch = regex_match(qsl("^joinchat/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), query, matchOptions)) {
|
|
||||||
return qsl("tg://join?invite=") + url_encode(joinChatMatch->captured(1));
|
|
||||||
} else if (auto stickerSetMatch = regex_match(qsl("^addstickers/([a-zA-Z0-9\\.\\_]+)(\\?|$)"), query, matchOptions)) {
|
|
||||||
return qsl("tg://addstickers?set=") + url_encode(stickerSetMatch->captured(1));
|
|
||||||
} else if (auto languageMatch = regex_match(qsl("^setlanguage/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), query, matchOptions)) {
|
|
||||||
return qsl("tg://setlanguage?lang=") + url_encode(languageMatch->captured(1));
|
|
||||||
} else if (auto shareUrlMatch = regex_match(qsl("^share/url/?\\?(.+)$"), query, matchOptions)) {
|
|
||||||
return qsl("tg://msg_url?") + shareUrlMatch->captured(1);
|
|
||||||
} else if (auto confirmPhoneMatch = regex_match(qsl("^confirmphone/?\\?(.+)"), query, matchOptions)) {
|
|
||||||
return qsl("tg://confirmphone?") + confirmPhoneMatch->captured(1);
|
|
||||||
} else if (auto ivMatch = regex_match(qsl("^iv/?\\?(.+)(#|$)"), query, matchOptions)) {
|
|
||||||
//
|
|
||||||
// We need to show our t.me page, not the url directly.
|
|
||||||
//
|
|
||||||
//auto params = url_parse_params(ivMatch->captured(1), UrlParamNameTransform::ToLower);
|
|
||||||
//auto previewedUrl = params.value(qsl("url"));
|
|
||||||
//if (previewedUrl.startsWith(qstr("http://"), Qt::CaseInsensitive)
|
|
||||||
// || previewedUrl.startsWith(qstr("https://"), Qt::CaseInsensitive)) {
|
|
||||||
// return previewedUrl;
|
|
||||||
//}
|
|
||||||
return url;
|
|
||||||
} else if (auto socksMatch = regex_match(qsl("^socks/?\\?(.+)(#|$)"), query, matchOptions)) {
|
|
||||||
return qsl("tg://socks?") + socksMatch->captured(1);
|
|
||||||
} else if (auto proxyMatch = regex_match(qsl("^proxy/?\\?(.+)(#|$)"), query, matchOptions)) {
|
|
||||||
return qsl("tg://proxy?") + proxyMatch->captured(1);
|
|
||||||
} else if (auto bgMatch = regex_match(qsl("^bg/([a-zA-Z0-9\\.\\_\\-]+)(\\?(.+)?)?$"), query, matchOptions)) {
|
|
||||||
const auto params = bgMatch->captured(3);
|
|
||||||
return qsl("tg://bg?slug=") + bgMatch->captured(1) + (params.isEmpty() ? QString() : '&' + params);
|
|
||||||
} else if (auto postMatch = regex_match(qsl("^c/(\\-?\\d+)/(\\d+)(#|$)"), query, matchOptions)) {
|
|
||||||
return qsl("tg://privatepost?channel=%1&post=%2").arg(postMatch->captured(1)).arg(postMatch->captured(2));
|
|
||||||
} else if (auto usernameMatch = regex_match(qsl("^([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$|/(\\d+)/?(?:\\?|$))"), query, matchOptions)) {
|
|
||||||
auto params = query.mid(usernameMatch->captured(0).size()).toString();
|
|
||||||
auto postParam = QString();
|
|
||||||
if (auto postMatch = regex_match(qsl("^/\\d+/?(?:\\?|$)"), usernameMatch->captured(2))) {
|
|
||||||
postParam = qsl("&post=") + usernameMatch->captured(3);
|
|
||||||
}
|
|
||||||
return qsl("tg://resolve/?domain=") + url_encode(usernameMatch->captured(1)) + postParam + (params.isEmpty() ? QString() : '&' + params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool UrlRequiresConfirmation(const QUrl &url) {
|
bool UrlRequiresConfirmation(const QUrl &url) {
|
||||||
using namespace qthelp;
|
using namespace qthelp;
|
||||||
return !regex_match(qsl("(^|\\.)(telegram\\.org|telegra\\.ph|telesco\\.pe)$"), url.host(), RegExOption::CaseInsensitive);
|
return !regex_match(qsl("(^|\\.)(telegram\\.org|telegra\\.ph|telesco\\.pe)$"), url.host(), RegExOption::CaseInsensitive);
|
||||||
|
@ -120,7 +68,7 @@ QString UrlClickHandler::url() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void UrlClickHandler::Open(QString url, QVariant context) {
|
void UrlClickHandler::Open(QString url, QVariant context) {
|
||||||
url = tryConvertUrlToLocal(url);
|
url = Core::TryConvertUrlToLocal(url);
|
||||||
if (Core::InternalPassportLink(url)) {
|
if (Core::InternalPassportLink(url)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -143,7 +91,7 @@ auto UrlClickHandler::getTextEntity() const -> TextEntity {
|
||||||
}
|
}
|
||||||
|
|
||||||
void HiddenUrlClickHandler::Open(QString url, QVariant context) {
|
void HiddenUrlClickHandler::Open(QString url, QVariant context) {
|
||||||
url = tryConvertUrlToLocal(url);
|
url = Core::TryConvertUrlToLocal(url);
|
||||||
if (Core::InternalPassportLink(url)) {
|
if (Core::InternalPassportLink(url)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -174,7 +122,7 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void BotGameUrlClickHandler::onClick(ClickContext context) const {
|
void BotGameUrlClickHandler::onClick(ClickContext context) const {
|
||||||
const auto url = tryConvertUrlToLocal(this->url());
|
const auto url = Core::TryConvertUrlToLocal(this->url());
|
||||||
if (Core::InternalPassportLink(url)) {
|
if (Core::InternalPassportLink(url)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -401,6 +401,58 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString TryConvertUrlToLocal(QString url) {
|
||||||
|
if (url.size() > 8192) {
|
||||||
|
url = url.mid(0, 8192);
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace qthelp;
|
||||||
|
auto matchOptions = RegExOption::CaseInsensitive;
|
||||||
|
auto telegramMeMatch = regex_match(qsl("^https?://(www\\.)?(telegram\\.(me|dog)|t\\.me)/(.+)$"), url, matchOptions);
|
||||||
|
if (telegramMeMatch) {
|
||||||
|
auto query = telegramMeMatch->capturedRef(4);
|
||||||
|
if (auto joinChatMatch = regex_match(qsl("^joinchat/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), query, matchOptions)) {
|
||||||
|
return qsl("tg://join?invite=") + url_encode(joinChatMatch->captured(1));
|
||||||
|
} else if (auto stickerSetMatch = regex_match(qsl("^addstickers/([a-zA-Z0-9\\.\\_]+)(\\?|$)"), query, matchOptions)) {
|
||||||
|
return qsl("tg://addstickers?set=") + url_encode(stickerSetMatch->captured(1));
|
||||||
|
} else if (auto languageMatch = regex_match(qsl("^setlanguage/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), query, matchOptions)) {
|
||||||
|
return qsl("tg://setlanguage?lang=") + url_encode(languageMatch->captured(1));
|
||||||
|
} else if (auto shareUrlMatch = regex_match(qsl("^share/url/?\\?(.+)$"), query, matchOptions)) {
|
||||||
|
return qsl("tg://msg_url?") + shareUrlMatch->captured(1);
|
||||||
|
} else if (auto confirmPhoneMatch = regex_match(qsl("^confirmphone/?\\?(.+)"), query, matchOptions)) {
|
||||||
|
return qsl("tg://confirmphone?") + confirmPhoneMatch->captured(1);
|
||||||
|
} else if (auto ivMatch = regex_match(qsl("^iv/?\\?(.+)(#|$)"), query, matchOptions)) {
|
||||||
|
//
|
||||||
|
// We need to show our t.me page, not the url directly.
|
||||||
|
//
|
||||||
|
//auto params = url_parse_params(ivMatch->captured(1), UrlParamNameTransform::ToLower);
|
||||||
|
//auto previewedUrl = params.value(qsl("url"));
|
||||||
|
//if (previewedUrl.startsWith(qstr("http://"), Qt::CaseInsensitive)
|
||||||
|
// || previewedUrl.startsWith(qstr("https://"), Qt::CaseInsensitive)) {
|
||||||
|
// return previewedUrl;
|
||||||
|
//}
|
||||||
|
return url;
|
||||||
|
} else if (auto socksMatch = regex_match(qsl("^socks/?\\?(.+)(#|$)"), query, matchOptions)) {
|
||||||
|
return qsl("tg://socks?") + socksMatch->captured(1);
|
||||||
|
} else if (auto proxyMatch = regex_match(qsl("^proxy/?\\?(.+)(#|$)"), query, matchOptions)) {
|
||||||
|
return qsl("tg://proxy?") + proxyMatch->captured(1);
|
||||||
|
} else if (auto bgMatch = regex_match(qsl("^bg/([a-zA-Z0-9\\.\\_\\-]+)(\\?(.+)?)?$"), query, matchOptions)) {
|
||||||
|
const auto params = bgMatch->captured(3);
|
||||||
|
return qsl("tg://bg?slug=") + bgMatch->captured(1) + (params.isEmpty() ? QString() : '&' + params);
|
||||||
|
} else if (auto postMatch = regex_match(qsl("^c/(\\-?\\d+)/(\\d+)(#|$)"), query, matchOptions)) {
|
||||||
|
return qsl("tg://privatepost?channel=%1&post=%2").arg(postMatch->captured(1)).arg(postMatch->captured(2));
|
||||||
|
} else if (auto usernameMatch = regex_match(qsl("^([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$|/(\\d+)/?(?:\\?|$))"), query, matchOptions)) {
|
||||||
|
auto params = query.mid(usernameMatch->captured(0).size()).toString();
|
||||||
|
auto postParam = QString();
|
||||||
|
if (auto postMatch = regex_match(qsl("^/\\d+/?(?:\\?|$)"), usernameMatch->captured(2))) {
|
||||||
|
postParam = qsl("&post=") + usernameMatch->captured(3);
|
||||||
|
}
|
||||||
|
return qsl("tg://resolve/?domain=") + url_encode(usernameMatch->captured(1)) + postParam + (params.isEmpty() ? QString() : '&' + params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
bool InternalPassportLink(const QString &url) {
|
bool InternalPassportLink(const QString &url) {
|
||||||
const auto urlTrimmed = url.trimmed();
|
const auto urlTrimmed = url.trimmed();
|
||||||
if (!urlTrimmed.startsWith(qstr("tg://"), Qt::CaseInsensitive)) {
|
if (!urlTrimmed.startsWith(qstr("tg://"), Qt::CaseInsensitive)) {
|
||||||
|
|
|
@ -25,10 +25,12 @@ struct LocalUrlHandler {
|
||||||
const QVariant &context)> handler;
|
const QVariant &context)> handler;
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::vector<LocalUrlHandler> &LocalUrlHandlers();
|
[[nodiscard]] const std::vector<LocalUrlHandler> &LocalUrlHandlers();
|
||||||
|
|
||||||
bool InternalPassportLink(const QString &url);
|
[[nodiscard]] QString TryConvertUrlToLocal(QString url);
|
||||||
|
|
||||||
bool StartUrlRequiresActivate(const QString &url);
|
[[nodiscard]] bool InternalPassportLink(const QString &url);
|
||||||
|
|
||||||
|
[[nodiscard]] bool StartUrlRequiresActivate(const QString &url);
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -2000,6 +2000,7 @@ bool InnerWidget::hasHistoryInResults(not_null<History*> history) const {
|
||||||
|
|
||||||
bool InnerWidget::searchReceived(
|
bool InnerWidget::searchReceived(
|
||||||
const QVector<MTPMessage> &messages,
|
const QVector<MTPMessage> &messages,
|
||||||
|
HistoryItem *inject,
|
||||||
SearchRequestType type,
|
SearchRequestType type,
|
||||||
int fullCount) {
|
int fullCount) {
|
||||||
const auto uniquePeers = uniqueSearchResults();
|
const auto uniquePeers = uniqueSearchResults();
|
||||||
|
@ -2010,6 +2011,16 @@ bool InnerWidget::searchReceived(
|
||||||
auto isMigratedSearch = (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset);
|
auto isMigratedSearch = (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset);
|
||||||
|
|
||||||
TimeId lastDateFound = 0;
|
TimeId lastDateFound = 0;
|
||||||
|
if (inject
|
||||||
|
&& (!_searchInChat
|
||||||
|
|| inject->history() == _searchInChat.history())) {
|
||||||
|
Assert(_searchResults.empty());
|
||||||
|
_searchResults.push_back(
|
||||||
|
std::make_unique<FakeRow>(
|
||||||
|
_searchInChat,
|
||||||
|
inject));
|
||||||
|
++fullCount;
|
||||||
|
}
|
||||||
for (const auto &message : messages) {
|
for (const auto &message : messages) {
|
||||||
auto msgId = IdFromMessage(message);
|
auto msgId = IdFromMessage(message);
|
||||||
auto peerId = PeerFromMessage(message);
|
auto peerId = PeerFromMessage(message);
|
||||||
|
|
|
@ -70,6 +70,7 @@ public:
|
||||||
|
|
||||||
bool searchReceived(
|
bool searchReceived(
|
||||||
const QVector<MTPMessage> &result,
|
const QVector<MTPMessage> &result,
|
||||||
|
HistoryItem *inject,
|
||||||
SearchRequestType type,
|
SearchRequestType type,
|
||||||
int fullCount);
|
int fullCount);
|
||||||
void peerSearchReceived(
|
void peerSearchReceived(
|
||||||
|
|
|
@ -166,7 +166,8 @@ Widget::Widget(
|
||||||
, _cancelSearch(_searchControls, st::dialogsCancelSearch)
|
, _cancelSearch(_searchControls, st::dialogsCancelSearch)
|
||||||
, _lockUnlock(_searchControls, st::dialogsLock)
|
, _lockUnlock(_searchControls, st::dialogsLock)
|
||||||
, _scroll(this, st::dialogsScroll)
|
, _scroll(this, st::dialogsScroll)
|
||||||
, _scrollToTop(_scroll, st::dialogsToUp) {
|
, _scrollToTop(_scroll, st::dialogsToUp)
|
||||||
|
, _singleMessageSearch(&controller->session()) {
|
||||||
_inner = _scroll->setOwnedWidget(object_ptr<InnerWidget>(this, controller));
|
_inner = _scroll->setOwnedWidget(object_ptr<InnerWidget>(this, controller));
|
||||||
|
|
||||||
rpl::combine(
|
rpl::combine(
|
||||||
|
@ -380,6 +381,7 @@ void Widget::fullSearchRefreshOn(rpl::producer<> events) {
|
||||||
}) | rpl::start_with_next([=] {
|
}) | rpl::start_with_next([=] {
|
||||||
_searchTimer.stop();
|
_searchTimer.stop();
|
||||||
_searchCache.clear();
|
_searchCache.clear();
|
||||||
|
_singleMessageSearch.clear();
|
||||||
_searchQueries.clear();
|
_searchQueries.clear();
|
||||||
_searchQuery = QString();
|
_searchQuery = QString();
|
||||||
_scroll->scrollToY(0);
|
_scroll->scrollToY(0);
|
||||||
|
@ -730,6 +732,12 @@ bool Widget::onSearchMessages(bool searchCache) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (searchCache) {
|
if (searchCache) {
|
||||||
|
const auto success = _singleMessageSearch.lookup(q, [=] {
|
||||||
|
onNeedSearchMessages();
|
||||||
|
});
|
||||||
|
if (!success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const auto i = _searchCache.constFind(q);
|
const auto i = _searchCache.constFind(q);
|
||||||
if (i != _searchCache.cend()) {
|
if (i != _searchCache.cend()) {
|
||||||
_searchQuery = q;
|
_searchQuery = q;
|
||||||
|
@ -992,6 +1000,10 @@ void Widget::searchReceived(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const auto inject = (type == SearchRequestType::FromStart
|
||||||
|
|| type == SearchRequestType::PeerFromStart)
|
||||||
|
? *_singleMessageSearch.lookup(_searchQuery)
|
||||||
|
: nullptr;
|
||||||
|
|
||||||
if (_searchRequest == requestId) {
|
if (_searchRequest == requestId) {
|
||||||
switch (result.type()) {
|
switch (result.type()) {
|
||||||
|
@ -1003,7 +1015,7 @@ void Widget::searchReceived(
|
||||||
session().data().processChats(d.vchats());
|
session().data().processChats(d.vchats());
|
||||||
}
|
}
|
||||||
auto &msgs = d.vmessages().v;
|
auto &msgs = d.vmessages().v;
|
||||||
_inner->searchReceived(msgs, type, msgs.size());
|
_inner->searchReceived(msgs, inject, type, msgs.size());
|
||||||
if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) {
|
if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) {
|
||||||
_searchFullMigrated = true;
|
_searchFullMigrated = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1019,7 +1031,7 @@ void Widget::searchReceived(
|
||||||
session().data().processChats(d.vchats());
|
session().data().processChats(d.vchats());
|
||||||
}
|
}
|
||||||
auto &msgs = d.vmessages().v;
|
auto &msgs = d.vmessages().v;
|
||||||
const auto someAdded = _inner->searchReceived(msgs, type, d.vcount().v);
|
const auto someAdded = _inner->searchReceived(msgs, inject, type, d.vcount().v);
|
||||||
const auto nextRate = d.vnext_rate();
|
const auto nextRate = d.vnext_rate();
|
||||||
const auto rateUpdated = nextRate && (nextRate->v != _searchNextRate);
|
const auto rateUpdated = nextRate && (nextRate->v != _searchNextRate);
|
||||||
const auto finished = (type == SearchRequestType::FromStart || type == SearchRequestType::FromOffset)
|
const auto finished = (type == SearchRequestType::FromStart || type == SearchRequestType::FromOffset)
|
||||||
|
@ -1058,7 +1070,7 @@ void Widget::searchReceived(
|
||||||
session().data().processChats(d.vchats());
|
session().data().processChats(d.vchats());
|
||||||
}
|
}
|
||||||
auto &msgs = d.vmessages().v;
|
auto &msgs = d.vmessages().v;
|
||||||
if (!_inner->searchReceived(msgs, type, d.vcount().v)) {
|
if (!_inner->searchReceived(msgs, inject, type, d.vcount().v)) {
|
||||||
if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) {
|
if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) {
|
||||||
_searchFullMigrated = true;
|
_searchFullMigrated = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1292,6 +1304,7 @@ void Widget::setSearchInChat(Key chat, UserData *from) {
|
||||||
|
|
||||||
void Widget::clearSearchCache() {
|
void Widget::clearSearchCache() {
|
||||||
_searchCache.clear();
|
_searchCache.clear();
|
||||||
|
_singleMessageSearch.clear();
|
||||||
_searchQueries.clear();
|
_searchQueries.clear();
|
||||||
_searchQuery = QString();
|
_searchQuery = QString();
|
||||||
_searchQueryFrom = nullptr;
|
_searchQueryFrom = nullptr;
|
||||||
|
|
|
@ -7,11 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "dialogs/dialogs_key.h"
|
||||||
#include "window/section_widget.h"
|
#include "window/section_widget.h"
|
||||||
#include "ui/effects/animations.h"
|
#include "ui/effects/animations.h"
|
||||||
#include "ui/widgets/scroll_area.h"
|
#include "ui/widgets/scroll_area.h"
|
||||||
#include "dialogs/dialogs_key.h"
|
|
||||||
#include "ui/special_buttons.h"
|
#include "ui/special_buttons.h"
|
||||||
|
#include "api/api_single_message_search.h"
|
||||||
|
|
||||||
namespace Main {
|
namespace Main {
|
||||||
class Session;
|
class Session;
|
||||||
|
@ -218,17 +219,11 @@ private:
|
||||||
bool _searchFullMigrated = false;
|
bool _searchFullMigrated = false;
|
||||||
mtpRequestId _searchRequest = 0;
|
mtpRequestId _searchRequest = 0;
|
||||||
|
|
||||||
using SearchCache = QMap<QString, MTPmessages_Messages>;
|
QMap<QString, MTPmessages_Messages> _searchCache;
|
||||||
SearchCache _searchCache;
|
Api::SingleMessageSearch _singleMessageSearch;
|
||||||
|
QMap<mtpRequestId, QString> _searchQueries;
|
||||||
using SearchQueries = QMap<mtpRequestId, QString>;
|
QMap<QString, MTPcontacts_Found> _peerSearchCache;
|
||||||
SearchQueries _searchQueries;
|
QMap<mtpRequestId, QString> _peerSearchQueries;
|
||||||
|
|
||||||
using PeerSearchCache = QMap<QString, MTPcontacts_Found>;
|
|
||||||
PeerSearchCache _peerSearchCache;
|
|
||||||
|
|
||||||
using PeerSearchQueries = QMap<mtpRequestId, QString>;
|
|
||||||
PeerSearchQueries _peerSearchQueries;
|
|
||||||
|
|
||||||
QPixmap _widthAnimationCache;
|
QPixmap _widthAnimationCache;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<(src_loc)/api/api_sending.cpp
|
<(src_loc)/api/api_sending.cpp
|
||||||
<(src_loc)/api/api_sending.h
|
<(src_loc)/api/api_sending.h
|
||||||
|
<(src_loc)/api/api_single_message_search.cpp
|
||||||
|
<(src_loc)/api/api_single_message_search.h
|
||||||
<(src_loc)/boxes/peers/add_participants_box.cpp
|
<(src_loc)/boxes/peers/add_participants_box.cpp
|
||||||
<(src_loc)/boxes/peers/add_participants_box.h
|
<(src_loc)/boxes/peers/add_participants_box.h
|
||||||
<(src_loc)/boxes/peers/edit_contact_box.cpp
|
<(src_loc)/boxes/peers/edit_contact_box.cpp
|
||||||
|
|
Loading…
Reference in New Issue