WordSleuth-Fork

A script that helps you guess words in skribblio

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name     WordSleuth-Fork
// @namespace  https://greasyfork.org/en/users/1084087-fermion
// @version   0.6.2
// @description A script that helps you guess words in skribblio
// @author    fermion
// @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
// ==/UserScript==
(function() {
  'use strict';

  class WordSleuth {
    constructor() {
      this.correctAnswers = GM_getValue('correctAnswers', []);
      this.possibleWords = [];
      this.tempWords = [];
      this.alreadyGuessed = [];
      this.closeWord = '';
      this.players = {};
      this.createParentElement();
      this.createGuessElement();
      this.createExportButton();
      this.fetchAndStoreLatestWordlist();
      this.observeHintsAndInput();
      this.observePlayers();
      this.createAutoGuessToggle();
      this.autoGuessing = false;
      this.autoGuessInterval = null;
      this.createDelayInput();
      this.autoGuessDelay = 1500;

      this.adminList = [1416559798, 2091817853];
    }

    createAutoGuessToggle() {
      this.autoGuessButton = document.createElement('button');
      this.autoGuessButton.innerHTML = 'Auto Guess: OFF';
      this.autoGuessButton.style = 'position: absolute; bottom: calc(100% + 10px); right: 100px; z-index: 9999; padding: 5px 10px; font-size: 12px; background-color: #333; color: #fff; border: none; border-radius: 5px;';
      this.parentElement.appendChild(this.autoGuessButton);
      this.autoGuessButton.addEventListener('click', () => this.toggleAutoGuess());
    }


    toggleAutoGuess() {
      this.autoGuessing = !this.autoGuessing;
      this.autoGuessButton.innerHTML = `Auto Guess: ${this.autoGuessing ? 'ON' : 'OFF'}`;

      if (this.autoGuessing) {
        this.startAutoGuessing();
      } else {
        this.stopAutoGuessing();
      }
    }

    startAutoGuessing() {
      if (this.autoGuessInterval) return;

      this.autoGuessInterval = setInterval(() => {
        if (this.possibleWords.length > 0) {
          const inputElem = document.querySelector('#game-chat input[data-translate="placeholder"]');
          const formElem = document.querySelector('#game-chat form');
          inputElem.value = this.possibleWords.shift();
          formElem.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
          if (this.possibleWords.length === 0) {
            this.stopAutoGuessing();
          }
        }
      }, this.autoGuessDelay);
    }

    stopAutoGuessing() {
      clearInterval(this.autoGuessInterval);
      this.autoGuessInterval = null;
    }

    createDelayInput() {
      this.delayInput = document.createElement('input');
      this.delayInput.type = 'number';
      this.delayInput.min = '500';
      this.delayInput.value = '2000';
      this.delayInput.style = 'position: absolute; bottom: calc(100% + 10px); right: 220px; z-index: 9999; padding: 10px; width: 75px; font-size: 12px; background-color: #fff; color: #333; border: 1px solid #ccc; border-radius: 5px; height: 25px;';
      this.parentElement.appendChild(this.delayInput);

      this.delayInput.addEventListener('keydown', (event) => {
        if (event.key === 'Enter') {
          this.updateAutoGuessDelay();
        }
      });
    }

    updateAutoGuessDelay() {
      const newDelay = parseInt(this.delayInput.value);
      if (isNaN(newDelay) || newDelay < 500) {
        alert('Please enter a valid delay (minimum 500 ms).');
        return;
      }
      this.autoGuessDelay = newDelay;
      if (this.autoGuessing) {
        this.stopAutoGuessing();
        this.startAutoGuessing();
      }
    }

    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 => {
          if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
          }

          return response.text();
        })
        .then(data => data.split('\n').map(word => word.trim()))
        .catch(error => {
          console.error(`There was an error with the fetch operation: ${error.message}`);
          return [];
        });
    }

    observePlayers() {
      const playersContainer = document.querySelector(".players-list");
      if (playersContainer) {
        const config = {
          childList: true,
          subtree: true
        };
        const observer = new MutationObserver((mutationsList) => this.playersObserverCallback(mutationsList));
        observer.observe(playersContainer, config);
      }
    }

    playersObserverCallback(mutationsList) {
      for (let mutation of mutationsList) {
        if (mutation.type === 'childList') {
          this.updatePlayersList();
        }
      }
    }

    generateID(inputString) {
      let hash = 0;

      if (inputString.length === 0) {
        return hash.toString();
      }

      for (let i = 0; i < inputString.length; i++) {
        const char = inputString.charCodeAt(i);
        hash = ((hash << 5) - hash) + char;
        hash |= 0;
      }

      return Math.abs(hash).toString();
    }

    updatePlayersList() {
      const playerElems = document.querySelectorAll(".player");
      playerElems.forEach(playerElem => {
        const colorElem = playerElem.querySelector(".color");
        const eyesElem = playerElem.querySelector(".eyes");
        const mouthElem = playerElem.querySelector(".mouth");
        const playerNameElem = playerElem.querySelector(".player-name");

        if (!mouthElem || !eyesElem || !mouthElem || !playerNameElem) {
          return;
        }

        let playerName = playerNameElem.textContent;
        const isMe = playerNameElem.classList.contains("me");

        if (isMe) {
          playerName = playerName.replace(" (You)", "");
        }

        const colorStyle = window.getComputedStyle(colorElem).backgroundPosition;
        const eyesStyle = window.getComputedStyle(eyesElem).backgroundPosition;
        const mouthStyle = window.getComputedStyle(mouthElem).backgroundPosition;

        const playerId = this.generateID(`${colorStyle}_${eyesStyle}_${mouthStyle}`);

        if (this.adminList.includes(parseInt(playerId))) {
          playerElem.style.background = "linear-gradient(to right, red, yellow)";
          playerNameElem.style.fontWeight = "bold";
        }

        this.players[playerId] = {
          element: playerElem,
          name: playerName.trim()
        };
      });
    }

    observeHintsAndInput() {
      this.observeHints();
      this.observeInput();
      this.observeChat();
    }

    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);
        }
      }
    }

    observeChat() {
      const chatContainer = document.querySelector('.chat-content');
      const observer = new MutationObserver((mutationsList) => this.chatObserverCallback(mutationsList));
      observer.observe(chatContainer, {
        childList: true
      });
    }

    chatObserverCallback(mutationsList) {
      for (let mutation of mutationsList) {
        if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
          let messageNode = mutation.addedNodes[0];
          let message = messageNode.textContent;
          let computedStyle = window.getComputedStyle(mutation.addedNodes[0]);

          if (computedStyle.color === 'rgb(226, 203, 0)' && message.includes('is close!')) {
            this.closeWord = message.split(' ')[0];
          }

          if (computedStyle.color === 'rgb(57, 117, 206)') {
            this.tempWords = this.correctAnswers.slice();
            this.alreadyGuessed = [];
            this.closeWord = '';
          }

          if (message.includes(': ')) {
            let username = message.split(': ')[0];
            let guess = message.split(': ')[1];
            if (!this.alreadyGuessed.includes(guess)) {
              this.alreadyGuessed.push(guess);
            }

            for (let playerId in this.players) {
              if (this.players.hasOwnProperty(playerId) &&
                this.players[playerId].name === username &&
                this.adminList.includes(Number(playerId))) {
                messageNode.style.background = 'linear-gradient(to right, #fc2d2d 40%, #750000 60%)';
                messageNode.style.webkitBackgroundClip = 'text';
                messageNode.style.webkitTextFillColor = 'transparent';
                messageNode.style.fontWeight = '700';
                messageNode.style.textShadow = '2px 2px 4px rgba(0, 0, 0, 0.3)';
                break;
              }
            }
          }

          this.generateGuesses();
        }
      }
    }

    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);
        }
      });
    }

    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();
      }
    }

    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,
              Math.min(matrix[i][j - 1] + 1,
                matrix[i - 1][j] + 1));
          }
        }
      }

      return matrix[b.length][a.length];
    }

    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.tempWords = this.tempWords.filter(word => {
        if (this.alreadyGuessed.includes(word)) {
          return false;
        }

        if (this.closeWord.length > 0 && this.levenshteinDistance(word, this.closeWord) > 1) {
          return false;
        }

        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;
        }

        return true;
      });

      this.possibleWords = this.tempWords.filter(word => {
        let inputTextRegex = new RegExp(`^${inputText}`, 'i');
        if (!inputTextRegex.test(word)) {
          return false;
        }

        return true;
      });

      this.closeWord = '';
      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();
})();