Compare commits

...

8 Commits

Author SHA1 Message Date
Przemek Pawlas
bb7ee17362 Prepare Twitch action for introduction of new action types 2023-11-26 17:48:45 +01:00
Przemek Pawlas
70854ef012 Reorder Twitch condition enum value definitions 2023-11-26 17:48:45 +01:00
Przemek Pawlas
053c14056d Add more token options and reorder existing ones 2023-11-26 17:48:45 +01:00
Przemek Pawlas
9397d50764 Refactor twitch-helpers
Added more http helpers, reordered parameters, and reduced code
duplication
2023-11-26 17:48:45 +01:00
Przemek Pawlas
473e24cc42 Use consistent function naming for setting widget visibility 2023-11-26 17:48:45 +01:00
WarmUpTill
d9394605f4 Set tempvar values for raid events 2023-11-26 15:29:54 +01:00
WarmUpTill
d2b9074852 Fix build for Twitch mock 2023-11-26 15:29:54 +01:00
WarmUpTill
b525eb7a0e Add temp variable support to MIDI condition 2023-11-26 13:05:44 +01:00
18 changed files with 733 additions and 323 deletions

View File

@@ -852,15 +852,15 @@ AdvSceneSwitcher.action.sceneLock.type.unlock="unlock"
AdvSceneSwitcher.action.sceneLock.type.toggle="toggle lock of" AdvSceneSwitcher.action.sceneLock.type.toggle="toggle lock of"
AdvSceneSwitcher.action.sceneLock.entry="On{{scenes}}{{actions}}{{sources}}" AdvSceneSwitcher.action.sceneLock.entry="On{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.twitch="Twitch" AdvSceneSwitcher.action.twitch="Twitch"
AdvSceneSwitcher.action.twitch.type.title="Set stream title" AdvSceneSwitcher.action.twitch.type.channel.info.title.set="Set stream title"
AdvSceneSwitcher.action.twitch.type.category="Set stream category" AdvSceneSwitcher.action.twitch.type.channel.info.category.set="Set stream category"
AdvSceneSwitcher.action.twitch.type.marker="Create stream marker" AdvSceneSwitcher.action.twitch.type.raid.start="Start raid"
AdvSceneSwitcher.action.twitch.type.clip="Create stream clip" AdvSceneSwitcher.action.twitch.type.commercial.start="Start commercial"
AdvSceneSwitcher.action.twitch.type.commercial="Start commercial with duration" AdvSceneSwitcher.action.twitch.type.marker.create="Create stream marker"
AdvSceneSwitcher.action.twitch.type.announcement="Send chat announcement" AdvSceneSwitcher.action.twitch.type.clip.create="Create stream clip"
AdvSceneSwitcher.action.twitch.type.emoteOnlyEnable="Enable chat's emote-only mode" AdvSceneSwitcher.action.twitch.type.chat.announcement.send="Send chat announcement"
AdvSceneSwitcher.action.twitch.type.emoteOnlyDisable="Disable chat's emote-only mode" AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.enable="Enable chat's emote-only mode"
AdvSceneSwitcher.action.twitch.type.raid="Raid channel" AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable="Disable chat's emote-only mode"
AdvSceneSwitcher.action.twitch.type.sendChatMessage="Send chat message" AdvSceneSwitcher.action.twitch.type.sendChatMessage="Send chat message"
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Cannot select category without selecting a Twitch account first!" AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Cannot select category without selecting a Twitch account first!"
AdvSceneSwitcher.action.twitch.entry.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}" AdvSceneSwitcher.action.twitch.entry.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}"
@@ -1027,14 +1027,10 @@ AdvSceneSwitcher.twitchToken.request.notSet="Account is not connected!"
AdvSceneSwitcher.twitchToken.permissions="Token permissions:" AdvSceneSwitcher.twitchToken.permissions="Token permissions:"
AdvSceneSwitcher.twitchToken.permissionsInsufficient="Permissions of selected token are insufficient to perform selected action!" AdvSceneSwitcher.twitchToken.permissionsInsufficient="Permissions of selected token are insufficient to perform selected action!"
AdvSceneSwitcher.twitchToken.channel.broadcast.manage="Manage a channels broadcast configuration, including updating channel configuration and managing stream markers and stream tags." AdvSceneSwitcher.twitchToken.channel.broadcast.manage="Manage a channels broadcast configuration, including updating channel configuration and managing stream markers and stream tags."
AdvSceneSwitcher.twitchToken.channel.commercial.edit="Run commercials on a channel."
AdvSceneSwitcher.twitchToken.clips.edit="Create clips from channel's broadcasts."
AdvSceneSwitcher.twitchToken.moderator.announcements.manage="Manage channel's chat announcements."
AdvSceneSwitcher.twitchToken.moderator.chat.settings.manage="Manage channel's chat settings, such as emote-only or slow mode."
AdvSceneSwitcher.twitchToken.channel.raid="Manage channel's raids."
AdvSceneSwitcher.twitchToken.channel.subscriptions.read="Read channel's subscribers list and info." AdvSceneSwitcher.twitchToken.channel.subscriptions.read="Read channel's subscribers list and info."
AdvSceneSwitcher.twitchToken.moderator.followers.read="Read the followers of a broadcaster." AdvSceneSwitcher.twitchToken.moderator.followers.read="Read the followers of a broadcaster."
AdvSceneSwitcher.twitchToken.bits.read="Read channel's Bits information." AdvSceneSwitcher.twitchToken.bits.read="Read channel's Bits information."
AdvSceneSwitcher.twitchToken.channel.raids.manage="Manage channel's raids."
AdvSceneSwitcher.twitchToken.moderator.shoutouts.read="Read channel's shoutouts." AdvSceneSwitcher.twitchToken.moderator.shoutouts.read="Read channel's shoutouts."
AdvSceneSwitcher.twitchToken.moderator.shoutouts.manage="Manage channel's shoutouts." AdvSceneSwitcher.twitchToken.moderator.shoutouts.manage="Manage channel's shoutouts."
AdvSceneSwitcher.twitchToken.channel.polls.read="Read channel's polls' state." AdvSceneSwitcher.twitchToken.channel.polls.read="Read channel's polls' state."
@@ -1049,7 +1045,17 @@ AdvSceneSwitcher.twitchToken.moderator.shieldMode.manage="Manage channel's Shiel
AdvSceneSwitcher.twitchToken.channel.redemptions.read="Read channel's point rewards and their redemptions." AdvSceneSwitcher.twitchToken.channel.redemptions.read="Read channel's point rewards and their redemptions."
AdvSceneSwitcher.twitchToken.channel.redemptions.manage="Manage channel's point rewards and their redemptions." AdvSceneSwitcher.twitchToken.channel.redemptions.manage="Manage channel's point rewards and their redemptions."
AdvSceneSwitcher.twitchToken.channel.moderate="Moderate the channel." AdvSceneSwitcher.twitchToken.channel.moderate="Moderate the channel."
AdvSceneSwitcher.twitchToken.channel.moderation.read="Read channel's moderation data." AdvSceneSwitcher.twitchToken.moderator.bannedUsers.manage="Manage channel's banned users."
AdvSceneSwitcher.twitchToken.user.blockedUsers.manage="Manage user's blocked users."
AdvSceneSwitcher.twitchToken.moderation.read="Read channel's moderation data."
AdvSceneSwitcher.twitchToken.channel.moderators.manage="Manage channel's moderators."
AdvSceneSwitcher.twitchToken.channel.vips.manage="Manage channel's VIPs."
AdvSceneSwitcher.twitchToken.channel.commercial.edit="Run commercials on a channel."
AdvSceneSwitcher.twitchToken.channel.ads.manage="Manage channel's commercials."
AdvSceneSwitcher.twitchToken.clips.edit="Create clips from channel's broadcasts."
AdvSceneSwitcher.twitchToken.moderator.announcements.manage="Manage channel's chat announcements."
AdvSceneSwitcher.twitchToken.moderator.chat.settings.manage="Manage channel's chat settings, such as emote-only or slow mode."
AdvSceneSwitcher.twitchToken.user.whispers.manage="Manage user's whispers."
AdvSceneSwitcher.twitchToken.chat.read="View live stream chat messages." AdvSceneSwitcher.twitchToken.chat.read="View live stream chat messages."
AdvSceneSwitcher.twitchToken.chat.edit="Send live stream chat messages." AdvSceneSwitcher.twitchToken.chat.edit="Send live stream chat messages."
@@ -1412,6 +1418,20 @@ AdvSceneSwitcher.tempVar.twitch.chatter="User login"
AdvSceneSwitcher.tempVar.twitch.chatter.description="The user login of the person who sent the chat message." AdvSceneSwitcher.tempVar.twitch.chatter.description="The user login of the person who sent the chat message."
AdvSceneSwitcher.tempVar.twitch.chat_message="Chat message" AdvSceneSwitcher.tempVar.twitch.chat_message="Chat message"
AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_id.raid="Raid creator user ID"
AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_id.raid.description="The broadcaster ID that created the raid."
AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_login.raid="Raid creator user login"
AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_login.raid.description="The broadcaster login that created the raid."
AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_name.raid="Raid creator user name"
AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_name.raid.description="The broadcaster display name that created the raid."
AdvSceneSwitcher.tempVar.twitch.to_broadcaster_user_id.raid="Raid receiver user ID"
AdvSceneSwitcher.tempVar.twitch.to_broadcaster_user_id.raid.description="The broadcaster ID that created the raid."
AdvSceneSwitcher.tempVar.twitch.to_broadcaster_user_login.raid="Raid receiver user login"
AdvSceneSwitcher.tempVar.twitch.to_broadcaster_user_login.raid.description="The broadcaster login that created the raid."
AdvSceneSwitcher.tempVar.twitch.to_broadcaster_user_name.raid="Raid receiver user name"
AdvSceneSwitcher.tempVar.twitch.to_broadcaster_user_name.raid.description="The broadcaster display name that created the raid."
AdvSceneSwitcher.tempVar.twitch.viewers.raid="Raid participants"
AdvSceneSwitcher.tempVar.twitch.viewers.raid.description="The number of viewers in the raid."
AdvSceneSwitcher.tempVar.audio.output_volume="Output volume" AdvSceneSwitcher.tempVar.audio.output_volume="Output volume"
AdvSceneSwitcher.tempVar.audio.output_volume.description="The volume the audio source is outputting." AdvSceneSwitcher.tempVar.audio.output_volume.description="The volume the audio source is outputting."
@@ -1442,6 +1462,12 @@ AdvSceneSwitcher.tempVar.date.minute="Minute"
AdvSceneSwitcher.tempVar.date.second="Second" AdvSceneSwitcher.tempVar.date.second="Second"
AdvSceneSwitcher.tempVar.date.dayOfWeek="Day of week" AdvSceneSwitcher.tempVar.date.dayOfWeek="Day of week"
AdvSceneSwitcher.tempVar.midi.type="Message type"
AdvSceneSwitcher.tempVar.midi.channel="Channel"
AdvSceneSwitcher.tempVar.midi.note="Note"
AdvSceneSwitcher.tempVar.midi.value1="Value 1"
AdvSceneSwitcher.tempVar.midi.value2="Value 2"
AdvSceneSwitcher.selectScene="--select scene--" AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene" AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current Scene" AdvSceneSwitcher.selectCurrentScene="Current Scene"

