435 lines
12 KiB
JavaScript
435 lines
12 KiB
JavaScript
/* ----------------------- */
|
|
/* TIKTOK MODULE VARIABLES */
|
|
/* ----------------------- */
|
|
|
|
const showTiktok = getURLParam("showTiktok", true);
|
|
|
|
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);
|
|
|
|
userColors.set('tiktok', new Map());
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
if (showTiktok) {
|
|
const tiktokStatistics = `
|
|
<div class="platform" id="tiktok" style="display: none;">
|
|
<img src="js/modules/tiktok/images/logo-tiktok.svg" alt="">
|
|
<span class="viewers"><i class="fa-solid fa-user"></i> <span>0</span></span>
|
|
<span class="likes"><i class="fa-solid fa-thumbs-up"></i> <span>0</span></span>
|
|
</div>
|
|
`;
|
|
|
|
document.querySelector('#statistics').insertAdjacentHTML('beforeend', tiktokStatistics);
|
|
|
|
if (showTikTokStatistics == true) { document.querySelector('#statistics #tiktok').style.display = ''; }
|
|
|
|
console.debug('[TikTok][Debug] DOMContentLoaded fired');
|
|
|
|
tiktokConnection();
|
|
}
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// -----------------------
|
|
// TIKTOK CONNECT HANDLER
|
|
|
|
async function tiktokConnection() {
|
|
const tikfinityWebSocketURL = 'ws://localhost:21213/'; // Replace with real URL
|
|
const reconnectDelay = 10000; // 10 seconds
|
|
const maxTries = 20;
|
|
let retryCount = 0;
|
|
|
|
function connect() {
|
|
const tikfinityWebSocket = new WebSocket(tikfinityWebSocketURL);
|
|
|
|
tikfinityWebSocket.onopen = () => {
|
|
console.debug(`[TikFinity] Connected to TikFinity successfully!`);
|
|
retryCount = 0; // Reset retry count on success
|
|
|
|
notifySuccess({
|
|
title: 'Connected to TikFinity',
|
|
text: ``
|
|
});
|
|
};
|
|
|
|
tikfinityWebSocket.onmessage = (response) => {
|
|
|
|
const data = JSON.parse(response.data);
|
|
const tiktokData = data.data;
|
|
|
|
console.debug(`[TikTok] ${data.event}`, data.data);
|
|
|
|
switch (data.event) {
|
|
case 'roomUser' : tiktokUpdateStatistics(tiktokData, 'viewers'); break;
|
|
case 'like': tiktokLikesMessage(tiktokData); tiktokUpdateStatistics(tiktokData, 'likes'); break;
|
|
case 'chat': tiktokChatMessage(tiktokData); break;
|
|
case 'follow': tiktokFollowMessage(tiktokData); break;
|
|
case 'gift': tiktokGiftMessage(tiktokData); break;
|
|
case 'subscribe': tiktokSubMessage(tiktokData); break;
|
|
}
|
|
};
|
|
|
|
tikfinityWebSocket.onclose = (event) => {
|
|
|
|
setTimeout(() => {
|
|
connect();
|
|
}, reconnectDelay);
|
|
|
|
|
|
/*console.error(`[TikFinity] Disconnected (code: ${event.code})`);
|
|
|
|
if (retryCount < maxTries) {
|
|
retryCount++;
|
|
console.warn(`[TikFinity] Attempt ${retryCount}/${maxTries} - Reconnecting in ${reconnectDelay / 1000}s...`);
|
|
|
|
notifyError({
|
|
title: 'TikFinity Disconnected',
|
|
text: `Attempt ${retryCount}/${maxTries} - Reconnecting in ${reconnectDelay / 1000}...`
|
|
});
|
|
|
|
setTimeout(() => {
|
|
connect();
|
|
}, reconnectDelay);
|
|
}
|
|
else {
|
|
notifyError({
|
|
title: 'TikFinity Reconnect Failed',
|
|
text: `Maximum retries (${maxTries}) reached. Reload ChatRD to try again.<br>(Check DevTools Debug for more info).`
|
|
});
|
|
console.error('[TikFinity] Max reconnect attempts reached. Giving up.');
|
|
}*/
|
|
};
|
|
|
|
tikfinityWebSocket.onerror = (error) => {
|
|
console.error(`[TikFinity] Connection error:`, error);
|
|
|
|
// Force close to trigger onclose and centralize retry logic
|
|
if (tikfinityWebSocket.readyState !== WebSocket.CLOSED) {
|
|
tikfinityWebSocket.close();
|
|
}
|
|
};
|
|
|
|
return tikfinityWebSocket;
|
|
}
|
|
|
|
return connect(); // Returns the initial WebSocket instance
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------
|
|
// TIKTOK UTILITY FUNCTIONS
|
|
|
|
async function tiktokChatMessage(data) {
|
|
|
|
if (showTikTokMessages == false) return;
|
|
if (ignoreUserList.includes(data.nickname.toLowerCase())) return;
|
|
if (data.comment.startsWith("!") && excludeCommands == true) return;
|
|
|
|
const template = chatTemplate;
|
|
const clone = template.content.cloneNode(true);
|
|
const messageId = data.msgId;
|
|
const userId = data.userId;
|
|
|
|
const {
|
|
'first-message': firstMessage,
|
|
'shared-chat': sharedChat,
|
|
|
|
header,
|
|
timestamp,
|
|
platform,
|
|
badges,
|
|
avatar,
|
|
pronouns: pronoun,
|
|
user,
|
|
|
|
reply,
|
|
'actual-message': message
|
|
} = Object.fromEntries(
|
|
[...clone.querySelectorAll('[class]')]
|
|
.map(el => [el.className, el])
|
|
);
|
|
|
|
const classes = ['tiktok', 'chat'];
|
|
|
|
if (data.isModerator) classes.push('mod');
|
|
if (data.isSubscriber) classes.push('sub');
|
|
|
|
const [avatarImage, messageHTML, badgesHTML] = await Promise.all([
|
|
getTikTokAvatar(data),
|
|
getTikTokEmotes(data),
|
|
getTikTokBadges(data),
|
|
]);
|
|
|
|
header.remove();
|
|
firstMessage.remove();
|
|
|
|
sharedChat.remove();
|
|
reply.remove();
|
|
pronoun.remove();
|
|
|
|
if (showAvatar) avatar.innerHTML = `<img src="${avatarImage}">`; else avatar.remove();
|
|
|
|
if (showBadges) {
|
|
if (!badgesHTML) { badges.remove(); }
|
|
else { badges.innerHTML = badgesHTML; }
|
|
}
|
|
else { badges.remove(); }
|
|
|
|
var color = await createRandomColor('tiktok', data.uniqueId);
|
|
|
|
user.style.color = color;
|
|
user.innerHTML = `<strong>${data.nickname}</strong>`;
|
|
message.innerHTML = messageHTML;
|
|
|
|
addMessageItem('tiktok', clone, classes, userId, messageId);
|
|
}
|
|
|
|
|
|
|
|
async function tiktokFollowMessage(data) {
|
|
|
|
if (showTikTokFollows == false) return;
|
|
|
|
const template = eventTemplate;
|
|
const clone = template.content.cloneNode(true);
|
|
const messageId = data.msgId;
|
|
const userId = data.userId;
|
|
|
|
const {
|
|
header,
|
|
platform,
|
|
user,
|
|
action,
|
|
value,
|
|
'actual-message': message
|
|
} = Object.fromEntries(
|
|
[...clone.querySelectorAll('[class]')]
|
|
.map(el => [el.className, el])
|
|
);
|
|
|
|
const classes = ['tiktok', 'follow'];
|
|
|
|
header.remove();
|
|
message.remove();
|
|
value.remove();
|
|
|
|
|
|
user.innerHTML = `<strong>${data.nickname}</strong>`;
|
|
|
|
action.innerHTML = ` followed you`;
|
|
|
|
addEventItem('tiktok', clone, classes, userId, messageId);
|
|
}
|
|
|
|
|
|
|
|
async function tiktokLikesMessage(data) {
|
|
|
|
if (showTikTokLikes == false) return;
|
|
|
|
const template = eventTemplate;
|
|
const clone = template.content.cloneNode(true);
|
|
const messageId = data.msgId;
|
|
const userId = data.userId;
|
|
|
|
const {
|
|
header,
|
|
platform,
|
|
user,
|
|
action,
|
|
value,
|
|
'actual-message': message
|
|
} = Object.fromEntries(
|
|
[...clone.querySelectorAll('[class]')]
|
|
.map(el => [el.className, el])
|
|
);
|
|
|
|
const classes = ['tiktok', 'likes'];
|
|
|
|
|
|
var likeCountTotal = parseInt(data.likeCount);
|
|
|
|
// Search for Previous Likes from the Same User
|
|
const previousLikeContainer = chatContainer.querySelector(`div.event.tiktok.likes[data-user="${data.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) {
|
|
const likeCountElem = previousLikeContainer.querySelector('.value strong');
|
|
if (likeCountElem) {
|
|
var likeCountPrev = parseInt(likeCountElem.textContent);
|
|
likeCountTotal = Math.floor(likeCountPrev + likeCountTotal);
|
|
removeItem(previousLikeContainer);
|
|
}
|
|
}
|
|
|
|
header.remove();
|
|
|
|
|
|
user.innerHTML = `<strong>${data.nickname}</strong>`;
|
|
action.innerHTML = ` sent you `;
|
|
|
|
var likes = likeCountTotal > 1 ? 'likes' : 'like';
|
|
value.innerHTML = `<strong>${likeCountTotal}</strong> ${likes} ❤️`;
|
|
|
|
message.remove();
|
|
|
|
addEventItem('tiktok', clone, classes, userId, messageId);
|
|
}
|
|
|
|
|
|
|
|
async function tiktokSubMessage(data) {
|
|
|
|
if (showTikTokSubs == false) return;
|
|
|
|
const template = eventTemplate;
|
|
const clone = template.content.cloneNode(true);
|
|
const messageId = data.msgId;
|
|
const userId = data.userId;
|
|
|
|
const {
|
|
header,
|
|
platform,
|
|
user,
|
|
action,
|
|
value,
|
|
'actual-message': message
|
|
} = Object.fromEntries(
|
|
[...clone.querySelectorAll('[class]')]
|
|
.map(el => [el.className, el])
|
|
);
|
|
|
|
const classes = ['tiktok', 'sub'];
|
|
|
|
header.remove();
|
|
|
|
|
|
user.innerHTML = `<strong>${data.nickname}</strong>`;
|
|
action.innerHTML = ` subscribed for `;
|
|
|
|
var months = data.subMonth > 1 ? 'months' : 'month';
|
|
value.innerHTML = `<strong>${data.subMonth} ${months}</strong>`;
|
|
|
|
message.remove();
|
|
|
|
addEventItem('tiktok', clone, classes, userId, messageId);
|
|
}
|
|
|
|
|
|
|
|
async function tiktokGiftMessage(data) {
|
|
|
|
if (showTikTokGifts == false) return;
|
|
if (data.giftType === 1 && !data.repeatEnd) return;
|
|
|
|
const template = eventTemplate;
|
|
const clone = template.content.cloneNode(true);
|
|
const messageId = data.msgId;
|
|
const userId = data.userId;
|
|
|
|
const {
|
|
header,
|
|
platform,
|
|
user,
|
|
action,
|
|
value,
|
|
'actual-message': message
|
|
} = Object.fromEntries(
|
|
[...clone.querySelectorAll('[class]')]
|
|
.map(el => [el.className, el])
|
|
);
|
|
|
|
const classes = ['tiktok', 'gift'];
|
|
|
|
header.remove();
|
|
|
|
|
|
user.innerHTML = `<strong>${data.nickname}</strong>`;
|
|
action.innerHTML = ` has sent you `;
|
|
value.innerHTML = `<strong>x${data.repeatCount} ${data.giftName}</strong> <img src="${data.giftPictureUrl}"> `;
|
|
|
|
message.remove();
|
|
|
|
addEventItem('tiktok', clone, classes, userId, messageId);
|
|
}
|
|
|
|
|
|
|
|
async function getTikTokEmotes(data) {
|
|
const {
|
|
comment: message,
|
|
emotes,
|
|
} = data;
|
|
|
|
var fullmessage = message;
|
|
|
|
if (emotes.length > 0) {
|
|
emotes.forEach(emote => {
|
|
var emotetoadd = ` <img src="${emote.emoteImageUrl}" class="emote" data-emote-id="${emote.emoteId}"> `;
|
|
var position = emote.placeInComment;
|
|
fullmessage = [fullmessage.slice(0, position), emotetoadd, fullmessage.slice(position)].join('');
|
|
});
|
|
}
|
|
|
|
return fullmessage;
|
|
}
|
|
|
|
|
|
async function getTikTokAvatar(data) {
|
|
const {
|
|
profilePictureUrl
|
|
} = data;
|
|
|
|
return profilePictureUrl;
|
|
}
|
|
|
|
async function getTikTokBadges(data) {
|
|
const {
|
|
isSubscriber,
|
|
isModerator,
|
|
} = data;
|
|
|
|
let badgesHTML = [
|
|
isSubscriber && '<span class="badge sub"><i class="fa-solid fa-star"></i></span>',
|
|
isModerator && '<span class="badge mod"><i class="fa-solid fa-user-gear"></i></span>',
|
|
].filter(Boolean).join('');
|
|
|
|
return badgesHTML;
|
|
}
|
|
|
|
|
|
async function tiktokUpdateStatistics(data, type) {
|
|
|
|
if (showPlatformStatistics == false || showTikTokStatistics == false) return;
|
|
|
|
if (type == 'viewers') {
|
|
const viewers = DOMPurify.sanitize(data.viewerCount);
|
|
document.querySelector('#statistics #tiktok .viewers span').textContent = formatNumber(viewers);
|
|
}
|
|
|
|
if (type == 'likes') {
|
|
const likes = DOMPurify.sanitize(data.totalLikeCount);
|
|
document.querySelector('#statistics #tiktok .likes span').textContent = formatNumber(likes);
|
|
}
|
|
|
|
} |