- // ==UserScript==
- // @name Skribbl Autoguesser
- // @name:zh-CN Skribbl 自动猜词器
- // @name:zh-TW Skribbl 自動猜詞器
- // @name:hi स्क्रिब्ल ऑटोगेसर
- // @name:es Skribbl Adivinador Automático
- // @namespace http://tampermonkey.net/
- // @supportURL https://github.com/zkisaboss/reorderedwordlist
- // @version 1.01
- // @description The best script currently out there for skribbl.
- // @description:zh-CN 一个帮助你在skribblio中猜词的脚本。
- // @description:zh-TW 一個幫助你在skribblio中猜詞的腳本。
- // @description:hi एक स्क्रिप्ट जो आपको स्क्रिब्लियो में शब्दों का अनुमान लगाने में मदद करता है।
- // @description:es Un script que te ayuda a adivinar palabras en skribblio.
- // @author Zach Kosove
- // @match http*://skribbl.io/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=skribbl.io
- // @grant GM_setValue
- // @grant GM_getValue
- // @license MIT
- // @compatible chrome
- // @compatible firefox
- // @compatible opera
- // @compatible safari
- // @compatible edge
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- // Variables
- let autoGuessing = false;
-
-
- // UI Elements
- const parentElement = document.createElement('div');
- Object.assign(parentElement.style, { position: 'fixed', bottom: '0', right: '0', width: '100%', height: 'auto' });
- document.body.appendChild(parentElement);
-
- const guessElem = document.createElement('div');
- Object.assign(guessElem.style, { padding: '10px', backgroundColor: 'white', maxHeight: '200px', overflowX: 'auto', whiteSpace: 'nowrap', width: '100%' });
- parentElement.appendChild(guessElem);
-
- const settingsElem = document.createElement('div');
- Object.assign(settingsElem.style, { position: 'absolute', bottom: 'calc(100%)', right: '0', padding: '10px 5px', display: 'flex', alignItems: 'center', gap: '10px' });
- parentElement.appendChild(settingsElem);
-
- const autoGuessButton = document.createElement('button');
- autoGuessButton.innerHTML = `Auto Guess: ${autoGuessing ? 'ON' : 'OFF'}`;
- Object.assign(autoGuessButton.style, { padding: '5px 10px', fontSize: '12px', backgroundColor: '#333', color: '#fff' });
- settingsElem.appendChild(autoGuessButton);
-
- const exportButton = document.createElement('button');
- exportButton.innerHTML = 'Export Answers';
- Object.assign(exportButton.style, { padding: '5px 10px', fontSize: '12px', backgroundColor: '#333', color: '#fff' });
- settingsElem.appendChild(exportButton);
-
-
- // Functions
- const correctAnswers = GM_getValue('correctAnswers', []);
-
- async function fetchWords(url) {
- const response = await fetch(url);
- if (!response.ok) return [];
-
- const data = await response.text();
- return data.split('\n').filter(elem => elem !== '');
- }
-
- async function fetchAndStoreLatestWordlist() {
- const words = await fetchWords('https://raw.githubusercontent.com/zkisaboss/reorderedwordlist/main/wordlist.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);
- console.log(`New Word: ${text}`)
- GM_setValue('correctAnswers', correctAnswers);
- }
- });
- });
-
- observer.observe(target, { childList: true });
- }
-
- observeDrawingTurn();
-
-
- // Core functionality
- let possibleWords = [];
-
- function renderGuesses(possibleWords) {
- guessElem.innerHTML = '';
-
- possibleWords.forEach(word => {
- const wordElem = document.createElement('div');
- wordElem.textContent = word;
- Object.assign(wordElem.style, {
- fontWeight: 'bold',
- display: 'inline-block',
- padding: '5px',
- marginRight: '2px',
- color: 'white',
- textShadow: '2px 2px 2px black',
- backgroundColor: 'hsl(205, 100%, 50%)'
- });
-
- wordElem.addEventListener('mouseenter', () => {
- if (!wordElem.classList.contains('pressed')) wordElem.style.backgroundColor = 'lightgray';
- wordElem.classList.add('hovered');
- });
-
- wordElem.addEventListener('mouseleave', () => {
- if (!wordElem.classList.contains('pressed')) wordElem.style.backgroundColor = 'hsl(205, 100%, 50%)';
- wordElem.classList.remove('hovered');
- });
-
- wordElem.addEventListener('mousedown', () => {
- wordElem.classList.add('pressed');
- wordElem.style.backgroundColor = 'gray';
- });
-
- wordElem.addEventListener('mouseup', () => {
- wordElem.classList.remove('pressed');
- wordElem.style.backgroundColor = wordElem.classList.contains('hovered') ? 'lightgray' : 'hsl(205, 100%, 50%)';
- });
-
- wordElem.addEventListener('click', () => {
- document.querySelector('#game-chat input[data-translate="placeholder"]').value = word;
- document.querySelector('#game-chat form').dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
- });
-
- guessElem.appendChild(wordElem);
- });
- }
-
- function generateGuesses() {
- const inputElem = document.querySelector('#game-chat input[data-translate="placeholder"]');
- const pattern = inputElem.value.toLowerCase().trim();
- const filteredWords = possibleWords.filter(word => word.startsWith(pattern));
-
- if (possibleWords.length === 1) {
- inputElem.value = possibleWords.shift();
- inputElem.closest('form').dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
- }
-
- renderGuesses(filteredWords);
- }
-
- function filterHints(inputWords) {
- const hints = Array.from(document.querySelectorAll('.hints .hint'));
-
- // turn into helper function or use a cleaner method to find allUncovered
- const allUncovered = hints.every(elem => elem.classList.contains('uncover'));
- if (allUncovered) {
- const correctAnswer = hints.map(elem => elem.textContent).join('').toLowerCase();
-
- if (correctAnswers.includes(correctAnswer)) {
- const currentIndex = correctAnswers.indexOf(correctAnswer);
- const newIndex = Math.max(0, currentIndex - 1);
- correctAnswers.splice(currentIndex, 1);
- correctAnswers.splice(newIndex, 0, correctAnswer);
- } else {
- correctAnswers.push(correctAnswer);
- console.log(`New Word: ${correctAnswer}`)
- }
-
- GM_setValue('correctAnswers', correctAnswers);
- return [];
- }
-
- const hintPattern = hints.map(hint => hint.textContent === '_' ? '[a-z]' : hint.textContent).join('');
- const hintRegex = new RegExp(`^${hintPattern}$`, 'i');
- return inputWords.filter(word => hintRegex.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();
-
-
- // 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++) {
- if (b.charAt(i - 1) === a.charAt(j - 1)) {
- matrix[i][j] = matrix[i - 1][j - 1];
- } else {
- matrix[i][j] = Math.min(
- matrix[i - 1][j - 1] + 1,
- matrix[i][j - 1] + 1,
- matrix[i - 1][j] + 1
- );
- }
- }
- }
- return matrix[b.length][a.length];
- }
-
- 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();
- }
-
- 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();
- }
-
- 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();
-
-
- function observeInput() {
- const inputElem = document.querySelector('#game-chat input[data-translate="placeholder"]');
-
- inputElem.addEventListener('input', generateGuesses);
-
- inputElem.addEventListener('keydown', ({ key }) => {
- if (key === 'Enter') {
- const guessDiv = guessElem.querySelector('div');
- if (guessDiv) {
- inputElem.value = guessDiv.innerText;
- inputElem.closest('form').dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
- }
- }
- });
- }
-
- observeInput();
-
-
- let autoGuessInterval;
-
- function startAutoGuessing() {
- if (autoGuessing) {
- autoGuessInterval = setInterval(() => {
- if (possibleWords.length > 0) {
- document.querySelector('#game-chat input[data-translate="placeholder"]').value = possibleWords.shift();
- document.querySelector('#game-chat form').dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
- }
- }, 8000);
- }
- }
-
- startAutoGuessing();
-
-
- 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);
- }
-
- exportButton.addEventListener('click', exportNewWords);
- })();