From 7dc0effa7e489b57d6dd15e6ec6b37a6b1d4bb85 Mon Sep 17 00:00:00 2001 From: Dennis Rijsdijk Date: Sat, 16 May 2026 13:08:29 +0200 Subject: [PATCH 1/4] fix: cheerMote detection in cheerMessage this could occasionally remove strings like "huds601", and fails with cheermotes like "huds601Cheer1", only removing "Cheer1" --- .../twitch/api/eventsub/eventsub-chat-helpers.ts | 15 +++++++++++++++ .../twitch/api/eventsub/eventsub-client.ts | 2 +- .../twitch/variables/bits/cheer-message.ts | 5 +---- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-chat-helpers.ts b/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-chat-helpers.ts index 95129f864..4c0589373 100644 --- a/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-chat-helpers.ts +++ b/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-chat-helpers.ts @@ -9,6 +9,7 @@ import { type EventSubAutoModMessageHoldV2Event, type EventSubChannelChatNotificationEvent, type EventSubUserWhisperMessageEvent, + EventSubChannelBitsUseEvent, EventSubChannelChatAnnouncementNotificationEvent, EventSubChannelChatMessageEvent } from "@twurple/eventsub-base"; @@ -833,6 +834,20 @@ class TwitchEventSubChatHelpers { .filter(r => r.showBadgeInChat) .map(r => r.name); } + + sanitizeCheerMessage(event: EventSubChannelBitsUseEvent): string { + const cheerMessageParts: string[] = []; + + for (const part of event.messageParts) { + if (part.type === "cheermote") { + continue; + } + + cheerMessageParts.push(part.text.trim()); + } + + return cheerMessageParts.join(" "); + } } const chatHelpers = new TwitchEventSubChatHelpers(); diff --git a/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-client.ts b/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-client.ts index 27a9ddfa5..ce2d338c8 100644 --- a/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-client.ts +++ b/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-client.ts @@ -72,7 +72,7 @@ class TwitchEventSubClient { event.userDisplayName, event.bits, totalBits, - event.messageText ?? "" + TwitchEventSubChatHelpers.sanitizeCheerMessage(event) ); break; } diff --git a/src/backend/streaming-platforms/twitch/variables/bits/cheer-message.ts b/src/backend/streaming-platforms/twitch/variables/bits/cheer-message.ts index 263a55668..2a7720ac4 100644 --- a/src/backend/streaming-platforms/twitch/variables/bits/cheer-message.ts +++ b/src/backend/streaming-platforms/twitch/variables/bits/cheer-message.ts @@ -13,10 +13,7 @@ const model : ReplaceVariable = { possibleDataOutput: ["text"] }, evaluator: (trigger: Trigger) => { - const cheerMessage = (trigger.metadata.eventData.cheerMessage || ""); - return cheerMessage - .replace(/[a-zA-Z]+\d+( |\b)/g, "") - .trim(); + return trigger.metadata.eventData.cheerMessage || ""; } }; From 66c32602d83155f1703ca7161e76129ef2ee7cb4 Mon Sep 17 00:00:00 2001 From: Dennis Rijsdijk Date: Tue, 19 May 2026 21:49:56 +0200 Subject: [PATCH 2/4] fix: cheermotes in simulate --- .../api/eventsub/eventsub-chat-helpers.ts | 15 -------------- .../twitch/api/eventsub/eventsub-client.ts | 3 ++- .../streaming-platforms/twitch/events/bits.ts | 7 +++++-- .../twitch/variables/bits/cheer-message.ts | 20 ++++++++++++++++++- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-chat-helpers.ts b/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-chat-helpers.ts index 4c0589373..95129f864 100644 --- a/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-chat-helpers.ts +++ b/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-chat-helpers.ts @@ -9,7 +9,6 @@ import { type EventSubAutoModMessageHoldV2Event, type EventSubChannelChatNotificationEvent, type EventSubUserWhisperMessageEvent, - EventSubChannelBitsUseEvent, EventSubChannelChatAnnouncementNotificationEvent, EventSubChannelChatMessageEvent } from "@twurple/eventsub-base"; @@ -834,20 +833,6 @@ class TwitchEventSubChatHelpers { .filter(r => r.showBadgeInChat) .map(r => r.name); } - - sanitizeCheerMessage(event: EventSubChannelBitsUseEvent): string { - const cheerMessageParts: string[] = []; - - for (const part of event.messageParts) { - if (part.type === "cheermote") { - continue; - } - - cheerMessageParts.push(part.text.trim()); - } - - return cheerMessageParts.join(" "); - } } const chatHelpers = new TwitchEventSubChatHelpers(); diff --git a/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-client.ts b/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-client.ts index ce2d338c8..46a5d9b98 100644 --- a/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-client.ts +++ b/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-client.ts @@ -72,7 +72,8 @@ class TwitchEventSubClient { event.userDisplayName, event.bits, totalBits, - TwitchEventSubChatHelpers.sanitizeCheerMessage(event) + event.messageText, + event.messageParts ); break; } diff --git a/src/backend/streaming-platforms/twitch/events/bits.ts b/src/backend/streaming-platforms/twitch/events/bits.ts index a357c0efe..d3ea8b376 100644 --- a/src/backend/streaming-platforms/twitch/events/bits.ts +++ b/src/backend/streaming-platforms/twitch/events/bits.ts @@ -1,6 +1,7 @@ import { EventManager } from "../../../events/event-manager"; import powerUpsManager from "../../../power-ups/power-ups-manager"; import frontendCommunicator from "../../../common/frontend-communicator"; +import { EventSubChannelBitsUseMessagePart } from "@twurple/eventsub-base/lib/events/EventSubChannelBitsUseEvent.external"; export function triggerCheer( username: string, @@ -8,7 +9,8 @@ export function triggerCheer( userDisplayName: string, bits: number, totalBits: number, - cheerMessage: string + cheerMessage: string, + cheerMessageParts: EventSubChannelBitsUseMessagePart[] ): void { void EventManager.triggerEvent("twitch", "cheer", { username, @@ -17,7 +19,8 @@ export function triggerCheer( isAnonymous: false, bits, totalBits, - cheerMessage + cheerMessage, + cheerMessageParts }); } diff --git a/src/backend/streaming-platforms/twitch/variables/bits/cheer-message.ts b/src/backend/streaming-platforms/twitch/variables/bits/cheer-message.ts index 2a7720ac4..2ff38e281 100644 --- a/src/backend/streaming-platforms/twitch/variables/bits/cheer-message.ts +++ b/src/backend/streaming-platforms/twitch/variables/bits/cheer-message.ts @@ -1,4 +1,5 @@ import type { ReplaceVariable, Trigger, TriggersObject } from "../../../../../types/variables"; +import { EventSubChannelBitsUseMessagePart } from "@twurple/eventsub-base/lib/events/EventSubChannelBitsUseEvent.external"; const triggers: TriggersObject = {}; triggers["event"] = ["twitch:cheer", "twitch:bits-powerup-message-effect", "twitch:bits-powerup-gigantified-emote"]; @@ -13,7 +14,24 @@ const model : ReplaceVariable = { possibleDataOutput: ["text"] }, evaluator: (trigger: Trigger) => { - return trigger.metadata.eventData.cheerMessage || ""; + if (trigger.metadata.eventData.cheerMessageParts == null) { + const cheerMessage = (trigger.metadata.eventData.cheerMessage || "") as string; + return cheerMessage + .replace(/[a-zA-Z]+\d+( |\b)/g, "") + .trim(); + } + + const cheerMessageParts: string[] = []; + + for (const part of (trigger.metadata.eventData.cheerMessageParts as EventSubChannelBitsUseMessagePart[])) { + if (part.type === "cheermote") { + continue; + } + + cheerMessageParts.push(part.text.trim()); + } + + return cheerMessageParts.join(" "); } }; From b1082f812f299f14baef25e65a0d228bec5d1af1 Mon Sep 17 00:00:00 2001 From: Dennis Rijsdijk Date: Tue, 19 May 2026 21:51:32 +0200 Subject: [PATCH 3/4] restore default string for messageText --- .../streaming-platforms/twitch/api/eventsub/eventsub-client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-client.ts b/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-client.ts index 46a5d9b98..bfaade49f 100644 --- a/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-client.ts +++ b/src/backend/streaming-platforms/twitch/api/eventsub/eventsub-client.ts @@ -72,7 +72,7 @@ class TwitchEventSubClient { event.userDisplayName, event.bits, totalBits, - event.messageText, + event.messageText ?? "", event.messageParts ); break; From c7c48be159efa5ea851f8012b386a8a7f5fcbe13 Mon Sep 17 00:00:00 2001 From: Dennis Rijsdijk Date: Tue, 19 May 2026 22:04:37 +0200 Subject: [PATCH 4/4] move bitsuse message part to private type mirror --- .../streaming-platforms/twitch/api/twurple-private-types.d.ts | 2 ++ src/backend/streaming-platforms/twitch/events/bits.ts | 2 +- .../streaming-platforms/twitch/variables/bits/cheer-message.ts | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/backend/streaming-platforms/twitch/api/twurple-private-types.d.ts b/src/backend/streaming-platforms/twitch/api/twurple-private-types.d.ts index 6fb48d73c..49e7d909d 100644 --- a/src/backend/streaming-platforms/twitch/api/twurple-private-types.d.ts +++ b/src/backend/streaming-platforms/twitch/api/twurple-private-types.d.ts @@ -46,6 +46,8 @@ export interface EventSubChatMessageMentionPart { export type EventSubChatMessagePart = EventSubChatMessageTextPart | EventSubChatMessageCheermotePart | EventSubChatMessageEmotePart | EventSubChatMessageMentionPart; +export type EventSubChannelBitsUseMessagePart = EventSubChatMessageTextPart | EventSubChatMessageCheermotePart | EventSubChatMessageEmotePart; + export interface EventSubChatMessageData { text: string; fragments: EventSubChatMessagePart[]; diff --git a/src/backend/streaming-platforms/twitch/events/bits.ts b/src/backend/streaming-platforms/twitch/events/bits.ts index d3ea8b376..f0d25d627 100644 --- a/src/backend/streaming-platforms/twitch/events/bits.ts +++ b/src/backend/streaming-platforms/twitch/events/bits.ts @@ -1,7 +1,7 @@ import { EventManager } from "../../../events/event-manager"; import powerUpsManager from "../../../power-ups/power-ups-manager"; import frontendCommunicator from "../../../common/frontend-communicator"; -import { EventSubChannelBitsUseMessagePart } from "@twurple/eventsub-base/lib/events/EventSubChannelBitsUseEvent.external"; +import { EventSubChannelBitsUseMessagePart } from "../api/twurple-private-types"; export function triggerCheer( username: string, diff --git a/src/backend/streaming-platforms/twitch/variables/bits/cheer-message.ts b/src/backend/streaming-platforms/twitch/variables/bits/cheer-message.ts index 2ff38e281..ac54fcb04 100644 --- a/src/backend/streaming-platforms/twitch/variables/bits/cheer-message.ts +++ b/src/backend/streaming-platforms/twitch/variables/bits/cheer-message.ts @@ -1,5 +1,5 @@ import type { ReplaceVariable, Trigger, TriggersObject } from "../../../../../types/variables"; -import { EventSubChannelBitsUseMessagePart } from "@twurple/eventsub-base/lib/events/EventSubChannelBitsUseEvent.external"; +import { EventSubChannelBitsUseMessagePart } from "../../api/twurple-private-types"; const triggers: TriggersObject = {}; triggers["event"] = ["twitch:cheer", "twitch:bits-powerup-message-effect", "twitch:bits-powerup-gigantified-emote"];