diff --git a/.common/utils/helpers.js b/.common/utils/helpers.js
index 298a2bf..02293b4 100644
--- a/.common/utils/helpers.js
+++ b/.common/utils/helpers.js
@@ -12,39 +12,39 @@ const pronounMap = new Map();
//////////////////////
function GetBooleanParam(paramName, defaultValue) {
- const urlParams = new URLSearchParams(window.location.search);
- const paramValue = urlParams.get(paramName);
+ const urlParams = new URLSearchParams(window.location.search);
+ const paramValue = urlParams.get(paramName);
- if (paramValue === null) {
- return defaultValue; // Parameter not found
- }
+ if (paramValue === null) {
+ return defaultValue; // Parameter not found
+ }
- const lowercaseValue = paramValue.toLowerCase(); // Handle case-insensitivity
+ const lowercaseValue = paramValue.toLowerCase(); // Handle case-insensitivity
- if (lowercaseValue === 'true') {
- return true;
- } else if (lowercaseValue === 'false') {
- return false;
- } else {
- return paramValue; // Return original string if not 'true' or 'false'
- }
+ if (lowercaseValue === 'true') {
+ return true;
+ } else if (lowercaseValue === 'false') {
+ return false;
+ } else {
+ return paramValue; // Return original string if not 'true' or 'false'
+ }
}
function GetIntParam(paramName, defaultValue) {
- const urlParams = new URLSearchParams(window.location.search);
- const paramValue = urlParams.get(paramName);
+ const urlParams = new URLSearchParams(window.location.search);
+ const paramValue = urlParams.get(paramName);
- if (paramValue === null) {
- return defaultValue; // or undefined, or a default value, depending on your needs
- }
+ if (paramValue === null) {
+ return defaultValue; // or undefined, or a default value, depending on your needs
+ }
- const intValue = parseInt(paramValue, 10); // Parse as base 10 integer
+ const intValue = parseInt(paramValue, 10); // Parse as base 10 integer
- if (isNaN(intValue)) {
- return null; // or handle the error in another way, e.g., throw an error
- }
+ if (isNaN(intValue)) {
+ return null; // or handle the error in another way, e.g., throw an error
+ }
- return intValue;
+ return intValue;
}
async function GetKickIds(username) {
@@ -143,82 +143,89 @@ async function GetAvatar(username, platform) {
}
async function GetPronouns(platform, username) {
- if (pronounMap.has(username)) {
- console.debug(`Pronouns found for ${username}. Retrieving from hash map.`)
- return pronounMap.get(username);
- }
- else {
- console.debug(`No pronouns found for ${username}. Retrieving from alejo.io.`)
- const response = await client.getUserPronouns(platform, username);
- const userFound = response.pronoun.userFound;
- const pronouns = userFound ? `${response.pronoun.pronounSubject}/${response.pronoun.pronounObject}` : '';
+ if (pronounMap.has(username)) {
+ console.debug(`Pronouns found for ${username}. Retrieving from hash map.`)
+ return pronounMap.get(username);
+ }
+ else {
+ console.debug(`No pronouns found for ${username}. Retrieving from alejo.io.`)
+ const response = await client.getUserPronouns(platform, username);
+ const userFound = response.pronoun.userFound;
+ const pronouns = userFound ? `${response.pronoun.pronounSubject}/${response.pronoun.pronounObject}` : '';
- pronounMap.set(username, pronouns);
+ pronounMap.set(username, pronouns);
- return pronouns;
- }
+ return pronouns;
+ }
}
function GetCurrentTimeFormatted() {
- const now = new Date();
- let hours = now.getHours();
- const minutes = String(now.getMinutes()).padStart(2, '0');
- const ampm = hours >= 12 ? 'PM' : 'AM';
+ const now = new Date();
+ let hours = now.getHours();
+ const minutes = String(now.getMinutes()).padStart(2, '0');
+ const ampm = hours >= 12 ? 'PM' : 'AM';
- hours = hours % 12;
- hours = hours ? hours : 12; // the hour '0' should be '12'
+ hours = hours % 12;
+ hours = hours ? hours : 12; // the hour '0' should be '12'
- const formattedTime = `${hours}:${minutes} ${ampm}`;
- return formattedTime;
+ const formattedTime = `${hours}:${minutes} ${ampm}`;
+ return formattedTime;
}
function DecodeHTMLString(html) {
- var txt = document.createElement("textarea");
- txt.innerHTML = html;
- return txt.value;
+ var txt = document.createElement("textarea");
+ txt.innerHTML = html;
+ return txt.value;
}
function TranslateToFurry(sentence) {
- const words = sentence.toLowerCase().split(/\b/);
+ // Split on
tags, keeping them in the result
+ const parts = sentence.split(/(
]*>)/gi);
- const furryWords = words.map(word => {
- if (/\w+/.test(word)) {
- let newWord = word;
+ const furryParts = parts.map(part => {
+ // If this part is an
, leave it unchanged
+ if (/^
]*>$/.test(part)) {
+ return part;
+ }
- // Common substitutions
- newWord = newWord.replace(/l/g, 'w');
- newWord = newWord.replace(/r/g, 'w');
- newWord = newWord.replace(/th/g, 'f');
- newWord = newWord.replace(/you/g, 'yous');
- newWord = newWord.replace(/my/g, 'mah');
- newWord = newWord.replace(/me/g, 'meh');
- newWord = newWord.replace(/am/g, 'am');
- newWord = newWord.replace(/is/g, 'is');
- newWord = newWord.replace(/are/g, 'are');
- newWord = newWord.replace(/very/g, 'vewy');
- newWord = newWord.replace(/pretty/g, 'pwetty');
- newWord = newWord.replace(/little/g, 'wittle');
- newWord = newWord.replace(/nice/g, 'nyce');
+ // Otherwise, apply furry translation
+ const words = part.toLowerCase().split(/\b/);
- // Random additions
- if (Math.random() < 0.15) {
- newWord += ' nya~';
- } else if (Math.random() < 0.1) {
- newWord += ' >w<';
- } else if (Math.random() < 0.05) {
- newWord += ' owo';
- }
+ return words.map(word => {
+ if (/\w+/.test(word)) {
+ let newWord = word;
- return newWord;
- }
- return word;
- });
+ // Common substitutions
+ newWord = newWord.replace(/l/g, 'w');
+ newWord = newWord.replace(/r/g, 'w');
+ newWord = newWord.replace(/th/g, 'f');
+ newWord = newWord.replace(/you/g, 'yous');
+ newWord = newWord.replace(/my/g, 'mah');
+ newWord = newWord.replace(/me/g, 'meh');
+ newWord = newWord.replace(/am/g, 'am');
+ newWord = newWord.replace(/is/g, 'is');
+ newWord = newWord.replace(/are/g, 'are');
+ newWord = newWord.replace(/very/g, 'vewy');
+ newWord = newWord.replace(/pretty/g, 'pwetty');
+ newWord = newWord.replace(/little/g, 'wittle');
+ newWord = newWord.replace(/nice/g, 'nyce');
- return furryWords.join('');
-}
+ // Random additions
+ if (Math.random() < 0.15) {
+ newWord += ' nya~';
+ } else if (Math.random() < 0.1) {
+ newWord += ' >w<';
+ } else if (Math.random() < 0.05) {
+ newWord += ' owo';
+ }
-function EscapeRegExp(string) {
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
+ return newWord;
+ }
+ return word;
+ }).join('');
+ });
+
+ return furryParts.join('');
}
// Given a string, return a random hex code. The same input always results in the same output
@@ -260,4 +267,63 @@ function RandomHex(str) {
}
return hslToHex(hue, saturation, lightness);
+}
+
+// Used to construct a message from "parts" variable commonly found in Streamer.bot chat messages
+function ConstructMessageFromParts(parts) {
+ return parts.map(part => {
+ switch (part.type)
+ {
+ case "text":
+ return part.text;
+ case "cheer":
+ // Render the cheer emote image
+ const emoteImg = `
`;
+
+ // Render the bits count
+ const bitLabel = `${part.bits}`;
+
+ return emoteImg + bitLabel;
+ default:
+ return `
`;
+ }
+ }).join('');
+}
+
+// Used to construct a message from "emotes" variable commonly found in Streamer.bot chat messages
+function RenderMessageWithEmotesHTML(originalMessage, emotes) {
+ if (!emotes || emotes.length === 0) return originalMessage;
+
+ // Sort emotes by startIndex
+ emotes.sort((a, b) => a.startIndex - b.startIndex);
+
+ let html = '';
+ let cursor = 0;
+
+ emotes.forEach(emote => {
+ // Add text before the emote
+ if (emote.startIndex > cursor) {
+ html += escapeHTML(originalMessage.slice(cursor, emote.startIndex));
+ }
+
+ // Add emote image
+ html += `
`;
+
+ cursor = emote.endIndex + 1;
+ });
+
+ // Add remaining text after last emote
+ if (cursor < originalMessage.length) {
+ html += escapeHTML(originalMessage.slice(cursor));
+ }
+
+ // Simple HTML escape function
+ function escapeHTML(str) {
+ return str.replace(/[&<>"']/g, match => {
+ const escape = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
+ return escape[match];
+ });
+ }
+
+ return html;
}
\ No newline at end of file
diff --git a/horizontal-chat/script.js b/horizontal-chat/script.js
index 93d4008..08d4186 100644
--- a/horizontal-chat/script.js
+++ b/horizontal-chat/script.js
@@ -50,9 +50,8 @@ const showTwitchRaids = GetBooleanParam("showTwitchRaids", true);
const showTwitchWatchStreaks = GetBooleanParam("showTwitchWatchStreaks", true);
const showTwitchSharedChat = GetBooleanParam("showTwitchSharedChat", true);
-const kickUsername = urlParams.get("kickUsername") || "";
const showKickMessages = GetBooleanParam("showKickMessages", true);
-// const showKickFollows = GetBooleanParam("showKickFollows", false);
+const showKickFollows = GetBooleanParam("showKickFollows", false);
const showKickSubs = GetBooleanParam("showKickSubs", true);
const showKickChannelPointRedemptions = GetBooleanParam("showKickChannelPointRedemptions", true);
const showKickHosts = GetBooleanParam("showKickHosts", true);
@@ -86,6 +85,7 @@ const animationSpeed = GetIntParam("animationSpeed", 0.5);
const randomYouTubeColors = GetBooleanParam("randomYouTubeColors", false);
const youtubeColor = urlParams.get("youtubeColor") || "#f70000";
const youtubeCustomSubIcon = urlParams.get("youtubeCustomSubIcon") || "";
+const kickUsername = urlParams.get("kickUsername") || "";
@@ -233,6 +233,37 @@ client.on('YouTube.GiftMembershipReceived', (response) => {
YouTubeGiftMembershipReceived();
})
+client.on('Kick.ChatMessage', (response) => {
+ console.debug(response.data);
+ KickChatMessage(response.data);
+})
+
+client.on('Kick.Follow', (response) => {
+ console.debug(response.data);
+ KickFollow(response.data);
+})
+
+// // TODO: Seems to be missing data, using Pusher until this is fixed
+// client.on('Kick.Subscription', (response) => {
+// console.debug(response.data);
+// KickSubscription(response.data);
+// })
+
+// client.on('Kick.Resubscription', (response) => {
+// console.debug(response.data);
+// KickSubscription(response.data);
+// })
+
+// client.on('Kick.GiftSubscription', (response) => {
+// console.debug(response.data);
+// KickGiftedSubscriptions(response.data);
+// })
+
+// client.on('Kick.RewardRedemption', (response) => {
+// console.debug(response.data);
+// KickRewardRedeemed(response.data);
+// })
+
client.on('Streamlabs.Donation', (response) => {
console.debug(response.data);
StreamlabsDonation(response.data);
@@ -311,8 +342,17 @@ client.on('Fourthwall.GiftDrawEnded', (response) => {
// Connect and handle Pusher WebSocket
async function KickConnect() {
+ // If user has not manually set Kick username, try to grab if from Streamer.bot
if (!kickUsername)
- return;
+ {
+ // Fetch from Streamer.bot
+ const broadcasterInfo = await client.getBroadcaster();
+
+ if (broadcasterInfo.platforms.kick)
+ kickUsername = broadcasterInfo.platforms.kick.broadcasterLogin;
+ else
+ return;
+ }
// Channel to subscribe to (you'll need the correct channel name here)
const kickIds = await GetKickIds(kickUsername);
@@ -327,7 +367,7 @@ async function KickConnect() {
// Reconnect
websocket.onclose = function () {
console.log(`Reconnecting to ${kickUsername}...`);
- setTimeout(connectPusher, 5000);
+ setTimeout(KickConnect, 5000);
};
websocket.onopen = function () {
@@ -358,9 +398,9 @@ async function KickConnect() {
const eventArgs = JSON.parse(data.data);
const event = data.event.split('\\').pop();
switch (event) {
- case 'ChatMessageEvent':
- KickChatMessage(eventArgs);
- break;
+ // case 'ChatMessageEvent':
+ // KickChatMessage(eventArgs);
+ // break;
//// 'Follows' unsupported by pusher
// case 'FollowEvent':
// break;
@@ -397,7 +437,6 @@ async function KickConnect() {
window.addEventListener('load', KickConnect);
-
//////////////////////
// TIKFINITY CLIENT //
//////////////////////
@@ -522,7 +561,7 @@ async function TwitchChatMessage(data) {
}
// Set the message data
- let message = data.message.message;
+ let message = ConstructMessageFromParts(data.parts);
const messageColor = data.message.color;
const role = data.message.role;
@@ -532,7 +571,7 @@ async function TwitchChatMessage(data) {
// Set message text
if (showMessage) {
- messageDiv.innerText = message;
+ messageDiv.innerHTML = message;
}
// Set the "action" color
@@ -545,7 +584,6 @@ async function TwitchChatMessage(data) {
platformDiv.innerHTML = platformElements;
}
-
// Render badges
if (showBadges) {
badgeListDiv.innerHTML = "";
@@ -557,38 +595,6 @@ async function TwitchChatMessage(data) {
}
}
- // 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');
- messageDiv.innerHTML = messageDiv.innerHTML.replace(regex, emoteElement);
- }
-
- // Render cheermotes
- for (i in data.cheerEmotes) {
- // const cheerEmoteElement = `
`;
- // messageDiv.innerHTML = messageDiv.innerHTML.replace(new RegExp(`\\b${data.cheerEmotes[i].name}\\b`), cheerEmoteElement);
- const bits = data.cheerEmotes[i].bits;
- const imageUrl = data.cheerEmotes[i].imageUrl;
- const name = data.cheerEmotes[i].name;
- const cheerEmoteElement = `
`;
- const bitsElements = `${bits}`
- messageDiv.innerHTML = messageDiv.innerHTML.replace(new RegExp(`\\b${name}${bits}\\b`, 'i'), cheerEmoteElement + bitsElements);
- }
-
// Render avatars
if (showAvatar) {
const username = data.message.username;
@@ -660,29 +666,7 @@ async function TwitchAnnouncement(data) {
break;
}
- let message = data.text;
-
- // Render emotes
- for (i in data.parts) {
- if (data.parts[i].type == `emote`) {
- const emoteElement = `
`;
- const emoteName = EscapeRegExp(data.parts[i].text);
-
- 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');
- message = message.replace(regex, emoteElement);
- }
- }
+ let message = ConstructMessageFromParts(data.parts);
ShowAlert(message, background);
}
@@ -918,13 +902,17 @@ function YouTubeMessage(data) {
usernameDiv.style.color = youtubeColor; // YouTube users do not have colors, so just set it to red
}
- if (showMessage) {
- messageDiv.innerText = data.message;
- }
-
+ // Set the message data
+ let message = RenderMessageWithEmotesHTML(data.message, data.emotes);
+
// Set furry mode
if (furryMode)
- messageDiv.innerText = TranslateToFurry(data.message);
+ message = TranslateToFurry(message);
+
+ // Set message text
+ if (showMessage) {
+ messageDiv.innerHTML = message;
+ }
// Render platform
if (showPlatform) {
@@ -972,12 +960,6 @@ function YouTubeMessage(data) {
badgeListDiv.appendChild(badge);
}
- // Render emotes
- for (i in data.emotes) {
- const emoteElement = `
`;
- messageDiv.innerHTML = messageDiv.innerHTML.replace(data.emotes[i].name, emoteElement);
- }
-
// Render avatars
if (showAvatar) {
const avatar = new Image();
@@ -1305,11 +1287,11 @@ async function KickChatMessage(data) {
return;
// Don't post messages starting with "!"
- if (data.content.startsWith("!") && excludeCommands)
+ if (data.text.startsWith("!") && excludeCommands)
return;
// Don't post messages from users from the ignore list
- if (ignoreUserList.includes(data.sender.username.toLowerCase()))
+ if (ignoreUserList.includes(data.user.name.toLowerCase()))
return;
// Get a reference to the template
@@ -1336,12 +1318,12 @@ async function KickChatMessage(data) {
// Set the username info
if (showUsername) {
- usernameDiv.innerText = data.sender.username;
- usernameDiv.style.color = data.sender.identity.color;
+ usernameDiv.innerText = data.user.name;
+ usernameDiv.style.color = data.user.color;
}
// Set the message data
- let message = data.content;
+ let message = ConstructMessageFromParts(data.parts);
// Set furry mode
if (furryMode)
@@ -1349,7 +1331,7 @@ async function KickChatMessage(data) {
// Set message text
if (showMessage) {
- messageDiv.innerText = message;
+ messageDiv.innerHTML = message;
}
// Render platform
@@ -1361,28 +1343,38 @@ async function KickChatMessage(data) {
// Render badges
if (showBadges) {
badgeListDiv.innerHTML = "";
- for (i in data.sender.identity.badges) {
+ for (i in data.user.badges) {
const badge = new Image();
- badge.src = GetKickBadgeURL(data.sender.identity.badges[i]);
+ if (data.user.badges[i].imageUrl)
+ badge.src = data.user.badges[i].imageUrl;
+ else
+ badge.src = GetKickBadgeURL(data.user.badges[i]);
badge.classList.add("badge");
badgeListDiv.appendChild(badge);
}
}
+ function GetKickBadgeURL(data) {
+ switch (data.id) {
+ case 'subscriber':
+ return CalculateKickSubBadge(data.count);
+ default:
+ return `icons/badges/kick-${data.id}.svg`;
+ }
+ function CalculateKickSubBadge(months) {
+ if (!Array.isArray(kickSubBadges)) return null;
- // Render emotes
- function replaceEmotes(message) {
- const emoteRegex = /\[emote:(\d+):([^\]]+)\]/g;
+ // Filter for eligible badges, then get the one with the highest 'months'
+ const badge = kickSubBadges
+ .filter(b => b.months <= months)
+ .sort((a, b) => b.months - a.months)[0];
- return message.replace(emoteRegex, (_, id, name) => {
- const imgUrl = `https://files.kick.com/emotes/${id}/fullsize`;
- return `
`;
- });
+ return badge?.badge_image?.src || `icons/badges/kick-subscriber.svg`;
+ }
}
- messageDiv.innerHTML = replaceEmotes(message);
// Render avatars
if (showAvatar) {
- const username = data.sender.slug;
+ const username = data.user.login;
const avatarURL = await GetAvatar(username, 'kick');
const avatar = new Image();
avatar.src = avatarURL;
@@ -1399,7 +1391,21 @@ async function KickChatMessage(data) {
userInfoDiv.style.display = "none";
}
- AddMessageItem(instance, data.id, 'kick', data.sender.id);
+ AddMessageItem(instance, data.messageId, 'kick', data.user.id);
+}
+
+function KickFollow(data) {
+ if (!showKickFollows)
+ return;
+
+ // 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 message = `${username} followed`;
+
+ ShowAlert(message, 'kick');
}
function KickSubscription(data) {
@@ -1408,7 +1414,7 @@ function KickSubscription(data) {
// Set the text
const username = data.username;
- const months = data.months;
+ const months = data.months
let message = '';
if (months <= 1)
@@ -1710,43 +1716,6 @@ function AddMessageItem(element, elementID, platform, userId) {
}, 200);
}
-// I used Gemini for this shit so if it doesn't work, blame Google
-function FindFirstImageUrl(jsonObject) {
- if (typeof jsonObject !== 'object' || jsonObject === null) {
- return null; // Handle invalid input
- }
-
- function iterate(obj) {
- if (Array.isArray(obj)) {
- for (const item of obj) {
- const result = iterate(item);
- if (result) {
- return result;
- }
- }
- return null;
- }
-
- for (const key in obj) {
- if (obj.hasOwnProperty(key)) {
- if (key === 'imageUrl') {
- return obj[key]; // Found it! Return the value.
- }
-
- if (typeof obj[key] === 'object' && obj[key] !== null) {
- const result = iterate(obj[key]); // Recursive call for nested objects
- if (result) {
- return result; // Propagate the found value
- }
- }
- }
- }
- return null; // Key not found in this level
- }
-
- return iterate(jsonObject);
-}
-
function ShowAlert(message, background = null, duration = animationDuration) {
// Check if the widget is in the middle of an animation
@@ -1810,26 +1779,6 @@ function GetWinnersList(gifts) {
}
}
-function GetKickBadgeURL(data) {
- switch (data.type) {
- case 'subscriber':
- return CalculateKickSubBadge(data.count);
- default:
- return `icons/badges/kick-${data.type}.svg`;
- }
-}
-
-function CalculateKickSubBadge(months) {
- if (!Array.isArray(kickSubBadges)) return null;
-
- // Filter for eligible badges, then get the one with the highest 'months'
- const badge = kickSubBadges
- .filter(b => b.months <= months)
- .sort((a, b) => b.months - a.months)[0];
-
- return badge?.badge_image?.src || `icons/badges/kick-subscriber.svg`;
-}
-
///////////////////////////////////
diff --git a/horizontal-chat/settings/settings.json b/horizontal-chat/settings/settings.json
index 7a21efc..bf48dea 100644
--- a/horizontal-chat/settings/settings.json
+++ b/horizontal-chat/settings/settings.json
@@ -231,14 +231,6 @@
"defaultValue": true,
"group": "Which YouTube messages do you want to see?"
},
- {
- "id": "kickUsername",
- "label": "Kick Username",
- "description": "",
- "type": "text",
- "defaultValue": "",
- "group": "Which Kick messages do you want to see?"
- },
{
"id": "showKickMessages",
"label": "Chat Messages",
@@ -247,6 +239,14 @@
"defaultValue": true,
"group": "Which Kick messages do you want to see?"
},
+ {
+ "id": "showKickFollows",
+ "label": "New Followers",
+ "description": "",
+ "type": "checkbox",
+ "defaultValue": false,
+ "group": "Which Kick messages do you want to see?"
+ },
{
"id": "showKickSubs",
"label": "New Subscribers",
diff --git a/multichat-overlay/script.js b/multichat-overlay/script.js
index 73e794a..bcaf0b8 100644
--- a/multichat-overlay/script.js
+++ b/multichat-overlay/script.js
@@ -55,9 +55,8 @@ const showTwitchRaids = GetBooleanParam("showTwitchRaids", true);
const showTwitchWatchStreaks = GetBooleanParam("showTwitchWatchStreaks", true);
const showTwitchSharedChat = GetIntParam("showTwitchSharedChat", 2);
-const kickUsername = urlParams.get("kickUsername") || "";
const showKickMessages = GetBooleanParam("showKickMessages", true);
-// const showKickFollows = GetBooleanParam("showKickFollows", false);
+const showKickFollows = GetBooleanParam("showKickFollows", false);
const showKickSubs = GetBooleanParam("showKickSubs", true);
const showKickChannelPointRedemptions = GetBooleanParam("showKickChannelPointRedemptions", true);
const showKickHosts = GetBooleanParam("showKickHosts", true);
@@ -92,6 +91,7 @@ const animationSpeed = GetIntParam("animationSpeed", 0.1);
const randomYouTubeColors = GetBooleanParam("randomYouTubeColors", false);
const youtubeColor = urlParams.get("youtubeColor") || "#f70000";
const youtubeCustomSubIcon = urlParams.get("youtubeCustomSubIcon") || "";
+let kickUsername = urlParams.get("kickUsername") || "";
@@ -268,6 +268,37 @@ client.on('YouTube.GiftMembershipReceived', (response) => {
YouTubeGiftMembershipReceived(response.data);
})
+client.on('Kick.ChatMessage', (response) => {
+ console.debug(response.data);
+ KickChatMessage(response.data);
+})
+
+client.on('Kick.Follow', (response) => {
+ console.debug(response.data);
+ KickFollow(response.data);
+})
+
+// // TODO: Seems to be missing data, using Pusher until this is fixed
+// client.on('Kick.Subscription', (response) => {
+// console.debug(response.data);
+// KickSubscription(response.data);
+// })
+
+// client.on('Kick.Resubscription', (response) => {
+// console.debug(response.data);
+// KickSubscription(response.data);
+// })
+
+// client.on('Kick.GiftSubscription', (response) => {
+// console.debug(response.data);
+// KickGiftedSubscriptions(response.data);
+// })
+
+// client.on('Kick.RewardRedemption', (response) => {
+// console.debug(response.data);
+// KickRewardRedeemed(response.data);
+// })
+
client.on('Streamlabs.Donation', (response) => {
console.debug(response.data);
StreamlabsDonation(response.data);
@@ -351,8 +382,17 @@ client.on('Fourthwall.GiftDrawEnded', (response) => {
// Connect and handle Pusher WebSocket
async function KickConnect() {
+ // If user has not manually set Kick username, try to grab if from Streamer.bot
if (!kickUsername)
- return;
+ {
+ // Fetch from Streamer.bot
+ const broadcasterInfo = await client.getBroadcaster();
+
+ if (broadcasterInfo.platforms.kick)
+ kickUsername = broadcasterInfo.platforms.kick.broadcasterLogin;
+ else
+ return;
+ }
// Channel to subscribe to (you'll need the correct channel name here)
const kickIds = await GetKickIds(kickUsername);
@@ -367,7 +407,7 @@ async function KickConnect() {
// Reconnect
websocket.onclose = function () {
console.log(`Reconnecting to ${kickUsername}...`);
- setTimeout(connectPusher, 5000);
+ setTimeout(KickConnect, 5000);
};
websocket.onopen = function () {
@@ -398,9 +438,9 @@ async function KickConnect() {
const eventArgs = JSON.parse(data.data);
const event = data.event.split('\\').pop();
switch (event) {
- case 'ChatMessageEvent':
- KickChatMessage(eventArgs);
- break;
+ // case 'ChatMessageEvent':
+ // KickChatMessage(eventArgs);
+ // break;
//// 'Follows' unsupported by pusher
// case 'FollowEvent':
// break;
@@ -617,7 +657,7 @@ async function TwitchChatMessage(data) {
}
// Set the message data
- let message = data.message.message;
+ let message = ConstructMessageFromParts(data.parts);
const messageColor = data.message.color;
const role = data.message.role;
@@ -627,7 +667,7 @@ async function TwitchChatMessage(data) {
// Set message text
if (showMessage) {
- messageDiv.innerText = message;
+ messageDiv.innerHTML = message;
}
// Set the "action" color
@@ -658,36 +698,6 @@ async function TwitchChatMessage(data) {
}
}
- // 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');
- messageDiv.innerHTML = messageDiv.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 = `
`;
- const bitsElements = `${bits}`
- messageDiv.innerHTML = messageDiv.innerHTML.replace(new RegExp(`\\b${name}${bits}\\b`, 'i'), cheerEmoteElement + bitsElements);
- }
-
// Render avatars
if (showAvatar) {
const username = data.message.username;
@@ -893,7 +903,7 @@ async function TwitchAnnouncement(data) {
else
content.querySelector("#username").innerText = `${data.user.name} (${data.user.login})`;
content.querySelector("#username").style.color = data.user.color;
- content.querySelector("#message").innerText = data.text;
+ content.querySelector("#message").innerHTML = ConstructMessageFromParts(data.parts);
// Remove the line break
content.querySelector("#colon-separator").style.display = `inline`;
@@ -921,28 +931,6 @@ async function TwitchAnnouncement(data) {
content.querySelector("#pronouns").innerText = pronouns;
}
- // Render emotes
- for (i in data.parts) {
- if (data.parts[i].type == `emote`) {
- const emoteElement = `
`;
- const emoteName = EscapeRegExp(data.parts[i].text);
-
- 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');
- content.querySelector("#message").innerHTML = content.querySelector("#message").innerHTML.replace(regex, emoteElement);
- }
- }
-
// Insert the modified template instance into the DOM
instance.querySelector("#content").appendChild(content);
@@ -1244,30 +1232,10 @@ async function TwitchWatchStreak(data) {
const displayName = data.displayName;
const watchStreak = data.watchStreak;
- const message = data.message;
+ const message = RenderMessageWithEmotesHTML(data.message, data.emotes);
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);
- }
+ contentDiv.innerHTML = message;
AddMessageItem(instance, data.messageId);
}
@@ -1381,14 +1349,18 @@ async function YouTubeMessage(data) {
else
usernameDiv.style.color = youtubeColor; // YouTube users do not have colors, so just set it to red
}
-
- if (showMessage) {
- messageDiv.innerText = data.message;
- }
-
+
+ // Set the message data
+ let message = RenderMessageWithEmotesHTML(data.message, data.emotes);
+
// Set furry mode
if (furryMode)
- messageDiv.innerText = TranslateToFurry(data.message);
+ message = TranslateToFurry(message);
+
+ // Set message text
+ if (showMessage) {
+ messageDiv.innerHTML = message;
+ }
// Remove the line break
if (inlineChat) {
@@ -1443,13 +1415,6 @@ async function YouTubeMessage(data) {
badgeListDiv.appendChild(badge);
}
- // Render emotes
- for (i in data.emotes) {
- const emoteElement = `
`;
- // messageDiv.innerHTML = messageDiv.innerHTML.replace(new RegExp(`\\b${data.emotes[i].name}\\b`), emoteElement);
- messageDiv.innerHTML = messageDiv.innerHTML.replace(data.emotes[i].name, emoteElement);
- }
-
// Render avatars
if (showAvatar) {
const avatar = new Image();
@@ -1483,7 +1448,6 @@ async function YouTubeMessage(data) {
}
// Embed image
- const message = data.message;
if (IsThisUserAllowedToPostImagesOrNotReturnTrueIfTheyCanReturnFalseIfTheyCannot(imageEmbedPermissionLevel, data, 'youtube') && IsImageUrl(message)) {
const image = new Image();
@@ -1573,6 +1537,43 @@ function YouTubeSuperSticker(data) {
const stickerURL = FindFirstImageUrl(data);
const stickerImage = `
`;
+ // I used Gemini for this shit so if it doesn't work, blame Google
+ function FindFirstImageUrl(jsonObject) {
+ if (typeof jsonObject !== 'object' || jsonObject === null) {
+ return null; // Handle invalid input
+ }
+
+ function iterate(obj) {
+ if (Array.isArray(obj)) {
+ for (const item of obj) {
+ const result = iterate(item);
+ if (result) {
+ return result;
+ }
+ }
+ return null;
+ }
+
+ for (const key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ if (key === 'imageUrl') {
+ return obj[key]; // Found it! Return the value.
+ }
+
+ if (typeof obj[key] === 'object' && obj[key] !== null) {
+ const result = iterate(obj[key]); // Recursive call for nested objects
+ if (result) {
+ return result; // Propagate the found value
+ }
+ }
+ }
+ }
+ return null; // Key not found in this level
+ }
+
+ return iterate(jsonObject);
+ }
+
avatarDiv.innerHTML = stickerImage;
titleDiv.innerHTML = `${user} sent a Super Sticker (${amount})`;
@@ -2249,11 +2250,11 @@ async function KickChatMessage(data) {
return;
// Don't post messages starting with "!"
- if (data.content.startsWith("!") && excludeCommands)
+ if (data.text.startsWith("!") && excludeCommands)
return;
// Don't post messages from users from the ignore list
- if (ignoreUserList.includes(data.sender.username.toLowerCase()))
+ if (ignoreUserList.includes(data.user.name.toLowerCase()))
return;
// Get a reference to the template
@@ -2298,10 +2299,10 @@ async function KickChatMessage(data) {
// }
// Set Reply Message
- const isReply = data.type == 'reply';
+ const isReply = data.isReply;
if (isReply && showMessage) {
- const replyUser = data.metadata.original_sender.username;
- const replyMsg = data.metadata.original_message.content;
+ const replyUser = data.reply.sender.name;
+ const replyMsg = data.reply.sender.content;
replyDiv.style.display = 'block';
replyUserDiv.innerText = replyUser;
@@ -2316,13 +2317,13 @@ async function KickChatMessage(data) {
// Set the username info
if (showUsername) {
- usernameDiv.innerText = data.sender.username;
- usernameDiv.style.color = data.sender.identity.color;
+ usernameDiv.innerText = data.user.name;
+ usernameDiv.style.color = data.user.color;
}
// Set the message data
- let message = data.content;
-
+ let message = ConstructMessageFromParts(data.parts);
+
// Highlight mentions
const mentionRgx = new RegExp(`(^|\\s)@${kickUsername}(\\s|$)`, 'i');
const mention = mentionRgx.test(message);
@@ -2335,7 +2336,7 @@ async function KickChatMessage(data) {
// Set message text
if (showMessage) {
- messageDiv.innerText = message;
+ messageDiv.innerHTML = message;
}
// Remove the line break
@@ -2354,28 +2355,38 @@ async function KickChatMessage(data) {
// Render badges
if (showBadges) {
badgeListDiv.innerHTML = "";
- for (i in data.sender.identity.badges) {
+ for (i in data.user.badges) {
const badge = new Image();
- badge.src = GetKickBadgeURL(data.sender.identity.badges[i]);
+ if (data.user.badges[i].imageUrl)
+ badge.src = data.user.badges[i].imageUrl;
+ else
+ badge.src = GetKickBadgeURL(data.user.badges[i]);
badge.classList.add("badge");
badgeListDiv.appendChild(badge);
}
}
+ function GetKickBadgeURL(data) {
+ switch (data.id) {
+ case 'subscriber':
+ return CalculateKickSubBadge(data.count);
+ default:
+ return `icons/badges/kick-${data.id}.svg`;
+ }
+ function CalculateKickSubBadge(months) {
+ if (!Array.isArray(kickSubBadges)) return null;
- // Render emotes
- function replaceEmotes(message) {
- const emoteRegex = /\[emote:(\d+):([^\]]+)\]/g;
+ // Filter for eligible badges, then get the one with the highest 'months'
+ const badge = kickSubBadges
+ .filter(b => b.months <= months)
+ .sort((a, b) => b.months - a.months)[0];
- return message.replace(emoteRegex, (_, id, name) => {
- const imgUrl = `https://files.kick.com/emotes/${id}/fullsize`;
- return `
`;
- });
+ return badge?.badge_image?.src || `icons/badges/kick-subscriber.svg`;
+ }
}
- messageDiv.innerHTML = replaceEmotes(message);
// Render avatars
if (showAvatar) {
- const username = data.sender.slug;
+ const username = data.user.login;
const avatarURL = await GetAvatar(username, 'kick');
const avatar = new Image();
avatar.src = avatarURL;
@@ -2389,7 +2400,7 @@ async function KickChatMessage(data) {
if (groupConsecutiveMessages && messageList.children.length > 0 && scrollDirection != 2) {
const lastPlatform = messageList.lastChild.dataset.platform;
const lastUserId = messageList.lastChild.dataset.userId;
- if (lastPlatform == "kick" && lastUserId == data.sender.id) {
+ if (lastPlatform == "kick" && lastUserId == data.user.id) {
userInfoDiv.style.display = "none";
avatarDiv.style.visibility = "hidden";
avatarDiv.style.height = "0px";
@@ -2406,7 +2417,7 @@ async function KickChatMessage(data) {
messageDiv.innerHTML = '';
messageDiv.appendChild(image);
- AddMessageItem(instance, data.id, 'kick', data.sender.id);
+ AddMessageItem(instance, data.messageId, 'kick', data.user.id);
};
const urlObj = new URL(message);
@@ -2416,7 +2427,7 @@ async function KickChatMessage(data) {
image.src = "https://external-content.duckduckgo.com/iu/?u=" + urlObj.toString();
}
else {
- AddMessageItem(instance, data.id, 'kick', data.sender.id);
+ AddMessageItem(instance, data.messageId, 'kick', data.user.id);
}
// Render YouTube links
@@ -2428,33 +2439,33 @@ async function KickChatMessage(data) {
}
}
-// async function KickFollow(data) {
-// if (!showKickFollows)
-// return;
+async function KickFollow(data) {
+ if (!showKickFollows)
+ return;
-// // Get a reference to the template
-// const template = document.getElementById('cardTemplate');
+ // Get a reference to the template
+ const template = document.getElementById('cardTemplate');
-// // Create a new instance of the template
-// const instance = template.content.cloneNode(true);
+ // 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("#contentDiv");
+ // 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("#contentDiv");
-// // Set the card background colors
-// cardDiv.classList.add('kick');
+ // Set the card background colors
+ cardDiv.classList.add('kick');
-// // Set the text
-// let username = data.user;
-// titleDiv.innerText = `${username} followed`;
+ // Set the text
+ let username = data.user.name;
+ titleDiv.innerText = `${username} followed`;
-// AddMessageItem(instance, data.messageId);
-// }
+ AddMessageItem(instance, data.user.id, 'kick', data.user.id);
+}
async function KickSubscription(data) {
if (!showKickSubs)
@@ -2604,7 +2615,7 @@ async function KickRewardRedeemed(data) {
// Set the text
const username = data.username;
const rewardName = data.reward_title;
- const userInput = data.user_input;
+ const userInput = data.userInput;
titleDiv.innerHTML = `${username} redeemed ${rewardName}`;
contentDiv.innerText = `${userInput}`;
@@ -3154,43 +3165,6 @@ function AddMessageItem(element, elementID, platform, userId) {
}, 200);
}
-// I used Gemini for this shit so if it doesn't work, blame Google
-function FindFirstImageUrl(jsonObject) {
- if (typeof jsonObject !== 'object' || jsonObject === null) {
- return null; // Handle invalid input
- }
-
- function iterate(obj) {
- if (Array.isArray(obj)) {
- for (const item of obj) {
- const result = iterate(item);
- if (result) {
- return result;
- }
- }
- return null;
- }
-
- for (const key in obj) {
- if (obj.hasOwnProperty(key)) {
- if (key === 'imageUrl') {
- return obj[key]; // Found it! Return the value.
- }
-
- if (typeof obj[key] === 'object' && obj[key] !== null) {
- const result = iterate(obj[key]); // Recursive call for nested objects
- if (result) {
- return result; // Propagate the found value
- }
- }
- }
- }
- return null; // Key not found in this level
- }
-
- return iterate(jsonObject);
-}
-
function IsThisUserAllowedToPostImagesOrNotReturnTrueIfTheyCanReturnFalseIfTheyCannot(targetPermissions, data, platform) {
return GetPermissionLevel(data, platform) >= targetPermissions;
}
@@ -3209,14 +3183,14 @@ function GetPermissionLevel(data, platform) {
else
return 10;
case 'kick':
- if (data.sender.identity.badges.some(item => item.type === 'broadcaster'))
+ if (data.user.badges.some(item => item.id === 'broadcaster'))
return 40;
- else if (data.sender.identity.badges.some(item => item.type === 'moderator'))
+ else if (data.user.badges.some(item => item.id === 'moderator'))
return 30;
- else if (data.sender.identity.badges.some(item => item.type === 'vip') ||
- data.sender.identity.badges.some(item => item.type === 'og'))
+ else if (data.user.badges.some(item => item.id === 'vip') ||
+ data.user.badges.some(item => item.type === 'og'))
return 20;
- else if (data.sender.identity.badges.some(item => item.type === 'subscriber'))
+ else if (data.user.badges.some(item => item.id === 'subscriber'))
return 15;
else
return 10;
@@ -3249,26 +3223,6 @@ function GetWinnersList(gifts) {
}
}
-function GetKickBadgeURL(data) {
- switch (data.type) {
- case 'subscriber':
- return CalculateKickSubBadge(data.count);
- default:
- return `icons/badges/kick-${data.type}.svg`;
- }
-}
-
-function CalculateKickSubBadge(months) {
- if (!Array.isArray(kickSubBadges)) return null;
-
- // Filter for eligible badges, then get the one with the highest 'months'
- const badge = kickSubBadges
- .filter(b => b.months <= months)
- .sort((a, b) => b.months - a.months)[0];
-
- return badge?.badge_image?.src || `icons/badges/kick-subscriber.svg`;
-}
-
///////////////////////////////////
diff --git a/multichat-overlay/settings/settings.json b/multichat-overlay/settings/settings.json
index 47a812b..68d2f89 100644
--- a/multichat-overlay/settings/settings.json
+++ b/multichat-overlay/settings/settings.json
@@ -352,14 +352,6 @@
"defaultValue": true,
"group": "Which YouTube messages do you want to see?"
},
- {
- "id": "kickUsername",
- "label": "Kick Username",
- "description": "",
- "type": "text",
- "defaultValue": "",
- "group": "Which Kick messages do you want to see?"
- },
{
"id": "showKickMessages",
"label": "Chat Messages",
@@ -368,6 +360,14 @@
"defaultValue": true,
"group": "Which Kick messages do you want to see?"
},
+ {
+ "id": "showKickFollows",
+ "label": "New Followers",
+ "description": "",
+ "type": "checkbox",
+ "defaultValue": false,
+ "group": "Which Kick messages do you want to see?"
+ },
{
"id": "showKickSubs",
"label": "New Subscribers",
diff --git a/multistream-alerts/script.js b/multistream-alerts/script.js
index e628056..0b22d8b 100644
--- a/multistream-alerts/script.js
+++ b/multistream-alerts/script.js
@@ -71,7 +71,8 @@ const showTwitchWatchStreaks = GetBooleanParam("showTwitchWatchStreaks", true);
const twitchWatchStreaksAction = urlParams.get("twitchWatchStreaksAction") || "";
// Which Kick alerts do you want to see?
-const kickUsername = urlParams.get("kickUsername") || "";
+const showKickFollows = GetBooleanParam("showKickFollows", false);
+const kickFollowAction = urlParams.get("kickFollowAction") || "";
const showKickSubs = GetBooleanParam("showKickSubs", true);
const kickSubAction = urlParams.get("kickSubAction") || "";
const showKickChannelPointRedemptions = GetBooleanParam("showKickChannelPointRedemptions", true);
@@ -110,6 +111,12 @@ const tipeeestreamDonationAction = urlParams.get("tipeeestreamDonationAction") |
const showFourthwallAlerts = GetBooleanParam("showFourthwallAlerts", false);
const fourthwallAlertAction = urlParams.get("fourthwallAlertAction") || "";
+////////////////////
+// HIDDEN OPTIONS //
+////////////////////
+
+let kickUsername = urlParams.get("kickUsername") || "";
+
// Set avatar visibility
if (!showAvatar) {
avatarElement.style.display = 'none';
@@ -316,8 +323,17 @@ client.on('Fourthwall.GiftDrawEnded', (response) => {
// Connect and handle Pusher WebSocket
async function KickConnect() {
+ // If user has not manually set Kick username, try to grab if from Streamer.bot
if (!kickUsername)
- return;
+ {
+ // Fetch from Streamer.bot
+ const broadcasterInfo = await client.getBroadcaster();
+
+ if (broadcasterInfo.platforms.kick)
+ kickUsername = broadcasterInfo.platforms.kick.broadcasterLogin;
+ else
+ return;
+ }
// Channel to subscribe to (you'll need the correct channel name here)
const kickIds = await GetKickIds(kickUsername);
@@ -332,7 +348,7 @@ async function KickConnect() {
// Reconnect
websocket.onclose = function () {
console.log(`Reconnecting to ${kickUsername}...`);
- setTimeout(connectPusher, 5000);
+ setTimeout(KickConnect, 5000);
};
websocket.onopen = function () {
@@ -482,41 +498,11 @@ async function TwitchCheer(data) {
// Set the text
const username = data.message.displayName;
const bits = data.bits;
- let message = data.message.message;
+ let message = ConstructMessageFromParts(data.parts);
// Render avatars
const avatarURL = await GetAvatar(username, 'twitch');
- // 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');
- message = message.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 = `
`;
- const bitsElements = `${bits}`
- message = message.replace(new RegExp(`\\b${name}${bits}\\b`, 'i'), cheerEmoteElement + bitsElements);
- }
-
UpdateAlertBox(
'twitch',
avatarURL,
@@ -1262,28 +1248,28 @@ function FourthwallGiftDrawEnded(data) {
);
}
-// async function KickFollow(data) {
-// if (!showKickFollows)
-// return;
+async function KickFollow(data) {
+ if (!showKickFollows)
+ return;
-// // Set the text
-// const username = data.user;
+ // Set the text
+ const username = data.user.name;
-// // Render avatars
-// const avatarURL = await GetAvatar(username, 'kick');
+ // Render avatars
+ const avatarURL = await GetAvatar(username, 'kick');
-// UpdateAlertBox(
-// 'kick',
-// avatarURL,
-// `${username}`,
-// `followed`,
-// '',
-// username,
-// '',
-// kickFollowAction,
-// data
-// );
-// }
+ UpdateAlertBox(
+ 'kick',
+ avatarURL,
+ `${username}`,
+ `followed`,
+ '',
+ username,
+ '',
+ kickFollowAction,
+ data
+ );
+}
async function KickSubscription(data) {
if (!showKickSubs)
diff --git a/multistream-alerts/settings/settings.json b/multistream-alerts/settings/settings.json
index b59bc74..61d4982 100644
--- a/multistream-alerts/settings/settings.json
+++ b/multistream-alerts/settings/settings.json
@@ -381,11 +381,20 @@
"group": "Which YouTube alerts do you want to see?"
},
{
- "id": "kickUsername",
- "label": "Kick Username",
+ "id": "showKickFollows",
+ "label": "New Followers",
"description": "",
- "type": "text",
+ "type": "checkbox",
+ "defaultValue": false,
+ "group": "Which Kick alerts do you want to see?"
+ },
+ {
+ "id": "KickFollowAction",
+ "label": "",
+ "description": "Also run this Streamer.bot Action",
+ "type": "sb-actions",
"defaultValue": "",
+ "showIf": "showKickFollows",
"group": "Which Kick alerts do you want to see?"
},
{
diff --git a/printer-bot/contents/script.js b/printer-bot/contents/script.js
index 945a62b..0354948 100644
--- a/printer-bot/contents/script.js
+++ b/printer-bot/contents/script.js
@@ -51,37 +51,7 @@ async function CustomEvent(data) {
subtitleEl.innerText = `${data.user}`;
const messageEl = document.createElement('div');
- messageEl.innerHTML = data.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');
- 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 = `
`;
- const bitsElements = `${bits}`
- messageEl.innerHTML = messageEl.innerHTML.replace(new RegExp(`\\b${name}${bits}\\b`, 'i'), cheerEmoteElement + bitsElements);
- }
+ messageEl.innerHTML = ConstructMessageFromParts(data.parts);
contentEl.appendChild(messageEl);
@@ -695,10 +665,6 @@ function IsValidUrl(string) {
}
}
-function EscapeRegExp(string) {
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
-}
-
function SetPlatformIcon(el, platform) {
// Set the platform icon
let baseURL = window.location.href;