diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 128c49798..81179a028 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -781,6 +781,7 @@ AdvSceneSwitcher.action.twitch.type.category="Set stream category" AdvSceneSwitcher.action.twitch.type.commercial="Start commercial with duration" AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Cannot select category without selecting a Twitch account first!" AdvSceneSwitcher.action.twitch.entry="On{{account}}{{actions}}{{text}}{{category}}{{manualCategorySearch}}{{duration}}" +AdvSceneSwitcher.action.twitch.tokenPermissionsInsufficient="Permissions of selected token are insufficient to perform selected action!" ; Hotkey AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher" diff --git a/src/macro-external/twitch/macro-action-twitch.cpp b/src/macro-external/twitch/macro-action-twitch.cpp index fd29241f4..0dcbf30f1 100644 --- a/src/macro-external/twitch/macro-action-twitch.cpp +++ b/src/macro-external/twitch/macro-action-twitch.cpp @@ -153,6 +153,22 @@ std::string MacroActionTwitch::GetShortDesc() const return GetWeakTwitchTokenName(_token); } +bool MacroActionTwitch::ActionIsSupportedByToken() +{ + static const std::unordered_map requiredOption = { + {Action::TITLE, {"channel:manage:broadcast"}}, + {Action::CATEGORY, {"channel:manage:broadcast"}}, + {Action::COMMERCIAL, {"channel:edit:commercial"}}, + }; + auto token = _token.lock(); + if (!token) { + return false; + } + auto option = requiredOption.find(_action); + assert(option != requiredOption.end()); + return token->OptionIsEnabled(option->second); +} + static inline void populateActionSelection(QComboBox *list) { for (const auto &[_, name] : actionTypes) { @@ -169,7 +185,9 @@ MacroActionTwitchEdit::MacroActionTwitchEdit( _category(new TwitchCategorySelection(this)), _manualCategorySearch(new TwitchCategorySearchButton()), _duration(new DurationSelection(this, false, 0)), - _layout(new QHBoxLayout()) + _layout(new QHBoxLayout()), + _tokenPermissionWarning(new QLabel(obs_module_text( + "AdvSceneSwitcher.action.twitch.tokenPermissionsInsufficient"))) { _text->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); @@ -190,6 +208,8 @@ MacroActionTwitchEdit::MacroActionTwitchEdit( SLOT(CategoreyChanged(const TwitchCategory &))); QObject::connect(_duration, SIGNAL(DurationChanged(const Duration &)), this, SLOT(DurationChanged(const Duration &))); + QWidget::connect(&_tokenPermissionCheckTimer, SIGNAL(timeout()), this, + SLOT(CheckTokenPermissions())); PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.twitch.entry"), _layout, @@ -199,7 +219,14 @@ MacroActionTwitchEdit::MacroActionTwitchEdit( {"{{category}}", _category}, {"{{manualCategorySearch}}", _manualCategorySearch}, {"{{duration}}", _duration}}); - setLayout(_layout); + _layout->setContentsMargins(0, 0, 0, 0); + + auto mainLayout = new QVBoxLayout(); + mainLayout->addLayout(_layout); + mainLayout->addWidget(_tokenPermissionWarning); + setLayout(mainLayout); + + _tokenPermissionCheckTimer.start(1000); _entryData = entryData; UpdateEntryData(); @@ -250,6 +277,14 @@ void MacroActionTwitchEdit::DurationChanged(const Duration &duration) _entryData->_duration = duration; } +void MacroActionTwitchEdit::CheckTokenPermissions() +{ + _tokenPermissionWarning->setVisible( + _entryData && !_entryData->ActionIsSupportedByToken()); + adjustSize(); + updateGeometry(); +} + void MacroActionTwitchEdit::SetupWidgetVisibility() { _text->setVisible(_entryData->_action == @@ -266,6 +301,9 @@ void MacroActionTwitchEdit::SetupWidgetVisibility() AddStretchIfNecessary(_layout); } + _tokenPermissionWarning->setVisible( + !_entryData->ActionIsSupportedByToken()); + adjustSize(); updateGeometry(); } diff --git a/src/macro-external/twitch/macro-action-twitch.hpp b/src/macro-external/twitch/macro-action-twitch.hpp index 3aedca40e..f767e8b00 100644 --- a/src/macro-external/twitch/macro-action-twitch.hpp +++ b/src/macro-external/twitch/macro-action-twitch.hpp @@ -21,6 +21,7 @@ class MacroActionTwitch : public MacroAction { { return std::make_shared(m); } + bool ActionIsSupportedByToken(); enum class Action { TITLE, @@ -65,6 +66,7 @@ private slots: void TextChanged(); void CategoreyChanged(const TwitchCategory &); void DurationChanged(const Duration &); + void CheckTokenPermissions(); signals: void HeaderInfoChanged(const QString &); @@ -82,6 +84,8 @@ private slots: TwitchCategorySearchButton *_manualCategorySearch; DurationSelection *_duration; QHBoxLayout *_layout; + QLabel *_tokenPermissionWarning; + QTimer _tokenPermissionCheckTimer; bool _loading = true; }; diff --git a/src/macro-external/twitch/token.cpp b/src/macro-external/twitch/token.cpp index 26437b62a..ea6f10f54 100644 --- a/src/macro-external/twitch/token.cpp +++ b/src/macro-external/twitch/token.cpp @@ -80,7 +80,8 @@ std::string TokenOption::GetLocale() const return apiIdToLocale.at(apiId); } -const std::unordered_map &TokenOption::GetTokenMap() +const std::unordered_map & +TokenOption::GetTokenOptionMap() { return apiIdToLocale; } @@ -329,8 +330,10 @@ TwitchTokenSettingsDialog::TwitchTokenSettingsDialog( row = 0; auto optionsBox = new QGroupBox( obs_module_text("AdvSceneSwitcher.twitchToken.permissions")); - for (const auto &[id, _] : TokenOption::GetTokenMap()) { + for (const auto &[id, _] : TokenOption::GetTokenOptionMap()) { auto checkBox = addOption({id}, settings, optionsGrid, row); + QWidget::connect(checkBox, SIGNAL(stateChanged(int)), this, + SLOT(TokenOptionChanged(int))); _optionWidgets[id] = checkBox; } MinimizeSizeOfColumn(optionsGrid, 0); @@ -359,7 +362,7 @@ TwitchTokenSettingsDialog::TwitchTokenSettingsDialog( } HideToken(); - if (_name->text() == "") { + if (_name->text().isEmpty()) { PulseWidget(_requestToken, Qt::green, QColor(0, 0, 0, 0), true); } @@ -378,6 +381,17 @@ void TwitchTokenSettingsDialog::HideToken() _currentTokenValue->setEchoMode(QLineEdit::PasswordEchoOnEdit); } +void TwitchTokenSettingsDialog::TokenOptionChanged(int) +{ + if (!_name->text().isEmpty()) { + PulseWidget(_requestToken, Qt::green, QColor(0, 0, 0, 0), true); + } + _name->setText(""); + QMetaObject::invokeMethod(this, "NameChanged", + Q_ARG(const QString &, "")); + _currentTokenValue->setText(""); +} + static std::string generateStateString() { const char *chars = diff --git a/src/macro-external/twitch/token.hpp b/src/macro-external/twitch/token.hpp index f9014edf9..f6068a4b2 100644 --- a/src/macro-external/twitch/token.hpp +++ b/src/macro-external/twitch/token.hpp @@ -18,7 +18,7 @@ class TokenOption { std::string GetLocale() const; static const std::unordered_map & - GetTokenMap(); + GetTokenOptionMap(); bool operator<(const TokenOption &other) const; std::string apiId = ""; @@ -90,6 +90,7 @@ class TwitchTokenSettingsDialog : public ItemSettingsDialog { private slots: void ShowToken(); void HideToken(); + void TokenOptionChanged(int); void RequestToken(); void GotToken(const std::optional &);