您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
When you click a clue, fills the Word box with the matching answer from TDN. Does not submit.
// ==UserScript== // @name Neopets Faerie Crossword — Fill word from TDN on clue click (no submit) // @namespace neopets // @version 1.0.0 // @description When you click a clue, fills the Word box with the matching answer from TDN. Does not submit. // @match https://www.neopets.com/games/crossword/crossword.phtml* // @match http://www.neopets.com/games/crossword/crossword.phtml* // @run-at document-idle // @grant GM_xmlhttpRequest // @connect thedailyneopets.com // @license MIT // ==/UserScript== (function () { "use strict"; // --- tiny helper --- const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); // --- fetch & parse TDN (https://thedailyneopets.com/index/fca) --- async function fetchTDN() { const url = "https://thedailyneopets.com/index/fca"; const html = await gmGet(url); return parseTDN(html); } function gmGet(url) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url, onload: (r) => { if (r.status >= 200 && r.status < 300) resolve(r.responseText); else reject(new Error(`${url} -> ${r.status}`)); }, onerror: () => reject(new Error(`${url} -> network error`)), }); }); } function textify(html) { // turn HTML into simple text so the simple regex works regardless of tags/brs return html .replace(/<script[\s\S]*?<\/script>/gi, "") .replace(/<style[\s\S]*?<\/style>/gi, "") .replace(/<br\s*\/?>/gi, "\n") .replace(/<\/p>/gi, "\n") .replace(/<[^>]+>/g, "") .replace(/\u00A0/g, " ") .replace(/[ \t]+/g, " ") .replace(/\n{2,}/g, "\n") .trim(); } function parseTDN(html) { const txt = textify(html); // Grab the Across block and the Down block const acrossBlock = /Across:\s*([\s\S]*?)\bDown:/i.exec(txt)?.[1] || ""; const downBlock = /\bDown:\s*([\s\S]*)$/i.exec(txt)?.[1] || ""; const across = extractNumWordPairs(acrossBlock); const down = extractNumWordPairs(downBlock); return { across, down }; // maps: number -> UPPERCASE word } function extractNumWordPairs(blockText) { // Accept lines like: "4. bbq" (maybe mixed case / punctuation) const map = new Map(); const lines = blockText.split("\n"); for (const line of lines) { const m = line.match(/^\s*(\d{1,2})\s*[\.\:\-)]\s*([A-Za-z'-]+)\s*$/); if (m) { const num = Number(m[1]); const word = m[2].toUpperCase(); // puzzle takes letters; upper is convenient if (!map.has(num)) map.set(num, word); } } return map; } function parseSetClueArgs(el) { // Extract (row, col, dir, num) from the onclick="set_clue(r,c,dir,num)" const s = (el.getAttribute("onclick") || "").replace(/\s+/g, ""); const m = s.match(/set_clue\((\d+),(\d+),(\d+),(\d+)\)/); if (!m) return null; return { row: +m[1], col: +m[2], dir: +m[3], num: +m[4] }; } // --- main wiring --- const answersPromise = fetchTDN().catch(() => ({ across: new Map(), down: new Map() })); document.addEventListener( "click", async (evt) => { const a = evt.target?.closest('a[onclick*="set_clue("]'); if (!a) return; // Let the page's set_clue() run first so it updates the “Active Clue” box await sleep(0); const args = parseSetClueArgs(a); if (!args) return; const form = document.forms?.clueform; const wordInput = form?.elements?.x_word; if (!wordInput) return; const { across, down } = await answersPromise; const isAcross = args.dir === 1; const map = isAcross ? across : down; const answer = map.get(args.num); if (answer) { wordInput.value = answer; // FILL ONLY — no submit } else { // If today’s page hasn’t updated yet, do nothing silently. // (You can manually type the word or refresh later.) } }, true ); })();