// ==UserScript== // @name Skribbl AutoGuesser // @name:zh-CN Skribbl 自动猜词器 // @name:zh-TW Skribbl 自動猜詞器 // @name:hi Skribbl स्वतः अनुमान स्क्रिप्ट // @name:es Skribbl Adivinador Automático // @namespace http://tampermonkey.net/ // @version 1.08 // @description Automatically suggests guesses in Skribbl.io. Fast, easy, and effective. // @description:zh-CN 自动在 Skribbl.io 中猜词,快速、简单、有效。 // @description:zh-TW 自動在 Skribbl.io 中猜詞,快速、簡單、有效。 // @description:hi Skribbl.io में शब्दों का अनुमान लगाने वाली तेज़ और आसान स्क्रिप्ट। // @description:es Adivina palabras automáticamente en Skribbl.io de forma rápida y sencilla. // @author Zach Kosove // @supportURL https://github.com/zkisaboss/reorderedwordlist // @match https://skribbl.io/* // @icon https://skribbl.io/favicon.png // @grant GM_setValue // @grant GM_getValue // @license MIT // @compatible chrome // @compatible firefox // @compatible opera // @compatible safari // @compatible edge // @downloadURL https://update.greasyfork.icu/scripts/503563/Skribbl%20AutoGuesser.user.js // @updateURL https://update.greasyfork.icu/scripts/503563/Skribbl%20AutoGuesser.meta.js // ==/UserScript== (function() { 'use strict'; function createUI() { const bottomUI = document.createElement('div'); bottomUI.id = 'bottom-ui'; bottomUI.innerHTML = `
`; document.body.appendChild(bottomUI); const ui = document.getElementById('bottom-ui'); document.addEventListener('keydown', (e) => { if (e.key === 'ArrowDown') ui.classList.add('hidden'); if (e.key === 'ArrowUp') ui.classList.remove('hidden'); }); } createUI(); const correctAnswers = GM_getValue('correctAnswers', []); async function fetchWords(url) { const response = await fetch(url); if (!response.ok) return []; const text = await response.text(); return text.split('\n').filter(word => word !== ''); } async function fetchAndStoreLatestWordlist() { const words = await fetchWords('https://raw.githubusercontent.com/zkisaboss/reorderedwordlist/main/wordlist_test.txt'); words.forEach(word => { if (!correctAnswers.includes(word)) correctAnswers.push(word); }); } fetchAndStoreLatestWordlist(); let myUsername = ''; function findUsername() { const target = document.querySelector(".players-list"); if (!target) return; const observer = new MutationObserver(() => { myUsername = document.querySelector(".me").textContent.replace(" (You)", "") observer.disconnect(); }); observer.observe(target, { childList: true }); } findUsername(); function observeDrawingTurn() { const target = document.querySelector('.words'); if (!target) return; const observer = new MutationObserver(() => { target.childNodes.forEach(word => { const text = word.textContent.toLowerCase(); if (!correctAnswers.includes(text)) { correctAnswers.push(text); GM_setValue('correctAnswers', correctAnswers); } }); }); observer.observe(target, { childList: true }); } observeDrawingTurn(); const remainingButton = document.getElementById('remaining-guesses'); const guessShelf = document.getElementById('guess-shelf'); let possibleWords = []; const input = document.querySelector('#game-chat input[data-translate="placeholder"]'); function renderGuesses(words) { guessShelf.innerHTML = ''; remainingButton.textContent = `Remaining Guesses: ${possibleWords.length}`; words.forEach(word => { const button = Object.assign(document.createElement('button'), { className: 'ui-btn', textContent: word, onclick: () => { input.value = word; input.closest('form').dispatchEvent(new Event('submit', { bubbles: true, cancelable: true })); } }); guessShelf.appendChild(button); }); }; function generateGuesses() { if (possibleWords.length === 1) { input.value = possibleWords.shift(); input.closest('form').dispatchEvent(new Event('submit', { bubbles: true, cancelable: true })); } const pattern = input.value.toLowerCase().trim(); const words = possibleWords.filter(word => word.startsWith(pattern)); renderGuesses(words); } function observeInput() { input.addEventListener('input', generateGuesses); input.addEventListener('keydown', ({ key }) => { if (key === 'Enter') { input.value = guessShelf.firstElementChild.innerText; input.closest('form').dispatchEvent(new Event('submit', { bubbles: true, cancelable: true })); } }); } observeInput(); function storeAnswer(word) { if (correctAnswers.includes(word)) { const index = correctAnswers.indexOf(word); const newIndex = Math.max(0, index - 1); correctAnswers.splice(index, 1); correctAnswers.splice(newIndex, 0, word); } else { correctAnswers.push(word); } GM_setValue('correctAnswers', correctAnswers); return []; } function filterHints(inputWords) { const hints = Array.from(document.querySelectorAll('.hints .hint')); const combined = hints.map(hint => hint.textContent === '_' ? '[a-z]' : hint.textContent).join('').toLowerCase(); const allUncovered = hints.every(hint => hint.classList.contains('uncover')); if (allUncovered) return storeAnswer(combined); const regex = new RegExp(`^${combined}$`, 'i'); return inputWords.filter(word => regex.test(word)); } function observeHints() { const target = document.querySelector('.hints .container'); if (!target) return; const observer = new MutationObserver(() => { possibleWords = filterHints(possibleWords); generateGuesses(); }); observer.observe(target, { childList: true, subtree: true }); } observeHints(); /* // Levenshtein: O(n * m) time, O(n * m) space // https://youtu.be/Dd_NgYVOdLk function levenshteinDistance(a, b) { const matrix = []; for (let i = 0; i <= b.length; i++) matrix[i] = [i]; for (let j = 0; j <= a.length; j++) matrix[0][j] = j; for (let i = 1; i <= b.length; i++) { for (let j = 1; j <= a.length; j++) { matrix[i][j] = Math.min( matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + (b[i - 1] !== a[j - 1]) ); } } return matrix[b.length][a.length]; } */ // Banded Levenshtein: O(min(|n|, |m|) * k) time, O(min(m,n)) space // https://www.baeldung.com/cs/levenshtein-distance-computation function levenshteinDistance(a, b, k = 1) { if (a.length > b.length) { const t = a; a = b; b = t; } const m = a.length; const n = b.length; if (n - m > k) return -1; if (m === 0) return n; const rows = [ new Uint16Array(m + 1), new Uint16Array(m + 1) ]; for (let j = 0; j <= m; j++) rows[0][j] = j; let flip = 0; for (let i = 0; i < n; i++) { flip ^= 1; const curr = rows[flip]; const prev = rows[flip ^ 1]; curr[0] = i + 1; const stripeStart = Math.max(1, i + 1 - k); const stripeEnd = Math.min(m, i + 1 + k); let rowMin = k + 1; for (let j = stripeStart; j <= stripeEnd; j++) { curr[j] = Math.min( prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + (a[j - 1] !== b[i]) ); if (curr[j] < rowMin) rowMin = curr[j]; } if (rowMin > k) return -1; } return rows[flip][m]; } let previousWords = []; function handleChatMessage(messageNode) { const messageColor = window.getComputedStyle(messageNode).color; const message = messageNode.textContent; if (messageColor === 'rgb(57, 117, 206)' && message.endsWith('is drawing now!')) { possibleWords = filterHints(correctAnswers); generateGuesses(); } else if (message.includes(': ')) { const [username, guess] = message.split(': '); possibleWords = possibleWords.filter(word => word !== guess); previousWords = possibleWords; if (username === myUsername) { possibleWords = possibleWords.filter(word => levenshteinDistance(word, guess) === -1); } generateGuesses(); } else if (messageColor === 'rgb(226, 203, 0)' && message.endsWith('is close!')) { const closeWord = message.replace(' is close!', ''); possibleWords = previousWords.filter(word => levenshteinDistance(word, closeWord) === 1); generateGuesses(); } } function observeChat() { const target = document.querySelector('.chat-content'); if (!target) return; const observer = new MutationObserver(() => { const lastMessage = target.lastElementChild; if (lastMessage) handleChatMessage(lastMessage); }); observer.observe(target, { childList: true }); } observeChat(); let autoGuessInterval; let autoGuessing = false; function startAutoGuessing() { if (!autoGuessing) return; autoGuessInterval = setInterval(() => { if (possibleWords.length > 0) { input.value = possibleWords.shift(); input.closest('form').dispatchEvent(new Event('submit', { bubbles: true, cancelable: true })); } }, 7500); } startAutoGuessing(); const autoGuessButton = document.getElementById('auto-guess'); function toggleAutoGuessing() { autoGuessing = !autoGuessing; autoGuessButton.innerHTML = `Auto Guess: ${autoGuessing ? 'ON' : 'OFF'}`; if (autoGuessing) { startAutoGuessing(); } else { clearInterval(autoGuessInterval); autoGuessInterval = null; } } autoGuessButton.addEventListener('click', toggleAutoGuessing); async function exportNewWords() { const old = await fetchWords('https://raw.githubusercontent.com/zkisaboss/reorderedwordlist/main/wordlist.txt'); const newWords = correctAnswers.filter(word => !old.includes(word)); const blob = new Blob([newWords.join('\n')], { type: 'text/plain;charset=utf-8' }); const anchor = document.createElement('a'); anchor.href = URL.createObjectURL(blob); anchor.download = 'newWords.txt'; document.body.appendChild(anchor); anchor.click(); document.body.removeChild(anchor); } const exportButton = document.getElementById('export-answers'); exportButton.addEventListener('click', exportNewWords); const secretButton = document.getElementById('get-special'); function runSecret() { const avatars = document.querySelectorAll('.avatar-container .avatar'); const interval = setInterval(() => { let allSecretsVisible = true; avatars.forEach(avatar => { const secret = avatar.querySelector('.special'); if (getComputedStyle(secret).display === 'none') { avatar.click(); allSecretsVisible = false; } }); if (allSecretsVisible) clearInterval(interval); }, 15); } secretButton.addEventListener('click', runSecret); function observeSecret() { const target = document.getElementById('home'); if (!target) return; const observer = new MutationObserver(() => { secretButton.style.display = target.hasAttribute('style') ? 'none' : 'inline-block'; }); observer.observe(target, { attributes: true, attributeFilter: ['style'] }); } observeSecret(); })();