mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-03-22 01:44:49 -05:00
Add Twitch condition
It supports: * Checking if a channel is live (polling) * Checking if a channel went live and it's stream type (event) * Checking if a channel weng offline (event) * Checking the stream title of a channel (polling) * Checking the stream category of a channel (polling) * Checking if the channel information was changed (event) * Checking for new followers (event) * Checking for subsciptions (new / end / gift / resub message) (event) * Checking for cheers (event)
This commit is contained in:
parent
ce40b80d90
commit
9ea90ed61b
|
|
@ -490,6 +490,28 @@ AdvSceneSwitcher.condition.slideshow.condition.slideIndex="Current slide number
|
|||
AdvSceneSwitcher.condition.slideshow.condition.slidePath="Current slide path is"
|
||||
AdvSceneSwitcher.condition.slideshow.updateIntervalTooltip="Information about the slide show status will only be updated based on the configured time between slides"
|
||||
AdvSceneSwitcher.condition.slideshow.entry="{{sources}}{{conditions}}{{index}}{{path}}"
|
||||
AdvSceneSwitcher.condition.twitch="Twitch"
|
||||
AdvSceneSwitcher.condition.twitch.type.channelIsLive="Is currently live"
|
||||
AdvSceneSwitcher.condition.twitch.type.channelWentLive="Went live"
|
||||
AdvSceneSwitcher.condition.twitch.type.channelStartedPlaylist="Started playlist"
|
||||
AdvSceneSwitcher.condition.twitch.type.channelStartedWatchparty="Started watchparty"
|
||||
AdvSceneSwitcher.condition.twitch.type.channelStartedPremiere="Started premiere"
|
||||
AdvSceneSwitcher.condition.twitch.type.channelStartedRerun="Started rerun"
|
||||
AdvSceneSwitcher.condition.twitch.type.channelWentOffline="Went offline"
|
||||
AdvSceneSwitcher.condition.twitch.type.channelTitle="Title matches"
|
||||
AdvSceneSwitcher.condition.twitch.type.category="Category is"
|
||||
AdvSceneSwitcher.condition.twitch.type.channelUpdateEvent="Channel information was updated"
|
||||
AdvSceneSwitcher.condition.twitch.type.channelFollow="Channel received a follow"
|
||||
AdvSceneSwitcher.condition.twitch.type.channelSubscribe="Channel received a subscription"
|
||||
AdvSceneSwitcher.condition.twitch.type.channelSubscribeEnd="Channel subscription expired"
|
||||
AdvSceneSwitcher.condition.twitch.type.channelSubscribeGift="Channel received gifted subscriptions"
|
||||
AdvSceneSwitcher.condition.twitch.type.channelSubscribeMessage="Channel received re-subscription chat message"
|
||||
AdvSceneSwitcher.condition.twitch.type.channelCheer="Channel received a cheer chat message"
|
||||
AdvSceneSwitcher.condition.twitch.categorySelectionDisabled="Cannot select category without selecting a Twitch account first!"
|
||||
AdvSceneSwitcher.condition.twitch.entry="Channel{{channel}}{{conditions}}{{streamTitle}}{{regex}}{{category}}"
|
||||
AdvSceneSwitcher.condition.twitch.entry.account="Check using account{{account}}"
|
||||
AdvSceneSwitcher.condition.twitch.tokenPermissionsInsufficient="Permissions of selected token are insufficient to perform selected action!"
|
||||
AdvSceneSwitcher.condition.twitch.title.title="Enter title"
|
||||
|
||||
; Macro Actions
|
||||
AdvSceneSwitcher.action.scene="Switch scene"
|
||||
|
|
@ -793,7 +815,6 @@ AdvSceneSwitcher.action.twitch.type.emoteOnlyDisable="Disable chat's emote-only
|
|||
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}}{{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"
|
||||
AdvSceneSwitcher.action.twitch.clip.hasDelay="Add a slight delay before capturing the clip"
|
||||
|
|
@ -954,6 +975,7 @@ AdvSceneSwitcher.twitchToken.request.success="Successfully received token!"
|
|||
AdvSceneSwitcher.twitchToken.request.success.browser="Authentication successful! You can close this window now."
|
||||
AdvSceneSwitcher.twitchToken.request.notSet="Account is not connected!"
|
||||
AdvSceneSwitcher.twitchToken.permissions="Token permissions:"
|
||||
AdvSceneSwitcher.twitchToken.permissionsInsufficient="Permissions of selected token are insufficient to perform selected action!"
|
||||
AdvSceneSwitcher.twitchToken.analytics.readExtensions="View analytics data for the Twitch Extensions owned by the authenticated account."
|
||||
AdvSceneSwitcher.twitchToken.analytics.readGames="View analytics data for the games owned by the authenticated account."
|
||||
AdvSceneSwitcher.twitchToken.bits.read="View Bits information for a channel."
|
||||
|
|
@ -963,6 +985,9 @@ 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.twitchToken.bits="View Bits information for a channel."
|
||||
AdvSceneSwitcher.twitchToken.readSubscriptions="View a list of all subscribers to a channel and check if a user is subscribed to a channel."
|
||||
AdvSceneSwitcher.twitchToken.readFollowers="Read the followers of a broadcaster."
|
||||
|
||||
AdvSceneSwitcher.channel.open="Show channel"
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ target_sources(
|
|||
event-sub.hpp
|
||||
macro-action-twitch.cpp
|
||||
macro-action-twitch.hpp
|
||||
macro-condition-twitch.cpp
|
||||
macro-condition-twitch.hpp
|
||||
token.cpp
|
||||
token.hpp
|
||||
twitch-helpers.cpp
|
||||
|
|
|
|||
|
|
@ -358,7 +358,7 @@ MacroActionTwitchEdit::MacroActionTwitchEdit(
|
|||
_actions(new FilterComboBox()),
|
||||
_tokens(new TwitchConnectionSelection()),
|
||||
_tokenPermissionWarning(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.action.twitch.tokenPermissionsInsufficient"))),
|
||||
"AdvSceneSwitcher.twitchToken.permissionsInsufficient"))),
|
||||
_streamTitle(new VariableLineEdit(this)),
|
||||
_category(new TwitchCategoryWidget(this)),
|
||||
_markerDescription(new VariableLineEdit(this)),
|
||||
|
|
|
|||
800
src/macro-external/twitch/macro-condition-twitch.cpp
Normal file
800
src/macro-external/twitch/macro-condition-twitch.cpp
Normal file
|
|
@ -0,0 +1,800 @@
|
|||
#include "macro-condition-twitch.hpp"
|
||||
#include "twitch-helpers.hpp"
|
||||
|
||||
#include <log-helper.hpp>
|
||||
#include <utility.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroConditionTwitch::id = "twitch";
|
||||
|
||||
bool MacroConditionTwitch::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionTwitch::id,
|
||||
{MacroConditionTwitch::Create, MacroConditionTwitchEdit::Create,
|
||||
"AdvSceneSwitcher.condition.twitch"});
|
||||
|
||||
const static std::map<MacroConditionTwitch::Condition, std::string> conditionTypes = {
|
||||
{MacroConditionTwitch::Condition::LIVE_EVENT_REGULAR,
|
||||
"AdvSceneSwitcher.condition.twitch.type.channelWentLive"},
|
||||
{MacroConditionTwitch::Condition::LIVE_EVENT_PLAYLIST,
|
||||
"AdvSceneSwitcher.condition.twitch.type.channelStartedPlaylist"},
|
||||
{MacroConditionTwitch::Condition::LIVE_EVENT_WATCHPARTY,
|
||||
"AdvSceneSwitcher.condition.twitch.type.channelStartedWatchparty"},
|
||||
{MacroConditionTwitch::Condition::LIVE_EVENT_PREMIERE,
|
||||
"AdvSceneSwitcher.condition.twitch.type.channelStartedPremiere"},
|
||||
{MacroConditionTwitch::Condition::LIVE_EVENT_RERUN,
|
||||
"AdvSceneSwitcher.condition.twitch.type.channelStartedRerun"},
|
||||
{MacroConditionTwitch::Condition::OFFLINE_EVENT,
|
||||
"AdvSceneSwitcher.condition.twitch.type.channelWentOffline"},
|
||||
{MacroConditionTwitch::Condition::FOLLOW_EVENT,
|
||||
"AdvSceneSwitcher.condition.twitch.type.channelFollow"},
|
||||
{MacroConditionTwitch::Condition::SUBSCRIBE_EVENT,
|
||||
"AdvSceneSwitcher.condition.twitch.type.channelSubscribe"},
|
||||
{MacroConditionTwitch::Condition::SUBSCRIBE_END_EVENT,
|
||||
"AdvSceneSwitcher.condition.twitch.type.channelSubscribeEnd"},
|
||||
{MacroConditionTwitch::Condition::SUBSCRIBE_GIFT_EVENT,
|
||||
"AdvSceneSwitcher.condition.twitch.type.channelSubscribeGift"},
|
||||
{MacroConditionTwitch::Condition::SUBSCRIBE_MESSAGE_EVENT,
|
||||
"AdvSceneSwitcher.condition.twitch.type.channelSubscribeMessage"},
|
||||
{MacroConditionTwitch::Condition::CHEER_EVENT,
|
||||
"AdvSceneSwitcher.condition.twitch.type.channelCheer"},
|
||||
{MacroConditionTwitch::Condition::LIVE,
|
||||
"AdvSceneSwitcher.condition.twitch.type.channelIsLive"},
|
||||
{MacroConditionTwitch::Condition::TITLE,
|
||||
"AdvSceneSwitcher.condition.twitch.type.channelTitle"},
|
||||
{MacroConditionTwitch::Condition::CATEGORY,
|
||||
"AdvSceneSwitcher.condition.twitch.type.category"},
|
||||
{MacroConditionTwitch::Condition::CHANNEL_UPDATE_EVENT,
|
||||
"AdvSceneSwitcher.condition.twitch.type.channelUpdateEvent"},
|
||||
};
|
||||
|
||||
const static std::map<MacroConditionTwitch::Condition, std::string>
|
||||
eventIdentifiers = {
|
||||
{MacroConditionTwitch::Condition::LIVE_EVENT_REGULAR,
|
||||
"stream.online"},
|
||||
{MacroConditionTwitch::Condition::LIVE_EVENT_PLAYLIST,
|
||||
"stream.online"},
|
||||
{MacroConditionTwitch::Condition::LIVE_EVENT_WATCHPARTY,
|
||||
"stream.online"},
|
||||
{MacroConditionTwitch::Condition::LIVE_EVENT_PREMIERE,
|
||||
"stream.online"},
|
||||
{MacroConditionTwitch::Condition::LIVE_EVENT_RERUN,
|
||||
"stream.online"},
|
||||
{MacroConditionTwitch::Condition::OFFLINE_EVENT,
|
||||
"stream.offline"},
|
||||
{MacroConditionTwitch::Condition::CHANNEL_UPDATE_EVENT,
|
||||
"channel.update"},
|
||||
{MacroConditionTwitch::Condition::FOLLOW_EVENT,
|
||||
"channel.follow"},
|
||||
{MacroConditionTwitch::Condition::SUBSCRIBE_EVENT,
|
||||
"channel.subscribe"},
|
||||
{MacroConditionTwitch::Condition::SUBSCRIBE_END_EVENT,
|
||||
"channel.subscription.end"},
|
||||
{MacroConditionTwitch::Condition::SUBSCRIBE_GIFT_EVENT,
|
||||
"channel.subscription.gift"},
|
||||
{MacroConditionTwitch::Condition::SUBSCRIBE_MESSAGE_EVENT,
|
||||
"channel.subscription.message"},
|
||||
{MacroConditionTwitch::Condition::CHEER_EVENT, "channel.cheer"},
|
||||
};
|
||||
|
||||
const static std::map<MacroConditionTwitch::Condition, std::string>
|
||||
liveEventIDs = {
|
||||
{MacroConditionTwitch::Condition::LIVE_EVENT_REGULAR, "live"},
|
||||
{MacroConditionTwitch::Condition::LIVE_EVENT_PLAYLIST,
|
||||
"playlist"},
|
||||
{MacroConditionTwitch::Condition::LIVE_EVENT_WATCHPARTY,
|
||||
"watch_party"},
|
||||
{MacroConditionTwitch::Condition::LIVE_EVENT_PREMIERE,
|
||||
"premiere"},
|
||||
{MacroConditionTwitch::Condition::LIVE_EVENT_RERUN, "rerun"},
|
||||
};
|
||||
|
||||
static bool titleMatches(const RegexConfig &conf, const std::string &title,
|
||||
const std::string &expr)
|
||||
{
|
||||
if (!conf.Enabled()) {
|
||||
return title == expr;
|
||||
}
|
||||
|
||||
auto regex = conf.GetRegularExpression(expr);
|
||||
if (!regex.isValid()) {
|
||||
return false;
|
||||
}
|
||||
auto match = regex.match(QString::fromStdString(title));
|
||||
return match.hasMatch();
|
||||
}
|
||||
|
||||
bool MacroConditionTwitch::CheckChannelLiveEvents(TwitchToken &token)
|
||||
{
|
||||
auto eventSub = token.GetEventSub();
|
||||
if (!eventSub) {
|
||||
return false;
|
||||
}
|
||||
auto events = eventSub->Events();
|
||||
for (const auto &event : events) {
|
||||
if (event.type != eventIdentifiers.find(_condition)->second) {
|
||||
continue;
|
||||
}
|
||||
auto id =
|
||||
obs_data_get_string(event.data, "broadcaster_user_id");
|
||||
if (id != _channel.GetUserID(token)) {
|
||||
continue;
|
||||
}
|
||||
auto type = obs_data_get_string(event.data, "type");
|
||||
auto it = liveEventIDs.find(_condition);
|
||||
if (it == liveEventIDs.end()) {
|
||||
continue;
|
||||
}
|
||||
const auto &typeId = it->second;
|
||||
if (type != typeId) {
|
||||
continue;
|
||||
}
|
||||
SetVariableValue(event.ToString());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionTwitch::CheckChannelOfflineEvents(TwitchToken &token)
|
||||
{
|
||||
auto eventSub = token.GetEventSub();
|
||||
if (!eventSub) {
|
||||
return false;
|
||||
}
|
||||
auto events = eventSub->Events();
|
||||
for (const auto &event : events) {
|
||||
if (event.type != eventIdentifiers.find(_condition)->second) {
|
||||
continue;
|
||||
}
|
||||
auto id =
|
||||
obs_data_get_string(event.data, "broadcaster_user_id");
|
||||
if (id != _channel.GetUserID(token)) {
|
||||
continue;
|
||||
}
|
||||
SetVariableValue(event.ToString());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionTwitch::CheckChannelUpdateEvents(TwitchToken &token)
|
||||
{
|
||||
auto eventSub = token.GetEventSub();
|
||||
if (!eventSub) {
|
||||
return false;
|
||||
}
|
||||
auto events = eventSub->Events();
|
||||
for (const auto &event : events) {
|
||||
if (event.type != eventIdentifiers.find(_condition)->second) {
|
||||
continue;
|
||||
}
|
||||
auto id =
|
||||
obs_data_get_string(event.data, "broadcaster_user_id");
|
||||
if (id != _channel.GetUserID(token)) {
|
||||
continue;
|
||||
}
|
||||
SetVariableValue(event.ToString());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionTwitch::CheckChannelFollowEvents(TwitchToken &token)
|
||||
{
|
||||
auto eventSub = token.GetEventSub();
|
||||
if (!eventSub) {
|
||||
return false;
|
||||
}
|
||||
auto events = eventSub->Events();
|
||||
for (const auto &event : events) {
|
||||
if (event.type != eventIdentifiers.find(_condition)->second) {
|
||||
continue;
|
||||
}
|
||||
auto id =
|
||||
obs_data_get_string(event.data, "broadcaster_user_id");
|
||||
if (id != _channel.GetUserID(token)) {
|
||||
continue;
|
||||
}
|
||||
SetVariableValue(event.ToString());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionTwitch::CheckChannelSubscribeEvents(TwitchToken &token)
|
||||
{
|
||||
auto eventSub = token.GetEventSub();
|
||||
if (!eventSub) {
|
||||
return false;
|
||||
}
|
||||
auto events = eventSub->Events();
|
||||
for (const auto &event : events) {
|
||||
if (event.type != eventIdentifiers.find(_condition)->second) {
|
||||
continue;
|
||||
}
|
||||
auto id =
|
||||
obs_data_get_string(event.data, "broadcaster_user_id");
|
||||
if (id != _channel.GetUserID(token)) {
|
||||
continue;
|
||||
}
|
||||
SetVariableValue(event.ToString());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionTwitch::CheckChannelCheerEvents(TwitchToken &token)
|
||||
{
|
||||
auto eventSub = token.GetEventSub();
|
||||
if (!eventSub) {
|
||||
return false;
|
||||
}
|
||||
auto events = eventSub->Events();
|
||||
for (const auto &event : events) {
|
||||
if (event.type != eventIdentifiers.find(_condition)->second) {
|
||||
continue;
|
||||
}
|
||||
auto id =
|
||||
obs_data_get_string(event.data, "broadcaster_user_id");
|
||||
if (id != _channel.GetUserID(token)) {
|
||||
continue;
|
||||
}
|
||||
SetVariableValue(event.ToString());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionTwitch::CheckCondition()
|
||||
{
|
||||
SetVariableValue("");
|
||||
auto token = _token.lock();
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto eventSub = token->GetEventSub();
|
||||
|
||||
if (IsUsingEventSubCondition()) {
|
||||
if (!eventSub) {
|
||||
return false;
|
||||
}
|
||||
CheckEventSubscription(*eventSub);
|
||||
if (_subscriptionIDFuture.valid()) {
|
||||
// Still waiting for the subscription to be registered
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
switch (_condition) {
|
||||
case Condition::LIVE: {
|
||||
auto info = _channel.GetLiveInfo(*token);
|
||||
if (!info) {
|
||||
return false;
|
||||
}
|
||||
return info->IsLive();
|
||||
}
|
||||
case Condition::LIVE_EVENT_REGULAR:
|
||||
case Condition::LIVE_EVENT_PLAYLIST:
|
||||
case Condition::LIVE_EVENT_WATCHPARTY:
|
||||
case Condition::LIVE_EVENT_PREMIERE:
|
||||
case Condition::LIVE_EVENT_RERUN:
|
||||
return CheckChannelLiveEvents(*token);
|
||||
case Condition::TITLE: {
|
||||
auto info = _channel.GetInfo(*token);
|
||||
if (!info) {
|
||||
return false;
|
||||
}
|
||||
SetVariableValue(info->title);
|
||||
return titleMatches(_regex, info->title, _streamTitle);
|
||||
}
|
||||
case Condition::CATEGORY: {
|
||||
auto info = _channel.GetInfo(*token);
|
||||
if (!info) {
|
||||
return false;
|
||||
}
|
||||
SetVariableValue(info->game_name);
|
||||
return info->game_id == std::to_string(_category.id);
|
||||
}
|
||||
case Condition::CHANNEL_UPDATE_EVENT:
|
||||
return CheckChannelUpdateEvents(*token);
|
||||
case Condition::FOLLOW_EVENT:
|
||||
return CheckChannelFollowEvents(*token);
|
||||
case Condition::SUBSCRIBE_EVENT:
|
||||
case Condition::SUBSCRIBE_END_EVENT:
|
||||
case Condition::SUBSCRIBE_GIFT_EVENT:
|
||||
case Condition::SUBSCRIBE_MESSAGE_EVENT:
|
||||
return CheckChannelSubscribeEvents(*token);
|
||||
case Condition::CHEER_EVENT:
|
||||
return CheckChannelCheerEvents(*token);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MacroConditionTwitch::CheckEventSubscription(EventSub &eventSub)
|
||||
{
|
||||
if (_subscriptionIDFuture.valid()) {
|
||||
if (_subscriptionIDFuture.wait_for(std::chrono::seconds(0)) !=
|
||||
std::future_status::ready)
|
||||
return;
|
||||
|
||||
_subscriptionID = _subscriptionIDFuture.get();
|
||||
}
|
||||
if (eventSub.SubscriptionIsActive(_subscriptionID)) {
|
||||
return;
|
||||
}
|
||||
SetupEventSubscriptions();
|
||||
}
|
||||
|
||||
void MacroConditionTwitch::SetCondition(const Condition &cond)
|
||||
{
|
||||
_condition = cond;
|
||||
_subscriptionID = "";
|
||||
}
|
||||
|
||||
bool MacroConditionTwitch::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
obs_data_set_string(obj, "token",
|
||||
GetWeakTwitchTokenName(_token).c_str());
|
||||
_streamTitle.Save(obj, "streamTitle");
|
||||
_category.Save(obj);
|
||||
_channel.Save(obj);
|
||||
_regex.Save(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionTwitch::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_condition = static_cast<Condition>(obs_data_get_int(obj, "condition"));
|
||||
_token = GetWeakTwitchTokenByName(obs_data_get_string(obj, "token"));
|
||||
_streamTitle.Load(obj, "streamTitle");
|
||||
_category.Load(obj);
|
||||
_channel.Load(obj);
|
||||
_regex.Load(obj);
|
||||
_subscriptionID = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroConditionTwitch::GetShortDesc() const
|
||||
{
|
||||
return GetWeakTwitchTokenName(_token);
|
||||
}
|
||||
|
||||
bool MacroConditionTwitch::ConditionIsSupportedByToken()
|
||||
{
|
||||
static const std::unordered_map<Condition, TokenOption> requiredOption = {
|
||||
{Condition::LIVE_EVENT_REGULAR, {""}},
|
||||
{Condition::LIVE_EVENT_PLAYLIST, {""}},
|
||||
{Condition::LIVE_EVENT_WATCHPARTY, {""}},
|
||||
{Condition::LIVE_EVENT_PREMIERE, {""}},
|
||||
{Condition::LIVE_EVENT_RERUN, {""}},
|
||||
{Condition::OFFLINE_EVENT, {""}},
|
||||
{Condition::CHANNEL_UPDATE_EVENT, {""}},
|
||||
{Condition::FOLLOW_EVENT, {"moderator:read:followers"}},
|
||||
{Condition::SUBSCRIBE_EVENT, {"channel:read:subscriptions"}},
|
||||
{Condition::SUBSCRIBE_END_EVENT,
|
||||
{"channel:read:subscriptions"}},
|
||||
{Condition::SUBSCRIBE_GIFT_EVENT,
|
||||
{"channel:read:subscriptions"}},
|
||||
{Condition::SUBSCRIBE_MESSAGE_EVENT,
|
||||
{"channel:read:subscriptions"}},
|
||||
{Condition::CHEER_EVENT, {"bits:read"}},
|
||||
{Condition::LIVE, {""}},
|
||||
{Condition::TITLE, {""}},
|
||||
{Condition::CATEGORY, {""}},
|
||||
};
|
||||
auto token = _token.lock();
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
auto option = requiredOption.find(_condition);
|
||||
assert(option != requiredOption.end());
|
||||
if (option == requiredOption.end()) {
|
||||
return false;
|
||||
}
|
||||
return option->second.apiId.empty() ||
|
||||
token->OptionIsEnabled(option->second);
|
||||
}
|
||||
|
||||
void MacroConditionTwitch::SetupEventSubscriptions()
|
||||
{
|
||||
if (!IsUsingEventSubCondition()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (_condition) {
|
||||
case MacroConditionTwitch::Condition::LIVE_EVENT_REGULAR:
|
||||
case MacroConditionTwitch::Condition::LIVE_EVENT_PLAYLIST:
|
||||
case MacroConditionTwitch::Condition::LIVE_EVENT_WATCHPARTY:
|
||||
case MacroConditionTwitch::Condition::LIVE_EVENT_PREMIERE:
|
||||
case MacroConditionTwitch::Condition::LIVE_EVENT_RERUN:
|
||||
AddChannelLiveEventSubscription();
|
||||
break;
|
||||
case MacroConditionTwitch::Condition::OFFLINE_EVENT:
|
||||
AddChannelOfflineEventSubscription();
|
||||
break;
|
||||
case MacroConditionTwitch::Condition::CHANNEL_UPDATE_EVENT:
|
||||
AddChannelUpdateEventSubscription();
|
||||
break;
|
||||
case MacroConditionTwitch::Condition::FOLLOW_EVENT:
|
||||
AddChannelFollowEventSubscription();
|
||||
break;
|
||||
case MacroConditionTwitch::Condition::SUBSCRIBE_EVENT:
|
||||
case MacroConditionTwitch::Condition::SUBSCRIBE_END_EVENT:
|
||||
case MacroConditionTwitch::Condition::SUBSCRIBE_GIFT_EVENT:
|
||||
case MacroConditionTwitch::Condition::SUBSCRIBE_MESSAGE_EVENT:
|
||||
AddChannelSubscribeEventSubscription();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroConditionTwitch::IsUsingEventSubCondition()
|
||||
{
|
||||
const static std::set<Condition> eventConditions{
|
||||
Condition::LIVE_EVENT_REGULAR,
|
||||
Condition::LIVE_EVENT_PLAYLIST,
|
||||
Condition::LIVE_EVENT_WATCHPARTY,
|
||||
Condition::LIVE_EVENT_PREMIERE,
|
||||
Condition::LIVE_EVENT_RERUN,
|
||||
Condition::CHANNEL_UPDATE_EVENT,
|
||||
Condition::FOLLOW_EVENT,
|
||||
Condition::SUBSCRIBE_EVENT,
|
||||
Condition::SUBSCRIBE_END_EVENT,
|
||||
Condition::SUBSCRIBE_GIFT_EVENT,
|
||||
Condition::SUBSCRIBE_MESSAGE_EVENT,
|
||||
Condition::CHEER_EVENT,
|
||||
};
|
||||
return eventConditions.find(_condition) != eventConditions.end();
|
||||
}
|
||||
|
||||
std::future<std::string>
|
||||
waitForSubscription(const std::shared_ptr<TwitchToken> &token,
|
||||
const Subscription &subscription)
|
||||
{
|
||||
return std::async(std::launch::async, [token, subscription]() {
|
||||
auto id = EventSub::AddEventSubscribtion(token, subscription);
|
||||
return id;
|
||||
});
|
||||
}
|
||||
|
||||
void MacroConditionTwitch::AddChannelLiveEventSubscription()
|
||||
{
|
||||
if (!IsUsingEventSubCondition()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto token = _token.lock();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
OBSDataAutoRelease temp = obs_data_create();
|
||||
Subscription subscription{temp.Get()};
|
||||
obs_data_set_string(subscription.data, "type",
|
||||
eventIdentifiers.find(_condition)->second.c_str());
|
||||
obs_data_set_string(subscription.data, "version", "1");
|
||||
OBSDataAutoRelease condition = obs_data_create();
|
||||
obs_data_set_string(condition, "broadcaster_user_id",
|
||||
_channel.GetUserID(*token).c_str());
|
||||
obs_data_set_obj(subscription.data, "condition", condition);
|
||||
_subscriptionIDFuture = waitForSubscription(token, subscription);
|
||||
}
|
||||
|
||||
void MacroConditionTwitch::AddChannelOfflineEventSubscription()
|
||||
{
|
||||
if (!IsUsingEventSubCondition()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto token = _token.lock();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
OBSDataAutoRelease temp = obs_data_create();
|
||||
Subscription subscription{temp.Get()};
|
||||
obs_data_set_string(subscription.data, "type",
|
||||
eventIdentifiers.find(_condition)->second.c_str());
|
||||
obs_data_set_string(subscription.data, "version", "1");
|
||||
OBSDataAutoRelease condition = obs_data_create();
|
||||
obs_data_set_string(condition, "broadcaster_user_id",
|
||||
_channel.GetUserID(*token).c_str());
|
||||
obs_data_set_obj(subscription.data, "condition", condition);
|
||||
_subscriptionIDFuture = waitForSubscription(token, subscription);
|
||||
}
|
||||
|
||||
void MacroConditionTwitch::AddChannelUpdateEventSubscription()
|
||||
{
|
||||
if (!IsUsingEventSubCondition()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto token = _token.lock();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
OBSDataAutoRelease temp = obs_data_create();
|
||||
Subscription subscription{temp.Get()};
|
||||
obs_data_set_string(subscription.data, "type",
|
||||
eventIdentifiers.find(_condition)->second.c_str());
|
||||
obs_data_set_string(subscription.data, "version", "2");
|
||||
OBSDataAutoRelease condition = obs_data_create();
|
||||
obs_data_set_string(condition, "broadcaster_user_id",
|
||||
_channel.GetUserID(*token).c_str());
|
||||
obs_data_set_obj(subscription.data, "condition", condition);
|
||||
_subscriptionIDFuture = waitForSubscription(token, subscription);
|
||||
}
|
||||
|
||||
void MacroConditionTwitch::AddChannelFollowEventSubscription()
|
||||
{
|
||||
if (!IsUsingEventSubCondition()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto token = _token.lock();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
OBSDataAutoRelease temp = obs_data_create();
|
||||
Subscription subscription{temp.Get()};
|
||||
obs_data_set_string(subscription.data, "type",
|
||||
eventIdentifiers.find(_condition)->second.c_str());
|
||||
obs_data_set_string(subscription.data, "version", "2");
|
||||
OBSDataAutoRelease condition = obs_data_create();
|
||||
obs_data_set_string(condition, "broadcaster_user_id",
|
||||
_channel.GetUserID(*token).c_str());
|
||||
obs_data_set_string(condition, "moderator_user_id",
|
||||
token->GetUserID().c_str());
|
||||
obs_data_set_obj(subscription.data, "condition", condition);
|
||||
_subscriptionIDFuture = waitForSubscription(token, subscription);
|
||||
}
|
||||
|
||||
void MacroConditionTwitch::AddChannelSubscribeEventSubscription()
|
||||
{
|
||||
if (!IsUsingEventSubCondition()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto token = _token.lock();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
OBSDataAutoRelease temp = obs_data_create();
|
||||
Subscription subscription{temp.Get()};
|
||||
obs_data_set_string(subscription.data, "type",
|
||||
eventIdentifiers.find(_condition)->second.c_str());
|
||||
obs_data_set_string(subscription.data, "version", "1");
|
||||
OBSDataAutoRelease condition = obs_data_create();
|
||||
obs_data_set_string(condition, "broadcaster_user_id",
|
||||
_channel.GetUserID(*token).c_str());
|
||||
obs_data_set_obj(subscription.data, "condition", condition);
|
||||
_subscriptionIDFuture = waitForSubscription(token, subscription);
|
||||
}
|
||||
|
||||
void MacroConditionTwitch::AddChannelCheerEventSubscription()
|
||||
{
|
||||
if (!IsUsingEventSubCondition()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto token = _token.lock();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
OBSDataAutoRelease temp = obs_data_create();
|
||||
Subscription subscription{temp.Get()};
|
||||
obs_data_set_string(subscription.data, "type",
|
||||
eventIdentifiers.find(_condition)->second.c_str());
|
||||
obs_data_set_string(subscription.data, "version", "1");
|
||||
OBSDataAutoRelease condition = obs_data_create();
|
||||
obs_data_set_string(condition, "broadcaster_user_id",
|
||||
_channel.GetUserID(*token).c_str());
|
||||
obs_data_set_obj(subscription.data, "condition", condition);
|
||||
_subscriptionIDFuture = waitForSubscription(token, subscription);
|
||||
}
|
||||
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[condition, name] : conditionTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(condition));
|
||||
}
|
||||
}
|
||||
|
||||
MacroConditionTwitchEdit::MacroConditionTwitchEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionTwitch> entryData)
|
||||
: QWidget(parent),
|
||||
_conditions(new FilterComboBox()),
|
||||
_tokens(new TwitchConnectionSelection()),
|
||||
_streamTitle(new VariableLineEdit(this)),
|
||||
_category(new TwitchCategoryWidget(this)),
|
||||
_channel(new TwitchChannelSelection(this)),
|
||||
_regex(new RegexConfigWidget(parent)),
|
||||
_layout(new QHBoxLayout()),
|
||||
_tokenPermissionWarning(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.twitchToken.permissionsInsufficient")))
|
||||
{
|
||||
_streamTitle->setSizePolicy(QSizePolicy::MinimumExpanding,
|
||||
QSizePolicy::Preferred);
|
||||
_streamTitle->setMaxLength(140);
|
||||
|
||||
populateConditionSelection(_conditions);
|
||||
|
||||
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(_tokens, SIGNAL(SelectionChanged(const QString &)),
|
||||
this, SLOT(TwitchTokenChanged(const QString &)));
|
||||
QWidget::connect(_streamTitle, SIGNAL(editingFinished()), this,
|
||||
SLOT(StreamTitleChanged()));
|
||||
QWidget::connect(_category,
|
||||
SIGNAL(CategoreyChanged(const TwitchCategory &)), this,
|
||||
SLOT(CategoreyChanged(const TwitchCategory &)));
|
||||
QWidget::connect(_channel,
|
||||
SIGNAL(ChannelChanged(const TwitchChannel &)), this,
|
||||
SLOT(ChannelChanged(const TwitchChannel &)));
|
||||
QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
|
||||
SLOT(RegexChanged(RegexConfig)));
|
||||
QWidget::connect(&_tokenPermissionCheckTimer, SIGNAL(timeout()), this,
|
||||
SLOT(CheckTokenPermissions()));
|
||||
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.twitch.entry"),
|
||||
_layout,
|
||||
{{"{{conditions}}", _conditions},
|
||||
{"{{streamTitle}}", _streamTitle},
|
||||
{"{{category}}", _category},
|
||||
{"{{regex}}", _regex},
|
||||
{"{{channel}}", _channel}});
|
||||
_layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto accountLayout = new QHBoxLayout();
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.twitch.entry.account"),
|
||||
accountLayout, {{"{{account}}", _tokens}});
|
||||
accountLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto mainLayout = new QVBoxLayout();
|
||||
mainLayout->addLayout(_layout);
|
||||
mainLayout->addLayout(accountLayout);
|
||||
mainLayout->addWidget(_tokenPermissionWarning);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_tokenPermissionCheckTimer.start(1000);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionTwitchEdit::TwitchTokenChanged(const QString &token)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_token = GetWeakTwitchTokenByQString(token);
|
||||
_category->SetToken(_entryData->_token);
|
||||
SetupWidgetVisibility();
|
||||
emit(HeaderInfoChanged(token));
|
||||
}
|
||||
|
||||
void MacroConditionTwitchEdit::StreamTitleChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_streamTitle = _streamTitle->text().toStdString();
|
||||
}
|
||||
|
||||
void MacroConditionTwitchEdit::CategoreyChanged(const TwitchCategory &category)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_category = category;
|
||||
}
|
||||
|
||||
void MacroConditionTwitchEdit::CheckTokenPermissions()
|
||||
{
|
||||
_tokenPermissionWarning->setVisible(
|
||||
_entryData && !_entryData->ConditionIsSupportedByToken());
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionTwitchEdit::SetupWidgetVisibility()
|
||||
{
|
||||
auto condition = _entryData->GetCondition();
|
||||
_streamTitle->setVisible(condition ==
|
||||
MacroConditionTwitch::Condition::TITLE);
|
||||
_regex->setVisible(condition == MacroConditionTwitch::Condition::TITLE);
|
||||
_category->setVisible(condition ==
|
||||
MacroConditionTwitch::Condition::CATEGORY);
|
||||
if (condition == MacroConditionTwitch::Condition::TITLE) {
|
||||
RemoveStretchIfPresent(_layout);
|
||||
} else {
|
||||
AddStretchIfNecessary(_layout);
|
||||
}
|
||||
|
||||
_tokenPermissionWarning->setVisible(
|
||||
!_entryData->ConditionIsSupportedByToken());
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionTwitchEdit::ChannelChanged(const TwitchChannel &channel)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_channel = channel;
|
||||
}
|
||||
|
||||
void MacroConditionTwitchEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_conditions->setCurrentIndex(_conditions->findData(
|
||||
static_cast<int>(_entryData->GetCondition())));
|
||||
_tokens->SetToken(_entryData->_token);
|
||||
_streamTitle->setText(_entryData->_streamTitle);
|
||||
_category->SetToken(_entryData->_token);
|
||||
_category->SetCategory(_entryData->_category);
|
||||
_channel->SetChannel(_entryData->_channel);
|
||||
_regex->SetRegexConfig(_entryData->_regex);
|
||||
SetupWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionTwitchEdit::ConditionChanged(int idx)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (idx == -1) { // Reset to previous selection
|
||||
_conditions->setCurrentIndex(_conditions->findData(
|
||||
static_cast<int>(_entryData->GetCondition())));
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->SetCondition(static_cast<MacroConditionTwitch::Condition>(
|
||||
_conditions->itemData(idx).toInt()));
|
||||
SetupWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionTwitchEdit::RegexChanged(RegexConfig conf)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_regex = conf;
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
129
src/macro-external/twitch/macro-condition-twitch.hpp
Normal file
129
src/macro-external/twitch/macro-condition-twitch.hpp
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "token.hpp"
|
||||
#include "category-selection.hpp"
|
||||
#include "channel-selection.hpp"
|
||||
|
||||
#include <variable-line-edit.hpp>
|
||||
#include <regex-config.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroConditionTwitch : public MacroCondition {
|
||||
public:
|
||||
MacroConditionTwitch(Macro *m) : MacroCondition(m, true) {}
|
||||
bool CheckCondition();
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetShortDesc() const;
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroCondition> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroConditionTwitch>(m);
|
||||
}
|
||||
bool ConditionIsSupportedByToken();
|
||||
|
||||
enum class Condition {
|
||||
// Event based
|
||||
LIVE_EVENT_REGULAR = 10,
|
||||
LIVE_EVENT_PLAYLIST = 20,
|
||||
LIVE_EVENT_WATCHPARTY = 30,
|
||||
LIVE_EVENT_PREMIERE = 40,
|
||||
LIVE_EVENT_RERUN = 50,
|
||||
OFFLINE_EVENT = 60,
|
||||
CHANNEL_UPDATE_EVENT = 70,
|
||||
FOLLOW_EVENT = 80,
|
||||
SUBSCRIBE_EVENT = 90,
|
||||
SUBSCRIBE_END_EVENT = 100,
|
||||
SUBSCRIBE_GIFT_EVENT = 110,
|
||||
SUBSCRIBE_MESSAGE_EVENT = 120,
|
||||
CHEER_EVENT = 130,
|
||||
|
||||
// Polling
|
||||
LIVE = 1000,
|
||||
TITLE = 1010,
|
||||
CATEGORY = 1020,
|
||||
};
|
||||
|
||||
void SetCondition(const Condition &);
|
||||
Condition GetCondition() { return _condition; }
|
||||
|
||||
std::weak_ptr<TwitchToken> _token;
|
||||
TwitchChannel _channel;
|
||||
StringVariable _streamTitle = obs_module_text(
|
||||
"AdvSceneSwitcher.condition.twitch.title.title");
|
||||
RegexConfig _regex = RegexConfig::PartialMatchRegexConfig();
|
||||
TwitchCategory _category;
|
||||
|
||||
private:
|
||||
bool CheckChannelLiveEvents(TwitchToken &);
|
||||
bool CheckChannelOfflineEvents(TwitchToken &);
|
||||
bool CheckChannelUpdateEvents(TwitchToken &);
|
||||
bool CheckChannelFollowEvents(TwitchToken &);
|
||||
bool CheckChannelSubscribeEvents(TwitchToken &);
|
||||
bool CheckChannelCheerEvents(TwitchToken &);
|
||||
|
||||
bool IsUsingEventSubCondition();
|
||||
void SetupEventSubscriptions();
|
||||
void CheckEventSubscription(EventSub &);
|
||||
void AddChannelLiveEventSubscription();
|
||||
void AddChannelOfflineEventSubscription();
|
||||
void AddChannelUpdateEventSubscription();
|
||||
void AddChannelFollowEventSubscription();
|
||||
void AddChannelSubscribeEventSubscription();
|
||||
void AddChannelCheerEventSubscription();
|
||||
|
||||
Condition _condition = Condition::LIVE;
|
||||
std::future<std::string> _subscriptionIDFuture;
|
||||
std::string _subscriptionID;
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionTwitchEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionTwitchEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionTwitch> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionTwitchEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionTwitch>(cond));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ConditionChanged(int);
|
||||
void TwitchTokenChanged(const QString &);
|
||||
void StreamTitleChanged();
|
||||
void CategoreyChanged(const TwitchCategory &);
|
||||
void ChannelChanged(const TwitchChannel &);
|
||||
void RegexChanged(RegexConfig);
|
||||
void CheckTokenPermissions();
|
||||
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<MacroConditionTwitch> _entryData;
|
||||
|
||||
private:
|
||||
void SetupWidgetVisibility();
|
||||
|
||||
FilterComboBox *_conditions;
|
||||
TwitchConnectionSelection *_tokens;
|
||||
VariableLineEdit *_streamTitle;
|
||||
TwitchCategoryWidget *_category;
|
||||
TwitchChannelSelection *_channel;
|
||||
RegexConfigWidget *_regex;
|
||||
QHBoxLayout *_layout;
|
||||
QLabel *_tokenPermissionWarning;
|
||||
QTimer _tokenPermissionCheckTimer;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
|
@ -21,6 +21,11 @@ const std::unordered_map<std::string, std::string> TokenOption::_apiIdToLocale{
|
|||
{"moderator:manage:chat_settings",
|
||||
"AdvSceneSwitcher.twitchToken.moderator.manageChatSettings"},
|
||||
{"channel:manage:raids", "AdvSceneSwitcher.twitchToken.channel.raid"},
|
||||
{"moderator:read:followers",
|
||||
"AdvSceneSwitcher.twitchToken.readSubscriptions"},
|
||||
{"channel:read:subscriptions",
|
||||
"AdvSceneSwitcher.twitchToken.readFollowers"},
|
||||
{"bits:read", "AdvSceneSwitcher.twitchToken.bits"},
|
||||
};
|
||||
|
||||
static void saveConnections(obs_data_t *obj);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user