Esim.gg 取消充值限制

取消ESIM.GG充值金额限制

// ==UserScript==
// @name         Esim.gg 取消充值限制
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  取消ESIM.GG充值金额限制
// @author       @fooyao2
// @match        https://esim.gg/*
// @run-at       document-start
// @license MIT
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    // ---------- Config ----------
    const ESTONIA_PATH = '/app/new/number/estonia/page-';
    const BULK_PATH = '/estonia/bulk/page-';

    // ---------- Utils ----------
    function classifyUrl(url) {
        const s = (url || '').toString();
        const isBulk = s.includes(BULK_PATH);
        const isTarget = isBulk || s.includes(ESTONIA_PATH);
        return { isTarget, isBulk };
    }

    function absoluteUrl(url) {
        const s = (url || '').toString();
        if (!s) return '';
        if (s.startsWith('http://') || s.startsWith('https://')) return s;
        if (s.startsWith('/')) return 'https://esim.gg' + s;
        return s;
    }

    function injectScript(code) {
        const s = document.createElement('script');
        s.textContent = code;
        (document.head || document.documentElement).appendChild(s);
    }

    function getScriptSrc(el) {
        // Prefer property, then attribute (covers TrustedScriptURL/URL-ish values)
        return el && (el.src || el.getAttribute('src') || '');
    }

    // ---------- Core replacement ----------
    function replaceContent(text, isBulkPage) {
        let m = text;

        if (isBulkPage) {
            // useState(.25) -> useState(.001)
            m = m.replace(/useState\)\(\.25\)/g, 'useState)(.001)');

            // number input: min .25 -> 0, step .05 -> .001
            m = m.replace(/type:"number",min:\.25,max:10,step:\.05/g, 'type:"number",min:0,max:10,step:.001');
            m = m.replace(/min:\.25/g, 'min:0');
            m = m.replace(/step:\.05/g, 'step:.001');

            // Display text €0.25 -> €0
            m = m.replace(/"€0\.25"/g, '"€0"')
                .replace(/'€0\.25'/g, "'€0'")
                .replace(/>€0\.25</g, '>€0<');
        } else {
            // Array of quick values
            m = m.replace(/\[\.5,1,2,5,10,25,50,100,200\]/g, '[.01,.1,.2,.5,1,2,5,10,25]');

            // Conditions and input attributes
            m = m.replace(/V>=\.5/g, 'V>=.001');
            m = m.replace(/type:"number",step:"0\.01",min:"0\.5"/g, 'type:"number",step:"0.001",min:"0"');

            // Math constraints
            m = m.replace(/Math\.max\(\.5,/g, 'Math.max(.001,')
                .replace(/Math\.max\(0\.5,/g, 'Math.max(0.001,');
            m = m.replace(/parseFloat\(([^)]+)\)\|\|1/g, 'parseFloat($1)||.001');

            // Slider
            m = m.replace(/min:\.5,max:200,step:\.5/g, 'min:.001,max:200,step:.001')
                .replace(/min:\.5(?=,|;|})/g, 'min:.001')
                .replace(/step:\.5(?=,|;|})/g, 'step:.001');

            // Display text €0.50 -> €0
            m = m.replace(/"€0\.50"/g, '"€0"')
                .replace(/'€0\.50'/g, "'€0'")
                .replace(/>€0\.50</g, '>€0<');
        }

        return m;
    }

    function fetchReplaceAndRun(url, isBulk) {
        const full = absoluteUrl(url);
        if (!full) return;
        fetch(full)
            .then(r => r.text())
            .then(code => injectScript(replaceContent(code, isBulk)))
            .catch(() => {});
    }

    // Handle a script node if it targets our Estonia/Bulk pages.
    function handleScriptNode(scriptEl) {
        if (!scriptEl || scriptEl.tagName !== 'SCRIPT') return false;
        const src = getScriptSrc(scriptEl);
        const { isTarget, isBulk } = classifyUrl(src);
        if (!isTarget) return false;

        // Prevent original script from loading/executing
        scriptEl.removeAttribute('src');
        scriptEl.type = 'text/plain';
        if (scriptEl.parentNode) scriptEl.parentNode.removeChild(scriptEl);

        // Load, patch, and inject our modified code
        fetchReplaceAndRun(src, isBulk);
        return true;
    }

    // ---------- Hooks (minimal + robust) ----------
    const originalAppendChild = Element.prototype.appendChild;
    Element.prototype.appendChild = function (child) {
        if (child && child.tagName === 'SCRIPT' && getScriptSrc(child)) {
            if (handleScriptNode(child)) return child;
        }
        return originalAppendChild.call(this, child);
    };

    const originalInsertBefore = Element.prototype.insertBefore;
    Element.prototype.insertBefore = function (newNode, referenceNode) {
        if (newNode && newNode.tagName === 'SCRIPT' && getScriptSrc(newNode)) {
            if (handleScriptNode(newNode)) return newNode;
        }
        return originalInsertBefore.call(this, newNode, referenceNode);
    };

    // Catch scripts created via innerHTML/parsing where append hooks may not run first
    const mo = new MutationObserver((mutations) => {
        for (const m of mutations) {
            for (const node of m.addedNodes) {
                if (node && node.tagName === 'SCRIPT') {
                    handleScriptNode(node);
                }
            }
        }
    });
    mo.observe(document.documentElement || document, { childList: true, subtree: true });

    // Fallback: scan at DOMContentLoaded in case anything slipped through
    document.addEventListener('DOMContentLoaded', () => {
        document.querySelectorAll('script[src*="estonia"]').forEach((s) => {
            handleScriptNode(s);
        });
    });
})();