Skip to content
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
52 changes: 39 additions & 13 deletions src/backend/channel-rewards/channel-reward-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { CustomReward, RewardRedemption, RewardRedemptionsApprovalRequest } from
import { EffectTrigger } from "../../shared/effect-constants";
import { RewardRedemptionMetadata, SavedChannelReward } from "../../types/channel-rewards";
import { TriggerType } from "../common/EffectType";
import { EffectList } from "../../types/effects";

class ChannelRewardManager {
channelRewards: Record<string, SavedChannelReward> = {};
Expand Down Expand Up @@ -259,7 +260,29 @@ class ChannelRewardManager {
return channelReward ? channelReward.id : null;
}

async triggerChannelReward(rewardId: string, metadata: RewardRedemptionMetadata, manual = false): Promise<any> {
private async triggerRewardEffects(metadata: RewardRedemptionMetadata, effectList?: EffectList, manual = false): Promise<void> {
if (effectList == null || effectList.list == null) {
return;
}

const effectRunner = require("../common/effect-runner");

const processEffectsRequest = {
trigger: {
type: manual ? EffectTrigger.MANUAL : EffectTrigger.CHANNEL_REWARD,
metadata: metadata
},
effects: effectList
};

try {
return effectRunner.processEffects(processEffectsRequest);
} catch (reason) {
console.log(`error when running effects: ${reason}`);
}
}

async triggerChannelReward(rewardId: string, metadata: RewardRedemptionMetadata, manual = false): Promise<boolean | void> {
const savedReward = this.channelRewards[rewardId];
if (savedReward == null || savedReward.effects == null || savedReward.effects.list == null) {
return;
Expand Down Expand Up @@ -325,22 +348,25 @@ class ChannelRewardManager {
}
}

return this.triggerRewardEffects(metadata, savedReward.effects, manual);
}

const effectRunner = require("../common/effect-runner");
async triggerChannelRewardFulfilled(rewardId: string, metadata: RewardRedemptionMetadata, manual = false): Promise<void> {
const savedReward = this.channelRewards[rewardId];
if (savedReward == null) {
return;
}

const processEffectsRequest = {
trigger: {
type: manual ? EffectTrigger.MANUAL : EffectTrigger.CHANNEL_REWARD,
metadata: metadata
},
effects: savedReward.effects
};
return this.triggerRewardEffects(metadata, savedReward.effectsFulfilled, manual);
}

try {
return effectRunner.processEffects(processEffectsRequest);
} catch (reason) {
console.log(`error when running effects: ${reason}`);
async triggerChannelRewardCanceled(rewardId: string, metadata: RewardRedemptionMetadata, manual = false): Promise<void> {
const savedReward = this.channelRewards[rewardId];
if (savedReward == null) {
return;
}

return this.triggerRewardEffects(metadata, savedReward.effectsCanceled, manual);
}

async refreshChannelRewardRedemptions(): Promise<void> {
Expand Down
50 changes: 50 additions & 0 deletions src/backend/events/builtin/twitch-event-source.js
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,56 @@ module.exports = {
}
}
},
{
id: "channel-reward-redemption-fulfilled",
name: "Channel Reward Redemption Approved",
description: "When a CUSTOM channel reward redemption is Completed/Approved",
cached: false,
cacheMetaKey: "username",
cacheTtlInSecs: 1,
queued: false,
manualMetadata: {
username: "firebot",
userDisplayName: "Firebot",
userId: "",
rewardName: "Test Reward",
rewardImage: "https://static-cdn.jtvnw.net/automatic-reward-images/highlight-1.png",
rewardCost: 200,
messageText: "Test message"
},
activityFeed: {
icon: "fad fa-circle",
getMessage: (eventData) => {
const showUserIdName = eventData.username.toLowerCase() !== eventData.userDisplayName.toLowerCase();
return `**${eventData.userDisplayName}${showUserIdName ? ` (${eventData.username})` : ""}**'s redemption of **${eventData.rewardName}** was approved. ${eventData.messageText && !!eventData.messageText.length ? `*${eventData.messageText}*` : ''}`;
}
}
},
{
id: "channel-reward-redemption-canceled",
name: "Channel Reward Redemption Rejected",
description: "When a CUSTOM channel reward redemption is Rejected/Refunded",
cached: false,
cacheMetaKey: "username",
cacheTtlInSecs: 1,
queued: false,
manualMetadata: {
username: "firebot",
userDisplayName: "Firebot",
userId: "",
rewardName: "Test Reward",
rewardImage: "https://static-cdn.jtvnw.net/automatic-reward-images/highlight-1.png",
rewardCost: 200,
messageText: "Test message"
},
activityFeed: {
icon: "fad fa-circle",
getMessage: (eventData) => {
const showUserIdName = eventData.username.toLowerCase() !== eventData.userDisplayName.toLowerCase();
return `**${eventData.userDisplayName}${showUserIdName ? ` (${eventData.username})` : ""}**'s redemption of **${eventData.rewardName}** was rejected. ${eventData.messageText && !!eventData.messageText.length ? `*${eventData.messageText}*` : ''}`;
}
}
},
{
id: "whisper",
name: "Whisper",
Expand Down
37 changes: 37 additions & 0 deletions src/backend/events/twitch-events/reward-redemption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,41 @@ export function handleRewardRedemption(
rewardManager.triggerChannelReward(rewardId, redemptionMeta);
eventManager.triggerEvent("twitch", "channel-reward-redemption", redemptionMeta);
}, 100);
}

export function handleRewardUpdated(
redemptionId: string,
status: string,
messageText: string,
userId: string,
username: string,
userDisplayName: string,
rewardId: string,
rewardTitle: string,
rewardPrompt: string,
rewardCost: number,
rewardImageUrl: string
): void {
const redemptionMeta = {
username,
userId,
userDisplayName,
messageText,
args: (messageText ?? "").split(" "),
redemptionId,
rewardId,
rewardImage: rewardImageUrl,
rewardName: rewardTitle,
rewardDescription: rewardPrompt,
rewardCost: rewardCost
};

// Possible values for status are 'fulfilled' and 'canceled' according to Twitch docs
if (status === 'fulfilled') {
rewardManager.triggerChannelRewardFulfilled(rewardId, redemptionMeta);
eventManager.triggerEvent("twitch", "channel-reward-redemption-fulfilled", redemptionMeta);
} else {
rewardManager.triggerChannelRewardCanceled(rewardId, redemptionMeta);
eventManager.triggerEvent("twitch", "channel-reward-redemption-canceled", redemptionMeta);
}
}
30 changes: 29 additions & 1 deletion src/backend/twitch-api/eventsub/eventsub-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,35 @@ class TwitchEventSubClient {
});
this._subscriptions.push(customRewardRedemptionSubscription);

