changed observer callback
update names based on the element from the mutation event rather than checking the chatbox for unchecked messages each mutation
This commit is contained in:
Vendored
+105
-62
@@ -4,6 +4,7 @@
|
|||||||
// @match https://www.twitch.tv/*
|
// @match https://www.twitch.tv/*
|
||||||
// @version 0.1
|
// @version 0.1
|
||||||
// @runat document-end
|
// @runat document-end
|
||||||
|
// @grant unsafeWindow
|
||||||
// @downloadURL https://git.zomo.dev/zomo/browser-scripts/raw/branch/main/dist/ttv-obfuscated-names.user.js
|
// @downloadURL https://git.zomo.dev/zomo/browser-scripts/raw/branch/main/dist/ttv-obfuscated-names.user.js
|
||||||
// @supportURL https://git.zomo.dev/zomo/browser-scripts/issues
|
// @supportURL https://git.zomo.dev/zomo/browser-scripts/issues
|
||||||
// @homepageURL https://git.zomo.dev/zomo/browser-scripts
|
// @homepageURL https://git.zomo.dev/zomo/browser-scripts
|
||||||
@@ -31,6 +32,8 @@ var nameList = [
|
|||||||
// image: 'personimage1',
|
// image: 'personimage1',
|
||||||
// },
|
// },
|
||||||
]
|
]
|
||||||
|
var usernameExtraSuffix = nameConfig =>
|
||||||
|
nameConfig.nameCount > 0 ? `${nameConfig.nameCount + 1}` : ''
|
||||||
|
|
||||||
var { storedUsers, nameListUsed, nameListCount } = loadStoredUserData()
|
var { storedUsers, nameListUsed, nameListCount } = loadStoredUserData()
|
||||||
function getStoredUser(name) {
|
function getStoredUser(name) {
|
||||||
@@ -81,6 +84,16 @@ function loadStoredUserData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function clearStoredUsers() {
|
||||||
|
localStorage.removeItem('obf-data')
|
||||||
|
storedUsers = {}
|
||||||
|
nameListUsed = {}
|
||||||
|
nameListCount = 0
|
||||||
|
}
|
||||||
|
unsafeWindow.obfClear = () => {
|
||||||
|
clearStoredUsers()
|
||||||
|
location.reload()
|
||||||
|
}
|
||||||
function getRandomName() {
|
function getRandomName() {
|
||||||
const startingIndex = Math.round(Math.random() * (nameList.length - 1))
|
const startingIndex = Math.round(Math.random() * (nameList.length - 1))
|
||||||
let foundName = false
|
let foundName = false
|
||||||
@@ -127,6 +140,7 @@ function obfuscator(chatMessage) {
|
|||||||
if (ignoreMod && chatMessage.isMod) {
|
if (ignoreMod && chatMessage.isMod) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
console.log(`[OBFUSCATOR] Updating Username: ${chatMessage.username}`)
|
||||||
chatMessage.username = chatMessage.username.toLowerCase()
|
chatMessage.username = chatMessage.username.toLowerCase()
|
||||||
const userData = getStoredUser(chatMessage.username)
|
const userData = getStoredUser(chatMessage.username)
|
||||||
if (userData !== null) {
|
if (userData !== null) {
|
||||||
@@ -141,19 +155,17 @@ function innermostElement(elem) {
|
|||||||
if (elem.children.length === 0) {
|
if (elem.children.length === 0) {
|
||||||
return elem
|
return elem
|
||||||
}
|
}
|
||||||
return innermostElement(elem.children[0])
|
let child = elem.children[0]
|
||||||
|
return innermostElement(child)
|
||||||
}
|
}
|
||||||
function usernameTemplateSuffix(newChatMessage) {
|
function elementTreeFind(elem, fn) {
|
||||||
if (newChatMessage.nameCount === 0) {
|
if (fn(elem)) {
|
||||||
return ''
|
return elem
|
||||||
}
|
}
|
||||||
return `${newChatMessage.nameCount}`
|
if (!elem.parentElement) {
|
||||||
}
|
return null
|
||||||
function usernameImageTemplateSuffix(newChatMessage) {
|
|
||||||
if (newChatMessage.nameCount === 0) {
|
|
||||||
return ''
|
|
||||||
}
|
}
|
||||||
return `${newChatMessage.nameCount}`
|
return elementTreeFind(elem.parentElement, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadChatMessage(chatboxMessage) {
|
function loadChatMessage(chatboxMessage) {
|
||||||
@@ -166,15 +178,14 @@ function loadChatMessage(chatboxMessage) {
|
|||||||
const chatboxBadgeContainer = chatboxMessage.querySelector(
|
const chatboxBadgeContainer = chatboxMessage.querySelector(
|
||||||
'.chat-line__message--badges'
|
'.chat-line__message--badges'
|
||||||
)
|
)
|
||||||
if (!chatboxBadgeContainer) {
|
|
||||||
console.error("found message, couldn't find badges")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const chatboxUser = chatboxMessage.querySelector('.chat-line__username')
|
const chatboxUser = chatboxMessage.querySelector('.chat-line__username')
|
||||||
if (!chatboxUser) {
|
if (!chatboxUser) {
|
||||||
console.error("found message, couldn't find user")
|
console.error("found message, couldn't find user")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (chatboxUser.querySelector('.obf-name')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const chatboxUserInner = chatboxUser.querySelector(
|
const chatboxUserInner = chatboxUser.querySelector(
|
||||||
'.chat-author__display-name'
|
'.chat-author__display-name'
|
||||||
)
|
)
|
||||||
@@ -182,19 +193,17 @@ function loadChatMessage(chatboxMessage) {
|
|||||||
console.error("found message, couldn't find userInner")
|
console.error("found message, couldn't find userInner")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
chatboxMessage.classList.add('obf-loaded')
|
|
||||||
if (chatboxUser.classList.contains('seventv-paint')) {
|
|
||||||
chatboxUser.classList.remove('seventv-paint')
|
|
||||||
}
|
|
||||||
let isMod = false
|
let isMod = false
|
||||||
for (const badge of chatboxBadgeContainer.children) {
|
if (chatboxBadgeContainer) {
|
||||||
if (
|
for (const badge of chatboxBadgeContainer.children) {
|
||||||
badge.hasAttribute('data-badge') &&
|
if (
|
||||||
(badge.getAttribute('data-badge') === 'moderator' ||
|
badge.hasAttribute('data-badge') &&
|
||||||
badge.getAttribute('data-badge') === 'broadcaster')
|
(badge.getAttribute('data-badge') === 'moderator' ||
|
||||||
) {
|
badge.getAttribute('data-badge') === 'broadcaster')
|
||||||
isMod = true
|
) {
|
||||||
chatboxMessage.classList.add('ismod')
|
isMod = true
|
||||||
|
chatboxMessage.classList.add('ismod')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const chatMessage = {
|
const chatMessage = {
|
||||||
@@ -203,7 +212,7 @@ function loadChatMessage(chatboxMessage) {
|
|||||||
}
|
}
|
||||||
const newChatMessage = obfuscator(chatMessage)
|
const newChatMessage = obfuscator(chatMessage)
|
||||||
if (newChatMessage !== null) {
|
if (newChatMessage !== null) {
|
||||||
setUsernameDetails(newChatMessage, chatboxUserInner, chatboxUser)
|
setUsernameDetails(newChatMessage, chatboxUserInner, true)
|
||||||
}
|
}
|
||||||
loadReplyLine(chatboxMessage)
|
loadReplyLine(chatboxMessage)
|
||||||
loadMessageMentions(chatboxMessage)
|
loadMessageMentions(chatboxMessage)
|
||||||
@@ -214,24 +223,30 @@ function loadReplyLine(chatboxMessage) {
|
|||||||
if (!replyUsername) {
|
if (!replyUsername) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (replyUsername.querySelector('.obf-name')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const chatMessage = {
|
const chatMessage = {
|
||||||
username: replyUsername.textContent.replace(/^@/, ''),
|
username: replyUsername.textContent.replace(/^@/, ''),
|
||||||
isMod: false,
|
isMod: false,
|
||||||
}
|
}
|
||||||
const newChatMessage = obfuscator(chatMessage)
|
const newChatMessage = obfuscator(chatMessage)
|
||||||
if (newChatMessage !== null) {
|
if (newChatMessage !== null) {
|
||||||
setUsernameDetails(newChatMessage, replyUsername, void 0, '@')
|
setUsernameDetails(newChatMessage, replyUsername, false, '@')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function loadMessageMentions(chatboxMessage) {
|
function loadMessageMentions(chatboxMessage) {
|
||||||
function eachMention(messageMention) {
|
function eachMention(messageMention) {
|
||||||
|
if (messageMention.querySelector('.obf-name')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const chatMessage = {
|
const chatMessage = {
|
||||||
username: messageMention.textContent.replace(/^@/, ''),
|
username: messageMention.textContent.replace(/^@/, ''),
|
||||||
isMod: false,
|
isMod: false,
|
||||||
}
|
}
|
||||||
const newChatMessage = obfuscator(chatMessage)
|
const newChatMessage = obfuscator(chatMessage)
|
||||||
if (newChatMessage !== null) {
|
if (newChatMessage !== null) {
|
||||||
setUsernameDetails(newChatMessage, messageMention, void 0, '@')
|
setUsernameDetails(newChatMessage, messageMention, false, '@')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const messageMentions = chatboxMessage.querySelectorAll(
|
const messageMentions = chatboxMessage.querySelectorAll(
|
||||||
@@ -244,8 +259,10 @@ function loadMessageMentions(chatboxMessage) {
|
|||||||
function loadAdditionalUserNames(chatboxMessage) {
|
function loadAdditionalUserNames(chatboxMessage) {
|
||||||
const chatterNames = chatboxMessage.querySelectorAll('.chatter-name')
|
const chatterNames = chatboxMessage.querySelectorAll('.chatter-name')
|
||||||
for (const chatterName of chatterNames) {
|
for (const chatterName of chatterNames) {
|
||||||
chatboxMessage.classList.add('obf-loaded')
|
|
||||||
const chatterNameBox = innermostElement(chatterName)
|
const chatterNameBox = innermostElement(chatterName)
|
||||||
|
if (chatterNameBox.querySelector('.obf-name')) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
const username = chatterNameBox.textContent
|
const username = chatterNameBox.textContent
|
||||||
if (!username) {
|
if (!username) {
|
||||||
continue
|
continue
|
||||||
@@ -260,53 +277,72 @@ function loadAdditionalUserNames(chatboxMessage) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function setUsernameDetails(newChatMessage, textbox, colorbox, prefixStr = '') {
|
function setUsernameDetails(
|
||||||
const username = `${newChatMessage.username}${usernameTemplateSuffix(newChatMessage)}`
|
newChatMessage,
|
||||||
|
usernamebox,
|
||||||
|
doColor = false,
|
||||||
|
prefix = ''
|
||||||
|
) {
|
||||||
|
const suffix = usernameExtraSuffix(newChatMessage)
|
||||||
|
const username = `${newChatMessage.username}${suffix}`
|
||||||
|
const container = document.createElement('span')
|
||||||
|
container.classList.add('obf-name')
|
||||||
const imageName = newChatMessage.image
|
const imageName = newChatMessage.image
|
||||||
if (imageName) {
|
if (imageName) {
|
||||||
const image = nameImages[imageName]
|
const image = nameImages[imageName]
|
||||||
const img = document.createElement('img')
|
const imgElem = document.createElement('img')
|
||||||
img.classList.add('obf-image', 'ffz--pointer-events', 'ffz-tooltip')
|
imgElem.classList.add('obf-image', 'ffz--pointer-events', 'ffz-tooltip')
|
||||||
img.setAttribute('data-tooltip-type', 'html')
|
imgElem.setAttribute('data-tooltip-type', 'html')
|
||||||
img.setAttribute('data-title', username)
|
imgElem.setAttribute('data-title', username)
|
||||||
img.setAttribute('alt', username)
|
imgElem.setAttribute('alt', username)
|
||||||
img.setAttribute('src', image)
|
imgElem.setAttribute('src', image)
|
||||||
const prefix = document.createElement('span')
|
const prefixElem = document.createElement('span')
|
||||||
prefix.textContent = prefixStr
|
prefixElem.textContent = prefix
|
||||||
const suffix = document.createElement('span')
|
const suffixElem = document.createElement('span')
|
||||||
suffix.textContent = usernameImageTemplateSuffix(newChatMessage)
|
suffixElem.textContent = suffix
|
||||||
textbox.replaceChildren(prefix, img, suffix)
|
container.replaceChildren(prefixElem, imgElem, suffixElem)
|
||||||
} else {
|
} else {
|
||||||
textbox.textContent = `${prefixStr}${username}`
|
container.textContent = `${prefix}${username}`
|
||||||
}
|
}
|
||||||
if (colorbox) {
|
if (doColor) {
|
||||||
colorbox.style.color = newChatMessage.color
|
container.style.color = newChatMessage.color
|
||||||
|
}
|
||||||
|
usernamebox.replaceChildren(container)
|
||||||
|
}
|
||||||
|
function eachMutation(record) {
|
||||||
|
const target = record.target
|
||||||
|
if (target.classList.contains('chat-scrollable-area__message-container')) {
|
||||||
|
for (const target2 of record.addedNodes) {
|
||||||
|
eachMutationTarget(record, target2)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eachMutationTarget(record, record.target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function eachMutationTarget(record, target) {
|
||||||
var OBSERVER_RATE_LIMIT = 1
|
const chatboxMessage = elementTreeFind(
|
||||||
var observerLastRun = 0
|
target,
|
||||||
function observerCallback() {
|
elem =>
|
||||||
if (Date.now() - observerLastRun < OBSERVER_RATE_LIMIT) {
|
elem.parentElement?.classList.contains(
|
||||||
return
|
'chat-scrollable-area__message-container'
|
||||||
}
|
) || false
|
||||||
observerLastRun = Date.now()
|
|
||||||
const chatbox = document.querySelector(
|
|
||||||
'.chat-scrollable-area__message-container'
|
|
||||||
)
|
)
|
||||||
const chatboxMessages = Array.from(chatbox?.children ?? [])
|
if (chatboxMessage) {
|
||||||
for (const chatboxMessage of chatboxMessages) {
|
console.warn('[OBFUSCATOR] mutated message', chatboxMessage, record)
|
||||||
if (chatboxMessage.classList.contains('obf-loaded')) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
loadChatMessage(chatboxMessage)
|
loadChatMessage(chatboxMessage)
|
||||||
loadAdditionalUserNames(chatboxMessage)
|
loadAdditionalUserNames(chatboxMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var observer = new MutationObserver(observerCallback)
|
|
||||||
|
var observer = new MutationObserver(mutationRecords => {
|
||||||
|
for (const record of mutationRecords) {
|
||||||
|
eachMutation(record)
|
||||||
|
}
|
||||||
|
})
|
||||||
observer.observe(document, { attributes: true, childList: true, subtree: true })
|
observer.observe(document, { attributes: true, childList: true, subtree: true })
|
||||||
var styleAddition = document.createElement('style')
|
var styleAddition = document.createElement('style')
|
||||||
styleAddition.innerHTML = `
|
styleAddition.innerHTML = `
|
||||||
|
/* hide badges, except broadcaster and moderator */
|
||||||
.chat-line__message--badges {
|
.chat-line__message--badges {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -321,10 +357,17 @@ styleAddition.innerHTML = `
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* disable 7tv paint colors */
|
||||||
.seventv-paint {
|
.seventv-paint {
|
||||||
background-image: none !important;
|
background-image: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* disable username translations */
|
||||||
|
.chat-author__intl-login {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* style image when used as name */
|
||||||
img.obf-image {
|
img.obf-image {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin: -0.5rem 0;
|
margin: -0.5rem 0;
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
import { obfuscator } from './obfuscator'
|
import { obfuscator } from './obfuscator'
|
||||||
import { NameConfigInstance, nameImages } from './options'
|
import { NameConfigInstance, nameImages, usernameExtraSuffix } from './options'
|
||||||
import {
|
import { ChatMessage, elementTreeFind, innermostElement } from './util'
|
||||||
ChatMessage,
|
|
||||||
innermostElement,
|
|
||||||
usernameImageTemplateSuffix,
|
|
||||||
usernameTemplateSuffix,
|
|
||||||
} from './util'
|
|
||||||
|
|
||||||
export function loadChatMessage(chatboxMessage: Element) {
|
function loadChatMessage(chatboxMessage: Element) {
|
||||||
// only chat messages
|
// only chat messages
|
||||||
const chatboxMessageInner = chatboxMessage.querySelector(
|
const chatboxMessageInner = chatboxMessage.querySelector(
|
||||||
'.chat-line__message'
|
'.chat-line__message'
|
||||||
@@ -20,10 +15,6 @@ export function loadChatMessage(chatboxMessage: Element) {
|
|||||||
const chatboxBadgeContainer = chatboxMessage.querySelector<HTMLSpanElement>(
|
const chatboxBadgeContainer = chatboxMessage.querySelector<HTMLSpanElement>(
|
||||||
'.chat-line__message--badges'
|
'.chat-line__message--badges'
|
||||||
)
|
)
|
||||||
if (!chatboxBadgeContainer) {
|
|
||||||
console.error("found message, couldn't find badges")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const chatboxUser = chatboxMessage.querySelector<HTMLSpanElement>(
|
const chatboxUser = chatboxMessage.querySelector<HTMLSpanElement>(
|
||||||
'.chat-line__username'
|
'.chat-line__username'
|
||||||
@@ -33,6 +24,11 @@ export function loadChatMessage(chatboxMessage: Element) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// already applied username
|
||||||
|
if (chatboxUser.querySelector('.obf-name')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const chatboxUserInner = chatboxUser.querySelector<HTMLSpanElement>(
|
const chatboxUserInner = chatboxUser.querySelector<HTMLSpanElement>(
|
||||||
'.chat-author__display-name'
|
'.chat-author__display-name'
|
||||||
)
|
)
|
||||||
@@ -41,24 +37,18 @@ export function loadChatMessage(chatboxMessage: Element) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// got the data, so we're loaded
|
|
||||||
chatboxMessage.classList.add('obf-loaded')
|
|
||||||
|
|
||||||
// hide 7tv extra colors
|
|
||||||
if (chatboxUser.classList.contains('seventv-paint')) {
|
|
||||||
chatboxUser.classList.remove('seventv-paint')
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if mod
|
// check if mod
|
||||||
let isMod = false
|
let isMod = false
|
||||||
for (const badge of chatboxBadgeContainer.children) {
|
if (chatboxBadgeContainer) {
|
||||||
if (
|
for (const badge of chatboxBadgeContainer.children) {
|
||||||
badge.hasAttribute('data-badge') &&
|
if (
|
||||||
(badge.getAttribute('data-badge') === 'moderator' ||
|
badge.hasAttribute('data-badge') &&
|
||||||
badge.getAttribute('data-badge') === 'broadcaster')
|
(badge.getAttribute('data-badge') === 'moderator' ||
|
||||||
) {
|
badge.getAttribute('data-badge') === 'broadcaster')
|
||||||
isMod = true
|
) {
|
||||||
chatboxMessage.classList.add('ismod')
|
isMod = true
|
||||||
|
chatboxMessage.classList.add('ismod')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +62,7 @@ export function loadChatMessage(chatboxMessage: Element) {
|
|||||||
const newChatMessage = obfuscator(chatMessage)
|
const newChatMessage = obfuscator(chatMessage)
|
||||||
|
|
||||||
if (newChatMessage !== null) {
|
if (newChatMessage !== null) {
|
||||||
setUsernameDetails(newChatMessage, chatboxUserInner, chatboxUser)
|
setUsernameDetails(newChatMessage, chatboxUserInner, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
loadReplyLine(chatboxMessage)
|
loadReplyLine(chatboxMessage)
|
||||||
@@ -89,6 +79,11 @@ function loadReplyLine(chatboxMessage: Element) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// already applied username
|
||||||
|
if (replyUsername.querySelector('.obf-name')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const chatMessage: ChatMessage = {
|
const chatMessage: ChatMessage = {
|
||||||
username: replyUsername.textContent.replace(/^@/, ''),
|
username: replyUsername.textContent.replace(/^@/, ''),
|
||||||
isMod: false,
|
isMod: false,
|
||||||
@@ -98,12 +93,17 @@ function loadReplyLine(chatboxMessage: Element) {
|
|||||||
const newChatMessage = obfuscator(chatMessage)
|
const newChatMessage = obfuscator(chatMessage)
|
||||||
|
|
||||||
if (newChatMessage !== null) {
|
if (newChatMessage !== null) {
|
||||||
setUsernameDetails(newChatMessage, replyUsername, undefined, '@')
|
setUsernameDetails(newChatMessage, replyUsername, false, '@')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadMessageMentions(chatboxMessage: Element) {
|
function loadMessageMentions(chatboxMessage: Element) {
|
||||||
function eachMention(messageMention: HTMLElement) {
|
function eachMention(messageMention: HTMLElement) {
|
||||||
|
// already applied username
|
||||||
|
if (messageMention.querySelector('.obf-name')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const chatMessage: ChatMessage = {
|
const chatMessage: ChatMessage = {
|
||||||
username: messageMention.textContent.replace(/^@/, ''),
|
username: messageMention.textContent.replace(/^@/, ''),
|
||||||
isMod: false,
|
isMod: false,
|
||||||
@@ -113,7 +113,7 @@ function loadMessageMentions(chatboxMessage: Element) {
|
|||||||
const newChatMessage = obfuscator(chatMessage)
|
const newChatMessage = obfuscator(chatMessage)
|
||||||
|
|
||||||
if (newChatMessage !== null) {
|
if (newChatMessage !== null) {
|
||||||
setUsernameDetails(newChatMessage, messageMention, undefined, '@')
|
setUsernameDetails(newChatMessage, messageMention, false, '@')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,15 +126,21 @@ function loadMessageMentions(chatboxMessage: Element) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadAdditionalUserNames(chatboxMessage: Element) {
|
function loadAdditionalUserNames(chatboxMessage: Element) {
|
||||||
// look for additional chat member names
|
// look for additional chat member names
|
||||||
const chatterNames =
|
const chatterNames =
|
||||||
chatboxMessage.querySelectorAll<HTMLElement>('.chatter-name')
|
chatboxMessage.querySelectorAll<HTMLElement>('.chatter-name')
|
||||||
|
|
||||||
for (const chatterName of chatterNames) {
|
for (const chatterName of chatterNames) {
|
||||||
chatboxMessage.classList.add('obf-loaded')
|
const chatterNameBox = innermostElement<HTMLElement, HTMLElement>(
|
||||||
|
chatterName
|
||||||
|
)
|
||||||
|
|
||||||
|
// already applied username
|
||||||
|
if (chatterNameBox.querySelector('.obf-name')) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
const chatterNameBox = innermostElement(chatterName)
|
|
||||||
const username = chatterNameBox.textContent
|
const username = chatterNameBox.textContent
|
||||||
if (!username) {
|
if (!username) {
|
||||||
continue
|
continue
|
||||||
@@ -157,37 +163,69 @@ export function loadAdditionalUserNames(chatboxMessage: Element) {
|
|||||||
|
|
||||||
function setUsernameDetails(
|
function setUsernameDetails(
|
||||||
newChatMessage: NameConfigInstance,
|
newChatMessage: NameConfigInstance,
|
||||||
textbox: HTMLElement,
|
usernamebox: HTMLElement,
|
||||||
colorbox?: HTMLElement,
|
doColor = false,
|
||||||
prefixStr: string = ''
|
prefix: string = ''
|
||||||
) {
|
) {
|
||||||
const username = `${newChatMessage.username}${usernameTemplateSuffix(newChatMessage)}`
|
const suffix = usernameExtraSuffix(newChatMessage)
|
||||||
|
const username = `${newChatMessage.username}${suffix}`
|
||||||
|
|
||||||
|
const container = document.createElement('span')
|
||||||
|
container.classList.add('obf-name')
|
||||||
|
|
||||||
const imageName = newChatMessage.image
|
const imageName = newChatMessage.image
|
||||||
if (imageName) {
|
if (imageName) {
|
||||||
// image name
|
// image name
|
||||||
const image = nameImages[imageName]
|
const image = nameImages[imageName]
|
||||||
|
|
||||||
const img = document.createElement('img')
|
const imgElem = document.createElement('img')
|
||||||
img.classList.add('obf-image', 'ffz--pointer-events', 'ffz-tooltip')
|
imgElem.classList.add('obf-image', 'ffz--pointer-events', 'ffz-tooltip')
|
||||||
img.setAttribute('data-tooltip-type', 'html')
|
imgElem.setAttribute('data-tooltip-type', 'html')
|
||||||
img.setAttribute('data-title', username)
|
imgElem.setAttribute('data-title', username)
|
||||||
img.setAttribute('alt', username)
|
imgElem.setAttribute('alt', username)
|
||||||
img.setAttribute('src', image)
|
imgElem.setAttribute('src', image)
|
||||||
|
|
||||||
const prefix = document.createElement('span')
|
const prefixElem = document.createElement('span')
|
||||||
prefix.textContent = prefixStr
|
prefixElem.textContent = prefix
|
||||||
|
|
||||||
const suffix = document.createElement('span')
|
const suffixElem = document.createElement('span')
|
||||||
suffix.textContent = usernameImageTemplateSuffix(newChatMessage)
|
suffixElem.textContent = suffix
|
||||||
|
|
||||||
textbox.replaceChildren(prefix, img, suffix)
|
container.replaceChildren(prefixElem, imgElem, suffixElem)
|
||||||
} else {
|
} else {
|
||||||
// text name only
|
// text name only
|
||||||
textbox.textContent = `${prefixStr}${username}`
|
container.textContent = `${prefix}${username}`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (colorbox) {
|
if (doColor) {
|
||||||
colorbox.style.color = newChatMessage.color
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,17 @@
|
|||||||
import { loadAdditionalUserNames, loadChatMessage } from './dom'
|
import { eachMutation } from './dom'
|
||||||
|
|
||||||
const OBSERVER_RATE_LIMIT = 1
|
|
||||||
let observerLastRun: number = 0
|
|
||||||
|
|
||||||
function observerCallback() {
|
|
||||||
if (Date.now() - observerLastRun < OBSERVER_RATE_LIMIT) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
observerLastRun = Date.now()
|
|
||||||
|
|
||||||
const chatbox = document.querySelector<HTMLDivElement>(
|
|
||||||
'.chat-scrollable-area__message-container'
|
|
||||||
)
|
|
||||||
const chatboxMessages = Array.from(chatbox?.children ?? [])
|
|
||||||
|
|
||||||
for (const chatboxMessage of chatboxMessages) {
|
|
||||||
// set flag on message
|
|
||||||
if (chatboxMessage.classList.contains('obf-loaded')) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
loadChatMessage(chatboxMessage)
|
|
||||||
loadAdditionalUserNames(chatboxMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// attach observer
|
// attach observer
|
||||||
const observer = new MutationObserver(observerCallback)
|
const observer = new MutationObserver((mutationRecords: MutationRecord[]) => {
|
||||||
|
for (const record of mutationRecords) {
|
||||||
|
eachMutation(record)
|
||||||
|
}
|
||||||
|
})
|
||||||
observer.observe(document, { attributes: true, childList: true, subtree: true })
|
observer.observe(document, { attributes: true, childList: true, subtree: true })
|
||||||
|
|
||||||
// attach styles
|
// attach styles
|
||||||
const styleAddition = document.createElement('style')
|
const styleAddition = document.createElement('style')
|
||||||
styleAddition.innerHTML = `
|
styleAddition.innerHTML = `
|
||||||
|
/* hide badges, except broadcaster and moderator */
|
||||||
.chat-line__message--badges {
|
.chat-line__message--badges {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -46,10 +26,17 @@ styleAddition.innerHTML = `
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* disable 7tv paint colors */
|
||||||
.seventv-paint {
|
.seventv-paint {
|
||||||
background-image: none !important;
|
background-image: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* disable username translations */
|
||||||
|
.chat-author__intl-login {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* style image when used as name */
|
||||||
img.obf-image {
|
img.obf-image {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin: -0.5rem 0;
|
margin: -0.5rem 0;
|
||||||
|
|||||||
@@ -5,5 +5,6 @@
|
|||||||
"namespace": "zomo.dev",
|
"namespace": "zomo.dev",
|
||||||
"match": ["https://www.twitch.tv/*"],
|
"match": ["https://www.twitch.tv/*"],
|
||||||
"version": "0.1",
|
"version": "0.1",
|
||||||
"runat": "document-end"
|
"runat": "document-end",
|
||||||
|
"grant": ["unsafeWindow"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ export function obfuscator(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`[OBFUSCATOR] Updating Username: ${chatMessage.username}`)
|
||||||
|
|
||||||
chatMessage.username = chatMessage.username.toLowerCase()
|
chatMessage.username = chatMessage.username.toLowerCase()
|
||||||
|
|
||||||
// return stored data
|
// return stored data
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ export interface NameConfigInstance {
|
|||||||
nameCount: number
|
nameCount: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UsernameExtraSuffix = (nameConfig: NameConfigInstance) => string
|
||||||
|
|
||||||
export const ignoreMod: boolean = false
|
export const ignoreMod: boolean = false
|
||||||
|
|
||||||
export const nameImages = {
|
export const nameImages = {
|
||||||
@@ -40,3 +42,6 @@ export const nameList: NameConfig[] = [
|
|||||||
// image: 'personimage1',
|
// image: 'personimage1',
|
||||||
// },
|
// },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const usernameExtraSuffix: UsernameExtraSuffix = nameConfig =>
|
||||||
|
nameConfig.nameCount > 0 ? `${nameConfig.nameCount + 1}` : ''
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { NameConfigInstance, NameConfig, nameList, colorList } from './options'
|
import { NameConfigInstance, nameList, colorList } from './options'
|
||||||
|
|
||||||
type StoredUsers = {
|
type StoredUsers = {
|
||||||
[username: string]: NameConfigInstance
|
[username: string]: NameConfigInstance
|
||||||
@@ -72,7 +72,17 @@ function loadStoredUserData(): StoredData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO will be useful for better options
|
// TODO will be useful for better options
|
||||||
export function clearStoredUsers() {}
|
export function clearStoredUsers() {
|
||||||
|
localStorage.removeItem('obf-data')
|
||||||
|
storedUsers = {}
|
||||||
|
nameListUsed = {}
|
||||||
|
nameListCount = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
;(unsafeWindow as any).obfClear = () => {
|
||||||
|
clearStoredUsers()
|
||||||
|
location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
export function getRandomName(): NameConfigInstance {
|
export function getRandomName(): NameConfigInstance {
|
||||||
const startingIndex = Math.round(Math.random() * (nameList.length - 1))
|
const startingIndex = Math.round(Math.random() * (nameList.length - 1))
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# obfuscator todo list
|
# obfuscator todo list
|
||||||
|
|
||||||
- can i detect when ffz loads?
|
|
||||||
- better options
|
- better options
|
||||||
|
|
||||||
## known issues
|
## known issues
|
||||||
@@ -44,5 +43,25 @@ watch streaks
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
raid message
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="Layout-sc-1xcs6mc-0">
|
||||||
|
<div class="Layout-sc-1xcs6mc-0">
|
||||||
|
<div class="Layout-sc-1xcs6mc-0 ikqKEy">
|
||||||
|
<div style="background: rgb(100, 65, 165);" class="Layout-sc-1xcs6mc-0 ijxYla"></div>
|
||||||
|
<div style="overflow-wrap: break-word;" data-test-selector="user-notice-line"
|
||||||
|
class="Layout-sc-1xcs6mc-0 cglxbn">
|
||||||
|
<div class="Layout-sc-1xcs6mc-0 fHdBNk">
|
||||||
|
<div class="Layout-sc-1xcs6mc-0 hgZmyJ">
|
||||||
|
<div class="Layout-sc-1xcs6mc-0 jfyitl"><strong>thezomo</strong> is raiding with a party of
|
||||||
|
<strong>12</strong>.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,31 +1,33 @@
|
|||||||
import { NameConfigInstance } from './options'
|
|
||||||
|
|
||||||
export interface ChatMessage {
|
export interface ChatMessage {
|
||||||
username: string
|
username: string
|
||||||
isMod: boolean
|
isMod: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function innermostElement<T extends Element>(elem: T) {
|
export function innermostElement<T extends Element, U extends Element>(
|
||||||
|
elem: T
|
||||||
|
): U {
|
||||||
if (elem.children.length === 0) {
|
if (elem.children.length === 0) {
|
||||||
return elem
|
// U is the type of the innermost element
|
||||||
|
// since we're at the innermost element, we know T = U
|
||||||
|
// so we can cast elem (T) to U
|
||||||
|
return elem as any as U
|
||||||
}
|
}
|
||||||
return innermostElement(elem.children[0])
|
let child = elem.children[0]
|
||||||
|
return innermostElement<Element, U>(child)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usernameTemplateSuffix(newChatMessage: NameConfigInstance) {
|
export function elementTreeFind<T extends Element, U extends Element>(
|
||||||
if (newChatMessage.nameCount === 0) {
|
elem: T,
|
||||||
return ''
|
fn: (elem: Element) => boolean
|
||||||
|
): U | null {
|
||||||
|
if (fn(elem)) {
|
||||||
|
// U is the type of the element that satisfies fn()
|
||||||
|
// since we're at the element that satisfies fn(), we know T = U
|
||||||
|
// so we can cast elem (T) to U
|
||||||
|
return elem as any as U
|
||||||
}
|
}
|
||||||
|
if (!elem.parentElement) {
|
||||||
return `${newChatMessage.nameCount}`
|
return null
|
||||||
}
|
|
||||||
|
|
||||||
export function usernameImageTemplateSuffix(
|
|
||||||
newChatMessage: NameConfigInstance
|
|
||||||
) {
|
|
||||||
if (newChatMessage.nameCount === 0) {
|
|
||||||
return ''
|
|
||||||
}
|
}
|
||||||
|
return elementTreeFind<Element, U>(elem.parentElement, fn)
|
||||||
return `${newChatMessage.nameCount}`
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user