diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 8c152e56..d3c2761b 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -1607,7 +1607,10 @@ AdvSceneSwitcher.twitchToken.moderator.chat.settings.manage="Manage channel's ch AdvSceneSwitcher.twitchToken.user.whispers.manage="Manage user's whispers." AdvSceneSwitcher.twitchToken.chat.read="View live stream chat messages." AdvSceneSwitcher.twitchToken.chat.edit="Send live stream chat messages." -AdvSceneSwitcher.twitchToken.validateTimestamps="Validate timestamps of received Twitch event messages. (Recommended)" +AdvSceneSwitcher.twitchToken.validateTimestamps="Validate timestamps of received Twitch event messages." +AdvSceneSwitcher.twitchToken.warnIfInvalid="Display warning if the token expired or is invalid." +AdvSceneSwitcher.twitchToken.warnIfInvalid.doNotShowAgain="Don't show again for this connection" +AdvSceneSwitcher.twitchToken.warnIfInvalid.message="The Twitch connection \"%1\" is invalid.\nOpen connection settings or ignore?" AdvSceneSwitcher.twitch.selection.channel.open="Open channel" AdvSceneSwitcher.twitch.selection.channel.open.tooltip.details="Open channel in external application handling the HTTPS protocol." diff --git a/plugins/twitch/token.cpp b/plugins/twitch/token.cpp index 1af3c386..912c1713 100644 --- a/plugins/twitch/token.cpp +++ b/plugins/twitch/token.cpp @@ -163,7 +163,8 @@ TwitchToken::TwitchToken(const TwitchToken &other) _userID(other._userID), _tokenOptions(other._tokenOptions), _eventSub(), - _validateEventSubTimestamps(other._validateEventSubTimestamps) + _validateEventSubTimestamps(other._validateEventSubTimestamps), + _warnIfInvalid(other._warnIfInvalid) { } @@ -174,6 +175,7 @@ TwitchToken &TwitchToken::operator=(const TwitchToken &other) _userID = other._userID; _tokenOptions = other._tokenOptions; _validateEventSubTimestamps = other._validateEventSubTimestamps; + _warnIfInvalid = other._warnIfInvalid; return *this; } @@ -185,6 +187,9 @@ void TwitchToken::Load(obs_data_t *obj) obs_data_set_default_bool(obj, "validateEventSubTimestamps", true); _validateEventSubTimestamps = obs_data_get_bool(obj, "validateEventSubTimestamps"); + _warnIfInvalid = obs_data_has_user_value(obj, "warnIfInvalid") + ? obs_data_get_bool(obj, "warnIfInvalid") + : true; _tokenOptions.clear(); OBSDataArrayAutoRelease options = obs_data_get_array(obj, "options"); size_t count = obs_data_array_count(options); @@ -203,6 +208,7 @@ void TwitchToken::Save(obs_data_t *obj) const obs_data_set_string(obj, "userID", _userID.c_str()); obs_data_set_bool(obj, "validateEventSubTimestamps", _validateEventSubTimestamps); + obs_data_set_bool(obj, "warnIfInvalid", _warnIfInvalid); OBSDataArrayAutoRelease options = obs_data_array_create(); for (auto &option : _tokenOptions) { OBSDataAutoRelease arrayObj = obs_data_create(); @@ -477,7 +483,9 @@ TwitchTokenSettingsDialog::TwitchTokenSettingsDialog( _tokenStatus(new QLabel()), _generalSettingsGrid(new QGridLayout()), _validateTimestamps(new QCheckBox(obs_module_text( - "AdvSceneSwitcher.twitchToken.validateTimestamps"))) + "AdvSceneSwitcher.twitchToken.validateTimestamps"))), + _warnIfInvalid(new QCheckBox(obs_module_text( + "AdvSceneSwitcher.twitchToken.warnIfInvalid"))) { _showToken->setMaximumWidth(22); _showToken->setFlat(true); @@ -544,6 +552,7 @@ TwitchTokenSettingsDialog::TwitchTokenSettingsDialog( layout->addLayout(_generalSettingsGrid); layout->addWidget(optionsBox); layout->addWidget(_validateTimestamps); + layout->addWidget(_warnIfInvalid); layout->setContentsMargins(0, 0, 0, 0); scrollArea->setWidget(contentWidget); @@ -569,6 +578,7 @@ TwitchTokenSettingsDialog::TwitchTokenSettingsDialog( #ifndef VERIFY_TIMESTAMPS _validateTimestamps->hide(); #endif + _warnIfInvalid->setChecked(settings._warnIfInvalid); _currentToken = settings; @@ -777,6 +787,8 @@ bool TwitchTokenSettingsDialog::AskForSettings(QWidget *parent, settings._tokenOptions = dialog.GetEnabledOptions(); settings._validateEventSubTimestamps = dialog._validateTimestamps->isChecked(); + settings._warnIfInvalid = dialog._warnIfInvalid->isChecked(); + if (settings._eventSub) { settings._eventSub->EnableTimestampValidation( settings._validateEventSubTimestamps); diff --git a/plugins/twitch/token.hpp b/plugins/twitch/token.hpp index b3763af4..f2e02cda 100644 --- a/plugins/twitch/token.hpp +++ b/plugins/twitch/token.hpp @@ -55,6 +55,8 @@ public: std::shared_ptr GetEventSub(); bool ValidateTimestamps() const { return _validateEventSubTimestamps; } bool IsValid(bool forceUpdate = false) const; + bool WarnIfInvalid() const { return _warnIfInvalid; } + void SetWarnIfInvalid(bool value) { _warnIfInvalid = value; } size_t PermissionCount() const { return _tokenOptions.size(); } private: @@ -67,6 +69,7 @@ private: std::set _tokenOptions = TokenOption::GetAllTokenOptions(); std::shared_ptr _eventSub; bool _validateEventSubTimestamps = false; + bool _warnIfInvalid = true; static bool _setup; @@ -131,6 +134,7 @@ private: TwitchToken _currentToken; std::unordered_map _optionWidgets; QCheckBox *_validateTimestamps; + QCheckBox *_warnIfInvalid; QTimer _validationTimer; }; diff --git a/plugins/twitch/twitch-tab.cpp b/plugins/twitch/twitch-tab.cpp index 771c0772..d7cfcc85 100644 --- a/plugins/twitch/twitch-tab.cpp +++ b/plugins/twitch/twitch-tab.cpp @@ -1,24 +1,47 @@ #include "twitch-tab.hpp" #include "obs-module-helper.hpp" +#include "plugin-state-helpers.hpp" #include "sync-helpers.hpp" #include "tab-helpers.hpp" #include "token.hpp" #include "ui-helpers.hpp" +#include #include namespace advss { -static bool registerTab(); +static bool setup(); static void setupTab(QTabWidget *); -static bool registerTabDone = registerTab(); +static bool setupDone = setup(); static TwitchConnectionsTable *tabWidget = nullptr; -static bool registerTab() +static QStringList getInvalidTokens(); + +static bool setup() { AddSetupTabCallback("twitchConnectionTab", TwitchConnectionsTable::Create, setupTab); + + static const auto showInvalidWarnings = []() { + const auto invalidTokens = getInvalidTokens(); + for (const auto &token : invalidTokens) { + QueueUITask( + [](void *tokenPtr) { + auto tokenName = static_cast( + tokenPtr); + InvalidTokenDialog::ShowWarning( + *tokenName); + }, + (void *)&token); + } + }; + + AddLoadStep([](obs_data_t *) { + AddPostLoadStep([]() { showInvalidWarnings(); }); + }); + return true; } @@ -188,6 +211,25 @@ static void updateConnectionStatus(QTableWidget *table) } } +static QStringList getInvalidTokens() +{ + QStringList tokens; + for (const auto &t : GetTwitchTokens()) { + if (!t) { + continue; + } + + auto token = std::static_pointer_cast(t); + if (!token->WarnIfInvalid() || token->IsValid(true)) { + continue; + } + + tokens << QString::fromStdString(token->GetName()); + } + + return tokens; +} + static void setupTab(QTabWidget *tab) { if (GetTwitchTokens().empty()) { @@ -222,6 +264,63 @@ static void setupTab(QTabWidget *tab) updateConnectionStatus(tabWidget->Table()); } }); + + const auto invalidTokens = getInvalidTokens(); + for (const auto &token : invalidTokens) { + // Constructing the warning dialog in the constructor of the settings + // window might lead to a crash, so wait for the settings window + // constructor to complete + QTimer::singleShot(0, tab, [token]() { + InvalidTokenDialog::ShowWarning(token); + }); + } +} + +void InvalidTokenDialog::ShowWarning(const QString &tokenName) +{ + auto weakToken = GetWeakTwitchTokenByQString(tokenName); + auto token = weakToken.lock(); + if (!token) { + return; + } + + auto dialog = new InvalidTokenDialog(tokenName); + dialog->setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle")); + const bool ignore = dialog->exec() != DialogCode::Accepted; + token->SetWarnIfInvalid(!dialog->_doNotShowAgain->isChecked()); + dialog->deleteLater(); + + if (ignore) { + return; + } + + TwitchTokenSettingsDialog::AskForSettings(GetSettingsWindow(), + *token.get()); +} + +InvalidTokenDialog::InvalidTokenDialog(const QString &name) + : QDialog(GetSettingsWindow()), + _doNotShowAgain(new QCheckBox(obs_module_text( + "AdvSceneSwitcher.twitchToken.warnIfInvalid.doNotShowAgain"))) +{ + auto buttons = new QDialogButtonBox(QDialogButtonBox::Ignore | + QDialogButtonBox::Open); + + connect(buttons->button(QDialogButtonBox::Ignore), + &QPushButton::clicked, this, &QDialog::reject); + connect(buttons->button(QDialogButtonBox::Open), &QPushButton::clicked, + this, &QDialog::accept); + buttons->setCenterButtons(true); + + const QString format(obs_module_text( + "AdvSceneSwitcher.twitchToken.warnIfInvalid.message")); + const QString message = format.arg(name); + + auto layout = new QVBoxLayout; + layout->addWidget(new QLabel(message)); + layout->addWidget(_doNotShowAgain); + layout->addWidget(buttons); + setLayout(layout); } } // namespace advss diff --git a/plugins/twitch/twitch-tab.hpp b/plugins/twitch/twitch-tab.hpp index 706b109f..775b9300 100644 --- a/plugins/twitch/twitch-tab.hpp +++ b/plugins/twitch/twitch-tab.hpp @@ -1,6 +1,9 @@ #pragma once #include "resource-table.hpp" +#include +#include + namespace advss { class TwitchConnectionsTable final : public ResourceTable { @@ -17,4 +20,14 @@ private: TwitchConnectionsTable(QTabWidget *parent = nullptr); }; +class InvalidTokenDialog : public QDialog { + Q_OBJECT +public: + static void ShowWarning(const QString &name); + +private: + InvalidTokenDialog(const QString &name); + QCheckBox *_doNotShowAgain; +}; + } // namespace advss