Greasy Fork

Greasy Fork is available in English.

Wargaming商店外区货币转换器

Wargaming商店外区货币价值转换为CNY并附代充折扣显示(目前支持ARS、SGD、HKD、TWD)

当前为 2025-10-13 提交的版本,查看 最新版本

// ==UserScript==
// @name         Wargaming商店外区货币转换器
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Wargaming商店外区货币价值转换为CNY并附代充折扣显示(目前支持ARS、SGD、HKD、TWD)
// @author       SundayRX
// @match        https://wargaming.net/shop/*
// @grant        GM_xmlhttpRequest
// @connect      api.exchangerate-api.com
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // 配置
    const CONFIG = {

        // 打折
        discount:0.87, //代冲折扣(自行修改)
        // 更新间隔(毫秒)
        updateInterval: 600000, // 10分钟
        // 高亮样式
        highlightStyle: `
            .ars-to-cny-conversion {
                background-color: #ffffcc;
                border-radius: 3px;
                padding: 2px 4px;
                margin-left: 5px;
                font-weight: bold;
                font-size: 0.9em;
                color: #d32f2f;
            }
            .ars-to-cny-processed {
                display: inline-flex;
                align-items: center;
            }
        `,
        // 是否显示原始值
        showOriginalValue: true
    };
    class Currency {
            constructor(Type, ExchangeRateAPI, ExchangeRate, MatchRegex) {
                this.Type = Type;
                this.ExchangeRateAPI = ExchangeRateAPI;
                this.ExchangeRate = ExchangeRate;
                this.MatchRegex = MatchRegex;
            }
    }
    const CurrencyDict = [
        new Currency('ARS', 'https://api.exchangerate-api.com/v4/latest/ARS',0.005,/([\d,]+(?:\.\d+)?)\s*(ARS)/i),
        new Currency('SGD', 'https://api.exchangerate-api.com/v4/latest/SGD',5.5,/([\d,]+(?:\.\d+)?)\s*(SGD)/i),
        new Currency('HKD', 'https://api.exchangerate-api.com/v4/latest/HKD',0.916,/([\d,]+(?:\.\d+)?)\s*(HKD)/i),
        new Currency('TWD', 'https://api.exchangerate-api.com/v4/latest/TWD',0.233,/([\d,]+(?:\.\d+)?)\s*(TWD)/i)
    ];
    let isProcessing = false;
    let observer = null;

    // 初始化脚本
    function init() {
        console.log('ARS转CNY转换器已加载');

        // 添加样式
        addStyles();

        // 获取汇率
        FetchExchangeRate();

        // 设置定期更新汇率
        setInterval(FetchExchangeRate, CONFIG.updateInterval);

        // 初始转换
        convertPageARSValues();

        // 监听DOM变化
        observeDOMChanges();
    }

    // 添加样式到页面
    function addStyles() {
        const style = document.createElement('style');
        style.textContent = CONFIG.highlightStyle;
        document.head.appendChild(style);
    }

    // 获取汇率
    function FetchExchangeRate() {
        for(let C=0;C<CurrencyDict.length;C++)
        {
            GM_xmlhttpRequest({
                method: 'GET',
                url: CurrencyDict[C].ExchangeRateAPI,
                onload: function(response) {
                    try {
                            const data = JSON.parse(response.responseText);
                            if (data && data.rates && data.rates.CNY) {
                                CurrencyDict[C].ExchangeRate = data.rates.CNY;
                                console.log(`汇率已更新: 1 ${CurrencyDict[C].Type} = ${CurrencyDict[C].ExchangeRate} CNY`);

                                // 更新页面上的转换值
                                updateConvertedValues();
                            } else {
                                console.warn('无法从API获取汇率,使用备用汇率');
                        }
                    } catch (e) {
                        console.warn('解析汇率API响应失败:', e);
                    }
                },
                onerror: function(error) {
                    console.warn('获取汇率失败:', error, '使用备用汇率');
                }
            });
        }
    }

    function ContainsSpecialValue(text) {
        for(let C=0;C<CurrencyDict.length;C++)
        {
            const arsRegex = CurrencyDict[C].MatchRegex;
            if(arsRegex.test(text)) return true;
        }
        return false;
    }

    function ExtractSpecialValue(text) {
        for(let C=0;C<CurrencyDict.length;C++)
        {
            const match = text.match(CurrencyDict[C].MatchRegex);
            if (match && match[1])
            {
                const numericValue = match[1].replace(/,/g, '');
                return [parseFloat(numericValue),CurrencyDict[C].Type];
            }
        }
        return [null,null];
    }

    function FormatCurrency(value, currency) {
        for(let C=0;C<CurrencyDict.length;C++)
        {
            if (currency === CurrencyDict[C].Type)
            {
                const fixValue= value * CurrencyDict[C].ExchangeRate*CONFIG.discount;
                return `${(value * CurrencyDict[C].ExchangeRate).toFixed(2)}\(${fixValue.toFixed(2)}\)CNY`;
            }
        }
        return value.toFixed(2);
    }
    // 转换页面中的ARS值
    function convertPageARSValues() {
        if (isProcessing) return;
        isProcessing = true;

        // 暂停DOM监听器,避免重复处理
        if (observer) {
            observer.disconnect();
        }

        // 查找所有价格元素
        const priceElements = findPriceElements();

        // 处理价格元素
        priceElements.forEach(processPriceElement);

        // 重新启动DOM监听器
        if (observer) {
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        }

        isProcessing = false;
    }

    // 查找所有价格元素
    function findPriceElements() {
        const elements = [];
        const seenElements = new Set(); // 用于去重

        // 使用更精确的选择器,避免重复匹配
        const selectors = [
            // '.product-price_old:not(.ars-to-cny-processed)',
            // '[data-qa="original_product_price"]:not(.ars-to-cny-processed)',
            '.product-price_wrap > span:not(.ars-to-cny-processed)',
            '[data-qa="product_price"] > span:not(.ars-to-cny-processed)'
        ];

        selectors.forEach(selector => {
            const foundElements = document.querySelectorAll(selector);
            foundElements.forEach(element => {
                // 检查元素是否包含ARS货币值且未被见过
                if (ContainsSpecialValue(element.textContent) && !seenElements.has(element)) {
                    elements.push(element);
                    seenElements.add(element);
                }
            });
        });

        return elements;
    }


    // 处理价格元素
    function processPriceElement(element) {
        if (element.classList.contains('ars-to-cny-processed')) {
            return;
        }

        const originalText = element.textContent.trim();
        const [PriceValue,PriceType] = ExtractSpecialValue(originalText);

        if (PriceValue !== null && PriceValue!=null) {
            const formattedCNY = FormatCurrency(PriceValue, PriceType);

            // 检查是否已经存在转换元素
            const existingConversion = findExistingConversion(element);
            if (existingConversion) {
                // 如果已存在,更新它
                existingConversion.textContent = `≈${formattedCNY}`;
                existingConversion.title = `${formatNumber(PriceValue)} ARS ≈ ${formattedCNY}`;
            } else {
                // 如果不存在,创建新的转换元素
                const conversionElement = document.createElement('span');
                conversionElement.className = 'ars-to-cny-conversion';
                conversionElement.textContent = `≈${formattedCNY}`;
                conversionElement.title = `${formatNumber(PriceValue)} ARS ≈ ${formattedCNY}`;

                // 插入到价格元素后面
                element.parentNode.insertBefore(conversionElement, element.nextSibling);
            }

            // 标记元素为已处理
            element.classList.add('ars-to-cny-processed');
        }
    }

    // 查找已存在的转换元素
    function findExistingConversion(element) {
        // 检查相邻的兄弟元素
        let sibling = element.nextElementSibling;
        while (sibling) {
            if (sibling.classList && sibling.classList.contains('ars-to-cny-conversion')) {
                return sibling;
            }
            sibling = sibling.nextElementSibling;
        }

        // 检查父元素的兄弟元素(针对某些特殊布局)
        const parent = element.parentElement;
        if (parent && parent.nextElementSibling) {
            const nextSibling = parent.nextElementSibling;
            if (nextSibling.classList && nextSibling.classList.contains('ars-to-cny-conversion')) {
                return nextSibling;
            }
        }

        return null;
    }

    // 格式化数字(添加千位分隔符)
    function formatNumber(num) {
        return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    }

    // 更新已转换的值
    function updateConvertedValues() {
        const convertedElements = document.querySelectorAll('.ars-to-cny-conversion');
        convertedElements.forEach(element => {
            // 查找相邻的价格元素
            const priceElement = findAdjacentPriceElement(element);
            if (priceElement) {
                const originalText = priceElement.textContent.trim();
                const [PriceValue,PriceType] = ExtractSpecialValue(originalText);

                if (PriceValue !== null && PriceType!=null) {

                    const formattedCNY = FormatCurrency(PriceValue, PriceType);

                    element.textContent = `≈${formattedCNY}`;
                    element.title = `${formatNumber(PriceValue)} ARS ≈ ${formattedCNY}`;
                }
            }
        });
    }

    // 查找相邻的价格元素
    function findAdjacentPriceElement(conversionElement) {
        // 检查前一个兄弟元素
        let sibling = conversionElement.previousElementSibling;
        while (sibling) {
            if (sibling.classList && sibling.classList.contains('ars-to-cny-processed')) {
                return sibling;
            }
            sibling = sibling.previousElementSibling;
        }

        // 检查父元素的前一个兄弟元素
        const parent = conversionElement.parentElement;
        if (parent && parent.previousElementSibling) {
            const prevSibling = parent.previousElementSibling;
            if (prevSibling.classList && prevSibling.classList.contains('ars-to-cny-processed')) {
                return prevSibling;
            }
        }

        return null;
    }

    // 监听DOM变化
    function observeDOMChanges() {
        observer = new MutationObserver(function(mutations) {
            let shouldConvert = false;

            mutations.forEach(function(mutation) {
                if (mutation.type === 'childList') {
                    mutation.addedNodes.forEach(function(node) {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            // 检查新添加的元素是否包含价格元素
                            const priceSelectors = [
                                // '.product-price_old:not(.ars-to-cny-processed)',
                                // '[data-qa="original_product_price"]:not(.ars-to-cny-processed)',
                                '.product-price_wrap > span:not(.ars-to-cny-processed)',
                                '[data-qa="product_price"] > span:not(.ars-to-cny-processed)'
                            ];

                            const priceElements = node.querySelectorAll ?
                                node.querySelectorAll(priceSelectors.join(', ')) : [];

                            if (priceElements.length > 0) {
                                shouldConvert = true;
                            }

                            // 检查元素本身是否是价格元素
                            if (node.matches && node.matches(priceSelectors.join(', '))) {
                                shouldConvert = true;
                            }
                        }
                    });
                }
            });

            if (shouldConvert && !isProcessing) {
                // 使用防抖,避免频繁处理
                clearTimeout(window.arsToCnyTimeout);
                window.arsToCnyTimeout = setTimeout(() => {
                    convertPageARSValues();
                }, 500);
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    // 页面加载完成后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();