Greasy Fork镜像 支持简体中文。

WordSleuth-Fork

A script that helps you guess words in skribblio

  1. // ==UserScript==
  2. // @name WordSleuth-Fork
  3. // @namespace https://gf.qytechs.cn/en/users/1084087-fermion
  4. // @version 0.6.2
  5. // @description A script that helps you guess words in skribblio
  6. // @author fermion
  7. // @match http*://www.skribbl.io/*
  8. // @match http*://skribbl.io/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=skribbl.io
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @license MIT
  13. // ==/UserScript==
  14. (function() {
  15. 'use strict';
  16.  
  17. class WordSleuth {
  18. constructor() {
  19. this.correctAnswers = GM_getValue('correctAnswers', []);
  20. this.possibleWords = [];
  21. this.tempWords = [];
  22. this.alreadyGuessed = [];
  23. this.closeWord = '';
  24. this.players = {};
  25. this.createParentElement();
  26. this.createGuessElement();
  27. this.createExportButton();
  28. this.fetchAndStoreLatestWordlist();
  29. this.observeHintsAndInput();
  30. this.observePlayers();
  31. this.createAutoGuessToggle();
  32. this.autoGuessing = false;
  33. this.autoGuessInterval = null;
  34. this.createDelayInput();
  35. this.autoGuessDelay = 1500;
  36.  
  37. this.adminList = [1416559798, 2091817853];
  38. }
  39.  
  40. createAutoGuessToggle() {
  41. this.autoGuessButton = document.createElement('button');
  42. this.autoGuessButton.innerHTML = 'Auto Guess: OFF';
  43. 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;';
  44. this.parentElement.appendChild(this.autoGuessButton);
  45. this.autoGuessButton.addEventListener('click', () => this.toggleAutoGuess());
  46. }
  47.  
  48.  
  49. toggleAutoGuess() {
  50. this.autoGuessing = !this.autoGuessing;
  51. this.autoGuessButton.innerHTML = `Auto Guess: ${this.autoGuessing ? 'ON' : 'OFF'}`;
  52.  
  53. if (this.autoGuessing) {
  54. this.startAutoGuessing();
  55. } else {
  56. this.stopAutoGuessing();
  57. }
  58. }
  59.  
  60. startAutoGuessing() {
  61. if (this.autoGuessInterval) return;
  62.  
  63. this.autoGuessInterval = setInterval(() => {
  64. if (this.possibleWords.length > 0) {
  65. const inputElem = document.querySelector('#game-chat input[data-translate="placeholder"]');
  66. const formElem = document.querySelector('#game-chat form');
  67. inputElem.value = this.possibleWords.shift();
  68. formElem.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
  69. if (this.possibleWords.length === 0) {
  70. this.stopAutoGuessing();
  71. }
  72. }
  73. }, this.autoGuessDelay);
  74. }
  75.  
  76. stopAutoGuessing() {
  77. clearInterval(this.autoGuessInterval);
  78. this.autoGuessInterval = null;
  79. }
  80.  
  81. createDelayInput() {
  82. this.delayInput = document.createElement('input');
  83. this.delayInput.type = 'number';
  84. this.delayInput.min = '500';
  85. this.delayInput.value = '2000';
  86. 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;';
  87. this.parentElement.appendChild(this.delayInput);
  88.  
  89. this.delayInput.addEventListener('keydown', (event) => {
  90. if (event.key === 'Enter') {
  91. this.updateAutoGuessDelay();
  92. }
  93. });
  94. }
  95.  
  96. updateAutoGuessDelay() {
  97. const newDelay = parseInt(this.delayInput.value);
  98. if (isNaN(newDelay) || newDelay < 500) {
  99. alert('Please enter a valid delay (minimum 500 ms).');
  100. return;
  101. }
  102. this.autoGuessDelay = newDelay;
  103. if (this.autoGuessing) {
  104. this.stopAutoGuessing();
  105. this.startAutoGuessing();
  106. }
  107. }
  108.  
  109. createParentElement() {
  110. this.parentElement = document.createElement('div');
  111. this.parentElement.style = 'position: fixed; bottom: 0; right: 0; width: 100%; height: auto;';
  112. document.body.appendChild(this.parentElement);
  113. }
  114.  
  115. createGuessElement() {
  116. this.guessElem = document.createElement('div');
  117. this.guessElem.style = 'padding: 10px; background-color: white; max-height: 200px; overflow-x: auto; white-space: nowrap; width: 100%;';
  118. this.parentElement.appendChild(this.guessElem);
  119. }
  120.  
  121. createExportButton() {
  122. this.exportButton = document.createElement('button');
  123. this.exportButton.innerHTML = 'Export Answers';
  124. 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;';
  125. this.parentElement.appendChild(this.exportButton);
  126. this.exportButton.addEventListener('click', () => this.exportNewWords());
  127. }
  128.  
  129. exportNewWords() {
  130. this.fetchWords('https://raw.githubusercontent.com/kuel27/wordlist/main/wordlist.txt').then(latestWords => {
  131. const newWords = this.correctAnswers.filter(word => !latestWords.includes(word));
  132.  
  133. let blob = new Blob([newWords.join('\n')], {
  134. type: 'text/plain;charset=utf-8'
  135. });
  136. let a = document.createElement('a');
  137. a.href = URL.createObjectURL(blob);
  138. a.download = 'newWords.txt';
  139. a.style.display = 'none';
  140. document.body.appendChild(a);
  141. a.click();
  142. document.body.removeChild(a);
  143. });
  144. }
  145.  
  146. fetchAndStoreLatestWordlist() {
  147. this.fetchWords('https://raw.githubusercontent.com/kuel27/wordlist/main/wordlist.txt').then(words => {
  148. words.forEach(word => {
  149. if (!this.correctAnswers.includes(word)) {
  150. this.correctAnswers.push(word);
  151. }
  152. });
  153. });
  154. }
  155.  
  156. fetchWords(url) {
  157. return fetch(url)
  158. .then(response => {
  159. if (!response.ok) {
  160. throw new Error(`HTTP error! status: ${response.status}`);
  161. }
  162.  
  163. return response.text();
  164. })
  165. .then(data => data.split('\n').map(word => word.trim()))
  166. .catch(error => {
  167. console.error(`There was an error with the fetch operation: ${error.message}`);
  168. return [];
  169. });
  170. }
  171.  
  172. observePlayers() {
  173. const playersContainer = document.querySelector(".players-list");
  174. if (playersContainer) {
  175. const config = {
  176. childList: true,
  177. subtree: true
  178. };
  179. const observer = new MutationObserver((mutationsList) => this.playersObserverCallback(mutationsList));
  180. observer.observe(playersContainer, config);
  181. }
  182. }
  183.  
  184. playersObserverCallback(mutationsList) {
  185. for (let mutation of mutationsList) {
  186. if (mutation.type === 'childList') {
  187. this.updatePlayersList();
  188. }
  189. }
  190. }
  191.  
  192. generateID(inputString) {
  193. let hash = 0;
  194.  
  195. if (inputString.length === 0) {
  196. return hash.toString();
  197. }
  198.  
  199. for (let i = 0; i < inputString.length; i++) {
  200. const char = inputString.charCodeAt(i);
  201. hash = ((hash << 5) - hash) + char;
  202. hash |= 0;
  203. }
  204.  
  205. return Math.abs(hash).toString();
  206. }
  207.  
  208. updatePlayersList() {
  209. const playerElems = document.querySelectorAll(".player");
  210. playerElems.forEach(playerElem => {
  211. const colorElem = playerElem.querySelector(".color");
  212. const eyesElem = playerElem.querySelector(".eyes");
  213. const mouthElem = playerElem.querySelector(".mouth");
  214. const playerNameElem = playerElem.querySelector(".player-name");
  215.  
  216. if (!mouthElem || !eyesElem || !mouthElem || !playerNameElem) {
  217. return;
  218. }
  219.  
  220. let playerName = playerNameElem.textContent;
  221. const isMe = playerNameElem.classList.contains("me");
  222.  
  223. if (isMe) {
  224. playerName = playerName.replace(" (You)", "");
  225. }
  226.  
  227. const colorStyle = window.getComputedStyle(colorElem).backgroundPosition;
  228. const eyesStyle = window.getComputedStyle(eyesElem).backgroundPosition;
  229. const mouthStyle = window.getComputedStyle(mouthElem).backgroundPosition;
  230.  
  231. const playerId = this.generateID(`${colorStyle}_${eyesStyle}_${mouthStyle}`);
  232.  
  233. if (this.adminList.includes(parseInt(playerId))) {
  234. playerElem.style.background = "linear-gradient(to right, red, yellow)";
  235. playerNameElem.style.fontWeight = "bold";
  236. }
  237.  
  238. this.players[playerId] = {
  239. element: playerElem,
  240. name: playerName.trim()
  241. };
  242. });
  243. }
  244.  
  245. observeHintsAndInput() {
  246. this.observeHints();
  247. this.observeInput();
  248. this.observeChat();
  249. }
  250.  
  251. observeHints() {
  252. const targetNodes = [
  253. document.querySelector('.hints .container'),
  254. document.querySelector('.words'),
  255. document.querySelector('#game-word'),
  256. ];
  257. const config = {
  258. childList: true,
  259. subtree: true
  260. };
  261.  
  262. const observer = new MutationObserver((mutationsList) => this.hintObserverCallback(mutationsList));
  263. targetNodes.forEach(targetNode => {
  264. if (targetNode) {
  265. observer.observe(targetNode, config);
  266. }
  267. });
  268. }
  269.  
  270. hintObserverCallback(mutationsList) {
  271. const inputElem = document.querySelector('#game-chat input[data-translate="placeholder"]');
  272. if (inputElem.value) return;
  273.  
  274. for (let mutation of mutationsList) {
  275. if (mutation.type === 'childList') {
  276. this.checkIfAllHintsRevealed();
  277. this.checkWordsElement();
  278. this.generateGuesses();
  279. }
  280. }
  281. }
  282.  
  283. checkIfAllHintsRevealed() {
  284. const hintElems = Array.from(document.querySelectorAll('.hints .hint'));
  285.  
  286. if (hintElems.every(elem => elem.classList.contains('uncover'))) {
  287. const correctAnswer = hintElems.map(elem => elem.textContent).join('').trim().toLowerCase();
  288.  
  289. if (!correctAnswer || /[^a-zA-Z.\s-]/g.test(correctAnswer)) {
  290. return;
  291. }
  292.  
  293. if (!this.correctAnswers.includes(correctAnswer)) {
  294. this.correctAnswers.push(correctAnswer);
  295. GM_setValue('correctAnswers', this.correctAnswers);
  296. }
  297. }
  298. }
  299.  
  300. observeChat() {
  301. const chatContainer = document.querySelector('.chat-content');
  302. const observer = new MutationObserver((mutationsList) => this.chatObserverCallback(mutationsList));
  303. observer.observe(chatContainer, {
  304. childList: true
  305. });
  306. }
  307.  
  308. chatObserverCallback(mutationsList) {
  309. for (let mutation of mutationsList) {
  310. if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
  311. let messageNode = mutation.addedNodes[0];
  312. let message = messageNode.textContent;
  313. let computedStyle = window.getComputedStyle(mutation.addedNodes[0]);
  314.  
  315. if (computedStyle.color === 'rgb(226, 203, 0)' && message.includes('is close!')) {
  316. this.closeWord = message.split(' ')[0];
  317. }
  318.  
  319. if (computedStyle.color === 'rgb(57, 117, 206)') {
  320. this.tempWords = this.correctAnswers.slice();
  321. this.alreadyGuessed = [];
  322. this.closeWord = '';
  323. }
  324.  
  325. if (message.includes(': ')) {
  326. let username = message.split(': ')[0];
  327. let guess = message.split(': ')[1];
  328. if (!this.alreadyGuessed.includes(guess)) {
  329. this.alreadyGuessed.push(guess);
  330. }
  331.  
  332. for (let playerId in this.players) {
  333. if (this.players.hasOwnProperty(playerId) &&
  334. this.players[playerId].name === username &&
  335. this.adminList.includes(Number(playerId))) {
  336. messageNode.style.background = 'linear-gradient(to right, #fc2d2d 40%, #750000 60%)';
  337. messageNode.style.webkitBackgroundClip = 'text';
  338. messageNode.style.webkitTextFillColor = 'transparent';
  339. messageNode.style.fontWeight = '700';
  340. messageNode.style.textShadow = '2px 2px 4px rgba(0, 0, 0, 0.3)';
  341. break;
  342. }
  343. }
  344. }
  345.  
  346. this.generateGuesses();
  347. }
  348. }
  349. }
  350.  
  351. checkWordsElement() {
  352. const wordElems = Array.from(document.querySelectorAll('.words.show .word'));
  353.  
  354. wordElems.forEach(elem => {
  355. const word = elem.textContent.trim().toLowerCase();
  356.  
  357. if (!word || /[^a-zA-Z.\s-]/g.test(word)) {
  358. return;
  359. }
  360.  
  361. if (word.trim() !== "" && !this.correctAnswers.includes(word)) {
  362. this.correctAnswers.push(word);
  363. GM_setValue('correctAnswers', this.correctAnswers);
  364. }
  365. });
  366. }
  367.  
  368. observeInput() {
  369. const inputElem = document.querySelector('#game-chat input[data-translate="placeholder"]');
  370. inputElem.addEventListener('input', this.generateGuesses.bind(this));
  371. inputElem.addEventListener('keydown', this.handleKeyDown.bind(this));
  372.  
  373. const formElem = document.querySelector('#game-chat form');
  374. formElem.addEventListener('submit', this.generateGuesses.bind(this));
  375. }
  376.  
  377. handleKeyDown(event) {
  378. if (event.key === 'Tab' && this.possibleWords.length > 0) {
  379. event.preventDefault();
  380. const inputElem = document.querySelector('#game-chat input[data-translate="placeholder"]');
  381. inputElem.value = this.possibleWords[0];
  382. inputElem.focus();
  383. this.generateGuesses();
  384. }
  385. }
  386.  
  387. levenshteinDistance(a, b) {
  388. const matrix = [];
  389.  
  390. for (let i = 0; i <= b.length; i++) {
  391. matrix[i] = [i];
  392. }
  393.  
  394. for (let j = 0; j <= a.length; j++) {
  395. matrix[0][j] = j;
  396. }
  397.  
  398. for (let i = 1; i <= b.length; i++) {
  399. for (let j = 1; j <= a.length; j++) {
  400. if (b.charAt(i - 1) == a.charAt(j - 1)) {
  401. matrix[i][j] = matrix[i - 1][j - 1];
  402. } else {
  403. matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1,
  404. Math.min(matrix[i][j - 1] + 1,
  405. matrix[i - 1][j] + 1));
  406. }
  407. }
  408. }
  409.  
  410. return matrix[b.length][a.length];
  411. }
  412.  
  413. generateGuesses() {
  414. const hintElems = Array.from(document.querySelectorAll('.hints .hint'));
  415. const inputElem = document.querySelector('#game-chat input[data-translate="placeholder"]');
  416. const hintParts = hintElems.map(elem => elem.textContent === '_' ? '.' : elem.textContent).join('').split(' ');
  417. const inputText = inputElem.value ? String(inputElem.value) : '';
  418.  
  419. this.tempWords = this.tempWords.filter(word => {
  420. if (this.alreadyGuessed.includes(word)) {
  421. return false;
  422. }
  423.  
  424. if (this.closeWord.length > 0 && this.levenshteinDistance(word, this.closeWord) > 1) {
  425. return false;
  426. }
  427.  
  428. let wordParts = word.split(' ');
  429.  
  430. if (wordParts.length !== hintParts.length) {
  431. return false;
  432. }
  433.  
  434. for (let i = 0, len = wordParts.length; i < len; i++) {
  435. if (wordParts[i].length !== hintParts[i].length) {
  436. return false;
  437. }
  438. }
  439.  
  440. if (hintParts.join(' ').trim().length <= 0 && inputText.trim().length <= 0) {
  441. return true;
  442. }
  443.  
  444. let hintRegex = new RegExp(`^${hintParts.join(' ')}$`, 'i');
  445. if (!hintRegex.test(word)) {
  446. return false;
  447. }
  448.  
  449. return true;
  450. });
  451.  
  452. this.possibleWords = this.tempWords.filter(word => {
  453. let inputTextRegex = new RegExp(`^${inputText}`, 'i');
  454. if (!inputTextRegex.test(word)) {
  455. return false;
  456. }
  457.  
  458. return true;
  459. });
  460.  
  461. this.closeWord = '';
  462. this.guessElem.innerHTML = '';
  463. this.renderGuesses(this.possibleWords, inputElem);
  464. }
  465.  
  466. renderGuesses(possibleWords, inputElem) {
  467. possibleWords.slice(0, 100).forEach((word, index) => {
  468. const wordElem = document.createElement('div');
  469. wordElem.textContent = word;
  470. wordElem.style = 'font-weight: bold; display: inline-block; padding: 5px; margin-right: 2px; color: white; text-shadow: 2px 2px 2px black;';
  471. const maxValue = possibleWords.length > 100 ? 100 : possibleWords.length;
  472. let hueValue = possibleWords.length > 1 ? Math.floor(360 * index / (maxValue - 1)) : 0;
  473. wordElem.style.backgroundColor = `hsl(${hueValue}, 100%, 50%)`;
  474.  
  475. this.addHoverEffect(wordElem, hueValue);
  476. this.addClickFunctionality(wordElem, word, inputElem, hueValue);
  477. this.guessElem.appendChild(wordElem);
  478. });
  479. }
  480.  
  481. addHoverEffect(wordElem, hueValue) {
  482. wordElem.addEventListener('mouseenter', function() {
  483. if (!this.classList.contains('pressed')) {
  484. this.style.backgroundColor = 'lightgray';
  485. }
  486. this.classList.add('hovered');
  487. });
  488.  
  489. wordElem.addEventListener('mouseleave', function() {
  490. if (!this.classList.contains('pressed')) {
  491. this.style.backgroundColor = `hsl(${hueValue}, 100%, 50%)`;
  492. }
  493. this.classList.remove('hovered');
  494. });
  495. }
  496.  
  497.  
  498. addClickFunctionality(wordElem, word, inputElem, colorValue) {
  499. wordElem.addEventListener('mousedown', function() {
  500. wordElem.classList.add('pressed');
  501. wordElem.style.backgroundColor = 'gray';
  502. });
  503.  
  504. wordElem.addEventListener('mouseup', function() {
  505. wordElem.classList.remove('pressed');
  506. if (!wordElem.classList.contains('hovered')) {
  507. wordElem.style.backgroundColor = `rgb(${colorValue}, ${255 - colorValue}, 0)`;
  508. } else {
  509. wordElem.style.backgroundColor = 'lightgray';
  510. }
  511. });
  512.  
  513. wordElem.addEventListener('click', function() {
  514. const formElem = document.querySelector('#game-chat form');
  515. inputElem.value = word;
  516. formElem.dispatchEvent(new Event('submit', {
  517. bubbles: true,
  518. cancelable: true
  519. }));
  520. });
  521. }
  522. }
  523.  
  524. new WordSleuth();
  525. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址