From a9ab584cbb127c91d5a243949a0ee10b64601036 Mon Sep 17 00:00:00 2001 From: iProdigy <8106344+iProdigy@users.noreply.github.com> Date: Sun, 28 Dec 2025 05:26:48 -0600 Subject: [PATCH] feat: add duration and title options to clip command (#6669) Reviewed-by: pajlada --- CHANGELOG.md | 1 + mocks/include/mocks/Helix.hpp | 3 +- src/controllers/commands/builtin/Misc.cpp | 36 ++++++++++++++++++++++- src/providers/twitch/TwitchChannel.cpp | 5 ++-- src/providers/twitch/TwitchChannel.hpp | 2 +- src/providers/twitch/api/Helix.cpp | 13 +++++++- src/providers/twitch/api/Helix.hpp | 6 ++-- src/widgets/splits/Split.cpp | 2 +- src/widgets/splits/SplitHeader.cpp | 2 +- 9 files changed, 60 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29ef2953..f470bb4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Minor: Added broadcaster-only `/poll`, `/cancelpoll`, and `/endpoll` commands. (#6583, #6605) - Minor: Added broadcaster-only `/prediction`, `/cancelprediction`, `/lockprediction`, and `/completeprediction` commands. (#6583, #6612, #6632) - Minor: Added support for BetterTTV Pro subscriber badges. (#6625) +- Minor: Added title and duration options for `/clip` command. (#6669) - Minor: Added Markdown support to user notes. (#6490) - Bugfix: Moderation checks now include the lead moderator badge. (#6642) - Bugfix: Fixed lead moderator badges not being filtered by the `Channel` badge setting. (#6665) diff --git a/mocks/include/mocks/Helix.hpp b/mocks/include/mocks/Helix.hpp index e018ef43..a4fb01ac 100644 --- a/mocks/include/mocks/Helix.hpp +++ b/mocks/include/mocks/Helix.hpp @@ -79,7 +79,8 @@ public: (override)); MOCK_METHOD(void, createClip, - (QString channelId, ResultCallback successCallback, + (QString channelId, QString title, std::optional duration, + ResultCallback successCallback, std::function failureCallback, std::function finallyCallback), (override)); diff --git a/src/controllers/commands/builtin/Misc.cpp b/src/controllers/commands/builtin/Misc.cpp index a1adf5b3..4ed6ceb1 100644 --- a/src/controllers/commands/builtin/Misc.cpp +++ b/src/controllers/commands/builtin/Misc.cpp @@ -200,7 +200,41 @@ QString clip(const CommandContext &ctx) return ""; } - ctx.twitchChannel->createClip(); + QString title = ""; + std::optional duration = std::nullopt; + if (!ctx.words.empty()) + { + QCommandLineParser parser; + parser.setSingleDashWordOptionMode( + QCommandLineParser::ParseAsLongOptions); + parser.setOptionsAfterPositionalArgumentsMode( + QCommandLineParser::ParseAsPositionalArguments); + parser.addPositionalArgument("title", "The title of the clip"); + + QCommandLineOption durationOption( + {"d", "duration"}, "The duration of the clip", "duration"); + parser.addOptions({ + durationOption, + }); + parser.parse(ctx.words); + + title = parser.positionalArguments().join(' '); + + if (parser.isSet(durationOption)) + { + bool ok = false; + duration = parser.value(durationOption).toInt(&ok); + + if (!ok) + { + ctx.channel->addSystemMessage( + "Could not parse clip duration to an integer."); + return ""; + } + } + } + + ctx.twitchChannel->createClip(title, duration); return ""; } diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp index 830a88e7..bbcd669a 100644 --- a/src/providers/twitch/TwitchChannel.cpp +++ b/src/providers/twitch/TwitchChannel.cpp @@ -1956,7 +1956,8 @@ void TwitchChannel::setCheerEmoteSets( *this->cheerEmoteSets_.access() = std::move(emoteSets); } -void TwitchChannel::createClip() +void TwitchChannel::createClip(const QString &title, + const std::optional duration) { if (!this->isLive()) { @@ -1980,7 +1981,7 @@ void TwitchChannel::createClip() this->isClipCreationInProgress = true; getHelix()->createClip( - this->roomId(), + this->roomId(), title, duration, // successCallback [this](const HelixClip &clip) { MessageBuilder builder; diff --git a/src/providers/twitch/TwitchChannel.hpp b/src/providers/twitch/TwitchChannel.hpp index 03018490..de11896f 100644 --- a/src/providers/twitch/TwitchChannel.hpp +++ b/src/providers/twitch/TwitchChannel.hpp @@ -182,7 +182,7 @@ public: bool canReconnect() const override; void reconnect() override; QString getCurrentStreamID() const override; - void createClip(); + void createClip(const QString &title, std::optional duration); /// Delete the message with the specified ID as a moderator. /// diff --git a/src/providers/twitch/api/Helix.cpp b/src/providers/twitch/api/Helix.cpp index 0ce1b761..0f3eab10 100644 --- a/src/providers/twitch/api/Helix.cpp +++ b/src/providers/twitch/api/Helix.cpp @@ -354,13 +354,24 @@ void Helix::getGameById(QString gameId, } void Helix::createClip( - QString channelId, ResultCallback successCallback, + QString channelId, QString title, std::optional duration, + ResultCallback successCallback, std::function failureCallback, std::function finallyCallback) { QUrlQuery urlQuery; urlQuery.addQueryItem("broadcaster_id", channelId); + if (!title.isEmpty()) + { + urlQuery.addQueryItem("title", title); + } + + if (duration.has_value()) + { + urlQuery.addQueryItem("duration", QString::number(*duration)); + } + this->makePost("clips", urlQuery) .header("Content-Type", "application/json") .onSuccess([successCallback, failureCallback](auto result) { diff --git a/src/providers/twitch/api/Helix.hpp b/src/providers/twitch/api/Helix.hpp index be8949b3..e8f82337 100644 --- a/src/providers/twitch/api/Helix.hpp +++ b/src/providers/twitch/api/Helix.hpp @@ -1036,7 +1036,8 @@ public: // https://dev.twitch.tv/docs/api/reference#create-clip virtual void createClip( - QString channelId, ResultCallback successCallback, + QString channelId, QString title, std::optional duration, + ResultCallback successCallback, std::function failureCallback, std::function finallyCallback) = 0; @@ -1428,7 +1429,8 @@ public: // https://dev.twitch.tv/docs/api/reference#create-clip void createClip( - QString channelId, ResultCallback successCallback, + QString channelId, QString title, std::optional duration, + ResultCallback successCallback, std::function failureCallback, std::function finallyCallback) final; diff --git a/src/widgets/splits/Split.cpp b/src/widgets/splits/Split.cpp index 8123d0aa..8aebd9bc 100644 --- a/src/widgets/splits/Split.cpp +++ b/src/widgets/splits/Split.cpp @@ -483,7 +483,7 @@ void Split::addShortcuts() auto *twitchChannel = dynamic_cast(this->getChannel().get()); - twitchChannel->createClip(); + twitchChannel->createClip({}, {}); return ""; }}, {"reloadEmotes", diff --git a/src/widgets/splits/SplitHeader.cpp b/src/widgets/splits/SplitHeader.cpp index 6ed71880..a9e16c74 100644 --- a/src/widgets/splits/SplitHeader.cpp +++ b/src/widgets/splits/SplitHeader.cpp @@ -478,7 +478,7 @@ std::unique_ptr SplitHeader::createMainMenu() h->getDisplaySequence(HotkeyCategory::Split, "createClip"), this->split_, [twitchChannel] { - twitchChannel->createClip(); + twitchChannel->createClip({}, {}); }) ->setVisible(twitchChannel->isLive());