UI Tweaks By web crawler 136 installs Rating 0.0 (0) approved

Neopets - Shop Till Max Withdrawal

Neopets - Shop Till Max Withdrawal
neopets shop shop till till
https://www.scriptneo.com/script/neopets-shop-till-max-withdrawal

Version selector


SHA256
9e6fc99ef8f6d7ecf7513c90b7bbc67274d55bbeb3eade4a2baba05b94f3d65f
No scan flags on this version.

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();
    }
})();