- // ==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.0
- // @description A script that helps you guess words in skribblio.
- // @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');
- }
-
- 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 setUsername() {
- document.querySelectorAll(".player .player-name").forEach(playerNameElem => {
- const playerName = playerNameElem.textContent;
- if (playerName.endsWith(" (You)")) {
- myUsername = playerName.replace(" (You)", "");
- }
- });
- }
-
- function observePlayers() {
- const playersContainer = document.querySelector(".players-list");
- if (playersContainer) {
- const observer = new MutationObserver(() => {
- if (myUsername === '') {
- setUsername();
- } else {
- observer.disconnect();
- }
- });
- observer.observe(playersContainer, { childList: true, subtree: true });
- }
- }
-
- observePlayers();
-
-
- function observeDrawingTurn() {
- const wordsElement = document.querySelector('.words');
-
- const processNewChildNodes = (childNodes) => {
- childNodes.forEach(childNode => {
- const textContent = childNode.textContent.toLowerCase().trim();
- if (textContent && !correctAnswers.includes(textContent)) {
- correctAnswers.push(textContent);
- GM_setValue('correctAnswers', correctAnswers);
- }
- });
- };
-
- const mutationCallback = (mutationsList) => {
- const mutation = mutationsList.find(mutation => mutation.type === 'childList');
- if (mutation) {
- processNewChildNodes(mutation.target.childNodes);
- }
- };
-
- const observer = new MutationObserver(mutationCallback);
-
- observer.observe(wordsElement, { subtree: true, childList: true });
- }
-
- observeDrawingTurn();
-
-
- // Core functionality
- let possibleWords = [];
-
- function renderGuesses(possibleWords) {
- guessElem.innerHTML = '';
-
- possibleWords.forEach((word, index) => {
- const wordElem = document.createElement('div');
- wordElem.innerHTML = 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'));
- 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 hintTargets = [
- document.querySelector('.hints .container'),
- document.querySelector('.words'),
- document.querySelector('#game-word')
- ].filter(Boolean);
-
- const observer = new MutationObserver(() => {
- const hintElems = Array.from(document.querySelectorAll('.hints .hint'));
- const allUncovered = hintElems.every(elem => elem.classList.contains('uncover'));
-
- if (allUncovered) {
- const correctAnswer = hintElems.map(elem => elem.textContent).join('').trim().toLowerCase();
- const x = 1;
-
- if (correctAnswers.includes(correctAnswer)) {
- const currentIndex = correctAnswers.indexOf(correctAnswer);
- const newIndex = Math.max(0, currentIndex - x);
- correctAnswers.splice(currentIndex, 1);
- correctAnswers.splice(newIndex, 0, correctAnswer);
- } else {
- correctAnswers.push(correctAnswer);
- }
-
- GM_setValue('correctAnswers', correctAnswers);
- possibleWords = [];
- } else {
- possibleWords = filterHints(possibleWords);
- }
-
- generateGuesses();
- });
- hintTargets.forEach(target => 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)') {
- 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!', ''); // works for multi-word guesses?
- possibleWords = previousWords.filter(word => levenshteinDistance(word, closeWord) === 1);
-
- generateGuesses();
- }
- }
-
- function observeChat() {
- const chatContainer = document.querySelector('.chat-content');
- if (chatContainer) {
- const observer = new MutationObserver(mutationsList => {
- mutationsList.forEach(mutation => {
- if (mutation.addedNodes.length > 0) handleChatMessage(mutation.addedNodes[0]);
- });
- });
- observer.observe(chatContainer, { 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') {
- inputElem.value = guessElem.querySelector('div').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 }));
- }
- }, 10000);
- }
- }
-
- 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';
- // anchor.style.display = 'none';
-
- document.body.appendChild(anchor);
- anchor.click();
-
- document.body.removeChild(anchor);
- }
-
-
- exportButton.addEventListener('click', exportNewWords);
- })();