// ==UserScript== // @name WordSleuth // @namespace https://greasyfork.org/en/users/1084087-kueldev // @version 0.5.1 // @description A script that helps you guess words in skribblio // @author kueldev // @match http*://www.skribbl.io/* // @match http*://skribbl.io/* // @icon https://www.google.com/s2/favicons?sz=64&domain=skribbl.io // @grant GM_setValue // @grant GM_getValue // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; class WordSleuth { constructor() { this.correctAnswers = GM_getValue('correctAnswers', []); this.possibleWords = []; this.createParentElement(); this.createGuessElement(); this.createExportButton(); this.fetchAndStoreLatestWordlist(); this.observeHintsAndInput(); } createParentElement() { this.parentElement = document.createElement('div'); this.parentElement.style = 'position: fixed; bottom: 0; right: 0; width: 100%; height: auto;'; document.body.appendChild(this.parentElement); } createGuessElement() { this.guessElem = document.createElement('div'); this.guessElem.style = 'padding: 10px; background-color: white; max-height: 200px; overflow-x: auto; white-space: nowrap; width: 100%;'; this.parentElement.appendChild(this.guessElem); } createExportButton() { this.exportButton = document.createElement('button'); this.exportButton.innerHTML = 'Export Answers'; this.exportButton.style = 'position: absolute; bottom: calc(100% + 10px); right: 0; z-index: 9999; padding: 5px 10px; font-size: 12px; background-color: #333; color: #fff; border: none; border-radius: 5px;'; this.parentElement.appendChild(this.exportButton); this.exportButton.addEventListener('click', () => this.exportNewWords()); } exportNewWords() { this.fetchWords('https://raw.githubusercontent.com/kuel27/wordlist/main/wordlist.txt').then(latestWords => { const newWords = this.correctAnswers.filter(word => !latestWords.includes(word)); let blob = new Blob([newWords.join('\n')], { type: 'text/plain;charset=utf-8' }); let a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'newWords.txt'; a.style.display = 'none'; document.body.appendChild(a); a.click(); document.body.removeChild(a); }); } fetchAndStoreLatestWordlist() { this.fetchWords('https://raw.githubusercontent.com/kuel27/wordlist/main/wordlist.txt').then(words => { words.forEach(word => { if (!this.correctAnswers.includes(word)) { this.correctAnswers.push(word); } }); }); } fetchWords(url) { return fetch(url) .then(response => response.text()) .then(data => data.split('\n').map(word => word.trim())); } observeHintsAndInput() { this.observeHints(); this.observeInput(); } observeHints() { const targetNodes = [ document.querySelector('.hints .container'), document.querySelector('.words'), document.querySelector('#game-word'), ]; const config = { childList: true, subtree: true }; const observer = new MutationObserver((mutationsList) => this.hintObserverCallback(mutationsList)); targetNodes.forEach(targetNode => { if (targetNode) { observer.observe(targetNode, config); } }); } hintObserverCallback(mutationsList) { const inputElem = document.querySelector('#game-chat input[data-translate="placeholder"]'); if (inputElem.value) return; for (let mutation of mutationsList) { if (mutation.type === 'childList') { this.checkIfAllHintsRevealed(); this.checkWordsElement(); this.generateGuesses(); } } } checkIfAllHintsRevealed() { const hintElems = Array.from(document.querySelectorAll('.hints .hint')); if (hintElems.every(elem => elem.classList.contains('uncover'))) { const correctAnswer = hintElems.map(elem => elem.textContent).join('').trim().toLowerCase(); if (!correctAnswer || /[^a-zA-Z.\s-]/g.test(correctAnswer)) { return; } if (!this.correctAnswers.includes(correctAnswer)) { this.correctAnswers.push(correctAnswer); GM_setValue('correctAnswers', this.correctAnswers); console.log("added " + correctAnswer + " to the wordlist"); } } } checkWordsElement() { const wordElems = Array.from(document.querySelectorAll('.words.show .word')); wordElems.forEach(elem => { const word = elem.textContent.trim().toLowerCase(); if (!word || /[^a-zA-Z.\s-]/g.test(word)) { return; } if (word.trim() !== "" && !this.correctAnswers.includes(word)) { this.correctAnswers.push(word); GM_setValue('correctAnswers', this.correctAnswers); console.log("added " + word + " to the wordlist"); } }); } observeInput() { const inputElem = document.querySelector('#game-chat input[data-translate="placeholder"]'); inputElem.addEventListener('input', this.generateGuesses.bind(this)); inputElem.addEventListener('keydown', this.handleKeyDown.bind(this)); const formElem = document.querySelector('#game-chat form'); formElem.addEventListener('submit', this.generateGuesses.bind(this)); } handleKeyDown(event) { if (event.key === 'Tab' && this.possibleWords.length > 0) { event.preventDefault(); const inputElem = document.querySelector('#game-chat input[data-translate="placeholder"]'); inputElem.value = this.possibleWords[0]; inputElem.focus(); this.generateGuesses(); } } generateGuesses() { const hintElems = Array.from(document.querySelectorAll('.hints .hint')); const inputElem = document.querySelector('#game-chat input[data-translate="placeholder"]'); const hintParts = hintElems.map(elem => elem.textContent === '_' ? '.' : elem.textContent).join('').split(' '); const inputText = inputElem.value ? String(inputElem.value) : ''; this.possibleWords = this.correctAnswers.filter(word => { let wordParts = word.split(' '); if (wordParts.length !== hintParts.length) { return false; } for (let i = 0, len = wordParts.length; i < len; i++) { if (wordParts[i].length !== hintParts[i].length) { return false; } } if (hintParts.join(' ').trim().length <= 0 && inputText.trim().length <= 0) { return true; } let hintRegex = new RegExp(`^${hintParts.join(' ')}$`, 'i'); if (!hintRegex.test(word)) { return false; } let inputTextRegex = new RegExp(`^${inputText}`, 'i'); if (!inputTextRegex.test(word)) { return false; } return true; }); this.guessElem.innerHTML = ''; this.renderGuesses(this.possibleWords, inputElem); } renderGuesses(possibleWords, inputElem) { possibleWords.slice(0, 100).forEach((word, index) => { const wordElem = document.createElement('div'); wordElem.textContent = word; wordElem.style = 'font-weight: bold; display: inline-block; padding: 5px; margin-right: 2px; color: white; text-shadow: 2px 2px 2px black;'; const maxValue = possibleWords.length > 100 ? 100 : possibleWords.length; let hueValue = possibleWords.length > 1 ? Math.floor(360 * index / (maxValue - 1)) : 0; wordElem.style.backgroundColor = `hsl(${hueValue}, 100%, 50%)`; this.addHoverEffect(wordElem, hueValue); this.addClickFunctionality(wordElem, word, inputElem, hueValue); this.guessElem.appendChild(wordElem); }); } addHoverEffect(wordElem, hueValue) { wordElem.addEventListener('mouseenter', function() { if (!this.classList.contains('pressed')) { this.style.backgroundColor = 'lightgray'; } this.classList.add('hovered'); }); wordElem.addEventListener('mouseleave', function() { if (!this.classList.contains('pressed')) { this.style.backgroundColor = `hsl(${hueValue}, 100%, 50%)`; } this.classList.remove('hovered'); }); } addClickFunctionality(wordElem, word, inputElem, colorValue) { wordElem.addEventListener('mousedown', function() { wordElem.classList.add('pressed'); wordElem.style.backgroundColor = 'gray'; }); wordElem.addEventListener('mouseup', function() { wordElem.classList.remove('pressed'); if (!wordElem.classList.contains('hovered')) { wordElem.style.backgroundColor = `rgb(${colorValue}, ${255 - colorValue}, 0)`; } else { wordElem.style.backgroundColor = 'lightgray'; } }); wordElem.addEventListener('click', function() { const formElem = document.querySelector('#game-chat form'); inputElem.value = word; formElem.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true })); }); } } new WordSleuth(); })();