//////////////////
// CHAT OVERLAY //
//////////////////
;(function() {
let sbShowChat = true;
const moduleUrl = window.getModuleUrl();
loadCSSModule('overlay-chat-css', moduleUrl + '/css/chat.css');
function initModule() {
const container = document.body;
if (!document.getElementById('chat-container')) {
const chatContainer = document.createElement('div');
chatContainer.className = 'chat-container';
chatContainer.id = 'chat-container';
const sticky = document.createElement('div');
sticky.id = 'stickyContainer';
const announceList = document.createElement('ul');
announceList.id = 'announcementList';
sticky.appendChild(announceList);
const messages = document.createElement('div');
messages.id = 'messagesContainer';
const messageList = document.createElement('ul');
messageList.id = 'messageList';
messages.appendChild(messageList);
chatContainer.append(sticky, messages);
container.appendChild(chatContainer);
}
function injectTemplate(id, html) {
if (!document.getElementById(id)) {
const tpl = document.createElement('template');
tpl.id = id;
tpl.innerHTML = html.trim();
document.body.appendChild(tpl);
}
}
injectTemplate('messageTemplate', `
`);
injectTemplate('announcementTemplate', `
`);
}
initModule();
StreamerBot.on('Twitch.ChatMessage', (data) => {
if (sbDebugMode)
console.log(data.data);
TwitchChatMessage(data.data);
})
StreamerBot.on('Twitch.Announcement', (data) => {
if (sbDebugMode)
console.log(data.data);
TwitchAnnouncement(data.data);
})
StreamerBot.on('Twitch.ChatMessageDeleted', (data) => {
if (sbDebugMode)
console.log(data.data);
TwitchChatMessageDeleted(data.data);
})
StreamerBot.on('Twitch.UserBanned', (data) => {
if (sbDebugMode)
console.log(data.data);
TwitchUserBanned(data.data);
})
StreamerBot.on('Twitch.UserTimedOut', (data) => {
if (sbDebugMode)
console.log(data.data);
TwitchUserBanned(data.data);
})
StreamerBot.on('Twitch.ChatCleared', (data) => {
if (sbDebugMode)
console.log(data.data);
TwitchChatCleared(data.data);
})
if (window.SBdispatcher) {
SBdispatcher.on('chat-show', () => {
divShow("chat-container");
});
SBdispatcher.on('chat-hide', () => {
divHide("chat-container");
});
SBdispatcher.on('chat-toggle', () => {
divToggle("chat-container");
});
SBdispatcher.on('chat-left', () => {
divAnimate("chat-container", "chat-container-left");
});
SBdispatcher.on('chat-right', () => {
divAnimate("chat-container", "chat-container-right");
});
}
//////////////////
// CHAT OPTIONS //
//////////////////
const showPlatform = getBooleanParam("showPlatform", true);
const showAvatar = getBooleanParam("showAvatar", true);
const showTimestamps = getBooleanParam("showTimestamps", true);
const showBadges = getBooleanParam("showBadges", true);
const showPronouns = getBooleanParam("showPronouns", true);
const showUsername = getBooleanParam("showUsername", true);
const hideAfter = getIntParam("hideAfter", 900);
const hideAnnounceAfter = getIntParam("hideAfter", 180);
const ignoreChatters = urlParams.get("ignoreChatters") || "botarex";
const ignoreUserList = ignoreChatters.split(',').map(item => item.trim().toLowerCase()) || [];
const chatContainer = document.getElementById('chat-container');
if (!sbShowChat) {
chatContainer.style.opacity = 0;
}
///////////////
// CHAT CODE //
///////////////
function twitchChatDisplay() {
setTimeout(function () {
chatContainer.style.transition = "all 1s ease";
chatContainer.style.opacity = 1;
}, 1000);
}
function twitchChatHide() {
setTimeout(function () {
chatContainer.style.transition = "all 1s ease";
chatContainer.style.opacity = 0;
}, 1000);
}
async function TwitchChatMessage(data) {
// 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.toLowerCase()))
return;
// Get a reference to the template
const template = document.getElementById('messageTemplate');
// Create a new instance of the template
const chatMessage = template.content.cloneNode(true);
// Get divs
const messageContainer = chatMessage.querySelector("#messageContainer");
const messageDiv = chatMessage.querySelector("#messageContent");
const usernameDiv = chatMessage.querySelector("#username");
const timestampDiv = chatMessage.querySelector("#timestamp");
const badgeListDiv = chatMessage.querySelector("#badges");
const avatarImg = chatMessage.querySelector("#avatar");
const roleIconDiv = chatMessage.querySelector("#role");
// Set timestamp
if (showTimestamps) {
timestampDiv.innerText = GetCurrentTimeFormatted();
}
// Set the username info
if (showUsername) {
usernameDiv.innerText = data.message.displayName;
usernameDiv.style.color = data.message.color;
}
// Set the message data
let message = data.message.message;
const messageColor = data.message.color;
messageDiv.innerText = message;
// Set the "action" color
if (data.message.isMe)
messageDiv.style.color = messageColor;
// 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 = `
`;
const emoteName = EscapeRegExp(data.emotes[i].name);
let regexPattern = emoteName;
// Check if the emote name consists only of word characters (alphanumeric and underscore)
if (/^\w+$/.test(emoteName)) {
regexPattern = `\\b${emoteName}\\b`;
}
else {
// For non-word emotes, ensure they are surrounded by non-word characters or boundaries
regexPattern = `(?:^|[^\\w])${emoteName}(?:$|[^\\w])`;
}
const regex = new RegExp(regexPattern, 'g');
messageDiv.innerHTML = messageDiv.innerHTML.replace(regex, emoteElement);
}
// Render cheermotes
for (i in data.cheerEmotes) {
const bits = data.cheerEmotes[i].bits;
const imageUrl = data.cheerEmotes[i].imageUrl;
const name = data.cheerEmotes[i].name;
const cheerEmoteElement = `
`;
const bitsElements = `${bits}`
messageDiv.innerHTML = messageDiv.innerHTML.replace(new RegExp(`\\b${name}${bits}\\b`, 'i'), cheerEmoteElement + bitsElements);
}
// Render avatars
if (showAvatar) {
const username = data.message.username;
const avatarURL = await GetAvatar(username);
avatarImg.src = avatarURL;
}
// Add User Role Style
switch (data.user.role) {
case 2:
// User VIP
messageContainer.classList.add("vip");
const roleVip = new Image();
roleVip.src = moduleUrl + "/assets/role-vip.png";
roleIconDiv.appendChild(roleVip);
break;
case 3:
// User Moderator
messageContainer.classList.add("mod");
const roleMod = new Image();
roleMod.src = moduleUrl + "/assets/role-moderator.png";
roleIconDiv.appendChild(roleMod);
break;
case 4:
// User Broadcaster
messageContainer.classList.add("streamer");
const roleStreamer = new Image();
roleStreamer.src = moduleUrl + "/assets/role-broadcaster.png";
roleIconDiv.appendChild(roleStreamer);
break;
default:
messageContainer.classList.add("user");
}
addMessage(chatMessage,data.message.msgId, data.user.id);
}
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 => {
item.style.opacity = 0;
item.style.height = 0;
setTimeout(function () {
messageList.removeChild(item);
}, 500);
});
}
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);
}
}
async function TwitchAnnouncement(data) {
// Get a reference to the template
const template = document.getElementById('announcementTemplate');
// Create a new instance of the template
const stickyMessage = template.content.cloneNode(true);
const messageDiv = stickyMessage.querySelector("#messageContent");
// Set the message data
let message = data.text;
messageDiv.innerText = message;
for (i in data.parts) {
if (data.parts[i].type == `emote`) {
const emoteElement = `
`;
const emoteName = EscapeRegExp(data.parts[i].text);
let regexPattern = emoteName;
// Check if the emote name consists only of word characters (alphanumeric and underscore)
if (/^\w+$/.test(emoteName)) {
regexPattern = `\\b${emoteName}\\b`;
}
else {
// For non-word emotes, ensure they are surrounded by non-word characters or boundaries
regexPattern = `(?:^|[^\\w])${emoteName}(?:$|[^\\w])`;
}
const regex = new RegExp(regexPattern, 'g');
messageDiv.innerHTML = messageDiv.innerHTML.replace(regex, emoteElement);
}
}
addAnnounce(stickyMessage, data.messageId, data.user.id);
}
function addMessage(messageElement,elementId,userId) {
const chatMessages = document.getElementById("messageList");
var lineItem = document.createElement('li');
lineItem.id = elementId;
lineItem.dataset.userId = userId;
lineItem.appendChild(messageElement);
chatMessages.appendChild(lineItem);
chatMessages.scrollTop = chatMessages.scrollHeight;
if (hideAfter > 0) {
setTimeout(function () {
lineItem.style.opacity = 0;
setTimeout(function () {
chatMessages.removeChild(lineItem);
}, 500);
}, hideAfter * 1000);
}
}
function addAnnounce(messageElement,elementId,userId) {
const chatMessages = document.getElementById("announcementList");
// Clear the previous announcements
while (chatMessages.firstChild) {
chatMessages.removeChild(chatMessages.firstChild);
}
var lineItem = document.createElement('li');
lineItem.id = elementId;
lineItem.dataset.userId = userId;
lineItem.appendChild(messageElement);
chatMessages.appendChild(lineItem);
chatMessages.scrollTop = chatMessages.scrollHeight;
if (hideAnnounceAfter > 0) {
setTimeout(function () {
lineItem.style.opacity = 0;
setTimeout(function () {
chatMessages.removeChild(lineItem);
}, 500);
}, hideAnnounceAfter * 1000);
}
}
function GetCurrentTimeFormatted() {
const now = new Date();
let hours = now.getHours();
const minutes = String(now.getMinutes()).padStart(2, '0');
const formattedTime = `${hours}:${minutes}`;
return formattedTime;
// 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;
}
}
function EscapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
})();