From e64e74bcd0586fb2827ea39b97ce36dad81c30dd Mon Sep 17 00:00:00 2001 From: Arne <78976058+4rneee@users.noreply.github.com> Date: Sun, 9 Aug 2026 11:12:30 +0000 Subject: [PATCH] feat: allow opening arbitrary urls with streamlink (#7162) Reported-by: ContinualDissapointment Reviewed-by: pajlada --- src/controllers/commands/builtin/Misc.cpp | 6 +++--- src/singletons/Toasts.cpp | 2 +- src/util/StreamLink.cpp | 25 +++++++++++++++-------- src/util/StreamLink.hpp | 8 ++++---- src/widgets/dialogs/QualityPopup.cpp | 10 ++++----- src/widgets/dialogs/QualityPopup.hpp | 6 +++--- src/widgets/splits/Split.cpp | 2 +- 7 files changed, 34 insertions(+), 25 deletions(-) diff --git a/src/controllers/commands/builtin/Misc.cpp b/src/controllers/commands/builtin/Misc.cpp index f8d6715c8..2b5b84470 100644 --- a/src/controllers/commands/builtin/Misc.cpp +++ b/src/controllers/commands/builtin/Misc.cpp @@ -340,15 +340,15 @@ QString streamlink(const CommandContext &ctx) else { ctx.channel->addSystemMessage( - "/streamlink [channel]. Open specified Twitch channel in " - "streamlink. If no channel argument is specified, open the " + "/streamlink [channel/URL]. Open specified Twitch channel or " + "URL in streamlink. If no argument is specified, open the " "current Twitch channel instead."); return ""; } } stripChannelName(target); - openStreamlinkForChannel(target); + openStreamlinkForChannelOrUrl(target); return ""; } diff --git a/src/singletons/Toasts.cpp b/src/singletons/Toasts.cpp index fd4773392..d62563ce3 100644 --- a/src/singletons/Toasts.cpp +++ b/src/singletons/Toasts.cpp @@ -80,7 +80,7 @@ void performReaction(const ToastReaction &reaction, const QString &channelName) QDesktopServices::openUrl(QUrl(TWITCH_PLAYER_URL.arg(channelName))); break; case ToastReaction::OpenInStreamlink: { - openStreamlinkForChannel(channelName); + openStreamlinkForChannelOrUrl(channelName); break; } case ToastReaction::OpenInCustomPlayer: { diff --git a/src/util/StreamLink.cpp b/src/util/StreamLink.cpp index f11cdd898..47ca30dd3 100644 --- a/src/util/StreamLink.cpp +++ b/src/util/StreamLink.cpp @@ -5,6 +5,7 @@ #include "util/StreamLink.hpp" #include "Application.hpp" +#include "common/LinkParser.hpp" #include "common/QLogging.hpp" #include "common/Version.hpp" #include "singletons/Settings.hpp" @@ -159,12 +160,12 @@ void getStreamQualities(const QString &channelURL, p->start(); } -void openStreamlink(const QString &channelURL, const QString &quality, +void openStreamlink(const QString &url, const QString &quality, QStringList extraArguments) { auto *proc = createStreamlinkProcess(); auto arguments = proc->arguments() - << std::move(extraArguments) << channelURL << quality; + << std::move(extraArguments) << url << quality; // Remove empty arguments before appending additional streamlink options // as the options might purposely contain empty arguments @@ -182,7 +183,7 @@ void openStreamlink(const QString &channelURL, const QString &quality, } } -void openStreamlinkForChannel(const QString &channel) +void openStreamlinkForChannelOrUrl(const QString &channelOrUrl) { static const QString INFO_TEMPLATE("Opening %1 in Streamlink ..."); @@ -197,18 +198,26 @@ void openStreamlinkForChannel(const QString &channel) if (currentSplit != nullptr) { currentSplit->getChannel()->addSystemMessage( - INFO_TEMPLATE.arg(channel)); + INFO_TEMPLATE.arg(channelOrUrl)); } } - QString channelURL = "twitch.tv/" + channel; + QString url; + if (linkparser::parse(channelOrUrl).has_value()) + { + url = channelOrUrl; + } + else + { + url = "twitch.tv/" + channelOrUrl; + } auto preferredQuality = getSettings()->preferredQuality.getEnum(); if (preferredQuality == StreamLinkPreferredQuality::Choose) { - getStreamQualities(channelURL, [=](QStringList qualityOptions) { - QualityPopup::showDialog(channelURL, qualityOptions); + getStreamQualities(url, [=](QStringList qualityOptions) { + QualityPopup::showDialog(url, qualityOptions); }); return; @@ -249,7 +258,7 @@ void openStreamlinkForChannel(const QString &channel) args << "--stream-sorting-excludes" << exclude; } - openStreamlink(channelURL, quality, args); + openStreamlink(url, quality, args); } } // namespace chatterino diff --git a/src/util/StreamLink.hpp b/src/util/StreamLink.hpp index d4441d46e..516c85fce 100644 --- a/src/util/StreamLink.hpp +++ b/src/util/StreamLink.hpp @@ -24,14 +24,14 @@ constexpr inline QStringView STREAMLINK_BINARY_NAME = u"streamlink.exe"; constexpr inline QStringView STREAMLINK_BINARY_NAME = u"streamlink"; #endif -// Open streamlink for given channel, quality and extra arguments +// Open streamlink for given url, quality and extra arguments // the "Additional arguments" are fetched and added at the beginning of the // streamlink call -void openStreamlink(const QString &channelURL, const QString &quality, +void openStreamlink(const QString &url, const QString &quality, QStringList extraArguments = QStringList()); -// Start opening streamlink for the given channel, reading settings like quality +// Start opening streamlink for the given channel or url, reading settings like quality // from settings and opening a quality dialog if the quality is "Choose" -void openStreamlinkForChannel(const QString &channel); +void openStreamlinkForChannelOrUrl(const QString &channelOrUrl); } // namespace chatterino diff --git a/src/widgets/dialogs/QualityPopup.cpp b/src/widgets/dialogs/QualityPopup.cpp index 41d4e2a94..26501803d 100644 --- a/src/widgets/dialogs/QualityPopup.cpp +++ b/src/widgets/dialogs/QualityPopup.cpp @@ -12,14 +12,14 @@ namespace chatterino { -QualityPopup::QualityPopup(const QString &channelURL, QStringList options) +QualityPopup::QualityPopup(const QString &url, QStringList options) : BasePopup( { BaseWindow::DisableLayoutSave, BaseWindow::BoundsCheckOnShow, }, static_cast(&(getApp()->getWindows()->getMainWindow()))) - , channelURL_(channelURL) + , url_(url) { this->ui_.selector = new QComboBox(this); this->ui_.vbox = new QVBoxLayout(this); @@ -39,9 +39,9 @@ QualityPopup::QualityPopup(const QString &channelURL, QStringList options) this->setLayout(this->ui_.vbox); } -void QualityPopup::showDialog(const QString &channelURL, QStringList options) +void QualityPopup::showDialog(const QString &url, QStringList options) { - QualityPopup *instance = new QualityPopup(channelURL, options); + QualityPopup *instance = new QualityPopup(url, options); instance->window()->setWindowTitle("Chatterino - select stream quality"); instance->setAttribute(Qt::WA_DeleteOnClose, true); @@ -69,7 +69,7 @@ void QualityPopup::okButtonClicked() { try { - openStreamlink(this->channelURL_, this->ui_.selector->currentText()); + openStreamlink(this->url_, this->ui_.selector->currentText()); } catch (const Exception &ex) { diff --git a/src/widgets/dialogs/QualityPopup.hpp b/src/widgets/dialogs/QualityPopup.hpp index 5916281a1..2f3909d33 100644 --- a/src/widgets/dialogs/QualityPopup.hpp +++ b/src/widgets/dialogs/QualityPopup.hpp @@ -15,8 +15,8 @@ namespace chatterino { class QualityPopup : public BasePopup { public: - QualityPopup(const QString &channelURL, QStringList options); - static void showDialog(const QString &channelURL, QStringList options); + QualityPopup(const QString &url, QStringList options); + static void showDialog(const QString &url, QStringList options); protected: void keyPressEvent(QKeyEvent *e) override; @@ -31,7 +31,7 @@ private: QDialogButtonBox *buttonBox; } ui_{}; - QString channelURL_; + QString url_; }; } // namespace chatterino diff --git a/src/widgets/splits/Split.cpp b/src/widgets/splits/Split.cpp index 51381fef3..46888bdba 100644 --- a/src/widgets/splits/Split.cpp +++ b/src/widgets/splits/Split.cpp @@ -814,7 +814,7 @@ void Split::openChannelInStreamlink(const QString channelName) { try { - openStreamlinkForChannel(channelName); + openStreamlinkForChannelOrUrl(channelName); } catch (const Exception &ex) {