From da2475b1e5519dcd944a3ccd7a25bfc4f93ad997 Mon Sep 17 00:00:00 2001 From: Rodrigo Emanuel Date: Wed, 7 May 2025 19:05:01 -0300 Subject: [PATCH] Add files via upload --- README.md | 2 +- index.html | 1 + js/app.js | 6 ++++ js/lang/en.js | 1 + js/lang/es.js | 1 + js/lang/ptbr.js | 1 + js/tiktok/module.js | 48 +++++++++++++++++++++++++ js/youtube/module.js | 84 ++++++++++++++++++++++++++++++++------------ 8 files changed, 121 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 2a7c542..753af53 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ ChatRD is a chat overlay widget for OBS that unifies messages and events from ** ## 📦 Dependencies - [Streamer.Bot](https://streamer.bot) -- [Speaker.Bot](https://speaker.bot/) +- [Speaker.Bot](http://speaker.bot/) - [Streamer.Bot Client JS](https://streamerbot.github.io/client/) - [TikFinity Desktop App](https://tikfinity.zerody.one/) - [Font Awesome](https://fontawesome.com/) diff --git a/index.html b/index.html index 4605595..f8641d7 100644 --- a/index.html +++ b/index.html @@ -129,6 +129,7 @@

TikTok

+
diff --git a/js/app.js b/js/app.js index 1e1efeb..ef46894 100644 --- a/js/app.js +++ b/js/app.js @@ -344,4 +344,10 @@ async function cleanStringOfHTMLButEmotes(string) { // Remove todo o restante do HTML return container.textContent || ""; +} + + +function stripStringFromHtml(html) { + let doc = new DOMParser().parseFromString(html, 'text/html'); + return doc.body.textContent || ""; } \ No newline at end of file diff --git a/js/lang/en.js b/js/lang/en.js index 9282062..f771580 100644 --- a/js/lang/en.js +++ b/js/lang/en.js @@ -71,6 +71,7 @@ const en = { tiktok : { follow : () => ` followed the channel`, + likes : (likes) => `sent ${likes} likes`, sub : ({ months }) => ` subscribed for ${months || 1 } ${(months && months > 1) ? 'months' : 'month'}`, gift : ({ gift, count, coins }) => ` gifted ${gift} x${count} (🪙 ${coins} ${(coins && coins > 1) ? 'coins' : 'coin'})`, diff --git a/js/lang/es.js b/js/lang/es.js index 1d382dc..b0c7819 100644 --- a/js/lang/es.js +++ b/js/lang/es.js @@ -65,6 +65,7 @@ const es = { tiktok : { follow : () => ` siguió el canal`, + likes : (likes) => `envió ${likes} likes`, sub : ({ months }) => ` se suscribió por ${months || 1 } ${(months && months > 1) ? 'meses' : 'mes'}`, gift : ({ gift, count, coins }) => ` regaló ${gift} x${count} (🪙 ${coins} ${(coins && coins > 1) ? 'monedas' : 'moneda'})`, } diff --git a/js/lang/ptbr.js b/js/lang/ptbr.js index 2b31303..5b90475 100644 --- a/js/lang/ptbr.js +++ b/js/lang/ptbr.js @@ -71,6 +71,7 @@ const ptbr = { tiktok : { follow : () => ` seguiu o canal`, + likes : (likes) => `mandou ${likes} likes`, sub : ({ months }) => ` se inscreveu por ${months || 1 } ${(months && months > 1) ? 'meses' : 'mês'}`, gift : ({ gift, count, coins }) => ` doou ${gift} x${count} (🪙 ${coins} ${(coins && coins > 1) ? 'moedas' : 'moeda'})`, diff --git a/js/tiktok/module.js b/js/tiktok/module.js index 65d9d5a..cdf7755 100644 --- a/js/tiktok/module.js +++ b/js/tiktok/module.js @@ -9,6 +9,7 @@ const showTikTokMessages = getURLParam("showTikTokMessages", true); const showTikTokFollows = getURLParam("showTikTokFollows", true); +const showTikTokLikes = getURLParam("showTikTokLikes", true); const showTikTokGifts = getURLParam("showTikTokGifts", true); const showTikTokSubs = getURLParam("showTikTokSubs", true); const showTikTokStatistics = getURLParam("showTikTokStatistics", true); @@ -31,6 +32,8 @@ streamerBotClient.on('General.Custom', (response) => { break; case 'like' : tiktokUpdateStatistics(jsonData, 'likes'); + /*console.log('TikTok Likes', jsonData); + tiktokLikesMessage(jsonData);*/ break; case 'chat' : 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) { if (showTikTokSubs == false) return; diff --git a/js/youtube/module.js b/js/youtube/module.js index 1566592..0e8ab09 100644 --- a/js/youtube/module.js +++ b/js/youtube/module.js @@ -329,15 +329,15 @@ async function youTubeUpdateStatistics(data) { async function getYouTubeEmotes(data) { let message = data.message; - const channelId = data.broadcast?.channelId; 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) { try { const res = await fetch(`https://api.betterttv.net/3/cached/users/youtube/${channelId}`); const emoteData = await res.json(); + console.debug('Getting YouTube BTTV Channel Emojis', `https://api.betterttv.net/3/cached/users/youtube/${channelId}`, emoteData); youTubeBTTVEmotes = [ ...(emoteData.sharedEmotes || []), ...(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) { - const escapedCode = escapeRegex(emote.code); - const emoteRegex = new RegExp(`(?`; - - message = message.replace(emoteRegex, emoteElement); + const emoteElement = `${emote.code}`; + emoteMap.set(emote.code, { html: emoteElement, raw: emote.code }); } - // Replace built-in YouTube emotes + // YouTube emotes (ex: :vortisLaugh:) if (data.emotes) { for (const emote of data.emotes) { - const emoteRegex = new RegExp(escapeRegex(emote.name), 'g'); const emoteElement = `${emote.name}`; - message = message.replace(emoteRegex, emoteElement); + emoteMap.set(emote.name, { html: emoteElement, raw: emote.name }); } } - // Replace Custom Member Emotes Defined at Settings. - // Shows if user is a Member or the Owner - if (data.user.isSponsor == true || data.user.isOwner == true) { - message = message.replace(/:([a-zA-Z0-9_]+):/g, (match, emoteName) => { - if (youTubeCustomEmotes[emoteName]) { - return `${emoteName}`; - } - return match; - }); + // Custom Member Emotes (também com dois-pontos) + if (data.user.isSponsor === true || data.user.isOwner === true) { + for (const [name, url] of Object.entries(youTubeCustomEmotes)) { + const emoteElement = `${name}`; + emoteMap.set(`:${name}:`, { html: emoteElement, raw: `:${name}:` }); + } } + // Usa DOMParser para substituir apenas nós de texto + const parser = new DOMParser(); + const doc = parser.parseFromString(`
${message}
`, '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(`(?