const customRewardRedemptionUpdateSubscription = this._eventSubListener.onChannelRedemptionUpdate(streamer.userId, async () => {
const customRewardRedemptionUpdateSubscription = this._eventSubListener.onChannelRedemptionUpdate(streamer.userId, async (event) => {
const reward = await twitchApi.channelRewards.getCustomChannelReward(event.rewardId);
let imageUrl = "";

if (reward && reward.defaultImage) {
const images = reward.defaultImage;
if (images.url4x) {
imageUrl = images.url4x;
} else if (images.url2x) {
imageUrl = images.url2x;
} else if (images.url1x) {
imageUrl = images.url1x;
}
}

twitchEventsHandler.rewardRedemption.handleRewardUpdated(
event.id,
event.status,
event.input,
event.userId,
event.userName,
event.userDisplayName,
event.rewardId,
event.rewardTitle,
event.rewardPrompt,
event.rewardCost,
imageUrl
);

rewardManager.refreshChannelRewardRedemptions();
});
this._subscriptions.push(customRewardRedemptionUpdateSubscription);
Expand Down
7 changes: 5 additions & 2 deletions src/backend/variables/builtin/twitch/reward/reward-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import { ReplaceVariable } from "../../../../../types/variables";
import { EffectTrigger } from "../../../../../shared/effect-constants";
import { OutputDataType, VariableCategory } from "../../../../../shared/variable-constants";


const triggers = {};
triggers[EffectTrigger.EVENT] = ["twitch:channel-reward-redemption"];
triggers[EffectTrigger.EVENT] = [
"twitch:channel-reward-redemption",
"twitch:channel-reward-redemption-fulfilled",
"twitch:channel-reward-redemption-canceled"
];
triggers[EffectTrigger.CHANNEL_REWARD] = true;
triggers[EffectTrigger.PRESET_LIST] = true;
triggers[EffectTrigger.MANUAL] = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import { ReplaceVariable } from "../../../../../types/variables";
import { EffectTrigger } from "../../../../../shared/effect-constants";
import { OutputDataType, VariableCategory } from "../../../../../shared/variable-constants";


const triggers = {};
triggers[EffectTrigger.EVENT] = ["twitch:channel-reward-redemption"];
triggers[EffectTrigger.EVENT] = [
"twitch:channel-reward-redemption",
"twitch:channel-reward-redemption-fulfilled",
"twitch:channel-reward-redemption-canceled"
];
triggers[EffectTrigger.CHANNEL_REWARD] = true;
triggers[EffectTrigger.PRESET_LIST] = true;
triggers[EffectTrigger.MANUAL] = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { EffectTrigger } from "../../../../../shared/effect-constants";
import { OutputDataType, VariableCategory } from "../../../../../shared/variable-constants";

const triggers = {};
triggers[EffectTrigger.EVENT] = ["twitch:channel-reward-redemption"];
triggers[EffectTrigger.EVENT] = [
"twitch:channel-reward-redemption",
"twitch:channel-reward-redemption-fulfilled",
"twitch:channel-reward-redemption-canceled"
];
triggers[EffectTrigger.CHANNEL_REWARD] = true;
triggers[EffectTrigger.PRESET_LIST] = true;
triggers[EffectTrigger.MANUAL] = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { EffectTrigger } from "../../../../../shared/effect-constants";
import { OutputDataType, VariableCategory } from "../../../../../shared/variable-constants";

const triggers = {};
triggers[EffectTrigger.EVENT] = ["twitch:channel-reward-redemption"];
triggers[EffectTrigger.EVENT] = [
"twitch:channel-reward-redemption",
"twitch:channel-reward-redemption-fulfilled",
"twitch:channel-reward-redemption-canceled"
];
triggers[EffectTrigger.CHANNEL_REWARD] = true;
triggers[EffectTrigger.PRESET_LIST] = true;
triggers[EffectTrigger.MANUAL] = true;
Expand Down
Loading