From 2077f510842eb875a58b246cf0a7756720ec4a8a Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 28 Jan 2025 11:49:45 +0400 Subject: [PATCH] Open video from ?t= links. --- .../boxes/peers/edit_peer_color_box.cpp | 1 + .../SourceFiles/core/local_url_handlers.cpp | 15 ++++ .../SourceFiles/core/local_url_handlers.h | 2 + Telegram/SourceFiles/data/data_session.cpp | 7 ++ Telegram/SourceFiles/data/data_session.h | 2 + Telegram/SourceFiles/data/data_web_page.cpp | 16 ++--- Telegram/SourceFiles/data/data_web_page.h | 4 +- Telegram/SourceFiles/history/history_item.cpp | 1 + .../SourceFiles/history/history_widget.cpp | 68 ++++++++----------- Telegram/SourceFiles/history/history_widget.h | 26 ++----- Telegram/SourceFiles/mainwidget.cpp | 6 +- .../window/window_session_controller.cpp | 7 +- .../window/window_session_controller.h | 4 +- .../window_session_controller_link_info.h | 1 + 14 files changed, 86 insertions(+), 74 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp index 8b827dc7c..73b3004f0 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp @@ -317,6 +317,7 @@ PreviewWrap::PreviewWrap( 0, // duration QString(), // author false, // hasLargeMedia + false, // photoIsVideoCover 0)) // pendingTill , _theme(theme) , _style(style) diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 3eda30a36..8aa7ef9ba 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -598,6 +598,8 @@ bool ResolveUsernameOrPhone( const auto threadParam = params.value(u"thread"_q); const auto threadId = topicId ? topicId : threadParam.toInt(); const auto gameParam = params.value(u"game"_q); + const auto videot = params.value(u"t"_q); + if (!gameParam.isEmpty() && validDomain(gameParam)) { startToken = gameParam; resolveType = ResolveType::ShareGame; @@ -614,6 +616,9 @@ bool ResolveUsernameOrPhone( .phone = phone, .messageId = post, .storyId = storyId, + .videoTimestamp = (!videot.isEmpty() + ? ParseVideoTimestamp(videot) + : std::optional()), .text = params.value(u"text"_q), .repliesInfo = commentId ? Window::RepliesByLinkInfo{ @@ -1747,4 +1752,14 @@ void ResolveAndShowUniqueGift( 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 diff --git a/Telegram/SourceFiles/core/local_url_handlers.h b/Telegram/SourceFiles/core/local_url_handlers.h index 4f9b68003..ce71def0f 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.h +++ b/Telegram/SourceFiles/core/local_url_handlers.h @@ -50,4 +50,6 @@ void ResolveAndShowUniqueGift( std::shared_ptr show, const QString &slug); +[[nodiscard]] TimeId ParseVideoTimestamp(QStringView value); + } // namespace Core diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 42bc9b3c8..87d45eeb6 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -3469,6 +3469,7 @@ not_null Session::processWebpage( 0, QString(), false, + false, data.vdate().v ? data.vdate().v : (base::unixtime::now() + kDefaultPendingTimeout)); @@ -3496,6 +3497,7 @@ not_null Session::webpage( 0, QString(), false, + false, TimeId(0)); } @@ -3516,6 +3518,7 @@ not_null Session::webpage( int duration, const QString &author, bool hasLargeMedia, + bool photoIsVideoCover, TimeId pendingTill) { const auto result = webpage(id); webpageApplyFields( @@ -3536,6 +3539,7 @@ not_null Session::webpage( duration, author, hasLargeMedia, + photoIsVideoCover, pendingTill); return result; } @@ -3723,6 +3727,7 @@ void Session::webpageApplyFields( data.vduration().value_or_empty(), qs(data.vauthor().value_or_empty()), data.is_has_large_media(), + false, // photo_is_video_cover pendingTill); } @@ -3744,6 +3749,7 @@ void Session::webpageApplyFields( int duration, const QString &author, bool hasLargeMedia, + bool photoIsVideoCover, TimeId pendingTill) { const auto requestPending = (!page->pendingTill && pendingTill > 0); const auto changed = page->applyChanges( @@ -3763,6 +3769,7 @@ void Session::webpageApplyFields( duration, author, hasLargeMedia, + photoIsVideoCover, pendingTill); if (requestPending) { _session->api().requestWebPageDelayed(page); diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index bf1520a59..3e5392173 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -631,6 +631,7 @@ public: int duration, const QString &author, bool hasLargeMedia, + bool photoIsVideoCover, TimeId pendingTill); [[nodiscard]] not_null game(GameId id); @@ -916,6 +917,7 @@ private: int duration, const QString &author, bool hasLargeMedia, + bool photoIsVideoCover, TimeId pendingTill); void gameApplyFields( diff --git a/Telegram/SourceFiles/data/data_web_page.cpp b/Telegram/SourceFiles/data/data_web_page.cpp index fe364e038..bd14735eb 100644 --- a/Telegram/SourceFiles/data/data_web_page.cpp +++ b/Telegram/SourceFiles/data/data_web_page.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_photo.h" #include "data/data_channel.h" #include "data/data_document.h" +#include "core/local_url_handlers.h" #include "lang/lang_keys.h" #include "iv/iv_data.h" #include "ui/image/image.h" @@ -228,6 +229,7 @@ bool WebPageData::applyChanges( int newDuration, const QString &newAuthor, bool newHasLargeMedia, + bool newPhotoIsVideoCover, int newPendingTill) { if (newPendingTill != 0 && (!url.isEmpty() || failed) @@ -265,6 +267,9 @@ bool WebPageData::applyChanges( || (hasSiteName + hasTitle + hasDescription < 2)) { newHasLargeMedia = false; } + if (!newDocument || !newDocument->isVideoFile() || !newPhoto) { + newPhotoIsVideoCover = false; + } if (type == newType && url == resultUrl @@ -283,6 +288,7 @@ bool WebPageData::applyChanges( && duration == newDuration && author == resultAuthor && hasLargeMedia == (newHasLargeMedia ? 1 : 0) + && photoIsVideoCover == (newPhotoIsVideoCover ? 1 : 0) && pendingTill == newPendingTill) { return false; } @@ -291,6 +297,7 @@ bool WebPageData::applyChanges( } type = newType; hasLargeMedia = newHasLargeMedia ? 1 : 0; + photoIsVideoCover = newPhotoIsVideoCover ? 1 : 0; url = resultUrl; displayUrl = resultDisplayUrl; siteName = resultSiteName; @@ -383,14 +390,7 @@ TimeId WebPageData::extractVideoTimestamp() const { const auto parts = params.split('&'); for (const auto &part : parts) { if (part.startsWith(u"t="_q)) { - const auto value = 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 Core::ParseVideoTimestamp(part.mid(2)); } } return 0; diff --git a/Telegram/SourceFiles/data/data_web_page.h b/Telegram/SourceFiles/data/data_web_page.h index 04b1a44a1..f8a19f1c1 100644 --- a/Telegram/SourceFiles/data/data_web_page.h +++ b/Telegram/SourceFiles/data/data_web_page.h @@ -106,6 +106,7 @@ struct WebPageData { int newDuration, const QString &newAuthor, bool newHasLargeMedia, + bool newPhotoIsVideoCover, int newPendingTill); static void ApplyChanges( @@ -135,7 +136,8 @@ struct WebPageData { std::shared_ptr uniqueGift; int duration = 0; TimeId pendingTill = 0; - uint32 version : 30 = 0; + uint32 version : 29 = 0; + uint32 photoIsVideoCover : 1 = 0; uint32 hasLargeMedia : 1 = 0; uint32 failed : 1 = 0; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index ef0741fe3..e70a5203d 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -726,6 +726,7 @@ HistoryItem::HistoryItem( 0, QString(), false, + false, 0); auto webpageMedia = std::make_unique( this, diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 81fe2777f..e5bd6baa4 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -2207,15 +2207,12 @@ bool HistoryWidget::insideJumpToEndInsteadOfToUnread() const { } void HistoryWidget::showHistory( - const PeerId &peerId, + PeerId peerId, MsgId showAtMsgId, - const TextWithEntities &highlightPart, - int highlightPartOffsetHint) { - + const Window::SectionShow ¶ms) { _pinnedClickedId = FullMsgId(); _minPinnedId = std::nullopt; - _showAtMsgHighlightPart = {}; - _showAtMsgHighlightPartOffsetHint = 0; + _showAtMsgParams = {}; const auto wasState = controller()->dialogsEntryStateCurrent(); const auto startBot = (showAtMsgId == ShowAndStartBotMsgId); @@ -2265,16 +2262,10 @@ void HistoryWidget::showHistory( ).arg(_history->inboxReadTillId().bare ).arg(Logs::b(_history->loadedAtBottom()) ).arg(showAtMsgId.bare)); - delayedShowAt( - showAtMsgId, - highlightPart, - highlightPartOffsetHint); + delayedShowAt(showAtMsgId, params); } else if (_showAtMsgId != showAtMsgId) { clearAllLoadRequests(); - setMsgId( - showAtMsgId, - highlightPart, - highlightPartOffsetHint); + setMsgId(showAtMsgId, params); firstLoadMessages(); doneShow(); } @@ -2294,10 +2285,7 @@ void HistoryWidget::showHistory( _cornerButtons.skipReplyReturn(skipId); } - setMsgId( - showAtMsgId, - highlightPart, - highlightPartOffsetHint); + setMsgId(showAtMsgId, params); if (_historyInited) { DEBUG_LOG(("JumpToEnd(%1, %2, %3): " "Showing instant at %4." @@ -2402,8 +2390,7 @@ void HistoryWidget::showHistory( clearInlineBot(); _showAtMsgId = showAtMsgId; - _showAtMsgHighlightPart = highlightPart; - _showAtMsgHighlightPartOffsetHint = highlightPartOffsetHint; + _showAtMsgParams = params; _historyInited = false; _contactStatus = nullptr; _businessBotStatus = nullptr; @@ -3642,10 +3629,7 @@ void HistoryWidget::messagesReceived( } _delayedShowAtRequest = 0; - setMsgId( - _delayedShowAtMsgId, - _delayedShowAtMsgHighlightPart, - _delayedShowAtMsgHighlightPartOffsetHint); + setMsgId(_delayedShowAtMsgId, _delayedShowAtMsgParams); historyLoaded(); } if (session().supportMode()) { @@ -3897,15 +3881,11 @@ void HistoryWidget::loadMessagesDown() { void HistoryWidget::delayedShowAt( MsgId showAtMsgId, - const TextWithEntities &highlightPart, - int highlightPartOffsetHint) { + const Window::SectionShow ¶ms) { if (!_history) { return; } - if (_delayedShowAtMsgHighlightPart != highlightPart) { - _delayedShowAtMsgHighlightPart = highlightPart; - } - _delayedShowAtMsgHighlightPartOffsetHint = highlightPartOffsetHint; + _delayedShowAtMsgParams = params; if (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId) { return; } @@ -4539,12 +4519,8 @@ PeerData *HistoryWidget::peer() const { // Sometimes _showAtMsgId is set directly. void HistoryWidget::setMsgId( MsgId showAtMsgId, - const TextWithEntities &highlightPart, - int highlightPartOffsetHint) { - if (_showAtMsgHighlightPart != highlightPart) { - _showAtMsgHighlightPart = highlightPart; - } - _showAtMsgHighlightPartOffsetHint = highlightPartOffsetHint; + const Window::SectionShow ¶ms) { + _showAtMsgParams = params; if (_showAtMsgId != showAtMsgId) { _showAtMsgId = showAtMsgId; if (_history) { @@ -6289,8 +6265,8 @@ int HistoryWidget::countInitialScrollTop() { enqueueMessageHighlight({ item, - base::take(_showAtMsgHighlightPart), - base::take(_showAtMsgHighlightPartOffsetHint), + base::take(_showAtMsgParams.highlightPart), + base::take(_showAtMsgParams.highlightPartOffsetHint), }); const auto result = itemTopForHighlight(view); createUnreadBarIfBelowVisibleArea(result); @@ -6507,6 +6483,22 @@ void HistoryWidget::updateHistoryGeometry( } const auto toY = std::clamp(newScrollTop, 0, _scroll->scrollTopMax()); 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() { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 9bdced55b..a3531eac6 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/field_characters_count_manager.h" #include "data/data_report.h" #include "window/section_widget.h" +#include "window/window_session_controller.h" #include "ui/widgets/fields/input_field.h" #include "mtproto/sender.h" @@ -86,10 +87,6 @@ namespace Webrtc { enum class RecordAvailability : uchar; } // namespace Webrtc -namespace Window { -class SessionController; -} // namespace Window - namespace ChatHelpers { class TabbedPanel; class TabbedSelector; @@ -160,10 +157,7 @@ public: void loadMessages(); void loadMessagesDown(); void firstLoadMessages(); - void delayedShowAt( - MsgId showAtMsgId, - const TextWithEntities &highlightPart, - int highlightPartOffsetHint); + void delayedShowAt(MsgId showAtMsgId, const Window::SectionShow ¶ms); bool updateReplaceMediaButton(); void updateFieldPlaceholder(); @@ -176,10 +170,7 @@ public: History *history() const; PeerData *peer() const; - void setMsgId( - MsgId showAtMsgId, - const TextWithEntities &highlightPart = {}, - int highlightPartOffsetHint = 0); + void setMsgId(MsgId showAtMsgId, const Window::SectionShow ¶ms = {}); MsgId msgId() const; bool hasTopBarShadow() const { @@ -244,10 +235,9 @@ public: bool applyDraft( FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); void showHistory( - const PeerId &peer, + PeerId peerId, MsgId showAtMsgId, - const TextWithEntities &highlightPart = {}, - int highlightPartOffsetHint = 0); + const Window::SectionShow ¶ms = {}); void setChooseReportMessagesDetails( Data::ReportInput reportInput, Fn)> callback); @@ -728,8 +718,7 @@ private: bool _canSendTexts = false; MsgId _showAtMsgId = ShowAtUnreadMsgId; base::flat_set _topicsRequested; - TextWithEntities _showAtMsgHighlightPart; - int _showAtMsgHighlightPartOffsetHint = 0; + Window::SectionShow _showAtMsgParams; bool _showAndMaybeSendStart = false; int _firstLoadRequest = 0; // Not real mtpRequestId. @@ -737,8 +726,7 @@ private: int _preloadDownRequest = 0; // Not real mtpRequestId. MsgId _delayedShowAtMsgId = -1; - TextWithEntities _delayedShowAtMsgHighlightPart; - int _delayedShowAtMsgHighlightPartOffsetHint = 0; + Window::SectionShow _delayedShowAtMsgParams; int _delayedShowAtRequest = 0; // Not real mtpRequestId. History *_supportPreloadHistory = nullptr; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 1af7432da..f4ba6983c 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1449,11 +1449,7 @@ void MainWidget::showHistory( && way != Way::Forward) { ClearBotStartToken(_history->peer()); } - _history->showHistory( - peerId, - showAtMsgId, - params.highlightPart, - params.highlightPartOffsetHint); + _history->showHistory(peerId, showAtMsgId, params); if (alreadyThatPeer && params.reapplyLocalDraft) { _history->applyDraft(HistoryWidget::FieldHistoryAction::NewEntry); } diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 3461db2aa..a4c5fced8 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -755,6 +755,7 @@ void SessionNavigation::showPeerByLinkResolved( }); } else { const auto draft = info.text; + params.videoTimestamp = info.videoTimestamp; crl::on_main(this, [=] { if (peer->isUser() && !draft.isEmpty()) { Data::SetChatLinkDraft(peer, { draft }); @@ -2780,19 +2781,21 @@ void SessionController::openDocument( not_null document, bool showInMediaView, MessageContext message, - const Data::StoriesContext *stories) { + const Data::StoriesContext *stories, + std::optional videoTimestampOverride) { const auto item = session().data().message(message.id); if (openSharedStory(item) || openFakeItemStory(message.id, stories)) { return; } else if (showInMediaView) { using namespace Media::View; + const auto timestamp = item ? ExtractVideoTimestamp(item) : 0; _window->openInMediaView(OpenRequest( this, document, item, message.topicRootId, false, - (item ? ExtractVideoTimestamp(item) : 0) * crl::time(1000))); + videoTimestampOverride.value_or(timestamp) * crl::time(1000))); return; } Data::ResolveDocument(this, document, item, message.topicRootId); diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 0bde193f0..e4a53672e 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -158,6 +158,7 @@ struct SectionShow { TextWithEntities highlightPart; int highlightPartOffsetHint = 0; + std::optional videoTimestamp; Way way = Way::Forward; anim::type animated = anim::type::normal; anim::activation activation = anim::activation::normal; @@ -505,7 +506,8 @@ public: not_null document, bool showInMediaView, MessageContext message, - const Data::StoriesContext *stories = nullptr); + const Data::StoriesContext *stories = nullptr, + std::optional videoTimestampOverride = {}); bool openSharedStory(HistoryItem *item); bool openFakeItemStory( FullMsgId fakeItemId, diff --git a/Telegram/SourceFiles/window/window_session_controller_link_info.h b/Telegram/SourceFiles/window/window_session_controller_link_info.h index bbee2385b..6a5744a93 100644 --- a/Telegram/SourceFiles/window/window_session_controller_link_info.h +++ b/Telegram/SourceFiles/window/window_session_controller_link_info.h @@ -40,6 +40,7 @@ struct PeerByLinkInfo { QString chatLinkSlug; MsgId messageId = ShowAtUnreadMsgId; StoryId storyId = 0; + std::optional videoTimestamp; QString text; RepliesByLinkInfo repliesInfo; ResolveType resolveType = ResolveType::Default;