// ==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 // @downloadURL none // ==/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(); } })();