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
QString(), // author
false, // hasLargeMedia
false, // photoIsVideoCover
0)) // pendingTill
, _theme(theme)
, _style(style)

View file

@ -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<TimeId>()),
.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

View file

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

View file

@ -3469,6 +3469,7 @@ not_null<WebPageData*> Session::processWebpage(
0,
QString(),
false,
false,
data.vdate().v
? data.vdate().v
: (base::unixtime::now() + kDefaultPendingTimeout));
@ -3496,6 +3497,7 @@ not_null<WebPageData*> Session::webpage(
0,
QString(),
false,
false,
TimeId(0));
}
@ -3516,6 +3518,7 @@ not_null<WebPageData*> 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<WebPageData*> 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);

View file

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

View file

@ -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;

View file

@ -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<Data::UniqueGift> 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;

View file

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

View file

@ -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 &params) {
_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 &params) {
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 &params) {
_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() {

View file

@ -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 &params);
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 &params = {});
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 &params = {});
void setChooseReportMessagesDetails(
Data::ReportInput reportInput,
Fn<void(std::vector<MsgId>)> callback);
@ -728,8 +718,7 @@ private:
bool _canSendTexts = false;
MsgId _showAtMsgId = ShowAtUnreadMsgId;
base::flat_set<MsgId> _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;

View file

@ -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);
}

View file

@ -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<DocumentData*> document,
bool showInMediaView,
MessageContext message,
const Data::StoriesContext *stories) {
const Data::StoriesContext *stories,
std::optional<TimeId> 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);

View file

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

View file

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