vocabulary.com bot

2/21/2025, 7:27:07 PM

安装此脚本?
作者推荐脚本

您可能也喜欢Auto Remove Try not to rush!

安装此脚本
  1. // ==UserScript==
  2. // @name vocabulary.com bot
  3. // @namespace Violentmonkey Scripts
  4. // @match https://www.vocabulary.com/lists/*/practice*
  5. // @grant none
  6. // @version 1.0
  7. // @author -
  8. // @description 2/21/2025, 7:27:07 PM
  9. // @license GPL-3.0-or-later
  10. // ==/UserScript==
  11. /*
  12. This program is free software: you can redistribute it and/or modify
  13. it under the terms of the GNU General Public License as published by
  14. the Free Software Foundation, either version 3 of the License, or
  15. (at your option) any later version.
  16.  
  17. This program is distributed in the hope that it will be useful,
  18. but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. GNU General Public License for more details.
  21.  
  22. You should have received a copy of the GNU General Public License
  23. along with this program. If not, see <https://www.gnu.org/licenses/>.
  24. */
  25.  
  26. (function () {
  27. 'use strict';
  28.  
  29. // -- state for pausing
  30. let paused = false;
  31.  
  32. // -- create a small overlay to toggle pause and display extra info
  33. function createpauseoverlay() {
  34. const div = document.createElement('div');
  35. div.id = 'pause-overlay';
  36. div.style.position = 'fixed';
  37. div.style.top = '10px';
  38. div.style.left = '10px';
  39. div.style.zIndex = '9999';
  40. div.style.background = '#333';
  41. div.style.color = '#fff';
  42. div.style.padding = '8px';
  43. div.style.cursor = 'pointer';
  44. div.style.borderRadius = '4px';
  45.  
  46. // pause button
  47. const pauseText = document.createElement('div');
  48. pauseText.innerText = 'pause script';
  49. pauseText.style.marginBottom = '5px';
  50. pauseText.style.cursor = 'pointer';
  51. pauseText.addEventListener('click', () => {
  52. paused = !paused;
  53. pauseText.innerText = paused ? 'resume script' : 'pause script';
  54. console.log(paused ? 'script paused' : 'script resumed');
  55. });
  56.  
  57. // dynamic display area (shared for all q types)
  58. const infoDisplay = document.createElement('div');
  59. infoDisplay.id = 'info-display';
  60. infoDisplay.style.fontSize = '12px';
  61. infoDisplay.style.color = '#ddd';
  62. infoDisplay.innerHTML = `
  63. <div id="qtype-display">Q Type: N/A</div>
  64. <div id="extra-info">Info: N/A</div>
  65. `;
  66.  
  67. div.appendChild(pauseText);
  68. div.appendChild(infoDisplay);
  69. document.body.appendChild(div);
  70.  
  71. }
  72.  
  73. function updateOverlay(qtype, info) {
  74. const qtypeDisplay = document.getElementById('qtype-display');
  75. const extraInfo = document.getElementById('extra-info');
  76.  
  77. if (qtypeDisplay) qtypeDisplay.innerText = `Q Type: ${qtype}`;
  78. if (extraInfo) extraInfo.innerText = `Info: ${info || 'N/A'}`;
  79. }
  80.  
  81.  
  82.  
  83. // -- "synonym" fetcher using vocabulary.com dictionary page
  84. async function fetchsynonyms(word) {
  85. const url = `https://www.vocabulary.com/dictionary/${encodeURIComponent(word)}`;
  86. try {
  87. const resp = await fetch(url);
  88. if (!resp.ok) throw new Error('Failed to fetch vocabulary.com page');
  89. const text = await resp.text();
  90. const parser = new DOMParser();
  91. const doc = parser.parseFromString(text, 'text/html');
  92. let synonyms = [];
  93.  
  94. const instances = doc.querySelectorAll("div.div-replace-dl.instances");
  95. instances.forEach(instance => {
  96. const detailSpan = instance.querySelector("span.detail");
  97. if (detailSpan && detailSpan.textContent.trim().toLowerCase().includes("synonyms")) {
  98. instance.querySelectorAll("a.word").forEach(a => {
  99. synonyms.push(a.textContent.trim().toLowerCase());
  100. });
  101. }
  102. });
  103.  
  104. return synonyms;
  105. } catch (err) {
  106. console.error("Error fetching synonyms from vocabulary.com:", err);
  107. return [];
  108. }
  109. }
  110.  
  111. // -- helper to see if a choice is correct
  112. function iscorrect(choice) {
  113. return choice.className.includes('correct');
  114. }
  115.  
  116. // -- helper: attempt synonyms only if qtype == 'S'
  117. async function handleTypeS(curq, qlist, choices) {
  118. const synonyms = await fetchsynonyms(curq.q.toLowerCase());
  119. updateOverlay('S', synonyms.length ? `Synonyms: ${synonyms.join(', ')}` : 'No synonyms found.');
  120.  
  121. if (!synonyms.length) {
  122. console.log('no synonyms found. falling back.');
  123. return false;
  124. }
  125.  
  126. for (let i = 0; i < choices.length; i++) {
  127. const text = choices[i].innerText.trim().toLowerCase();
  128. if (synonyms.includes(text)) {
  129. console.log(`clicking synonym match: ${text}`);
  130. choices[i].click();
  131. if (iscorrect(choices[i])) {
  132. qlist[curq.q] = text;
  133. localStorage.practiceLists = JSON.stringify(plists);
  134. console.log(`recorded: "${curq.q}" -> "${text}"`);
  135. clicknext();
  136. }
  137. return true;
  138. }
  139. }
  140.  
  141. return false;
  142. }
  143.  
  144.  
  145. // -- Modular handler for question type 'D' (definition-based questions)
  146. async function handleTypeD(curQ, qList, choices, pLists) {
  147. const allDefs = JSON.parse(localStorage.getItem('words&defs') || '[]');
  148. const entry = allDefs.find(e => e.word?.toLowerCase() === curQ.q.toLowerCase());
  149.  
  150. if (!entry || !entry.definition) {
  151. console.log(`no local definition found for "${curQ.q}"`);
  152. updateOverlay('D', 'No definition found.');
  153. return false;
  154. }
  155.  
  156. updateOverlay('D', `Definition: ${entry.definition}`);
  157.  
  158. // naive token matching
  159. const defTokens = entry.definition.toLowerCase().split(/\W+/);
  160. let bestIndex = -1;
  161. let bestScore = -1;
  162.  
  163. for (let i = 0; i < choices.length; i++) {
  164. const choiceTokens = choices[i].innerText.trim().toLowerCase().split(/\W+/);
  165. let score = choiceTokens.filter(t => defTokens.includes(t)).length;
  166. if (score > bestScore) {
  167. bestScore = score;
  168. bestIndex = i;
  169. }
  170. }
  171.  
  172. if (bestIndex !== -1) {
  173. choices[bestIndex].click();
  174. console.log(`attempting definition match: "${choices[bestIndex].innerText.trim()}" (score: ${bestScore})`);
  175.  
  176. if (iscorrect(choices[bestIndex])) {
  177. qList[curQ.q] = choices[bestIndex].innerText.trim();
  178. localStorage.practiceLists = JSON.stringify(pLists);
  179. return true;
  180. }
  181. }
  182.  
  183. return false;
  184. }
  185.  
  186.  
  187. // -- Modular handler for question type 'F' (fill-based questions)
  188. async function handleTypeF(curq, qlist, choices, pLists) {
  189. const allDefs = JSON.parse(localStorage.getItem('words&defs') || '[]');
  190. const knownWords = allDefs.map(e => e.word?.toLowerCase()).filter(Boolean);
  191.  
  192. // Convert HTMLCollection to an array so we can safely use .map()
  193. const choiceArray = Array.from(choices);
  194.  
  195. const matchedWords = choiceArray
  196. .map((c, i) => ({ text: c.innerText.trim().toLowerCase(), index: i }))
  197. .filter(item => knownWords.includes(item.text));
  198.  
  199. // Update the overlay
  200. updateOverlay('F', matchedWords.length ? `Matched: ${matchedWords.map(m => m.text).join(', ')}` : 'No match found.');
  201.  
  202. // If exactly one match, click it
  203. if (matchedWords.length === 1) {
  204. const index = matchedWords[0].index;
  205. choices[index].click();
  206. console.log(`F-type guess: matched known word "${matchedWords[0].text}"`);
  207.  
  208. if (iscorrect(choices[index])) {
  209. qlist[curq.q] = matchedWords[0].text;
  210. localStorage.practiceLists = JSON.stringify(pLists);
  211. return true;
  212. }
  213. }
  214.  
  215. return false;
  216. }
  217.  
  218.  
  219.  
  220.  
  221. // -- main object to store question-to-answer mappings
  222. function practicelist(id) {
  223. this.id = id;
  224. this.qtyped = {};
  225. this.qtypes = {};
  226. this.qtypep = {};
  227. this.qtypeh = {};
  228. this.qtypel = {};
  229. this.qtypea = {};
  230. this.qtypef = {};
  231. this.qtypei = {};
  232. this.qtypeg = {};
  233. }
  234.  
  235. // -- read page context
  236. const parts = window.location.href.split('/');
  237. const ispractice = parts[3] === 'lists';
  238. const practiceid = parts[4];
  239. const stor = window.localStorage;
  240.  
  241. // -- load or create practice lists
  242. const plists = stor.practiceLists ? JSON.parse(stor.practiceLists) : {};
  243. if (!plists[practiceid]) {
  244. plists[practiceid] = new practicelist(practiceid);
  245. stor.practiceLists = JSON.stringify(plists);
  246. }
  247. const curlist = plists[practiceid];
  248. console.log(`curlist: ${curlist.id}`);
  249.  
  250. const keyword = ispractice ? '.question' : '.box-question';
  251. const keytypeindex = ispractice ? 4 : 5;
  252.  
  253. let lastq = null;
  254. let triedindices = [];
  255. let recordedtried = false;
  256.  
  257. // -- map question types to correct sub-objects
  258. function getqlist(list, t) {
  259. const map = {
  260. 'S': list.qtypes,
  261. 'D': list.qtyped,
  262. 'P': list.qtypep,
  263. 'H': list.qtypeh,
  264. 'L': list.qtypel,
  265. 'A': list.qtypea,
  266. 'F': list.qtypef,
  267. 'I': list.qtypei,
  268. 'G': list.qtypeg,
  269. };
  270. return map[t.toUpperCase()];
  271. }
  272.  
  273. // -- fill in blank type
  274. function answertypet(curq) {
  275. const ans = curq.querySelector('.complete').children[0].innerText;
  276. curq.querySelector('input').value = ans;
  277. curq.querySelector('.spellit').click();
  278. }
  279.  
  280. // -- click next
  281. function clicknext() {
  282. const btn = ispractice ? document.querySelector('.next') : document.querySelector('.btn-next');
  283. if (btn) btn.click();
  284. }
  285.  
  286. // -- helper: extracts an "answer" string from the choice (especially for images)
  287. function extractanswer(choice, qtype) {
  288. return qtype === 'I'
  289. ? choice.style.backgroundImage.split('/')[5]
  290. : choice.innerText.trim();
  291. }
  292.  
  293. // -- main loop
  294. setInterval(answerquestion, 300);
  295.  
  296. async function answerquestion() {
  297. if (paused) return; // skip logic if paused
  298.  
  299. const qnodes = document.querySelectorAll(keyword);
  300. if (!qnodes.length) return;
  301. const curq = qnodes[qnodes.length - 1];
  302. const classes = curq.classList[1] || '';
  303. const qtype = classes.charAt(keytypeindex).toUpperCase();
  304.  
  305. // Update overlay displays
  306. updateOverlay(qtype, 'Loading...');
  307.  
  308. // Type 'T' is fill-in-the-blank
  309. if (qtype === 'T') {
  310. answertypet(curq);
  311. clicknext();
  312. return;
  313. }
  314.  
  315. // Parse question text for various types
  316. if (qtype === 'P' || qtype === 'L' || qtype === 'H') {
  317. curq.q = curq.querySelector('.sentence').children[0].innerText;
  318. } else if (qtype === 'F') {
  319. curq.q = curq.querySelector('.sentence').innerText.split(' ')[0];
  320. } else if (qtype === 'I') {
  321. curq.q = ispractice
  322. ? curq.querySelector('.wrapper').innerText.split('\n')[1]
  323. : curq.querySelector('.box-word').innerText.split('\n')[1];
  324. } else if (qtype === 'G') {
  325. curq.q = curq.querySelector('.questionContent').style.backgroundImage.split('/')[5];
  326. } else {
  327. curq.q = curq.querySelector('.instructions strong').innerText;
  328. }
  329.  
  330. // Reset if new question
  331. if (lastq !== curq.q) {
  332. lastq = curq.q;
  333. triedindices = [];
  334. recordedtried = false;
  335. }
  336.  
  337. const qlist = getqlist(curlist, qtype);
  338. if (!qlist) return;
  339.  
  340. const choices = curq.querySelector('.choices').children;
  341.  
  342. // If we have a recorded answer, try it first
  343. if (qlist.hasOwnProperty(curq.q)) {
  344. updateOverlay(qtype, `Using recorded knowledge: "${qlist[curq.q]}"`);
  345. const stored = qlist[curq.q];
  346. let found = -1;
  347. for (let i = 0; i < choices.length; i++) {
  348. if (choices[i].innerText.trim() === stored) {
  349. found = i;
  350. break;
  351. }
  352. }
  353. if (found !== -1) {
  354. if (!recordedtried) {
  355. choices[found].click();
  356. console.log(`clicked recorded answer: "${stored}"`);
  357. recordedtried = true;
  358. return;
  359. } else {
  360. console.log(`recorded answer for "${curq.q}" failed. Removing & trying synonyms or random.`);
  361. delete qlist[curq.q];
  362. }
  363. } else {
  364. console.log(`recorded answer not found in choices, removing & trying synonyms or random.`);
  365. delete qlist[curq.q];
  366. }
  367. }
  368.  
  369. // Modular handling: attempt qtype-specific logic
  370. if (qtype === 'S') {
  371. const handled = await handleTypeS(curq, qlist, choices);
  372. if (handled) return;
  373. }
  374. if (qtype === 'D') {
  375. const handled = await handleTypeD(curq, qlist, choices, plists);
  376. if (handled) { clicknext(); return; }
  377. }
  378. if (qtype === 'F') {
  379. const handled = await handleTypeF(curq, qlist, choices, plists);
  380. if (handled) { clicknext(); return; }
  381. }
  382.  
  383.  
  384. // Fallback: sequential guess method as the last resort
  385. let available = [];
  386. for (let i = 0; i < choices.length; i++) {
  387. if (!triedindices.includes(i)) available.push(i);
  388. }
  389. if (!available.length) {
  390. triedindices = [];
  391. available = Array.from({ length: choices.length }, (_, i) => i);
  392. }
  393. const r = available[Math.floor(Math.random() * available.length)];
  394. choices[r].click();
  395.  
  396. if (iscorrect(choices[r])) {
  397. const ans = extractanswer(choices[r], qtype);
  398. qlist[curq.q] = ans;
  399. stor.practiceLists = JSON.stringify(plists);
  400. console.log(`recorded: "${curq.q}" -> "${ans}"`);
  401. triedindices = [];
  402. recordedtried = false;
  403. clicknext();
  404. } else {
  405. triedindices.push(r);
  406. }
  407. }
  408.  
  409. // -- initialize the pause overlay
  410. createpauseoverlay();
  411. })();

QingJ © 2025

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