Add files via upload
This commit is contained in:
@@ -66,7 +66,7 @@ ChatRD is a chat overlay widget for OBS that unifies messages and events from **
|
|||||||
## 📦 Dependencies
|
## 📦 Dependencies
|
||||||
|
|
||||||
- [Streamer.Bot](https://streamer.bot)
|
- [Streamer.Bot](https://streamer.bot)
|
||||||
- [Speaker.Bot](https://speaker.bot/)
|
- [Speaker.Bot](http://speaker.bot/)
|
||||||
- [Streamer.Bot Client JS](https://streamerbot.github.io/client/)
|
- [Streamer.Bot Client JS](https://streamerbot.github.io/client/)
|
||||||
- [TikFinity Desktop App](https://tikfinity.zerody.one/)
|
- [TikFinity Desktop App](https://tikfinity.zerody.one/)
|
||||||
- [Font Awesome](https://fontawesome.com/)
|
- [Font Awesome](https://fontawesome.com/)
|
||||||
|
@@ -129,6 +129,7 @@
|
|||||||
<h2><i class="fa-brands fa-tiktok"></i> TikTok</h2>
|
<h2><i class="fa-brands fa-tiktok"></i> TikTok</h2>
|
||||||
<div class="setting"><label>Chat</label><label class="switch"><input type="checkbox" name="showTikTokMessages" checked><span class="slider"></span></label></div>
|
<div class="setting"><label>Chat</label><label class="switch"><input type="checkbox" name="showTikTokMessages" checked><span class="slider"></span></label></div>
|
||||||
<div class="setting"><label>Followers</label><label class="switch"><input type="checkbox" name="showTikTokFollows" checked><span class="slider"></span></label></div>
|
<div class="setting"><label>Followers</label><label class="switch"><input type="checkbox" name="showTikTokFollows" checked><span class="slider"></span></label></div>
|
||||||
|
<!--<div class="setting"><label>Likes</label><label class="switch"><input type="checkbox" name="showTikTokLikes" checked><span class="slider"></span></label></div>-->
|
||||||
<div class="setting"><label>Gifts</label><label class="switch"><input type="checkbox" name="showTikTokGifts" checked><span class="slider"></span></label></div>
|
<div class="setting"><label>Gifts</label><label class="switch"><input type="checkbox" name="showTikTokGifts" checked><span class="slider"></span></label></div>
|
||||||
<div class="setting"><label>Subscriptions</label><label class="switch"><input type="checkbox" name="showTikTokSubs" checked><span class="slider"></span></label></div>
|
<div class="setting"><label>Subscriptions</label><label class="switch"><input type="checkbox" name="showTikTokSubs" checked><span class="slider"></span></label></div>
|
||||||
<div class="setting"><label>Statistics<br><small>Shows viewers and likes.</small></label><label class="switch"><input type="checkbox" name="showTikTokStatistics" checked><span class="slider"></span></label></div>
|
<div class="setting"><label>Statistics<br><small>Shows viewers and likes.</small></label><label class="switch"><input type="checkbox" name="showTikTokStatistics" checked><span class="slider"></span></label></div>
|
||||||
|
@@ -345,3 +345,9 @@ async function cleanStringOfHTMLButEmotes(string) {
|
|||||||
// Remove todo o restante do HTML
|
// Remove todo o restante do HTML
|
||||||
return container.textContent || "";
|
return container.textContent || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function stripStringFromHtml(html) {
|
||||||
|
let doc = new DOMParser().parseFromString(html, 'text/html');
|
||||||
|
return doc.body.textContent || "";
|
||||||
|
}
|
@@ -71,6 +71,7 @@ const en = {
|
|||||||
|
|
||||||
tiktok : {
|
tiktok : {
|
||||||
follow : () => ` followed the channel`,
|
follow : () => ` followed the channel`,
|
||||||
|
likes : (likes) => `sent <strong><i class="fa-solid fa-heart"></i> <em class="likecount" style="font-style: normal;">${likes}</em> likes</strong>`,
|
||||||
sub : ({ months }) => ` subscribed for <i class="fa-solid fa-star"></i> <strong>${months || 1 } ${(months && months > 1) ? 'months' : 'month'}</strong>`,
|
sub : ({ months }) => ` subscribed for <i class="fa-solid fa-star"></i> <strong>${months || 1 } ${(months && months > 1) ? 'months' : 'month'}</strong>`,
|
||||||
gift : ({ gift, count, coins }) => ` gifted <strong>${gift} x${count}</strong> (🪙 <strong>${coins} ${(coins && coins > 1) ? 'coins' : 'coin'})</strong>`,
|
gift : ({ gift, count, coins }) => ` gifted <strong>${gift} x${count}</strong> (🪙 <strong>${coins} ${(coins && coins > 1) ? 'coins' : 'coin'})</strong>`,
|
||||||
|
|
||||||
|
@@ -65,6 +65,7 @@ const es = {
|
|||||||
|
|
||||||
tiktok : {
|
tiktok : {
|
||||||
follow : () => ` siguió el canal`,
|
follow : () => ` siguió el canal`,
|
||||||
|
likes : (likes) => `envió <strong><i class="fa-solid fa-heart"></i> <em class="likecount" style="font-style: normal;">${likes}</em> likes</strong>`,
|
||||||
sub : ({ months }) => ` se suscribió por <i class="fa-solid fa-star"></i> <strong>${months || 1 } ${(months && months > 1) ? 'meses' : 'mes'}</strong>`,
|
sub : ({ months }) => ` se suscribió por <i class="fa-solid fa-star"></i> <strong>${months || 1 } ${(months && months > 1) ? 'meses' : 'mes'}</strong>`,
|
||||||
gift : ({ gift, count, coins }) => ` regaló <strong>${gift} x${count}</strong> (🪙 <strong>${coins} ${(coins && coins > 1) ? 'monedas' : 'moneda'})</strong>`,
|
gift : ({ gift, count, coins }) => ` regaló <strong>${gift} x${count}</strong> (🪙 <strong>${coins} ${(coins && coins > 1) ? 'monedas' : 'moneda'})</strong>`,
|
||||||
}
|
}
|
||||||
|
@@ -71,6 +71,7 @@ const ptbr = {
|
|||||||
|
|
||||||
tiktok : {
|
tiktok : {
|
||||||
follow : () => ` seguiu o canal`,
|
follow : () => ` seguiu o canal`,
|
||||||
|
likes : (likes) => `mandou <strong><i class="fa-solid fa-heart"></i> <em class="likecount" style="font-style: normal;">${likes}</em> likes</strong>`,
|
||||||
sub : ({ months }) => ` se inscreveu por <i class="fa-solid fa-star"></i> <strong>${months || 1 } ${(months && months > 1) ? 'meses' : 'mês'}</strong>`,
|
sub : ({ months }) => ` se inscreveu por <i class="fa-solid fa-star"></i> <strong>${months || 1 } ${(months && months > 1) ? 'meses' : 'mês'}</strong>`,
|
||||||
gift : ({ gift, count, coins }) => ` doou <strong>${gift} x${count}</strong> (🪙 <strong>${coins} ${(coins && coins > 1) ? 'moedas' : 'moeda'})</strong>`,
|
gift : ({ gift, count, coins }) => ` doou <strong>${gift} x${count}</strong> (🪙 <strong>${coins} ${(coins && coins > 1) ? 'moedas' : 'moeda'})</strong>`,
|
||||||
|
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
const showTikTokMessages = getURLParam("showTikTokMessages", true);
|
const showTikTokMessages = getURLParam("showTikTokMessages", true);
|
||||||
const showTikTokFollows = getURLParam("showTikTokFollows", true);
|
const showTikTokFollows = getURLParam("showTikTokFollows", true);
|
||||||
|
const showTikTokLikes = getURLParam("showTikTokLikes", true);
|
||||||
const showTikTokGifts = getURLParam("showTikTokGifts", true);
|
const showTikTokGifts = getURLParam("showTikTokGifts", true);
|
||||||
const showTikTokSubs = getURLParam("showTikTokSubs", true);
|
const showTikTokSubs = getURLParam("showTikTokSubs", true);
|
||||||
const showTikTokStatistics = getURLParam("showTikTokStatistics", true);
|
const showTikTokStatistics = getURLParam("showTikTokStatistics", true);
|
||||||
@@ -31,6 +32,8 @@ streamerBotClient.on('General.Custom', (response) => {
|
|||||||
break;
|
break;
|
||||||
case 'like' :
|
case 'like' :
|
||||||
tiktokUpdateStatistics(jsonData, 'likes');
|
tiktokUpdateStatistics(jsonData, 'likes');
|
||||||
|
/*console.log('TikTok Likes', jsonData);
|
||||||
|
tiktokLikesMessage(jsonData);*/
|
||||||
break;
|
break;
|
||||||
case 'chat' :
|
case 'chat' :
|
||||||
console.log('TikTok Chat', jsonData);
|
console.log('TikTok Chat', jsonData);
|
||||||
@@ -140,6 +143,51 @@ async function tiktokFollowMessage(data) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async function tiktokLikesMessage(data) {
|
||||||
|
|
||||||
|
if (showTikTokLikes == false) return;
|
||||||
|
|
||||||
|
const {
|
||||||
|
userId: userID,
|
||||||
|
msgId: messageID,
|
||||||
|
profilePictureUrl: avatar,
|
||||||
|
nickname: userName,
|
||||||
|
likeCount: likesSent
|
||||||
|
} = data;
|
||||||
|
|
||||||
|
var likeCountTotal = parseInt(likesSent);
|
||||||
|
|
||||||
|
// Search for Previous Likes from the Same User
|
||||||
|
const previousLikeContainer = chatContainer.querySelector(`div.message[data-user="${userID}"]`);
|
||||||
|
|
||||||
|
// If found, fetches the previous likes, deletes the element
|
||||||
|
// and then creates a new count with a sum of the like count
|
||||||
|
if (previousLikeContainer) {
|
||||||
|
var likeCountPrev = parseInt(previousLikeContainer.querySelector('.likecount').textContent);
|
||||||
|
likeCountTotal = Math.floor(likeCountPrev + likeCountTotal);
|
||||||
|
previousLikeContainer.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = currentLang.tiktok.likes(likeCountTotal)
|
||||||
|
const classes = 'likes'
|
||||||
|
|
||||||
|
const messageData = {
|
||||||
|
classes: classes,
|
||||||
|
avatar,
|
||||||
|
badges: '',
|
||||||
|
userName,
|
||||||
|
color: '#FFF',
|
||||||
|
message,
|
||||||
|
reply: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
addEventToChat(userID, messageID, 'tiktok', messageData);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function tiktokSubMessage(data) {
|
async function tiktokSubMessage(data) {
|
||||||
|
|
||||||
if (showTikTokSubs == false) return;
|
if (showTikTokSubs == false) return;
|
||||||
|
@@ -329,15 +329,15 @@ async function youTubeUpdateStatistics(data) {
|
|||||||
|
|
||||||
async function getYouTubeEmotes(data) {
|
async function getYouTubeEmotes(data) {
|
||||||
let message = data.message;
|
let message = data.message;
|
||||||
|
|
||||||
const channelId = data.broadcast?.channelId;
|
const channelId = data.broadcast?.channelId;
|
||||||
if (!channelId) return message;
|
if (!channelId) return message;
|
||||||
|
|
||||||
// Load emotes if not already loaded
|
// Carrega os emotes do canal BTTV se ainda não estiverem carregados
|
||||||
if (youTubeBTTVEmotes.length === 0) {
|
if (youTubeBTTVEmotes.length === 0) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`https://api.betterttv.net/3/cached/users/youtube/${channelId}`);
|
const res = await fetch(`https://api.betterttv.net/3/cached/users/youtube/${channelId}`);
|
||||||
const emoteData = await res.json();
|
const emoteData = await res.json();
|
||||||
|
console.debug('Getting YouTube BTTV Channel Emojis', `https://api.betterttv.net/3/cached/users/youtube/${channelId}`, emoteData);
|
||||||
youTubeBTTVEmotes = [
|
youTubeBTTVEmotes = [
|
||||||
...(emoteData.sharedEmotes || []),
|
...(emoteData.sharedEmotes || []),
|
||||||
...(emoteData.channelEmotes || [])
|
...(emoteData.channelEmotes || [])
|
||||||
@@ -347,41 +347,81 @@ async function getYouTubeEmotes(data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace BTTV emotes
|
// Cria o mapa de emotes
|
||||||
|
const emoteMap = new Map();
|
||||||
|
|
||||||
|
// BTTV emotes
|
||||||
for (const emote of youTubeBTTVEmotes) {
|
for (const emote of youTubeBTTVEmotes) {
|
||||||
const escapedCode = escapeRegex(emote.code);
|
|
||||||
const emoteRegex = new RegExp(`(?<!\\S)${escapedCode}(?!\\S)`, 'g');
|
|
||||||
|
|
||||||
const imageUrl = `https://cdn.betterttv.net/emote/${emote.id}/1x`;
|
const imageUrl = `https://cdn.betterttv.net/emote/${emote.id}/1x`;
|
||||||
const emoteElement = `<img src="${imageUrl}" class="emote" alt="${escapedCode}">`;
|
const emoteElement = `<img src="${imageUrl}" class="emote" alt="${emote.code}">`;
|
||||||
|
emoteMap.set(emote.code, { html: emoteElement, raw: emote.code });
|
||||||
message = message.replace(emoteRegex, emoteElement);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace built-in YouTube emotes
|
// YouTube emotes (ex: :vortisLaugh:)
|
||||||
if (data.emotes) {
|
if (data.emotes) {
|
||||||
for (const emote of data.emotes) {
|
for (const emote of data.emotes) {
|
||||||
const emoteRegex = new RegExp(escapeRegex(emote.name), 'g');
|
|
||||||
const emoteElement = `<img src="${emote.imageUrl}" class="emote" alt="${emote.name}">`;
|
const emoteElement = `<img src="${emote.imageUrl}" class="emote" alt="${emote.name}">`;
|
||||||
message = message.replace(emoteRegex, emoteElement);
|
emoteMap.set(emote.name, { html: emoteElement, raw: emote.name });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace Custom Member Emotes Defined at Settings.
|
// Custom Member Emotes (também com dois-pontos)
|
||||||
// Shows if user is a Member or the Owner
|
if (data.user.isSponsor === true || data.user.isOwner === true) {
|
||||||
if (data.user.isSponsor == true || data.user.isOwner == true) {
|
for (const [name, url] of Object.entries(youTubeCustomEmotes)) {
|
||||||
message = message.replace(/:([a-zA-Z0-9_]+):/g, (match, emoteName) => {
|
const emoteElement = `<img src="${url}" class="emote" alt="${name}">`;
|
||||||
if (youTubeCustomEmotes[emoteName]) {
|
emoteMap.set(`:${name}:`, { html: emoteElement, raw: `:${name}:` });
|
||||||
return `<img src="${youTubeCustomEmotes[emoteName]}" class="emote" alt="${emoteName}">`;
|
|
||||||
}
|
}
|
||||||
return match;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Usa DOMParser para substituir apenas nós de texto
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const doc = parser.parseFromString(`<div>${message}</div>`, 'text/html');
|
||||||
|
const container = doc.body.firstChild;
|
||||||
|
|
||||||
return message;
|
function escapeRegex(string) {
|
||||||
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceEmotesInText(text) {
|
||||||
|
// Ordena por tamanho decrescente pra evitar conflitos entre nomes parecidos
|
||||||
|
const sorted = Array.from(emoteMap.values()).sort((a, b) => b.raw.length - a.raw.length);
|
||||||
|
|
||||||
|
for (const { raw, html } of sorted) {
|
||||||
|
const escaped = escapeRegex(raw);
|
||||||
|
|
||||||
|
// Emotes com dois-pontos: :emote: → permitem colados
|
||||||
|
const isDelimited = raw.startsWith(':') && raw.endsWith(':');
|
||||||
|
const regex = isDelimited
|
||||||
|
? new RegExp(escaped, 'g')
|
||||||
|
: new RegExp(`(?<!\\S)${escaped}(?!\\S)`, 'g');
|
||||||
|
|
||||||
|
text = text.replace(regex, html);
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function walk(node) {
|
||||||
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
|
const replaced = replaceEmotesInText(node.nodeValue);
|
||||||
|
if (replaced !== node.nodeValue) {
|
||||||
|
const span = doc.createElement('span');
|
||||||
|
span.innerHTML = replaced;
|
||||||
|
node.replaceWith(...span.childNodes);
|
||||||
|
}
|
||||||
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
||||||
|
for (const child of Array.from(node.childNodes)) {
|
||||||
|
walk(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
walk(container);
|
||||||
|
|
||||||
|
return container.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ChatGPT created this. :)
|
// ChatGPT created this. :)
|
||||||
async function getYouTubeStickerImage(data) {
|
async function getYouTubeStickerImage(data) {
|
||||||
const stack = [data];
|
const stack = [data];
|
||||||
|
Reference in New Issue
Block a user