xd2shuffle

Visual larp: Changes ARS to $ everywhere (bets, balance, everything) - keeps numbers the same

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         xd2shuffle
// @namespace    http://tampermonkey.net/
// @version      7.7.8.1
// @description  Visual larp: Changes ARS to $ everywhere (bets, balance, everything) - keeps numbers the same
// @author       fusi | @loficat on tg | codedfusi on github
// @match        *://*.shuffle.com/*
// @grant        none
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function () {
    const processedTextNodes = new WeakSet();
    const processedImages = new WeakSet();

    // Force update a text node
    function updateTextNode(node) {
        if (node.nodeType !== 3 || !node.nodeValue) return false;
        
        let original = node.nodeValue;
        let newValue = original;
        let changed = false;

        // Replace ARS with $ (case insensitive just in case)
        if (newValue.includes("ARS")) {
            newValue = newValue.replace(/ARS/g, "$");
            changed = true;
        }
        
        // Replace lowercase "ars" if it appears
        if (newValue.includes("ars")) {
            newValue = newValue.replace(/ars/g, "$");
            changed = true;
        }

        // Remove space after the new $
        if (changed) {
            newValue = newValue.replace(/\$\s+/g, '$');
        }

        if (changed && newValue !== original) {
            node.nodeValue = newValue;
            return true;
        }
        return false;
    }

    function processTextNode(node) {
        if (node.nodeType === 3 && node.nodeValue && !processedTextNodes.has(node)) {
            if (updateTextNode(node)) {
                processedTextNodes.add(node);
            }
        }
    }

    function processImageElement(el) {
        if (el.nodeType === 1 && el.hasAttribute("src") && !processedImages.has(el)) {
            const src = el.getAttribute("src");
            if (src && src.includes("/icons/fiat/ARS.svg")) {
                el.setAttribute("src", src.replace("/icons/fiat/ARS.svg", "/icons/fiat/USD.svg"));
                processedImages.add(el);
            }
        }
    }

    function walkAndProcess(root) {
        if (!root) return;

        // Process all text nodes
        const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
        let node;
        while ((node = walker.nextNode())) {
            processTextNode(node);
        }

        // Process all ARS flag images
        const images = root.querySelectorAll('img[src*="/icons/fiat/ARS.svg"]');
        images.forEach(img => processImageElement(img));
    }

    // More aggressive mutation handling
    function onMutation(mutations) {
        let needsFullScan = false;
        
        for (const mutation of mutations) {
            // If attributes changed (like a bet amount updating), scan the target
            if (mutation.type === 'attributes') {
                if (mutation.target.nodeType === 1) {
                    // Re-scan this element and its children
                    walkAndProcess(mutation.target);
                    // Also scan its parent in case the change bubbled
                    if (mutation.target.parentNode) {
                        walkAndProcess(mutation.target.parentNode);
                    }
                }
                needsFullScan = true;
            }
            
            // If child nodes were added, process them
            if (mutation.type === 'childList') {
                for (const addedNode of mutation.addedNodes) {
                    if (addedNode.nodeType === 1) {
                        walkAndProcess(addedNode);
                    } else if (addedNode.nodeType === 3) {
                        processTextNode(addedNode);
                    }
                }
                needsFullScan = true;
            }
            
            // If text itself changed (characterData mutation)
            if (mutation.type === 'characterData') {
                if (mutation.target.nodeType === 3) {
                    // Re-process this text node even if it was processed before
                    const node = mutation.target;
                    if (node.nodeValue && (node.nodeValue.includes("ARS") || node.nodeValue.includes("ars"))) {
                        updateTextNode(node);
                    }
                }
                needsFullScan = true;
            }
        }
        
        // Do a quick partial scan of the body periodically to catch anything missed
        if (needsFullScan) {
            setTimeout(() => {
                // Scan the whole body but skip nodes we've already processed to save performance
                const allTextNodes = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
                let tn;
                while ((tn = allTextNodes.nextNode())) {
                    if (tn.nodeValue && (tn.nodeValue.includes("ARS") || tn.nodeValue.includes("ars"))) {
                        if (updateTextNode(tn)) {
                            processedTextNodes.add(tn);
                        }
                    }
                }
            }, 100);
        }
    }

    // Start when DOM is ready
    document.addEventListener("DOMContentLoaded", () => {
        walkAndProcess(document.body);

        // Watch for attribute changes (like bet amounts), text changes, AND child lists
        const observer = new MutationObserver(onMutation);
        observer.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true,      // <-- CRITICAL: catches bet amount updates
            attributeFilter: ['textContent', 'innerHTML', 'value'], // Focus on text-related attributes
            characterData: true    // <-- CRITICAL: catches direct text changes
        });
        
        // Also run every second as a backup (heavy but ensures bets get caught)
        setInterval(() => {
            const allTextNodes = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
            let tn;
            while ((tn = allTextNodes.nextNode())) {
                if (tn.nodeValue && (tn.nodeValue.includes("ARS") || tn.nodeValue.includes("ars"))) {
                    updateTextNode(tn);
                }
            }
        }, 1000);
    });
})();