View File

@@ -736,9 +736,9 @@ AdvSceneSwitcher.action.sceneLock.type.unlock="déverrouiller"
AdvSceneSwitcher.action.sceneLock.type.toggle="basculer le verrouillage de" AdvSceneSwitcher.action.sceneLock.type.toggle="basculer le verrouillage de"
AdvSceneSwitcher.action.sceneLock.entry="Sur{{scenes}}{{actions}}{{sources}}" AdvSceneSwitcher.action.sceneLock.entry="Sur{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.twitch="Twitch" AdvSceneSwitcher.action.twitch="Twitch"
AdvSceneSwitcher.action.twitch.type.title="Définir le titre du flux" AdvSceneSwitcher.action.twitch.type.channel.info.title.set="Définir le titre du flux"
AdvSceneSwitcher.action.twitch.type.category="Définir la catégorie du flux" AdvSceneSwitcher.action.twitch.type.channel.info.category.set="Définir la catégorie du flux"
AdvSceneSwitcher.action.twitch.type.commercial="Démarrer une publicité d'une durée de" AdvSceneSwitcher.action.twitch.type.commercial.start="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.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}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}" AdvSceneSwitcher.action.twitch.entry.line1="Sur{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}"

View File

@@ -192,7 +192,7 @@ void MacroActionWebsocketEdit::CheckForSettingsConflict()
updateGeometry(); updateGeometry();
} }
void MacroActionWebsocketEdit::SetupWidgetVisibility() void MacroActionWebsocketEdit::SetWidgetVisibility()
{ {
_messageType->setVisible(_entryData->_api == _messageType->setVisible(_entryData->_api ==
MacroActionWebsocket::API::SCENE_SWITCHER); MacroActionWebsocket::API::SCENE_SWITCHER);
@@ -283,7 +283,7 @@ void MacroActionWebsocketEdit::UpdateEntryData()
_message->setPlainText(_entryData->_message); _message->setPlainText(_entryData->_message);
_connection->SetConnection(_entryData->_connection); _connection->SetConnection(_entryData->_connection);
SetupWidgetVisibility(); SetWidgetVisibility();
} }
void MacroActionWebsocketEdit::APITypeChanged(int index) void MacroActionWebsocketEdit::APITypeChanged(int index)
@@ -294,7 +294,7 @@ void MacroActionWebsocketEdit::APITypeChanged(int index)
auto lock = LockContext(); auto lock = LockContext();
_entryData->_api = static_cast<MacroActionWebsocket::API>(index); _entryData->_api = static_cast<MacroActionWebsocket::API>(index);
SetupWidgetVisibility(); SetWidgetVisibility();
emit HeaderInfoChanged( emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc())); QString::fromStdString(_entryData->GetShortDesc()));
} }
@@ -308,7 +308,7 @@ void MacroActionWebsocketEdit::MessageTypeChanged(int index)
auto lock = LockContext(); auto lock = LockContext();
_entryData->_type = _entryData->_type =
static_cast<MacroActionWebsocket::MessageType>(index); static_cast<MacroActionWebsocket::MessageType>(index);
SetupWidgetVisibility(); SetWidgetVisibility();
emit HeaderInfoChanged( emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc())); QString::fromStdString(_entryData->GetShortDesc()));
} }

View File

@@ -76,7 +76,7 @@ protected:
private: private:
void CheckForSettingsConflict(); void CheckForSettingsConflict();
void SetupWidgetVisibility(); void SetWidgetVisibility();
void SetupRequestEdit(); void SetupRequestEdit();
void SetupEventEdit(); void SetupEventEdit();
void SetupGenericEdit(); void SetupGenericEdit();

View File

@@ -19,8 +19,7 @@ bool MacroConditionMidi::CheckCondition()
for (const auto &m : *messages) { for (const auto &m : *messages) {
if (m.Matches(_message)) { if (m.Matches(_message)) {
SetVariableValue(std::to_string(m.Note()) + " " + SetVariableValues(m);
std::to_string(m.Value()));
return true; return true;
} }
} }
@@ -49,6 +48,36 @@ std::string MacroConditionMidi::GetShortDesc() const
return _device.Name(); return _device.Name();
} }
void MacroConditionMidi::SetupTempVars()
{
MacroCondition::SetupTempVars();
AddTempvar("type",
obs_module_text("AdvSceneSwitcher.tempVar.midi.type"));
AddTempvar("channel",
obs_module_text("AdvSceneSwitcher.tempVar.midi.channel"));
AddTempvar("note",
obs_module_text("AdvSceneSwitcher.tempVar.midi.note"));
AddTempvar("value1",
obs_module_text("AdvSceneSwitcher.tempVar.midi.value1"));
AddTempvar("value2",
obs_module_text("AdvSceneSwitcher.tempVar.midi.value2"));
}
void MacroConditionMidi::SetVariableValues(const MidiMessage &m)
{
SetVariableValue(std::to_string(m.Note()) + " " +
std::to_string(m.Value()));
SetTempVarValue("type", m.MidiTypeToString(m.Type()));
SetTempVarValue("channel", std::to_string(m.Channel()));
try {
SetTempVarValue("note",
GetAllNotes().at(m.Note()).toStdString());
} catch (...) {
}
SetTempVarValue("value1", std::to_string(m.Note()));
SetTempVarValue("value2", std::to_string(m.Value()));
}
MacroConditionMidiEdit::MacroConditionMidiEdit( MacroConditionMidiEdit::MacroConditionMidiEdit(
QWidget *parent, std::shared_ptr<MacroConditionMidi> entryData) QWidget *parent, std::shared_ptr<MacroConditionMidi> entryData)
: QWidget(parent), : QWidget(parent),

View File

@@ -24,6 +24,9 @@ public:
MidiMessage _message; MidiMessage _message;
private: private:
void SetupTempVars();
void SetVariableValues(const MidiMessage &);
static bool _registered; static bool _registered;
static const std::string id; static const std::string id;
}; };

View File

@@ -190,7 +190,7 @@ void CategoryGrabber::Search(const std::string &)
std::string cursor; std::string cursor;
httplib::Params params = { httplib::Params params = {
{"first", "100"}, {"after", cursor}, {"query", _searchString}}; {"first", "100"}, {"after", cursor}, {"query", _searchString}};
auto response = SendGetRequest(uri, path, *_token, params); auto response = SendGetRequest(*_token, uri, path, params);
while (response.status == 200 && !_stop) { while (response.status == 200 && !_stop) {
cursor = ParseReply(response.data); cursor = ParseReply(response.data);
@@ -200,7 +200,7 @@ void CategoryGrabber::Search(const std::string &)
params = {{"first", "100"}, params = {{"first", "100"},
{"after", cursor}, {"after", cursor},
{"query", _searchString}}; {"query", _searchString}};
response = SendGetRequest(uri, path, *_token, params); response = SendGetRequest(*_token, uri, path, params);
emit CategoryCountUpdated(_categoryMap.size() - startCount); emit CategoryCountUpdated(_categoryMap.size() - startCount);
} }
} }
@@ -214,7 +214,7 @@ void CategoryGrabber::GetAll()
static std::string cursor; static std::string cursor;
httplib::Params params = {{"first", "100"}, {"after", cursor}}; httplib::Params params = {{"first", "100"}, {"after", cursor}};
auto response = SendGetRequest(uri, path, *_token, params); auto response = SendGetRequest(*_token, uri, path, params);
while (response.status == 200 && !_stop) { while (response.status == 200 && !_stop) {
cursor = ParseReply(response.data); cursor = ParseReply(response.data);
@@ -222,7 +222,7 @@ void CategoryGrabber::GetAll()
break; // End of category list break; // End of category list
} }
params = {{"first", "100"}, {"after", cursor}}; params = {{"first", "100"}, {"after", cursor}};
response = SendGetRequest(uri, path, *_token, params); response = SendGetRequest(*_token, uri, path, params);
emit CategoryCountUpdated(_categoryMap.size()); emit CategoryCountUpdated(_categoryMap.size());
} }
} }

View File

