Open video from ?t= links.

This commit is contained in:
John Preston 2025-01-28 11:49:45 +04:00
parent 1e77a3df20
commit 2077f51084
14 changed files with 86 additions and 74 deletions

View file

@ -317,6 +317,7 @@ PreviewWrap::PreviewWrap(
0, // duration 0, // duration
QString(), // author QString(), // author
false, // hasLargeMedia false, // hasLargeMedia
false, // photoIsVideoCover
0)) // pendingTill 0)) // pendingTill
, _theme(theme) , _theme(theme)
, _style(style) , _style(style)

View file

@ -598,6 +598,8 @@ bool ResolveUsernameOrPhone(
const auto threadParam = params.value(u"thread"_q); const auto threadParam = params.value(u"thread"_q);
const auto threadId = topicId ? topicId : threadParam.toInt(); const auto threadId = topicId ? topicId : threadParam.toInt();
const auto gameParam = params.value(u"game"_q); const auto gameParam = params.value(u"game"_q);
const auto videot = params.value(u"t"_q);
if (!gameParam.isEmpty() && validDomain(gameParam)) { if (!gameParam.isEmpty() && validDomain(gameParam)) {
startToken = gameParam; startToken = gameParam;
resolveType = ResolveType::ShareGame; resolveType = ResolveType::ShareGame;
@ -614,6 +616,9 @@ bool ResolveUsernameOrPhone(
.phone = phone, .phone = phone,
.messageId = post, .messageId = post,
.storyId = storyId, .storyId = storyId,
.videoTimestamp = (!videot.isEmpty()
? ParseVideoTimestamp(videot)
: std::optional<TimeId>()),
.text = params.value(u"text"_q), .text = params.value(u"text"_q),
.repliesInfo = commentId .repliesInfo = commentId
? Window::RepliesByLinkInfo{ ? Window::RepliesByLinkInfo{
@ -1747,4 +1752,14 @@ void ResolveAndShowUniqueGift(
ResolveAndShowUniqueGift(std::move(show), slug, {}); ResolveAndShowUniqueGift(std::move(show), slug, {});
} }
TimeId ParseVideoTimestamp(QStringView value) {
const auto kExp = u"^(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?$"_q;
const auto m = QRegularExpression(kExp).match(value);
return m.hasMatch()
? (m.capturedView(1).toInt() * 3600
+ m.capturedView(2).toInt() * 60
+ m.capturedView(3).toInt())
: value.toInt();
}
} // namespace Core } // namespace Core

View file

@ -50,4 +50,6 @@ void ResolveAndShowUniqueGift(
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
const QString &slug); const QString &slug);
[[nodiscard]] TimeId ParseVideoTimestamp(QStringView value);
} // namespace Core } // namespace Core

View file

@ -3469,6 +3469,7 @@ not_null<WebPageData*> Session::processWebpage(
0, 0,
QString(), QString(),
false, false,
false,
data.vdate().v data.vdate().v
? data.vdate().v ? data.vdate().v
: (base::unixtime::now() + kDefaultPendingTimeout)); : (base::unixtime::now() + kDefaultPendingTimeout));
@ -3496,6 +3497,7 @@ not_null<WebPageData*> Session::webpage(
0, 0,
QString(), QString(),
false, false,
false,
TimeId(0)); TimeId(0));
} }
@ -3516,6 +3518,7 @@ not_null<WebPageData*> Session::webpage(
int duration, int duration,
const QString &author, const QString &author,
bool hasLargeMedia, bool hasLargeMedia,
bool photoIsVideoCover,
TimeId pendingTill) { TimeId pendingTill) {
const auto result = webpage(id); const auto result = webpage(id);
webpageApplyFields( webpageApplyFields(
@ -3536,6 +3539,7 @@ not_null<WebPageData*> Session::webpage(
duration, duration,
author, author,
hasLargeMedia, hasLargeMedia,
photoIsVideoCover,
pendingTill); pendingTill);
return result; return result;
} }
@ -3723,6 +3727,7 @@ void Session::webpageApplyFields(
data.vduration().value_or_empty(), data.vduration().value_or_empty(),
qs(data.vauthor().value_or_empty()), qs(data.vauthor().value_or_empty()),
data.is_has_large_media(), data.is_has_large_media(),
false, // photo_is_video_cover
pendingTill); pendingTill);
} }
@ -3744,6 +3749,7 @@ void Session::webpageApplyFields(
int duration, int duration,
const QString &author, const QString &author,
bool hasLargeMedia, bool hasLargeMedia,
bool photoIsVideoCover,
TimeId pendingTill) { TimeId pendingTill) {
const auto requestPending = (!page->pendingTill && pendingTill > 0); const auto requestPending = (!page->pendingTill && pendingTill > 0);
const auto changed = page->applyChanges( const auto changed = page->applyChanges(
@ -3763,6 +3769,7 @@ void Session::webpageApplyFields(
duration, duration,
author, author,
hasLargeMedia, hasLargeMedia,
photoIsVideoCover,
pendingTill); pendingTill);
if (requestPending) { if (requestPending) {
_session->api().requestWebPageDelayed(page); _session->api().requestWebPageDelayed(page);

View file

@ -631,6 +631,7 @@ public:
int duration, int duration,
const QString &author, const QString &author,
bool hasLargeMedia, bool hasLargeMedia,
bool photoIsVideoCover,
TimeId pendingTill); TimeId pendingTill);
[[nodiscard]] not_null<GameData*> game(GameId id); [[nodiscard]] not_null<GameData*> game(GameId id);
@ -916,6 +917,7 @@ private:
int duration, int duration,
const QString &author, const QString &author,
bool hasLargeMedia, bool hasLargeMedia,
bool photoIsVideoCover,
TimeId pendingTill); TimeId pendingTill);
void gameApplyFields( void gameApplyFields(

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo.h" #include "data/data_photo.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "core/local_url_handlers.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "iv/iv_data.h" #include "iv/iv_data.h"
#include "ui/image/image.h" #include "ui/image/image.h"
@ -228,6 +229,7 @@ bool WebPageData::applyChanges(
int newDuration, int newDuration,
const QString &newAuthor, const QString &newAuthor,
bool newHasLargeMedia, bool newHasLargeMedia,
bool newPhotoIsVideoCover,
int newPendingTill) { int newPendingTill) {
if (newPendingTill != 0 if (newPendingTill != 0
&& (!url.isEmpty() || failed) && (!url.isEmpty() || failed)
@ -265,6 +267,9 @@ bool WebPageData::applyChanges(
|| (hasSiteName + hasTitle + hasDescription < 2)) { || (hasSiteName + hasTitle + hasDescription < 2)) {
newHasLargeMedia = false; newHasLargeMedia = false;
} }
if (!newDocument || !newDocument->isVideoFile() || !newPhoto) {
newPhotoIsVideoCover = false;
}
if (type == newType if (type == newType
&& url == resultUrl && url == resultUrl
@ -283,6 +288,7 @@ bool WebPageData::applyChanges(
&& duration == newDuration && duration == newDuration
&& author == resultAuthor && author == resultAuthor
&& hasLargeMedia == (newHasLargeMedia ? 1 : 0) && hasLargeMedia == (newHasLargeMedia ? 1 : 0)
&& photoIsVideoCover == (newPhotoIsVideoCover ? 1 : 0)
&& pendingTill == newPendingTill) { && pendingTill == newPendingTill) {
return false; return false;
} }
@ -291,6 +297,7 @@ bool WebPageData::applyChanges(
} }
type = newType; type = newType;
hasLargeMedia = newHasLargeMedia ? 1 : 0; hasLargeMedia = newHasLargeMedia ? 1 : 0;
photoIsVideoCover = newPhotoIsVideoCover ? 1 : 0;
url = resultUrl; url = resultUrl;
displayUrl = resultDisplayUrl; displayUrl = resultDisplayUrl;
siteName = resultSiteName; siteName = resultSiteName;
@ -383,14 +390,7 @@ TimeId WebPageData::extractVideoTimestamp() const {
const auto parts = params.split('&'); const auto parts = params.split('&');
for (const auto &part : parts) { for (const auto &part : parts) {
if (part.startsWith(u"t="_q)) { if (part.startsWith(u"t="_q)) {
const auto value = part.mid(2); return Core::ParseVideoTimestamp(part.mid(2));
const auto kExp = u"^(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?$"_q;
const auto m = QRegularExpression(kExp).match(value);
return m.hasMatch()
? (m.capturedView(1).toInt() * 3600
+ m.capturedView(2).toInt() * 60
+ m.capturedView(3).toInt())
: value.toInt();
} }
} }
return 0; return 0;

View file

@ -106,6 +106,7 @@ struct WebPageData {
int newDuration, int newDuration,
const QString &newAuthor, const QString &newAuthor,
bool newHasLargeMedia, bool newHasLargeMedia,
bool newPhotoIsVideoCover,
int newPendingTill); int newPendingTill);
static void ApplyChanges( static void ApplyChanges(
@ -135,7 +136,8 @@ struct WebPageData {
std::shared_ptr<Data::UniqueGift> uniqueGift; std::shared_ptr<Data::UniqueGift> uniqueGift;
int duration = 0; int duration = 0;
TimeId pendingTill = 0; TimeId pendingTill = 0;
uint32 version : 30 = 0; uint32 version : 29 = 0;
uint32 photoIsVideoCover : 1 = 0;
uint32 hasLargeMedia : 1 = 0; uint32 hasLargeMedia : 1 = 0;
uint32 failed : 1 = 0; uint32 failed : 1 = 0;

View file

@ -726,6 +726,7 @@ HistoryItem::HistoryItem(
0, 0,
QString(), QString(),
false, false,
false,
0); 0);
auto webpageMedia = std::make_unique<Data::MediaWebPage>( auto webpageMedia = std::make_unique<Data::MediaWebPage>(
this, this,

View file

@ -2207,15 +2207,12 @@ bool HistoryWidget::insideJumpToEndInsteadOfToUnread() const {
} }
void HistoryWidget::showHistory( void HistoryWidget::showHistory(
const PeerId &peerId, PeerId peerId,
MsgId showAtMsgId, MsgId showAtMsgId,
const TextWithEntities &highlightPart, const Window::SectionShow &params) {
int highlightPartOffsetHint) {
_pinnedClickedId = FullMsgId(); _pinnedClickedId = FullMsgId();
_minPinnedId = std::nullopt; _minPinnedId = std::nullopt;
_showAtMsgHighlightPart = {}; _showAtMsgParams = {};
_showAtMsgHighlightPartOffsetHint = 0;
const auto wasState = controller()->dialogsEntryStateCurrent(); const auto wasState = controller()->dialogsEntryStateCurrent();
const auto startBot = (showAtMsgId == ShowAndStartBotMsgId); const auto startBot = (showAtMsgId == ShowAndStartBotMsgId);
@ -2265,16 +2262,10 @@ void HistoryWidget::showHistory(
).arg(_history->inboxReadTillId().bare ).arg(_history->inboxReadTillId().bare
).arg(Logs::b(_history->loadedAtBottom()) ).arg(Logs::b(_history->loadedAtBottom())
).arg(showAtMsgId.bare)); ).arg(showAtMsgId.bare));
delayedShowAt( delayedShowAt(showAtMsgId, params);
showAtMsgId,
highlightPart,
highlightPartOffsetHint);
} else if (_showAtMsgId != showAtMsgId) { } else if (_showAtMsgId != showAtMsgId) {
clearAllLoadRequests(); clearAllLoadRequests();
setMsgId( setMsgId(showAtMsgId, params);
showAtMsgId,
highlightPart,
highlightPartOffsetHint);
firstLoadMessages(); firstLoadMessages();
doneShow(); doneShow();
} }
@ -2294,10 +2285,7 @@ void HistoryWidget::showHistory(
_cornerButtons.skipReplyReturn(skipId); _cornerButtons.skipReplyReturn(skipId);
} }
setMsgId( setMsgId(showAtMsgId, params);
showAtMsgId,
highlightPart,
highlightPartOffsetHint);
if (_historyInited) { if (_historyInited) {
DEBUG_LOG(("JumpToEnd(%1, %2, %3): " DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
"Showing instant at %4." "Showing instant at %4."
@ -2402,8 +2390,7 @@ void HistoryWidget::showHistory(
clearInlineBot(); clearInlineBot();
_showAtMsgId = showAtMsgId; _showAtMsgId = showAtMsgId;
_showAtMsgHighlightPart = highlightPart; _showAtMsgParams = params;
_showAtMsgHighlightPartOffsetHint = highlightPartOffsetHint;
_historyInited = false; _historyInited = false;
_contactStatus = nullptr; _contactStatus = nullptr;
_businessBotStatus = nullptr; _businessBotStatus = nullptr;
@ -3642,10 +3629,7 @@ void HistoryWidget::messagesReceived(
} }
_delayedShowAtRequest = 0; _delayedShowAtRequest = 0;
setMsgId( setMsgId(_delayedShowAtMsgId, _delayedShowAtMsgParams);
_delayedShowAtMsgId,
_delayedShowAtMsgHighlightPart,
_delayedShowAtMsgHighlightPartOffsetHint);
historyLoaded(); historyLoaded();
} }
if (session().supportMode()) { if (session().supportMode()) {
@ -3897,15 +3881,11 @@ void HistoryWidget::loadMessagesDown() {
void HistoryWidget::delayedShowAt( void HistoryWidget::delayedShowAt(
MsgId showAtMsgId, MsgId showAtMsgId,
const TextWithEntities &highlightPart, const Window::SectionShow &params) {
int highlightPartOffsetHint) {
if (!_history) { if (!_history) {
return; return;
} }
if (_delayedShowAtMsgHighlightPart != highlightPart) { _delayedShowAtMsgParams = params;
_delayedShowAtMsgHighlightPart = highlightPart;
}
_delayedShowAtMsgHighlightPartOffsetHint = highlightPartOffsetHint;
if (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId) { if (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId) {
return; return;
} }
@ -4539,12 +4519,8 @@ PeerData *HistoryWidget::peer() const {
// Sometimes _showAtMsgId is set directly. // Sometimes _showAtMsgId is set directly.
void HistoryWidget::setMsgId( void HistoryWidget::setMsgId(
MsgId showAtMsgId, MsgId showAtMsgId,
const TextWithEntities &highlightPart, const Window::SectionShow &params) {
int highlightPartOffsetHint) { _showAtMsgParams = params;
if (_showAtMsgHighlightPart != highlightPart) {
_showAtMsgHighlightPart = highlightPart;
}
_showAtMsgHighlightPartOffsetHint = highlightPartOffsetHint;
if (_showAtMsgId != showAtMsgId) { if (_showAtMsgId != showAtMsgId) {
_showAtMsgId = showAtMsgId; _showAtMsgId = showAtMsgId;
if (_history) { if (_history) {
@ -6289,8 +6265,8 @@ int HistoryWidget::countInitialScrollTop() {
enqueueMessageHighlight({ enqueueMessageHighlight({
item, item,
base::take(_showAtMsgHighlightPart), base::take(_showAtMsgParams.highlightPart),
base::take(_showAtMsgHighlightPartOffsetHint), base::take(_showAtMsgParams.highlightPartOffsetHint),
}); });
const auto result = itemTopForHighlight(view); const auto result = itemTopForHighlight(view);
createUnreadBarIfBelowVisibleArea(result); createUnreadBarIfBelowVisibleArea(result);
@ -6507,6 +6483,22 @@ void HistoryWidget::updateHistoryGeometry(
} }
const auto toY = std::clamp(newScrollTop, 0, _scroll->scrollTopMax()); const auto toY = std::clamp(newScrollTop, 0, _scroll->scrollTopMax());
synteticScrollToY(toY); synteticScrollToY(toY);
if (initial && _showAtMsgId) {
const auto timestamp = base::take(_showAtMsgParams.videoTimestamp);
if (timestamp.has_value()) {
const auto item = session().data().message(_peer, _showAtMsgId);
const auto media = item ? item->media() : nullptr;
const auto document = media ? media->document() : nullptr;
if (document && document->isVideoFile()) {
controller()->openDocument(
document,
true,
{ item->fullId() },
nullptr,
timestamp);
}
}
}
} }
void HistoryWidget::revealItemsCallback() { void HistoryWidget::revealItemsCallback() {

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/field_characters_count_manager.h" #include "chat_helpers/field_characters_count_manager.h"
#include "data/data_report.h" #include "data/data_report.h"
#include "window/section_widget.h" #include "window/section_widget.h"
#include "window/window_session_controller.h"
#include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/input_field.h"
#include "mtproto/sender.h" #include "mtproto/sender.h"
@ -86,10 +87,6 @@ namespace Webrtc {
enum class RecordAvailability : uchar; enum class RecordAvailability : uchar;
} // namespace Webrtc } // namespace Webrtc
namespace Window {
class SessionController;
} // namespace Window
namespace ChatHelpers { namespace ChatHelpers {
class TabbedPanel; class TabbedPanel;
class TabbedSelector; class TabbedSelector;
@ -160,10 +157,7 @@ public:
void loadMessages(); void loadMessages();
void loadMessagesDown(); void loadMessagesDown();
void firstLoadMessages(); void firstLoadMessages();
void delayedShowAt( void delayedShowAt(MsgId showAtMsgId, const Window::SectionShow &params);
MsgId showAtMsgId,
const TextWithEntities &highlightPart,
int highlightPartOffsetHint);
bool updateReplaceMediaButton(); bool updateReplaceMediaButton();
void updateFieldPlaceholder(); void updateFieldPlaceholder();
@ -176,10 +170,7 @@ public:
History *history() const; History *history() const;
PeerData *peer() const; PeerData *peer() const;
void setMsgId( void setMsgId(MsgId showAtMsgId, const Window::SectionShow &params = {});
MsgId showAtMsgId,
const TextWithEntities &highlightPart = {},
int highlightPartOffsetHint = 0);
MsgId msgId() const; MsgId msgId() const;
bool hasTopBarShadow() const { bool hasTopBarShadow() const {
@ -244,10 +235,9 @@ public:
bool applyDraft( bool applyDraft(
FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear);
void showHistory( void showHistory(
const PeerId &peer, PeerId peerId,
MsgId showAtMsgId, MsgId showAtMsgId,
const TextWithEntities &highlightPart = {}, const Window::SectionShow &params = {});
int highlightPartOffsetHint = 0);
void setChooseReportMessagesDetails( void setChooseReportMessagesDetails(
Data::ReportInput reportInput, Data::ReportInput reportInput,
Fn<void(std::vector<MsgId>)> callback); Fn<void(std::vector<MsgId>)> callback);
@ -728,8 +718,7 @@ private:
bool _canSendTexts = false; bool _canSendTexts = false;
MsgId _showAtMsgId = ShowAtUnreadMsgId; MsgId _showAtMsgId = ShowAtUnreadMsgId;
base::flat_set<MsgId> _topicsRequested; base::flat_set<MsgId> _topicsRequested;
TextWithEntities _showAtMsgHighlightPart; Window::SectionShow _showAtMsgParams;
int _showAtMsgHighlightPartOffsetHint = 0;
bool _showAndMaybeSendStart = false; bool _showAndMaybeSendStart = false;
int _firstLoadRequest = 0; // Not real mtpRequestId. int _firstLoadRequest = 0; // Not real mtpRequestId.
@ -737,8 +726,7 @@ private:
int _preloadDownRequest = 0; // Not real mtpRequestId. int _preloadDownRequest = 0; // Not real mtpRequestId.
MsgId _delayedShowAtMsgId = -1; MsgId _delayedShowAtMsgId = -1;
TextWithEntities _delayedShowAtMsgHighlightPart; Window::SectionShow _delayedShowAtMsgParams;
int _delayedShowAtMsgHighlightPartOffsetHint = 0;
int _delayedShowAtRequest = 0; // Not real mtpRequestId. int _delayedShowAtRequest = 0; // Not real mtpRequestId.
History *_supportPreloadHistory = nullptr; History *_supportPreloadHistory = nullptr;

View file

@ -1449,11 +1449,7 @@ void MainWidget::showHistory(
&& way != Way::Forward) { && way != Way::Forward) {
ClearBotStartToken(_history->peer()); ClearBotStartToken(_history->peer());
} }
_history->showHistory( _history->showHistory(peerId, showAtMsgId, params);
peerId,
showAtMsgId,
params.highlightPart,
params.highlightPartOffsetHint);
if (alreadyThatPeer && params.reapplyLocalDraft) { if (alreadyThatPeer && params.reapplyLocalDraft) {
_history->applyDraft(HistoryWidget::FieldHistoryAction::NewEntry); _history->applyDraft(HistoryWidget::FieldHistoryAction::NewEntry);
} }

View file

@ -755,6 +755,7 @@ void SessionNavigation::showPeerByLinkResolved(
}); });
} else { } else {
const auto draft = info.text; const auto draft = info.text;
params.videoTimestamp = info.videoTimestamp;
crl::on_main(this, [=] { crl::on_main(this, [=] {
if (peer->isUser() && !draft.isEmpty()) { if (peer->isUser() && !draft.isEmpty()) {
Data::SetChatLinkDraft(peer, { draft }); Data::SetChatLinkDraft(peer, { draft });
@ -2780,19 +2781,21 @@ void SessionController::openDocument(
not_null<DocumentData*> document, not_null<DocumentData*> document,
bool showInMediaView, bool showInMediaView,
MessageContext message, MessageContext message,
const Data::StoriesContext *stories) { const Data::StoriesContext *stories,
std::optional<TimeId> videoTimestampOverride) {
const auto item = session().data().message(message.id); const auto item = session().data().message(message.id);
if (openSharedStory(item) || openFakeItemStory(message.id, stories)) { if (openSharedStory(item) || openFakeItemStory(message.id, stories)) {
return; return;
} else if (showInMediaView) { } else if (showInMediaView) {
using namespace Media::View; using namespace Media::View;
const auto timestamp = item ? ExtractVideoTimestamp(item) : 0;
_window->openInMediaView(OpenRequest( _window->openInMediaView(OpenRequest(
this, this,
document, document,
item, item,
message.topicRootId, message.topicRootId,
false, false,
(item ? ExtractVideoTimestamp(item) : 0) * crl::time(1000))); videoTimestampOverride.value_or(timestamp) * crl::time(1000)));
return; return;
} }
Data::ResolveDocument(this, document, item, message.topicRootId); Data::ResolveDocument(this, document, item, message.topicRootId);

View file

@ -158,6 +158,7 @@ struct SectionShow {
TextWithEntities highlightPart; TextWithEntities highlightPart;
int highlightPartOffsetHint = 0; int highlightPartOffsetHint = 0;
std::optional<TimeId> videoTimestamp;
Way way = Way::Forward; Way way = Way::Forward;
anim::type animated = anim::type::normal; anim::type animated = anim::type::normal;
anim::activation activation = anim::activation::normal; anim::activation activation = anim::activation::normal;
@ -505,7 +506,8 @@ public:
not_null<DocumentData*> document, not_null<DocumentData*> document,
bool showInMediaView, bool showInMediaView,
MessageContext message, MessageContext message,
const Data::StoriesContext *stories = nullptr); const Data::StoriesContext *stories = nullptr,
std::optional<TimeId> videoTimestampOverride = {});
bool openSharedStory(HistoryItem *item); bool openSharedStory(HistoryItem *item);
bool openFakeItemStory( bool openFakeItemStory(
FullMsgId fakeItemId, FullMsgId fakeItemId,

View file

@ -40,6 +40,7 @@ struct PeerByLinkInfo {
QString chatLinkSlug; QString chatLinkSlug;
MsgId messageId = ShowAtUnreadMsgId; MsgId messageId = ShowAtUnreadMsgId;
StoryId storyId = 0; StoryId storyId = 0;
std::optional<TimeId> videoTimestamp;
QString text; QString text;
RepliesByLinkInfo repliesInfo; RepliesByLinkInfo repliesInfo;
ResolveType resolveType = ResolveType::Default; ResolveType resolveType = ResolveType::Default;