mirror of
https://github.com/nuttylmao/nutty.gg.git
synced 2026-09-19 04:00:59 -04:00
+146
-80
@@ -12,39 +12,39 @@ const pronounMap = new Map();
|
|||||||
//////////////////////
|
//////////////////////
|
||||||
|
|
||||||
function GetBooleanParam(paramName, defaultValue) {
|
function GetBooleanParam(paramName, defaultValue) {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const paramValue = urlParams.get(paramName);
|
const paramValue = urlParams.get(paramName);
|
||||||
|
|
||||||
if (paramValue === null) {
|
if (paramValue === null) {
|
||||||
return defaultValue; // Parameter not found
|
return defaultValue; // Parameter not found
|
||||||
}
|
}
|
||||||
|
|
||||||
const lowercaseValue = paramValue.toLowerCase(); // Handle case-insensitivity
|
const lowercaseValue = paramValue.toLowerCase(); // Handle case-insensitivity
|
||||||
|
|
||||||
if (lowercaseValue === 'true') {
|
if (lowercaseValue === 'true') {
|
||||||
return true;
|
return true;
|
||||||
} else if (lowercaseValue === 'false') {
|
} else if (lowercaseValue === 'false') {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return paramValue; // Return original string if not 'true' or 'false'
|
return paramValue; // Return original string if not 'true' or 'false'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function GetIntParam(paramName, defaultValue) {
|
function GetIntParam(paramName, defaultValue) {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const paramValue = urlParams.get(paramName);
|
const paramValue = urlParams.get(paramName);
|
||||||
|
|
||||||
if (paramValue === null) {
|
if (paramValue === null) {
|
||||||
return defaultValue; // or undefined, or a default value, depending on your needs
|
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)) {
|
if (isNaN(intValue)) {
|
||||||
return null; // or handle the error in another way, e.g., throw an error
|
return null; // or handle the error in another way, e.g., throw an error
|
||||||
}
|
}
|
||||||
|
|
||||||
return intValue;
|
return intValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function GetKickIds(username) {
|
async function GetKickIds(username) {
|
||||||
@@ -143,82 +143,89 @@ async function GetAvatar(username, platform) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function GetPronouns(platform, username) {
|
async function GetPronouns(platform, username) {
|
||||||
if (pronounMap.has(username)) {
|
if (pronounMap.has(username)) {
|
||||||
console.debug(`Pronouns found for ${username}. Retrieving from hash map.`)
|
console.debug(`Pronouns found for ${username}. Retrieving from hash map.`)
|
||||||
return pronounMap.get(username);
|
return pronounMap.get(username);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.debug(`No pronouns found for ${username}. Retrieving from alejo.io.`)
|
console.debug(`No pronouns found for ${username}. Retrieving from alejo.io.`)
|
||||||
const response = await client.getUserPronouns(platform, username);
|
const response = await client.getUserPronouns(platform, username);
|
||||||
const userFound = response.pronoun.userFound;
|
const userFound = response.pronoun.userFound;
|
||||||
const pronouns = userFound ? `${response.pronoun.pronounSubject}/${response.pronoun.pronounObject}` : '';
|
const pronouns = userFound ? `${response.pronoun.pronounSubject}/${response.pronoun.pronounObject}` : '';
|
||||||
|
|
||||||
pronounMap.set(username, pronouns);
|
pronounMap.set(username, pronouns);
|
||||||
|
|
||||||
return pronouns;
|
return pronouns;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function GetCurrentTimeFormatted() {
|
function GetCurrentTimeFormatted() {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
let hours = now.getHours();
|
let hours = now.getHours();
|
||||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||||
const ampm = hours >= 12 ? 'PM' : 'AM';
|
const ampm = hours >= 12 ? 'PM' : 'AM';
|
||||||
|
|
||||||
hours = hours % 12;
|
hours = hours % 12;
|
||||||
hours = hours ? hours : 12; // the hour '0' should be '12'
|
hours = hours ? hours : 12; // the hour '0' should be '12'
|
||||||
|
|
||||||
const formattedTime = `${hours}:${minutes} ${ampm}`;
|
const formattedTime = `${hours}:${minutes} ${ampm}`;
|
||||||
return formattedTime;
|
return formattedTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DecodeHTMLString(html) {
|
function DecodeHTMLString(html) {
|
||||||
var txt = document.createElement("textarea");
|
var txt = document.createElement("textarea");
|
||||||
txt.innerHTML = html;
|
txt.innerHTML = html;
|
||||||
return txt.value;
|
return txt.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function TranslateToFurry(sentence) {
|
function TranslateToFurry(sentence) {
|
||||||
const words = sentence.toLowerCase().split(/\b/);
|
// Split on <img ...> tags, keeping them in the result
|
||||||
|
const parts = sentence.split(/(<img\b[^>]*>)/gi);
|
||||||
|
|
||||||
const furryWords = words.map(word => {
|
const furryParts = parts.map(part => {
|
||||||
if (/\w+/.test(word)) {
|
// If this part is an <img>, leave it unchanged
|
||||||
let newWord = word;
|
if (/^<img\b[^>]*>$/.test(part)) {
|
||||||
|
return part;
|
||||||
|
}
|
||||||
|
|
||||||
// Common substitutions
|
// Otherwise, apply furry translation
|
||||||
newWord = newWord.replace(/l/g, 'w');
|
const words = part.toLowerCase().split(/\b/);
|
||||||
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');
|
|
||||||
|
|
||||||
// Random additions
|
return words.map(word => {
|
||||||
if (Math.random() < 0.15) {
|
if (/\w+/.test(word)) {
|
||||||
newWord += ' nya~';
|
let newWord = word;
|
||||||
} else if (Math.random() < 0.1) {
|
|
||||||
newWord += ' >w<';
|
|
||||||
} else if (Math.random() < 0.05) {
|
|
||||||
newWord += ' owo';
|
|
||||||
}
|
|
||||||
|
|
||||||
return newWord;
|
// Common substitutions
|
||||||
}
|
newWord = newWord.replace(/l/g, 'w');
|
||||||
return word;
|
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 newWord;
|
||||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
}
|
||||||
|
return word;
|
||||||
|
}).join('');
|
||||||
|
});
|
||||||
|
|
||||||
|
return furryParts.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given a string, return a random hex code. The same input always results in the same output
|
// Given a string, return a random hex code. The same input always results in the same output
|
||||||
@@ -261,3 +268,62 @@ function RandomHex(str) {
|
|||||||
|
|
||||||
return hslToHex(hue, saturation, lightness);
|
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 = `<img src="${part.imageUrl}" alt="${part.text}" title="${part.text}" class="emote">`;
|
||||||
|
|
||||||
|
// Render the bits count
|
||||||
|
const bitLabel = `<span class="bits">${part.bits}</span>`;
|
||||||
|
|
||||||
|
return emoteImg + bitLabel;
|
||||||
|
default:
|
||||||
|
return `<img src="${part.imageUrl}" alt="${part.text}" title="${part.text}" class="emote">`;
|
||||||
|
}
|
||||||
|
}).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 += `<img src="${emote.imageUrl}" alt="${escapeHTML(emote.name)}" title="${escapeHTML(emote.name)}" class="emote">`;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
+119
-153
@@ -41,6 +41,7 @@ const ignoreChatters = urlParams.get("ignoreChatters") || "";
|
|||||||
const groupConsecutiveMessages = GetBooleanParam("groupConsecutiveMessages", true);
|
const groupConsecutiveMessages = GetBooleanParam("groupConsecutiveMessages", true);
|
||||||
|
|
||||||
const showTwitchMessages = GetBooleanParam("showTwitchMessages", true);
|
const showTwitchMessages = GetBooleanParam("showTwitchMessages", true);
|
||||||
|
const showTwitchCheers = GetBooleanParam("showTwitchCheers", false);
|
||||||
const showTwitchAnnouncements = GetBooleanParam("showTwitchAnnouncements", true);
|
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);
|
||||||
@@ -49,9 +50,8 @@ 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);
|
||||||
|
|
||||||
const kickUsername = urlParams.get("kickUsername") || "";
|
|
||||||
const showKickMessages = GetBooleanParam("showKickMessages", true);
|
const showKickMessages = GetBooleanParam("showKickMessages", true);
|
||||||
// const showKickFollows = GetBooleanParam("showKickFollows", false);
|
const showKickFollows = GetBooleanParam("showKickFollows", false);
|
||||||
const showKickSubs = GetBooleanParam("showKickSubs", true);
|
const showKickSubs = GetBooleanParam("showKickSubs", true);
|
||||||
const showKickChannelPointRedemptions = GetBooleanParam("showKickChannelPointRedemptions", true);
|
const showKickChannelPointRedemptions = GetBooleanParam("showKickChannelPointRedemptions", true);
|
||||||
const showKickHosts = GetBooleanParam("showKickHosts", true);
|
const showKickHosts = GetBooleanParam("showKickHosts", true);
|
||||||
@@ -85,6 +85,7 @@ 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") || "";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -139,7 +140,7 @@ client.on('Twitch.ChatMessage', (response) => {
|
|||||||
|
|
||||||
client.on('Twitch.Cheer', (response) => {
|
client.on('Twitch.Cheer', (response) => {
|
||||||
console.debug(response.data);
|
console.debug(response.data);
|
||||||
TwitchChatMessage(response.data);
|
TwitchCheer(response.data);
|
||||||
})
|
})
|
||||||
|
|
||||||
client.on('Twitch.Announcement', (response) => {
|
client.on('Twitch.Announcement', (response) => {
|
||||||
@@ -232,6 +233,37 @@ client.on('YouTube.GiftMembershipReceived', (response) => {
|
|||||||
YouTubeGiftMembershipReceived();
|
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) => {
|
client.on('Streamlabs.Donation', (response) => {
|
||||||
console.debug(response.data);
|
console.debug(response.data);
|
||||||
StreamlabsDonation(response.data);
|
StreamlabsDonation(response.data);
|
||||||
@@ -310,8 +342,17 @@ client.on('Fourthwall.GiftDrawEnded', (response) => {
|
|||||||
|
|
||||||
// Connect and handle Pusher WebSocket
|
// Connect and handle Pusher WebSocket
|
||||||
async function KickConnect() {
|
async function KickConnect() {
|
||||||
|
// If user has not manually set Kick username, try to grab if from Streamer.bot
|
||||||
if (!kickUsername)
|
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)
|
// Channel to subscribe to (you'll need the correct channel name here)
|
||||||
const kickIds = await GetKickIds(kickUsername);
|
const kickIds = await GetKickIds(kickUsername);
|
||||||
@@ -326,7 +367,7 @@ async function KickConnect() {
|
|||||||
// Reconnect
|
// Reconnect
|
||||||
websocket.onclose = function () {
|
websocket.onclose = function () {
|
||||||
console.log(`Reconnecting to ${kickUsername}...`);
|
console.log(`Reconnecting to ${kickUsername}...`);
|
||||||
setTimeout(connectPusher, 5000);
|
setTimeout(KickConnect, 5000);
|
||||||
};
|
};
|
||||||
|
|
||||||
websocket.onopen = function () {
|
websocket.onopen = function () {
|
||||||
@@ -357,9 +398,9 @@ async function KickConnect() {
|
|||||||
const eventArgs = JSON.parse(data.data);
|
const eventArgs = JSON.parse(data.data);
|
||||||
const event = data.event.split('\\').pop();
|
const event = data.event.split('\\').pop();
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case 'ChatMessageEvent':
|
// case 'ChatMessageEvent':
|
||||||
KickChatMessage(eventArgs);
|
// KickChatMessage(eventArgs);
|
||||||
break;
|
// break;
|
||||||
//// 'Follows' unsupported by pusher
|
//// 'Follows' unsupported by pusher
|
||||||
// case 'FollowEvent':
|
// case 'FollowEvent':
|
||||||
// break;
|
// break;
|
||||||
@@ -396,7 +437,6 @@ async function KickConnect() {
|
|||||||
window.addEventListener('load', KickConnect);
|
window.addEventListener('load', KickConnect);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////////
|
//////////////////////
|
||||||
// TIKFINITY CLIENT //
|
// TIKFINITY CLIENT //
|
||||||
//////////////////////
|
//////////////////////
|
||||||
@@ -521,7 +561,7 @@ async function TwitchChatMessage(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the message data
|
// Set the message data
|
||||||
let message = data.message.message;
|
let message = ConstructMessageFromParts(data.parts);
|
||||||
const messageColor = data.message.color;
|
const messageColor = data.message.color;
|
||||||
const role = data.message.role;
|
const role = data.message.role;
|
||||||
|
|
||||||
@@ -531,7 +571,7 @@ async function TwitchChatMessage(data) {
|
|||||||
|
|
||||||
// Set message text
|
// Set message text
|
||||||
if (showMessage) {
|
if (showMessage) {
|
||||||
messageDiv.innerText = message;
|
messageDiv.innerHTML = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the "action" color
|
// Set the "action" color
|
||||||
@@ -544,7 +584,6 @@ async function TwitchChatMessage(data) {
|
|||||||
platformDiv.innerHTML = platformElements;
|
platformDiv.innerHTML = platformElements;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Render badges
|
// Render badges
|
||||||
if (showBadges) {
|
if (showBadges) {
|
||||||
badgeListDiv.innerHTML = "";
|
badgeListDiv.innerHTML = "";
|
||||||
@@ -556,38 +595,6 @@ async function TwitchChatMessage(data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render emotes
|
|
||||||
for (i in data.emotes) {
|
|
||||||
const emoteElement = `<img src="${data.emotes[i].imageUrl}" class="emote"/>`;
|
|
||||||
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 = `<img src="${data.cheerEmotes[i].imageUrl}" class="emote"/>`;
|
|
||||||
// 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 = `<img src="${imageUrl}" class="emote"/>`;
|
|
||||||
const bitsElements = `<span class="bits">${bits}</span>`
|
|
||||||
messageDiv.innerHTML = messageDiv.innerHTML.replace(new RegExp(`\\b${name}${bits}\\b`, 'i'), cheerEmoteElement + bitsElements);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render avatars
|
// Render avatars
|
||||||
if (showAvatar) {
|
if (showAvatar) {
|
||||||
const username = data.message.username;
|
const username = data.message.username;
|
||||||
@@ -621,6 +628,22 @@ async function TwitchChatMessage(data) {
|
|||||||
AddMessageItem(instance, data.message.msgId, 'twitch', data.user.id);
|
AddMessageItem(instance, data.message.msgId, 'twitch', data.user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function TwitchCheer(data) {
|
||||||
|
TwitchChatMessage(data);
|
||||||
|
if (!showTwitchCheers)
|
||||||
|
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})`;
|
||||||
|
let bits = data.bits;
|
||||||
|
|
||||||
|
const message = `${username} cheered ${bits} bits`;
|
||||||
|
|
||||||
|
ShowAlert(message, 'twitch');
|
||||||
|
}
|
||||||
|
|
||||||
async function TwitchAnnouncement(data) {
|
async function TwitchAnnouncement(data) {
|
||||||
if (!showTwitchAnnouncements)
|
if (!showTwitchAnnouncements)
|
||||||
return;
|
return;
|
||||||
@@ -643,29 +666,7 @@ async function TwitchAnnouncement(data) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let message = data.text;
|
let message = ConstructMessageFromParts(data.parts);
|
||||||
|
|
||||||
// Render emotes
|
|
||||||
for (i in data.parts) {
|
|
||||||
if (data.parts[i].type == `emote`) {
|
|
||||||
const emoteElement = `<img src="${data.parts[i].imageUrl}" class="emote"/>`;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowAlert(message, background);
|
ShowAlert(message, background);
|
||||||
}
|
}
|
||||||
@@ -901,13 +902,17 @@ function YouTubeMessage(data) {
|
|||||||
usernameDiv.style.color = youtubeColor; // YouTube users do not have colors, so just set it to red
|
usernameDiv.style.color = youtubeColor; // YouTube users do not have colors, so just set it to red
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showMessage) {
|
// Set the message data
|
||||||
messageDiv.innerText = data.message;
|
let message = RenderMessageWithEmotesHTML(data.message, data.emotes);
|
||||||
}
|
|
||||||
|
|
||||||
// Set furry mode
|
// Set furry mode
|
||||||
if (furryMode)
|
if (furryMode)
|
||||||
messageDiv.innerText = TranslateToFurry(data.message);
|
message = TranslateToFurry(message);
|
||||||
|
|
||||||
|
// Set message text
|
||||||
|
if (showMessage) {
|
||||||
|
messageDiv.innerHTML = message;
|
||||||
|
}
|
||||||
|
|
||||||
// Render platform
|
// Render platform
|
||||||
if (showPlatform) {
|
if (showPlatform) {
|
||||||
@@ -955,12 +960,6 @@ function YouTubeMessage(data) {
|
|||||||
badgeListDiv.appendChild(badge);
|
badgeListDiv.appendChild(badge);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render emotes
|
|
||||||
for (i in data.emotes) {
|
|
||||||
const emoteElement = `<img src="${data.emotes[i].imageUrl}" class="emote"/>`;
|
|
||||||
messageDiv.innerHTML = messageDiv.innerHTML.replace(data.emotes[i].name, emoteElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render avatars
|
// Render avatars
|
||||||
if (showAvatar) {
|
if (showAvatar) {
|
||||||
const avatar = new Image();
|
const avatar = new Image();
|
||||||
@@ -1288,11 +1287,11 @@ async function KickChatMessage(data) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Don't post messages starting with "!"
|
// Don't post messages starting with "!"
|
||||||
if (data.content.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.sender.username.toLowerCase()))
|
if (ignoreUserList.includes(data.user.name.toLowerCase()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Get a reference to the template
|
// Get a reference to the template
|
||||||
@@ -1319,12 +1318,12 @@ async function KickChatMessage(data) {
|
|||||||
|
|
||||||
// Set the username info
|
// Set the username info
|
||||||
if (showUsername) {
|
if (showUsername) {
|
||||||
usernameDiv.innerText = data.sender.username;
|
usernameDiv.innerText = data.user.name;
|
||||||
usernameDiv.style.color = data.sender.identity.color;
|
usernameDiv.style.color = data.user.color;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the message data
|
// Set the message data
|
||||||
let message = data.content;
|
let message = ConstructMessageFromParts(data.parts);
|
||||||
|
|
||||||
// Set furry mode
|
// Set furry mode
|
||||||
if (furryMode)
|
if (furryMode)
|
||||||
@@ -1332,7 +1331,7 @@ async function KickChatMessage(data) {
|
|||||||
|
|
||||||
// Set message text
|
// Set message text
|
||||||
if (showMessage) {
|
if (showMessage) {
|
||||||
messageDiv.innerText = message;
|
messageDiv.innerHTML = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render platform
|
// Render platform
|
||||||
@@ -1344,28 +1343,38 @@ async function KickChatMessage(data) {
|
|||||||
// Render badges
|
// Render badges
|
||||||
if (showBadges) {
|
if (showBadges) {
|
||||||
badgeListDiv.innerHTML = "";
|
badgeListDiv.innerHTML = "";
|
||||||
for (i in data.sender.identity.badges) {
|
for (i in data.user.badges) {
|
||||||
const badge = new Image();
|
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");
|
badge.classList.add("badge");
|
||||||
badgeListDiv.appendChild(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
|
// Filter for eligible badges, then get the one with the highest 'months'
|
||||||
function replaceEmotes(message) {
|
const badge = kickSubBadges
|
||||||
const emoteRegex = /\[emote:(\d+):([^\]]+)\]/g;
|
.filter(b => b.months <= months)
|
||||||
|
.sort((a, b) => b.months - a.months)[0];
|
||||||
|
|
||||||
return message.replace(emoteRegex, (_, id, name) => {
|
return badge?.badge_image?.src || `icons/badges/kick-subscriber.svg`;
|
||||||
const imgUrl = `https://files.kick.com/emotes/${id}/fullsize`;
|
}
|
||||||
return `<img src="${imgUrl}" alt="${name}" title="${name}" class="emote" />`;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
messageDiv.innerHTML = replaceEmotes(message);
|
|
||||||
|
|
||||||
// Render avatars
|
// Render avatars
|
||||||
if (showAvatar) {
|
if (showAvatar) {
|
||||||
const username = data.sender.slug;
|
const username = data.user.login;
|
||||||
const avatarURL = await GetAvatar(username, 'kick');
|
const avatarURL = await GetAvatar(username, 'kick');
|
||||||
const avatar = new Image();
|
const avatar = new Image();
|
||||||
avatar.src = avatarURL;
|
avatar.src = avatarURL;
|
||||||
@@ -1382,7 +1391,21 @@ async function KickChatMessage(data) {
|
|||||||
userInfoDiv.style.display = "none";
|
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) {
|
function KickSubscription(data) {
|
||||||
@@ -1391,7 +1414,7 @@ function KickSubscription(data) {
|
|||||||
|
|
||||||
// Set the text
|
// Set the text
|
||||||
const username = data.username;
|
const username = data.username;
|
||||||
const months = data.months;
|
const months = data.months
|
||||||
|
|
||||||
let message = '';
|
let message = '';
|
||||||
if (months <= 1)
|
if (months <= 1)
|
||||||
@@ -1693,43 +1716,6 @@ function AddMessageItem(element, elementID, platform, userId) {
|
|||||||
}, 200);
|
}, 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) {
|
function ShowAlert(message, background = null, duration = animationDuration) {
|
||||||
|
|
||||||
// Check if the widget is in the middle of an animation
|
// Check if the widget is in the middle of an animation
|
||||||
@@ -1793,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`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////
|
///////////////////////////////////
|
||||||
|
|||||||
@@ -135,6 +135,14 @@
|
|||||||
"defaultValue": true,
|
"defaultValue": true,
|
||||||
"group": "Which Twitch messages do you want to see?"
|
"group": "Which Twitch messages do you want to see?"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "showTwitchCheers",
|
||||||
|
"label": "Cheers",
|
||||||
|
"description": "",
|
||||||
|
"type": "checkbox",
|
||||||
|
"defaultValue": false,
|
||||||
|
"group": "Which Twitch messages do you want to see?"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "showTwitchAnnouncements",
|
"id": "showTwitchAnnouncements",
|
||||||
"label": "Announcements",
|
"label": "Announcements",
|
||||||
@@ -223,14 +231,6 @@
|
|||||||
"defaultValue": true,
|
"defaultValue": true,
|
||||||
"group": "Which YouTube messages do you want to see?"
|
"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",
|
"id": "showKickMessages",
|
||||||
"label": "Chat Messages",
|
"label": "Chat Messages",
|
||||||
@@ -239,6 +239,14 @@
|
|||||||
"defaultValue": true,
|
"defaultValue": true,
|
||||||
"group": "Which Kick messages do you want to see?"
|
"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",
|
"id": "showKickSubs",
|
||||||
"label": "New Subscribers",
|
"label": "New Subscribers",
|
||||||
|
|||||||
+203
-204
@@ -46,6 +46,7 @@ const imageEmbedPermissionLevel = GetIntParam("imageEmbedPermissionLevel", 20);
|
|||||||
const showYouTubeLinkPreviews = GetBooleanParam("showYouTubeLinkPreviews", true);
|
const showYouTubeLinkPreviews = GetBooleanParam("showYouTubeLinkPreviews", true);
|
||||||
|
|
||||||
const showTwitchMessages = GetBooleanParam("showTwitchMessages", true);
|
const showTwitchMessages = GetBooleanParam("showTwitchMessages", true);
|
||||||
|
const showTwitchCheers = GetBooleanParam("showTwitchCheers", false);
|
||||||
const showTwitchAnnouncements = GetBooleanParam("showTwitchAnnouncements", true);
|
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);
|
||||||
@@ -54,9 +55,8 @@ 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);
|
||||||
|
|
||||||
const kickUsername = urlParams.get("kickUsername") || "";
|
|
||||||
const showKickMessages = GetBooleanParam("showKickMessages", true);
|
const showKickMessages = GetBooleanParam("showKickMessages", true);
|
||||||
// const showKickFollows = GetBooleanParam("showKickFollows", false);
|
const showKickFollows = GetBooleanParam("showKickFollows", false);
|
||||||
const showKickSubs = GetBooleanParam("showKickSubs", true);
|
const showKickSubs = GetBooleanParam("showKickSubs", true);
|
||||||
const showKickChannelPointRedemptions = GetBooleanParam("showKickChannelPointRedemptions", true);
|
const showKickChannelPointRedemptions = GetBooleanParam("showKickChannelPointRedemptions", true);
|
||||||
const showKickHosts = GetBooleanParam("showKickHosts", true);
|
const showKickHosts = GetBooleanParam("showKickHosts", true);
|
||||||
@@ -91,6 +91,7 @@ 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 kickUsername = urlParams.get("kickUsername") || "";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -138,6 +139,7 @@ document.documentElement.style.setProperty('--animation-speed', `${animationSpee
|
|||||||
const client = new StreamerbotClient({
|
const client = new StreamerbotClient({
|
||||||
host: sbServerAddress,
|
host: sbServerAddress,
|
||||||
port: sbServerPort,
|
port: sbServerPort,
|
||||||
|
scheme: 'ws',
|
||||||
|
|
||||||
onConnect: (data) => {
|
onConnect: (data) => {
|
||||||
console.log(`Streamer.bot successfully connected to ${sbServerAddress}:${sbServerPort}`)
|
console.log(`Streamer.bot successfully connected to ${sbServerAddress}:${sbServerPort}`)
|
||||||
@@ -158,7 +160,7 @@ client.on('Twitch.ChatMessage', (response) => {
|
|||||||
|
|
||||||
client.on('Twitch.Cheer', (response) => {
|
client.on('Twitch.Cheer', (response) => {
|
||||||
console.debug(response.data);
|
console.debug(response.data);
|
||||||
TwitchChatMessage(response.data);
|
TwitchCheer(response.data);
|
||||||
})
|
})
|
||||||
|
|
||||||
client.on('Twitch.AutomaticRewardRedemption', (response) => {
|
client.on('Twitch.AutomaticRewardRedemption', (response) => {
|
||||||
@@ -266,6 +268,37 @@ client.on('YouTube.GiftMembershipReceived', (response) => {
|
|||||||
YouTubeGiftMembershipReceived(response.data);
|
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) => {
|
client.on('Streamlabs.Donation', (response) => {
|
||||||
console.debug(response.data);
|
console.debug(response.data);
|
||||||
StreamlabsDonation(response.data);
|
StreamlabsDonation(response.data);
|
||||||
@@ -349,8 +382,17 @@ client.on('Fourthwall.GiftDrawEnded', (response) => {
|
|||||||
|
|
||||||
// Connect and handle Pusher WebSocket
|
// Connect and handle Pusher WebSocket
|
||||||
async function KickConnect() {
|
async function KickConnect() {
|
||||||
|
// If user has not manually set Kick username, try to grab if from Streamer.bot
|
||||||
if (!kickUsername)
|
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)
|
// Channel to subscribe to (you'll need the correct channel name here)
|
||||||
const kickIds = await GetKickIds(kickUsername);
|
const kickIds = await GetKickIds(kickUsername);
|
||||||
@@ -365,7 +407,7 @@ async function KickConnect() {
|
|||||||
// Reconnect
|
// Reconnect
|
||||||
websocket.onclose = function () {
|
websocket.onclose = function () {
|
||||||
console.log(`Reconnecting to ${kickUsername}...`);
|
console.log(`Reconnecting to ${kickUsername}...`);
|
||||||
setTimeout(connectPusher, 5000);
|
setTimeout(KickConnect, 5000);
|
||||||
};
|
};
|
||||||
|
|
||||||
websocket.onopen = function () {
|
websocket.onopen = function () {
|
||||||
@@ -396,9 +438,9 @@ async function KickConnect() {
|
|||||||
const eventArgs = JSON.parse(data.data);
|
const eventArgs = JSON.parse(data.data);
|
||||||
const event = data.event.split('\\').pop();
|
const event = data.event.split('\\').pop();
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case 'ChatMessageEvent':
|
// case 'ChatMessageEvent':
|
||||||
KickChatMessage(eventArgs);
|
// KickChatMessage(eventArgs);
|
||||||
break;
|
// break;
|
||||||
//// 'Follows' unsupported by pusher
|
//// 'Follows' unsupported by pusher
|
||||||
// case 'FollowEvent':
|
// case 'FollowEvent':
|
||||||
// break;
|
// break;
|
||||||
@@ -615,7 +657,7 @@ async function TwitchChatMessage(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the message data
|
// Set the message data
|
||||||
let message = data.message.message;
|
let message = ConstructMessageFromParts(data.parts);
|
||||||
const messageColor = data.message.color;
|
const messageColor = data.message.color;
|
||||||
const role = data.message.role;
|
const role = data.message.role;
|
||||||
|
|
||||||
@@ -625,7 +667,7 @@ async function TwitchChatMessage(data) {
|
|||||||
|
|
||||||
// Set message text
|
// Set message text
|
||||||
if (showMessage) {
|
if (showMessage) {
|
||||||
messageDiv.innerText = message;
|
messageDiv.innerHTML = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the "action" color
|
// Set the "action" color
|
||||||
@@ -656,36 +698,6 @@ async function TwitchChatMessage(data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render emotes
|
|
||||||
for (i in data.emotes) {
|
|
||||||
const emoteElement = `<img src="${data.emotes[i].imageUrl}" class="emote"/>`;
|
|
||||||
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 = `<img src="${imageUrl}" class="emote"/>`;
|
|
||||||
const bitsElements = `<span class="bits">${bits}</span>`
|
|
||||||
messageDiv.innerHTML = messageDiv.innerHTML.replace(new RegExp(`\\b${name}${bits}\\b`, 'i'), cheerEmoteElement + bitsElements);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render avatars
|
// Render avatars
|
||||||
if (showAvatar) {
|
if (showAvatar) {
|
||||||
const username = data.message.username;
|
const username = data.message.username;
|
||||||
@@ -752,6 +764,49 @@ async function TwitchChatMessage(data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function TwitchCheer(data) {
|
||||||
|
TwitchChatMessage(data);
|
||||||
|
if (!showTwitchCheers)
|
||||||
|
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("#contentDiv");
|
||||||
|
|
||||||
|
// Set the card background colors
|
||||||
|
cardDiv.classList.add('twitch');
|
||||||
|
|
||||||
|
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})`;
|
||||||
|
let bits = data.bits;
|
||||||
|
|
||||||
|
titleDiv.innerText = `${username} cheered ${bits} bits`;
|
||||||
|
|
||||||
|
AddMessageItem(instance, data.messageId);
|
||||||
|
}
|
||||||
|
|
||||||
async function TwitchAutomaticRewardRedemption(data) {
|
async function TwitchAutomaticRewardRedemption(data) {
|
||||||
// Get a reference to the template
|
// Get a reference to the template
|
||||||
const template = document.getElementById('messageTemplate');
|
const template = document.getElementById('messageTemplate');
|
||||||
@@ -848,7 +903,7 @@ async function TwitchAnnouncement(data) {
|
|||||||
else
|
else
|
||||||
content.querySelector("#username").innerText = `${data.user.name} (${data.user.login})`;
|
content.querySelector("#username").innerText = `${data.user.name} (${data.user.login})`;
|
||||||
content.querySelector("#username").style.color = data.user.color;
|
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
|
// Remove the line break
|
||||||
content.querySelector("#colon-separator").style.display = `inline`;
|
content.querySelector("#colon-separator").style.display = `inline`;
|
||||||
@@ -876,28 +931,6 @@ async function TwitchAnnouncement(data) {
|
|||||||
content.querySelector("#pronouns").innerText = pronouns;
|
content.querySelector("#pronouns").innerText = pronouns;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render emotes
|
|
||||||
for (i in data.parts) {
|
|
||||||
if (data.parts[i].type == `emote`) {
|
|
||||||
const emoteElement = `<img src="${data.parts[i].imageUrl}" class="emote"/>`;
|
|
||||||
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
|
// Insert the modified template instance into the DOM
|
||||||
instance.querySelector("#content").appendChild(content);
|
instance.querySelector("#content").appendChild(content);
|
||||||
|
|
||||||
@@ -1199,30 +1232,10 @@ async function TwitchWatchStreak(data) {
|
|||||||
|
|
||||||
const displayName = data.displayName;
|
const displayName = data.displayName;
|
||||||
const watchStreak = data.watchStreak;
|
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! `;
|
titleDiv.innerText = `${displayName} is currently on a ${watchStreak} stream streak! `;
|
||||||
contentDiv.innerText = `${message}`;
|
contentDiv.innerHTML = message;
|
||||||
|
|
||||||
// Render emotes
|
|
||||||
for (i in data.emotes) {
|
|
||||||
const emoteElement = `<img src="${data.emotes[i].imageUrl}" class="emote"/>`;
|
|
||||||
const emoteName = EscapeRegExp(data.emotes[i].name);
|
|
||||||
|
|
||||||
let regexPattern = emoteName;
|
|
||||||
|
|
||||||
// Check if the emote name consists only of word characters (alphanumeric and underscore)
|
|
||||||
if (/^\w+$/.test(emoteName)) {
|
|
||||||
regexPattern = `\\b${emoteName}\\b`;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// For non-word emotes, ensure they are surrounded by non-word characters or boundaries
|
|
||||||
regexPattern = `(?<=^|[^\\w])${emoteName}(?=$|[^\\w])`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const regex = new RegExp(regexPattern, 'g');
|
|
||||||
contentDiv.innerHTML = contentDiv.innerHTML.replace(regex, emoteElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
AddMessageItem(instance, data.messageId);
|
AddMessageItem(instance, data.messageId);
|
||||||
}
|
}
|
||||||
@@ -1337,13 +1350,17 @@ async function YouTubeMessage(data) {
|
|||||||
usernameDiv.style.color = youtubeColor; // YouTube users do not have colors, so just set it to red
|
usernameDiv.style.color = youtubeColor; // YouTube users do not have colors, so just set it to red
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showMessage) {
|
// Set the message data
|
||||||
messageDiv.innerText = data.message;
|
let message = RenderMessageWithEmotesHTML(data.message, data.emotes);
|
||||||
}
|
|
||||||
|
|
||||||
// Set furry mode
|
// Set furry mode
|
||||||
if (furryMode)
|
if (furryMode)
|
||||||
messageDiv.innerText = TranslateToFurry(data.message);
|
message = TranslateToFurry(message);
|
||||||
|
|
||||||
|
// Set message text
|
||||||
|
if (showMessage) {
|
||||||
|
messageDiv.innerHTML = message;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove the line break
|
// Remove the line break
|
||||||
if (inlineChat) {
|
if (inlineChat) {
|
||||||
@@ -1398,13 +1415,6 @@ async function YouTubeMessage(data) {
|
|||||||
badgeListDiv.appendChild(badge);
|
badgeListDiv.appendChild(badge);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render emotes
|
|
||||||
for (i in data.emotes) {
|
|
||||||
const emoteElement = `<img src="${data.emotes[i].imageUrl}" class="emote"/>`;
|
|
||||||
// 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
|
// Render avatars
|
||||||
if (showAvatar) {
|
if (showAvatar) {
|
||||||
const avatar = new Image();
|
const avatar = new Image();
|
||||||
@@ -1438,7 +1448,6 @@ async function YouTubeMessage(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Embed image
|
// Embed image
|
||||||
const message = data.message;
|
|
||||||
if (IsThisUserAllowedToPostImagesOrNotReturnTrueIfTheyCanReturnFalseIfTheyCannot(imageEmbedPermissionLevel, data, 'youtube') && IsImageUrl(message)) {
|
if (IsThisUserAllowedToPostImagesOrNotReturnTrueIfTheyCanReturnFalseIfTheyCannot(imageEmbedPermissionLevel, data, 'youtube') && IsImageUrl(message)) {
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
|
|
||||||
@@ -1528,6 +1537,43 @@ function YouTubeSuperSticker(data) {
|
|||||||
const stickerURL = FindFirstImageUrl(data);
|
const stickerURL = FindFirstImageUrl(data);
|
||||||
const stickerImage = `<img src="${stickerURL}" class="youtube-super-sticker"/>`;
|
const stickerImage = `<img src="${stickerURL}" class="youtube-super-sticker"/>`;
|
||||||
|
|
||||||
|
// 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;
|
avatarDiv.innerHTML = stickerImage;
|
||||||
titleDiv.innerHTML = `${user} sent a Super Sticker (${amount})`;
|
titleDiv.innerHTML = `${user} sent a Super Sticker (${amount})`;
|
||||||
|
|
||||||
@@ -2204,11 +2250,11 @@ async function KickChatMessage(data) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Don't post messages starting with "!"
|
// Don't post messages starting with "!"
|
||||||
if (data.content.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.sender.username.toLowerCase()))
|
if (ignoreUserList.includes(data.user.name.toLowerCase()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Get a reference to the template
|
// Get a reference to the template
|
||||||
@@ -2253,10 +2299,10 @@ async function KickChatMessage(data) {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
// Set Reply Message
|
// Set Reply Message
|
||||||
const isReply = data.type == 'reply';
|
const isReply = data.isReply;
|
||||||
if (isReply && showMessage) {
|
if (isReply && showMessage) {
|
||||||
const replyUser = data.metadata.original_sender.username;
|
const replyUser = data.reply.sender.name;
|
||||||
const replyMsg = data.metadata.original_message.content;
|
const replyMsg = data.reply.sender.content;
|
||||||
|
|
||||||
replyDiv.style.display = 'block';
|
replyDiv.style.display = 'block';
|
||||||
replyUserDiv.innerText = replyUser;
|
replyUserDiv.innerText = replyUser;
|
||||||
@@ -2271,12 +2317,12 @@ async function KickChatMessage(data) {
|
|||||||
|
|
||||||
// Set the username info
|
// Set the username info
|
||||||
if (showUsername) {
|
if (showUsername) {
|
||||||
usernameDiv.innerText = data.sender.username;
|
usernameDiv.innerText = data.user.name;
|
||||||
usernameDiv.style.color = data.sender.identity.color;
|
usernameDiv.style.color = data.user.color;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the message data
|
// Set the message data
|
||||||
let message = data.content;
|
let message = ConstructMessageFromParts(data.parts);
|
||||||
|
|
||||||
// Highlight mentions
|
// Highlight mentions
|
||||||
const mentionRgx = new RegExp(`(^|\\s)@${kickUsername}(\\s|$)`, 'i');
|
const mentionRgx = new RegExp(`(^|\\s)@${kickUsername}(\\s|$)`, 'i');
|
||||||
@@ -2290,7 +2336,7 @@ async function KickChatMessage(data) {
|
|||||||
|
|
||||||
// Set message text
|
// Set message text
|
||||||
if (showMessage) {
|
if (showMessage) {
|
||||||
messageDiv.innerText = message;
|
messageDiv.innerHTML = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the line break
|
// Remove the line break
|
||||||
@@ -2309,28 +2355,38 @@ async function KickChatMessage(data) {
|
|||||||
// Render badges
|
// Render badges
|
||||||
if (showBadges) {
|
if (showBadges) {
|
||||||
badgeListDiv.innerHTML = "";
|
badgeListDiv.innerHTML = "";
|
||||||
for (i in data.sender.identity.badges) {
|
for (i in data.user.badges) {
|
||||||
const badge = new Image();
|
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");
|
badge.classList.add("badge");
|
||||||
badgeListDiv.appendChild(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
|
// Filter for eligible badges, then get the one with the highest 'months'
|
||||||
function replaceEmotes(message) {
|
const badge = kickSubBadges
|
||||||
const emoteRegex = /\[emote:(\d+):([^\]]+)\]/g;
|
.filter(b => b.months <= months)
|
||||||
|
.sort((a, b) => b.months - a.months)[0];
|
||||||
|
|
||||||
return message.replace(emoteRegex, (_, id, name) => {
|
return badge?.badge_image?.src || `icons/badges/kick-subscriber.svg`;
|
||||||
const imgUrl = `https://files.kick.com/emotes/${id}/fullsize`;
|
}
|
||||||
return `<img src="${imgUrl}" alt="${name}" class="emote" />`;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
messageDiv.innerHTML = replaceEmotes(message);
|
|
||||||
|
|
||||||
// Render avatars
|
// Render avatars
|
||||||
if (showAvatar) {
|
if (showAvatar) {
|
||||||
const username = data.sender.slug;
|
const username = data.user.login;
|
||||||
const avatarURL = await GetAvatar(username, 'kick');
|
const avatarURL = await GetAvatar(username, 'kick');
|
||||||
const avatar = new Image();
|
const avatar = new Image();
|
||||||
avatar.src = avatarURL;
|
avatar.src = avatarURL;
|
||||||
@@ -2344,7 +2400,7 @@ async function KickChatMessage(data) {
|
|||||||
if (groupConsecutiveMessages && messageList.children.length > 0 && scrollDirection != 2) {
|
if (groupConsecutiveMessages && messageList.children.length > 0 && scrollDirection != 2) {
|
||||||
const lastPlatform = messageList.lastChild.dataset.platform;
|
const lastPlatform = messageList.lastChild.dataset.platform;
|
||||||
const lastUserId = messageList.lastChild.dataset.userId;
|
const lastUserId = messageList.lastChild.dataset.userId;
|
||||||
if (lastPlatform == "kick" && lastUserId == data.sender.id) {
|
if (lastPlatform == "kick" && lastUserId == data.user.id) {
|
||||||
userInfoDiv.style.display = "none";
|
userInfoDiv.style.display = "none";
|
||||||
avatarDiv.style.visibility = "hidden";
|
avatarDiv.style.visibility = "hidden";
|
||||||
avatarDiv.style.height = "0px";
|
avatarDiv.style.height = "0px";
|
||||||
@@ -2361,7 +2417,7 @@ async function KickChatMessage(data) {
|
|||||||
messageDiv.innerHTML = '';
|
messageDiv.innerHTML = '';
|
||||||
messageDiv.appendChild(image);
|
messageDiv.appendChild(image);
|
||||||
|
|
||||||
AddMessageItem(instance, data.id, 'kick', data.sender.id);
|
AddMessageItem(instance, data.messageId, 'kick', data.user.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const urlObj = new URL(message);
|
const urlObj = new URL(message);
|
||||||
@@ -2371,7 +2427,7 @@ async function KickChatMessage(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.id, 'kick', data.sender.id);
|
AddMessageItem(instance, data.messageId, 'kick', data.user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render YouTube links
|
// Render YouTube links
|
||||||
@@ -2383,33 +2439,33 @@ async function KickChatMessage(data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// async function KickFollow(data) {
|
async function KickFollow(data) {
|
||||||
// if (!showKickFollows)
|
if (!showKickFollows)
|
||||||
// return;
|
return;
|
||||||
|
|
||||||
// // Get a reference to the template
|
// Get a reference to the template
|
||||||
// const template = document.getElementById('cardTemplate');
|
const template = document.getElementById('cardTemplate');
|
||||||
|
|
||||||
// // Create a new instance of the template
|
// Create a new instance of the template
|
||||||
// const instance = template.content.cloneNode(true);
|
const instance = template.content.cloneNode(true);
|
||||||
|
|
||||||
// // Get divs
|
// Get divs
|
||||||
// const cardDiv = instance.querySelector("#card");
|
const cardDiv = instance.querySelector("#card");
|
||||||
// const headerDiv = instance.querySelector("#header");
|
const headerDiv = instance.querySelector("#header");
|
||||||
// const avatarDiv = instance.querySelector("#avatar");
|
const avatarDiv = instance.querySelector("#avatar");
|
||||||
// const iconDiv = instance.querySelector("#icon");
|
const iconDiv = instance.querySelector("#icon");
|
||||||
// const titleDiv = instance.querySelector("#title");
|
const titleDiv = instance.querySelector("#title");
|
||||||
// const contentDiv = instance.querySelector("#contentDiv");
|
const contentDiv = instance.querySelector("#contentDiv");
|
||||||
|
|
||||||
// // Set the card background colors
|
// Set the card background colors
|
||||||
// cardDiv.classList.add('kick');
|
cardDiv.classList.add('kick');
|
||||||
|
|
||||||
// // Set the text
|
// Set the text
|
||||||
// let username = data.user;
|
let username = data.user.name;
|
||||||
// titleDiv.innerText = `${username} followed`;
|
titleDiv.innerText = `${username} followed`;
|
||||||
|
|
||||||
// AddMessageItem(instance, data.messageId);
|
AddMessageItem(instance, data.user.id, 'kick', data.user.id);
|
||||||
// }
|
}
|
||||||
|
|
||||||
async function KickSubscription(data) {
|
async function KickSubscription(data) {
|
||||||
if (!showKickSubs)
|
if (!showKickSubs)
|
||||||
@@ -2559,7 +2615,7 @@ async function KickRewardRedeemed(data) {
|
|||||||
// Set the text
|
// Set the text
|
||||||
const username = data.username;
|
const username = data.username;
|
||||||
const rewardName = data.reward_title;
|
const rewardName = data.reward_title;
|
||||||
const userInput = data.user_input;
|
const userInput = data.userInput;
|
||||||
|
|
||||||
titleDiv.innerHTML = `${username} redeemed ${rewardName}`;
|
titleDiv.innerHTML = `${username} redeemed ${rewardName}`;
|
||||||
contentDiv.innerText = `${userInput}`;
|
contentDiv.innerText = `${userInput}`;
|
||||||
@@ -3109,43 +3165,6 @@ function AddMessageItem(element, elementID, platform, userId) {
|
|||||||
}, 200);
|
}, 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) {
|
function IsThisUserAllowedToPostImagesOrNotReturnTrueIfTheyCanReturnFalseIfTheyCannot(targetPermissions, data, platform) {
|
||||||
return GetPermissionLevel(data, platform) >= targetPermissions;
|
return GetPermissionLevel(data, platform) >= targetPermissions;
|
||||||
}
|
}
|
||||||
@@ -3164,14 +3183,14 @@ function GetPermissionLevel(data, platform) {
|
|||||||
else
|
else
|
||||||
return 10;
|
return 10;
|
||||||
case 'kick':
|
case 'kick':
|
||||||
if (data.sender.identity.badges.some(item => item.type === 'broadcaster'))
|
if (data.user.badges.some(item => item.id === 'broadcaster'))
|
||||||
return 40;
|
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;
|
return 30;
|
||||||
else if (data.sender.identity.badges.some(item => item.type === 'vip') ||
|
else if (data.user.badges.some(item => item.id === 'vip') ||
|
||||||
data.sender.identity.badges.some(item => item.type === 'og'))
|
data.user.badges.some(item => item.type === 'og'))
|
||||||
return 20;
|
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;
|
return 15;
|
||||||
else
|
else
|
||||||
return 10;
|
return 10;
|
||||||
@@ -3204,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`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////
|
///////////////////////////////////
|
||||||
|
|||||||
@@ -242,6 +242,14 @@
|
|||||||
"defaultValue": true,
|
"defaultValue": true,
|
||||||
"group": "Which Twitch messages do you want to see?"
|
"group": "Which Twitch messages do you want to see?"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "showTwitchCheers",
|
||||||
|
"label": "Cheers",
|
||||||
|
"description": "",
|
||||||
|
"type": "checkbox",
|
||||||
|
"defaultValue": false,
|
||||||
|
"group": "Which Twitch messages do you want to see?"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "showTwitchAnnouncements",
|
"id": "showTwitchAnnouncements",
|
||||||
"label": "Announcements",
|
"label": "Announcements",
|
||||||
@@ -344,14 +352,6 @@
|
|||||||
"defaultValue": true,
|
"defaultValue": true,
|
||||||
"group": "Which YouTube messages do you want to see?"
|
"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",
|
"id": "showKickMessages",
|
||||||
"label": "Chat Messages",
|
"label": "Chat Messages",
|
||||||
@@ -360,6 +360,14 @@
|
|||||||
"defaultValue": true,
|
"defaultValue": true,
|
||||||
"group": "Which Kick messages do you want to see?"
|
"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",
|
"id": "showKickSubs",
|
||||||
"label": "New Subscribers",
|
"label": "New Subscribers",
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ const showTwitchWatchStreaks = GetBooleanParam("showTwitchWatchStreaks", true);
|
|||||||
const twitchWatchStreaksAction = urlParams.get("twitchWatchStreaksAction") || "";
|
const twitchWatchStreaksAction = urlParams.get("twitchWatchStreaksAction") || "";
|
||||||
|
|
||||||
// Which Kick alerts do you want to see?
|
// 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 showKickSubs = GetBooleanParam("showKickSubs", true);
|
||||||
const kickSubAction = urlParams.get("kickSubAction") || "";
|
const kickSubAction = urlParams.get("kickSubAction") || "";
|
||||||
const showKickChannelPointRedemptions = GetBooleanParam("showKickChannelPointRedemptions", true);
|
const showKickChannelPointRedemptions = GetBooleanParam("showKickChannelPointRedemptions", true);
|
||||||
@@ -110,6 +111,12 @@ const tipeeestreamDonationAction = urlParams.get("tipeeestreamDonationAction") |
|
|||||||
const showFourthwallAlerts = GetBooleanParam("showFourthwallAlerts", false);
|
const showFourthwallAlerts = GetBooleanParam("showFourthwallAlerts", false);
|
||||||
const fourthwallAlertAction = urlParams.get("fourthwallAlertAction") || "";
|
const fourthwallAlertAction = urlParams.get("fourthwallAlertAction") || "";
|
||||||
|
|
||||||
|
////////////////////
|
||||||
|
// HIDDEN OPTIONS //
|
||||||
|
////////////////////
|
||||||
|
|
||||||
|
let kickUsername = urlParams.get("kickUsername") || "";
|
||||||
|
|
||||||
// Set avatar visibility
|
// Set avatar visibility
|
||||||
if (!showAvatar) {
|
if (!showAvatar) {
|
||||||
avatarElement.style.display = 'none';
|
avatarElement.style.display = 'none';
|
||||||
@@ -316,8 +323,17 @@ client.on('Fourthwall.GiftDrawEnded', (response) => {
|
|||||||
|
|
||||||
// Connect and handle Pusher WebSocket
|
// Connect and handle Pusher WebSocket
|
||||||
async function KickConnect() {
|
async function KickConnect() {
|
||||||
|
// If user has not manually set Kick username, try to grab if from Streamer.bot
|
||||||
if (!kickUsername)
|
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)
|
// Channel to subscribe to (you'll need the correct channel name here)
|
||||||
const kickIds = await GetKickIds(kickUsername);
|
const kickIds = await GetKickIds(kickUsername);
|
||||||
@@ -332,7 +348,7 @@ async function KickConnect() {
|
|||||||
// Reconnect
|
// Reconnect
|
||||||
websocket.onclose = function () {
|
websocket.onclose = function () {
|
||||||
console.log(`Reconnecting to ${kickUsername}...`);
|
console.log(`Reconnecting to ${kickUsername}...`);
|
||||||
setTimeout(connectPusher, 5000);
|
setTimeout(KickConnect, 5000);
|
||||||
};
|
};
|
||||||
|
|
||||||
websocket.onopen = function () {
|
websocket.onopen = function () {
|
||||||
@@ -482,41 +498,11 @@ async function TwitchCheer(data) {
|
|||||||
// Set the text
|
// Set the text
|
||||||
const username = data.message.displayName;
|
const username = data.message.displayName;
|
||||||
const bits = data.bits;
|
const bits = data.bits;
|
||||||
let message = data.message.message;
|
let message = ConstructMessageFromParts(data.parts);
|
||||||
|
|
||||||
// Render avatars
|
// Render avatars
|
||||||
const avatarURL = await GetAvatar(username, 'twitch');
|
const avatarURL = await GetAvatar(username, 'twitch');
|
||||||
|
|
||||||
// Render emotes
|
|
||||||
for (i in data.emotes) {
|
|
||||||
const emoteElement = `<img src="${data.emotes[i].imageUrl}" class="emote"/>`;
|
|
||||||
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 = `<img src="${imageUrl}" class="emote"/>`;
|
|
||||||
const bitsElements = `<span class="bits">${bits}</span>`
|
|
||||||
message = message.replace(new RegExp(`\\b${name}${bits}\\b`, 'i'), cheerEmoteElement + bitsElements);
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateAlertBox(
|
UpdateAlertBox(
|
||||||
'twitch',
|
'twitch',
|
||||||
avatarURL,
|
avatarURL,
|
||||||
@@ -1262,28 +1248,28 @@ function FourthwallGiftDrawEnded(data) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// async function KickFollow(data) {
|
async function KickFollow(data) {
|
||||||
// if (!showKickFollows)
|
if (!showKickFollows)
|
||||||
// return;
|
return;
|
||||||
|
|
||||||
// // Set the text
|
// Set the text
|
||||||
// const username = data.user;
|
const username = data.user.name;
|
||||||
|
|
||||||
// // Render avatars
|
// Render avatars
|
||||||
// const avatarURL = await GetAvatar(username, 'kick');
|
const avatarURL = await GetAvatar(username, 'kick');
|
||||||
|
|
||||||
// UpdateAlertBox(
|
UpdateAlertBox(
|
||||||
// 'kick',
|
'kick',
|
||||||
// avatarURL,
|
avatarURL,
|
||||||
// `${username}`,
|
`${username}`,
|
||||||
// `followed`,
|
`followed`,
|
||||||
// '',
|
'',
|
||||||
// username,
|
username,
|
||||||
// '',
|
'',
|
||||||
// kickFollowAction,
|
kickFollowAction,
|
||||||
// data
|
data
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
|
|
||||||
async function KickSubscription(data) {
|
async function KickSubscription(data) {
|
||||||
if (!showKickSubs)
|
if (!showKickSubs)
|
||||||
|
|||||||
@@ -381,11 +381,20 @@
|
|||||||
"group": "Which YouTube alerts do you want to see?"
|
"group": "Which YouTube alerts do you want to see?"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "kickUsername",
|
"id": "showKickFollows",
|
||||||
"label": "Kick Username",
|
"label": "New Followers",
|
||||||
"description": "",
|
"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": "",
|
"defaultValue": "",
|
||||||
|
"showIf": "showKickFollows",
|
||||||
"group": "Which Kick alerts do you want to see?"
|
"group": "Which Kick alerts do you want to see?"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ async function CustomEvent(data) {
|
|||||||
for (i in data.emotes) {
|
for (i in data.emotes) {
|
||||||
const emoteElement = `<img src="${data.emotes[i].imageUrl}" class="emote"/>`;
|
const emoteElement = `<img src="${data.emotes[i].imageUrl}" class="emote"/>`;
|
||||||
const emoteName = EscapeRegExp(data.emotes[i].name);
|
const emoteName = EscapeRegExp(data.emotes[i].name);
|
||||||
|
function EscapeRegExp(string) {
|
||||||
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||||
|
}
|
||||||
|
|
||||||
let regexPattern = emoteName;
|
let regexPattern = emoteName;
|
||||||
|
|
||||||
@@ -695,10 +698,6 @@ function IsValidUrl(string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function EscapeRegExp(string) {
|
|
||||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
|
||||||
}
|
|
||||||
|
|
||||||
function SetPlatformIcon(el, platform) {
|
function SetPlatformIcon(el, platform) {
|
||||||
// Set the platform icon
|
// Set the platform icon
|
||||||
let baseURL = window.location.href;
|
let baseURL = window.location.href;
|
||||||
|
|||||||
Reference in New Issue
Block a user