Source code
// ==UserScript==
// @name Neopets - Shop Till Max Withdrawal
// @namespace https://www.scriptneo.com/
// @version 0.3
// @description Automatically keeps the withdrawal amount synced to the current NP in your shop till.
// @author Naud
// @license MIT
// @match *://www.neopets.com/market.phtml*
// @run-at document-idle
// @downloadURL https://www.scriptneo.com/scripts/download.php?id=8
// @updateURL https://www.scriptneo.com/scripts/download.php?id=8
// ==/UserScript==
(function () {
'use strict';
const DEBUG = false;
const SCRIPT_NAME = 'Shop Till Max Withdrawal';
let lastFilledAmount = null;
let fillTimer = null;
let observer = null;
function log(...args) {
if (DEBUG) console.log(`[${SCRIPT_NAME}]`, ...args);
}
function isTillPage() {
try {
const url = new URL(window.location.href);
return url.pathname.endsWith('/market.phtml') && url.searchParams.get('type') === 'till';
} catch {
return false;
}
}
function parseNP(value) {
const text = String(value || '').replace(/\s+/g, ' ');
const match = text.match(/([\d,]+)\s*NP/i) || text.match(/[\d,]+/);
if (!match) return 0;
const raw = match[1] || match[0];
const amount = parseInt(raw.replace(/,/g, ''), 10);
return Number.isFinite(amount) ? amount : 0;
}
function setInputValue(input, value) {
const setter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype,
'value'
)?.set;
if (setter) {
setter.call(input, String(value));
} else {
input.value = String(value);
}
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
}
function getBalanceElement() {
return (
document.querySelector('#mkt-till-balance') ||
[...document.querySelectorAll('b, strong, p, td, div, span')]
.find(el => /till/i.test(el.textContent || '') && /[\d,]+\s*NP/i.test(el.textContent || ''))
);
}
function getAmountInput() {
const form = document.querySelector('#mkt-till-form') || document.querySelector('form');
return (
document.querySelector('#mkt-till-form input[name="amount"]') ||
document.querySelector('input[name="amount"]') ||
(form ? [...form.querySelectorAll('input[type="text"], input:not([type])')]
.find(input => /amount|withdraw/i.test(input.name || input.id || input.placeholder || '')) : null)
);
}
function getTillAmount() {
const balanceEl = getBalanceElement();
return balanceEl ? parseNP(balanceEl.textContent) : 0;
}
function fillTillAmount(force = false) {
if (!isTillPage()) return false;
const amountInput = getAmountInput();
const tillAmount = getTillAmount();
if (!amountInput) {
log('Missing amount input.');
return false;
}
if (tillAmount <= 0) {
if (force || lastFilledAmount !== 0) {
setInputValue(amountInput, 0);
lastFilledAmount = 0;
}
log('Till is empty or not found.');
return false;
}
const currentAmount = parseNP(amountInput.value);
if (!force && currentAmount === tillAmount) {
return true;
}
setInputValue(amountInput, tillAmount);
lastFilledAmount = tillAmount;
log('Set withdrawal amount:', tillAmount);
return true;
}
function scheduleFill(force = false) {
clearTimeout(fillTimer);
fillTimer = setTimeout(() => {
fillTillAmount(force);
}, 100);
}
function attachSubmitHandler() {
document.addEventListener('submit', function (event) {
if (!isTillPage()) return;
const form = event.target;
if (!(form instanceof HTMLFormElement)) return;
const amountInput = form.querySelector('input[name="amount"]') || getAmountInput();
if (!amountInput) return;
fillTillAmount(true);
log('Rechecked till amount before submit.');
}, true);
document.addEventListener('click', function (event) {
if (!isTillPage()) return;
const target = event.target;
if (!(target instanceof Element)) return;
const button = target.closest('input[type="submit"], button[type="submit"], button, input[type="button"]');
if (!button) return;
const text = `${button.value || ''} ${button.textContent || ''}`;
if (/withdraw|collect|submit/i.test(text)) {
fillTillAmount(true);
log('Rechecked till amount before withdraw click.');
}
}, true);
}
function startObserver() {
if (observer) observer.disconnect();
observer = new MutationObserver(() => {
scheduleFill(false);
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
characterData: true
});
log('MutationObserver started.');
}
function startBackupInterval() {
setInterval(() => {
if (!isTillPage()) return;
const tillAmount = getTillAmount();
const input = getAmountInput();
if (!input || tillAmount <= 0) return;
const inputAmount = parseNP(input.value);
if (inputAmount !== tillAmount) {
fillTillAmount(true);
}
}, 1500);
}
function init() {
if (!isTillPage()) return;
fillTillAmount(true);
attachSubmitHandler();
startObserver();
startBackupInterval();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init, { once: true });
} else {
init();
}
})();