MakerWorld Points to RMB Converter

Convert MakerWorld points to RMB display for both CN and international sites

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         MakerWorld Points to RMB Converter
// @name:zh-CN   MakerWorld积分转人民币转换器
// @namespace    http://tampermonkey.net/
// @version      0.9.1
// @description          Convert MakerWorld points to RMB display for both CN and international sites
// @description:zh-CN    支持国际站和中文站的MakerWorld积分自动转换人民币显示
// @author       AIScripter
// @match        https://makerworld.com.cn/*
// @match        https://makerworld.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const EXCHANGE_RATES = {
        'normal': 0.5795,    // Normal points exchange rate
        'exclusive': 0.47    // Exclusive points exchange rate
    };

    let isUpdating = false; // 防止重复执行的标志

    // Function to parse number text with internationalization support
    function parseNumber(text) {
        // Handle text with <em> tags for decimal parts
        if (text.includes('<em')) {
            const mainPart = text.split('<em')[0].trim();
            const decimalMatch = text.match(/\.<!-- -->(\d+)/);
            const decimal = decimalMatch ? decimalMatch[1] : '';
            return parseFloat(mainPart.replace(/,/g, '') + '.' + decimal);
        }
        // Handle regular numbers
        return parseFloat(text.replace(/,/g, ''));
    }

    // Function to convert points to RMB
    function convertPointsToRMB(points, type = 'normal') {
        const rate = EXCHANGE_RATES[type] || EXCHANGE_RATES.normal;
        return Math.round(points * rate);
    }

    // Function to create or update RMB display element
    function createOrUpdateRMBDisplay(container, value, className, styles = {}) {
        let rmbDisplay = container.querySelector(`.${className}`);
        if (!rmbDisplay) {
            rmbDisplay = document.createElement('span');
            rmbDisplay.className = className;
            rmbDisplay.setAttribute('data-rmb-converter', 'true'); // 标记为脚本创建的元素
            Object.assign(rmbDisplay.style, {
                color: '#666',
                marginLeft: '4px',
                display: 'inline-block',
                verticalAlign: 'baseline',
                ...styles
            });
            container.appendChild(rmbDisplay);
        }
        rmbDisplay.textContent = `≈ ${value} 元`;
        return rmbDisplay;
    }

    // Function to update points display
    function updatePointsDisplay() {
        if (isUpdating) return; // 如果正在更新,直接返回
        isUpdating = true;

        try {
            // Handle points type display area (both CN and international versions)
            const pointTypesContainer = document.querySelector('.mw-css-dx018d');
            if (pointTypesContainer) {
                const typeBlocks = pointTypesContainer.querySelectorAll('[class*="mw-css-"][class*="gc1l0t"]');
                typeBlocks.forEach(block => {
                    const pointsSpan = block.querySelector('[class*="mw-css-"][class*="yyek0l"]');
                    const typeLabel = block.querySelector('[class*="mw-css-"][class*="kx5qaq"]');
                    if (pointsSpan && typeLabel) {
                        const pointsText = pointsSpan.innerHTML;
                        const pointsValue = parseNumber(pointsText);
                        const isExclusive = typeLabel.textContent.includes('独家') ||
                                          typeLabel.textContent.toLowerCase().includes('exclusive');
                        const rmbValue = convertPointsToRMB(pointsValue, isExclusive ? 'exclusive' : 'normal');

                        createOrUpdateRMBDisplay(
                            block.querySelector('[class*="mw-css-"][class*="gr0cu"]'),
                            rmbValue,
                            'rmb-type-conversion',
                            { fontSize: '12px' }
                        );
                    }
                });
            }

            // Handle total points display area
            const totalPointsContainers = document.querySelectorAll('[class*="mw-css-"][class*="gjjkf7"]');
            totalPointsContainers.forEach(container => {
                const pointsSpan = container.querySelector('[class*="mw-css-"][class*="yyek0l"]');
                if (pointsSpan) {
                    const pointsText = pointsSpan.innerHTML;
                    const pointsValue = parseNumber(pointsText);
                    const rmbValue = convertPointsToRMB(pointsValue);

                    createOrUpdateRMBDisplay(
                        container,
                        rmbValue,
                        'rmb-total-conversion',
                        { fontSize: '14px', textAlign: 'center' }
                    );
                }
            });

            // Handle small version points display (including nested formats)
            const smallPointsContainers = document.querySelectorAll('[class*="mw-css-"][class*="foh4ep"]');
            smallPointsContainers.forEach(container => {
                // Try both direct and nested yyek0l span structures
                const pointsSpan = container.querySelector('[class*="mw-css-"][class*="yyek0l"]');
                const nestedPointsSpan = container.querySelector('[class*="mw-css-"][class*="1541sxf"] [class*="mw-css-"][class*="yyek0l"]');

                const targetSpan = pointsSpan || nestedPointsSpan;
                if (targetSpan) {
                    const pointsText = targetSpan.innerHTML;
                    const pointsValue = parseNumber(pointsText);
                    const rmbValue = convertPointsToRMB(pointsValue);

                    createOrUpdateRMBDisplay(
                        container,
                        rmbValue,
                        'rmb-individual-conversion',
                        { fontSize: '0.75em', textAlign: 'center' }
                    );
                }
            });

            // Handle income/expenditure details
            const incomeLabels = document.querySelectorAll('[class*="mw-css-"][class*="jrs7sa"]');
            incomeLabels.forEach(label => {
                const incomeSpans = label.querySelectorAll('[class*="mw-css-"][class*="jgoohm"] [class*="mw-css-"][class*="yyek0l"]');
                incomeSpans.forEach((span, index) => {
                    const pointsText = span.innerHTML;
                    const pointsValue = parseNumber(pointsText);
                    const rmbValue = convertPointsToRMB(pointsValue);

                    const parentContainer = span.closest('[class*="mw-css-"][class*="jgoohm"]');
                    createOrUpdateRMBDisplay(
                        parentContainer,
                        rmbValue,
                        `rmb-${index === 0 ? 'income' : 'expenditure'}-conversion`,
                        { fontSize: '0.75em' }
                    );
                });
            });
        } finally {
            isUpdating = false; // 确保标志被重置
        }
    }

    // 延迟执行函数,防止频繁调用
    function debounce(func, delay) {
        let timeoutId;
        return function(...args) {
            clearTimeout(timeoutId);
            timeoutId = setTimeout(() => func.apply(this, args), delay);
        };
    }

    // 创建防抖版本的更新函数
    const debouncedUpdate = debounce(updatePointsDisplay, 300);

    // Add page load event and mutation observer
    window.addEventListener('load', function() {
        updatePointsDisplay();

        const observer = new MutationObserver((mutations) => {
            let shouldUpdate = false;
            
            // 检查变化是否与我们的脚本无关
            for (let mutation of mutations) {
                for (let node of mutation.addedNodes) {
                    if (node.nodeType === Node.ELEMENT_NODE && 
                        !node.hasAttribute('data-rmb-converter') && 
                        !node.querySelector('[data-rmb-converter]')) {
                        shouldUpdate = true;
                        break;
                    }
                }
                if (shouldUpdate) break;
            }
            
            if (shouldUpdate) {
                debouncedUpdate();
            }
        });

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

    // 添加页面可见性变化时的更新
    document.addEventListener('visibilitychange', function() {
        if (!document.hidden) {
            setTimeout(updatePointsDisplay, 100);
        }
    });
})();