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 = `
`;
+ 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 = `
`;
- 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 `
`;
- }
- 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 = `
`;
+ 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(`(?