Merge pull request #29 from nuttylmao/beta

Beta
This commit is contained in:
nuttylmao
2026-05-21 04:14:23 +10:00
committed by GitHub
16 changed files with 629 additions and 73 deletions
+18
View File
@@ -70,6 +70,12 @@ function LoadJSON(settingsJson) {
settingItem.classList.add('setting-item'); settingItem.classList.add('setting-item');
settingItem.id = `item-${setting.id}`; settingItem.id = `item-${setting.id}`;
// Indent settings that are dependent on other settings using the showIf property
if (setting.showIf) {
const depth = GetSettingDepth(setting, data.settings);
settingItem.style.marginLeft = `${depth * 20}px`;
}
const labelDescriptionDiv = document.createElement('div'); const labelDescriptionDiv = document.createElement('div');
if (setting.label) { if (setting.label) {
@@ -469,6 +475,18 @@ window.addEventListener('message', (event) => {
} }
}); });
function GetSettingDepth(setting, allSettings) {
let depth = 0;
let current = setting;
while (current.showIf) {
depth++;
current = allSettings.find(s => s.id === current.showIf) || {};
}
return depth;
}
+11
View File
@@ -53,6 +53,7 @@ body {
height: 100%; height: 100%;
padding: 0px 20px; padding: 0px 20px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 1); box-shadow: 0px 0px 10px rgba(0, 0, 0, 1);
z-index: 2;
} }
#settingsPanel::-webkit-scrollbar { #settingsPanel::-webkit-scrollbar {
@@ -82,6 +83,16 @@ body {
flex-grow: 1; flex-grow: 1;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
/* Dark Checkerboard Pattern */
background-color: #181818; /* The "base" dark color */
background-image: conic-gradient(
#252525 90deg,
#181818 90deg 180deg,
#252525 180deg 270deg,
#181818 270deg
);
background-size: 30px 30px; /* Size of the squares */
} }
#widgetPreview { #widgetPreview {
+107 -2
View File
@@ -28,7 +28,7 @@ body {
padding: 0; padding: 0;
font-family: Inter, system-ui, sans-serif; font-family: Inter, system-ui, sans-serif;
/* font-family: "Segoe UI Emoji", "Apple Color Emoji", "Noto Color Emoji", sans-serif; */ /* font-family: "Segoe UI Emoji", "Apple Color Emoji", "Noto Color Emoji", sans-serif; */
/* font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif; */ /* font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif; */
color: white; color: white;
background-color: var(--background-color); background-color: var(--background-color);
} }
@@ -311,6 +311,17 @@ iframe {
/*** DIALOG ***/ /*** DIALOG ***/
/**************/ /**************/
.dialog-wrapper {
position: fixed;
width: 100%;
height: 100%;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 1000;
backdrop-filter: blur(var(--blur-intensity));
}
.dialog { .dialog {
font-size: 1em; font-size: 1em;
background-color: var(--dialog-background); background-color: var(--dialog-background);
@@ -327,7 +338,7 @@ iframe {
border: 1px solid #40404080; border: 1px solid #40404080;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
z-index: 1000; z-index: 1000;
/* above overlay */ backdrop-filter: blur(var(--blur-intensity));
} }
.dialog-nav-button { .dialog-nav-button {
@@ -384,4 +395,98 @@ select {
color: #fff; color: #fff;
cursor: pointer; cursor: pointer;
min-width: 8em; min-width: 8em;
}
/***************/
/*** SIDEBAR ***/
/***************/
#sidebar {
position: fixed;
top: 0;
left: 0;
width: 250px;
/* expanded width */
height: 100vh;
color: #fff;
overflow: hidden;
transition: width 0.3s ease;
backdrop-filter: blur(var(--blur-intensity));
/* <-- adds the blur */
box-shadow: 0 0 10px rgba(0, 0, 0, 1);
}
#sidebar.collapsed {
width: 3em;
/* collapsed width */
}
#sidebar button {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 0.5em;
padding: 0.5em 1em;
border-radius: 0;
}
#sidebar .sidebar-menu {
opacity: 1;
transition: opacity 0.3s ease;
}
#sidebar.collapsed .sidebar-menu {
opacity: 1;
}
#collapse-sidebar-btn {
width: 100%;
background: none;
border: none;
color: #fff;
cursor: pointer;
justify-content: flex-end !important;
}
.sidebar-item {
padding: 0.75em 1em;
text-align: left;
background: none;
border: none;
color: inherit;
font-size: 1em;
cursor: pointer;
white-space: nowrap;
/* prevent line breaks */
overflow: hidden;
/* hide overflow text */
text-overflow: ellipsis;
/* optional: show "..." if needed */
}
#sidebar.collapsed .sidebar-item {
justify-content: center;
padding: 0.75em 0;
}
.sidebar-label {
display: inline-block;
}
#sidebar.collapsed .sidebar-label {
display: none;
}
.sidebar-item svg {
width: 1em;
height: 1em;
flex-shrink: 0;
}
.sidebar-spacer {
flex: 1;
}
.sidebar-item:hover {
background-color: #333;
} }
@@ -0,0 +1,95 @@
:root {
--winning-color: #3be47680;
--vote-bar-color: #FFFFFF;
--vote-bar-track-color: #FFFFFF33;
}
.response-container {
display: flex;
align-items: center;
gap: 0.75em;
width: 100%;
transition: all 0.8s ease;
}
.response-index {
width: 2.5em;
height: 2.5em;
min-width: 2.5em;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.15);
display: flex;
align-items: center;
justify-content: center;
font-weight: 900;
font-size: 0.8em;
transition: all 0.8s ease-out;
}
.response-except-just-the-shit-on-the-right-side {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.25em;
min-width: 0;
}
.response-title,
.response-votes {
position: relative;
z-index: 1;
display: flex;
align-items: center;
line-height: 1.2em;
/* mix-blend-mode: exclusion; */ /* I couldn't make this shit look good so I'm turning it off for now */
}
.response-title {
flex: 1 1 auto;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.response-votes {
flex: 0 0 auto;
white-space: nowrap;
margin-left: 8px;
font-size: 0.8em;
}
.response-bar-track {
background-color: var(--vote-bar-track-color);
position: relative;
display: flex;
border-radius: 0.5em;
overflow: hidden;
padding: 0.5em 0.5em;
}
.response-bar-fill {
position: absolute; /* take it out of flex flow */
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: var(--vote-bar-color);
transition: all 0.25s ease;
z-index: 0; /* sits behind text */
inset: 0;
display: flex; /* 👈 KEY CHANGE */
overflow: hidden;
}
.trophy-icon {
width: 1.8em;
height: 1.8em;
vertical-align: middle;
}
.response-bar-segment {
height: 100%;
flex-shrink: 0;
transition: all 0.25s ease;
}
@@ -0,0 +1,16 @@
<template id="poll-response-template">
<div class="response-container" data-theme="dark-text">
<!-- Number circle -->
<div class="response-index">1</div>
<!-- Content -->
<div class="response-except-just-the-shit-on-the-right-side">
<!-- Response bar -->
<div class="response-bar-track">
<div class="response-title"></div>
<div class="response-votes"><span class="response-vote-percentage">0</span>% (<span class="response-vote-count"></span>)</div>
<div class="response-bar-fill" id="response-bar-total"></div>
</div>
</div>
</div>
</template>
+48 -3
View File
@@ -118,25 +118,59 @@ async function GetAvatar(username, platform) {
console.debug(`No avatar found for ${username} (${platform}). Retrieving from Kick.`); console.debug(`No avatar found for ${username} (${platform}). Retrieving from Kick.`);
let url = `https://kick.com/api/v2/channels/${username}`; let url = `https://kick.com/api/v2/channels/${username}`;
try { try {
let response = await fetch(url); let response = await fetch(url);
if (!response.ok) { if (!response.ok) {
// Retry with underscores replaced by dashes
const altUsername = username.replace(/_/g, "-"); const altUsername = username.replace(/_/g, "-");
url = `https://kick.com/api/v2/channels/${altUsername}`; url = `https://kick.com/api/v2/channels/${altUsername}`;
response = await fetch(url); response = await fetch(url);
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error ${response.status}`); throw new Error(`HTTP error ${response.status}`);
} }
} }
let data = await response.json(); let data = await response.json();
let avatarURL = data.user?.profile_pic || 'https://kick.com/img/default-profile-pictures/default2.jpeg';
const defaultAvatars = [
'https://kick.com/img/default-profile-pictures/default-avatar-1.webp',
'https://kick.com/img/default-profile-pictures/default-avatar-2.webp',
'https://kick.com/img/default-profile-pictures/default-avatar-3.webp',
'https://kick.com/img/default-profile-pictures/default-avatar-4.webp',
'https://kick.com/img/default-profile-pictures/default-avatar-5.webp',
'https://kick.com/img/default-profile-pictures/default-avatar-6.webp',
];
const avatarURL =
data.user?.profile_pic ||
defaultAvatars[hashString(`${username}-${platform}`) % defaultAvatars.length];
avatarMap.set(`${username}-${platform}`, avatarURL); avatarMap.set(`${username}-${platform}`, avatarURL);
return avatarURL; return avatarURL;
} catch (error) { } catch (error) {
console.error("Failed to fetch avatar:", error.message); console.error("Failed to fetch avatar:", error.message);
return 'https://kick.com/img/default-profile-pictures/default2.jpeg';
const defaultAvatars = [
'https://kick.com/img/default-profile-pictures/default-avatar-1.webp',
'https://kick.com/img/default-profile-pictures/default-avatar-2.webp',
'https://kick.com/img/default-profile-pictures/default-avatar-3.webp',
'https://kick.com/img/default-profile-pictures/default-avatar-4.webp',
'https://kick.com/img/default-profile-pictures/default-avatar-5.webp',
'https://kick.com/img/default-profile-pictures/default-avatar-6.webp',
];
return defaultAvatars[hashString(`${username}-${platform}`) % defaultAvatars.length];
}
function hashString(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash |= 0; // convert to 32-bit int
}
return Math.abs(hash);
} }
} }
} }
@@ -301,6 +335,15 @@ function ConstructMessageFromParts(parts) {
case "text": case "text":
return EscapeHTML(part.text); return EscapeHTML(part.text);
case "cheer": case "cheer":
// TODO: It seems like Streamer.bot v1.0.5-alpha3 doesn't include the imageUrl for bits in the message parts, only the bits count. We may need to hardcode the image URL format for Twitch bits, or find another way to retrieve it.
let imageUrl = '';
if (!part.imageUrl) {
const tiers = [100000, 10000, 5000, 1000, 100, 10, 1];
const activeTier = tiers.find(tier => part.bits >= tier) || 1;
part.imageUrl = `https://d3aqoihi2n8ty8.cloudfront.net/actions/cheer/dark/animated/${activeTier}/4.gif`;
}
// Render the cheer emote image // Render the cheer emote image
const emoteImg = `<img src="${EscapeHTML(part.imageUrl)}" alt="${EscapeHTML(part.text)}" title="${EscapeHTML(part.text)}" class="emote">`; const emoteImg = `<img src="${EscapeHTML(part.imageUrl)}" alt="${EscapeHTML(part.text)}" title="${EscapeHTML(part.text)}" class="emote">`;
@@ -308,6 +351,8 @@ function ConstructMessageFromParts(parts) {
const bitLabel = `<span class="bits">${EscapeHTML(part.bits.toString())}</span>`; const bitLabel = `<span class="bits">${EscapeHTML(part.bits.toString())}</span>`;
return emoteImg + bitLabel; return emoteImg + bitLabel;
case "mention":
return part.text;
default: default:
return `<img src="${EscapeHTML(part.imageUrl)}" alt="${EscapeHTML(part.text)}" title="${EscapeHTML(part.text)}" class="emote">`; return `<img src="${EscapeHTML(part.imageUrl)}" alt="${EscapeHTML(part.text)}" title="${EscapeHTML(part.text)}" class="emote">`;
} }
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 2664 2665" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1.03455,0,0,1.03455,-46.020186,0)">
<path d="M1332,0L2444,1578L1332,2576L220,1578L1332,0ZM1332,1778L1922,1519L1332,622L742,1519L1332,1778Z" style="fill:white;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 658 B

+54 -22
View File
@@ -46,6 +46,7 @@ const showTwitchAnnouncements = GetBooleanParam("showTwitchAnnouncements", true)
const showTwitchFollows = GetBooleanParam("showTwitchFollows", false); const showTwitchFollows = GetBooleanParam("showTwitchFollows", false);
const showTwitchSubs = GetBooleanParam("showTwitchSubs", true); const showTwitchSubs = GetBooleanParam("showTwitchSubs", true);
const showTwitchChannelPointRedemptions = GetBooleanParam("showTwitchChannelPointRedemptions", true); const showTwitchChannelPointRedemptions = GetBooleanParam("showTwitchChannelPointRedemptions", true);
const showTwitchPowerUpRedemptions = GetBooleanParam("showTwitchPowerUpRedemptions", true);
const showTwitchRaids = GetBooleanParam("showTwitchRaids", true); const showTwitchRaids = GetBooleanParam("showTwitchRaids", true);
const showTwitchWatchStreaks = GetBooleanParam("showTwitchWatchStreaks", true); const showTwitchWatchStreaks = GetBooleanParam("showTwitchWatchStreaks", true);
const showTwitchSharedChat = GetBooleanParam("showTwitchSharedChat", true); const showTwitchSharedChat = GetBooleanParam("showTwitchSharedChat", true);
@@ -85,7 +86,9 @@ const animationSpeed = GetIntParam("animationSpeed", 0.5);
const randomYouTubeColors = GetBooleanParam("randomYouTubeColors", false); const randomYouTubeColors = GetBooleanParam("randomYouTubeColors", false);
const youtubeColor = urlParams.get("youtubeColor") || "#f70000"; const youtubeColor = urlParams.get("youtubeColor") || "#f70000";
const youtubeCustomSubIcon = urlParams.get("youtubeCustomSubIcon") || ""; const youtubeCustomSubIcon = urlParams.get("youtubeCustomSubIcon") || "";
const kickUsername = urlParams.get("kickUsername") || ""; let twitchUsername = urlParams.get("twitchUsername") || "";
let kickUsername = urlParams.get("kickUsername") || "";
let youtubeUsername = urlParams.get("youtubeUsername") || "";
@@ -179,6 +182,11 @@ client.on('Twitch.RewardRedemption', (response) => {
TwitchRewardRedemption(response.data); TwitchRewardRedemption(response.data);
}) })
client.on('Twitch.CustomPowerUpRedemption', (response) => {
console.debug(response.data);
TwitchCustomPowerUpRedemption(response.data);
})
client.on('Twitch.Raid', (response) => { client.on('Twitch.Raid', (response) => {
console.debug(response.data); console.debug(response.data);
TwitchRaid(response.data); TwitchRaid(response.data);
@@ -343,12 +351,18 @@ client.on('Fourthwall.GiftDrawEnded', (response) => {
// Connect and handle Pusher WebSocket // Connect and handle Pusher WebSocket
async function KickConnect() { async function KickConnect() {
// Fetch from Streamer.bot
const broadcasterInfo = await client.getBroadcaster();
// This code has nothing to do with Kick, but it's a really good place to get Twitch and YouTube usernames as well, because I'm built different like that
if (broadcasterInfo.platforms.twitch)
twitchUsername = broadcasterInfo.platforms.twitch.broadcastUserName;
if (broadcasterInfo.platforms.youtube)
youtubeUsername = broadcasterInfo.platforms.youtube.broadcastUserName;
// If user has not manually set Kick username, try to grab if from Streamer.bot // If user has not manually set Kick username, try to grab if from Streamer.bot
if (!kickUsername) if (!kickUsername)
{ {
// Fetch from Streamer.bot
const broadcasterInfo = await client.getBroadcaster();
if (broadcasterInfo.platforms.kick) if (broadcasterInfo.platforms.kick)
kickUsername = broadcasterInfo.platforms.kick.broadcasterLogin; kickUsername = broadcasterInfo.platforms.kick.broadcasterLogin;
else else
@@ -504,11 +518,11 @@ async function TwitchChatMessage(data) {
return; return;
// Don't post messages starting with "!" // Don't post messages starting with "!"
if (data.message.message.startsWith("!") && excludeCommands) if (data.text.startsWith("!") && excludeCommands)
return; return;
// Don't post messages from users from the ignore list // Don't post messages from users from the ignore list
if (ignoreUserList.includes(data.message.username.toLowerCase())) if (ignoreUserList.includes(data.user.login.toLowerCase()))
return; return;
// Get a reference to the template // Get a reference to the template
@@ -544,15 +558,15 @@ async function TwitchChatMessage(data) {
// Set the username info // Set the username info
if (showUsername) { if (showUsername) {
if (data.message.displayName.toLowerCase() == data.message.username.toLowerCase()) if (data.user.name.toLowerCase() == data.user.login.toLowerCase())
usernameDiv.innerText = data.message.displayName; usernameDiv.innerText = data.user.name;
else else
usernameDiv.innerText = `${data.message.displayName} (${data.message.username})`; usernameDiv.innerText = `${data.user.name} (${data.user.login})`;
usernameDiv.style.color = data.message.color; usernameDiv.style.color = data.user.color;
} }
// Set pronouns // Set pronouns
const pronouns = await GetPronouns('twitch', data.message.username); const pronouns = await GetPronouns('twitch', data.user.login);
if (pronouns && showPronouns) { if (pronouns && showPronouns) {
pronounsDiv.classList.add("pronouns"); pronounsDiv.classList.add("pronouns");
pronounsDiv.innerText = pronouns; pronounsDiv.innerText = pronouns;
@@ -560,8 +574,8 @@ async function TwitchChatMessage(data) {
// Set the message data // Set the message data
let message = ConstructMessageFromParts(data.parts); let message = ConstructMessageFromParts(data.parts);
const messageColor = data.message.color; const messageColor = data.user.color;
const role = data.message.role; const role = data.user.role;
// Set furry mode // Set furry mode
if (furryMode) if (furryMode)
@@ -573,7 +587,7 @@ async function TwitchChatMessage(data) {
} }
// Set the "action" color // Set the "action" color
if (data.message.isMe) if (data.meta.isMe)
messageDiv.style.color = messageColor; messageDiv.style.color = messageColor;
// Render platform // Render platform
@@ -585,9 +599,9 @@ async function TwitchChatMessage(data) {
// Render badges // Render badges
if (showBadges) { if (showBadges) {
badgeListDiv.innerHTML = ""; badgeListDiv.innerHTML = "";
for (i in data.message.badges) { for (i in data.user.badges) {
const badge = new Image(); const badge = new Image();
badge.src = data.message.badges[i].imageUrl; badge.src = data.user.badges[i].imageUrl;
badge.classList.add("badge"); badge.classList.add("badge");
badgeListDiv.appendChild(badge); badgeListDiv.appendChild(badge);
} }
@@ -595,7 +609,7 @@ async function TwitchChatMessage(data) {
// Render avatars // Render avatars
if (showAvatar) { if (showAvatar) {
const username = data.message.username; const username = data.user.login;
const avatarURL = await GetAvatar(username, 'twitch'); const avatarURL = await GetAvatar(username, 'twitch');
const avatar = new Image(); const avatar = new Image();
avatar.src = avatarURL; avatar.src = avatarURL;
@@ -604,7 +618,7 @@ async function TwitchChatMessage(data) {
} }
// Custom styling for subs // Custom styling for subs
if (data.message.subscriber) { if (data.user.subscribed) {
usernameDiv.classList.add('sub-glow') usernameDiv.classList.add('sub-glow')
} }
@@ -623,7 +637,7 @@ async function TwitchChatMessage(data) {
userInfoDiv.style.display = "none"; userInfoDiv.style.display = "none";
} }
AddMessageItem(instance, data.message.msgId, 'twitch', data.user.id); AddMessageItem(instance, data.messageId, 'twitch', data.user.id);
} }
async function TwitchCheer(data) { async function TwitchCheer(data) {
@@ -780,6 +794,23 @@ async function TwitchRewardRedemption(data) {
ShowAlert(message, 'twitch'); ShowAlert(message, 'twitch');
} }
async function TwitchCustomPowerUpRedemption(data) {
if (!showTwitchPowerUpRedemptions)
return;
let username = data.user.name;
if (data.user.name.toLowerCase() != data.user.login.toLowerCase())
username = `${data.user.name} (${data.user.login})`;
const powerUpName = data.customPowerUp.title;
const cost = data.customPowerUp.bits;
const userInput = data.userInput;
const bitIcon = `<img src="icons/badges/twitch-bit.svg" class="platform"/>`;
let message = `${username} redeemed ${powerUpName} ${bitIcon} ${cost}`;
ShowAlert(message, 'twitch');
}
async function TwitchRaid(data) { async function TwitchRaid(data) {
if (!showTwitchRaids) if (!showTwitchRaids)
return; return;
@@ -798,8 +829,9 @@ async function TwitchWatchStreak(data) {
if (!showTwitchWatchStreaks) if (!showTwitchWatchStreaks)
return; return;
const displayName = data.displayName; // TODO: Streamer.bot v1.0.5-alpha3 changed the data sent with this event, so for backwards compatibility we need to check for both the old and new properties
const watchStreak = data.watchStreak; const displayName = data.displayName ?? data.user.name;
const watchStreak = data.watchStreak ?? data.streak_count;
let message = `${displayName} is currently on a ${watchStreak} stream streak!`; let message = `${displayName} is currently on a ${watchStreak} stream streak!`;
+8
View File
@@ -175,6 +175,14 @@
"defaultValue": true, "defaultValue": true,
"group": "Which Twitch messages do you want to see?" "group": "Which Twitch messages do you want to see?"
}, },
{
"id": "showTwitchPowerUpRedemptions",
"label": "Power-Up Redemptions",
"description": "",
"type": "checkbox",
"defaultValue": true,
"group": "Which Twitch messages do you want to see?"
},
{ {
"id": "showTwitchRaids", "id": "showTwitchRaids",
"label": "Raids", "label": "Raids",
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 2664 2665" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1.03455,0,0,1.03455,-46.020186,0)">
<path d="M1332,0L2444,1578L1332,2576L220,1578L1332,0ZM1332,1778L1922,1519L1332,622L742,1519L1332,1778Z" style="fill:white;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 658 B

+116 -36
View File
@@ -42,6 +42,7 @@ const ignoreChatters = urlParams.get("ignoreChatters") || "";
const scrollDirection = GetIntParam("scrollDirection", 1); const scrollDirection = GetIntParam("scrollDirection", 1);
const groupConsecutiveMessages = GetBooleanParam("groupConsecutiveMessages", false); const groupConsecutiveMessages = GetBooleanParam("groupConsecutiveMessages", false);
const inlineChat = GetBooleanParam("inlineChat", false); const inlineChat = GetBooleanParam("inlineChat", false);
const highlightMentions = GetBooleanParam("highlightMentions", true);
const imageEmbedPermissionLevel = GetIntParam("imageEmbedPermissionLevel", 20); const imageEmbedPermissionLevel = GetIntParam("imageEmbedPermissionLevel", 20);
const showYouTubeLinkPreviews = GetBooleanParam("showYouTubeLinkPreviews", true); const showYouTubeLinkPreviews = GetBooleanParam("showYouTubeLinkPreviews", true);
@@ -51,6 +52,7 @@ const showTwitchAnnouncements = GetBooleanParam("showTwitchAnnouncements", true)
const showTwitchFollows = GetBooleanParam("showTwitchFollows", false); const showTwitchFollows = GetBooleanParam("showTwitchFollows", false);
const showTwitchSubs = GetBooleanParam("showTwitchSubs", true); const showTwitchSubs = GetBooleanParam("showTwitchSubs", true);
const showTwitchChannelPointRedemptions = GetBooleanParam("showTwitchChannelPointRedemptions", true); const showTwitchChannelPointRedemptions = GetBooleanParam("showTwitchChannelPointRedemptions", true);
const showTwitchPowerUpRedemptions = GetBooleanParam("showTwitchPowerUpRedemptions", true);
const showTwitchRaids = GetBooleanParam("showTwitchRaids", true); const showTwitchRaids = GetBooleanParam("showTwitchRaids", true);
const showTwitchWatchStreaks = GetBooleanParam("showTwitchWatchStreaks", true); const showTwitchWatchStreaks = GetBooleanParam("showTwitchWatchStreaks", true);
const showTwitchSharedChat = GetIntParam("showTwitchSharedChat", 2); const showTwitchSharedChat = GetIntParam("showTwitchSharedChat", 2);
@@ -91,7 +93,9 @@ const animationSpeed = GetIntParam("animationSpeed", 0.1);
const randomYouTubeColors = GetBooleanParam("randomYouTubeColors", false); const randomYouTubeColors = GetBooleanParam("randomYouTubeColors", false);
const youtubeColor = urlParams.get("youtubeColor") || "#f70000"; const youtubeColor = urlParams.get("youtubeColor") || "#f70000";
const youtubeCustomSubIcon = urlParams.get("youtubeCustomSubIcon") || ""; const youtubeCustomSubIcon = urlParams.get("youtubeCustomSubIcon") || "";
let twitchUsername = urlParams.get("twitchUsername") || "";
let kickUsername = urlParams.get("kickUsername") || ""; let kickUsername = urlParams.get("kickUsername") || "";
let youtubeUsername = urlParams.get("youtubeUsername") || "";
@@ -199,6 +203,11 @@ client.on('Twitch.RewardRedemption', (response) => {
TwitchRewardRedemption(response.data); TwitchRewardRedemption(response.data);
}) })
client.on('Twitch.CustomPowerUpRedemption', (response) => {
console.debug(response.data);
TwitchCustomPowerUpRedemption(response.data);
})
client.on('Twitch.Raid', (response) => { client.on('Twitch.Raid', (response) => {
console.debug(response.data); console.debug(response.data);
TwitchRaid(response.data); TwitchRaid(response.data);
@@ -383,12 +392,20 @@ client.on('Fourthwall.GiftDrawEnded', (response) => {
// Connect and handle Pusher WebSocket // Connect and handle Pusher WebSocket
async function KickConnect() { async function KickConnect() {
// Fetch from Streamer.bot
const broadcasterInfo = await client.getBroadcaster();
console.log('Broadcaster info:', broadcasterInfo);
// This code has nothing to do with Kick, but it's a really good place to get Twitch and YouTube usernames as well, because I'm built different like that
if (broadcasterInfo.platforms.twitch)
twitchUsername = broadcasterInfo.platforms.twitch.broadcastUserName;
if (broadcasterInfo.platforms.youtube)
youtubeUsername = broadcasterInfo.platforms.youtube.broadcastUserName;
// If user has not manually set Kick username, try to grab if from Streamer.bot // If user has not manually set Kick username, try to grab if from Streamer.bot
if (!kickUsername) if (!kickUsername)
{ {
// Fetch from Streamer.bot
const broadcasterInfo = await client.getBroadcaster();
if (broadcasterInfo.platforms.kick) if (broadcasterInfo.platforms.kick)
kickUsername = broadcasterInfo.platforms.kick.broadcasterLogin; kickUsername = broadcasterInfo.platforms.kick.broadcasterLogin;
else else
@@ -551,11 +568,11 @@ async function TwitchChatMessage(data) {
return; return;
// Don't post messages starting with "!" // Don't post messages starting with "!"
if (data.message.message.startsWith("!") && excludeCommands) if (data.text.startsWith("!") && excludeCommands)
return; return;
// Don't post messages from users from the ignore list // Don't post messages from users from the ignore list
if (ignoreUserList.includes(data.message.username.toLowerCase())) if (ignoreUserList.includes(data.user.login.toLowerCase()))
return; return;
// Get a reference to the template // Get a reference to the template
@@ -593,7 +610,7 @@ async function TwitchChatMessage(data) {
} }
// Set First Time Chatter // Set First Time Chatter
const firstMessage = data.message.firstMessage; const firstMessage = data.meta.firstMessage;
if (firstMessage && showMessage) { if (firstMessage && showMessage) {
firstMessageDiv.style.display = 'block'; firstMessageDiv.style.display = 'block';
messageContainerDiv.classList.add("highlightMessage"); messageContainerDiv.classList.add("highlightMessage");
@@ -622,10 +639,10 @@ async function TwitchChatMessage(data) {
} }
// Set Reply Message // Set Reply Message
const isReply = data.message.isReply; const isReply = data.isReply;
if (isReply && showMessage) { if (isReply && showMessage) {
const replyUser = data.message.reply.userName; const replyUser = data.reply.userName;
const replyMsg = data.message.reply.msgBody; const replyMsg = data.reply.msgBody;
replyDiv.style.display = 'block'; replyDiv.style.display = 'block';
replyUserDiv.innerText = replyUser; replyUserDiv.innerText = replyUser;
@@ -640,15 +657,15 @@ async function TwitchChatMessage(data) {
// Set the username info // Set the username info
if (showUsername) { if (showUsername) {
if (data.message.displayName.toLowerCase() == data.message.username.toLowerCase()) if (data.user.name.toLowerCase() == data.user.login.toLowerCase())
usernameDiv.innerText = data.message.displayName; usernameDiv.innerText = data.user.name;
else else
usernameDiv.innerText = `${data.message.displayName} (${data.message.username})`; usernameDiv.innerText = `${data.user.name} (${data.user.login})`;
usernameDiv.style.color = data.message.color; usernameDiv.style.color = data.user.color;
} }
// Set pronouns // Set pronouns
const pronouns = await GetPronouns('twitch', data.message.username); const pronouns = await GetPronouns('twitch', data.user.login);
if (pronouns && showPronouns) { if (pronouns && showPronouns) {
pronounsDiv.classList.add("pronouns"); pronounsDiv.classList.add("pronouns");
pronounsDiv.innerText = pronouns; pronounsDiv.innerText = pronouns;
@@ -656,8 +673,14 @@ async function TwitchChatMessage(data) {
// Set the message data // Set the message data
let message = ConstructMessageFromParts(data.parts); let message = ConstructMessageFromParts(data.parts);
const messageColor = data.message.color; const messageColor = data.user.color;
const role = data.message.role; const role = data.user.role;
// Highlight mentions
const mentionRgx = new RegExp(`(^|\\s)@${twitchUsername}(\\s|$)`, 'i');
const mention = mentionRgx.test(data.text);
if (mention && highlightMentions && showMessage)
messageContainerDiv.classList.add("highlightMessage");
// Set furry mode // Set furry mode
if (furryMode) if (furryMode)
@@ -669,7 +692,7 @@ async function TwitchChatMessage(data) {
} }
// Set the "action" color // Set the "action" color
if (data.message.isMe) if (data.meta.isMe)
messageDiv.style.color = messageColor; messageDiv.style.color = messageColor;
// Remove the line break // Remove the line break
@@ -688,9 +711,9 @@ async function TwitchChatMessage(data) {
// Render badges // Render badges
if (showBadges) { if (showBadges) {
badgeListDiv.innerHTML = ""; badgeListDiv.innerHTML = "";
for (i in data.message.badges) { for (i in data.user.badges) {
const badge = new Image(); const badge = new Image();
badge.src = data.message.badges[i].imageUrl; badge.src = data.user.badges[i].imageUrl;
badge.classList.add("badge"); badge.classList.add("badge");
badgeListDiv.appendChild(badge); badgeListDiv.appendChild(badge);
} }
@@ -698,7 +721,7 @@ async function TwitchChatMessage(data) {
// Render avatars // Render avatars
if (showAvatar) { if (showAvatar) {
const username = data.message.username; const username = data.user.login;
const avatarURL = await GetAvatar(username, 'twitch'); const avatarURL = await GetAvatar(username, 'twitch');
const avatar = new Image(); const avatar = new Image();
avatar.src = avatarURL; avatar.src = avatarURL;
@@ -707,7 +730,7 @@ async function TwitchChatMessage(data) {
} }
// Custom styling for subs // Custom styling for subs
if (data.message.subscriber) { if (data.user.subscribed) {
usernameDiv.classList.add('sub-glow') usernameDiv.classList.add('sub-glow')
} }
@@ -740,7 +763,7 @@ async function TwitchChatMessage(data) {
messageDiv.innerHTML = ''; messageDiv.innerHTML = '';
messageDiv.appendChild(image); messageDiv.appendChild(image);
AddMessageItem(instance, data.message.msgId, 'twitch', data.user.id); AddMessageItem(instance, data.messageId, 'twitch', data.user.id);
}; };
const urlObj = new URL(message); const urlObj = new URL(message);
@@ -750,7 +773,7 @@ async function TwitchChatMessage(data) {
image.src = "https://external-content.duckduckgo.com/iu/?u=" + urlObj.toString(); image.src = "https://external-content.duckduckgo.com/iu/?u=" + urlObj.toString();
} }
else { else {
AddMessageItem(instance, data.message.msgId, 'twitch', data.user.id); AddMessageItem(instance, data.messageId, 'twitch', data.user.id);
} }
// Render YouTube links // Render YouTube links
@@ -1153,6 +1176,57 @@ async function TwitchRewardRedemption(data) {
AddMessageItem(instance, data.messageId); AddMessageItem(instance, data.messageId);
} }
async function TwitchCustomPowerUpRedemption(data) {
if (!showTwitchPowerUpRedemptions)
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');
const baseColor = data.customPowerUp.backgroundColor; // e.g., #3498db
// B3 = 70% opacity
// FF = 100% opacity
cardDiv.style.background = `linear-gradient(180deg, ${baseColor}B3 0%, ${baseColor}FF 100%)`;
if (showAvatar) {
// Render avatars
const username = data.user.login;
const avatarURL = await GetAvatar(username, 'twitch');
const avatar = new Image();
avatar.src = avatarURL;
avatar.classList.add("avatar");
avatarDiv.appendChild(avatar);
}
// Set the text
let username = data.user.name;
if (data.user.name.toLowerCase() != data.user.login.toLowerCase())
username = `${data.user.name} (${data.user.login})`;
const powerUpName = data.customPowerUp.title;
const cost = data.customPowerUp.bits;
const userInput = data.userInput;
const bitIcon = `<img src="icons/badges/twitch-bit.svg" class="platform"/>`;
titleDiv.innerHTML = `${username} redeemed ${powerUpName} ${bitIcon} ${cost}`;
contentDiv.innerText = `${userInput}`;
AddMessageItem(instance, data.messageId);
}
async function TwitchRaid(data) { async function TwitchRaid(data) {
if (!showTwitchRaids) if (!showTwitchRaids)
return; return;
@@ -1220,20 +1294,20 @@ async function TwitchWatchStreak(data) {
if (showAvatar) { if (showAvatar) {
// Render avatars // Render avatars
const username = data.userName; const username = data.user.login;
const avatarURL = await GetAvatar(username, 'twitch'); const avatarURL = await GetAvatar(username, 'twitch');
const avatar = new Image(); const avatar = new Image();
avatar.src = avatarURL; avatar.src = avatarURL;
avatar.classList.add("avatar"); avatar.classList.add("avatar");
avatarDiv.appendChild(avatar); avatarDiv.appendChild(avatar);
} }
const displayName = data.displayName; // TODO: Streamer.bot v1.0.5-alpha3 changed the data sent with this event, so for backwards compatibility we need to check for both the old and new properties
const watchStreak = data.watchStreak; const displayName = data.displayName ?? data.user.name;
const message = RenderMessageWithEmotesHTML(data.message, data.emotes); const watchStreak = data.watchStreak ?? data.streak_count;
titleDiv.innerText = `${displayName} is currently on a ${watchStreak} stream streak! `; titleDiv.innerText = `${displayName} is currently on a ${watchStreak} stream streak! `;
contentDiv.innerHTML = message; contentDiv.innerHTML = ConstructMessageFromParts(data.parts);
AddMessageItem(instance, data.messageId); AddMessageItem(instance, data.messageId);
} }
@@ -1352,6 +1426,12 @@ async function YouTubeMessage(data) {
//let message = RenderMessageWithEmotesHTML(data.message, data.emotes); //let message = RenderMessageWithEmotesHTML(data.message, data.emotes);
let message = ConstructMessageFromParts(data.parts); let message = ConstructMessageFromParts(data.parts);
// Highlight mentions
const mentionRgx = new RegExp(`(^|\\s)@${youtubeUsername}(\\s|$)`, 'i');
const mention = mentionRgx.test(message);
if (mention && highlightMentions && showMessage)
messageContainerDiv.classList.add("highlightMessage");
// Set furry mode // Set furry mode
if (furryMode) if (furryMode)
message = TranslateToFurry(message); message = TranslateToFurry(message);
@@ -2300,8 +2380,8 @@ async function KickChatMessage(data) {
// Set Reply Message // Set Reply Message
const isReply = data.isReply; const isReply = data.isReply;
if (isReply && showMessage) { if (isReply && showMessage) {
const replyUser = data.reply.sender.name; const replyUser = data.reply.userName;
const replyMsg = data.reply.content; const replyMsg = data.reply.msgBody;
replyDiv.style.display = 'block'; replyDiv.style.display = 'block';
replyUserDiv.innerText = replyUser; replyUserDiv.innerText = replyUser;
@@ -2326,7 +2406,7 @@ async function KickChatMessage(data) {
// Highlight mentions // Highlight mentions
const mentionRgx = new RegExp(`(^|\\s)@${kickUsername}(\\s|$)`, 'i'); const mentionRgx = new RegExp(`(^|\\s)@${kickUsername}(\\s|$)`, 'i');
const mention = mentionRgx.test(message); const mention = mentionRgx.test(message);
if (mention && showMessage) if (mention && highlightMentions && showMessage)
messageContainerDiv.classList.add("highlightMessage"); messageContainerDiv.classList.add("highlightMessage");
// Set furry mode // Set furry mode
@@ -3171,13 +3251,13 @@ function IsThisUserAllowedToPostImagesOrNotReturnTrueIfTheyCanReturnFalseIfTheyC
function GetPermissionLevel(data, platform) { function GetPermissionLevel(data, platform) {
switch (platform) { switch (platform) {
case 'twitch': case 'twitch':
if (data.message.role >= 4) if (data.user.role >= 4)
return 40; return 40;
else if (data.message.role >= 3) else if (data.user.role >= 3)
return 30; return 30;
else if (data.message.role >= 2) else if (data.user.role >= 2)
return 20; return 20;
else if (data.message.role >= 2 || data.message.subscriber) else if (data.user.role >= 2 || data.user.subscribed)
return 15; return 15;
else else
return 10; return 10;
+16
View File
@@ -192,6 +192,14 @@
"defaultValue": false, "defaultValue": false,
"group": "General" "group": "General"
}, },
{
"id": "highlightMentions",
"label": "Highlight Mentions",
"description": "Highlight messages that mention the streamer",
"type": "checkbox",
"defaultValue": true,
"group": "General"
},
{ {
"id": "imageEmbedPermissionLevel", "id": "imageEmbedPermissionLevel",
"label": "Embed Images", "label": "Embed Images",
@@ -282,6 +290,14 @@
"defaultValue": true, "defaultValue": true,
"group": "Which Twitch messages do you want to see?" "group": "Which Twitch messages do you want to see?"
}, },
{
"id": "showTwitchPowerUpRedemptions",
"label": "Power-Up Redemptions",
"description": "",
"type": "checkbox",
"defaultValue": true,
"group": "Which Twitch messages do you want to see?"
},
{ {
"id": "showTwitchRaids", "id": "showTwitchRaids",
"label": "Raids", "label": "Raids",
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 2664 2665" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1.03455,0,0,1.03455,-46.020186,0)">
<path d="M1332,0L2444,1578L1332,2576L220,1578L1332,0ZM1332,1778L1922,1519L1332,622L742,1519L1332,1778Z" style="fill:white;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 658 B

+54 -10
View File
@@ -63,6 +63,8 @@ const showTwitchSubs = GetBooleanParam("showTwitchSubs", true);
const twitchSubAction = urlParams.get("twitchSubAction") || ""; const twitchSubAction = urlParams.get("twitchSubAction") || "";
const showTwitchChannelPointRedemptions = GetBooleanParam("showTwitchChannelPointRedemptions", true); const showTwitchChannelPointRedemptions = GetBooleanParam("showTwitchChannelPointRedemptions", true);
const twitchChannelPointRedemptionAction = urlParams.get("twitchChannelPointRedemptionAction") || ""; const twitchChannelPointRedemptionAction = urlParams.get("twitchChannelPointRedemptionAction") || "";
const showTwitchPowerUpRedemptions = GetBooleanParam("showTwitchPowerUpRedemptions", true);
const twitchPowerUpRedemptionAction = urlParams.get("twitchPowerUpRedemptionAction") || "";
const showTwitchCheers = GetBooleanParam("showTwitchCheers", true); const showTwitchCheers = GetBooleanParam("showTwitchCheers", true);
const twitchCheerAction = urlParams.get("twitchCheerAction") || ""; const twitchCheerAction = urlParams.get("twitchCheerAction") || "";
const showTwitchRaids = GetBooleanParam("showTwitchRaids", true); const showTwitchRaids = GetBooleanParam("showTwitchRaids", true);
@@ -115,7 +117,9 @@ const fourthwallAlertAction = urlParams.get("fourthwallAlertAction") || "";
// HIDDEN OPTIONS // // HIDDEN OPTIONS //
//////////////////// ////////////////////
let twitchUsername = urlParams.get("twitchUsername") || "";
let kickUsername = urlParams.get("kickUsername") || ""; let kickUsername = urlParams.get("kickUsername") || "";
let youtubeUsername = urlParams.get("youtubeUsername") || "";
// Set avatar visibility // Set avatar visibility
if (!showAvatar) { if (!showAvatar) {
@@ -216,6 +220,11 @@ client.on('Twitch.RewardRedemption', (response) => {
TwitchRewardRedemption(response.data); TwitchRewardRedemption(response.data);
}) })
client.on('Twitch.CustomPowerUpRedemption', (response) => {
console.debug(response.data);
TwitchCustomPowerUpRedemption(response.data);
})
client.on('Twitch.Raid', (response) => { client.on('Twitch.Raid', (response) => {
console.debug(response.data); console.debug(response.data);
TwitchRaid(response.data); TwitchRaid(response.data);
@@ -324,12 +333,18 @@ client.on('Fourthwall.GiftDrawEnded', (response) => {
// Connect and handle Pusher WebSocket // Connect and handle Pusher WebSocket
async function KickConnect() { async function KickConnect() {
// Fetch from Streamer.bot
const broadcasterInfo = await client.getBroadcaster();
// This code has nothing to do with Kick, but it's a really good place to get Twitch and YouTube usernames as well, because I'm built different like that
if (broadcasterInfo.platforms.twitch)
twitchUsername = broadcasterInfo.platforms.twitch.broadcastUserName;
if (broadcasterInfo.platforms.youtube)
youtubeUsername = broadcasterInfo.platforms.youtube.broadcastUserName;
// If user has not manually set Kick username, try to grab if from Streamer.bot // If user has not manually set Kick username, try to grab if from Streamer.bot
if (!kickUsername) if (!kickUsername)
{ {
// Fetch from Streamer.bot
const broadcasterInfo = await client.getBroadcaster();
if (broadcasterInfo.platforms.kick) if (broadcasterInfo.platforms.kick)
kickUsername = broadcasterInfo.platforms.kick.broadcasterLogin; kickUsername = broadcasterInfo.platforms.kick.broadcasterLogin;
else else
@@ -494,7 +509,7 @@ async function TwitchCheer(data) {
return; return;
// Set the text // Set the text
const username = data.message.displayName; const username = data.user.login;
const bits = data.bits; const bits = data.bits;
let message = ConstructMessageFromParts(data.parts); let message = ConstructMessageFromParts(data.parts);
@@ -688,6 +703,34 @@ async function TwitchRewardRedemption(data) {
); );
} }
async function TwitchCustomPowerUpRedemption(data) {
if (!showTwitchPowerUpRedemptions)
return;
let username = data.user.name;
if (data.user.name.toLowerCase() != data.user.login.toLowerCase())
username = `${data.user.name} (${data.user.login})`;
const powerUpName = data.customPowerUp.title;
const cost = data.customPowerUp.bits;
const userInput = data.userInput;
const bitIcon = `<img src="icons/badges/twitch-bit.svg" class="platform" style="height: 1em"/>`;
// Render avatars
const avatarURL = await GetAvatar(data.user.login, 'twitch');
UpdateAlertBox(
'twitch',
avatarURL,
`${username} redeemed`,
`${powerUpName} ${bitIcon} ${cost}`,
'',
username,
userInput,
twitchPowerUpRedemptionAction,
data
);
}
async function TwitchRaid(data) { async function TwitchRaid(data) {
if (!showTwitchRaids) if (!showTwitchRaids)
return; return;
@@ -717,19 +760,20 @@ async function TwitchWatchStreak(data) {
return; return;
// Render avatars // Render avatars
const avatarURL = await GetAvatar(data.userName, 'twitch'); const avatarURL = await GetAvatar(data.user.login, 'twitch');
// Set the text // Set the text
const username = data.displayName; // TODO: Streamer.bot v1.0.5-alpha3 changed the data sent with this event, so for backwards compatibility we need to check for both the old and new properties
const watchStreak = data.watchStreak; const displayName = data.displayName ?? data.user.name;
const watchStreak = data.watchStreak ?? data.streak_count;
UpdateAlertBox( UpdateAlertBox(
'twitch', 'twitch',
avatarURL, avatarURL,
`${username}`, `${displayName}`,
`is currently on a ${watchStreak} stream streak!`, `is currently on a ${watchStreak} stream streak!`,
'', '',
username, displayName,
'', '',
twitchWatchStreaksAction, twitchWatchStreaksAction,
data data
+17
View File
@@ -278,6 +278,23 @@
"showIf": "showTwitchChannelPointRedemptions", "showIf": "showTwitchChannelPointRedemptions",
"group": "Which Twitch alerts do you want to see?" "group": "Which Twitch alerts do you want to see?"
}, },
{
"id": "showTwitchPowerUpRedemptions",
"label": "Power-Up Redemptions",
"description": "",
"type": "checkbox",
"defaultValue": true,
"group": "Which Twitch alerts do you want to see?"
},
{
"id": "twitchPowerUpRedemptionAction",
"label": "",
"description": "Also run this Streamer.bot Action",
"type": "sb-actions",
"defaultValue": "",
"showIf": "showTwitchPowerUpRedemptions",
"group": "Which Twitch alerts do you want to see?"
},
{ {
"id": "showTwitchCheers", "id": "showTwitchCheers",
"label": "Cheers", "label": "Cheers",
+48
View File
@@ -189,6 +189,54 @@ async function CustomEvent(data) {
SetPlatformIcon(iconEl, 'twitch'); SetPlatformIcon(iconEl, 'twitch');
} }
break; break;
case ('TwitchCustomPowerUpRedemption'):
{
avatarEl.src = await GetAvatar(data.userName, 'twitch');
titleEl.innerText = `${data["customPowerUp.bits"]} BITS`;
subtitleEl.innerText = `${data.user}`;
const messageEl = document.createElement('div');
messageEl.innerHTML = data.rawInput;
// // Render emotes
// for (i in data.emotes) {
// const emoteElement = `<img src="${data.emotes[i].imageUrl}" class="emote"/>`;
// const emoteName = EscapeRegExp(data.emotes[i].name);
// function EscapeRegExp(string) {
// return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
// }
// 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');
// messageEl.innerHTML = messageEl.innerHTML.replace(regex, emoteElement);
// }
// // Render cheermotes
// for (i in data.cheerEmotes) {
// const bits = data.cheerEmotes[i].bits;
// const imageUrl = data.cheerEmotes[i].imageUrl;
// const name = data.cheerEmotes[i].name;
// const cheerEmoteElement = `<img src="${imageUrl}" class="emote"/>`;
// const bitsElements = `<span class="bits">${bits}</span>`
// messageEl.innerHTML = messageEl.innerHTML.replace(new RegExp(`\\b${name}${bits}\\b`, 'i'), cheerEmoteElement + bitsElements);
// }
contentEl.appendChild(messageEl);
// Set the platform icon
SetPlatformIcon(iconEl, 'twitch');
}
break;
// YouTube Events // YouTube Events
case ('YouTubeNewSponsor'): case ('YouTubeNewSponsor'):