您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
2/21/2025, 7:27:07 PM
// ==UserScript== // @name vocabulary.com bot // @namespace Violentmonkey Scripts // @match https://www.vocabulary.com/lists/*/practice* // @grant none // @version 1.0 // @author - // @description 2/21/2025, 7:27:07 PM // @license GPL-3.0-or-later // ==/UserScript== /* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ (function () { 'use strict'; // -- state for pausing let paused = false; // -- create a small overlay to toggle pause and display extra info function createpauseoverlay() { const div = document.createElement('div'); div.id = 'pause-overlay'; div.style.position = 'fixed'; div.style.top = '10px'; div.style.left = '10px'; div.style.zIndex = '9999'; div.style.background = '#333'; div.style.color = '#fff'; div.style.padding = '8px'; div.style.cursor = 'pointer'; div.style.borderRadius = '4px'; // pause button const pauseText = document.createElement('div'); pauseText.innerText = 'pause script'; pauseText.style.marginBottom = '5px'; pauseText.style.cursor = 'pointer'; pauseText.addEventListener('click', () => { paused = !paused; pauseText.innerText = paused ? 'resume script' : 'pause script'; console.log(paused ? 'script paused' : 'script resumed'); }); // dynamic display area (shared for all q types) const infoDisplay = document.createElement('div'); infoDisplay.id = 'info-display'; infoDisplay.style.fontSize = '12px'; infoDisplay.style.color = '#ddd'; infoDisplay.innerHTML = ` <div id="qtype-display">Q Type: N/A</div> <div id="extra-info">Info: N/A</div> `; div.appendChild(pauseText); div.appendChild(infoDisplay); document.body.appendChild(div); } function updateOverlay(qtype, info) { const qtypeDisplay = document.getElementById('qtype-display'); const extraInfo = document.getElementById('extra-info'); if (qtypeDisplay) qtypeDisplay.innerText = `Q Type: ${qtype}`; if (extraInfo) extraInfo.innerText = `Info: ${info || 'N/A'}`; } // -- "synonym" fetcher using vocabulary.com dictionary page async function fetchsynonyms(word) { const url = `https://www.vocabulary.com/dictionary/${encodeURIComponent(word)}`; try { const resp = await fetch(url); if (!resp.ok) throw new Error('Failed to fetch vocabulary.com page'); const text = await resp.text(); const parser = new DOMParser(); const doc = parser.parseFromString(text, 'text/html'); let synonyms = []; const instances = doc.querySelectorAll("div.div-replace-dl.instances"); instances.forEach(instance => { const detailSpan = instance.querySelector("span.detail"); if (detailSpan && detailSpan.textContent.trim().toLowerCase().includes("synonyms")) { instance.querySelectorAll("a.word").forEach(a => { synonyms.push(a.textContent.trim().toLowerCase()); }); } }); return synonyms; } catch (err) { console.error("Error fetching synonyms from vocabulary.com:", err); return []; } } // -- helper to see if a choice is correct function iscorrect(choice) { return choice.className.includes('correct'); } // -- helper: attempt synonyms only if qtype == 'S' async function handleTypeS(curq, qlist, choices) { const synonyms = await fetchsynonyms(curq.q.toLowerCase()); updateOverlay('S', synonyms.length ? `Synonyms: ${synonyms.join(', ')}` : 'No synonyms found.'); if (!synonyms.length) { console.log('no synonyms found. falling back.'); return false; } for (let i = 0; i < choices.length; i++) { const text = choices[i].innerText.trim().toLowerCase(); if (synonyms.includes(text)) { console.log(`clicking synonym match: ${text}`); choices[i].click(); if (iscorrect(choices[i])) { qlist[curq.q] = text; localStorage.practiceLists = JSON.stringify(plists); console.log(`recorded: "${curq.q}" -> "${text}"`); clicknext(); } return true; } } return false; } // -- Modular handler for question type 'D' (definition-based questions) async function handleTypeD(curQ, qList, choices, pLists) { const allDefs = JSON.parse(localStorage.getItem('words&defs') || '[]'); const entry = allDefs.find(e => e.word?.toLowerCase() === curQ.q.toLowerCase()); if (!entry || !entry.definition) { console.log(`no local definition found for "${curQ.q}"`); updateOverlay('D', 'No definition found.'); return false; } updateOverlay('D', `Definition: ${entry.definition}`); // naive token matching const defTokens = entry.definition.toLowerCase().split(/\W+/); let bestIndex = -1; let bestScore = -1; for (let i = 0; i < choices.length; i++) { const choiceTokens = choices[i].innerText.trim().toLowerCase().split(/\W+/); let score = choiceTokens.filter(t => defTokens.includes(t)).length; if (score > bestScore) { bestScore = score; bestIndex = i; } } if (bestIndex !== -1) { choices[bestIndex].click(); console.log(`attempting definition match: "${choices[bestIndex].innerText.trim()}" (score: ${bestScore})`); if (iscorrect(choices[bestIndex])) { qList[curQ.q] = choices[bestIndex].innerText.trim(); localStorage.practiceLists = JSON.stringify(pLists); return true; } } return false; } // -- Modular handler for question type 'F' (fill-based questions) async function handleTypeF(curq, qlist, choices, pLists) { const allDefs = JSON.parse(localStorage.getItem('words&defs') || '[]'); const knownWords = allDefs.map(e => e.word?.toLowerCase()).filter(Boolean); // Convert HTMLCollection to an array so we can safely use .map() const choiceArray = Array.from(choices); const matchedWords = choiceArray .map((c, i) => ({ text: c.innerText.trim().toLowerCase(), index: i })) .filter(item => knownWords.includes(item.text)); // Update the overlay updateOverlay('F', matchedWords.length ? `Matched: ${matchedWords.map(m => m.text).join(', ')}` : 'No match found.'); // If exactly one match, click it if (matchedWords.length === 1) { const index = matchedWords[0].index; choices[index].click(); console.log(`F-type guess: matched known word "${matchedWords[0].text}"`); if (iscorrect(choices[index])) { qlist[curq.q] = matchedWords[0].text; localStorage.practiceLists = JSON.stringify(pLists); return true; } } return false; } // -- main object to store question-to-answer mappings function practicelist(id) { this.id = id; this.qtyped = {}; this.qtypes = {}; this.qtypep = {}; this.qtypeh = {}; this.qtypel = {}; this.qtypea = {}; this.qtypef = {}; this.qtypei = {}; this.qtypeg = {}; } // -- read page context const parts = window.location.href.split('/'); const ispractice = parts[3] === 'lists'; const practiceid = parts[4]; const stor = window.localStorage; // -- load or create practice lists const plists = stor.practiceLists ? JSON.parse(stor.practiceLists) : {}; if (!plists[practiceid]) { plists[practiceid] = new practicelist(practiceid); stor.practiceLists = JSON.stringify(plists); } const curlist = plists[practiceid]; console.log(`curlist: ${curlist.id}`); const keyword = ispractice ? '.question' : '.box-question'; const keytypeindex = ispractice ? 4 : 5; let lastq = null; let triedindices = []; let recordedtried = false; // -- map question types to correct sub-objects function getqlist(list, t) { const map = { 'S': list.qtypes, 'D': list.qtyped, 'P': list.qtypep, 'H': list.qtypeh, 'L': list.qtypel, 'A': list.qtypea, 'F': list.qtypef, 'I': list.qtypei, 'G': list.qtypeg, }; return map[t.toUpperCase()]; } // -- fill in blank type function answertypet(curq) { const ans = curq.querySelector('.complete').children[0].innerText; curq.querySelector('input').value = ans; curq.querySelector('.spellit').click(); } // -- click next function clicknext() { const btn = ispractice ? document.querySelector('.next') : document.querySelector('.btn-next'); if (btn) btn.click(); } // -- helper: extracts an "answer" string from the choice (especially for images) function extractanswer(choice, qtype) { return qtype === 'I' ? choice.style.backgroundImage.split('/')[5] : choice.innerText.trim(); } // -- main loop setInterval(answerquestion, 300); async function answerquestion() { if (paused) return; // skip logic if paused const qnodes = document.querySelectorAll(keyword); if (!qnodes.length) return; const curq = qnodes[qnodes.length - 1]; const classes = curq.classList[1] || ''; const qtype = classes.charAt(keytypeindex).toUpperCase(); // Update overlay displays updateOverlay(qtype, 'Loading...'); // Type 'T' is fill-in-the-blank if (qtype === 'T') { answertypet(curq); clicknext(); return; } // Parse question text for various types if (qtype === 'P' || qtype === 'L' || qtype === 'H') { curq.q = curq.querySelector('.sentence').children[0].innerText; } else if (qtype === 'F') { curq.q = curq.querySelector('.sentence').innerText.split(' ')[0]; } else if (qtype === 'I') { curq.q = ispractice ? curq.querySelector('.wrapper').innerText.split('\n')[1] : curq.querySelector('.box-word').innerText.split('\n')[1]; } else if (qtype === 'G') { curq.q = curq.querySelector('.questionContent').style.backgroundImage.split('/')[5]; } else { curq.q = curq.querySelector('.instructions strong').innerText; } // Reset if new question if (lastq !== curq.q) { lastq = curq.q; triedindices = []; recordedtried = false; } const qlist = getqlist(curlist, qtype); if (!qlist) return; const choices = curq.querySelector('.choices').children; // If we have a recorded answer, try it first if (qlist.hasOwnProperty(curq.q)) { updateOverlay(qtype, `Using recorded knowledge: "${qlist[curq.q]}"`); const stored = qlist[curq.q]; let found = -1; for (let i = 0; i < choices.length; i++) { if (choices[i].innerText.trim() === stored) { found = i; break; } } if (found !== -1) { if (!recordedtried) { choices[found].click(); console.log(`clicked recorded answer: "${stored}"`); recordedtried = true; return; } else { console.log(`recorded answer for "${curq.q}" failed. Removing & trying synonyms or random.`); delete qlist[curq.q]; } } else { console.log(`recorded answer not found in choices, removing & trying synonyms or random.`); delete qlist[curq.q]; } } // Modular handling: attempt qtype-specific logic if (qtype === 'S') { const handled = await handleTypeS(curq, qlist, choices); if (handled) return; } if (qtype === 'D') { const handled = await handleTypeD(curq, qlist, choices, plists); if (handled) { clicknext(); return; } } if (qtype === 'F') { const handled = await handleTypeF(curq, qlist, choices, plists); if (handled) { clicknext(); return; } } // Fallback: sequential guess method as the last resort let available = []; for (let i = 0; i < choices.length; i++) { if (!triedindices.includes(i)) available.push(i); } if (!available.length) { triedindices = []; available = Array.from({ length: choices.length }, (_, i) => i); } const r = available[Math.floor(Math.random() * available.length)]; choices[r].click(); if (iscorrect(choices[r])) { const ans = extractanswer(choices[r], qtype); qlist[curq.q] = ans; stor.practiceLists = JSON.stringify(plists); console.log(`recorded: "${curq.q}" -> "${ans}"`); triedindices = []; recordedtried = false; clicknext(); } else { triedindices.push(r); } } // -- initialize the pause overlay createpauseoverlay(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址