Add files via upload

This commit is contained in:
nuttylmao
2025-03-23 22:04:07 +11:00
committed by GitHub
parent f133722899
commit 6c53d973f7
97 changed files with 15625 additions and 0 deletions
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" width="20" height="20" aria-label="Icon" role="img"><path fill-rule="evenodd" d="M18 6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-1.959l2.75 1.588A1.5 1.5 0 0 0 23 16.33V7.67a1.5 1.5 0 0 0-2.25-1.3L18 7.96z" clip-rule="evenodd"></path></svg>

After

Width:  |  Height:  |  Size: 343 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Design Icons by Pictogrammers - https://github.com/Templarian/MaterialDesign/blob/master/LICENSE --><path fill="currentColor" d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.62L12 2L9.19 8.62L2 9.24l5.45 4.73L5.82 21z"/></svg>

After

Width:  |  Height:  |  Size: 333 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Design Icons by Pictogrammers - https://github.com/Templarian/MaterialDesign/blob/master/LICENSE --><path fill="currentColor" d="M6.92 5H5l9 9l1-.94m4.96 6.06l-.84.84a.996.996 0 0 1-1.41 0l-3.12-3.12l-2.68 2.66l-1.41-1.41l1.42-1.42L3 7.75V3h4.75l8.92 8.92l1.42-1.42l1.41 1.41l-2.67 2.67l3.12 3.12c.4.4.4 1.03.01 1.42"/></svg>

After

