您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
The essential toolkit for xbdeals.net. Converts all prices (lists, history, charts) to USD
// ==UserScript== // @name XBDeals Price Toolkit // @namespace https://github.com/sinazadeh/userscripts // @version 1.0.2 // @description The essential toolkit for xbdeals.net. Converts all prices (lists, history, charts) to USD // @author TheSina // @match *://xbdeals.net/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @connect cdn.jsdelivr.net // @license MIT // ==/UserScript== /* jshint esversion: 11 */ (function () { 'use strict'; // --- Constants and Global State --- const API_URL = 'https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/usd.json'; const hiddenStores = ['ae']; const currencyMap = { ae: 'aed', ar: 'ars', at: 'eur', au: 'aud', be: 'eur', br: 'brl', ca: 'cad', ch: 'chf', cl: 'clp', co: 'cop', cz: 'czk', de: 'eur', dk: 'dkk', es: 'eur', fi: 'eur', fr: 'eur', gb: 'gbp', gr: 'eur', hk: 'hkd', hu: 'huf', ie: 'eur', il: 'ils', in: 'inr', it: 'eur', jp: 'jpy', kr: 'krw', mx: 'mxn', nl: 'eur', no: 'nok', nz: 'nzd', pl: 'pln', pt: 'eur', sa: 'sar', se: 'sek', sg: 'sgd', sk: 'eur', tr: 'try', tw: 'twd', us: 'usd', za: 'zar', }; let ratesCache = null; // --- Utility Functions --- GM_addStyle(` .usd-price-appendix { display: block; font-size: 0.8em; font-weight: normal; color: #888; margin-top: 2px; } `); /** * Parses a price string into a float. Handles "FREE". */ function parseAmount(text) { const cleanText = text.trim().toUpperCase(); if (cleanText === 'FREE') return 0; let cleaned = text.replace(/[^\d.,]/g, ''); if (cleaned.includes(',') && cleaned.includes('.')) { const lastComma = cleaned.lastIndexOf(','); const lastDot = cleaned.lastIndexOf('.'); if (lastDot > lastComma) cleaned = cleaned.replace(/,/g, ''); else cleaned = cleaned.replace(/\./g, '').replace(',', '.'); } else if (cleaned.includes(',')) { const parts = cleaned.split(','); if (parts.length === 2 && parts[1].length <= 2) cleaned = cleaned.replace(',', '.'); else cleaned = cleaned.replace(/,/g, ''); } return parseFloat(cleaned); } async function fetchRates() { if (ratesCache) return ratesCache; return new Promise(resolve => { GM_xmlhttpRequest({ method: 'GET', url: API_URL, onload: res => { try { ratesCache = JSON.parse(res.responseText).usd; resolve(ratesCache); } catch (e) { console.error('Error parsing currency data:', e); resolve(null); } }, onerror: err => { console.error('Error fetching currency data:', err); resolve(null); }, }); }); } // --- Page-Specific Conversion Functions --- async function convertAndSort(container) { // Mark as processed immediately to prevent multiple runs on the same mutation event container.dataset.usdConverted = 'true'; console.log('Running conversion for price comparison list...'); const rates = await fetchRates(); if (!rates) { delete container.dataset.usdConverted; // Allow a retry if API fails return; } container .closest('.compare-prices-container') ?.style.setProperty('height', 'auto', 'important'); container.style.display = 'grid'; container.style.gridTemplateColumns = 'repeat(3, 1fr)'; container.style.gap = '10px'; const entries = Array.from(container.querySelectorAll('a')) .map(link => { const priceEl = link.querySelector('.compare-prices-price'); if (!priceEl) return null; const regionCode = link .getAttribute('href') ?.match(/^\/([a-z]{2})-store/i)?.[1] .toLowerCase(); if (!regionCode || hiddenStores.includes(regionCode)) return null; const currencyCode = currencyMap[regionCode]; const rate = rates[currencyCode]; if (!currencyCode || !rate) return null; const rawText = priceEl.textContent .replace(/\(≈.*?\)/g, '') .trim(); const localAmount = parseAmount(rawText); if (isNaN(localAmount)) return null; const usd = localAmount / rate; priceEl.textContent = `${rawText} ($${usd.toFixed(2)} USD)`; return {link, usdValue: usd}; }) .filter(Boolean); if (entries.length > 0) { entries.sort((a, b) => a.usdValue - b.usdValue); container.innerHTML = ''; entries.forEach(entry => container.appendChild(entry.link)); } } async function convertPriceHistory(container) { container.dataset.usdConverted = 'true'; console.log('Running conversion for price history...'); const rates = await fetchRates(); if (!rates) { delete container.dataset.usdConverted; // Allow retry return; } const breadcrumbLink = document.querySelector( '.breadcrumb a[itemprop="item"]', ); const regionCode = breadcrumbLink ?.getAttribute('href') ?.match(/^\/([a-z]{2})-store/i)?.[1] .toLowerCase(); if (!regionCode) return; const currencyCode = currencyMap[regionCode]; const rate = rates[currencyCode]; if (!currencyCode || !rate) return; container.querySelectorAll('.game-stats-col-number-big').forEach(el => { if (el.parentNode.querySelector('.usd-price-appendix')) return; // Already done const localAmount = parseAmount(el.textContent); if (!isNaN(localAmount)) { const usd = localAmount / rate; const usdText = document.createElement('small'); usdText.className = 'usd-price-appendix'; usdText.textContent = `($${usd.toFixed(2)} USD)`; el.parentNode.appendChild(usdText); } }); } // --- Main Execution Logic --- function runConversions() { // PATIENCE: Check for a container that is NOT processed AND has content. const listContainer = document.querySelector( '#compare-prices:not([data-usd-converted])', ); if ( listContainer && listContainer.querySelector('a .compare-prices-price') ) { convertAndSort(listContainer); } // PATIENCE: Check for a container that is NOT processed AND has content. const historyContainer = document.querySelector( '.game-stats-price-history:not([data-usd-converted])', ); if ( historyContainer && historyContainer.querySelector('.game-stats-col-number-big') ) { convertPriceHistory(historyContainer); } } // PERSISTENCE: This observer keeps watching the page for new content. const observer = new MutationObserver(runConversions); observer.observe(document.body, { childList: true, subtree: true, }); })();