您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a "Quickpick 20 tickets!" button that only buys as many as needed to reach 20 (with slight delay), plus a "Quickpick (fill only)" button.
// ==UserScript== // @name Neopets Lottery Quickpick // @namespace neopets // @version 1.3 // @description Adds a "Quickpick 20 tickets!" button that only buys as many as needed to reach 20 (with slight delay), plus a "Quickpick (fill only)" button. // @match https://www.neopets.com/games/lottery.phtml* // @match http://www.neopets.com/games/lottery.phtml* // @run-at document-idle // @grant GM_getValue // @grant GM_setValue // @license MIT // ==/UserScript== (function () { 'use strict'; // ---- Config ---- const MIN_DELAY_SEC = 6; const MAX_DELAY_SEC = 10; const DAILY_CAP = 20; // site limit per day const K = { ACTIVE: 'neo_lotto_active', RUNS: 'neo_lotto_runs', // how many we've bought in the current batch TODO: 'neo_lotto_todo', // how many we intend to buy in total this batch GUARD: 'neo_lotto_guard', }; if (window[K.GUARD]) return; window[K.GUARD] = true; // ---- Helpers ---- const randInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; function generateTicket() { const s = new Set(); while (s.size < 6) s.add(randInt(1, 30)); return [...s].sort((a, b) => a - b); } function toast(msg, ms = 2600) { const el = document.createElement('div'); el.textContent = msg; Object.assign(el.style, { position: 'fixed', bottom: '12px', right: '12px', padding: '8px 10px', background: 'rgba(0,0,0,.8)', color: '#fff', font: '12px/1.35 monospace', borderRadius: '6px', zIndex: 2147483647, }); document.body.appendChild(el); setTimeout(() => el.remove(), ms); } function findForm() { const one = document.querySelector('input[name="one"]'); return one ? one.closest('form') : null; } function fillNumbers(nums, form) { const fields = ['one','two','three','four','five','six']; fields.forEach((name, i) => { const el = form.querySelector(`input[name="${name}"]`); if (el) el.value = String(nums[i]); }); } function submitForm(form) { const btn = form.querySelector('input[type="submit"][value*="Buy a Lottery Ticket"]') || form.querySelector('input[type="submit"]') || form.querySelector('button[type="submit"]'); (btn && btn.click()) || form.submit(); } // Count how many tickets are already listed for the next draw function countOwnedTickets() { // Count occurrences like: "Ticket 1 :" (body text is most robust across markup changes) const text = document.body?.innerText || ''; const matches = text.match(/Ticket\s+\d+\s*:/g); return (matches ? matches.length : 0); } function scheduleOne(form, nextRunIndex, todoTotal) { const remaining = Math.max(0, todoTotal - (nextRunIndex - 1)); if (remaining <= 0) { GM_setValue(K.ACTIVE, false); return; } const delaySec = randInt(MIN_DELAY_SEC, MAX_DELAY_SEC); toast(`Quickpick #${nextRunIndex}/${todoTotal} in ${delaySec}s…`); setTimeout(() => { const nums = generateTicket(); fillNumbers(nums, form); // Persist progress BEFORE navigating GM_setValue(K.RUNS, nextRunIndex); // Stop after hitting our intended count if (nextRunIndex >= todoTotal) GM_setValue(K.ACTIVE, false); submitForm(form); }, delaySec * 1000); } function injectButtons(form) { if (document.getElementById('neo-quickpick20')) return; // Find submit button to place our main button beside it const submitBtn = form.querySelector('input[type="submit"][value*="Buy a Lottery Ticket"]') || form.querySelector('input[type="submit"]') || form.querySelector('button[type="submit"]'); // --- Quickpick to 20 (auto-buy as needed) --- const qp = document.createElement('button'); qp.type = 'button'; qp.id = 'neo-quickpick20'; qp.textContent = 'Quickpick 20 tickets!'; qp.style.marginLeft = '8px'; qp.style.padding = '3px 8px'; qp.style.cursor = 'pointer'; (submitBtn && submitBtn.parentNode) ? submitBtn.parentNode.insertBefore(qp, submitBtn.nextSibling) : form.appendChild(qp); qp.addEventListener('click', () => { const owned = countOwnedTickets(); const need = Math.max(0, Math.min(DAILY_CAP, DAILY_CAP - owned)); if (need <= 0) { GM_setValue(K.ACTIVE, false); GM_setValue(K.RUNS, 0); GM_setValue(K.TODO, 0); toast('You already have 20 tickets — nothing to buy.'); return; } GM_setValue(K.RUNS, 0); GM_setValue(K.TODO, need); GM_setValue(K.ACTIVE, true); toast(`Quickpick started: buying ${need} ticket(s) with 6–10s delays.`); const newForm = findForm(); if (newForm) scheduleOne(newForm, /*nextRunIndex*/ 1, /*todoTotal*/ need); }); // --- Fill-only button BELOW the submit row --- const lowerBar = document.createElement('div'); lowerBar.id = 'neo-quickpick-lowerbar'; lowerBar.style.marginTop = '8px'; const fillOnly = document.createElement('button'); fillOnly.type = 'button'; fillOnly.id = 'neo-quickpick-fill'; fillOnly.textContent = 'Quickpick (fill only)'; fillOnly.style.padding = '3px 8px'; fillOnly.style.cursor = 'pointer'; fillOnly.addEventListener('click', () => { const nums = generateTicket(); fillNumbers(nums, form); toast(`Filled: ${nums.join(' ')}`); }); lowerBar.appendChild(fillOnly); form.appendChild(lowerBar); } // ---- Wire up page ---- const form = findForm(); if (!form) return; injectButtons(form); // If a batch is in progress, only run up to the intended amount const active = !!GM_getValue(K.ACTIVE); const runsSoFar = Number(GM_getValue(K.RUNS) || 0); const todoTotal = Number(GM_getValue(K.TODO) || 0); if (active && runsSoFar < todoTotal) { scheduleOne(form, runsSoFar + 1, todoTotal); } else if (runsSoFar >= todoTotal) { GM_setValue(K.ACTIVE, false); } // ESC to stop auto-buy batch window.addEventListener('keydown', (e) => { if (e.key === 'Escape') { GM_setValue(K.ACTIVE, false); toast('Quickpick stopped.'); } }); })();