Width:  |  Height:  |  Size: 432 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from All by undefined - undefined --><path fill="currentColor" fill-rule="evenodd" d="M9.592 3.2a6 6 0 0 1-.495.399c-.298.2-.633.338-.985.408c-.153.03-.313.043-.632.068c-.801.064-1.202.096-1.536.214a2.71 2.71 0 0 0-1.655 1.655c-.118.334-.15.735-.214 1.536a6 6 0 0 1-.068.632c-.07.352-.208.687-.408.985c-.087.13-.191.252-.399.495c-.521.612-.782.918-.935 1.238c-.353.74-.353 1.6 0 2.34c.153.32.414.626.935 1.238c.208.243.312.365.399.495c.2.298.338.633.408.985c.03.153.043.313.068.632c.064.801.096 1.202.214 1.536a2.71 2.71 0 0 0 1.655 1.655c.334.118.735.15 1.536.214c.319.025.479.038.632.068c.352.07.687.209.985.408c.13.087.252.191.495.399c.612.521.918.782 1.238.935c.74.353 1.6.353 2.34 0c.32-.153.626-.414 1.238-.935c.243-.208.365-.312.495-.399c.298-.2.633-.338.985-.408c.153-.03.313-.043.632-.068c.801-.064 1.202-.096 1.536-.214a2.71 2.71 0 0 0 1.655-1.655c.118-.334.15-.735.214-1.536c.025-.319.038-.479.068-.632c.07-.352.209-.687.408-.985c.087-.13.191-.252.399-.495c.521-.612.782-.918.935-1.238c.353-.74.353-1.6 0-2.34c-.153-.32-.414-.626-.935-1.238a6 6 0 0 1-.399-.495a2.7 2.7 0 0 1-.408-.985a6 6 0 0 1-.068-.632c-.064-.801-.096-1.202-.214-1.536a2.71 2.71 0 0 0-1.655-1.655c-.334-.118-.735-.15-1.536-.214a6 6 0 0 1-.632-.068a2.7 2.7 0 0 1-.985-.408a6 6 0 0 1-.495-.399c-.612-.521-.918-.782-1.238-.935a2.71 2.71 0 0 0-2.34 0c-.32.153-.626.414-1.238.935m6.781 6.663a.814.814 0 0 0-1.15-1.15l-4.85 4.85l-1.596-1.595a.814.814 0 0 0-1.15 1.15l2.17 2.17a.814.814 0 0 0 1.15 0z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<link rel="shortcut icon" href="#" />
<link rel="stylesheet" href="./style.css" />
<script type="text/javascript" src="https://unpkg.com/@streamerbot/client/dist/streamerbot-client.js"></script>
</head>
<body>
<div id="statusContainer">Connecting...</div>
<div id="mainContainer" class="slide-fade">
<ul id="messageList">
</ul>
<div id="alertBox"></div>
<div id="IPutThisHereSoICanCalculateHowBigEachMessageIsSupposedToBeBeforeIAddItToTheMessageList">
</div>
</div>
</div>
<template id="messageTemplate">
<div style="display: inline;">
<label id="userInfo" style="margin-left: 10px;">
<span id="platform"></span>
<span id="avatar"></span>
<span id="timestamp"></span>
<span id="badgeList"></span>
<span id="pronouns"></span>
<span id="username"></span>
<span id="colon-separator">: </span>
</label>
<span id="message"></span>
</div>
</template>
</body>
<script src="./script.js"></script>
+881
View File
@@ -0,0 +1,881 @@
////////////////
// PARAMETERS //
////////////////
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const sbServerAddress = urlParams.get("address") || "127.0.0.1";
const sbServerPort = urlParams.get("port") || "8080";
const minimumRole = 2; // 1 - Viewer, 2 - VIP, 3 - Moderator, 4 - Broadcaster
const avatarMap = new Map();
const animationDuration = 8000;
let widgetLocked = false; // Needed to lock animation from overlapping
let alertQueue = [];
/////////////
// OPTIONS //
/////////////
const showPlatform = GetBooleanParam("showPlatform", true);
const showAvatar = GetBooleanParam("showAvatar", true);
const showTimestamps = GetBooleanParam("showTimestamps", false);
const showBadges = GetBooleanParam("showBadges", true);
const showPronouns = GetBooleanParam("showPronouns", false);
const showUsername = GetBooleanParam("showUsername", true);
const showMessage = GetBooleanParam("showMessage", true);
const font = urlParams.get("font") || "";
const fontSize = urlParams.get("fontSize") || "18";
const hideAfter = GetIntParam("hideAfter") || 0;
const excludeCommands = GetBooleanParam("excludeCommands", true);
const ignoreChatters = urlParams.get("ignoreChatters") || "";
const showTwitchMessages = GetBooleanParam("showTwitchMessages", true);
const showTwitchAnnouncements = GetBooleanParam("showTwitchAnnouncements", true);
const showTwitchSubs = GetBooleanParam("showTwitchSubs", true);
const showTwitchRaids = GetBooleanParam("showTwitchRaids", true);
const showYouTubeMessages = GetBooleanParam("showYouTubeMessages", true);
const showYouTubeSuperChats = GetBooleanParam("showYouTubeSuperChats", true);
const showYouTubeSuperStickers = GetBooleanParam("showYouTubeSuperStickers", true);
const showYouTubeMemberships = GetBooleanParam("showYouTubeMemberships", true);
const showStreamlabsDonations = GetBooleanParam("showStreamlabsDonations", true)
const showStreamElementsTips = GetBooleanParam("showStreamElementsTips", true);
// Set fonts for the widget
document.body.style.fontFamily = font;
document.body.style.fontSize = `${fontSize}px`;
// Get a list of chatters to ignore
const ignoreUserList = ignoreChatters.split(',').map(item => item.trim().toLowerCase()) || [];
/////////////////////////
// STREAMER.BOT CLIENT //
/////////////////////////
const client = new StreamerbotClient({
host: sbServerAddress,
port: sbServerPort,
onConnect: (data) => {
console.log(`Streamer.bot successfully connected to ${sbServerAddress}:${sbServerPort}`)
console.debug(data);
SetConnectionStatus(true);
},
onDisconnect: () => {
console.error(`Streamer.bot disconnected from ${sbServerAddress}:${sbServerPort}`)
SetConnectionStatus(false);
}
});
client.on('Twitch.ChatMessage', (response) => {
console.debug(response.data);
TwitchChatMessage(response.data);
})
client.on('Twitch.Cheer', (response) => {
console.debug(response.data);
TwitchChatMessage(response.data);
})
client.on('Twitch.Announcement', (response) => {
console.debug(response.data);
TwitchAnnouncement(response.data);
})
client.on('Twitch.Sub', (response) => {
console.debug(response.data);
TwitchSub(response.data);
})
client.on('Twitch.ReSub', (response) => {
console.debug(response.data);
TwitchResub(response.data);
})
client.on('Twitch.GiftSub', (response) => {
console.debug(response.data);
TwitchGiftSub(response.data);
})
client.on('Twitch.GiftBomb', (response) => {
console.debug(response.data);
TwitchGiftBomb(response.data);
})
client.on('Twitch.Raid', (response) => {
console.debug(response.data);
TwitchRaid(response.data);
})
client.on('Twitch.ChatMessageDeleted', (response) => {
console.debug(response.data);
TwitchChatMessageDeleted(response.data);
})
client.on('Twitch.UserBanned', (response) => {
console.debug(response.data);
TwitchUserBanned(response.data);
})
client.on('Twitch.UserTimedOut', (response) => {
console.debug(response.data);
TwitchUserBanned(response.data);
})
client.on('Twitch.ChatCleared', (response) => {
console.debug(response.data);
TwitchChatCleared(response.data);
})
client.on('YouTube.Message', (response) => {
console.debug(response.data);
YouTubeMessage(response.data)
})
client.on('YouTube.SuperChat', (response) => {
console.debug(response.data);
YouTubeSuperChat(response.data);
})
client.on('YouTube.SuperSticker', (response) => {
console.debug(response.data);
YouTubeSuperSticker(response.data);
})
client.on('YouTube.NewSponsor', (response) => {
console.debug(response.data);
YouTubeNewSponsor(response.data);
})
client.on('YouTube.GiftMembershipReceived', (response) => {
console.debug(response.data);
YouTubeGiftMembershipReceived();
})
client.on('Streamlabs.Donation', (response) => {
console.debug(response.data);
StreamlabsDonation(response.data);
})
client.on('StreamElements.Tip', (response) => {
console.debug(response.data);
StreamElementsTip(response.data);
})
/////////////////////
// HORIZONTAL CHAT //
/////////////////////
async function TwitchChatMessage(data) {
if (!showTwitchMessages)
return;
// Don't post messages starting with "!"
if (data.message.message.startsWith("!") && excludeCommands)
return;
// Don't post messages from users from the ignore list
if (ignoreUserList.includes(data.message.username))
return;
// Get a reference to the template
const template = document.getElementById('messageTemplate');
// Create a new instance of the template
const instance = template.content.cloneNode(true);
// Get divs
const messageContainer = instance.querySelector(".messageContainer");
const userInfoDiv = instance.querySelector("#userInfo");
const avatarDiv = instance.querySelector("#avatar");
const timestampDiv = instance.querySelector("#timestamp");
const platformDiv = instance.querySelector("#platform");
const badgeListDiv = instance.querySelector("#badgeList");
const pronounsDiv = instance.querySelector("#pronouns");
const usernameDiv = instance.querySelector("#username");
const messageDiv = instance.querySelector("#message");
// Set timestamp
if (showTimestamps) {
timestampDiv.classList.add("timestamp");
timestampDiv.innerText = GetCurrentTimeFormatted();
}
// Set the username info
if (showUsername) {
usernameDiv.innerText = data.message.displayName;
usernameDiv.style.color = data.message.color;
}
// Set pronouns
const pronouns = await GetPronouns('twitch', 'caffeinedaydream');
if (pronouns && showPronouns) {
pronounsDiv.classList.add("pronouns");
pronounsDiv.innerText = pronouns;
}
// Set the message data
const message = data.message.message;
const messageColor = data.message.color;
// Set message text
if (showMessage) {
messageDiv.innerText = message;
}
// Set the "action" color
if (data.message.isMe)
messageDiv.style.color = messageColor;
// Render platform
if (showPlatform) {
const platformElements = `<img src="icons/platforms/twitch.png" class="platform"/>`;
platformDiv.innerHTML = platformElements;
}
// Render badges
if (showBadges) {
badgeListDiv.innerHTML = "";
for (i in data.message.badges) {
const badge = new Image();
badge.src = data.message.badges[i].imageUrl;
badge.classList.add("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);
}
// 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
if (showAvatar) {
const username = data.message.username;
const avatarURL = await GetAvatar(username);
const avatar = new Image();
avatar.src = avatarURL;
avatar.classList.add("avatar");
avatarDiv.appendChild(avatar);
}
// Hide the header if the same username sends a message twice in a row
const messageList = document.getElementById("messageList");
if (messageList.children.length > 0) {
const lastPlatform = messageList.lastChild.dataset.platform;
const lastUserId = messageList.lastChild.dataset.userId;
if (lastPlatform == "twitch" && lastUserId == data.user.id)
userInfoDiv.style.display = "none";
}
AddMessageItem(instance, data.message.msgId, 'twitch', data.user.id);
}
async function TwitchAnnouncement(data) {
if (!showTwitchAnnouncements)
return;
let background = null;
// Set the card background colors
switch (data.announcementColor) {
case "BLUE":
background = 'announcementBlue';
break;
case "GREEN":
background = 'announcementGreen';
break;
case "ORANGE":
background = 'announcementOrange';
break;
case "PURPLE":
background = 'announcementPurple';
break;
}
let message = data.text;
// Render emotes
for (i in data.parts) {
if (data.parts[i].type == `emote`) {
const emoteElement = `<img src="${data.parts[i].imageUrl}" class="emote"/>`;
message = message.replace(new RegExp(`\\b${data.parts[i].text}\\b`), emoteElement);
}
}
ShowAlert(message, background);
}
async function TwitchSub(data) {
if (!showTwitchSubs)
return;
const username = data.user.name;
const subTier = data.sub_tier;
const isPrime = data.is_prime;
let message = '';
if (!isPrime)
message = `${username} subscribed with Tier ${subTier.charAt(0)}`;
else
message = `${username} used their Prime Sub`, 'twitch';
ShowAlert(message, 'twitch');
}
async function TwitchResub(data) {
if (!showTwitchSubs)
return;
const username = data.user.name;
const subTier = data.subTier;
const isPrime = data.isPrime;
const cumulativeMonths = data.cumulativeMonths;
let message = '';
if (!isPrime)
message = `${username} resubscribed with Tier ${subTier.charAt(0)} (${cumulativeMonths} months)`;
else
message = `${username} used their Prime Sub (${cumulativeMonths} months)`;
ShowAlert(message, 'twitch');
}
async function TwitchGiftSub(data) {
if (!showTwitchSubs)
return;
const username = data.user.name;
const subTier = data.subTier;
const recipient = data.recipient.name;
const fromCommunitySubGift = data.fromCommunitySubGift;
// Don't post alerts for gift bombs
if (fromCommunitySubGift)
return;
let message = `🎁 ${username} gifted a Tier ${subTier.charAt(0)} subscription to ${recipient}`;
ShowAlert(message, 'twitch');
}
async function TwitchGiftBomb(data) {
if (!showTwitchSubs)
return;
const username = data.displayName;
const gifts = data.gifts;
const subTier = data.subTier;
let message = `🎁 ${username} gifted ${gifts} Tier ${subTier} subs!`;
ShowAlert(message, 'twitch');
}
async function TwitchRaid(data) {
if (!showTwitchRaids)
return;
const username = data.from_broadcaster_user_login;
const viewers = data.viewers;
let message = `${username} is raiding with a party of ${viewers}`;
ShowAlert(message, 'twitch');
}
function TwitchChatMessageDeleted(data) {
const messageList = document.getElementById("messageList");
// Maintain a list of chat messages to delete
const messagesToRemove = [];
// ID of the message to remove
const messageId = data.messageId;
// Find the items to remove
for (let i = 0; i < messageList.children.length; i++) {
if (messageList.children[i].id === messageId) {
messagesToRemove.push(messageList.children[i]);
}
}
// Remove the items
messagesToRemove.forEach(item => {
messageList.removeChild(item);
});
}
function TwitchUserBanned(data) {
const messageList = document.getElementById("messageList");
// Maintain a list of chat messages to delete
const messagesToRemove = [];
// ID of the message to remove
const userId = data.user_id;
// Find the items to remove
for (let i = 0; i < messageList.children.length; i++) {
if (messageList.children[i].dataset.userId === userId) {
messagesToRemove.push(messageList.children[i]);
}
}
// Remove the items
messagesToRemove.forEach(item => {
messageList.removeChild(item);
});
}
function TwitchChatCleared(data) {
const messageList = document.getElementById("messageList");
while (messageList.firstChild) {
messageList.removeChild(messageList.firstChild);
}
}
function YouTubeMessage(data) {
if (!showYouTubeMessages)
return;
// Don't post messages starting with "!"
if (data.message.startsWith("!") && excludeCommands)
return;
// Don't post messages from users from the ignore list
if (ignoreUserList.includes(data.user.name))
return;
// Get a reference to the template
const template = document.getElementById('messageTemplate');
// Create a new instance of the template
const instance = template.content.cloneNode(true);
// Get divs
const userInfoDiv = instance.querySelector("#userInfo");
const avatarDiv = instance.querySelector("#avatar");
const timestampDiv = instance.querySelector("#timestamp");
const platformDiv = instance.querySelector("#platform");
const badgeListDiv = instance.querySelector("#badgeList");
const usernameDiv = instance.querySelector("#username");
const messageDiv = instance.querySelector("#message");
// Set timestamp
if (showTimestamps) {
timestampDiv.classList.add("timestamp");
timestampDiv.innerText = GetCurrentTimeFormatted();
}
// Set the message data
if (showUsername) {
usernameDiv.innerText = data.user.name;
usernameDiv.style.color = "#f70000"; // YouTube users do not have colors, so just set it to red
}
if (showMessage) {
messageDiv.innerText = data.message;
}
// Render platform
if (showPlatform) {
const platformElements = `<img src="icons/platforms/youtube.png" class="platform"/>`;
platformDiv.innerHTML = platformElements;
}
// Render badges
if (data.user.isOwner && showBadges) {
const badge = new Image();
badge.src = `icons/badges/youtube-broadcaster.svg`;
badge.style.filter = `invert(100%)`;
badge.style.opacity = 0.8;
badge.classList.add("badge");
badgeListDiv.appendChild(badge);
}
if (data.user.isModerator && showBadges) {
const badge = new Image();
badge.src = `icons/badges/youtube-moderator.svg`;
badge.style.filter = `invert(100%)`;
badge.style.opacity = 0.8;
badge.classList.add("badge");
badgeListDiv.appendChild(badge);
}
if (data.user.isSponsor && showBadges) {
const badge = new Image();
badge.src = `icons/badges/youtube-member.svg`;
badge.style.filter = `invert(100%)`;
badge.style.opacity = 0.8;
badge.classList.add("badge");
badgeListDiv.appendChild(badge);
}
if (data.user.isVerified && showBadges) {
const badge = new Image();
badge.src = `icons/badges/youtube-verified.svg`;
badge.style.filter = `invert(100%)`;
badge.style.opacity = 0.8;
badge.classList.add("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
if (showAvatar) {
const avatar = new Image();
avatar.src = data.user.profileImageUrl;
avatar.classList.add("avatar");
avatarDiv.appendChild(avatar);
}
// Hide the header if the same username sends a message twice in a row
const messageList = document.getElementById("messageList");
if (messageList.children.length > 0) {
const lastPlatform = messageList.lastChild.dataset.platform;
const lastUserId = messageList.lastChild.dataset.userId;
if (lastPlatform == "youtube" && lastUserId == data.user.id)
userInfoDiv.style.display = "none";
}
AddMessageItem(instance, data.eventId, 'youtube', data.user.id);
}
function YouTubeSuperChat(data) {
if (!showYouTubeSuperChats)
return;
let message = `🪙 ${data.user.name} sent a Super Chat (${data.amount})`;
ShowAlert(message, 'youtube');
}
function YouTubeSuperSticker(data) {
if (!showYouTubeSuperStickers)
return;
let message = `${data.user.name} sent a Super Sticker (${data.amount})`;
ShowAlert(message, 'youtube');
}
function YouTubeNewSponsor(data) {
if (!showYouTubeMemberships)
return;
// Set message text
let message = `⭐ New ${data.levelName} • Welcome ${data.user.name}!`;
ShowAlert(message, 'youtube');
}
function YouTubeGiftMembershipReceived(data) {
if (!showYouTubeMemberships)
return;
let message = `🎁 ${data.gifter.name} gifted a membership to ${data.user.name} (${data.tier})!`;
ShowAlert(message, 'youtube');
}
function StreamlabsDonation(data) {
if (!showStreamlabsDonations)
return;
const donater = data.from;
const formattedAmount = data.formattedAmount;
const currency = data.currency;
let message = `🪙 ${donater} donated ${currency}${formattedAmount}`;
ShowAlert(message, 'streamlabs');
}
function StreamElementsTip(data) {
if (!showStreamElementsTips)
return;
const donater = data.username;
const formattedAmount = `$${data.amount}`;
const currency = data.currency;
let message = `🪙 ${donater} donated ${currency}${formattedAmount}`;
ShowAlert(message, 'streamelements');
}
//////////////////////
// HELPER FUNCTIONS //
//////////////////////
function GetBooleanParam(paramName, defaultValue) {
const urlParams = new URLSearchParams(window.location.search);
const paramValue = urlParams.get(paramName);
if (paramValue === null) {
return defaultValue; // Parameter not found
}
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'
}
}
function GetIntParam(paramName) {
const urlParams = new URLSearchParams(window.location.search);
const paramValue = urlParams.get(paramName);
if (paramValue === null) {
return null; // or undefined, or a default value, depending on your needs
}
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
}
return intValue;
}
function GetCurrentTimeFormatted() {
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'
const formattedTime = `${hours}:${minutes} ${ampm}`;
return formattedTime;
}
async function GetAvatar(username) {
if (avatarMap.has(username)) {
console.debug(`Avatar found for ${username}. Retrieving from hash map.`)
return avatarMap.get(username);
}
else {
console.debug(`No avatar found for ${username}. Retrieving from Decapi.`)
let response = await fetch('https://decapi.me/twitch/avatar/' + username);
let data = await response.text()
avatarMap.set(username, data);
return data;
}
}
async function GetPronouns(platform, username) {
const response = await client.getUserPronouns(platform, username);
const userFound = response.pronoun.userFound;
const pronouns = `${response.pronoun.pronounSubject}/${response.pronoun.pronounObject}`;
if (userFound)
return `${response.pronoun.pronounSubject}/${response.pronoun.pronounObject}`;
else
return '';
}
function AddMessageItem(element, elementID, platform, userId) {
// Calculate the height of the div before inserting
const tempDiv = document.getElementById('IPutThisHereSoICanCalculateHowBigEachMessageIsSupposedToBeBeforeIAddItToTheMessageList');
tempDiv.appendChild(element);
setTimeout(function () {
const calculatedWidth = tempDiv.offsetWidth + "px";
//console.log(calculatedWidth);
// Create a new line item to add to the message list later
var lineItem = document.createElement('li');
lineItem.id = elementID;
lineItem.dataset.platform = platform;
lineItem.dataset.userId = userId;
// Move the element from the temp div to the new line item
while (tempDiv.firstChild) {
lineItem.appendChild(tempDiv.firstChild);
}
// Add the line item to the list and animate it
// We need to manually set the height as straight CSS can't animate on "height: auto"
messageList.appendChild(lineItem);
setTimeout(function () {
lineItem.className = lineItem.className + " show";
lineItem.style.width = calculatedWidth;
}, 10);
// Remove old messages that have gone off screen to save memory
while (messageList.clientWidth > 10 * window.innerWidth) {
messageList.removeChild(messageList.firstChild);
}
tempDiv.innerHTML = '';
if (hideAfter > 0)
{
setTimeout(function () {
lineItem.style.opacity = 0;
setTimeout(function() {
messageList.removeChild(lineItem);
}, 1000);
}, hideAfter * 1000);
}
}, 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
// If any alerts are requested while the animation is playing, it should be added to the alert queue
if (widgetLocked) {
console.debug("Animation is progress, added alert to queue");
let data = { message: message, background: background, duration: duration };
alertQueue.push(data);
return;
}
// Get divs
const messageListDiv = document.querySelector("#messageList");
const alertBoxDiv = document.querySelector("#alertBox");
// Set the message text
alertBoxDiv.innerHTML = message;
// Set the background
alertBoxDiv.classList.add(background);
// Start the animation
widgetLocked = true;
messageListDiv.style.animation = 'hideAlertBox 0.5s ease-in-out forwards';
alertBoxDiv.style.animation = 'showAlertBox 0.5s ease-in-out forwards';
// To stop the animation (remove the animation property):
setTimeout(() => {
messageListDiv.style.animation = 'showAlertBox 0.5s ease-in-out forwards';
alertBoxDiv.style.animation = 'hideAlertBox 0.5s ease-in-out forwards';
setTimeout(() => {
alertBoxDiv.classList = '';
widgetLocked = false;
if (alertQueue.length > 0) {
console.debug("Pulling next alert from the queue");
let data = alertQueue.shift();
ShowAlert(data.message, data.background, data.duration);
}
}, 500);
}, duration); // Remove after 5 seconds
}
///////////////////////////////////
// STREAMER.BOT WEBSOCKET STATUS //
///////////////////////////////////
// This function sets the visibility of the Streamer.bot status label on the overlay
function SetConnectionStatus(connected) {
let statusContainer = document.getElementById("statusContainer");
if (connected) {
statusContainer.style.background = "#2FB774";
statusContainer.innerText = "Connected!";
statusContainer.style.opacity = 1;
setTimeout(() => {
statusContainer.style.transition = "all 2s ease";
statusContainer.style.opacity = 0;
}, 10);
}
else {
statusContainer.style.background = "#D12025";
statusContainer.innerText = "Connecting...";
statusContainer.style.transition = "";
statusContainer.style.opacity = 1;
}
}
// let data = {
// "isAnonymous": false,
// "gifts": 10,
// "totalGifts": 0,
// "subTier": 1, /* 0 - Prime, 1 - Tier 1, 2 - Tier 2, 3 - Tier 3 */
// "userName": "<username of gifter>",
// "displayName": "<displayname of gifter>",
// "role": 1 /* 1 - Viewer, 2 - VIP, 3 - Moderator, 4 - Broadcaster */
// }
// TwitchGiftBomb(data);
+64
View File
@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Settings</title>
<style>
body {
margin: 0;
/* Remove default body margin */
display: flex;
/* Use flexbox for layout */
background: #181818;
}
#settings-container {
width: 600px;
/* 50% of viewport width */
min-width: 600px;
/* 50% of viewport width */
height: 100vh;
/* 100% of viewport height */
border: none;
/* Remove default iframe border */
}
#widget-container {
flex-grow: 1;
/* Allow the content-container to take up remaining space */
height: 100vh;
}
iframe {
width: 100%;
height: 100%;
border: none;
}
.content-container {
width: 50vw;
height: 100vh;
background-color: #f0f0f0;
/* Example background for right side */
padding: 20px;
box-sizing: border-box;
/* Include padding in width/height */
}
</style>
</head>
<body>
<iframe id="settings-container"></iframe>
<div id="widget-container">
<iframe id="widget"></iframe>
</div>
<script src="script.js"></script>
</body>
</html>
+29
View File
@@ -0,0 +1,29 @@
let settingsContainer = document.getElementById('settings-container');
settingsContainer.src = `https://nuttylmao.github.io/widget-customizer?settingsJson=${window.location.href}/settings.json`
console.log(settingsContainer.src);
function reloadWidget(data) {
let widget = document.getElementById("widget");
widget.src = `${getParentUrl()}?${data}`;
}
function getParentUrl() {
const currentUrl = window.location.href;
const urlParts = currentUrl.split('/');
// Remove the last part of the URL (the current page/file)
urlParts.pop();
// Remove the last part again to go one directory up
urlParts.pop();
// Reconstruct the URL
const parentUrl = urlParts.join('/');
// Ensure there's a trailing slash if necessary (if it was a directory)
if (urlParts.length > 2 && !parentUrl.endsWith('/')) {
return parentUrl + '/';
}
return parentUrl;
}
+200
View File
@@ -0,0 +1,200 @@
{
"settings": [
{
"id": "showPlatform",
"label": "Show Platform",
"description": "",
"type": "checkbox",
"defaultValue": true,
"group": "Appearance"
},
{
"id": "showAvatar",
"label": "Show Avatar",
"description": "",
"type": "checkbox",
"defaultValue": true,
"group": "Appearance"
},
{
"id": "showTimestamps",
"label": "Show Timestamps",
"description": "",
"type": "checkbox",
"defaultValue": false,
"group": "Appearance"
},
{
"id": "showBadges",
"label": "Show Badges",
"description": "",
"type": "checkbox",
"defaultValue": true,
"group": "Appearance"
},
{
"id": "showPronouns",
"label": "Show Pronouns",
"description": "",
"type": "checkbox",
"defaultValue": false,
"group": "Appearance"
},
{
"id": "showUsername",
"label": "Show Username",
"description": "",
"type": "checkbox",
"defaultValue": true,
"group": "Appearance"
},
{
"id": "showMessage",
"label": "Show Message",
"description": "Honestly, if you turn this off, you're stupid lmao",
"type": "checkbox",
"defaultValue": true,
"group": "Appearance"
},
{
"id": "font",
"label": "Font",
"description": "",
"type": "text",
"defaultValue": "",
"group": "Appearance"
},
{
"id": "fontSize",
"label": "Font Size",
"description": "",
"type": "number",
"min": 1,
"max": 120,
"defaultValue": 18,
"group": "Appearance"
},
{
"id": "hideAfter",
"label": "Hide After",
"description": "Messages fade after X seconds (0 to disable)",
"type": "number",
"min": 0,
"max": 120,
"defaultValue": 0,
"group": "General"
},
{
"id": "excludeCommands",
"label": "Exclude Commands",
"description": "Hide messages starting with \"!\"",
"type": "checkbox",
"defaultValue": true,
"group": "General"
},
{
"id": "ignoreChatters",
"label": "Ignore Chatters",
"description": "Ignored chatters will not be shown",
"type": "text",
"defaultValue": "StreamElements,Streamlabs",
"group": "General"
},
{
"id": "showTwitchMessages",
"label": "Chat Messages",
"description": "",
"type": "checkbox",
"defaultValue": true,
"group": "Which Twitch messages do you want to see?"
},
{
"id": "showTwitchAnnouncements",
"label": "Announcements",
"description": "",
"type": "checkbox",
"defaultValue": true,
"group": "Which Twitch messages do you want to see?"
},
{
"id": "showTwitchSubs",
"label": "New Subscribers",
"description": "",
"type": "checkbox",
"defaultValue": true,
"group": "Which Twitch messages do you want to see?"
},
{
"id": "showTwitchRaids",
"label": "Raids",
"description": "",
"type": "checkbox",
"defaultValue": true,
"group": "Which Twitch messages do you want to see?"
},
{
"id": "showYouTubeMessages",
"label": "Chat Messages",
"description": "",
"type": "checkbox",
"defaultValue": true,
"group": "Which YouTube messages do you want to see?"
},
{
"id": "showYouTubeSuperChats",
"label": "Super Chats",
"description": "",
"type": "checkbox",
"defaultValue": true,
"group": "Which YouTube messages do you want to see?"
},
{
"id": "showYouTubeSuperStickers",
"label": "Super Stickers",
"description": "",
"type": "checkbox",
"defaultValue": true,
"group": "Which YouTube messages do you want to see?"
},
{
"id": "showYouTubeMemberships",
"label": "Memberships",
"description": "",
"type": "checkbox",
"defaultValue": true,
"group": "Which YouTube messages do you want to see?"
},
{
"id": "showStreamlabsDonations",
"label": "Streamlabs Tips",
"description": "",
"type": "checkbox",
"defaultValue": true,
"group": "Which donation messages do you want to see?"
},
{
"id": "showStreamElementsTips",
"label": "StreamElements Tips",
"description": "",
"type": "checkbox",
"defaultValue": true,
"group": "Which donation messages do you want to see?"
},
{
"id": "address",
"label": "Address",
"description": "Streamer.bot Websocket Server Address",
"type": "text",
"defaultValue": "127.0.0.1",
"group": "Advanced"
},
{
"id": "port",
"label": "Port",
"description": "Streamer.bot Websocket Server Port",
"type": "text",
"defaultValue": "8080",
"group": "Advanced"
}
]
}
+303
View File
@@ -0,0 +1,303 @@
* {
margin: 0;
padding: 0;
--margin: 0px;
--border-radius: 20px;
}
html {
width: 100vw;
height: 100vh;
scroll-behavior: smooth;
/* background: rgba(0, 0, 0, 0.884); */
font-size: 18px;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
color: white;
text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.5);
overflow-wrap: break-word;
}
#statusContainer {
font-weight: 500;
font-size: 30px;
text-align: center;
background-color: #D12025;
color: white;
padding: 10px;
border-radius: 10px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#mainContainer {
position: absolute;
height: calc(100% - 2 * var(--margin));
width: calc(100% - 2 * var(--margin));
margin: var(--margin);
}
#messageList {
position: absolute;
bottom: 0;
right: 0;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
text-justify: right;
overflow: hidden;
white-space: nowrap;
}
#alertBox {
position: absolute;
bottom: 0;
right: 50%;
transform: translateX(50%);
overflow: hidden;
white-space: nowrap;
font-weight: 700;
line-height: 1.7em;
width: 100%;
text-align: center;
bottom: calc(-1 * var(--margin) - 2em);
}
@keyframes showAlertBox {
0% {
bottom: calc(-1 * var(--margin) - 2em);
}
100% {
bottom: 0px;
}
}
@keyframes hideAlertBox {
0% {
bottom: 0px;
}
100% {
bottom: calc(-1 * var(--margin) - 2em);
}
}
li {
list-style: none;
overflow: hidden;
width: 0px;
transition: all 1s ease-in-out;
}
.slide-fade li {
transition: all 1s ease-out;
opacity: 1;
}
.slide-fade li.show {
opacity: 1;
}
#IPutThisHereSoICanCalculateHowBigEachMessageIsSupposedToBeBeforeIAddItToTheMessageList {
/* margin-top: 10px; */
background: #2FB774;
display: inline;
opacity: 0;
overflow: hidden;
white-space: nowrap;
}
.avatar {
height: 1em;
margin: 1px;
transform: translate(0px, 0.25em);
border-radius: 50%
}
.timestamp,
.pronouns {
background: #adadad46;
/* border-radius: var(--border-radius); */
border-radius: 1em;
font-size: 0.7em;
/* padding: 5px 15px; */
padding: 0.15em 0.5em;
}
.bits {
background: #adadad46;
border-radius: calc(var(--border-radius) / 2);
font-size: 0.7em;
padding: 5px 10px;
}
.platform,
.badge,
.emote {
height: 1em;
margin: 1px;
transform: translate(0px, 0.25em);
}
#badgeList {
margin: 0px 5px;
}
#username,
#title {
font-weight: 700;
}
.title {
font-weight: 700;
}
#message {
font-weight: 700;
line-height: 1.7em;
margin: 0px 5px;
}
/* .firstMessage {
display: none;
font-size: 0.7em;
} */
/* .firstMessageHighlight {
background: #adadad46;
padding: 20px 40px;
border-radius: var(--border-radius);
} */
/* .reply {
display: none;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 0.7em;
opacity: 0.5;
} */
/* .card {
background: #adadad46;
padding: 20px 40px;
border-radius: var(--border-radius);
} */
/* .youtube-super-sticker {
display: block;
} */
/* .sticker-img {
height: 5em;
} */
/*****************/
/** CARD COLORS **/
/*****************/
.announcementBlue {
background: linear-gradient(#03d3d7BF, #8d49feBF) !important;
}
.announcementGreen {
background: linear-gradient(#01da86BF, #55bee4BF) !important;
}
.announcementOrange {
background: linear-gradient(#feb419BF, #e1df00BF) !important;
}
.announcementPurple {
background: linear-gradient(#9548ffBF, #fc74e6BF) !important;
}
.twitch {
background: linear-gradient(#6d5ca1bf, #9146ffBF) !important;
}
.youtube {
background: linear-gradient(#FF6C60BF, #FF0707BF) !important;
}
.streamlabs {
background: linear-gradient(#73dabbbf, #397765bf) !important;
}
.streamelements {
background: linear-gradient(#263b8abf, #0a112abf) !important;
}
/****************/
/** ANIMATIONS **/
/****************/
.statusConnected {
animation-name: statusConnected;
animation-duration: 2s;
animation-fill-mode: forwards;
}
.statusDisconnected {
animation-name: statusDisconnected;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
@keyframes statusConnected {
0% {
opacity: 1;
background-color: #2FB774;
}
100% {
opacity: 0;
background-color: #2FB774;
}
}
@keyframes statusDisconnected {
0% {
background-color: #D12025;
}
100% {
background-color: #D12025;
}
}
/*
#message {
display: none;
}
#username {
display: none;
}
#colon-separator {
display: none;
}
#avatar {
display: none;
}
#platform {
display: none;
}
#badgeList {
display: none;
}
#timestamp {
display: none;
}
#pronouns {
display: none;
}
*/