let $chatBox = $(".sl__chat__layout"); let usersPfp = {}; let parity = true; 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', ''); if (cleanedHTML.length >= 90) { $messageElement.addClass('long'); } $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) { $messageElement.attr('parity', parity ? 'even' : 'odd'); parity = !parity; 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); }