Files
browser-scripts/scripts/ttv-obfuscated-names/dom.ts
T
zomo 6218d6bad1 changed observer callback
update names based on the element from the mutation event rather than checking the chatbox for unchecked messages each mutation
2026-04-19 19:01:01 -05:00

232 lines
6.7 KiB
TypeScript

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<HTMLSpanElement>(
'.chat-line__message--badges'
)
const chatboxUser = chatboxMessage.querySelector<HTMLSpanElement>(
'.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<HTMLSpanElement>(
'.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<HTMLElement>(
'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<HTMLElement>(
'.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<HTMLElement>('.chatter-name')
for (const chatterName of chatterNames) {
const chatterNameBox = innermostElement<HTMLElement, HTMLElement>(
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<HTMLElement, HTMLElement>(
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)
}
}