diff --git a/.common/utils/helpers.js b/.common/utils/helpers.js index 8dde251..298a2bf 100644 --- a/.common/utils/helpers.js +++ b/.common/utils/helpers.js @@ -222,21 +222,42 @@ function EscapeRegExp(string) { } // Given a string, return a random hex code. The same input always results in the same output -function StringToHex(str) { - // Simple hash function to convert string to a number +function RandomHex(str) { + // Simple hash to convert string to a number let hash = 0; for (let i = 0; i < str.length; i++) { hash = str.charCodeAt(i) + ((hash << 5) - hash); - hash |= 0; // Convert to 32bit integer + hash |= 0; } - // Convert hash to a hex color - let color = '#'; - for (let i = 0; i < 3; i++) { - // Extract each byte and convert to 2-digit hex - const value = (hash >> (i * 8)) & 0xFF; - color += value.toString(16).padStart(2, '0'); + // Use the hash to generate a hue (0–360) + const hue = Math.abs(hash) % 360; + + // Force super saturation + readable brightness + const saturation = 100; // maximum saturation + const lightness = 55; // tweak 50–60 depending on your background + + // Convert HSL → RGB → hex + function hslToHex(h, s, l) { + s /= 100; + l /= 100; + + const k = n => (n + h / 30) % 12; + const a = s * Math.min(l, 1 - l); + const f = n => + l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1))); + + const r = Math.round(255 * f(0)); + const g = Math.round(255 * f(8)); + const b = Math.round(255 * f(4)); + + return ( + "#" + + r.toString(16).padStart(2, "0") + + g.toString(16).padStart(2, "0") + + b.toString(16).padStart(2, "0") + ); } - return color; + return hslToHex(hue, saturation, lightness); } \ No newline at end of file diff --git a/horizontal-chat/script.js b/horizontal-chat/script.js index 43358f7..709a42b 100644 --- a/horizontal-chat/script.js +++ b/horizontal-chat/script.js @@ -46,6 +46,7 @@ const showTwitchFollows = GetBooleanParam("showTwitchFollows", false); const showTwitchSubs = GetBooleanParam("showTwitchSubs", true); const showTwitchChannelPointRedemptions = GetBooleanParam("showTwitchChannelPointRedemptions", true); const showTwitchRaids = GetBooleanParam("showTwitchRaids", true); +const showTwitchWatchStreaks = GetBooleanParam("showTwitchWatchStreaks", true); const showTwitchSharedChat = GetBooleanParam("showTwitchSharedChat", true); const kickUsername = urlParams.get("kickUsername") || ""; @@ -181,6 +182,11 @@ client.on('Twitch.Raid', (response) => { TwitchRaid(response.data); }) +client.on('Twitch.WatchStreak', (response) => { + console.debug(response.data); + TwitchWatchStreak(response.data); +}) + client.on('Twitch.ChatMessageDeleted', (response) => { console.debug(response.data); TwitchChatMessageDeleted(response.data); @@ -789,6 +795,18 @@ async function TwitchRaid(data) { ShowAlert(message, 'twitch'); } +async function TwitchWatchStreak(data) { + if (!showTwitchWatchStreaks) + return; + + const displayName = data.displayName; + const watchStreak = data.watchStreak; + + let message = `${displayName} is currently on a ${watchStreak} stream streak!`; + + ShowAlert(message, 'twitch'); +} + function TwitchChatMessageDeleted(data) { const messageList = document.getElementById("messageList"); @@ -878,7 +896,7 @@ function YouTubeMessage(data) { if (showUsername) { usernameDiv.innerText = data.user.name; if (randomYouTubeColors) - usernameDiv.style.color = StringToHex(data.user.name); + usernameDiv.style.color = RandomHex(data.user.name); else usernameDiv.style.color = youtubeColor; // YouTube users do not have colors, so just set it to red } diff --git a/horizontal-chat/settings/settings.json b/horizontal-chat/settings/settings.json index 0222140..b1eed16 100644 --- a/horizontal-chat/settings/settings.json +++ b/horizontal-chat/settings/settings.json @@ -175,6 +175,14 @@ "defaultValue": true, "group": "Which Twitch messages do you want to see?" }, + { + "id": "showTwitchWatchStreaks", + "label": "Watch Streaks", + "description": "", + "type": "checkbox", + "defaultValue": true, + "group": "Which Twitch messages do you want to see?" + }, { "id": "showTwitchSharedChat", "label": "Shared Chat Messages", diff --git a/multichat-overlay/script.js b/multichat-overlay/script.js index abe2055..dc1a20d 100644 --- a/multichat-overlay/script.js +++ b/multichat-overlay/script.js @@ -51,6 +51,7 @@ const showTwitchFollows = GetBooleanParam("showTwitchFollows", false); const showTwitchSubs = GetBooleanParam("showTwitchSubs", true); const showTwitchChannelPointRedemptions = GetBooleanParam("showTwitchChannelPointRedemptions", true); const showTwitchRaids = GetBooleanParam("showTwitchRaids", true); +const showTwitchWatchStreaks = GetBooleanParam("showTwitchWatchStreaks", true); const showTwitchSharedChat = GetIntParam("showTwitchSharedChat", 2); const kickUsername = urlParams.get("kickUsername") || ""; @@ -200,6 +201,11 @@ client.on('Twitch.Raid', (response) => { TwitchRaid(response.data); }) +client.on('Twitch.WatchStreak', (response) => { + console.debug(response.data); + TwitchWatchStreak(response.data); +}) + client.on('Twitch.ChatMessageDeleted', (response) => { console.debug(response.data); TwitchChatMessageDeleted(response.data); @@ -1160,6 +1166,67 @@ async function TwitchRaid(data) { AddMessageItem(instance, data.messageId); } +async function TwitchWatchStreak(data) { + if (!showTwitchWatchStreaks) + return; + + // Get a reference to the template + const template = document.getElementById('cardTemplate'); + + // Create a new instance of the template + const instance = template.content.cloneNode(true); + + // Get divs + const cardDiv = instance.querySelector("#card"); + const headerDiv = instance.querySelector("#header"); + const avatarDiv = instance.querySelector("#avatar"); + const iconDiv = instance.querySelector("#icon"); + const titleDiv = instance.querySelector("#title"); + const contentDiv = instance.querySelector("#content"); + + // Set the card background colors + cardDiv.classList.add('twitch'); + + if (showAvatar) { + // Render avatars + const username = data.userName; + const avatarURL = await GetAvatar(username, 'twitch'); + const avatar = new Image(); + avatar.src = avatarURL; + avatar.classList.add("avatar"); + avatarDiv.appendChild(avatar); + } + + const displayName = data.displayName; + const watchStreak = data.watchStreak; + const message = data.message; + + titleDiv.innerText = `${displayName} is currently on a ${watchStreak} stream streak! `; + contentDiv.innerText = `${message}`; + + // Render emotes + for (i in data.emotes) { + const emoteElement = ``; + const emoteName = EscapeRegExp(data.emotes[i].name); + + let regexPattern = emoteName; + + // Check if the emote name consists only of word characters (alphanumeric and underscore) + if (/^\w+$/.test(emoteName)) { + regexPattern = `\\b${emoteName}\\b`; + } + else { + // For non-word emotes, ensure they are surrounded by non-word characters or boundaries + regexPattern = `(?<=^|[^\\w])${emoteName}(?=$|[^\\w])`; + } + + const regex = new RegExp(regexPattern, 'g'); + contentDiv.innerHTML = contentDiv.innerHTML.replace(regex, emoteElement); + } + + AddMessageItem(instance, data.messageId); +} + function TwitchChatMessageDeleted(data) { const messageList = document.getElementById("messageList"); @@ -1265,7 +1332,7 @@ async function YouTubeMessage(data) { if (showUsername) { usernameDiv.innerText = data.user.name; if (randomYouTubeColors) - usernameDiv.style.color = StringToHex(data.user.name); + usernameDiv.style.color = RandomHex(data.user.name); else usernameDiv.style.color = youtubeColor; // YouTube users do not have colors, so just set it to red } diff --git a/multichat-overlay/settings/settings.json b/multichat-overlay/settings/settings.json index dbf67a4..65ecd5a 100644 --- a/multichat-overlay/settings/settings.json +++ b/multichat-overlay/settings/settings.json @@ -282,6 +282,14 @@ "defaultValue": true, "group": "Which Twitch messages do you want to see?" }, + { + "id": "showTwitchWatchStreaks", + "label": "Watch Streaks", + "description": "", + "type": "checkbox", + "defaultValue": true, + "group": "Which Twitch messages do you want to see?" + }, { "id": "showTwitchSharedChat", "label": "Shared Chat Messages", diff --git a/multistream-alerts/script.js b/multistream-alerts/script.js index 3abfb05..e628056 100644 --- a/multistream-alerts/script.js +++ b/multistream-alerts/script.js @@ -67,6 +67,8 @@ const showTwitchCheers = GetBooleanParam("showTwitchCheers", true); const twitchCheerAction = urlParams.get("twitchCheerAction") || ""; const showTwitchRaids = GetBooleanParam("showTwitchRaids", true); const twitchRaidAction = urlParams.get("twitchRaidAction") || ""; +const showTwitchWatchStreaks = GetBooleanParam("showTwitchWatchStreaks", true); +const twitchWatchStreaksAction = urlParams.get("twitchWatchStreaksAction") || ""; // Which Kick alerts do you want to see? const kickUsername = urlParams.get("kickUsername") || ""; @@ -211,6 +213,11 @@ client.on('Twitch.Raid', (response) => { TwitchRaid(response.data); }) +client.on('Twitch.WatchStreak', (response) => { + console.debug(response.data); + TwitchWatchStreak(response.data); +}) + client.on('YouTube.SuperChat', (response) => { console.debug(response.data); YouTubeSuperChat(response.data); @@ -721,6 +728,30 @@ async function TwitchRaid(data) { ); } +async function TwitchWatchStreak(data) { + if (!showTwitchWatchStreaks) + return; + + // Render avatars + const avatarURL = await GetAvatar(data.userName, 'twitch'); + + // Set the text + const username = data.displayName; + const watchStreak = data.watchStreak; + + UpdateAlertBox( + 'twitch', + avatarURL, + `${username}`, + `is currently on a ${watchStreak} stream streak!`, + '', + username, + '', + twitchWatchStreaksAction, + data + ); +} + function YouTubeSuperChat(data) { if (!showYouTubeSuperChats) return; @@ -836,7 +867,7 @@ async function StreamElementsTip(data) { UpdateAlertBox( 'streamelements', - '' + '', `${donater}`, `donated ${currency}${formattedAmount}`, ``, diff --git a/multistream-alerts/settings/settings.json b/multistream-alerts/settings/settings.json index 89768e0..b59bc74 100644 --- a/multistream-alerts/settings/settings.json +++ b/multistream-alerts/settings/settings.json @@ -312,6 +312,23 @@ "showIf": "showTwitchRaids", "group": "Which Twitch alerts do you want to see?" }, + { + "id": "showTwitchWatchStreaks", + "label": "Watch Streaks", + "description": "", + "type": "checkbox", + "defaultValue": true, + "group": "Which Twitch alerts do you want to see?" + }, + { + "id": "twitchWatchStreaksAction", + "label": "", + "description": "Also run this Streamer.bot Action", + "type": "sb-actions", + "defaultValue": "", + "showIf": "showTwitchWatchStreaks", + "group": "Which Twitch alerts do you want to see?" + }, { "id": "showYouTubeSuperChats", "label": "Super Chats",