@@ -35,8 +35,8 @@ std::string TwitchChannel::GetUserID(const TwitchToken &token) const
return it->second; return it->second;
} }
auto res = SendGetRequest("https://api.twitch.tv", "/helix/users", auto res = SendGetRequest(token, "https://api.twitch.tv",
token, {{"login", _name}}); "/helix/users", {{"login", _name}});
if (res.status == 400) { if (res.status == 400) {
userIDCache[_name] = "invalid"; userIDCache[_name] = "invalid";
@@ -112,8 +112,8 @@ TwitchChannel::GetLiveInfo(const TwitchToken &token)
httplib::Params params = { httplib::Params params = {
{"first", "1"}, {"after", ""}, {"user_id", id}}; {"first", "1"}, {"after", ""}, {"user_id", id}};
auto result = SendGetRequest("https://api.twitch.tv", "/helix/streams", auto result = SendGetRequest(token, "https://api.twitch.tv",
token, params, true); "/helix/streams", params, true);
if (result.status != 200) { if (result.status != 200) {
return {}; return {};
} }
@@ -153,8 +153,8 @@ std::optional<ChannelInfo> TwitchChannel::GetInfo(const TwitchToken &token)
httplib::Params params = { httplib::Params params = {
{"first", "1"}, {"after", ""}, {"broadcaster_id", id}}; {"first", "1"}, {"after", ""}, {"broadcaster_id", id}};
auto result = SendGetRequest("https://api.twitch.tv", "/helix/channels", auto result = SendGetRequest(token, "https://api.twitch.tv",
token, params, true); "/helix/channels", params, true);
if (result.status != 200) { if (result.status != 200) {
return {}; return {};
} }

View File

@@ -432,8 +432,8 @@ void TwitchChatConnection::ConnectThread()
_client.reset(); _client.reset();
_connected = true; _connected = true;
websocketpp::lib::error_code ec; websocketpp::lib::error_code ec;
EventSubWSClient::connection_ptr con = websocketpp::client<websocketpp::config::asio_tls_client>::
_client.get_connection(_url, ec); connection_ptr con = _client.get_connection(_url, ec);
if (ec) { if (ec) {
blog(LOG_INFO, "Twitch TwitchChatConnection failed: %s", blog(LOG_INFO, "Twitch TwitchChatConnection failed: %s",
ec.message().c_str()); ec.message().c_str());
@@ -634,8 +634,10 @@ void TwitchChatConnection::OnOpen(connection_hdl)
Authenticate(); Authenticate();
} }
void TwitchChatConnection::OnMessage(connection_hdl, void TwitchChatConnection::OnMessage(
EventSubWSClient::message_ptr message) connection_hdl,
websocketpp::client<websocketpp::config::asio_tls_client>::message_ptr
message)
{ {
constexpr std::string_view authOKCommand = "001"; constexpr std::string_view authOKCommand = "001";
constexpr std::string_view pingCommand = "PING"; constexpr std::string_view pingCommand = "PING";
@@ -679,7 +681,8 @@ void TwitchChatConnection::OnMessage(connection_hdl,
void TwitchChatConnection::OnClose(connection_hdl hdl) void TwitchChatConnection::OnClose(connection_hdl hdl)
{ {
EventSubWSClient::connection_ptr con = _client.get_con_from_hdl(hdl); websocketpp::client<websocketpp::config::asio_tls_client>::connection_ptr
con = _client.get_con_from_hdl(hdl);
auto msg = con->get_ec().message(); auto msg = con->get_ec().message();
blog(LOG_INFO, "Twitch chat connection closed: %s", msg.c_str()); blog(LOG_INFO, "Twitch chat connection closed: %s", msg.c_str());
_connected = false; _connected = false;
@@ -687,7 +690,8 @@ void TwitchChatConnection::OnClose(connection_hdl hdl)
void TwitchChatConnection::OnFail(connection_hdl hdl) void TwitchChatConnection::OnFail(connection_hdl hdl)
{ {
EventSubWSClient::connection_ptr con = _client.get_con_from_hdl(hdl); websocketpp::client<websocketpp::config::asio_tls_client>::connection_ptr
con = _client.get_con_from_hdl(hdl);
auto msg = con->get_ec().message(); auto msg = con->get_ec().message();
blog(LOG_INFO, "Twitch chat connection failed: %s", msg.c_str()); blog(LOG_INFO, "Twitch chat connection failed: %s", msg.c_str());
_connected = false; _connected = false;

View File

@@ -230,14 +230,16 @@ std::string EventSub::AddEventSubscribtion(std::shared_ptr<TwitchToken> token,
OBSDataAutoRelease postData = copyData(subscription.data); OBSDataAutoRelease postData = copyData(subscription.data);
setTransportData(postData.Get(), eventSub->_sessionID); setTransportData(postData.Get(), eventSub->_sessionID);
auto result = SendPostRequest(registerSubscriptionURL.data(), auto result = SendPostRequest(*token, registerSubscriptionURL.data(),
registerSubscriptionPath.data(), *token, registerSubscriptionPath.data(), {},
postData.Get()); postData.Get());
if (result.status != 202) { if (result.status != 202) {
vblog(LOG_INFO, "failed to register Twitch EventSub (%d)", vblog(LOG_INFO, "failed to register Twitch EventSub (%d)",
result.status); result.status);
return ""; return "";
} }
OBSDataArrayAutoRelease replyArray = OBSDataArrayAutoRelease replyArray =
obs_data_get_array(result.data, "data"); obs_data_get_array(result.data, "data");
OBSDataAutoRelease replyData = obs_data_array_item(replyArray, 0); OBSDataAutoRelease replyData = obs_data_array_item(replyArray, 0);

View File

@@ -19,24 +19,24 @@ std::string MacroActionTwitch::GetShortDesc() const
} }
const static std::map<MacroActionTwitch::Action, std::string> actionTypes = { const static std::map<MacroActionTwitch::Action, std::string> actionTypes = {
{MacroActionTwitch::Action::TITLE, {MacroActionTwitch::Action::CHANNEL_INFO_TITLE_SET,
"AdvSceneSwitcher.action.twitch.type.title"}, "AdvSceneSwitcher.action.twitch.type.channel.info.title.set"},
{MacroActionTwitch::Action::CATEGORY, {MacroActionTwitch::Action::CHANNEL_INFO_CATEGORY_SET,
"AdvSceneSwitcher.action.twitch.type.category"}, "AdvSceneSwitcher.action.twitch.type.channel.info.category.set"},
{MacroActionTwitch::Action::MARKER, {MacroActionTwitch::Action::RAID_START,
"AdvSceneSwitcher.action.twitch.type.marker"}, "AdvSceneSwitcher.action.twitch.type.raid.start"},
{MacroActionTwitch::Action::CLIP, {MacroActionTwitch::Action::COMMERCIAL_START,
"AdvSceneSwitcher.action.twitch.type.clip"}, "AdvSceneSwitcher.action.twitch.type.commercial.start"},
{MacroActionTwitch::Action::COMMERCIAL, {MacroActionTwitch::Action::MARKER_CREATE,
"AdvSceneSwitcher.action.twitch.type.commercial"}, "AdvSceneSwitcher.action.twitch.type.marker.create"},
{MacroActionTwitch::Action::ANNOUNCEMENT, {MacroActionTwitch::Action::CLIP_CREATE,
"AdvSceneSwitcher.action.twitch.type.announcement"}, "AdvSceneSwitcher.action.twitch.type.clip.create"},
{MacroActionTwitch::Action::ENABLE_EMOTE_ONLY, {MacroActionTwitch::Action::CHAT_ANNOUNCEMENT_SEND,
"AdvSceneSwitcher.action.twitch.type.emoteOnlyEnable"}, "AdvSceneSwitcher.action.twitch.type.chat.announcement.send"},
{MacroActionTwitch::Action::DISABLE_EMOTE_ONLY, {MacroActionTwitch::Action::CHAT_EMOTE_ONLY_ENABLE,
"AdvSceneSwitcher.action.twitch.type.emoteOnlyDisable"}, "AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.enable"},
{MacroActionTwitch::Action::RAID, {MacroActionTwitch::Action::CHAT_EMOTE_ONLY_DISABLE,
"AdvSceneSwitcher.action.twitch.type.raid"}, "AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable"},
{MacroActionTwitch::Action::SEND_CHAT_MESSAGE, {MacroActionTwitch::Action::SEND_CHAT_MESSAGE,
"AdvSceneSwitcher.action.twitch.type.sendChatMessage"}, "AdvSceneSwitcher.action.twitch.type.sendChatMessage"},
}; };
@@ -64,6 +64,12 @@ const static std::map<MacroActionTwitch::AnnouncementColor, std::string>
{MacroActionTwitch::AnnouncementColor::PURPLE, "purple"}, {MacroActionTwitch::AnnouncementColor::PURPLE, "purple"},
}; };
const static std::map<MacroActionTwitch::RedemptionStatus, std::string>
redemptionStatusesTwitch = {
{MacroActionTwitch::RedemptionStatus::CANCELED, "CANCELED"},
{MacroActionTwitch::RedemptionStatus::FULFILLED, "FULFILLED"},
};
void MacroActionTwitch::SetStreamTitle( void MacroActionTwitch::SetStreamTitle(
const std::shared_ptr<TwitchToken> &token) const const std::shared_ptr<TwitchToken> &token) const
{ {
@@ -73,11 +79,10 @@ void MacroActionTwitch::SetStreamTitle(
OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "title", _streamTitle.c_str()); obs_data_set_string(data, "title", _streamTitle.c_str());
auto result = SendPatchRequest( auto result = SendPatchRequest(*token, "https://api.twitch.tv",
"https://api.twitch.tv", "/helix/channels",
std::string("/helix/channels?broadcaster_id=") + {{"broadcaster_id", token->GetUserID()}},
token->GetUserID(), data.Get());
*token, data.Get());
if (result.status != 204) { if (result.status != 204) {
blog(LOG_INFO, "Failed to set stream title! (%d)", blog(LOG_INFO, "Failed to set stream title! (%d)",
@@ -95,11 +100,11 @@ void MacroActionTwitch::SetStreamCategory(
OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "game_id", obs_data_set_string(data, "game_id",
std::to_string(_category.id).c_str()); std::to_string(_category.id).c_str());
auto result = SendPatchRequest( auto result = SendPatchRequest(*token, "https://api.twitch.tv",
"https://api.twitch.tv", "/helix/channels",
std::string("/helix/channels?broadcaster_id=") + {{"broadcaster_id", token->GetUserID()}},
token->GetUserID(), data.Get());
*token, data.Get());
if (result.status != 204) { if (result.status != 204) {
blog(LOG_INFO, "Failed to set stream category! (%d)", blog(LOG_INFO, "Failed to set stream category! (%d)",
result.status); result.status);
@@ -117,9 +122,8 @@ void MacroActionTwitch::CreateStreamMarker(
_markerDescription.c_str()); _markerDescription.c_str());
} }
auto result = SendPostRequest("https://api.twitch.tv", auto result = SendPostRequest(*token, "https://api.twitch.tv",
"/helix/streams/markers", *token, "/helix/streams/markers", {}, data.Get());
data.Get());
if (result.status != 200) { if (result.status != 200) {
blog(LOG_INFO, "Failed to create marker! (%d)", result.status); blog(LOG_INFO, "Failed to create marker! (%d)", result.status);
@@ -129,14 +133,12 @@ void MacroActionTwitch::CreateStreamMarker(
void MacroActionTwitch::CreateStreamClip( void MacroActionTwitch::CreateStreamClip(
const std::shared_ptr<TwitchToken> &token) const const std::shared_ptr<TwitchToken> &token) const
{ {
OBSDataAutoRelease data = obs_data_create();
auto hasDelay = _clipHasDelay ? "true" : "false"; auto hasDelay = _clipHasDelay ? "true" : "false";
auto result = SendPostRequest( auto result = SendPostRequest(*token, "https://api.twitch.tv",
"https://api.twitch.tv", "/helix/clips",
"/helix/clips?broadcaster_id=" + token->GetUserID() + {{"broadcaster_id", token->GetUserID()},
"&has_delay=" + hasDelay, {"has_delay", hasDelay}});
*token, data.Get());
if (result.status != 202) { if (result.status != 202) {
blog(LOG_INFO, "Failed to create clip! (%d)", result.status); blog(LOG_INFO, "Failed to create clip! (%d)", result.status);
@@ -149,9 +151,10 @@ void MacroActionTwitch::StartCommercial(
OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "broadcaster_id", token->GetUserID().c_str()); obs_data_set_string(data, "broadcaster_id", token->GetUserID().c_str());
obs_data_set_int(data, "length", _duration.Seconds()); obs_data_set_int(data, "length", _duration.Seconds());
auto result = SendPostRequest("https://api.twitch.tv", auto result = SendPostRequest(*token, "https://api.twitch.tv",
"/helix/channels/commercial", *token, "/helix/channels/commercial", {},
data.Get()); data.Get());
if (result.status != 200) { if (result.status != 200) {
OBSDataArrayAutoRelease replyArray = OBSDataArrayAutoRelease replyArray =
obs_data_get_array(result.data, "data"); obs_data_get_array(result.data, "data");
@@ -178,11 +181,10 @@ void MacroActionTwitch::SendChatAnnouncement(
announcementColorsTwitch.at(_announcementColor).c_str()); announcementColorsTwitch.at(_announcementColor).c_str());
auto userId = token->GetUserID(); auto userId = token->GetUserID();
auto result = auto result = SendPostRequest(
SendPostRequest("https://api.twitch.tv", *token, "https://api.twitch.tv", "/helix/chat/announcements",
"/helix/chat/announcements?broadcaster_id=" + {{"broadcaster_id", userId}, {"moderator_id", userId}},
userId + "&moderator_id=" + userId, data.Get());
*token, data.Get());
if (result.status != 204) { if (result.status != 204) {
blog(LOG_INFO, "Failed to send chat announcement! (%d)", blog(LOG_INFO, "Failed to send chat announcement! (%d)",
@@ -197,11 +199,10 @@ void MacroActionTwitch::SetChatEmoteOnlyMode(
obs_data_set_bool(data, "emote_mode", enable); obs_data_set_bool(data, "emote_mode", enable);
auto userId = token->GetUserID(); auto userId = token->GetUserID();
auto result = auto result = SendPatchRequest(
SendPatchRequest("https://api.twitch.tv", *token, "https://api.twitch.tv", "/helix/chat/settings",
"/helix/chat/settings?broadcaster_id=" + {{"broadcaster_id", userId}, {"moderator_id", userId}},
userId + "&moderator_id=" + userId, data.Get());
*token, data.Get());
if (result.status != 200) { if (result.status != 200) {
blog(LOG_INFO, "Failed to %s chat's emote-only mode! (%d)", blog(LOG_INFO, "Failed to %s chat's emote-only mode! (%d)",
@@ -216,8 +217,9 @@ void MacroActionTwitch::StartRaid(const std::shared_ptr<TwitchToken> &token)
token->GetUserID().c_str()); token->GetUserID().c_str());
obs_data_set_string(data, "to_broadcaster_id", obs_data_set_string(data, "to_broadcaster_id",
_channel.GetUserID(*token).c_str()); _channel.GetUserID(*token).c_str());
auto result = SendPostRequest("https://api.twitch.tv", "/helix/raids", auto result = SendPostRequest(*token, "https://api.twitch.tv",
*token, data.Get()); "/helix/raids", {}, data.Get());
if (result.status != 200) { if (result.status != 200) {
blog(LOG_INFO, "Failed to start raid! (%d)\n", result.status); blog(LOG_INFO, "Failed to start raid! (%d)\n", result.status);
} }
@@ -243,33 +245,32 @@ bool MacroActionTwitch::PerformAction()
} }
switch (_action) { switch (_action) {
case MacroActionTwitch::Action::TITLE: case Action::CHANNEL_INFO_TITLE_SET:
SetStreamTitle(token); SetStreamTitle(token);
break; break;
case MacroActionTwitch::Action::CATEGORY: case Action::CHANNEL_INFO_CATEGORY_SET:
SetStreamCategory(token); SetStreamCategory(token);
break; break;
case MacroActionTwitch::Action::MARKER: case Action::RAID_START:
CreateStreamMarker(token);
break;
case MacroActionTwitch::Action::CLIP:
CreateStreamClip(token);
break;
case MacroActionTwitch::Action::COMMERCIAL:
StartCommercial(token);
break;
case MacroActionTwitch::Action::ANNOUNCEMENT:
SendChatAnnouncement(token);
break;
case MacroActionTwitch::Action::ENABLE_EMOTE_ONLY:
SetChatEmoteOnlyMode(token, true);
break;
case MacroActionTwitch::Action::DISABLE_EMOTE_ONLY:
SetChatEmoteOnlyMode(token, false);
break;
case MacroActionTwitch::Action::RAID:
StartRaid(token); StartRaid(token);
break; break;
case Action::COMMERCIAL_START:
StartCommercial(token);
break;
case Action::MARKER_CREATE:
CreateStreamMarker(token);
break;
case Action::CLIP_CREATE:
CreateStreamClip(token);
break;
case Action::CHAT_ANNOUNCEMENT_SEND:
SendChatAnnouncement(token);
break;
case Action::CHAT_EMOTE_ONLY_ENABLE:
SetChatEmoteOnlyMode(token, true);
break;
case Action::CHAT_EMOTE_ONLY_DISABLE:
SetChatEmoteOnlyMode(token, false);
case MacroActionTwitch::Action::SEND_CHAT_MESSAGE: case MacroActionTwitch::Action::SEND_CHAT_MESSAGE:
SendChatMessage(token); SendChatMessage(token);
break; break;
@@ -338,27 +339,111 @@ bool MacroActionTwitch::Load(obs_data_t *obj)
bool MacroActionTwitch::ActionIsSupportedByToken() bool MacroActionTwitch::ActionIsSupportedByToken()
{ {
static const std::unordered_map<Action, TokenOption> requiredOption = { static const std::unordered_map<Action, TokenOption> requiredOption = {
{Action::TITLE, {"channel:manage:broadcast"}}, {Action::CHANNEL_INFO_TITLE_SET, {"channel:manage:broadcast"}},
{Action::CATEGORY, {"channel:manage:broadcast"}}, {Action::CHANNEL_INFO_CATEGORY_SET,
{Action::MARKER, {"channel:manage:broadcast"}}, {"channel:manage:broadcast"}},
{Action::CLIP, {"clips:edit"}}, {Action::CHANNEL_INFO_LANGUAGE_SET,
{Action::COMMERCIAL, {"channel:edit:commercial"}}, {"channel:manage:broadcast"}},
{Action::ANNOUNCEMENT, {"moderator:manage:announcements"}}, {Action::CHANNEL_INFO_DELAY_SET, {"channel:manage:broadcast"}},
{Action::ENABLE_EMOTE_ONLY, {"moderator:manage:chat_settings"}}, {Action::CHANNEL_INFO_BRANDED_CONTENT_ENABLE,
{Action::DISABLE_EMOTE_ONLY, {"channel:manage:broadcast"}},
{Action::CHANNEL_INFO_BRANDED_CONTENT_DISABLE,
{"channel:manage:broadcast"}},
{Action::RAID_START, {"channel:manage:raids"}},
{Action::RAID_END, {"channel:manage:raids"}},
{Action::SHOUTOUT_SEND, {"moderator:manage:shoutouts"}},
{Action::POLL_END, {"channel:manage:polls"}},
{Action::PREDICTION_END, {"channel:manage:predictions"}},
{Action::SHIELD_MODE_START, {"moderator:manage:shield_mode"}},
{Action::SHIELD_MODE_END, {"moderator:manage:shield_mode"}},
{Action::POINTS_REWARD_ENABLE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_DISABLE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_PAUSE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_UNPAUSE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_TITLE_SET,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_PROMPT_SET,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_COST_SET,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_USER_INPUT_REQUIRE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_USER_INPUT_UNREQUIRE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_COOLDOWN_ENABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_COOLDOWN_DISABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_QUEUE_SKIP_ENABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_QUEUE_SKIP_DISABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_MAX_PER_STREAM_ENABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_MAX_PER_STREAM_DISABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_MAX_PER_USER_ENABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_MAX_PER_USER_DISABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_DELETE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_REDEMPTION_CANCEL,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_REDEMPTION_FULFILL,
{"channel:manage:redemptions"}},
{Action::USER_BAN, {"moderator:manage:banned_users"}},
{Action::USER_UNBAN, {"moderator:manage:banned_users"}},
{Action::USER_BLOCK, {"user:manage:blocked_users"}},
{Action::USER_UNBLOCK, {"user:manage:blocked_users"}},
{Action::USER_MODERATOR_ADD, {"channel:manage:moderators"}},
{Action::USER_MODERATOR_DELETE, {"channel:manage:moderators"}},
{Action::USER_VIP_ADD, {"channel:manage:vips"}},
{Action::USER_VIP_DELETE, {"channel:manage:vips"}},
{Action::COMMERCIAL_START, {"channel:edit:commercial"}},
{Action::COMMERCIAL_SNOOZE, {"channel:manage:ads"}},
{Action::MARKER_CREATE, {"channel:manage:broadcast"}},
{Action::CLIP_CREATE, {"clips:edit"}},
{Action::CHAT_ANNOUNCEMENT_SEND,
{"moderator:manage:announcements"}},
{Action::CHAT_EMOTE_ONLY_ENABLE,
{"moderator:manage:chat_settings"}}, {"moderator:manage:chat_settings"}},
{Action::RAID, {"channel:manage:raids"}}, {Action::CHAT_EMOTE_ONLY_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_FOLLOWER_ONLY_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_FOLLOWER_ONLY_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_SUBSCRIBER_ONLY_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_SUBSCRIBER_ONLY_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_SLOW_MODE_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_SLOW_MODE_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_NON_MODERATOR_DELAY_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_NON_MODERATOR_DELAY_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_UNIQUE_MODE_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_UNIQUE_MODE_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::WHISPER_SEND, {"user:manage:whispers"}},
{Action::SEND_CHAT_MESSAGE, {"chat:edit"}}, {Action::SEND_CHAT_MESSAGE, {"chat:edit"}},
}; };
auto token = _token.lock(); auto token = _token.lock();
if (!token) { if (!token) {
return false; return false;
} }
auto option = requiredOption.find(_action); auto option = requiredOption.find(_action);
assert(option != requiredOption.end()); assert(option != requiredOption.end());
if (option == requiredOption.end()) { if (option == requiredOption.end()) {
return false; return false;
} }
return token->OptionIsEnabled(option->second); return token->OptionIsEnabled(option->second);
} }
@@ -401,49 +486,8 @@ MacroActionTwitchEdit::MacroActionTwitchEdit(
_channel(new TwitchChannelSelection(this)), _channel(new TwitchChannelSelection(this)),
_chatMessage(new VariableTextEdit(this)) _chatMessage(new VariableTextEdit(this))
{ {
_streamTitle->setSizePolicy(QSizePolicy::MinimumExpanding, SetWidgetProperties();
QSizePolicy::Preferred); SetWidgetSignalConnections();
_streamTitle->setMaxLength(140);
_markerDescription->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
_markerDescription->setMaxLength(140);
_announcementMessage->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
_announcementMessage->setMaxLength(500);
auto spinBox = _duration->SpinBox();
spinBox->setSuffix("s");
spinBox->setMaximum(180);
populateActionSelection(_actions);
populateAnnouncementColorSelection(_announcementColor);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_tokens, SIGNAL(SelectionChanged(const QString &)),
this, SLOT(TwitchTokenChanged(const QString &)));
QWidget::connect(&_tokenPermissionCheckTimer, SIGNAL(timeout()), this,
SLOT(CheckTokenPermissions()));
QWidget::connect(_streamTitle, SIGNAL(editingFinished()), this,
SLOT(StreamTitleChanged()));
QWidget::connect(_category,
SIGNAL(CategoreyChanged(const TwitchCategory &)), this,
SLOT(CategoreyChanged(const TwitchCategory &)));
QWidget::connect(_markerDescription, SIGNAL(editingFinished()), this,
SLOT(MarkerDescriptionChanged()));
QObject::connect(_clipHasDelay, SIGNAL(stateChanged(int)), this,
SLOT(ClipHasDelayChanged(int)));
QObject::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
this, SLOT(DurationChanged(const Duration &)));
QWidget::connect(_announcementMessage, SIGNAL(textChanged()), this,
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(_chatMessage, SIGNAL(textChanged()), this,
SLOT(ChatMessageChanged()));
_entryData = entryData; _entryData = entryData;
SetWidgetLayout(); SetWidgetLayout();
@@ -477,8 +521,8 @@ void MacroActionTwitchEdit::ActionChanged(int idx)
auto lock = LockContext(); auto lock = LockContext();
_entryData->_action = static_cast<MacroActionTwitch::Action>( _entryData->_action = static_cast<MacroActionTwitch::Action>(
_actions->itemData(idx).toInt()); _actions->itemData(idx).toInt());
SetupWidgetVisibility();
SetWidgetLayout(); SetWidgetLayout();
SetWidgetVisibility();
} }
void MacroActionTwitchEdit::TwitchTokenChanged(const QString &token) void MacroActionTwitchEdit::TwitchTokenChanged(const QString &token)
@@ -492,7 +536,7 @@ void MacroActionTwitchEdit::TwitchTokenChanged(const QString &token)
_category->SetToken(_entryData->_token); _category->SetToken(_entryData->_token);
_channel->SetToken(_entryData->_token); _channel->SetToken(_entryData->_token);
SetupWidgetVisibility(); SetWidgetVisibility();
emit(HeaderInfoChanged(token)); emit(HeaderInfoChanged(token));
} }
@@ -580,30 +624,86 @@ void MacroActionTwitchEdit::AnnouncementColorChanged(int index)
static_cast<MacroActionTwitch::AnnouncementColor>(index); static_cast<MacroActionTwitch::AnnouncementColor>(index);
} }
void MacroActionTwitchEdit::SetupWidgetVisibility() void MacroActionTwitchEdit::SetWidgetProperties()
{ {
_streamTitle->setVisible(_entryData->_action == _streamTitle->setSizePolicy(QSizePolicy::MinimumExpanding,
MacroActionTwitch::Action::TITLE); QSizePolicy::Preferred);
_category->setVisible(_entryData->_action == _streamTitle->setMaxLength(140);
MacroActionTwitch::Action::CATEGORY); _markerDescription->setSizePolicy(QSizePolicy::MinimumExpanding,
_markerDescription->setVisible(_entryData->_action == QSizePolicy::Preferred);
MacroActionTwitch::Action::MARKER); _markerDescription->setMaxLength(140);
_clipHasDelay->setVisible(_entryData->_action == _announcementMessage->setMaxLength(500);
MacroActionTwitch::Action::CLIP);
_duration->setVisible(_entryData->_action == auto durationSpinBox = _duration->SpinBox();
MacroActionTwitch::Action::COMMERCIAL); durationSpinBox->setMaximum(180);
_announcementMessage->setVisible( durationSpinBox->setSuffix("s");
_entryData->_action == MacroActionTwitch::Action::ANNOUNCEMENT);
_announcementColor->setVisible(_entryData->_action == populateActionSelection(_actions);
MacroActionTwitch::Action::ANNOUNCEMENT); populateAnnouncementColorSelection(_announcementColor);
}
void MacroActionTwitchEdit::SetWidgetSignalConnections()
{
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_tokens, SIGNAL(SelectionChanged(const QString &)),
this, SLOT(TwitchTokenChanged(const QString &)));
QWidget::connect(&_tokenPermissionCheckTimer, SIGNAL(timeout()), this,
SLOT(CheckTokenPermissions()));
QWidget::connect(_streamTitle, SIGNAL(editingFinished()), this,
SLOT(StreamTitleChanged()));
QWidget::connect(_category,
SIGNAL(CategoreyChanged(const TwitchCategory &)), this,
SLOT(CategoreyChanged(const TwitchCategory &)));
QWidget::connect(_markerDescription, SIGNAL(editingFinished()), this,
SLOT(MarkerDescriptionChanged()));
QObject::connect(_clipHasDelay, SIGNAL(stateChanged(int)), this,
SLOT(ClipHasDelayChanged(int)));
QObject::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
this, SLOT(DurationChanged(const Duration &)));
QWidget::connect(_announcementMessage, SIGNAL(textChanged()), this,
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(_chatMessage, SIGNAL(textChanged()), this,
SLOT(ChatMessageChanged()));
}
void MacroActionTwitchEdit::SetWidgetVisibility()
{
_streamTitle->setVisible(
_entryData->_action ==
MacroActionTwitch::Action::CHANNEL_INFO_TITLE_SET);
_category->setVisible(
_entryData->_action ==
MacroActionTwitch::Action::CHANNEL_INFO_CATEGORY_SET);
_channel->setVisible( _channel->setVisible(
_entryData->_action == MacroActionTwitch::Action::RAID || _entryData->_action == MacroActionTwitch::Action::RAID_START ||
_entryData->_action == MacroActionTwitch::Action::RAID_END ||
_entryData->_action == _entryData->_action ==
MacroActionTwitch::Action::SEND_CHAT_MESSAGE); MacroActionTwitch::Action::SEND_CHAT_MESSAGE);
_duration->setVisible(_entryData->_action ==
MacroActionTwitch::Action::COMMERCIAL_START);
_markerDescription->setVisible(
_entryData->_action ==
MacroActionTwitch::Action::MARKER_CREATE);
_clipHasDelay->setVisible(_entryData->_action ==
MacroActionTwitch::Action::CLIP_CREATE);
_announcementMessage->setVisible(
_entryData->_action ==
MacroActionTwitch::Action::CHAT_ANNOUNCEMENT_SEND);
_announcementColor->setVisible(
_entryData->_action ==
MacroActionTwitch::Action::CHAT_ANNOUNCEMENT_SEND);
_chatMessage->setVisible(_entryData->_action == _chatMessage->setVisible(_entryData->_action ==
MacroActionTwitch::Action::SEND_CHAT_MESSAGE); MacroActionTwitch::Action::SEND_CHAT_MESSAGE);
if (_entryData->_action == MacroActionTwitch::Action::TITLE ||
_entryData->_action == MacroActionTwitch::Action::MARKER) { if (_entryData->_action ==
MacroActionTwitch::Action::CHANNEL_INFO_TITLE_SET ||
_entryData->_action == MacroActionTwitch::Action::MARKER_CREATE) {
RemoveStretchIfPresent(_layout); RemoveStretchIfPresent(_layout);
} else { } else {
AddStretchIfNecessary(_layout); AddStretchIfNecessary(_layout);
@@ -641,7 +741,7 @@ void MacroActionTwitchEdit::ChatMessageChanged()
void MacroActionTwitchEdit::SetWidgetLayout() void MacroActionTwitchEdit::SetWidgetLayout()
{ {
static const std::vector<QWidget *> widgets{ const std::vector<QWidget *> widgets{
_tokens, _actions, _streamTitle, _tokens, _actions, _streamTitle,
_category, _markerDescription, _clipHasDelay, _category, _markerDescription, _clipHasDelay,
_duration, _announcementColor, _channel}; _duration, _announcementColor, _channel};
@@ -692,7 +792,7 @@ void MacroActionTwitchEdit::UpdateEntryData()
_channel->SetChannel(_entryData->_channel); _channel->SetChannel(_entryData->_channel);
_chatMessage->setPlainText(_entryData->_chatMessage); _chatMessage->setPlainText(_entryData->_chatMessage);
SetupWidgetVisibility(); SetWidgetVisibility();
} }
} // namespace advss } // namespace advss

View File

@@ -4,6 +4,7 @@
#include "category-selection.hpp" #include "category-selection.hpp"
#include "channel-selection.hpp" #include "channel-selection.hpp"
#include "chat-connection.hpp" #include "chat-connection.hpp"
#include "points-reward-selection.hpp"
#include <variable-line-edit.hpp> #include <variable-line-edit.hpp>
#include <variable-text-edit.hpp> #include <variable-text-edit.hpp>
@@ -22,16 +23,118 @@ public:
std::string GetShortDesc() const; std::string GetShortDesc() const;
enum class Action { enum class Action {
TITLE, // Channel info update
CATEGORY = 10, CHANNEL_INFO_TITLE_SET = 10,
MARKER = 20, CHANNEL_INFO_CATEGORY_SET = 20,
CLIP = 30, CHANNEL_INFO_LANGUAGE_SET = 30,
COMMERCIAL = 40, CHANNEL_INFO_DELAY_SET = 40,
ANNOUNCEMENT = 50, CHANNEL_INFO_TAGS_SET = 50, // TODO
ENABLE_EMOTE_ONLY = 60, CHANNEL_INFO_CONTENT_LABELS_SET = 60, // TODO
DISABLE_EMOTE_ONLY = 70, CHANNEL_INFO_BRANDED_CONTENT_ENABLE = 70,
RAID = 80, CHANNEL_INFO_BRANDED_CONTENT_DISABLE = 71,
SEND_CHAT_MESSAGE = 90,
// Raid
RAID_START = 110,
RAID_END = 120,
// Shoutout
SHOUTOUT_SEND = 200,
// Poll
POLL_START = 300, // TODO
POLL_END = 310,
// Prediction
PREDICTION_START = 400, // TODO
PREDICTION_END = 410,
// Shield mode
SHIELD_MODE_START = 500,
SHIELD_MODE_END = 501,
// Reward add
POINTS_REWARD_ADD = 600, // TODO
// Reward update
POINTS_REWARD_ENABLE = 710,
POINTS_REWARD_DISABLE = 711,
POINTS_REWARD_PAUSE = 712,
POINTS_REWARD_UNPAUSE = 713,
POINTS_REWARD_TITLE_SET = 720,
POINTS_REWARD_PROMPT_SET = 730,
POINTS_REWARD_COST_SET = 740,
POINTS_REWARD_BACKGROUND_COLOR_SET = 750, // TODO
POINTS_REWARD_USER_INPUT_REQUIRE = 760,
POINTS_REWARD_USER_INPUT_UNREQUIRE = 761,
POINTS_REWARD_COOLDOWN_ENABLE = 770,
POINTS_REWARD_COOLDOWN_DISABLE = 771,
POINTS_REWARD_QUEUE_SKIP_ENABLE = 780,
POINTS_REWARD_QUEUE_SKIP_DISABLE = 781,
POINTS_REWARD_MAX_PER_STREAM_ENABLE = 790,
POINTS_REWARD_MAX_PER_STREAM_DISABLE = 791,
POINTS_REWARD_MAX_PER_USER_ENABLE = 792,
POINTS_REWARD_MAX_PER_USER_DISABLE = 793,
// Reward delete
POINTS_REWARD_DELETE = 800,
// Reward redemption update
POINTS_REWARD_REDEMPTION_FULFILL = 900,
POINTS_REWARD_REDEMPTION_CANCEL = 910,
// User state
USER_BAN = 1000,
USER_UNBAN = 1100,
USER_BLOCK = 1200,
USER_UNBLOCK = 1300,
USER_MODERATOR_ADD = 1400,
USER_MODERATOR_DELETE = 1500,
USER_VIP_ADD = 1600,
USER_VIP_DELETE = 1700,
USER_CHAT_COLOR_UPDATE = 1800, // TODO
// Commercial
COMMERCIAL_START = 1900,
COMMERCIAL_SNOOZE = 2000,
// Marker
MARKER_CREATE = 2100,
// Clips
CLIP_CREATE = 2200,
// Videos
VIDEOS_DELETE = 2300, // TODO
// Chat
CHAT_MESSAGES_DELETE = 2400, // TODO
CHAT_ANNOUNCEMENT_SEND = 2500,
CHAT_EMOTE_ONLY_ENABLE = 2600,
CHAT_EMOTE_ONLY_DISABLE = 2601,
CHAT_FOLLOWER_ONLY_ENABLE = 2602,
CHAT_FOLLOWER_ONLY_DISABLE = 2603,
CHAT_SUBSCRIBER_ONLY_ENABLE = 2604,
CHAT_SUBSCRIBER_ONLY_DISABLE = 2605,
CHAT_SLOW_MODE_ENABLE = 2640,
CHAT_SLOW_MODE_DISABLE = 2641,
CHAT_NON_MODERATOR_DELAY_ENABLE = 2650,
CHAT_NON_MODERATOR_DELAY_DISABLE = 2651,
CHAT_UNIQUE_MODE_ENABLE = 2960,
CHAT_UNIQUE_MODE_DISABLE = 2661,
CHAT_AUTOMOD_UPDATE = 2700, // TODO
CHAT_AUTOMOD_MESSAGE_ALLOW = 2800, // TODO
CHAT_AUTOMOD_MESSAGE_DENY = 2801, // TODO
CHAT_BLOCKED_TERM_ADD = 2900, // TODO
CHAT_BLOCKED_TERM_DELETE = 3000, // TODO
WHISPER_SEND = 3100,
// Schedule
CHANNEL_SCHEDULE_UPDATE = 3200, // TODO
CHANNEL_SCHEDULE_SEGMENT_ADD = 3300, // TODO
CHANNEL_SCHEDULE_SEGMENT_UPDATE = 3400, // TODO
CHANNEL_SCHEDULE_SEGMENT_DELETE = 3500, // TODO
SEND_CHAT_MESSAGE = 5000,
}; };
enum class AnnouncementColor { enum class AnnouncementColor {
@@ -42,6 +145,8 @@ public:
PURPLE, PURPLE,
}; };
enum class RedemptionStatus { CANCELED, FULFILLED };
bool PerformAction(); bool PerformAction();
void LogAction() const; void LogAction() const;
bool Save(obs_data_t *obj) const; bool Save(obs_data_t *obj) const;
@@ -49,7 +154,7 @@ public:
bool ActionIsSupportedByToken(); bool ActionIsSupportedByToken();
void ResetChatConnection(); void ResetChatConnection();
Action _action = Action::TITLE; Action _action = Action::CHANNEL_INFO_TITLE_SET;
std::weak_ptr<TwitchToken> _token; std::weak_ptr<TwitchToken> _token;
StringVariable _streamTitle = StringVariable _streamTitle =
obs_module_text("AdvSceneSwitcher.action.twitch.title.title"); obs_module_text("AdvSceneSwitcher.action.twitch.title.title");
@@ -119,8 +224,10 @@ protected:
std::shared_ptr<MacroActionTwitch> _entryData; std::shared_ptr<MacroActionTwitch> _entryData;
private: private:
void SetupWidgetVisibility(); void SetWidgetProperties();
void SetWidgetSignalConnections();
void SetWidgetLayout(); void SetWidgetLayout();
void SetWidgetVisibility();
QHBoxLayout *_layout; QHBoxLayout *_layout;
FilterComboBox *_actions; FilterComboBox *_actions;

View File

@@ -866,7 +866,9 @@ void MacroConditionTwitch::SetupTempVars()
AddTempvar(id, name.empty() ? id : name, description); AddTempvar(id, name.empty() ? id : name, description);
}; };
if (_condition != Condition::CHAT_MESSAGE_RECEIVED) { if (_condition != Condition::CHAT_MESSAGE_RECEIVED &&
_condition != Condition::RAID_INBOUND_EVENT &&
_condition != Condition::RAID_OUTBOUND_EVENT) {
setupTempVarHelper("broadcaster_user_id"); setupTempVarHelper("broadcaster_user_id");
setupTempVarHelper("broadcaster_user_login"); setupTempVarHelper("broadcaster_user_login");
setupTempVarHelper("broadcaster_user_name"); setupTempVarHelper("broadcaster_user_name");
@@ -1141,7 +1143,16 @@ void MacroConditionTwitch::SetupTempVars()
setupTempVarHelper("user_name", ".follow"); setupTempVarHelper("user_name", ".follow");
setupTempVarHelper("followed_at"); setupTempVarHelper("followed_at");
break; break;
case Condition::RAID_INBOUND_EVENT:
case Condition::RAID_OUTBOUND_EVENT: case Condition::RAID_OUTBOUND_EVENT:
setupTempVarHelper("from_broadcaster_user_id", ".raid");
setupTempVarHelper("from_broadcaster_user_login", ".raid");
setupTempVarHelper("from_broadcaster_user_name", ".raid");
setupTempVarHelper("to_broadcaster_user_id", ".raid");
setupTempVarHelper("to_broadcaster_user_login", ".raid");
setupTempVarHelper("to_broadcaster_user_name", ".raid");
setupTempVarHelper("viewers", ".raid");
break;
case Condition::LIVE_POLLING: case Condition::LIVE_POLLING:
setupTempVarHelper("id", ".stream.online"); setupTempVarHelper("id", ".stream.online");
setupTempVarHelper("game_id"); setupTempVarHelper("game_id");
@@ -1280,7 +1291,7 @@ void MacroConditionTwitchEdit::ConditionChanged(int idx)
auto lock = LockContext(); auto lock = LockContext();
_entryData->SetCondition(static_cast<MacroConditionTwitch::Condition>( _entryData->SetCondition(static_cast<MacroConditionTwitch::Condition>(
_conditions->itemData(idx).toInt())); _conditions->itemData(idx).toInt()));
SetupWidgetVisibility(); SetWidgetVisibility();
emit TempVarsChanged(); emit TempVarsChanged();
} }
@@ -1297,7 +1308,7 @@ void MacroConditionTwitchEdit::TwitchTokenChanged(const QString &token)
_pointsReward->SetToken(_entryData->_token); _pointsReward->SetToken(_entryData->_token);
_entryData->ResetChatConnection(); _entryData->ResetChatConnection();
SetupWidgetVisibility(); SetWidgetVisibility();
emit(HeaderInfoChanged(token)); emit(HeaderInfoChanged(token));
} }
@@ -1388,7 +1399,7 @@ void MacroConditionTwitchEdit::CategoreyChanged(const TwitchCategory &category)
_entryData->_category = category; _entryData->_category = category;
} }
void MacroConditionTwitchEdit::SetupWidgetVisibility() void MacroConditionTwitchEdit::SetWidgetVisibility()
{ {
auto condition = _entryData->GetCondition(); auto condition = _entryData->GetCondition();
@@ -1448,7 +1459,7 @@ void MacroConditionTwitchEdit::UpdateEntryData()
_category->SetToken(_entryData->_token); _category->SetToken(_entryData->_token);
_category->SetCategory(_entryData->_category); _category->SetCategory(_entryData->_category);
SetupWidgetVisibility(); SetWidgetVisibility();
} }
} // namespace advss } // namespace advss

View File

@@ -23,8 +23,13 @@ public:
std::string GetShortDesc() const; std::string GetShortDesc() const;
enum class Condition { enum class Condition {
// Generic event based // Event based
STREAM_ONLINE_EVENT = 0, STREAM_ONLINE_EVENT = 0,
STREAM_ONLINE_LIVE_EVENT = 1,
STREAM_ONLINE_PLAYLIST_EVENT = 2,
STREAM_ONLINE_WATCHPARTY_EVENT = 3,
STREAM_ONLINE_PREMIERE_EVENT = 4,
STREAM_ONLINE_RERUN_EVENT = 5,
STREAM_OFFLINE_EVENT = 100, STREAM_OFFLINE_EVENT = 100,
CHANNEL_INFO_UPDATE_EVENT = 200, CHANNEL_INFO_UPDATE_EVENT = 200,
FOLLOW_EVENT = 300, FOLLOW_EVENT = 300,
@@ -66,13 +71,6 @@ public:
USER_MODERATOR_ADDITION_EVENT = 3900, USER_MODERATOR_ADDITION_EVENT = 3900,
USER_MODERATOR_DELETION_EVENT = 4000, USER_MODERATOR_DELETION_EVENT = 4000,
// Event based with custom logic
STREAM_ONLINE_LIVE_EVENT = 1,
STREAM_ONLINE_PLAYLIST_EVENT = 2,
STREAM_ONLINE_WATCHPARTY_EVENT = 3,
STREAM_ONLINE_PREMIERE_EVENT = 4,
STREAM_ONLINE_RERUN_EVENT = 5,
// Chat // Chat
CHAT_MESSAGE_RECEIVED = 500000, CHAT_MESSAGE_RECEIVED = 500000,
@@ -170,7 +168,7 @@ protected:
std::shared_ptr<MacroConditionTwitch> _entryData; std::shared_ptr<MacroConditionTwitch> _entryData;
private: private:
void SetupWidgetVisibility(); void SetWidgetVisibility();
QHBoxLayout *_layout; QHBoxLayout *_layout;
FilterComboBox *_conditions; FilterComboBox *_conditions;

View File

@@ -134,9 +134,9 @@ TwitchPointsRewardSelection::GetPointsRewards(
httplib::Params params = { httplib::Params params = {
{"broadcaster_id", channel.GetUserID(*token)}}; {"broadcaster_id", channel.GetUserID(*token)}};
auto response = SendGetRequest("https://api.twitch.tv", auto response = SendGetRequest(*token, "https://api.twitch.tv",
"/helix/channel_points/custom_rewards", "/helix/channel_points/custom_rewards",
*token, params); params);
if (response.status != 200) { if (response.status != 200) {
blog(LOG_WARNING, blog(LOG_WARNING,

View File

@@ -13,14 +13,6 @@ static std::deque<std::shared_ptr<Item>> twitchTokens;
const std::unordered_map<std::string, std::string> TokenOption::_apiIdToLocale{ const std::unordered_map<std::string, std::string> TokenOption::_apiIdToLocale{
{"channel:manage:broadcast", {"channel:manage:broadcast",
"AdvSceneSwitcher.twitchToken.channel.broadcast.manage"}, "AdvSceneSwitcher.twitchToken.channel.broadcast.manage"},
{"clips:edit", "AdvSceneSwitcher.twitchToken.clips.edit"},
{"channel:edit:commercial",
"AdvSceneSwitcher.twitchToken.channel.commercial.edit"},
{"moderator:manage:announcements",
"AdvSceneSwitcher.twitchToken.moderator.announcements.manage"},
{"moderator:manage:chat_settings",
"AdvSceneSwitcher.twitchToken.moderator.chat.settings.manage"},
{"channel:manage:raids", "AdvSceneSwitcher.twitchToken.channel.raid"},
{"moderator:read:followers", {"moderator:read:followers",
"AdvSceneSwitcher.twitchToken.moderator.followers.read"}, "AdvSceneSwitcher.twitchToken.moderator.followers.read"},
{"channel:read:subscriptions", {"channel:read:subscriptions",
@@ -28,6 +20,8 @@ const std::unordered_map<std::string, std::string> TokenOption::_apiIdToLocale{
{"bits:read", "AdvSceneSwitcher.twitchToken.bits.read"}, {"bits:read", "AdvSceneSwitcher.twitchToken.bits.read"},
{"moderator:read:shoutouts", {"moderator:read:shoutouts",
"AdvSceneSwitcher.twitchToken.moderator.shoutouts.read"}, "AdvSceneSwitcher.twitchToken.moderator.shoutouts.read"},
{"channel:manage:raids",
"AdvSceneSwitcher.twitchToken.channel.raids.manage"},
{"moderator:manage:shoutouts", {"moderator:manage:shoutouts",
"AdvSceneSwitcher.twitchToken.moderator.shoutouts.manage"}, "AdvSceneSwitcher.twitchToken.moderator.shoutouts.manage"},
{"channel:read:polls", {"channel:read:polls",
@@ -53,8 +47,26 @@ const std::unordered_map<std::string, std::string> TokenOption::_apiIdToLocale{
{"channel:manage:redemptions", {"channel:manage:redemptions",
"AdvSceneSwitcher.twitchToken.channel.redemptions.manage"}, "AdvSceneSwitcher.twitchToken.channel.redemptions.manage"},
{"channel:moderate", "AdvSceneSwitcher.twitchToken.channel.moderate"}, {"channel:moderate", "AdvSceneSwitcher.twitchToken.channel.moderate"},
{"moderation:read", {"moderator:manage:banned_users",
"AdvSceneSwitcher.twitchToken.channel.moderation.read"}, "AdvSceneSwitcher.twitchToken.moderator.bannedUsers.manage"},
{"user:manage:blocked_users",
"AdvSceneSwitcher.twitchToken.user.blockedUsers.manage"},
{"moderation:read", "AdvSceneSwitcher.twitchToken.moderation.read"},
{"channel:manage:moderators",
"AdvSceneSwitcher.twitchToken.channel.moderators.manage"},
{"channel:manage:vips",
"AdvSceneSwitcher.twitchToken.channel.vips.manage"},
{"channel:edit:commercial",
"AdvSceneSwitcher.twitchToken.channel.commercial.edit"},
{"channel:manage:ads",
"AdvSceneSwitcher.twitchToken.channel.ads.manage"},
{"clips:edit", "AdvSceneSwitcher.twitchToken.clips.edit"},
{"moderator:manage:announcements",
"AdvSceneSwitcher.twitchToken.moderator.announcements.manage"},
{"moderator:manage:chat_settings",
"AdvSceneSwitcher.twitchToken.moderator.chat.settings.manage"},
{"user:manage:whispers",
"AdvSceneSwitcher.twitchToken.user.whispers.manage"},
{"chat:read", "AdvSceneSwitcher.twitchToken.chat.read"}, {"chat:read", "AdvSceneSwitcher.twitchToken.chat.read"},
{"chat:edit", "AdvSceneSwitcher.twitchToken.chat.edit"}, {"chat:edit", "AdvSceneSwitcher.twitchToken.chat.edit"},
}; };
@@ -194,7 +206,7 @@ void TwitchToken::SetToken(const std::string &value)
{ {
_token = value; _token = value;
auto res = auto res =
SendGetRequest("https://api.twitch.tv", "/helix/users", *this); SendGetRequest(*this, "https://api.twitch.tv", "/helix/users");
if (res.status != 200) { if (res.status != 200) {
blog(LOG_WARNING, "failed to get Twitch user id from token!"); blog(LOG_WARNING, "failed to get Twitch user id from token!");
_userID = -1; _userID = -1;

View File

@@ -1,11 +1,18 @@
#include "twitch-helpers.hpp" #include "twitch-helpers.hpp"
#include "token.hpp" #include "token.hpp"
#include <log-helper.hpp>
namespace advss { namespace advss {
static constexpr std::string_view clientID = "ds5tt4ogliifsqc04mz3d3etnck3e5"; static constexpr std::string_view clientID = "ds5tt4ogliifsqc04mz3d3etnck3e5";
static const int cacheTimeoutSeconds = 10; static const int cacheTimeoutSeconds = 10;
const char *GetClientID()
{
return clientID.data();
}
class Args { class Args {
public: public:
Args(const std::string &uri, const std::string &path, Args(const std::string &uri, const std::string &path,
@@ -43,7 +50,7 @@ struct CacheEntry {
std::chrono::system_clock::now(); std::chrono::system_clock::now();
}; };
static bool chacheIsTooOld(const CacheEntry &cache) static bool cacheIsTooOld(const CacheEntry &cache)
{ {
auto currentTime = std::chrono::system_clock::now(); auto currentTime = std::chrono::system_clock::now();
auto diff = currentTime - cache.cacheTime; auto diff = currentTime - cache.cacheTime;
@@ -54,7 +61,7 @@ static bool cacheIsValid(const std::map<Args, CacheEntry> &cache,
const Args &args) const Args &args)
{ {
auto it = cache.find(args); auto it = cache.find(args);
return it != cache.end() && !chacheIsTooOld(it->second); return it != cache.end() && !cacheIsTooOld(it->second);
} }
static httplib::Headers getTokenRequestHeaders(const std::string &token) static httplib::Headers getTokenRequestHeaders(const std::string &token)
@@ -65,166 +72,262 @@ static httplib::Headers getTokenRequestHeaders(const std::string &token)
}; };
} }
RequestResult SendGetRequest(const std::string &uri, const std::string &path, static std::string getRequestBody(const OBSData &data)
const TwitchToken &token, {
auto json = obs_data_get_json(data);
return json ? json : "";
}
static RequestResult processResult(const httplib::Result &response,
const char *funcName)
{
if (!response) {
auto err = response.error();
blog(LOG_WARNING, "Twitch request failed in %s with error: %s",
funcName, httplib::to_string(err).c_str());
return {};
}
RequestResult result;
result.status = response->status;
if (response->body.empty()) {
return result;
}
OBSDataAutoRelease replyData =
obs_data_create_from_json(response->body.c_str());
result.data = replyData;
return result;
}
RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params) const httplib::Params &params)
{ {
httplib::Client cli(uri); httplib::Client cli(uri);
auto tokenStr = token.GetToken(); auto tokenStr = token.GetToken();
if (!tokenStr) { if (!tokenStr) {
return {}; return {};
} }
auto url = httplib::append_query_params(uri + path, params);
vblog(LOG_INFO, "Twitch GET request to %s began", url.c_str());
auto headers = getTokenRequestHeaders(*tokenStr); auto headers = getTokenRequestHeaders(*tokenStr);
auto response = cli.Get(path, params, headers); auto response = cli.Get(path, params, headers);
if (!response) {
auto err = response.error(); return processResult(response, __func__);
blog(LOG_INFO, "%s failed - %s", __func__,
httplib::to_string(err).c_str());
return {};
}
RequestResult result;
result.status = response->status;
if (response->body.empty()) {
return result;
}
OBSDataAutoRelease replyData =
obs_data_create_from_json(response->body.c_str());
result.data = replyData;
return result;
} }
RequestResult SendGetRequest(const std::string &uri, const std::string &path, RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
const TwitchToken &token, const std::string &path,
const httplib::Params &params, bool useCache) const httplib::Params &params, bool useCache)
{ {
static std::map<Args, CacheEntry> cache; static std::map<Args, CacheEntry> cache;
static std::mutex mtx; static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx); std::lock_guard<std::mutex> lock(mtx);
auto tokenStr = token.GetToken(); auto tokenStr = token.GetToken();
if (!tokenStr) { if (!tokenStr) {
return {}; return {};
} }
auto headers = getTokenRequestHeaders(*tokenStr); auto headers = getTokenRequestHeaders(*tokenStr);
Args args(uri, path, "", params, headers); Args args(uri, path, "", params, headers);
if (useCache && cacheIsValid(cache, args)) { if (useCache && cacheIsValid(cache, args)) {
auto it = cache.find(args); auto it = cache.find(args);
return it->second.result; return it->second.result;
} }
auto result = SendGetRequest(uri, path, token, params); auto result = SendGetRequest(token, uri, path, params);
cache[args] = {result}; cache[args] = {result};
return result; return result;
} }
RequestResult SendPostRequest(const std::string &uri, const std::string &path, RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
const TwitchToken &token, const OBSData &data) const std::string &path,
const httplib::Params &params,
const OBSData &data)
{ {
httplib::Client cli(uri); httplib::Client cli(uri);
auto tokenStr = token.GetToken(); auto tokenStr = token.GetToken();
if (!tokenStr) { if (!tokenStr) {
return {}; return {};
} }
auto pathWithParams = httplib::append_query_params(path, params);
auto url = uri + pathWithParams;
vblog(LOG_INFO, "Twitch POST request to %s began", url.c_str());
auto headers = getTokenRequestHeaders(*tokenStr); auto headers = getTokenRequestHeaders(*tokenStr);
auto json = obs_data_get_json(data); auto body = getRequestBody(data);
std::string body = json ? json : ""; auto response =
auto response = cli.Post(path, headers, body, "application/json"); cli.Post(pathWithParams, headers, body, "application/json");
if (!response) {
auto err = response.error(); return processResult(response, __func__);
blog(LOG_INFO, "%s failed - %s", __func__,
httplib::to_string(err).c_str());
return {};
}
RequestResult result;
result.status = response->status;
if (response->body.empty()) {
return result;
}
OBSDataAutoRelease replyData =
obs_data_create_from_json(response->body.c_str());
result.data = replyData;
return result;
} }
RequestResult SendPostRequest(const std::string &uri, const std::string &path, RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
const TwitchToken &token, const OBSData &data, const std::string &path,
bool useCache) const httplib::Params &params,
const OBSData &data, bool useCache)
{ {
static std::map<Args, CacheEntry> cache; static std::map<Args, CacheEntry> cache;
static std::mutex mtx; static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx); std::lock_guard<std::mutex> lock(mtx);
auto tokenStr = token.GetToken(); auto tokenStr = token.GetToken();
if (!tokenStr) { if (!tokenStr) {
return {}; return {};
} }
auto headers = getTokenRequestHeaders(*tokenStr); auto headers = getTokenRequestHeaders(*tokenStr);
auto jsonCstr = obs_data_get_json(data); auto body = getRequestBody(data);
Args args(uri, path, jsonCstr ? jsonCstr : "", {}, headers); Args args(uri, path, body, params, headers);
if (useCache && cacheIsValid(cache, args)) { if (useCache && cacheIsValid(cache, args)) {
auto it = cache.find(args); auto it = cache.find(args);
return it->second.result; return it->second.result;
} }
auto result = SendPostRequest(uri, path, token, data); auto result = SendPostRequest(token, uri, path, params, data);
cache[args] = {result}; cache[args] = {result};
return result; return result;
} }
RequestResult SendPatchRequest(const std::string &uri, const std::string &path, RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
const TwitchToken &token, const OBSData &data) const std::string &path,
const httplib::Params &params, const OBSData &data)
{ {
httplib::Client cli(uri); httplib::Client cli(uri);
auto tokenStr = token.GetToken(); auto tokenStr = token.GetToken();
if (!tokenStr) { if (!tokenStr) {
return {}; return {};
} }
auto pathWithParams = httplib::append_query_params(path, params);
auto url = uri + pathWithParams;
vblog(LOG_INFO, "Twitch PUT request to %s began", url.c_str());
auto headers = getTokenRequestHeaders(*tokenStr); auto headers = getTokenRequestHeaders(*tokenStr);
auto json = obs_data_get_json(data); auto body = getRequestBody(data);
std::string body = json ? json : ""; auto response =
auto response = cli.Patch(path, headers, body, "application/json"); cli.Put(pathWithParams, headers, body, "application/json");
if (!response) {
auto err = response.error(); return processResult(response, __func__);
blog(LOG_INFO, "%s failed - %s", __func__,
httplib::to_string(err).c_str());
return {};
}
RequestResult result;
result.status = response->status;
if (response->body.empty()) {
return result;
}
OBSDataAutoRelease replyData =
obs_data_create_from_json(response->body.c_str());
result.data = replyData;
return result;
} }
RequestResult SendPatchRequest(const std::string &uri, const std::string &path, RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
const TwitchToken &token, const OBSData &data, const std::string &path,
bool useCache) const httplib::Params &params, const OBSData &data,
bool useCache)
{ {
static std::map<Args, CacheEntry> cache; static std::map<Args, CacheEntry> cache;
static std::mutex mtx; static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx); std::lock_guard<std::mutex> lock(mtx);
auto tokenStr = token.GetToken(); auto tokenStr = token.GetToken();
if (!tokenStr) { if (!tokenStr) {
return {}; return {};
} }
auto headers = getTokenRequestHeaders(*tokenStr); auto headers = getTokenRequestHeaders(*tokenStr);
auto jsonCstr = obs_data_get_json(data); auto body = getRequestBody(data);
Args args(uri, path, jsonCstr ? jsonCstr : "", {}, headers); Args args(uri, path, body, params, headers);
if (useCache && cacheIsValid(cache, args)) { if (useCache && cacheIsValid(cache, args)) {
auto it = cache.find(args); auto it = cache.find(args);
return it->second.result; return it->second.result;
} }
auto result = SendPatchRequest(uri, path, token, data); auto result = SendPutRequest(token, uri, path, params, data);
cache[args] = {result}; cache[args] = {result};
return result; return result;
} }
const char *GetClientID() RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data)
{ {
return clientID.data(); httplib::Client cli(uri);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto pathWithParams = httplib::append_query_params(path, params);
auto url = uri + pathWithParams;
vblog(LOG_INFO, "Twitch PATCH request to %s began", url.c_str());
auto headers = getTokenRequestHeaders(*tokenStr);
auto body = getRequestBody(data);
auto response =
cli.Patch(pathWithParams, headers, body, "application/json");
return processResult(response, __func__);
}
RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data, bool useCache)
{
static std::map<Args, CacheEntry> cache;
static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto headers = getTokenRequestHeaders(*tokenStr);
auto body = getRequestBody(data);
Args args(uri, path, body, params, headers);
if (useCache && cacheIsValid(cache, args)) {
auto it = cache.find(args);
return it->second.result;
}
auto result = SendPatchRequest(token, uri, path, params, data);
cache[args] = {result};
return result;
}
RequestResult SendDeleteRequest(const TwitchToken &token,
const std::string &uri, const std::string &path,
const httplib::Params &params)
{
httplib::Client cli(uri);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto pathWithParams = httplib::append_query_params(path, params);
auto url = uri + pathWithParams;
vblog(LOG_INFO, "Twitch DELETE request to %s began", url.c_str());
auto headers = getTokenRequestHeaders(*tokenStr);
auto response =
cli.Delete(pathWithParams, headers, "", "application/json");
return processResult(response, __func__);
} }
} // namespace advss } // namespace advss

View File

@@ -13,29 +13,44 @@ struct RequestResult {
OBSData data = nullptr; OBSData data = nullptr;
}; };
// These functions do *not* use or create RequestResult cache entries const char *GetClientID();
RequestResult SendGetRequest(const std::string &uri, const std::string &path, // These functions do *not* use or create RequestResult cache entries
const TwitchToken &token, RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
const httplib::Params & = {}); const std::string &path,
RequestResult SendPostRequest(const std::string &uri, const std::string &path, const httplib::Params &params = {});
const TwitchToken &token, const OBSData &data); RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
RequestResult SendPatchRequest(const std::string &uri, const std::string &path, const std::string &path,
const TwitchToken &token, const OBSData &data); const httplib::Params &params = {},
const OBSData &data = nullptr);
RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params = {},
const OBSData &data = nullptr);
RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params = {},
const OBSData &data = nullptr);
RequestResult SendDeleteRequest(const TwitchToken &token,
const std::string &uri, const std::string &path,
const httplib::Params &params = {});
// These functions will cache the RequestResult for 10s // These functions will cache the RequestResult for 10s
// Note that the cache will be reported as a "memory leak" on OBS shutdown // Note that the cache will be reported as a "memory leak" on OBS shutdown
RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
RequestResult SendGetRequest(const std::string &uri, const std::string &path, const std::string &path,
const TwitchToken &token, const httplib::Params &, const httplib::Params &params, bool useCache);
RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data, bool useCache);
RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params, const OBSData &data,
bool useCache); bool useCache);
RequestResult SendPostRequest(const std::string &uri, const std::string &path, RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
const TwitchToken &token, const OBSData &data, const std::string &path,
bool useCache); const httplib::Params &params,
RequestResult SendPatchRequest(const std::string &uri, const std::string &path, const OBSData &data, bool useCache);
const TwitchToken &token, const OBSData &data,
bool useCache);
const char *GetClientID();
} // namespace advss } // namespace advss