sync from github
This commit is contained in:
435
js/modules/tiktok/module.js
Normal file
435
js/modules/tiktok/module.js
Normal file
@@ -0,0 +1,435 @@
|
||||
/* ----------------------- */
|
||||
/* 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);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user