Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Allow Update Channel Reward to accept strings, variables, and event metadata #2947

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 150 additions & 72 deletions src/backend/effects/builtin/update-channel-reward.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { EffectType } from "../../../types/effects";
import { validate } from "uuid";
import { EffectCategory } from '../../../shared/effect-constants';
import { SavedChannelReward } from "../../../types/channel-rewards";
import { EffectType } from "../../../types/effects";
import channelRewardsManager from "../../channel-rewards/channel-reward-manager";
import logger from "../../logwrapper";
import {SavedChannelReward} from "../../../types/channel-rewards";

type StringUpdatable = { update: boolean, newValue: string };
type StatusUpdatable = { update: boolean, newValue: 'toggle' | boolean };
Expand All @@ -17,7 +18,9 @@ type EffectMeta = {
enabled: StatusUpdatable;
paused: StatusUpdatable;
};
rewardSelectMode: "dropdown" | "associated" | "sortTag" | "custom";
channelRewardId: string;
customId: string;
useTag?: boolean;
sortTagId?: string;
}
Expand Down Expand Up @@ -45,15 +48,14 @@ const model: EffectType<EffectMeta> = {
dependencies: []
},
optionsTemplate: `
<eos-container ng-hide="!hasTags">
<label class="control-fb control--checkbox"> Use Sort Tags</tooltip>
<input type="checkbox" ng-model="effect.useTag">
<div class="control__indicator"></div>
</label>
<eos-container>
<firebot-radios
options="selectRewardOptions"
model="effect.rewardSelectMode">
</firebot-radios>
</eos-container>


<eos-container ng-hide="effect.useTag" header="Channel Reward">
<eos-container ng-if="effect.rewardSelectMode == 'dropdown'" header="Channel Reward">
<ui-select ng-model="effect.channelRewardId" theme="bootstrap">
<ui-select-match placeholder="Select or search for a channel reward... ">{{$select.selected.name}}</ui-select-match>
<ui-select-choices repeat="reward.id as reward in manageableRewards | filter: { name: $select.search }" style="position:relative;">
Expand All @@ -62,7 +64,7 @@ const model: EffectType<EffectMeta> = {
</ui-select>
</eos-container>

<eos-container ng-show="effect.useTag" header="Channel Reward Tags">
<eos-container ng-if="effect.rewardSelectMode == 'sortTag'" header="Channel Reward Tags">
<ui-select ng-model="effect.sortTagId" theme="bootstrap">
<ui-select-match placeholder="Select or search for a tag... ">{{$select.selected.name}}</ui-select-match>
<ui-select-choices repeat="sortTag.id as sortTag in sortTags | filter: { name: $select.search }" style="position:relative;">
Expand All @@ -71,12 +73,17 @@ const model: EffectType<EffectMeta> = {
</ui-select>
</eos-container>

<eos-container ng-show="effect.channelRewardId != null || (effect.useTag && effect.sortTagId != null)" header="Reward Settings" pad-top="true">
<eos-container ng-if="effect.rewardSelectMode == 'custom'" header="Channel Reward Name/ID">
<firebot-input placeholder="Channel Reward Name/ID" model="effect.customId" menu-position="under" />
</eos-container>

<label class="control-fb control--checkbox">Update Enabled
<input type="checkbox" ng-click="effect.rewardSettings.enabled.update = !effect.rewardSettings.enabled.update" ng-checked="effect.rewardSettings.enabled.update" aria-label="Toggle enabled" >
<div class="control__indicator"></div>
</label>
<eos-container ng-show="showRewardSettings()" header="Reward Settings" pad-top="true">

<firebot-checkbox
label="Update Enabled"
model="effect.rewardSettings.enabled.update"
aria-label="Toggle enabled"
/>
<div ng-show="effect.rewardSettings.enabled.update" style="margin-bottom: 15px;">
<div class="btn-group" uib-dropdown>
<button id="single-button" type="button" class="btn btn-default" uib-dropdown-toggle>
Expand All @@ -90,10 +97,11 @@ const model: EffectType<EffectMeta> = {
</div>
</div>

<label class="control-fb control--checkbox">Update Paused
<input type="checkbox" ng-click="effect.rewardSettings.paused.update = !effect.rewardSettings.paused.update" ng-checked="effect.rewardSettings.paused.update" aria-label="Toggle paused" >
<div class="control__indicator"></div>
</label>
<firebot-checkbox
label="Update Paused"
model="effect.rewardSettings.paused.update"
aria-label="Toggle paused"
/>
<div ng-show="effect.rewardSettings.paused.update" style="margin-bottom: 15px;">
<div class="btn-group" uib-dropdown>
<button id="single-button" type="button" class="btn btn-default" uib-dropdown-toggle>
Expand All @@ -107,42 +115,30 @@ const model: EffectType<EffectMeta> = {
</div>
</div>

<div ng-hide="effect.useTag">
<label class="control-fb control--checkbox">Update Name
<input
type="checkbox"
ng-click="effect.rewardSettings.name.update = !effect.rewardSettings.name.update"
ng-checked="effect.rewardSettings.name.update"
aria-label="Update name"
/>
<div class="control__indicator"></div>
</label>
<div ng-hide="effect.rewardSelectMode === 'sortTag'">
<firebot-checkbox
label="Update Name"
model="effect.rewardSettings.name.update"
aria-label="Update name"
/>
<div ng-show="effect.rewardSettings.name.update" style="margin-bottom: 15px;">
<firebot-input model="effect.rewardSettings.name.newValue" placeholder-text="Enter text" />
</div>

<label class="control-fb control--checkbox">Update Description
<input
type="checkbox"
ng-click="effect.rewardSettings.description.update = !effect.rewardSettings.description.update"
ng-checked="effect.rewardSettings.description.update"
aria-label="Update description"
/>
<div class="control__indicator"></div>
</label>
<firebot-checkbox
label="Update Description"
model="effect.rewardSettings.description.update"
aria-label="Update description"
/>
<div ng-show="effect.rewardSettings.description.update" style="margin-bottom: 15px;">
<firebot-input model="effect.rewardSettings.description.newValue" use-text-area="true" placeholder-text="Enter text" />
</div>

<label class="control-fb control--checkbox">Update Cost
<input
type="checkbox"
ng-click="effect.rewardSettings.cost.update = !effect.rewardSettings.cost.update"
ng-checked="effect.rewardSettings.cost.update"
aria-label="Update cost"
/>
<div class="control__indicator"></div>
</label>
<firebot-checkbox
label="Update Cost"
model="effect.rewardSettings.cost.update"
aria-label="Update cost"
/>
<div ng-show="effect.rewardSettings.cost.update" style="margin-bottom: 15px;">
<firebot-input model="effect.rewardSettings.cost.newValue" placeholder-text="Enter new cost" />
</div>
Expand All @@ -156,10 +152,47 @@ const model: EffectType<EffectMeta> = {
.channelRewards.filter(r => r.manageable)
.map(r => ({ id: r.twitchData.id, name: r.twitchData.title }));

$scope.sortTags = sortTagsService.getSortTags('channel rewards');
$scope.sortTags = sortTagsService.getSortTags("channel rewards");

$scope.hasTags = $scope.sortTags != null && $scope.sortTags.length > 0;

$scope.selectRewardOptions = {
dropdown: {
text: "Select Reward",
description: "Pick the Channel Reward from a dropdown list"
},
associated: {
text: "Associated Reward",
description: "Use the Channel Reward associated with the current Event",
hide: !($scope.trigger === "channel_reward" || $scope.triggerMeta?.triggerId?.startsWith("twitch:channel-reward-redemption"))
},
sortTag: {
text: "Sort Tag",
description: "Updates Channel Rewards with the specified Sort Tag",
hide: !$scope.hasTags
},
custom: {
text: "Custom",
description: "Manually specify a Channel Reward Name/ID"
}
};

if ($scope.effect.rewardSelectMode == null) {
// Support legacy bool useTag
$scope.effect.rewardSelectMode = $scope.effect.useTag ? "sortTag" : "dropdown";
}

if (!$scope.hasTags && $scope.effect.rewardSelectMode === "sortTag") {
$scope.effect.rewardSelectMode = "dropdown";
}

$scope.showRewardSettings = () => (
($scope.effect.rewardSelectMode === "dropdown" && $scope.effect.channelRewardId != null && $scope.effect.channelRewardId !== "") ||
($scope.effect.rewardSelectMode === "sortTag" && $scope.effect.sortTagId != null && $scope.effect.sortTagId !== "") ||
($scope.effect.rewardSelectMode === "custom" && $scope.effect.customId != null && $scope.effect.customId !== "") ||
($scope.effect.rewardSelectMode === "associated")
);

$scope.getToggleEnabledDisplay = (action) => {
if (action === "toggle") {
return "Toggle";
Expand Down Expand Up @@ -207,38 +240,65 @@ const model: EffectType<EffectMeta> = {
},
optionsValidator: (effect) => {
const errors = [];
if (!effect.useTag && effect.channelRewardId == null) {
errors.push("Please select a channel reward to update.");
} else if (effect.useTag &&
!effect.rewardSettings.paused.update &&
!effect.rewardSettings.enabled.update) {
errors.push("Please select a channel reward tag to update.");
} else if ((!effect.useTag &&
!effect.rewardSettings.paused.update &&
!effect.rewardSettings.enabled.update &&
!effect.rewardSettings.cost.update &&
!effect.rewardSettings.name.update &&
!effect.rewardSettings.description.update) ||
(effect.useTag &&
!effect.rewardSettings.paused.update &&
!effect.rewardSettings.enabled.update)) {

if (
effect.rewardSelectMode === null
|| (
effect.rewardSelectMode === "dropdown" &&
effect.channelRewardId == null
) || (
effect.rewardSelectMode === "sortTag" &&
effect.sortTagId == null
) || (
effect.rewardSelectMode === "custom" &&
effect.customId == null
)
) {
errors.push("Please specify a channel reward to update.");
}

if (
(
effect.rewardSelectMode !== "sortTag" &&
!effect.rewardSettings.paused.update &&
!effect.rewardSettings.enabled.update &&
!effect.rewardSettings.cost.update &&
!effect.rewardSettings.name.update &&
!effect.rewardSettings.description.update
) ||
(
effect.rewardSelectMode === "sortTag" &&
!effect.rewardSettings.paused.update &&
!effect.rewardSettings.enabled.update
)
) {
errors.push("Please select at least one property to update.");
} else if (effect.rewardSettings.name.update &&
}

if (effect.rewardSettings.name.update &&
(effect.rewardSettings.name.newValue == null ||
effect.rewardSettings.name.newValue === "")) {
effect.rewardSettings.name.newValue === "")
) {
errors.push("Please provide a new name for the reward.");
} else if (effect.rewardSettings.description.update &&
}

if (effect.rewardSettings.description.update &&
(effect.rewardSettings.description.newValue == null ||
effect.rewardSettings.description.newValue === "")) {
effect.rewardSettings.description.newValue === "")
) {
errors.push("Please provide a new description for the reward.");
} else if (effect.rewardSettings.cost.update &&
!effect.rewardSettings.cost.newValue?.length) {
}

if (effect.rewardSettings.cost.update &&
(effect.rewardSettings.cost.newValue == null ||
effect.rewardSettings.cost.newValue === "")
) {
errors.push("Please provide a new cost for the reward.");
}

return errors;
},
onTriggerEvent: async ({ effect }) => {
onTriggerEvent: async ({ trigger, effect }) => {
if (!effect.rewardSettings.paused.update &&
!effect.rewardSettings.enabled.update &&
!effect.rewardSettings.cost.update &&
Expand All @@ -247,7 +307,10 @@ const model: EffectType<EffectMeta> = {
logger.error("Update Channel Reward: No updates selected. Skipping effect.");
return false;
}
if (!effect.useTag) {
if (!effect.rewardSelectMode) {
effect.rewardSelectMode = effect.useTag ? "sortTag" : "dropdown";
}
if (effect.rewardSelectMode !== "sortTag") {
if (effect.rewardSettings.name.update && (effect.rewardSettings.name.newValue == null ||
effect.rewardSettings.name.newValue === "" ||
effect.rewardSettings.name.newValue.length > 45)) {
Expand All @@ -269,9 +332,24 @@ const model: EffectType<EffectMeta> = {
return;
}

const channelReward = channelRewardsManager.getChannelReward(effect.channelRewardId);
let rewardId;
switch (effect.rewardSelectMode) {
case "dropdown":
rewardId = effect.channelRewardId;
break;
case "associated":
rewardId = trigger.metadata.eventData?.rewardId ?? trigger.metadata.rewardId;
break;
case "custom":
rewardId = validate(effect.customId)
? effect.customId
: channelRewardsManager.getChannelRewardIdByName(effect.customId);
break;
}

const channelReward = channelRewardsManager.getChannelReward(rewardId);
if (channelReward == null) {
logger.error(`Update Channel Reward: Invalid Channel Reward ID: ${rewardId}`);
return;
}

Expand Down
7 changes: 4 additions & 3 deletions src/gui/app/directives/controls/firebot-radios.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";

(function() {
(function () {
angular
.module('firebotApp')
.component("firebotRadios", {
Expand All @@ -14,6 +14,7 @@
<firebot-radio-container inline="$ctrl.inline" style="{{$ctrl.style}}">
<firebot-radio
ng-repeat="(value, label) in $ctrl.options"
ng-hide="{{$ctrl.labelIsObj(label) ? label.hide : false}}"
label="{{$ctrl.labelIsObj(label) ? label.text : label}}"
description="{{$ctrl.labelIsObj(label) ? label.description : undefined}}"
tooltip="{{$ctrl.labelIsObj(label) ? label.tooltip : undefined}}"
Expand All @@ -22,10 +23,10 @@
/>
</firebot-radio-container>
`,
controller: function() {
controller: function () {
const $ctrl = this;

$ctrl.labelIsObj = label => typeof label === "object";
}
});
}());
}());
Loading