import { obfuscator } from './obfuscator' import { NameConfigInstance, nameImages, usernameExtraSuffix } from './options' import { ChatMessage, elementTreeFind, innermostElement } from './util' function loadChatMessage(chatboxMessage: Element) { // only chat messages const chatboxMessageInner = chatboxMessage.querySelector( '.chat-line__message' ) if (!chatboxMessageInner) { return } // get chatbox details const chatboxBadgeContainer = chatboxMessage.querySelector( '.chat-line__message--badges' ) const chatboxUser = chatboxMessage.querySelector( '.chat-line__username' ) if (!chatboxUser) { console.error("found message, couldn't find user") return } // already applied username if (chatboxUser.querySelector('.obf-name')) { return } const chatboxUserInner = chatboxUser.querySelector( '.chat-author__display-name' ) if (!chatboxUserInner) { console.error("found message, couldn't find userInner") return } // check if mod let isMod = false if (chatboxBadgeContainer) { for (const badge of chatboxBadgeContainer.children) { if ( badge.hasAttribute('data-badge') && (badge.getAttribute('data-badge') === 'moderator' || badge.getAttribute('data-badge') === 'broadcaster') ) { isMod = true chatboxMessage.classList.add('ismod') } } } // combine details into object const chatMessage: ChatMessage = { username: chatboxUserInner.textContent, isMod, } // run main script (process chatMessage), return new ChatMessage const newChatMessage = obfuscator(chatMessage) if (newChatMessage !== null) { setUsernameDetails(newChatMessage, chatboxUserInner, true) } loadReplyLine(chatboxMessage) loadMessageMentions(chatboxMessage) } function loadReplyLine(chatboxMessage: Element) { const replyline = chatboxMessage.querySelector('.ffz--fix-reply-line') const replyUsername = replyline?.querySelector( 'p > span:nth-child(1)' ) if (!replyUsername) { return } // already applied username if (replyUsername.querySelector('.obf-name')) { return } const chatMessage: ChatMessage = { username: replyUsername.textContent.replace(/^@/, ''), isMod: false, } // run main script (process chatMessage), return new ChatMessage const newChatMessage = obfuscator(chatMessage) if (newChatMessage !== null) { setUsernameDetails(newChatMessage, replyUsername, false, '@') } } function loadMessageMentions(chatboxMessage: Element) { function eachMention(messageMention: HTMLElement) { // already applied username if (messageMention.querySelector('.obf-name')) { return } const chatMessage: ChatMessage = { username: messageMention.textContent.replace(/^@/, ''), isMod: false, } // run main script (process chatMessage), return new ChatMessage const newChatMessage = obfuscator(chatMessage) if (newChatMessage !== null) { setUsernameDetails(newChatMessage, messageMention, false, '@') } } const messageMentions = chatboxMessage.querySelectorAll( '.chat-line__message-mention' ) for (const messageMention of messageMentions) { eachMention(messageMention) } } function loadAdditionalUserNames(chatboxMessage: Element) { // look for additional chat member names const chatterNames = chatboxMessage.querySelectorAll('.chatter-name') for (const chatterName of chatterNames) { const chatterNameBox = innermostElement( chatterName ) // already applied username if (chatterNameBox.querySelector('.obf-name')) { continue } const username = chatterNameBox.textContent if (!username) { continue } // combine details into object const chatMessage: ChatMessage = { username, isMod: false, } // run main script (process chatMessage), return new ChatMessage const newChatMessage = obfuscator(chatMessage) if (newChatMessage !== null) { setUsernameDetails(newChatMessage, chatterNameBox) } } } function setUsernameDetails( newChatMessage: NameConfigInstance, usernamebox: HTMLElement, doColor = false, prefix: string = '' ) { const suffix = usernameExtraSuffix(newChatMessage) const username = `${newChatMessage.username}${suffix}` const container = document.createElement('span') container.classList.add('obf-name') const imageName = newChatMessage.image if (imageName) { // image name const image = nameImages[imageName] const imgElem = document.createElement('img') imgElem.classList.add('obf-image', 'ffz--pointer-events', 'ffz-tooltip') imgElem.setAttribute('data-tooltip-type', 'html') imgElem.setAttribute('data-title', username) imgElem.setAttribute('alt', username) imgElem.setAttribute('src', image) const prefixElem = document.createElement('span') prefixElem.textContent = prefix const suffixElem = document.createElement('span') suffixElem.textContent = suffix container.replaceChildren(prefixElem, imgElem, suffixElem) } else { // text name only container.textContent = `${prefix}${username}` } if (doColor) { container.style.color = newChatMessage.color } usernamebox.replaceChildren(container) } export function eachMutation(record: MutationRecord) { const target = record.target as HTMLElement if (target.classList.contains('chat-scrollable-area__message-container')) { for (const target of record.addedNodes) { eachMutationTarget(record, target as HTMLElement) } } else { eachMutationTarget(record, record.target as HTMLElement) } } function eachMutationTarget(record: MutationRecord, target: HTMLElement) { const chatboxMessage = elementTreeFind( target, elem => elem.parentElement?.classList.contains( 'chat-scrollable-area__message-container' ) || false ) if (chatboxMessage) { console.warn('[OBFUSCATOR] mutated message', chatboxMessage, record) loadChatMessage(chatboxMessage) loadAdditionalUserNames(chatboxMessage) } }