Last active
June 10, 2023 23:01
-
-
Save Geczy/acbb9ca28c0d9a1c392474f2e59463b2 to your computer and use it in GitHub Desktop.
pyramid
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class TwitchPyramid { | |
ChatContainer = 'section[data-test-selector="chat-room-component-layout"]'; | |
ChatLine = '.chat-line__message'; | |
ROOT = '#root div'; | |
size; | |
constructor() { | |
this.store = this.getStore(); | |
this.chatService = this.getChatService(); | |
this.userLogin = this.chatService?.props?.currentUserLogin; | |
} | |
findReactParents = (node, predicate, maxDepth = 15, depth = 0) => { | |
let success = false; | |
try { | |
success = predicate(node); | |
} catch (_) {} | |
if (success) return node; | |
if (!node || depth > maxDepth) return null; | |
const { return: parent } = node; | |
if (parent) { | |
return this.findReactParents(parent, predicate, maxDepth, depth + 1); | |
} | |
return null; | |
}; | |
findReactChildren(node, predicate, maxDepth = 15, depth = 0) { | |
let success = false; | |
try { | |
success = predicate(node); | |
} catch (_) {} | |
if (success) return node; | |
if (!node || depth > maxDepth) return null; | |
const { child, sibling } = node; | |
if (child || sibling) { | |
return ( | |
this.findReactChildren(child, predicate, maxDepth, depth + 1) || | |
this.findReactChildren(sibling, predicate, maxDepth, depth + 1) | |
); | |
} | |
return null; | |
} | |
getReactInstance = (el) => { | |
for (const k in el) { | |
if (k.startsWith('__reactInternalInstance$')) { | |
return el[k]; | |
} | |
} | |
}; | |
getChatService = () => { | |
const node = this.findReactChildren( | |
this.getReactInstance(document.querySelectorAll(this.ROOT)[0]), | |
(n) => n.stateNode?.join && n.stateNode?.client, | |
1000 | |
); | |
return node?.stateNode; | |
}; | |
getChatLine = (el) => { | |
const inst = this.getReactInstance(el); | |
return { | |
component: inst?.return?.stateNode, | |
instance: inst, | |
}; | |
}; | |
/** | |
* Get chat lines with the element & react component, optionally filtered by an ID list | |
*/ | |
getChatLines = (idList = null) => { | |
let lines = Array.from(document.querySelectorAll(this.ChatLine)).map( | |
(element) => { | |
const chatLine = this.getChatLine(element); | |
return { | |
element, | |
component: chatLine.component, | |
inst: chatLine.instance, | |
}; | |
} | |
); | |
if (!!idList) { | |
lines = lines.filter(({ component }) => | |
idList?.includes(component?.props?.message?.id) | |
); | |
} | |
return lines; | |
}; | |
getChat = () => { | |
const node = this.findReactParents( | |
this.getReactInstance(document.querySelectorAll(this.ChatContainer)[0]), | |
(n) => n.stateNode?.props.onSendMessage | |
); | |
return node?.stateNode; | |
}; | |
getStore = () => { | |
let store = localStorage.getItem('twitch-pyramids'); | |
try { | |
store = JSON.parse(store); | |
} catch (e) { | |
store = []; | |
} | |
return store || []; | |
}; | |
save = (attempt) => { | |
this.store.push(attempt); | |
localStorage.setItem('twitch-pyramids', JSON.stringify(this.store)); | |
console.log(attempt); | |
}; | |
getViewers = () => { | |
const views = document.querySelector( | |
'[data-test-selector="stream-info-card-component__description"]' | |
); | |
if (!views) return; | |
return Number(views.textContent.replace('Dota 2', '').replace(/\D/g, '')); | |
}; | |
getCookieValue = (name) => | |
document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)')?.pop() || ''; | |
getInputValue = () => { | |
const el = document.querySelector('.chat-input textarea'); | |
if (el) return el.value; | |
return false; | |
}; | |
setInputValue = (value) => { | |
const el = document.querySelector('.chat-input textarea'); | |
if (!el) return false; | |
el.value = value; | |
el.dispatchEvent(new Event('input', { bubbles: true })); | |
const inst = this.getReactInstance(el); | |
if (inst) { | |
const props = inst.memoizedProps; | |
if (props && props.onChange) { | |
props.onChange({ target: el }); | |
} | |
} | |
}; | |
calculateInterruptions = () => { | |
const chatLines = this.getChatLines(); | |
const myIds = []; | |
// `some()` lets the loop quit early so it doesn't process the whole chat | |
chatLines.reverse().some((n) => { | |
if ( | |
n.component?.props?.message?.user?.login === this.userLogin && | |
n.component?.props?.message?.message.includes(this.emote) && | |
!n.component?.props?.message?.message.includes('HYPERCLAP') | |
) { | |
myIds.push(n.component?.props?.message?.id); | |
return false; | |
} | |
// If we found the end of the pyramid, quit the loop | |
if (myIds[this.size * 2 - 2]) { | |
return true; | |
} | |
return false; | |
}); | |
const pyramidEndId = myIds[this.size * 2 - 2]; | |
const pyramidStartId = myIds[0]; | |
let myPyramidStarted = false; | |
let myPyramidEnded = false; | |
let interruptions = 0; | |
chatLines.some((n) => { | |
if (myPyramidEnded) { | |
return true; | |
} | |
const chatLine = n.component?.props?.message; | |
const mine = chatLine?.user?.userLogin === this.userLogin; | |
if (pyramidStartId === chatLine.id) { | |
myPyramidStarted = true; | |
return false; | |
} | |
if (pyramidEndId === chatLine.id) { | |
myPyramidEnded = true; | |
return true; | |
} | |
if (myPyramidStarted && !mine) { | |
++interruptions; | |
return false; | |
} | |
}); | |
return interruptions; | |
}; | |
clickSend = () => { | |
document.querySelector('[data-a-target="chat-send-button"]').click(); | |
}; | |
send = (value, cb) => { | |
this.setInputValue(value); | |
this.clickSend(); | |
const keepSending = setInterval(() => { | |
if (!this.getInputValue()) { | |
clearInterval(keepSending); | |
cb(); | |
} else { | |
this.clickSend(); | |
} | |
}, 100); | |
}; | |
sleep = async (ms) => { | |
return new Promise((resolve) => setTimeout(resolve, ms)); | |
}; | |
run = async (emote = 'PepeLaugh', size = 5) => { | |
if (!this.userLogin) { | |
return 'No user found'; | |
} | |
if (!emote || !size) { | |
return 'Example usage: pyramid.run("PepeLA")'; | |
} | |
if (size < 2) { | |
return "Pyramid can't be smaller than 2 emoticons"; | |
} | |
this.size = size; | |
this.emote = emote; | |
const total = size * 2; | |
for (let i = 1; i < total; i++) { | |
await new Promise((resolve) => { | |
let n = i > size ? size * 2 - i : i; | |
this.send((emote + ' ').repeat(n), resolve); | |
}); | |
} | |
// Necessary to get latest chat lines | |
await this.sleep(500); | |
const interruptions = this.calculateInterruptions(); | |
this.save({ | |
interruptions, | |
emote, | |
size, | |
views: this.getViewers(), | |
user: this.userLogin, | |
}); | |
if (interruptions) { | |
await new Promise((resolve) => { | |
this.send( | |
`" ${emote} " pyramid had ${interruptions} interruptions pepeW`, | |
resolve | |
); | |
}); | |
return; | |
} | |
await new Promise((resolve) => { | |
this.send(`${emote} HYPERCLAP`, resolve); | |
}); | |
}; | |
} | |
let pyramid = new TwitchPyramid(); | |
pyramid.run(); |
How do I even use this? Please help
paste it into console on the twitch streamer's page
but it may not work anymore, shouldn't take much to fix tho
sorry for my ignorance, but what is supposed to happen when I paste it into console?
it builds a pyramid in chat
Welp It doesn't seem to work then, any chance you find a time to fix it? Thanks anyway
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
TamperMokey says that it's invalid