functional ttv-obfuscated-names
This commit is contained in:
Vendored
+335
File diff suppressed because one or more lines are too long
@@ -17,6 +17,10 @@ Requires Violentmonkey (or Tampermonkey etc): [Chrome](https://chrome.google.com
|
||||
- **zomo.dev** v1.0
|
||||
- `https://optifine.net/*`
|
||||
- automatically grab optifine links through the ads
|
||||
- [ttv obfuscated names](https://git.zomo.dev/zomo/browser-scripts/raw/branch/main/dist/ttv-obfuscated-names.user.js)
|
||||
- **zomo.dev** v0.1
|
||||
- `https://www.twitch.tv/*`
|
||||
-
|
||||
- [Twitch Clickable Video](https://git.zomo.dev/zomo/browser-scripts/raw/branch/main/dist/twitch-clickable-video.user.js)
|
||||
- **zomo.dev** v1.1
|
||||
- `https://www.twitch.tv/*`
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
import { obfuscator } from './obfuscator'
|
||||
import { NameConfigInstance, nameImages } from './options'
|
||||
import {
|
||||
ChatMessage,
|
||||
innermostElement,
|
||||
usernameImageTemplateSuffix,
|
||||
usernameTemplateSuffix,
|
||||
} from './util'
|
||||
|
||||
export 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'
|
||||
)
|
||||
if (!chatboxBadgeContainer) {
|
||||
console.error("found message, couldn't find badges")
|
||||
return
|
||||
}
|
||||
|
||||
const chatboxUser = chatboxMessage.querySelector<HTMLSpanElement>(
|
||||
'.chat-line__username'
|
||||
)
|
||||
if (!chatboxUser) {
|
||||
console.error("found message, couldn't find user")
|
||||
return
|
||||
}
|
||||
|
||||
const chatboxUserInner = chatboxUser.querySelector<HTMLSpanElement>(
|
||||
'.chat-author__display-name'
|
||||
)
|
||||
if (!chatboxUserInner) {
|
||||
console.error("found message, couldn't find userInner")
|
||||
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
|
||||
let isMod = false
|
||||
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, chatboxUser)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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, undefined, '@')
|
||||
}
|
||||
}
|
||||
|
||||
function loadMessageMentions(chatboxMessage: Element) {
|
||||
function eachMention(messageMention: HTMLElement) {
|
||||
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, undefined, '@')
|
||||
}
|
||||
}
|
||||
|
||||
const messageMentions = chatboxMessage.querySelectorAll<HTMLElement>(
|
||||
'.chat-line__message-mention'
|
||||
)
|
||||
|
||||
for (const messageMention of messageMentions) {
|
||||
eachMention(messageMention)
|
||||
}
|
||||
}
|
||||
|
||||
export function loadAdditionalUserNames(chatboxMessage: Element) {
|
||||
// look for additional chat member names
|
||||
const chatterNames =
|
||||
chatboxMessage.querySelectorAll<HTMLElement>('.chatter-name')
|
||||
|
||||
for (const chatterName of chatterNames) {
|
||||
chatboxMessage.classList.add('obf-loaded')
|
||||
|
||||
const chatterNameBox = innermostElement(chatterName)
|
||||
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,
|
||||
textbox: HTMLElement,
|
||||
colorbox?: HTMLElement,
|
||||
prefixStr: string = ''
|
||||
) {
|
||||
const username = `${newChatMessage.username}${usernameTemplateSuffix(newChatMessage)}`
|
||||
|
||||
const imageName = newChatMessage.image
|
||||
if (imageName) {
|
||||
// image name
|
||||
const image = nameImages[imageName]
|
||||
|
||||
const img = document.createElement('img')
|
||||
img.classList.add('obf-image', 'ffz--pointer-events', 'ffz-tooltip')
|
||||
img.setAttribute('data-tooltip-type', 'html')
|
||||
img.setAttribute('data-title', username)
|
||||
img.setAttribute('alt', username)
|
||||
img.setAttribute('src', image)
|
||||
|
||||
const prefix = document.createElement('span')
|
||||
prefix.textContent = prefixStr
|
||||
|
||||
const suffix = document.createElement('span')
|
||||
suffix.textContent = usernameImageTemplateSuffix(newChatMessage)
|
||||
|
||||
textbox.replaceChildren(prefix, img, suffix)
|
||||
} else {
|
||||
// text name only
|
||||
textbox.textContent = `${prefixStr}${username}`
|
||||
}
|
||||
|
||||
if (colorbox) {
|
||||
colorbox.style.color = newChatMessage.color
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { loadAdditionalUserNames, loadChatMessage } 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
|
||||
const observer = new MutationObserver(observerCallback)
|
||||
observer.observe(document, { attributes: true, childList: true, subtree: true })
|
||||
|
||||
// attach styles
|
||||
const styleAddition = document.createElement('style')
|
||||
styleAddition.innerHTML = `
|
||||
.chat-line__message--badges {
|
||||
display: none;
|
||||
}
|
||||
.ismod .chat-line__message--badges {
|
||||
display: inline;
|
||||
}
|
||||
.ismod .chat-line__message--badges > span {
|
||||
display: none;
|
||||
}
|
||||
.ismod .chat-line__message--badges > span[data-badge="moderator"],
|
||||
.ismod .chat-line__message--badges > span[data-badge="broadcaster"] {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.seventv-paint {
|
||||
background-image: none !important;
|
||||
}
|
||||
|
||||
img.obf-image {
|
||||
vertical-align: middle;
|
||||
margin: -0.5rem 0;
|
||||
|
||||
height: 1.8rem;
|
||||
}
|
||||
`
|
||||
document.head.appendChild(styleAddition)
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "https://git.zomo.dev/zomo/browser-scripts-builder/raw/branch/main/meta.schema.json",
|
||||
"name": "ttv obfuscated names",
|
||||
"description": "",
|
||||
"namespace": "zomo.dev",
|
||||
"match": ["https://www.twitch.tv/*"],
|
||||
"version": "0.1",
|
||||
"runat": "document-end"
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { NameConfigInstance, ignoreMod } from './options'
|
||||
import { getRandomName, getStoredUser, setStoredUser } from './storage'
|
||||
import { ChatMessage } from './util'
|
||||
|
||||
export function obfuscator(
|
||||
chatMessage: ChatMessage
|
||||
): NameConfigInstance | null {
|
||||
if (ignoreMod && chatMessage.isMod) {
|
||||
return null
|
||||
}
|
||||
|
||||
chatMessage.username = chatMessage.username.toLowerCase()
|
||||
|
||||
// return stored data
|
||||
const userData = getStoredUser(chatMessage.username)
|
||||
if (userData !== null) {
|
||||
return userData
|
||||
}
|
||||
|
||||
// store new data
|
||||
const newName = getRandomName()
|
||||
setStoredUser(chatMessage.username, newName)
|
||||
|
||||
return newName
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,130 @@
|
||||
import { NameConfigInstance, NameConfig, nameList, colorList } from './options'
|
||||
|
||||
type StoredUsers = {
|
||||
[username: string]: NameConfigInstance
|
||||
}
|
||||
type NameListUsed = {
|
||||
[username: string]: number
|
||||
}
|
||||
|
||||
interface StoredData {
|
||||
storedUsers: StoredUsers
|
||||
nameListUsed: NameListUsed
|
||||
nameListCount: number
|
||||
}
|
||||
|
||||
let { storedUsers, nameListUsed, nameListCount } = loadStoredUserData()
|
||||
|
||||
export function getStoredUser(name: string): NameConfigInstance | null {
|
||||
if (name in storedUsers) {
|
||||
return storedUsers[name]
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function setStoredUser(name: string, details: NameConfigInstance) {
|
||||
storedUsers[name] = details
|
||||
updateStorageData()
|
||||
}
|
||||
|
||||
export function increaseNameListUsed(name: string) {
|
||||
nameListUsed[name] = (nameListUsed[name] ?? 0) + 1
|
||||
updateStorageData()
|
||||
}
|
||||
|
||||
export function increaseNameListCount() {
|
||||
nameListCount++
|
||||
updateStorageData()
|
||||
}
|
||||
|
||||
function updateStorageData() {
|
||||
localStorage.setItem(
|
||||
'obf-data',
|
||||
JSON.stringify({
|
||||
storedUsers,
|
||||
nameListUsed,
|
||||
nameListCount,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function loadStoredUserData(): StoredData {
|
||||
const dataStr = localStorage.getItem('obf-data')
|
||||
if (dataStr === null) {
|
||||
return {
|
||||
storedUsers: {},
|
||||
nameListUsed: {},
|
||||
nameListCount: 0,
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.parse(dataStr)
|
||||
return data as StoredData
|
||||
} catch (e) {
|
||||
return {
|
||||
storedUsers: {},
|
||||
nameListUsed: {},
|
||||
nameListCount: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO will be useful for better options
|
||||
export function clearStoredUsers() {}
|
||||
|
||||
export function getRandomName(): NameConfigInstance {
|
||||
const startingIndex = Math.round(Math.random() * (nameList.length - 1))
|
||||
let foundName = false
|
||||
let currentIndex = startingIndex
|
||||
|
||||
for (let i = 0; i < nameList.length; i++) {
|
||||
currentIndex = (startingIndex + i) % nameList.length
|
||||
|
||||
let isUsed = nameListUsed[nameList[currentIndex].username] ?? 0
|
||||
if (isUsed === nameListCount) {
|
||||
foundName = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundName) {
|
||||
increaseNameListCount()
|
||||
currentIndex = startingIndex
|
||||
}
|
||||
|
||||
increaseNameListUsed(nameList[currentIndex].username)
|
||||
|
||||
const newName = nameList[currentIndex]
|
||||
const newNameInstance: NameConfigInstance = {
|
||||
username: newName.username,
|
||||
image: newName.image,
|
||||
color: '',
|
||||
nameCount: nameListCount,
|
||||
}
|
||||
|
||||
// choose a random color from the list
|
||||
if (Array.isArray(newName.color)) {
|
||||
const color =
|
||||
newName.color[
|
||||
Math.round(Math.random() * (newName.color.length - 1))
|
||||
]
|
||||
|
||||
newNameInstance.color = color
|
||||
return newNameInstance
|
||||
}
|
||||
|
||||
// choose a random color from the global list
|
||||
if (newName.color === null) {
|
||||
const color =
|
||||
colorList[Math.round(Math.random() * (colorList.length - 1))]
|
||||
|
||||
newNameInstance.color = color
|
||||
return newNameInstance
|
||||
}
|
||||
|
||||
// copy individual carbing
|
||||
newNameInstance.color = newName.color
|
||||
return newNameInstance
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
# obfuscator todo list
|
||||
|
||||
- can i detect when ffz loads?
|
||||
- better options
|
||||
|
||||
## known issues
|
||||
|
||||
watch streaks
|
||||
|
||||
- name is just in a string
|
||||
- there's no good ways to traverse the dom to reach the name's node
|
||||
|
||||
```html
|
||||
<div class="Layout-sc-1xcs6mc-0 obf-loaded">
|
||||
<div class="Layout-sc-1xcs6mc-0">
|
||||
<div class="Layout-sc-1xcs6mc-0 eCiQQE">
|
||||
<div style="background: var(--color-border-quote);" 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 hDlHnO">
|
||||
<div class="ScSvgWrapper-sc-wkgzod-0 dVDUDh tw-svg">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill-rule="evenodd"
|
||||
d="M5.295 8.05 10 2l3 4 2-3 3.8 5.067a11 11 0 0 1 2.2 6.6A7.333 7.333 0 0 1 13.667 22h-3.405A7.262 7.262 0 0 1 3 14.738c0-2.423.807-4.776 2.295-6.688Zm7.801 1.411 2-3L17.2 9.267a9 9 0 0 1 1.8 5.4 5.334 5.334 0 0 1-4.826 5.31 3 3 0 0 0 .174-3.748L12 13l-2.348 3.229a3 3 0 0 0 .18 3.754A5.263 5.263 0 0 1 5 14.738c0-1.978.66-3.9 1.873-5.46l3.098-3.983 3.125 4.166Z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="Layout-sc-1xcs6mc-0 doBHR">
|
||||
<div class="Layout-sc-1xcs6mc-0 hDlHnO">
|
||||
<span class="chatter-name chatter-name--no-outline" role="button"
|
||||
tabindex="0">
|
||||
<span class="CoreText-sc-1txzju1-0 cOQhxy">
|
||||
<span class="CoreText-sc-1txzju1-0 kJPnWP">
|
||||
<span></span>
|
||||
<img class="obf-image ffz--pointer-events ffz-tooltip"
|
||||
data-tooltip-type="html" data-title="person5" alt="person5"
|
||||
src="data:...">
|
||||
<span>2</span>
|
||||
</strong>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2021",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"types": [
|
||||
"../../node_modules/@types/greasemonkey",
|
||||
"../../node_modules/browser-scripts-builder"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { NameConfigInstance } from './options'
|
||||
|
||||
export interface ChatMessage {
|
||||
username: string
|
||||
isMod: boolean
|
||||
}
|
||||
|
||||
export function innermostElement<T extends Element>(elem: T) {
|
||||
if (elem.children.length === 0) {
|
||||
return elem
|
||||
}
|
||||
return innermostElement(elem.children[0])
|
||||
}
|
||||
|
||||
export function usernameTemplateSuffix(newChatMessage: NameConfigInstance) {
|
||||
if (newChatMessage.nameCount === 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return `${newChatMessage.nameCount}`
|
||||
}
|
||||
|
||||
export function usernameImageTemplateSuffix(
|
||||
newChatMessage: NameConfigInstance
|
||||
) {
|
||||
if (newChatMessage.nameCount === 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return `${newChatMessage.nameCount}`
|
||||
}
|
||||
Reference in New Issue
Block a user