/* Copyright (C) 2023 Max @sigmacw_ and Matt @mattsquare_.
*
* You may not distribute this code without obtaining explicit permission
* from Max @sigmacw_ and Matt @mattsquare_.
*
* All rights reserved.
*/
let ver = "1.0"
const colorSettings = {
streamerIconColor: '#FF4976',
modIconColor: '#FF4976',
vipIconColor: '#FF4976',
subIconColor: '#FF4976',
cornerPieceUnderBadgeColor: '#FF4976',
streamerRightBorderColor: '#FF4976',
messageMentionedUsernameColor: '#FF4976',
nonStreamerHeartNextToUsernameColor: '#FF4976',
nonStreamerHeartTopLeftColor: '#FFAEC2',
heartInChatBubbleOnTheRightColor: '#FFAEC2',
nonStreamerTopRightCornerColor: '#FF4976',
nonStreamerBottomRightCornerColor: '#FF4976',
nonStreamerBottomLeftCornerColor: '#FF4976',
nonStreamerHeartBottomLeftColor: '#FFAEC2',
eventBorderTopRightCornerColor: '#FFAEC2',
eventBorderTopLeftCornerColor: '#FFAEC2',
eventBorderBottomRightCornerColor: '#FFAEC2',
eventBorderBottomLeftCornerColor: '#FFAEC2',
eventTopLeftHeartColor: '#FFAEC2',
eventTopRightHeartColor: '#FFAEC2',
eventBottomHeartColor: '#FFAEC2',
eventLeftHeartColor: '#FFAEC2',
eventBackgroundLeftEnvelopeInsidesColor: '#FFAEC2',
eventBackgroundLeftEnvelopeOuterBorderColor: '#FFAEC2',
eventBackgroundLeftEnvelopeBottomLeftInsideLineColor: '#FFAEC2',
eventBackgroundLeftEnvelopeBottomRightInsideLineColor: '#FFAEC2',
eventBackgroundLeftEnvelopeInsideTopLinesColor: '#FFAEC2',
eventBackgroundRightEnvelopeInsidesColor: '#FFAEC2',
eventBackgroundRightEnvelopeOuterBorderColor: '#FFAEC2',
eventBackgroundRightEnvelopeBottomLeftInsideLineColor: '#FFAEC2',
eventBackgroundRightEnvelopeBottomRightInsideLineColor: '#FFAEC2',
eventBackgroundRightEnvelopeInsideTopLinesColor: '#FFAEC2',
eventBackgroundLeftHeartColor: '#FFAEC2',
unknown_1: '#FFAEC2',
unknown_2: '#FFAEC2',
eventBackgroundLeftWhiskersColor: '#FFDEE6',
eventBackgroundRightWhiskersColor: '#FFDEE6',
eventBackgroundSubscribeHeartLeftColor: '#FF4976',
eventBackgroundSubscribeHeartRightColor: '#FFDEE6',
eventBackgroundReSubscribeHeartLeftColor: '#FF4976',
eventBackgroundReSubscribeHeartRightColor: '#FFDEE6',
eventBackgroundGiftManyHeartLeftColor: '#FF4976',
eventBackgroundGiftManyHeartRightColor: '#FFDEE6',
eventBackgroundGiftOneHeartLeftColor: '#FF4976',
eventBackgroundGiftOneHeartRightColor: '#FFDEE6',
eventBackgroundCheerHeartLeftColor: '#FF4976',
eventBackgroundCheerHeartRightColor: '#FFDEE6',
eventBackgroundDonationHeartLeftColor: '#FF4976',
eventBackgroundDonationHeartRightColor: '#FFDEE6',
};
// Defining useful global variables
let msgLimit, hideAfter, hideCommands, provider, hasEmotes, widget_width;
let totalMsg = 0;
let ignoredUsers = [];
let commands = [];
let removeSelector;
let displayBadges = 'no';
let displayPronouns = 'no';
let displayUpdates = true
let colors_enabled = true
let test_streamer, test_mod, test_sub, test_reg;
let previousName = null;
let previousMsg = null;
let displayedMsg = []
let events = {
cheer: {
enabled: false,
min: 1,
class: "cheer"
},
tip: {
enabled: false,
min: 1,
currency: "$",
class: "tip"
},
sub: {
enabled: false,
class: "sub"
},
resub: {
enabled: false,
class: "resub"
},
gift: {
enabled: false,
class: "gift"
},
direct: {
enabled: false,
class: "direct"
}
}
// Initial setup on widget startup
window.addEventListener('onWidgetLoad', async function (obj) {
// Grabbing chat settings from fields
const fieldData = obj.detail.fieldData;
hideAfter = fieldData.hideAfter;
msgLimit = fieldData.messagesLimit;
hideCommands = fieldData.hideCommands;
displayPronouns = fieldData.displayPronouns;
events.cheer.enabled = fieldData.cheerEnabled;
events.cheer.min = fieldData.cheerMin;
events.tip.enabled = fieldData.tipEnabled;
events.tip.min = fieldData.tipMin;
events.tip.currency = fieldData.tipCurrency;
events.sub.enabled = fieldData.subEnabled;
events.resub.enabled = fieldData.resubEnabled;
events.gift.enabled = fieldData.giftEnabled;
events.direct.enabled = fieldData.directEnabled;
widget_width = $(document).width();
channelName = fieldData.testMessageName
test_streamer = new CustomEvent("onEventReceived", {
detail: {
listener: "message",
event: {
service: "twitch",
data: {
time: Date.now(),
tags: {
"badge-info": "",
badges: "broadcaster/1,subscriber/1",
color: "#5B99FF",
"display-name": "StreamElements",
emotes: "25:46-50",
flags: "",
id: "43285909-412c-4eee-b80d-89f72ba53142",
mod: "1",
"room-id": "85827806",
subscriber: "0",
"tmi-sent-ts": "1579444549265",
turbo: "0",
"user-id": "100135110",
"user-type": "mod"
},
nick: channelName,
userId: "100135110",
displayName: "Broadcaster",
displayColor: "#5B99FF",
badges: [{
type: "moderator",
version: "1",
url: "https://static-cdn.jtvnw.net/badges/v1/3267646d-33f0-4b17-b3df-f923a41db1d0/3",
description: "Moderator"
}, {
type: "partner",
version: "1",
url: "https://static-cdn.jtvnw.net/badges/v1/d12a2e27-16f6-41d0-ab77-b780518f00a3/3",
description: "Verified"
}],
channel: channelName,
text: "Check it out - only on Ko-Fi!",
isAction: !1,
emotes: [{
type: "twitch",
name: "Kappa",
id: "25",
gif: !1,
urls: {
1: "https://static-cdn.jtvnw.net/emoticons/v1/25/1.0",
2: "https://static-cdn.jtvnw.net/emoticons/v1/25/1.0",
4: "https://static-cdn.jtvnw.net/emoticons/v1/25/3.0"
},
start: 46,
end: 50
}],
msgId: ""
},
renderedText: 'Lorem Ipsum Broadcaster Test'
}
}
});
test_mod = new CustomEvent("onEventReceived", {
detail: {
listener: "message",
event: {
service: "twitch",
data: {
time: Date.now(),
tags: {
"badge-info": "",
badges: "moderator/1,partner/1",
color: "#5B99FF",
"display-name": "StreamElements",
emotes: "25:46-50",
flags: "",
id: "43285909-412c-4eee-b80d-89f72ba53142",
mod: "1",
"room-id": "85827806",
subscriber: "0",
"tmi-sent-ts": "1579444549265",
turbo: "0",
"user-id": "100135110",
"user-type": "mod"
},
nick: channelName,
userId: "100135110",
displayName: 'Moderator',
displayColor: "#5B99FF",
badges: [{
type: "moderator",
version: "1",
url: "https://static-cdn.jtvnw.net/badges/v1/3267646d-33f0-4b17-b3df-f923a41db1d0/3",
description: "Moderator"
}, {
type: "partner",
version: "1",
url: "https://static-cdn.jtvnw.net/badges/v1/d12a2e27-16f6-41d0-ab77-b780518f00a3/3",
description: "Verified"
}],
channel: channelName,
text: "Custom alert are here too! Chat can send you their love-letters.",
isAction: !1,
emotes: [],
msgId: ""
},
renderedText: 'Test message.
'
}
}
});
test_sub = new CustomEvent("onEventReceived", {
detail: {
listener: "message",
event: {
service: "twitch",
data: {
time: Date.now(),
tags: {
"badge-info": "",
badges: "subscriber/1",
color: "#5B99FF",
"display-name": "StreamElements",
emotes: "25:46-50",
flags: "",
id: "43285909-412c-4eee-b80d-89f72ba53142",
mod: "1",
"room-id": "85827806",
subscriber: "0",
"tmi-sent-ts": "1579444549265",
turbo: "0",
"user-id": "100135110",
"user-type": "mod"
},
nick: channelName,
userId: "100135110",
displayName: 'Subscriber',
displayColor: "#5B99FF",
badges: [{
type: "moderator",
version: "1",
url: "https://static-cdn.jtvnw.net/badges/v1/3267646d-33f0-4b17-b3df-f923a41db1d0/3",
description: "Moderator"
}, {
type: "partner",
version: "1",
url: "https://static-cdn.jtvnw.net/badges/v1/d12a2e27-16f6-41d0-ab77-b780518f00a3/3",
description: "Verified"
}],
channel: channelName,
text: "To celebrate, a Free to Use, Valentine's themed, hearty chat widget.",
isAction: !1,
emotes: [],
msgId: ""
},
renderedText: 'Test message.
'
}
}
});
test_reg = new CustomEvent("onEventReceived", {
detail: {
listener: "message",
event: {
service: "twitch",
data: {
time: Date.now(),
tags: {
"badge-info": "",
badges: "",
color: "#5B99FF",
"display-name": "StreamElements",
emotes: "25:46-50",
flags: "",
id: "43285909-412c-4eee-b80d-89f72ba53142",
mod: "1",
"room-id": "85827806",
subscriber: "0",
"tmi-sent-ts": "1579444549265",
turbo: "0",
"user-id": "100135110",
"user-type": "mod"
},
nick: channelName,
userId: "100135110",
displayName: channelName,
displayColor: "#5B99FF",
badges: [],
channel: channelName,
text: "Test message. Kappa",
isAction: !1,
emotes: [],
msgId: ""
},
renderedText: 'Test message.
'
}
}
});
test_reg.detail.event.data.text = fieldData.testMessageText;
// Grabbing the service used for chatbox (Twitch, YouTube, Mixer)
fetch('https://api.streamelements.com/kappa/v2/channels/' + obj.detail.channel.id + '/')
.then(response => response.json()).then((profile) => {
provider = profile.provider;
});
removeSelector = ".message-row:nth-last-child(n+" + (msgLimit + 1) + ")"
// Setting up the list of users whose messages will be ignored
ignoredUsers = fieldData.ignoredUsers.toLowerCase()
.replace(" ", "").split(",");
commands = fieldData.ignoredCommands.toLowerCase()
.replace(" ", "").split(",");
});
// Listen for StreamElements events
window.addEventListener('onEventReceived', function (obj) {
// Define what to do on different events
if (obj.detail.event.listener === 'widget-button') {
switch (obj.detail.event.field) {
case 'testMessage':
test_streamer.detail.event.data.msgId = (Math.random() + 1).toString(36).substring(7) + (Math.random() + 1).toString(36).substring(7)
window.dispatchEvent(test_streamer);
break;
case 'testMessage1':
test_mod.detail.event.data.msgId = (Math.random() + 1).toString(36).substring(7) + (Math.random() + 1).toString(36).substring(7)
window.dispatchEvent(test_mod);
break
case 'testMessage2':
test_sub.detail.event.data.msgId = (Math.random() + 1).toString(36).substring(7) + (Math.random() + 1).toString(36).substring(7)
window.dispatchEvent(test_sub);
break
case 'testMessage3':
test_reg.detail.event.data.msgId = (Math.random() + 1).toString(36).substring(7) + (Math.random() + 1).toString(36).substring(7)
window.dispatchEvent(test_reg);
break
}
}
// Check for events that correspond to message deletion
if (obj.detail.listener === "delete-message") {
const msgId = obj.detail.event.msgId;
$(`.message-row[data-msgid=${msgId}]`).remove();
return;
} else if (obj.detail.listener === "delete-messages") {
const sender = obj.detail.event.userId;
$(`.message-row[data-sender=${sender}]`).remove();
return;
}
if (obj.detail.listener === "cheer-latest") {
let evt = obj.detail.event;
let username = evt.name;
let amount = evt.amount;
if (amount < events.cheer.min || !events.cheer.enabled) {
return
}
alertCheer(username, amount);
return
}
if (obj.detail.listener === "tip-latest") {
let evt = obj.detail.event;
let username = evt.name;
let amount = evt.amount;
if (amount < events.tip.min || !events.tip.enabled) {
return
}
alertTip(username, amount);
return
}
if (obj.detail.listener === "subscriber-latest") {
let evt = obj.detail.event;
let username = evt.name;
let amount = evt.amount;
let bulkGifted = evt.bulkGifted;
let sender = evt.sender;
let isCommunityGift = false;
isCommunityGift = evt.isCommunityGift;
let gifted = false;
gifted = evt.gifted;
if (isCommunityGift) return;
if (bulkGifted && events.gift.enabled) {
alertBulkGift(username, amount);
} else if (gifted && events.direct.enabled) {
alertGift(sender, username);
} else if (amount > 1 && events.resub.enabled) {
alertReSub(username, amount);
} else if (events.sub.enabled) {
alertSub(username);
}
return;
}
// Check if the event is a chat message
if (obj.detail.listener === "message") {
let data = obj.detail.event.data;
if (ignoredUsers.indexOf(data.nick) !== -1) {
return;
}
let role = check_role(data);
let name = data.displayName;
let message = attachEmotes(data);
let badges = ``;
if (displayBadges) {
for (let i = 0; i < data.badges.length; i++) {
badge = data.badges[i];
badges += `
`;
}
}
const emoteOnly = isEmote(data);
const isCommand = commands.some(prefix => data.text.startsWith(prefix));
if (isCommand && hideCommands === "yes") {
return;
}
let pn = null;
const pn_api = fetch('https://pronouns.alejo.io/api/users/' + data.displayName.toLowerCase())
.then((response) => response.json())
.then((user) => {
if (!user.length) {
return null;
} else return user[0].pronoun_id;
});
const printAddress = async () => {
pn = await pn_api;
switch (pn) {
case "aeaer":
pn = "ae/aer";
break;
case "eem":
pn = "e/em";
break;
case "faefaer":
pn = "fae/faer";
break;
case "hehim":
pn = "he/him";
break;
case "heshe":
pn = "he/she";
break;
case "hethem":
pn = "he/they";
break;
case "itits":
pn = "it/its";
break;
case "perper":
pn = "per/per";
break;
case "sheher":
pn = "she/her";
break;
case "shethem":
pn = "she/they";
break;
case "theythem":
pn = "they/them";
break;
case "vever":
pn = "ve/ver";
break;
case "xexem":
pn = "xe/xem";
break;
case "ziehir":
pn = "zie/hir";
break;
default:
break;
}
return pn;
}
printAddress().then(pn => addMessage(name, null, role, badges, message, emoteOnly, data.isAction, data.userId, data.msgId, pn));
}
});
// A function that renders the message in DOM
function addMessage(name, reply, badge, badges, message, isEmote, isAction, userId, msgId, pn) {
// Advance the counter of total messages by 1 to keep track
// of how many there are at any point
totalMsg += 1;
// Define a CSS class for a certain action
const actionClass = isAction ? "action" : "";
const emoteOnly = isEmote ? "emote-only" : "";
let pn_none = ""
if (!displayPronouns || pn == null) {
pn_none = "style='display: none'"
}
const mentions = message.match(/@\S+/g)
if (mentions) {
for (var i = 0; i < mentions.length; i++) {
message = message.replace(mentions[i], `
${mentions[i]}
`) } } let roleIcon, broadcasterClass; broadcasterClass = ''; let broadcasterBorder = ''; let broadcasterFill = ''; role = badge[0] switch (role) { case "broadcaster": roleIcon = `