From 985324ac127436a6afcb4f42ca24901534efb435 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 29 Jul 2025 13:35:48 +0400 Subject: [PATCH] Support story album links. --- Telegram/Resources/langs/lang.strings | 2 + .../SourceFiles/core/local_url_handlers.cpp | 6 ++ Telegram/SourceFiles/data/data_stories.cpp | 4 +- Telegram/SourceFiles/data/data_web_page.cpp | 2 + Telegram/SourceFiles/data/data_web_page.h | 1 + .../view/media/history_view_web_page.cpp | 2 + .../stories/info_stories_inner_widget.cpp | 63 ++++++++++++++----- .../info/stories/info_stories_inner_widget.h | 1 + .../window/window_session_controller.cpp | 8 +++ .../window_session_controller_link_info.h | 1 + 10 files changed, 71 insertions(+), 19 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0ab5fe48b4..6e240e929e 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -6255,6 +6255,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_view_button_emojipack" = "View emoji"; "lng_view_button_collectible" = "View collectible"; "lng_view_button_call" = "Join call"; +"lng_view_button_storyalbum" = "View Album"; "lng_sponsored_hide_ads" = "Hide"; "lng_sponsored_title" = "What are sponsored messages?"; @@ -6466,6 +6467,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stories_album_empty_text" = "Add some of your stories to this album."; "lng_stories_album_all" = "All Stories"; "lng_stories_album_add_title" = "Add Stories"; +"lng_stories_album_share" = "Share Album"; "lng_stories_album_edit" = "Edit Name"; "lng_stories_album_limit_title" = "Limit Reached"; "lng_stories_album_limit_text" = "Please remove one of the existing albums to add a new one."; diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 90ee230586..a8b81f39c9 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -633,6 +633,8 @@ bool ResolveUsernameOrPhone( } const auto storyParam = params.value(u"story"_q); const auto storyId = storyParam.toInt(); + const auto storyAlbumParam = params.value(u"album"_q); + const auto storyAlbumId = storyAlbumParam.toInt(); const auto appname = webChannelPreviewLink ? QString() : appnameParam; const auto commentParam = params.value(u"comment"_q); const auto commentId = commentParam.toInt(); @@ -659,6 +661,7 @@ bool ResolveUsernameOrPhone( .phone = phone, .messageId = post, .storyId = storyId, + .storyAlbumId = storyAlbumId, .videoTimestamp = (!videot.isEmpty() ? ParseVideoTimestamp(videot) : std::optional()), @@ -1827,6 +1830,7 @@ QString TryConvertUrlToLocal(QString url) { "/[a-zA-Z0-9\\.\\_\\-]+/?(\\?|$)|" "/\\d+/?(\\?|$)|" "/s/\\d+/?(\\?|$)|" + "/a/\\d+/?(\\?|$)|" "/\\d+/\\d+/?(\\?|$)" ")"_q, query, matchOptions)) { const auto domain = usernameMatch->captured(1); @@ -1849,6 +1853,8 @@ QString TryConvertUrlToLocal(QString url) { added = u"&post="_q + postMatch->captured(1); } else if (const auto storyMatch = regex_match(u"^/s/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) { added = u"&story="_q + storyMatch->captured(1); + } else if (const auto storyMatch = regex_match(u"^/a/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) { + added = u"&album="_q + storyMatch->captured(1); } else if (const auto appNameMatch = regex_match(u"^/([a-zA-Z0-9\\.\\_\\-]+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) { added = u"&appname="_q + appNameMatch->captured(1); } diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index 308a9cfb43..c792aa0826 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -1862,9 +1862,9 @@ void Stories::loadAlbums(not_null peer, Albums &albums) { peer->input, MTP_long(albums.hash) )).done([=](const MTPstories_Albums &result) { + auto &albums = _albums[peer->id]; + albums.requestId = 0; result.match([&](const MTPDstories_albums &data) { - auto &albums = _albums[peer->id]; - albums.requestId = 0; albums.hash = data.vhash().v; auto parsed = std::vector(); const auto &list = data.valbums().v; diff --git a/Telegram/SourceFiles/data/data_web_page.cpp b/Telegram/SourceFiles/data/data_web_page.cpp index 508a45d7c0..6293cf2616 100644 --- a/Telegram/SourceFiles/data/data_web_page.cpp +++ b/Telegram/SourceFiles/data/data_web_page.cpp @@ -173,6 +173,8 @@ WebPageType ParseWebPageType( return WebPageType::Giftcode; } else if (type == u"telegram_stickerset"_q) { return WebPageType::StickerSet; + } else if (type == u"telegram_story_album"_q) { + return WebPageType::StoryAlbum; } else if (hasIV) { return WebPageType::ArticleWithIV; } else { diff --git a/Telegram/SourceFiles/data/data_web_page.h b/Telegram/SourceFiles/data/data_web_page.h index e6c004e64e..4fd2d4ab73 100644 --- a/Telegram/SourceFiles/data/data_web_page.h +++ b/Telegram/SourceFiles/data/data_web_page.h @@ -49,6 +49,7 @@ enum class WebPageType : uint8 { Theme, Story, StickerSet, + StoryAlbum, Article, ArticleWithIV, diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index be8ef86733..5b64cb2d01 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -232,6 +232,8 @@ constexpr auto kSponsoredUserpicLines = 2; ? tr::lng_view_button_emojipack(tr::now) : (type == WebPageType::StickerSet) ? tr::lng_view_button_stickerset(tr::now) + : (type == WebPageType::StoryAlbum) + ? tr::lng_view_button_storyalbum(tr::now) : QString()); if (page->iv) { const auto manager = &page->owner().customEmojiManager(); diff --git a/Telegram/SourceFiles/info/stories/info_stories_inner_widget.cpp b/Telegram/SourceFiles/info/stories/info_stories_inner_widget.cpp index c47a5968fb..43d38c2e02 100644 --- a/Telegram/SourceFiles/info/stories/info_stories_inner_widget.cpp +++ b/Telegram/SourceFiles/info/stories/info_stories_inner_widget.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/stories/info_stories_inner_widget.h" #include "apiwrap.h" +#include "boxes/share_box.h" #include "data/data_peer.h" #include "data/data_session.h" #include "data/data_stories.h" @@ -210,6 +211,16 @@ InnerWidget::InnerWidget( _albumId.value( ) | rpl::start_with_next([=](int albumId) { + if (_albumsTabs + && (albumId == Data::kStoriesAlbumIdSaved + || ranges::contains( + _albums, + albumId, + &Data::StoryAlbum::id))) { + _albumsTabs->setActiveTab((albumId == Data::kStoriesAlbumIdSaved) + ? u"all"_q + : QString::number(albumId)); + } _controller->replaceKey(Key(Tag(_peer, albumId, _addingToAlbumId))); reload(); }, lifetime()); @@ -699,16 +710,13 @@ void InnerWidget::refreshAlbumsTabs() { StoryId(), added)); } else { - _albumsTabs->setActiveTab(id); _albumIdChanges.fire((id == u"all"_q) ? 0 : id.toInt()); } }, _albumsTabs->lifetime()); _albumsTabs->contextMenuRequests( ) | rpl::start_with_next([=](const QString &id) { - if (id == u"add"_q - || id == u"all"_q - || !_peer->canEditStories()) { + if (id == u"add"_q || id == u"all"_q) { return; } showMenuForAlbum(id.toInt()); @@ -723,24 +731,39 @@ void InnerWidget::refreshAlbumsTabs() { } void InnerWidget::showMenuForAlbum(int id) { + Expects(id > 0); + if (_menu || _addingToAlbumId) { return; } _menu = base::make_unique_q(this, st::popupMenuWithIcons); const auto addAction = Ui::Menu::CreateAddActionCallback(_menu); - addAction(tr::lng_stories_album_add_title(tr::now), [=] { - editAlbumStories(id); - }, &st::menuIconStoriesSave); - addAction(tr::lng_stories_album_edit(tr::now), [=] { - editAlbumName(id); - }, &st::menuIconEdit); - addAction({ - .text = tr::lng_stories_album_delete(tr::now), - .handler = [=] { confirmDeleteAlbum(id); }, - .icon = &st::menuIconDeleteAttention, - .isAttention = true, - }); - _menu->popup(QCursor::pos()); + if (_peer->canEditStories()) { + addAction(tr::lng_stories_album_add_title(tr::now), [=] { + editAlbumStories(id); + }, &st::menuIconStoriesSave); + } + if (const auto username = _peer->username(); !username.isEmpty()) { + addAction(tr::lng_stories_album_share(tr::now), [=] { + shareAlbumLink(username, id); + }, &st::menuIconShare); + } + if (_peer->canEditStories()) { + addAction(tr::lng_stories_album_edit(tr::now), [=] { + editAlbumName(id); + }, &st::menuIconEdit); + addAction({ + .text = tr::lng_stories_album_delete(tr::now), + .handler = [=] { confirmDeleteAlbum(id); }, + .icon = &st::menuIconDeleteAttention, + .isAttention = true, + }); + } + if (_menu->empty()) { + _menu = nullptr; + } else { + _menu->popup(QCursor::pos()); + } } rpl::producer InnerWidget::albumIdChanges() const { @@ -771,6 +794,12 @@ void InnerWidget::editAlbumStories(int id) { _controller->uiShow()->show(std::move(box)); } +void InnerWidget::shareAlbumLink(const QString &username, int id) { + const auto url = _controller->session().createInternalLinkFull( + username + u"/a/"_q + QString::number(id)); + FastShareLink(_controller->parentController(), url); +} + void InnerWidget::editAlbumName(int id) { const auto done = [=](QString name) { albumRenamed(id, name); diff --git a/Telegram/SourceFiles/info/stories/info_stories_inner_widget.h b/Telegram/SourceFiles/info/stories/info_stories_inner_widget.h index b3657bf5ac..838c5044d9 100644 --- a/Telegram/SourceFiles/info/stories/info_stories_inner_widget.h +++ b/Telegram/SourceFiles/info/stories/info_stories_inner_widget.h @@ -69,6 +69,7 @@ public: void reload(); void editAlbumStories(int id); + void shareAlbumLink(const QString &username, int id); void editAlbumName(int id); void confirmDeleteAlbum(int id); void albumAdded(Data::StoryAlbum result); diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 76f4ed2732..53a155529e 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_filters_menu.h" #include "window/window_separate_id.h" #include "info/channel_statistics/earn/info_channel_earn_list.h" +#include "info/stories/info_stories_widget.h" #include "info/info_memento.h" #include "info/info_controller.h" #include "inline_bots/bot_attach_web_view.h" @@ -636,6 +637,11 @@ void SessionNavigation::showPeerByLinkResolved( } } else if (info.storyId) { const auto storyId = FullStoryId{ peer->id, info.storyId }; + const auto context = (info.storyAlbumId > 0) + ? Data::StoriesContext{ Data::StoriesContextAlbum{ + info.storyAlbumId, + } } + : Data::StoriesContext{ Data::StoriesContextSingle() }; peer->owner().stories().resolve(storyId, crl::guard(this, [=] { if (peer->owner().stories().lookup(storyId)) { parentController()->openPeerStory( @@ -646,6 +652,8 @@ void SessionNavigation::showPeerByLinkResolved( showToast(tr::lng_stories_link_invalid(tr::now)); } })); + } else if (info.storyAlbumId > 0) { + showSection(Info::Stories::Make(peer, info.storyAlbumId)); } else if (bot && resolveType == ResolveType::BotApp) { const auto itemId = info.clickFromMessageId; const auto item = _session->data().message(itemId); diff --git a/Telegram/SourceFiles/window/window_session_controller_link_info.h b/Telegram/SourceFiles/window/window_session_controller_link_info.h index 6a5744a93e..7421aac748 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; + int storyAlbumId = 0; std::optional videoTimestamp; QString text; RepliesByLinkInfo repliesInfo;