Greasy Fork

Greasy Fork is available in English.

Wargaming商店外区货币转换器

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

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

// ==UserScript==
// @name         Wargaming商店外区货币转换器
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Wargaming商店外区货币价值转换为CNY并附代充折扣显示(目前支持ARS、SGD、HKD、TWD、USD、JPY)
// @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: `
            .currency-conversion {
                background-color: #ffffcc;
                border-radius: 3px;
                padding: 2px 4px;
                margin-left: 5px;
                font-weight: bold;
                font-size: 0.9em;
                color: #d32f2f;
                display: inline-block;
            }
            .currency-processed {
                display: inline-flex;
                align-items: center;
            }
        `,
        showOriginalValue: true
    };

    class Currency {
        constructor(Type, ExchangeRateAPI, ExchangeRate, MatchRegex, Symbol = null) {
            this.Type = Type;
            this.ExchangeRateAPI = ExchangeRateAPI;
            this.ExchangeRate = ExchangeRate;
            this.MatchRegex = MatchRegex;
            this.Symbol = Symbol;
        }
    }

    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),
        new Currency('USD', 'https://api.exchangerate-api.com/v4/latest/USD', 7.2, null, 'USD'),
        new Currency('JPY', 'https://api.exchangerate-api.com/v4/latest/JPY', 0.048, null, 'JPY')
    ];

    let isProcessing = false;
    let observer = null;
    let processedElements = new Set();

    // 初始化脚本
    function init() {
        console.log('Wargaming商店货币转换器已加载 - 支持ARS, SGD, HKD, TWD, USD, JPY');
        addStyles();
        FetchExchangeRate();
        setInterval(FetchExchangeRate, CONFIG.updateInterval);
        convertPageCurrencyValues();
        observeDOMChanges();
    }

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

    // 获取汇率
    function FetchExchangeRate() {
        CurrencyDict.forEach(currency => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: currency.ExchangeRateAPI,
                onload: function(response) {
                    try {
                        const data = JSON.parse(response.responseText);
                        if (data?.rates?.CNY) {
                            currency.ExchangeRate = data.rates.CNY;
                            console.log(`汇率已更新: 1 ${currency.Type} = ${currency.ExchangeRate} CNY`);
                            updateConvertedValues();
                        } else {
                            console.warn(`无法从API获取${currency.Type}汇率,使用备用汇率`);
                        }
                    } catch (e) {
                        console.warn(`解析${currency.Type}汇率API响应失败:`, e);
                    }
                },
                onerror: function(error) {
                    console.warn(`获取${currency.Type}汇率失败:`, error, '使用备用汇率');
                }
            });
        });
    }

    function ContainsSpecialValue(element) {
        if (processedElements.has(element) || element.closest('.currency-processed')) {
            return false;
        }

        const text = element.textContent;
        const currencyElements = element.querySelectorAll('.currency-code');
        for (let currencyElement of currencyElements) {
            const title = currencyElement.getAttribute('title');
            if (title && (title === 'USD' || title === 'JPY')) {
                return true;
            }
        }

        for (let currency of CurrencyDict) {
            if (currency.MatchRegex && currency.MatchRegex.test(text)) {
                return true;
            }
        }
        return false;
    }

    function ExtractSpecialValue(element) {
        const text = element.textContent;
        const currencyElements = element.querySelectorAll('.currency-code');
        for (let currencyElement of currencyElements) {
            const title = currencyElement.getAttribute('title');
            if (title === 'USD' || title === 'JPY') {
                let priceContainer = element;
                while (priceContainer && !priceContainer.textContent.match(/([\d,]+(?:\.\d+)?)/)) {
                    priceContainer = priceContainer.parentElement;
                }

                if (priceContainer) {
                    const priceText = priceContainer.textContent;
                    const priceMatch = priceText.match(/([\d,]+(?:\.\d+)?)/);
                    if (priceMatch) {
                        const numericValue = priceMatch[1].replace(/,/g, '');
                        return [parseFloat(numericValue), title];
                    }
                }
            }
        }

        for (let currency of CurrencyDict) {
            if (currency.MatchRegex) {
                const match = text.match(currency.MatchRegex);
                if (match && match[1]) {
                    const numericValue = match[1].replace(/,/g, '');
                    return [parseFloat(numericValue), currency.Type];
                }
            }
        }
        return [null, null];
    }

    function FormatCurrency(value, currency) {
        const targetCurrency = CurrencyDict.find(c => c.Type === currency);
        if (targetCurrency) {
            const originalValue = value * targetCurrency.ExchangeRate;
            const discountedValue = originalValue * CONFIG.discount;
            return `${originalValue.toFixed(2)} (${discountedValue.toFixed(2)}) CNY`;
        }
        return value.toFixed(2);
    }

    // 转换页面中的货币值
    function convertPageCurrencyValues() {
        if (isProcessing) return;
        isProcessing = true;

        if (observer) observer.disconnect();
        const priceElements = findPriceElements();
        priceElements.forEach(processPriceElement);

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

        isProcessing = false;
    }

    // 查找所有价格元素(仅目标product-price容器)
    function findPriceElements() {
        const elements = [];
        const seenElements = new Set();
        // 仅选择目标span容器:.product-price(排除外层p标签)
        const selectors = ['.product-price:not(.currency-processed)'];

        selectors.forEach(selector => {
            document.querySelectorAll(selector).forEach(element => {
                if (ContainsSpecialValue(element) && !seenElements.has(element) && !processedElements.has(element)) {
                    const hasProcessedChild = element.querySelector('.currency-processed');
                    if (!hasProcessedChild) {
                        elements.push(element);
                        seenElements.add(element);
                    }
                }
            });
        });

        return elements;
    }

    // 处理价格元素
    function processPriceElement(element) {
        if (element.classList.contains('currency-processed') || processedElements.has(element)) {
            return;
        }

        const [PriceValue, PriceType] = ExtractSpecialValue(element);
        if (PriceValue !== null && PriceType !== null) {
            const formattedCNY = FormatCurrency(PriceValue, PriceType);
            const existingConversion = findExistingConversion(element);

            if (existingConversion) {
                existingConversion.textContent = `≈${formattedCNY}`;
                existingConversion.title = `${formatNumber(PriceValue)} ${PriceType} ≈ ${formattedCNY}`;
            } else {
                const conversionElement = document.createElement('span');
                conversionElement.className = 'currency-conversion';
                conversionElement.textContent = `≈${formattedCNY}`;
                conversionElement.title = `${formatNumber(PriceValue)} ${PriceType} ≈ ${formattedCNY}`;

                // 插入到.product-price_wrap后面(在目标容器内部)
                const priceWrap = element.querySelector('.product-price_wrap');
                if (priceWrap) {
                    priceWrap.parentNode.insertBefore(conversionElement, priceWrap.nextSibling);
                } else {
                    element.appendChild(conversionElement);
                }
            }

            element.classList.add('currency-processed');
            processedElements.add(element);
            // 标记子元素防止重复处理
            element.querySelectorAll('*').forEach(child => {
                child.classList.add('currency-processed');
                processedElements.add(child);
            });
        }
    }

    // 查找已存在的转换元素(在目标容器内部)
    function findExistingConversion(element) {
        return element.querySelector('.currency-conversion');
    }

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

    // 更新已转换的值
    function updateConvertedValues() {
        document.querySelectorAll('.currency-conversion').forEach(element => {
            const priceElement = element.closest('.product-price');
            if (priceElement) {
                const [PriceValue, PriceType] = ExtractSpecialValue(priceElement);
                if (PriceValue !== null && PriceType !== null) {
                    const formattedCNY = FormatCurrency(PriceValue, PriceType);
                    element.textContent = `≈${formattedCNY}`;
                    element.title = `${formatNumber(PriceValue)} ${PriceType} ≈ ${formattedCNY}`;
                }
            }
        });
    }

    // 监听DOM变化
    function observeDOMChanges() {
        observer = new MutationObserver(mutations => {
            let shouldConvert = false;
            mutations.forEach(mutation => {
                if (mutation.type === 'childList') {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            const priceSelectors = ['.product-price:not(.currency-processed)'];
                            const priceElements = node.querySelectorAll
                                ? node.querySelectorAll(priceSelectors.join(', '))
                                : [];

                            if (priceElements.length > 0 || (node.matches && node.matches(priceSelectors.join(', ')))) {
                                shouldConvert = true;
                            }
                        }
                    });
                }
            });

            if (shouldConvert && !isProcessing) {
                clearTimeout(window.currencyConversionTimeout);
                window.currencyConversionTimeout = setTimeout(convertPageCurrencyValues, 500);
            }
        });

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

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