From 944d1059dab3320df3007ef8182f46d443e0d3e1 Mon Sep 17 00:00:00 2001 From: WarmUpTill <19472752+WarmUpTill@users.noreply.github.com> Date: Wed, 18 Mar 2026 20:44:36 +0100 Subject: [PATCH] Add user moderation actions --- data/locale/en-US.ini | 12 + plugins/twitch/macro-action-twitch.cpp | 350 ++++++++++++++++++++++--- plugins/twitch/macro-action-twitch.hpp | 8 + plugins/twitch/twitch-helpers.cpp | 7 + plugins/twitch/twitch-helpers.hpp | 3 + 5 files changed, 346 insertions(+), 34 deletions(-) diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 2724ba74..4d5434d1 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -1263,6 +1263,14 @@ AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.enable="Enable chat's emote-o AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable="Disable chat's emote-only mode" AdvSceneSwitcher.action.twitch.type.chat.sendMessage="Send chat message" AdvSceneSwitcher.action.twitch.type.user.getInfo="Get user information" +AdvSceneSwitcher.action.twitch.type.user.ban="Ban user" +AdvSceneSwitcher.action.twitch.type.user.unban="Unban user" +AdvSceneSwitcher.action.twitch.type.user.block="Block user" +AdvSceneSwitcher.action.twitch.type.user.unblock="Unblock user" +AdvSceneSwitcher.action.twitch.type.user.moderator.add="Add user as moderator" +AdvSceneSwitcher.action.twitch.type.user.moderator.delete="Remove user as moderator" +AdvSceneSwitcher.action.twitch.type.user.vip.add="Add user as VIP" +AdvSceneSwitcher.action.twitch.type.user.vip.delete="Remove user as VIP" AdvSceneSwitcher.action.twitch.type.reward.getInfo="Get channel points reward information" AdvSceneSwitcher.action.twitch.type.channel.getInfo="Get channel information" AdvSceneSwitcher.action.twitch.reward.toggleControl="Toggle reward name / variable selection control" @@ -1271,6 +1279,10 @@ AdvSceneSwitcher.action.twitch.layout.default="On{{account}}{{actions}}{{streamT AdvSceneSwitcher.action.twitch.layout.chat="Using account{{account}}{{actions}}on{{channel}}" AdvSceneSwitcher.action.twitch.layout.channel.getInfo="Using account{{account}}{{actions}}of{{channel}}" AdvSceneSwitcher.action.twitch.layout.user.getInfo="Using account{{account}}{{actions}}for{{userInfoQueryType}}{{userLogin}}{{userId}}" +AdvSceneSwitcher.action.twitch.layout.user.moderation.row1="Using account{{account}}{{actions}}" +AdvSceneSwitcher.action.twitch.layout.user.moderation.row2="on channel{{channel}}for{{userInfoQueryType}}{{userLogin}}{{userId}}" +AdvSceneSwitcher.action.twitch.layout.user.ban.row1="Using account{{account}}{{actions}}timeout{{duration}}" +AdvSceneSwitcher.action.twitch.layout.user.ban.row2="on channel{{channel}}for{{userInfoQueryType}}{{userLogin}}{{userId}}reason{{banReason}}" AdvSceneSwitcher.action.twitch.layout.reward.getInfo="Using account{{account}}{{actions}}for channel{{channel}}{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}" AdvSceneSwitcher.action.twitch.title.title="Enter title" AdvSceneSwitcher.action.twitch.marker.description="Describe marker" diff --git a/plugins/twitch/macro-action-twitch.cpp b/plugins/twitch/macro-action-twitch.cpp index 590b6d63..54b6bd38 100644 --- a/plugins/twitch/macro-action-twitch.cpp +++ b/plugins/twitch/macro-action-twitch.cpp @@ -25,6 +25,7 @@ void MacroActionTwitch::ResolveVariablesToFixedValues() _chatMessage.ResolveVariables(); _userLogin.ResolveVariables(); _userId.ResolveVariables(); + _banReason.ResolveVariables(); _useVariableForRewardSelection = false; auto token = _token.lock(); if (token) { @@ -81,6 +82,22 @@ const static std::map actionTypes = { "AdvSceneSwitcher.action.twitch.type.chat.sendMessage"}, {MacroActionTwitch::Action::USER_GET_INFO, "AdvSceneSwitcher.action.twitch.type.user.getInfo"}, + {MacroActionTwitch::Action::USER_BAN, + "AdvSceneSwitcher.action.twitch.type.user.ban"}, + {MacroActionTwitch::Action::USER_UNBAN, + "AdvSceneSwitcher.action.twitch.type.user.unban"}, + {MacroActionTwitch::Action::USER_BLOCK, + "AdvSceneSwitcher.action.twitch.type.user.block"}, + {MacroActionTwitch::Action::USER_UNBLOCK, + "AdvSceneSwitcher.action.twitch.type.user.unblock"}, + {MacroActionTwitch::Action::USER_MODERATOR_ADD, + "AdvSceneSwitcher.action.twitch.type.user.moderator.add"}, + {MacroActionTwitch::Action::USER_MODERATOR_DELETE, + "AdvSceneSwitcher.action.twitch.type.user.moderator.delete"}, + {MacroActionTwitch::Action::USER_VIP_ADD, + "AdvSceneSwitcher.action.twitch.type.user.vip.add"}, + {MacroActionTwitch::Action::USER_VIP_DELETE, + "AdvSceneSwitcher.action.twitch.type.user.vip.delete"}, {MacroActionTwitch::Action::POINTS_REWARD_GET_INFO, "AdvSceneSwitcher.action.twitch.type.reward.getInfo"}, {MacroActionTwitch::Action::CHANNEL_GET_INFO, @@ -399,6 +416,45 @@ void MacroActionTwitch::GetChannelInfo(const std::shared_ptr &token info->is_branded_content ? "true" : "false"); } +std::optional MacroActionTwitch::GetTargetUserID( + const std::shared_ptr &token) const +{ + switch (_userInfoQueryType) { + case UserInfoQueryType::ID: { + const auto id = (uint64_t)_userId; + if (id == 0) { + blog(LOG_INFO, "Target user ID is not set!"); + return std::nullopt; + } + vblog(LOG_INFO, "Resolved target user ID: %llu", + (long long unsigned int)id); + return std::to_string(id); + } + case UserInfoQueryType::LOGIN: { + auto result = SendGetRequest(*token, "https://api.twitch.tv", + "/helix/users", + {{"login", _userLogin}}, true); + if (result.status != 200) { + blog(LOG_INFO, + "Failed to get user id for login '%s'! (%d)", + std::string(_userLogin).c_str(), result.status); + return std::nullopt; + } + OBSDataArrayAutoRelease array = + obs_data_get_array(result.data, "data"); + if (obs_data_array_count(array) == 0) { + blog(LOG_WARNING, "No user found for login '%s'", + std::string(_userLogin).c_str()); + return std::nullopt; + } + OBSDataAutoRelease userData = obs_data_array_item(array, 0); + return obs_data_get_string(userData, "id"); + } + default: + return std::nullopt; + } +} + bool MacroActionTwitch::ResolveVariableSelectionToRewardId( const std::shared_ptr &token) { @@ -653,6 +709,162 @@ bool MacroActionTwitch::PerformAction() case MacroActionTwitch::Action::USER_GET_INFO: GetUserInfo(token); break; + case Action::USER_BAN: { + const auto id = token->GetUserID(); + const auto targetId = GetTargetUserID(token); + if (!id || !targetId) { + break; + } + const auto channelId = _channel.GetUserID(*token); + const auto &broadcasterId = channelId.empty() ? *id : channelId; + OBSDataAutoRelease innerData = obs_data_create(); + obs_data_set_string(innerData, "user_id", targetId->c_str()); + const auto duration = (uint64_t)_duration.Seconds(); + if (duration > 0) { + obs_data_set_int(innerData, "duration", + (long long)duration); + } + const auto reason = std::string(_banReason); + if (!reason.empty()) { + obs_data_set_string(innerData, "reason", + reason.c_str()); + } + OBSDataAutoRelease body = obs_data_create(); + obs_data_set_obj(body, "data", innerData); + auto result = + SendPostRequest(*token, "https://api.twitch.tv", + "/helix/moderation/bans", + {{"broadcaster_id", broadcasterId}, + {"moderator_id", *id}}, + body.Get()); + if (result.status != 200) { + blog(LOG_INFO, "Failed to ban user! (%d)", + result.status); + } + break; + } + case Action::USER_UNBAN: { + const auto id = token->GetUserID(); + const auto targetId = GetTargetUserID(token); + if (!id || !targetId) { + break; + } + const auto channelId = _channel.GetUserID(*token); + const auto &broadcasterId = channelId.empty() ? *id : channelId; + auto result = + SendDeleteRequest(*token, "https://api.twitch.tv", + "/helix/moderation/bans", + {{"broadcaster_id", broadcasterId}, + {"moderator_id", *id}, + {"user_id", *targetId}}); + if (result.status != 204) { + blog(LOG_INFO, "Failed to unban user! (%d)", + result.status); + } + break; + } + case Action::USER_BLOCK: { + const auto targetId = GetTargetUserID(token); + if (!targetId) { + break; + } + auto result = SendPutRequest(*token, "https://api.twitch.tv", + "/helix/users/blocks", + {{"target_user_id", *targetId}}); + if (result.status != 204) { + blog(LOG_INFO, "Failed to block user! (%d)", + result.status); + } + break; + } + case Action::USER_UNBLOCK: { + const auto targetId = GetTargetUserID(token); + if (!targetId) { + break; + } + auto result = SendDeleteRequest( + *token, "https://api.twitch.tv", "/helix/users/blocks", + {{"target_user_id", *targetId}}); + if (result.status != 204) { + blog(LOG_INFO, "Failed to unblock user! (%d)", + result.status); + } + break; + } + case Action::USER_MODERATOR_ADD: { + const auto id = token->GetUserID(); + const auto targetId = GetTargetUserID(token); + if (!id || !targetId) { + break; + } + const auto channelId = _channel.GetUserID(*token); + const auto &broadcasterId = channelId.empty() ? *id : channelId; + auto result = + SendPostRequest(*token, "https://api.twitch.tv", + "/helix/moderation/moderators", + {{"broadcaster_id", broadcasterId}, + {"user_id", *targetId}}); + if (result.status != 204) { + blog(LOG_INFO, "Failed to add moderator! (%d)", + result.status); + } + break; + } + case Action::USER_MODERATOR_DELETE: { + const auto id = token->GetUserID(); + const auto targetId = GetTargetUserID(token); + if (!id || !targetId) { + break; + } + const auto channelId = _channel.GetUserID(*token); + const auto &broadcasterId = channelId.empty() ? *id : channelId; + auto result = + SendDeleteRequest(*token, "https://api.twitch.tv", + "/helix/moderation/moderators", + {{"broadcaster_id", broadcasterId}, + {"user_id", *targetId}}); + if (result.status != 204) { + blog(LOG_INFO, "Failed to remove moderator! (%d)", + result.status); + } + break; + } + case Action::USER_VIP_ADD: { + const auto id = token->GetUserID(); + const auto targetId = GetTargetUserID(token); + if (!id || !targetId) { + break; + } + const auto channelId = _channel.GetUserID(*token); + const auto &broadcasterId = channelId.empty() ? *id : channelId; + auto result = SendPostRequest( + *token, "https://api.twitch.tv", "/helix/channels/vips", + {{"broadcaster_id", broadcasterId}, + {"user_id", *targetId}}); + if (result.status != 204) { + blog(LOG_INFO, "Failed to add VIP! (%d)", + result.status); + } + break; + } + case Action::USER_VIP_DELETE: { + const auto id = token->GetUserID(); + const auto targetId = GetTargetUserID(token); + if (!id || !targetId) { + break; + } + const auto channelId = _channel.GetUserID(*token); + const auto &broadcasterId = channelId.empty() ? *id : channelId; + auto result = SendDeleteRequest( + *token, "https://api.twitch.tv", "/helix/channels/vips", + {{"broadcaster_id", broadcasterId}, + {"user_id", *targetId}}); + if (result.status != 204) { + blog(LOG_INFO, "Failed to remove VIP! (%d)", + result.status); + } + break; + } case MacroActionTwitch::Action::POINTS_REWARD_GET_INFO: GetRewardInfo(token); break; @@ -703,6 +915,7 @@ bool MacroActionTwitch::Save(obs_data_t *obj) const static_cast(_userInfoQueryType)); _userLogin.Save(obj, "userLogin"); _userId.Save(obj, "userId"); + _banReason.Save(obj, "banReason"); _pointsReward.Save(obj); obs_data_set_string(obj, "rewardVariable", GetWeakVariableName(_rewardVariable).c_str()); @@ -734,6 +947,7 @@ bool MacroActionTwitch::Load(obs_data_t *obj) obs_data_get_int(obj, "userInfoQueryType")); _userLogin.Load(obj, "userLogin"); _userId.Load(obj, "userId"); + _banReason.Load(obj, "banReason"); _pointsReward.Load(obj); _rewardVariable = GetWeakVariableByName( obs_data_get_string(obj, "rewardVariable")); @@ -952,10 +1166,16 @@ MacroActionTwitchEdit::MacroActionTwitchEdit( _userInfoQueryType(new QComboBox(this)), _userLogin(new VariableLineEdit(this)), _userId(new VariableDoubleSpinBox(this)), + _banReason(new VariableLineEdit(this)), + _userModerationRow(new QWidget()), + _layout2(new QHBoxLayout()), _pointsReward(new TwitchPointsRewardWidget(this, false)), _rewardVariable(new VariableSelection(this)), _toggleRewardSelection(new QPushButton()) { + _layout2->setContentsMargins(0, 0, 0, 0); + _userModerationRow->setLayout(_layout2); + SetWidgetProperties(); SetWidgetSignalConnections(); @@ -964,6 +1184,7 @@ MacroActionTwitchEdit::MacroActionTwitchEdit( auto mainLayout = new QVBoxLayout(); mainLayout->addLayout(_layout); + mainLayout->addWidget(_userModerationRow); mainLayout->addWidget(_announcementMessage); mainLayout->addWidget(_chatMessage); mainLayout->addWidget(_tags); @@ -1196,6 +1417,8 @@ void MacroActionTwitchEdit::SetWidgetSignalConnections() _userId, SIGNAL(NumberVariableChanged(const NumberVariable &)), this, SLOT(UserIdChanged(const NumberVariable &))); + QWidget::connect(_banReason, SIGNAL(editingFinished()), this, + SLOT(BanReasonChanged())); QWidget::connect( _pointsReward, SIGNAL(PointsRewardChanged(const TwitchPointsReward &)), this, @@ -1223,19 +1446,27 @@ void MacroActionTwitchEdit::SetWidgetVisibility() _contentClassification->setVisible( _entryData->GetAction() == MacroActionTwitch::Action::CHANNEL_INFO_CONTENT_LABELS_SET); + const auto action = _entryData->GetAction(); + const bool isChannelModAction = + action == MacroActionTwitch::Action::USER_BAN || + action == MacroActionTwitch::Action::USER_UNBAN || + action == MacroActionTwitch::Action::USER_MODERATOR_ADD || + action == MacroActionTwitch::Action::USER_MODERATOR_DELETE || + action == MacroActionTwitch::Action::USER_VIP_ADD || + action == MacroActionTwitch::Action::USER_VIP_DELETE; _channel->setVisible( - _entryData->GetAction() == - MacroActionTwitch::Action::CHANNEL_GET_INFO || - _entryData->GetAction() == - MacroActionTwitch::Action::RAID_START || - _entryData->GetAction() == - MacroActionTwitch::Action::RAID_END || - _entryData->GetAction() == - MacroActionTwitch::Action::SEND_CHAT_MESSAGE || - _entryData->GetAction() == - MacroActionTwitch::Action::POINTS_REWARD_GET_INFO); - _duration->setVisible(_entryData->GetAction() == - MacroActionTwitch::Action::COMMERCIAL_START); + action == MacroActionTwitch::Action::CHANNEL_GET_INFO || + action == MacroActionTwitch::Action::RAID_START || + action == MacroActionTwitch::Action::RAID_END || + action == MacroActionTwitch::Action::SHOUTOUT_SEND || + action == MacroActionTwitch::Action::SEND_CHAT_MESSAGE || + action == MacroActionTwitch::Action::POINTS_REWARD_GET_INFO || + isChannelModAction); + _duration->setVisible( + action == MacroActionTwitch::Action::COMMERCIAL_START || + action == MacroActionTwitch::Action::USER_BAN); + _banReason->setVisible(action == MacroActionTwitch::Action::USER_BAN); + _userModerationRow->setVisible(isChannelModAction); _markerDescription->setVisible( _entryData->GetAction() == MacroActionTwitch::Action::MARKER_CREATE); @@ -1249,16 +1480,31 @@ void MacroActionTwitchEdit::SetWidgetVisibility() MacroActionTwitch::Action::CHAT_ANNOUNCEMENT_SEND); _chatMessage->setVisible(_entryData->GetAction() == MacroActionTwitch::Action::SEND_CHAT_MESSAGE); - _userInfoQueryType->setVisible( + const bool isUserTargetAction = _entryData->GetAction() == - MacroActionTwitch::Action::USER_GET_INFO); + MacroActionTwitch::Action::USER_GET_INFO || + _entryData->GetAction() == + MacroActionTwitch::Action::USER_BAN || + _entryData->GetAction() == + MacroActionTwitch::Action::USER_UNBAN || + _entryData->GetAction() == + MacroActionTwitch::Action::USER_BLOCK || + _entryData->GetAction() == + MacroActionTwitch::Action::USER_UNBLOCK || + _entryData->GetAction() == + MacroActionTwitch::Action::USER_MODERATOR_ADD || + _entryData->GetAction() == + MacroActionTwitch::Action::USER_MODERATOR_DELETE || + _entryData->GetAction() == + MacroActionTwitch::Action::USER_VIP_ADD || + _entryData->GetAction() == + MacroActionTwitch::Action::USER_VIP_DELETE; + _userInfoQueryType->setVisible(isUserTargetAction); _userLogin->setVisible( - _entryData->GetAction() == - MacroActionTwitch::Action::USER_GET_INFO && + isUserTargetAction && _entryData->_userInfoQueryType == MacroActionTwitch::UserInfoQueryType::LOGIN); - _userId->setVisible(_entryData->GetAction() == - MacroActionTwitch::Action::USER_GET_INFO && + _userId->setVisible(isUserTargetAction && _entryData->_userInfoQueryType == MacroActionTwitch::UserInfoQueryType::ID); _pointsReward->setVisible( @@ -1340,6 +1586,12 @@ void MacroActionTwitchEdit::UserIdChanged(const NumberVariable &value) _entryData->_userId = value; } +void MacroActionTwitchEdit::BanReasonChanged() +{ + GUARD_LOADING_AND_LOCK(); + _entryData->_banReason = _banReason->text().toStdString(); +} + void MacroActionTwitchEdit::PointsRewardChanged(const TwitchPointsReward &reward) { GUARD_LOADING_AND_LOCK(); @@ -1360,24 +1612,64 @@ void MacroActionTwitchEdit::SetWidgetLayout() _userInfoQueryType, _userLogin, _userId, + _banReason, _pointsReward, _rewardVariable, _toggleRewardSelection}; for (auto widget : widgets) { _layout->removeWidget(widget); + _layout2->removeWidget(widget); } ClearLayout(_layout); + ClearLayout(_layout2); + + const std::unordered_map widgetsMap{ + {"{{account}}", _tokens}, + {"{{actions}}", _actions}, + {"{{streamTitle}}", _streamTitle}, + {"{{category}}", _category}, + {"{{markerDescription}}", _markerDescription}, + {"{{clipHasDelay}}", _clipHasDelay}, + {"{{duration}}", _duration}, + {"{{announcementColor}}", _announcementColor}, + {"{{channel}}", _channel}, + {"{{userInfoQueryType}}", _userInfoQueryType}, + {"{{userLogin}}", _userLogin}, + {"{{userId}}", _userId}, + {"{{banReason}}", _banReason}, + {"{{pointsReward}}", _pointsReward}, + {"{{rewardVariable}}", _rewardVariable}, + {"{{toggleRewardSelection}}", _toggleRewardSelection}}; const char *layoutText; + const char *layout2Text = nullptr; switch (_entryData->GetAction()) { case MacroActionTwitch::Action::SEND_CHAT_MESSAGE: layoutText = obs_module_text( "AdvSceneSwitcher.action.twitch.layout.chat"); break; case MacroActionTwitch::Action::USER_GET_INFO: + case MacroActionTwitch::Action::USER_BLOCK: + case MacroActionTwitch::Action::USER_UNBLOCK: layoutText = obs_module_text( "AdvSceneSwitcher.action.twitch.layout.user.getInfo"); break; + case MacroActionTwitch::Action::USER_BAN: + layoutText = obs_module_text( + "AdvSceneSwitcher.action.twitch.layout.user.ban.row1"); + layout2Text = obs_module_text( + "AdvSceneSwitcher.action.twitch.layout.user.ban.row2"); + break; + case MacroActionTwitch::Action::USER_UNBAN: + case MacroActionTwitch::Action::USER_MODERATOR_ADD: + case MacroActionTwitch::Action::USER_MODERATOR_DELETE: + case MacroActionTwitch::Action::USER_VIP_ADD: + case MacroActionTwitch::Action::USER_VIP_DELETE: + layoutText = obs_module_text( + "AdvSceneSwitcher.action.twitch.layout.user.moderation.row1"); + layout2Text = obs_module_text( + "AdvSceneSwitcher.action.twitch.layout.user.moderation.row2"); + break; case MacroActionTwitch::Action::POINTS_REWARD_GET_INFO: layoutText = obs_module_text( "AdvSceneSwitcher.action.twitch.layout.reward.getInfo"); @@ -1392,24 +1684,13 @@ void MacroActionTwitchEdit::SetWidgetLayout() break; } - PlaceWidgets(layoutText, _layout, - {{"{{account}}", _tokens}, - {"{{actions}}", _actions}, - {"{{streamTitle}}", _streamTitle}, - {"{{category}}", _category}, - {"{{markerDescription}}", _markerDescription}, - {"{{clipHasDelay}}", _clipHasDelay}, - {"{{duration}}", _duration}, - {"{{announcementColor}}", _announcementColor}, - {"{{channel}}", _channel}, - {"{{userInfoQueryType}}", _userInfoQueryType}, - {"{{userLogin}}", _userLogin}, - {"{{userId}}", _userId}, - {"{{pointsReward}}", _pointsReward}, - {"{{rewardVariable}}", _rewardVariable}, - {"{{toggleRewardSelection}}", _toggleRewardSelection}}); + PlaceWidgets(layoutText, _layout, widgetsMap); + if (layout2Text) { + PlaceWidgets(layout2Text, _layout2, widgetsMap); + } _layout->setContentsMargins(0, 0, 0, 0); + _layout2->setContentsMargins(0, 0, 0, 0); } void MacroActionTwitchEdit::UpdateEntryData() @@ -1444,6 +1725,7 @@ void MacroActionTwitchEdit::UpdateEntryData() static_cast(_entryData->_userInfoQueryType))); _userLogin->setText(_entryData->_userLogin); _userId->SetValue(_entryData->_userId); + _banReason->setText(_entryData->_banReason); _pointsReward->SetToken(_entryData->_token); _pointsReward->SetChannel(_entryData->_channel); _pointsReward->SetPointsReward(_entryData->_pointsReward); diff --git a/plugins/twitch/macro-action-twitch.hpp b/plugins/twitch/macro-action-twitch.hpp index 33e4566d..09985b2e 100644 --- a/plugins/twitch/macro-action-twitch.hpp +++ b/plugins/twitch/macro-action-twitch.hpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace advss { @@ -187,6 +188,7 @@ public: UserInfoQueryType _userInfoQueryType = UserInfoQueryType::LOGIN; StringVariable _userLogin = "user login"; DoubleVariable _userId = 0; + StringVariable _banReason = ""; TwitchPointsReward _pointsReward; std::weak_ptr _rewardVariable; bool _useVariableForRewardSelection = false; @@ -206,6 +208,8 @@ private: void GetRewardInfo(const std::shared_ptr &); void GetChannelInfo(const std::shared_ptr &token); + std::optional + GetTargetUserID(const std::shared_ptr &) const; bool ResolveVariableSelectionToRewardId( const std::shared_ptr &); @@ -256,6 +260,7 @@ private slots: void UserInfoQueryTypeChanged(int); void UserLoginChanged(); void UserIdChanged(const NumberVariable &); + void BanReasonChanged(); void PointsRewardChanged(const TwitchPointsReward &); void RewardVariableChanged(const QString &); void ToggleRewardSelection(bool); @@ -274,6 +279,8 @@ private: void SetTokenWarning(bool visible, const QString &text = ""); QHBoxLayout *_layout; + QWidget *_userModerationRow; + QHBoxLayout *_layout2; FilterComboBox *_actions; TwitchConnectionSelection *_tokens; QLabel *_tokenWarning; @@ -295,6 +302,7 @@ private: // QSpinBox uses int internally, which is too small for Twitch IDs, so // we use QDoubleSpinBox instead VariableDoubleSpinBox *_userId; + VariableLineEdit *_banReason; TwitchPointsRewardWidget *_pointsReward; VariableSelection *_rewardVariable; QPushButton *_toggleRewardSelection; diff --git a/plugins/twitch/twitch-helpers.cpp b/plugins/twitch/twitch-helpers.cpp index dc5fe998..ffdd062b 100644 --- a/plugins/twitch/twitch-helpers.cpp +++ b/plugins/twitch/twitch-helpers.cpp @@ -523,6 +523,13 @@ static RequestResult sendDeleteRequest(const TwitchToken &token, return processResult(response, __func__); } +RequestResult SendDeleteRequest(const TwitchToken &token, + const std::string &uri, const std::string &path, + const httplib::Params ¶ms) +{ + return sendDeleteRequest(token, uri, path, params); +} + void SetJsonTempVars(const std::string &jsonStr, std::function setVarFunc) { diff --git a/plugins/twitch/twitch-helpers.hpp b/plugins/twitch/twitch-helpers.hpp index 29e8857a..53707856 100644 --- a/plugins/twitch/twitch-helpers.hpp +++ b/plugins/twitch/twitch-helpers.hpp @@ -48,6 +48,9 @@ RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri, const httplib::Params ¶ms = {}, const std::string &data = "", bool useCache = false); +RequestResult SendDeleteRequest(const TwitchToken &token, + const std::string &uri, const std::string &path, + const httplib::Params ¶ms = {}); // Helper functions to set temp var values