From 5bf7d5f6208c80e945cf11bc3f787d3db5f957e5 Mon Sep 17 00:00:00 2001 From: Suyooo Date: Wed, 10 Apr 2024 13:07:19 +0200 Subject: [PATCH 1/5] Allow marking emotes as modifier emotes --- src/@types/betterttv.d.ts | 1 + src/@types/slime2.d.ts | 1 + src/services/emotes/BetterTTV.ts | 68 +++++++++++++++++++ src/services/emotes/FrankerFaceZ.ts | 1 + src/services/emotes/YouTube.ts | 1 + .../twitch/chat/transforms/useEmotePart.ts | 1 + .../platforms/twitch/useChannelEmotes.ts | 1 + 7 files changed, 74 insertions(+) diff --git a/src/@types/betterttv.d.ts b/src/@types/betterttv.d.ts index 479544d..46c6790 100644 --- a/src/@types/betterttv.d.ts +++ b/src/@types/betterttv.d.ts @@ -12,6 +12,7 @@ namespace BetterTTV { code: string imageType: string animated: boolean + modifier: boolean } type ChannelEmote = Emote & { diff --git a/src/@types/slime2.d.ts b/src/@types/slime2.d.ts index 2426ed5..6fe8351 100644 --- a/src/@types/slime2.d.ts +++ b/src/@types/slime2.d.ts @@ -120,6 +120,7 @@ namespace Slime2 { name: string images: Emote.Images source: Emote.Source + isModifier: boolean } type EmoteMap = Map diff --git a/src/services/emotes/BetterTTV.ts b/src/services/emotes/BetterTTV.ts index 6ee290f..855d2f6 100644 --- a/src/services/emotes/BetterTTV.ts +++ b/src/services/emotes/BetterTTV.ts @@ -10,6 +10,73 @@ export default async function getChannelEmotes( ): Promise { const emoteMap = new Map() + setEmotes([ + { + id: "6468f7acaee1f7f47567708e", + code: "c!", + imageType: "png", + animated: false, + userId: "5561169bd6b9d206222a8c19", + modifier: true, + }, + { + id: "6468f845aee1f7f47567709b", + code: "h!", + imageType: "png", + animated: false, + userId: "5561169bd6b9d206222a8c19", + modifier: true, + }, + { + id: "6468f869aee1f7f4756770a8", + code: "l!", + imageType: "png", + animated: false, + userId: "5561169bd6b9d206222a8c19", + modifier: true, + }, + { + id: "6468f883aee1f7f4756770b5", + code: "r!", + imageType: "png", + animated: false, + userId: "5561169bd6b9d206222a8c19", + modifier: true, + }, + { + id: "6468f89caee1f7f4756770c2", + code: "v!", + imageType: "png", + animated: false, + userId: "5561169bd6b9d206222a8c19", + modifier: true, + }, + { + id: "6468f8d1aee1f7f4756770cf", + code: "z!", + imageType: "png", + animated: false, + userId: "5561169bd6b9d206222a8c19", + modifier: true, + }, + { + id: "64e3b31920cb0d25d950a9f9", + code: "w!", + imageType: "png", + animated: false, + userId: "5561169bd6b9d206222a8c19", + modifier: true, + }, + { + id: "65cbe7dbaed093b2eaf87c65", + code: "p!", + imageType: "png", + animated: false, + userId: "5561169bd6b9d206222a8c19", + modifier: true, + } + ]) + const user = await bttvApi .get(`/users/${platform}/${userId}`) .then(response => response.data) @@ -31,6 +98,7 @@ export default async function getChannelEmotes( static: buildEmoteUrls(emote.id, true), }, source: 'betterttv', + isModifier: emote.modifier, }) }) } diff --git a/src/services/emotes/FrankerFaceZ.ts b/src/services/emotes/FrankerFaceZ.ts index 7bf1207..497eeed 100644 --- a/src/services/emotes/FrankerFaceZ.ts +++ b/src/services/emotes/FrankerFaceZ.ts @@ -33,6 +33,7 @@ export default async function getChannelEmotes( static: buildEmoteUrls(emote, true), }, source: 'frankerfacez', + isModifier: false, }) }) }) diff --git a/src/services/emotes/YouTube.ts b/src/services/emotes/YouTube.ts index a7f38dd..15ace1e 100644 --- a/src/services/emotes/YouTube.ts +++ b/src/services/emotes/YouTube.ts @@ -11,6 +11,7 @@ export function getGlobalEmotes(): Slime2.Event.Message.EmoteMap { static: buildEmoteUrls(emoji.image), }, source: 'youtube', + isModifier: false, }) }) diff --git a/src/services/platforms/twitch/chat/transforms/useEmotePart.ts b/src/services/platforms/twitch/chat/transforms/useEmotePart.ts index cdbbca1..b84a09c 100644 --- a/src/services/platforms/twitch/chat/transforms/useEmotePart.ts +++ b/src/services/platforms/twitch/chat/transforms/useEmotePart.ts @@ -41,6 +41,7 @@ export function useEmotePart() { static: buildEmoteUrls(id, true), }, source: 'twitch', + isModifier: false, }, } } diff --git a/src/services/platforms/twitch/useChannelEmotes.ts b/src/services/platforms/twitch/useChannelEmotes.ts index cee82b3..3d3e561 100644 --- a/src/services/platforms/twitch/useChannelEmotes.ts +++ b/src/services/platforms/twitch/useChannelEmotes.ts @@ -23,6 +23,7 @@ export default function useChannelEmotes() { static: buildEmoteUrls(id, true), }, source: 'twitch', + isModifier: false, } }) }, From f0acc6882749f7a0c03a3e0fe5d79e8728b21082 Mon Sep 17 00:00:00 2001 From: Suyooo Date: Wed, 10 Apr 2024 15:16:45 +0200 Subject: [PATCH 2/5] Add BTTV emote modifiers parsing and styling --- src/@types/twitch.d.ts | 2 +- src/main.css | 9 ++ .../twitch/chat/transforms/useMessage.ts | 4 +- .../twitch/chat/transforms/useText.ts | 96 ++++++++++++++++++- 4 files changed, 107 insertions(+), 4 deletions(-) diff --git a/src/@types/twitch.d.ts b/src/@types/twitch.d.ts index 60c808c..9cce6f1 100644 --- a/src/@types/twitch.d.ts +++ b/src/@types/twitch.d.ts @@ -107,7 +107,7 @@ namespace Twitch { type Type = | { type: 'text' } | { type: 'cheer'; cheer: Cheermote } - | { type: 'emote'; emote: Slime2.Event.Message.Emote } + | { type: 'emote'; emote: Slime2.Event.Message.Emote; modifier?: string } } type Cheermote = { diff --git a/src/main.css b/src/main.css index 13f8b90..b22e45b 100644 --- a/src/main.css +++ b/src/main.css @@ -129,3 +129,12 @@ html { opacity: 0; } } + +@keyframes slime2-emote-modifier-party { + from { + filter: sepia(0.5) hue-rotate(0deg) saturate(2.5); + } + to { + filter: sepia(0.5) hue-rotate(360deg) saturate(2.5); + } +} diff --git a/src/services/platforms/twitch/chat/transforms/useMessage.ts b/src/services/platforms/twitch/chat/transforms/useMessage.ts index 7b56f66..56c7e68 100644 --- a/src/services/platforms/twitch/chat/transforms/useMessage.ts +++ b/src/services/platforms/twitch/chat/transforms/useMessage.ts @@ -6,9 +6,9 @@ import useUser from './useUser' /** * Hook that returns the function {@link transform} */ -export default function useMessage() { +export default function useMessage(enableEmoteModifiers: boolean = false) { const { data: channelPointRewards } = useChannelPointRewards() - const transformText = useText() + const transformText = useText(enableEmoteModifiers) const transformUser = useUser() /** diff --git a/src/services/platforms/twitch/chat/transforms/useText.ts b/src/services/platforms/twitch/chat/transforms/useText.ts index 956dc58..03a510b 100644 --- a/src/services/platforms/twitch/chat/transforms/useText.ts +++ b/src/services/platforms/twitch/chat/transforms/useText.ts @@ -7,7 +7,7 @@ import { useTextPart } from './useTextPart' /** * Hook that returns the function {@link transform} */ -export default function useText() { +export default function useText(enableEmoteModifiers: boolean = false) { const { data: cheermotes } = useCheermotes() const transformTextPart = useTextPart() const transformCheerPart = useCheerPart() @@ -47,8 +47,102 @@ export default function useText() { } }) + let emoteModifiersQueued: Slime2.Event.Message.Emote[] = [] + for (let i = 0; i < parts.length; i++) { + const part = parts[i] + if (part.type === 'emote') { + if (part.emote.isModifier) { + emoteModifiersQueued.push(part.emote) + if ( + i + 1 < parts.length && + parts[i + 1].type === 'text' && + parts[i + 1].text === ' ' + ) { + // drop space seperating modifier from following emote + parts.splice(i + 1, 1) + } + } else if (emoteModifiersQueued.length > 0) { + i -= emoteModifiersQueued.length + parts.splice(i, emoteModifiersQueued.length) + + part.modifier = '' + emoteModifiersQueued.forEach(emote => { + switch (emote.name) { + case 'c!': // Cursed + part.modifier += + 'filter: grayscale(1) brightness(.7) contrast(2.5);' + break + case 'h!': // Horizontal Flip + part.modifier += 'transform: scaleX(-1);' + break + case 'l!': // Left Rotate + part.modifier += 'transform: rotate(-90deg);' + break + case 'p!': // Party + part.modifier += + 'animation: slime2-emote-modifier-party 1.5s linear infinite;' + break + case 'r!': // Right Rotate + part.modifier += 'transform: rotate(90deg);' + break + case 'v!': // Vertical Flip + part.modifier += 'transform: scaleY(-1);' + break + case 'w!': // Wide + part.modifier += 'transform: scaleX(3);' + // BTTV hardcodes emote size to make widened emote spacing work. + // Instead, we add blank emotes before and after this emote, for easy spacing no matter what widget + parts.splice(i + 1, 0, BLANK_EMOTE_PART) + parts.splice(i, 0, BLANK_EMOTE_PART) + i += 1 + break + case 'z!': // Zero Space + // Simply remove the space-only text part before this emote + if ( + i - 1 < parts.length && + parts[i - 1].type === 'text' && + parts[i - 1].text === ' ' + ) { + parts.splice(i - 1, 1) + } + break + } + }) + emoteModifiersQueued = [] + + if (part.modifier === '') part.modifier = undefined + } + } else if (emoteModifiersQueued.length > 0) { + emoteModifiersQueued = [] + } + } + console.log(parts) + return parts } return transform } + +const BLANK_EMOTE_PART: Twitch.Event.Message.Part = { + type: 'emote', + text: '', + emote: { + id: '', + name: ':blank:', + images: { + default: { + x1: 'data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', + x2: 'data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', + x4: 'data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', + }, + static: { + x1: 'data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', + x2: 'data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', + x4: 'data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', + }, + }, + source: 'betterttv', + isModifier: false, + }, +} From 198d885a3c30d58151a0dec14c2d7beb93e87eb6 Mon Sep 17 00:00:00 2001 From: Suyooo Date: Wed, 10 Apr 2024 15:22:02 +0200 Subject: [PATCH 3/5] Do not use modifier emotes when sending test messages --- src/services/platforms/twitch/chat/useEmulate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/platforms/twitch/chat/useEmulate.ts b/src/services/platforms/twitch/chat/useEmulate.ts index 822f68a..76246ac 100644 --- a/src/services/platforms/twitch/chat/useEmulate.ts +++ b/src/services/platforms/twitch/chat/useEmulate.ts @@ -41,7 +41,7 @@ export default function useEmulateTwitchMessage() { const emotes = [ ...channelEmotes!, ...Array.from(thirdPartyEmoteMap!.values()), - ] + ].filter(e => !e.isModifier) const date = new Date() const first = Random.chance(5) // 5% chance of being first time chat From 37f8852a688806fc845959a7df178983f6729bda Mon Sep 17 00:00:00 2001 From: Suyooo Date: Wed, 10 Apr 2024 15:37:45 +0200 Subject: [PATCH 4/5] Emote modifiers may be seperated from target emote by multiple whitespace characters --- .../platforms/twitch/chat/transforms/useText.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/services/platforms/twitch/chat/transforms/useText.ts b/src/services/platforms/twitch/chat/transforms/useText.ts index 03a510b..1e1921a 100644 --- a/src/services/platforms/twitch/chat/transforms/useText.ts +++ b/src/services/platforms/twitch/chat/transforms/useText.ts @@ -56,9 +56,9 @@ export default function useText(enableEmoteModifiers: boolean = false) { if ( i + 1 < parts.length && parts[i + 1].type === 'text' && - parts[i + 1].text === ' ' + parts[i + 1].text.trim() === '' ) { - // drop space seperating modifier from following emote + // Drop whitespace seperating the modifier from the following emote parts.splice(i + 1, 1) } } else if (emoteModifiersQueued.length > 0) { @@ -90,18 +90,18 @@ export default function useText(enableEmoteModifiers: boolean = false) { break case 'w!': // Wide part.modifier += 'transform: scaleX(3);' - // BTTV hardcodes emote size to make widened emote spacing work. - // Instead, we add blank emotes before and after this emote, for easy spacing no matter what widget + // We can't hardcod emote size to make widened emote spacing work. + // Instead, add blank emotes before and after this emote, for easy spacing no matter what widget parts.splice(i + 1, 0, BLANK_EMOTE_PART) parts.splice(i, 0, BLANK_EMOTE_PART) i += 1 break case 'z!': // Zero Space - // Simply remove the space-only text part before this emote + // Simply remove the whitespace-only text part before this emote if ( i - 1 < parts.length && parts[i - 1].type === 'text' && - parts[i - 1].text === ' ' + parts[i - 1].text.trim() === '' ) { parts.splice(i - 1, 1) } From e35c54ee861bfb627c33ec3e2ca91dec3359a48e Mon Sep 17 00:00:00 2001 From: Suyooo Date: Wed, 10 Apr 2024 16:33:44 +0200 Subject: [PATCH 5/5] Avoid using undefined property in BTTV emotes - `modifier` is only set in global emotes list --- src/@types/betterttv.d.ts | 5 ++- src/services/emotes/BetterTTV.ts | 71 ++++++++++++++++---------------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/src/@types/betterttv.d.ts b/src/@types/betterttv.d.ts index 46c6790..a02fab3 100644 --- a/src/@types/betterttv.d.ts +++ b/src/@types/betterttv.d.ts @@ -12,13 +12,16 @@ namespace BetterTTV { code: string imageType: string animated: boolean - modifier: boolean } type ChannelEmote = Emote & { userId: string } + type GlobalEmote = ChannelEmote & { + modifier: boolean + } + type SharedEmote = Emote & { user: { id: string diff --git a/src/services/emotes/BetterTTV.ts b/src/services/emotes/BetterTTV.ts index 855d2f6..a4d2ffb 100644 --- a/src/services/emotes/BetterTTV.ts +++ b/src/services/emotes/BetterTTV.ts @@ -10,71 +10,71 @@ export default async function getChannelEmotes( ): Promise { const emoteMap = new Map() - setEmotes([ + setEmotes([ { - id: "6468f7acaee1f7f47567708e", - code: "c!", - imageType: "png", + id: '6468f7acaee1f7f47567708e', + code: 'c!', + imageType: 'png', animated: false, - userId: "5561169bd6b9d206222a8c19", + userId: '5561169bd6b9d206222a8c19', modifier: true, }, { - id: "6468f845aee1f7f47567709b", - code: "h!", - imageType: "png", + id: '6468f845aee1f7f47567709b', + code: 'h!', + imageType: 'png', animated: false, - userId: "5561169bd6b9d206222a8c19", + userId: '5561169bd6b9d206222a8c19', modifier: true, }, { - id: "6468f869aee1f7f4756770a8", - code: "l!", - imageType: "png", + id: '6468f869aee1f7f4756770a8', + code: 'l!', + imageType: 'png', animated: false, - userId: "5561169bd6b9d206222a8c19", + userId: '5561169bd6b9d206222a8c19', modifier: true, }, { - id: "6468f883aee1f7f4756770b5", - code: "r!", - imageType: "png", + id: '6468f883aee1f7f4756770b5', + code: 'r!', + imageType: 'png', animated: false, - userId: "5561169bd6b9d206222a8c19", + userId: '5561169bd6b9d206222a8c19', modifier: true, }, { - id: "6468f89caee1f7f4756770c2", - code: "v!", - imageType: "png", + id: '6468f89caee1f7f4756770c2', + code: 'v!', + imageType: 'png', animated: false, - userId: "5561169bd6b9d206222a8c19", + userId: '5561169bd6b9d206222a8c19', modifier: true, }, { - id: "6468f8d1aee1f7f4756770cf", - code: "z!", - imageType: "png", + id: '6468f8d1aee1f7f4756770cf', + code: 'z!', + imageType: 'png', animated: false, - userId: "5561169bd6b9d206222a8c19", + userId: '5561169bd6b9d206222a8c19', modifier: true, }, { - id: "64e3b31920cb0d25d950a9f9", - code: "w!", - imageType: "png", + id: '64e3b31920cb0d25d950a9f9', + code: 'w!', + imageType: 'png', animated: false, - userId: "5561169bd6b9d206222a8c19", + userId: '5561169bd6b9d206222a8c19', modifier: true, }, { - id: "65cbe7dbaed093b2eaf87c65", - code: "p!", - imageType: "png", + id: '65cbe7dbaed093b2eaf87c65', + code: 'p!', + imageType: 'png', animated: false, - userId: "5561169bd6b9d206222a8c19", + userId: '5561169bd6b9d206222a8c19', modifier: true, - } + }, ]) const user = await bttvApi @@ -98,7 +98,8 @@ export default async function getChannelEmotes( static: buildEmoteUrls(emote.id, true), }, source: 'betterttv', - isModifier: emote.modifier, + isModifier: + 'modifier' in emote ? (emote).modifier : false, }) }) }