diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index f9b8a8c0..bd6befaa 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -790,8 +790,9 @@ AdvSceneSwitcher.action.twitch.type.commercial="Start commercial with duration" AdvSceneSwitcher.action.twitch.type.announcement="Send chat announcement" AdvSceneSwitcher.action.twitch.type.emoteOnlyEnable="Enable chat's emote-only mode" AdvSceneSwitcher.action.twitch.type.emoteOnlyDisable="Disable chat's emote-only mode" +AdvSceneSwitcher.action.twitch.type.raid="Raid channel" AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Cannot select category without selecting a Twitch account first!" -AdvSceneSwitcher.action.twitch.entry.line1="On{{account}}{{actions}}{{streamTitle}}{{category}}{{manualCategorySearch}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}" +AdvSceneSwitcher.action.twitch.entry.line1="On{{account}}{{actions}}{{streamTitle}}{{category}}{{manualCategorySearch}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}" AdvSceneSwitcher.action.twitch.tokenPermissionsInsufficient="Permissions of selected token are insufficient to perform selected action!" AdvSceneSwitcher.action.twitch.title.title="Enter title" AdvSceneSwitcher.action.twitch.marker.description="Describe marker" @@ -961,6 +962,9 @@ AdvSceneSwitcher.twitchToken.channel.startCommercial="Run commercials on a chann AdvSceneSwitcher.twitchToken.clips.edit="Create clips from channel's broadcasts." AdvSceneSwitcher.twitchToken.moderator.manageAnnouncements="Manage channel's chat announcements." AdvSceneSwitcher.twitchToken.moderator.manageChatSettings="Manage channel's chat settings, such as emote-only or slow mode." +AdvSceneSwitcher.twitchToken.channel.raid="Manage a channel raiding another channel." + +AdvSceneSwitcher.channel.open="Show channel" AdvSceneSwitcher.twitchCategories.fetchStart="Fetching stream categories ..." AdvSceneSwitcher.twitchCategories.fetchStatus="Got %1 stream categories." diff --git a/data/locale/fr-FR.ini b/data/locale/fr-FR.ini index 8f14cafa..8a70a619 100644 --- a/data/locale/fr-FR.ini +++ b/data/locale/fr-FR.ini @@ -739,15 +739,9 @@ AdvSceneSwitcher.action.sceneLock.entry="Sur{{scenes}}{{actions}}{{sources}}" AdvSceneSwitcher.action.twitch="Twitch" AdvSceneSwitcher.action.twitch.type.title="Définir le titre du flux" AdvSceneSwitcher.action.twitch.type.category="Définir la catégorie du flux" -AdvSceneSwitcher.action.twitch.type.marker="Create stream marker" -AdvSceneSwitcher.action.twitch.type.clip="Create stream clip" AdvSceneSwitcher.action.twitch.type.commercial="Démarrer une publicité d'une durée de" AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Impossible de sélectionner une catégorie sans avoir d'abord sélectionné un compte Twitch !" -AdvSceneSwitcher.action.twitch.entry.line1="Sur{{account}}{{actions}}{{streamTitle}}{{category}}{{manualCategorySearch}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementMessage}}{{announcementColor}}" -AdvSceneSwitcher.action.twitch.tokenPermissionsInsufficient="Permissions of selected token are insufficient to perform selected action!" -AdvSceneSwitcher.action.twitch.clip.hasDelay="Add a slight delay before capturing the clip" -AdvSceneSwitcher.action.twitch.marker.description="Describe marker" -AdvSceneSwitcher.action.twitch.title.title="Enter title" +AdvSceneSwitcher.action.twitch.entry.line1="Sur{{account}}{{actions}}{{streamTitle}}{{category}}{{manualCategorySearch}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementMessage}}{{announcementColor}}{{channel}}" ; Transition Tab AdvSceneSwitcher.transitionTab.title="Transition" diff --git a/src/macro-external/twitch/macro-action-twitch.cpp b/src/macro-external/twitch/macro-action-twitch.cpp index d3b0b55f..0b312c29 100644 --- a/src/macro-external/twitch/macro-action-twitch.cpp +++ b/src/macro-external/twitch/macro-action-twitch.cpp @@ -30,6 +30,8 @@ const static std::map actionTypes = { "AdvSceneSwitcher.action.twitch.type.emoteOnlyEnable"}, {MacroActionTwitch::Action::DISABLE_EMOTE_ONLY, "AdvSceneSwitcher.action.twitch.type.emoteOnlyDisable"}, + {MacroActionTwitch::Action::RAID, + "AdvSceneSwitcher.action.twitch.type.raid"}, }; const static std::map @@ -200,6 +202,20 @@ void MacroActionTwitch::SetChatEmoteOnlyMode( } } +void MacroActionTwitch::StartRaid(const std::shared_ptr &token) +{ + OBSDataAutoRelease data = obs_data_create(); + obs_data_set_string(data, "from_broadcaster_id", + token->GetUserID().c_str()); + obs_data_set_string(data, "to_broadcaster_id", + _channel.GetUserID(*token).c_str()); + auto result = SendPostRequest("https://api.twitch.tv", "/helix/raids", + *token, data.Get()); + if (result.status != 200) { + blog(LOG_INFO, "Failed to start raid! (%d)\n", result.status); + } +} + bool MacroActionTwitch::PerformAction() { auto token = _token.lock(); @@ -231,6 +247,8 @@ bool MacroActionTwitch::PerformAction() break; case MacroActionTwitch::Action::DISABLE_EMOTE_ONLY: SetChatEmoteOnlyMode(token, false); + case MacroActionTwitch::Action::RAID: + StartRaid(token); break; default: break; @@ -266,6 +284,7 @@ bool MacroActionTwitch::Save(obs_data_t *obj) const _announcementMessage.Save(obj, "announcementMessage"); obs_data_set_int(obj, "announcementColor", static_cast(_announcementColor)); + _channel.Save(obj); return true; } @@ -282,6 +301,7 @@ bool MacroActionTwitch::Load(obs_data_t *obj) _announcementMessage.Load(obj, "announcementMessage"); _announcementColor = static_cast( obs_data_get_int(obj, "announcementColor")); + _channel.Load(obj); return true; } @@ -302,6 +322,7 @@ bool MacroActionTwitch::ActionIsSupportedByToken() {Action::ENABLE_EMOTE_ONLY, {"moderator:manage:chat_settings"}}, {Action::DISABLE_EMOTE_ONLY, {"moderator:manage:chat_settings"}}, + {Action::RAID, {"channel:manage:raids"}}, }; auto token = _token.lock(); if (!token) { @@ -342,7 +363,8 @@ MacroActionTwitchEdit::MacroActionTwitchEdit( "AdvSceneSwitcher.action.twitch.clip.hasDelay"))), _duration(new DurationSelection(this, false, 0)), _announcementMessage(new VariableTextEdit(this)), - _announcementColor(new QComboBox(this)) + _announcementColor(new QComboBox(this)), + _channel(new TwitchChannelSelection(this)) { _streamTitle->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); @@ -382,6 +404,11 @@ MacroActionTwitchEdit::MacroActionTwitchEdit( SLOT(AnnouncementMessageChanged())); QWidget::connect(_announcementColor, SIGNAL(currentIndexChanged(int)), this, SLOT(AnnouncementColorChanged(int))); + QWidget::connect(_channel, + SIGNAL(ChannelChanged(const TwitchChannel &)), this, + SLOT(ChannelChanged(const TwitchChannel &))); + QWidget::connect(&_tokenPermissionCheckTimer, SIGNAL(timeout()), this, + SLOT(CheckTokenPermissions())); PlaceWidgets( obs_module_text("AdvSceneSwitcher.action.twitch.entry.line1"), @@ -394,7 +421,8 @@ MacroActionTwitchEdit::MacroActionTwitchEdit( {"{{markerDescription}}", _markerDescription}, {"{{clipHasDelay}}", _clipHasDelay}, {"{{duration}}", _duration}, - {"{{announcementColor}}", _announcementColor}}); + {"{{announcementColor}}", _announcementColor}, + {"{{channel}}", _channel}}); _layout->setContentsMargins(0, 0, 0, 0); auto mainLayout = new QVBoxLayout(); @@ -527,6 +555,8 @@ void MacroActionTwitchEdit::SetupWidgetVisibility() _announcementColor->setVisible(_entryData->_action == MacroActionTwitch::Action::ANNOUNCEMENT); + _channel->setVisible(_entryData->_action == + MacroActionTwitch::Action::RAID); if (_entryData->_action == MacroActionTwitch::Action::TITLE || _entryData->_action == MacroActionTwitch::Action::MARKER) { RemoveStretchIfPresent(_layout); @@ -541,6 +571,16 @@ void MacroActionTwitchEdit::SetupWidgetVisibility() updateGeometry(); } +void MacroActionTwitchEdit::ChannelChanged(const TwitchChannel &channel) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_channel = channel; +} + void MacroActionTwitchEdit::UpdateEntryData() { if (!_entryData) { @@ -560,6 +600,7 @@ void MacroActionTwitchEdit::UpdateEntryData() _announcementColor->setCurrentIndex( static_cast(_entryData->_announcementColor)); + _channel->SetChannel(_entryData->_channel); SetupWidgetVisibility(); } diff --git a/src/macro-external/twitch/macro-action-twitch.hpp b/src/macro-external/twitch/macro-action-twitch.hpp index 319c0f09..5b35f766 100644 --- a/src/macro-external/twitch/macro-action-twitch.hpp +++ b/src/macro-external/twitch/macro-action-twitch.hpp @@ -2,6 +2,7 @@ #include "macro-action-edit.hpp" #include "token.hpp" #include "category-selection.hpp" +#include "channel-selection.hpp" #include #include @@ -33,6 +34,7 @@ public: ANNOUNCEMENT, ENABLE_EMOTE_ONLY, DISABLE_EMOTE_ONLY, + RAID, }; enum class AnnouncementColor { @@ -55,6 +57,7 @@ public: StringVariable _announcementMessage = obs_module_text( "AdvSceneSwitcher.action.twitch.announcement.message"); AnnouncementColor _announcementColor = AnnouncementColor::PRIMARY; + TwitchChannel _channel; private: void SetStreamTitle(const std::shared_ptr &) const; @@ -65,6 +68,7 @@ private: void SendChatAnnouncement(const std::shared_ptr &) const; void SetChatEmoteOnlyMode(const std::shared_ptr &, bool enable) const; + void StartRaid(const std::shared_ptr &); static bool _registered; static const std::string id; @@ -97,6 +101,7 @@ private slots: void DurationChanged(const Duration &); void AnnouncementMessageChanged(); void AnnouncementColorChanged(int index); + void ChannelChanged(const TwitchChannel &); signals: void HeaderInfoChanged(const QString &); @@ -121,6 +126,7 @@ private: DurationSelection *_duration; VariableTextEdit *_announcementMessage; QComboBox *_announcementColor; + TwitchChannelSelection *_channel; bool _loading = true; }; diff --git a/src/macro-external/twitch/token.cpp b/src/macro-external/twitch/token.cpp index 234f1502..c50d474b 100644 --- a/src/macro-external/twitch/token.cpp +++ b/src/macro-external/twitch/token.cpp @@ -11,15 +11,6 @@ namespace advss { static std::deque> twitchTokens; const std::unordered_map TokenOption::apiIdToLocale{ - // Add necessary token permissions here - /* - {"analytics:read:extensions", - "AdvSceneSwitcher.twitchToken.analytics.readExtensions"}, - {"analytics:read:games", - "AdvSceneSwitcher.twitchToken.analytics.readGames"}, - {"bits:read", "AdvSceneSwitcher.twitchToken.bits.read"}, - */ - {"channel:manage:broadcast", "AdvSceneSwitcher.twitchToken.channel.manageBroadcast"}, {"clips:edit", "AdvSceneSwitcher.twitchToken.clips.edit"}, @@ -28,7 +19,9 @@ const std::unordered_map TokenOption::apiIdToLocale{ {"moderator:manage:announcements", "AdvSceneSwitcher.twitchToken.moderator.manageAnnouncements"}, {"moderator:manage:chat_settings", - "AdvSceneSwitcher.twitchToken.moderator.manageChatSettings"}}; + "AdvSceneSwitcher.twitchToken.moderator.manageChatSettings"}, + {"channel:manage:raids", "AdvSceneSwitcher.twitchToken.channel.raid"}, +}; static void saveConnections(obs_data_t *obj); static void loadConnections(obs_data_t *obj);