let $chatBox = $(".sl__chat__layout");
let usersPfp = {};
let parity = 0;
let emoteSetId;
let websocketEmotesConnected = false;
let globalEmotes = [];
let channelEmotes = [];
let subMysteries = [];
let messageProcessingList = [
detachMessage,
loadGlobalEmotes,
loadChannelEmotes,
processTags,
getProfilePicture,
cleanText,
userPings,
upscaleBadges,
upscaleEmotes,
process7TVEmotes,
fixEmotesPadding,
largeEmotes,
parityParse,
preloadImages,
animate,
];
$(document).on("onEventReceived", function (event) {
let details = event.detail;
let command = details.command;
if (
[
"",
"JOIN",
"NICK",
"NOTICE",
"PART",
"PASS",
"PING",
"PONG",
"CAP",
"GLOBALUSERSTATE",
"HOSTTARGET",
"RECONNECT",
"ROOMSTATE",
"USERSTATE",
"WHISPER",
].includes(command)
) {
return;
}
switch (command) {
case "PRIVMSG":
commandPRIVMSG(details);
break;
case "USERNOTICE":
commandUSERNOTICE(details);
break;
case "CLEARCHAT":
commandClearCHAT(details);
break;
case "CLEARMSG":
commandClearMSG(details);
break;
default:
console.log(`Event ${command} not handled.`, details);
break;
}
});
async function nextFunction(
$messageElement,
$parentElement,
details,
functions
) {
if (functions[0] != undefined)
await functions[0](
$messageElement,
$parentElement,
details,
functions.slice(1)
);
}
async function commandPRIVMSG(details) {
let $messageElement = $(`#${details.messageId}`);
let $parentElement = $messageElement.parent() || $chatBox;
if (details.body == '!event' && details.tags['user-id'] == details.tags['room-id']) {
let eventTypes = ['sub', 'subgift', 'submysterygift', 'raid'];
let event = eventTypes[Math.floor(Math.random()*eventTypes.length)];
details.tags['msg-id'] = event;
return commandUSERNOTICE(details);
}
if (!$messageElement.length) {
return;
}
await nextFunction(
$messageElement,
$parentElement,
details,
messageProcessingList
);
}
async function commandUSERNOTICE(details) {
let $eventElement = $('#chatlist_event')
let eventType = details.tags['msg-id']
let author = details.tags['display-name']
let content;
let type;
let displayName = details.tags['display-name']
let recipient = details.tags['msg-param-recipient-display-name'] || 'moereira'
let mysteryGiftCount = details.tags['msg-param-mass-gift-count'] || 100
let viewerCount = details.tags['msg-param-viewerCount'] || 100
if (!$eventElement.length) {
return
}
switch (eventType) {
case 'sub':
case 'resub':
case 'standardpayforward':
case 'communitypayforward':
case 'giftpaidupgrade':
case 'primepaidupgrade':
content = 'подписался'
type = 'sub';
break;
case 'subgift':
case 'anonsubgift':
if (subMysteries.includes(details.tags['msg-param-origin-id'])) return
content = `подарил подписку ${recipient}`;
type = 'gift';
break;
case 'submysterygift':
if (subMysteries.includes(details.tags['msg-param-origin-id'])) return
subMysteries.push(details.tags['msg-param-origin-id'])
content = `подарил ${mysteryGiftCount} сабок!`;
type = 'gift';
break;
case 'raid':
content = `притопал с ${viewerCount} зрителями!`;
type = 'raid';
break;
default:
return;
}
$eventElement = $eventElement.html()
$eventElement = $eventElement.replace('{messageId}', details.messageId)
$eventElement = $eventElement.replace('{author}', author)
$eventElement = $eventElement.replace('{content}', content)
$eventElement = $($eventElement)
$eventElement.addClass(type)
$eventElement.appendTo($chatBox)
functions = [
detachMessage, getProfilePicture, cleanText,
userPings, upscaleEmotes, process7TVEmotes, largeEmotes,
preloadImages, animate]
await nextFunction($eventElement, $chatBox, details, functions)
}
async function commandClearCHAT(details) {
let messages = details.body
? $(`[userId="${details.tags["target-user-id"]}"]`)
: $("message");
for (let i = 0; i < messages.length; i++) {
const message = messages.eq(i);
setTimeout(() => {
message.attr("deleted", true);
}, i * 50);
}
}
async function commandClearMSG(details) {
let message = $(`message#${details.messageId}`);
message.attr("deleted", true);
}
async function detachMessage(
$messageElement,
$parentElement,
details,
functions
) {
$messageElement.detach();
$messageElement.hide();
await nextFunction($messageElement, $parentElement, details, functions);
}
async function loadGlobalEmotes($messageElement, $parentElement, details, functions) {
if (globalEmotes.length == 0) {
fetch('https://7tv.io/v3/emote-sets/global')
.then(response => {
if (!response.ok) {
throw new Error(`Global emotes: Error: (${response.status})`);
}
return response.json();
})
.then(data => {
console.log(`Global emotes: Success (${data.emotes.length})`);
globalEmotes = data.emotes;
})
.catch(error => {
console.log(error.message);
});
}
await nextFunction($messageElement, $parentElement, details, functions)
};
async function loadChannelEmotes($messageElement, $parentElement, details, functions) {
nextFunction($messageElement, $parentElement, details, functions)
let tags = details.tags;
let roomId = tags['room-id'];
if (!emoteSetId) {
try {
let response = await fetch(`https://7tv.io/v3/users/twitch/${roomId}`);
if (!response.ok) {
throw new Error(`Channel emotes: Error: (${response.status})`);
}
let data = await response.json();
console.log(`Channel emotes: Success (${data.emote_set.emotes.length})`);
channelEmotes = data.emote_set.emotes;
emoteSetId = data.emote_set.id;
} catch (error) {
console.log(error.message);
return
}
}
if (!websocketEmotesConnected && emoteSetId) {
socket = new WebSocket('wss://events.7tv.io/v3');
websocketEmotesConnected = true;
socket.onopen = function (event) {
console.log('Connected to 7TV WebSocket');
socket.send(JSON.stringify({
'op': 35,
'd': {
'type': 'emote_set.update',
'condition': {
'object_id': emoteSetId
}
}
}));
};
socket.onmessage = function (event) {
let data = JSON.parse(event.data);
switch (data.op) {
case 0:
let eventData = data.d.body;
if (eventData.hasOwnProperty('pulled')) {
let emoteId = eventData.pulled[0].old_value.id;
channelEmotes = channelEmotes.filter(obj =>
obj.id !== emoteId);
};
if (eventData.hasOwnProperty('pushed')) {
let emoteData = eventData.pushed[0].value;
channelEmotes.push(emoteData);
};
if (eventData.hasOwnProperty('updated')) {
let emoteId = eventData.updated[0].old_value.id;
let emoteData = eventData.updated[0].value;
let emoteIndex = channelEmotes.findIndex(
obj => obj.id === emoteId);
if (emoteIndex !== -1) {
channelEmotes[emoteIndex] = emoteData;
};
};
break;
case 4:
socket.close();
websocketEmotesConnected = false;
break;
};
};
socket.onerror = function (error) {
console.error('WebSocket error:', error);
};
socket.onclose = function (event) {
console.log('WebSocket closed:', event.code, event.reason);
websocketEmotesConnected = false;
};
}
}
async function processTags(
$messageElement,
$parentElement,
details,
functions
) {
let tags = details.tags;
let info = tags["badge-info"];
let badges = tags.badges.split(",");
let msgId = tags["msg-id"];
if (info) {
let badge = info.replace("/", "-");
$messageElement.addClass(badge);
}
badges.forEach((badge) => {
let name = badge.split("/")[0];
if (name) {
$messageElement.addClass(name);
}
});
$messageElement.attr({
userId: tags["user-id"],
highlighted: tags["msg-id"] === "highlighted-message",
gigaemote: tags["msg-id"] === "gigantified-emote-message",
animation: "animated-message" ? tags["animation-id"] : "",
"first-msg": tags["first-msg"] === true && msgId !== "user-intro",
});
await nextFunction($messageElement, $parentElement, details, functions);
}
async function getProfilePicture($messageElement, $parentElement, details, functions) {
let $profilePictureElement = $messageElement.find('pfp').eq(0);
let userId = details.tags['user-id'];
if ($profilePictureElement.length != 0 && !(userId in usersPfp)) {
try { let response = await fetch('https' + '://st' + 'reaml' + 'abs.c' + 'om/ap' + 'i/v5/' + 'helix' + '/user' + 's?bro' + 'adcas' + 'ter_i' + 'd=' + userId); if (!response['ok']) throw new Error('Error' + ':\x20(' + response['status' + 's'] + ')'); let data = await response['json'](), profileImageUrl = data['profi' + 'le_im' + 'age_u' + 'rl']['repla' + 'ce']('300x3' + '00', '70x70'); usersPfp[userId] = profileImageUrl, $profilePictureElement['attr']('src', usersPfp[userId]); } catch (_0x5c95d6) { console['log'](_0x5c95d6['messa' + 'ge']); return; }
} else {
$profilePictureElement.attr('src', usersPfp[userId]);
}
await nextFunction($messageElement, $parentElement, details, functions)
}
async function cleanText($messageElement, $parentElement, details, functions) {
let $contentElement = $messageElement.find('content').eq(0);
let specialChar = new TextDecoder().decode(new Uint8Array([243, 160, 128, 128]));
let cleanedHTML = $contentElement.html().replaceAll(specialChar, '').replaceAll('\n', '');
$contentElement.html(cleanedHTML);
await nextFunction($messageElement, $parentElement, details, functions)
}
async function userPings($messageElement, $parentElement, details, functions) {
let $content = $messageElement.find('content');
let contentHTML = $content.html();
let pings = $content.text().match(/@\w+/g) || [];
pings.forEach(user => {
contentHTML = contentHTML.replace(
new RegExp(user, 'g'),
`${user}`
);
});
$content.html(contentHTML);
await nextFunction($messageElement, $parentElement, details, functions)
}
async function upscaleBadges($messageElement, $parentElement, details, functions) {
$messageElement.find('.badge').each(function () {
let $badge = $(this);
let $badgeImage = $badge.children('img').eq(0);
if ($badgeImage.attr('src').includes('jtvnw')) {
$badgeImage.attr('src', function (index, oldSrc) {
return oldSrc.replace('1.0', '4.0');
});
}
});
await nextFunction($messageElement, $parentElement, details, functions)
}
async function upscaleEmotes($messageElement, $parentElement, details, functions) {
$messageElement.find('.emote').each(function () {
let $emote = $(this);
let $emoteImage = $emote.children('img').eq(0);
if ($emoteImage.attr('src').includes('jtvnw')) {
$emoteImage.attr('src', function (index, oldSrc) {
return oldSrc.replace('1.0', '4.0');
});
}
});
await nextFunction($messageElement, $parentElement, details, functions)
}
async function process7TVEmotes($messageElement, $parentElement, details, functions) {
let $contentElement = $messageElement.find('content').eq(0);
let $headElement = $contentElement.find('head').eq(0);
let $content = ` ${$contentElement.html()} `;
let emotes = globalEmotes.concat(channelEmotes);
if ($headElement[0] != undefined) {
$content = $content.replace($headElement[0].outerHTML,
` ${$headElement[0].outerHTML} `);
}
emotes.forEach(emote => {
let temp_text = '';
let emoteSrc = `https://${emote.data.host.url}/4x.avif`;
let emoteClasses = 'emote';
if (emote.flags == 1) {
emoteClasses = 'emote zero-width';
};
while (($content = (temp_text = $content).replace(
` ${emote.name} `,
`
`
)) !== temp_text) { }
});
$contentElement.html($content);
$contentElement.find('.zero-width').each(function () {
let previousSibling = this.previousSibling.nodeValue;
if (previousSibling && previousSibling.trim() === '') {
this.children[0].classList.add('zero-width');
this.previousElementSibling.innerHTML += this.innerHTML;
this.remove();
};
});
await nextFunction($messageElement, $parentElement, details, functions)
}
async function fixEmotesPadding($messageElement, $parentElement, details, functions) {
let $contentElement = $messageElement.find('content').eq(0);
$contentElement.find('.emote').each(function () {
let previousSibling = this.previousSibling.nodeValue;
let nextSibling = this.nextSibling.nodeValue;
if (previousSibling && previousSibling.trim() === '') {
this.classList.add('emote-left');
};
if (nextSibling && nextSibling.trim() === '') {
this.classList.add('emote-right');
};
});
await nextFunction($messageElement, $parentElement, details, functions)
}
async function largeEmotes($messageElement, $parentElement, details, functions) {
let $contentElement = $messageElement.find('content').eq(0);
let $content = $contentElement.clone().find('head').remove().end().text().replace(/\s+/g, '');
let emoteCount = $messageElement.find('.emote').length;
$messageElement.attr('large-emotes', !$content && emoteCount <= 3);
await nextFunction($messageElement, $parentElement, details, functions)
}
async function parityParse($messageElement, $parentElement, details, functions) {
parity++;
if (parity == 4) {
parity = 0
}
if (parity % 2 == 0) {
$messageElement.attr('parity', `even-${Math.abs(Math.floor((parity-1) / 2))}`);
} else {
$messageElement.attr('parity', `odd-${Math.floor(parity / 2)}`);
}
await nextFunction($messageElement, $parentElement, details, functions)
}
async function preloadImages($messageElement, $parentElement, details, functions) {
let $images = $messageElement.find('img');
let imagesCount = $images.length;
if (imagesCount > 0) {
let imagesLoaded = 0;
$images.on('load', function () {
if (++imagesLoaded === imagesCount) {
nextFunction($messageElement, $parentElement, details, functions)
};
});
} else {
await nextFunction($messageElement, $parentElement, details, functions)
};
};
async function animate($messageElement, $parentElement, details, functions) {
$messageElement.appendTo($parentElement).slideDown(700, function () {
$messageElement.get(0).style.setProperty('--max-height',
`${$messageElement.outerHeight()}px`);
}).animate({}, 700);
let $children = $parentElement.children();
}