POE2 Trade 中文简繁转换器

自动转换简繁中文(页面转简体,输入转繁体)- stomtian

目前為 2024-12-31 提交的版本,檢視 最新版本

// ==UserScript==
// @name         POE2 Trade 中文简繁转换器
// @namespace    http://tampermonkey.net/
// @version      1.1.0
// @description  自动转换简繁中文(页面转简体,输入转繁体)- stomtian
// @author       stomtian
// @match        https://www.pathofexile.com/trade*
// @match        https://pathofexile.com/trade*
// @grant        GM_getValue
// @grant        GM_setValue
// @license      MIT
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/full.min.js
// @run-at       document-idle
// @noframes     true
// ==/UserScript==

(function() {
    'use strict';

    const STATE = {
        pageSimplified: GM_getValue('pageSimplified', true),
        inputTraditional: GM_getValue('inputTraditional', true),
        originalTexts: new WeakMap()
    };

    const CUSTOM_DICT = [
        ['回覆', '回復'],
        ['恢覆', '恢復'],
    ];

    const CONFIG = {
        maxAttempts: 50,
        checkInterval: 100,
        inputSelector: 'input[type="text"], textarea',
        textSelector: '.search-bar, .search-advanced-pane, .results-container, .resultset',
        excludeSelector: 'script, style, input, textarea, select, .converter-controls'
    };

    function waitForElement(selector) {
        return new Promise(resolve => {
            if (document.querySelector(selector)) {
                resolve();
                return;
            }

            const observer = new MutationObserver(() => {
                try {
                    if (document.querySelector(selector)) {
                        observer.disconnect();
                        resolve();
                    }
                } catch (error) {}
            });

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

    function waitForOpenCC() {
        return new Promise((resolve, reject) => {
            if (typeof window.OpenCC !== 'undefined') {
                resolve(window.OpenCC);
                return;
            }

            let attempts = 0;
            const checkInterval = setInterval(() => {
                if (typeof window.OpenCC !== 'undefined') {
                    clearInterval(checkInterval);
                    resolve(window.OpenCC);
                    return;
                }

                if (++attempts >= CONFIG.maxAttempts) {
                    clearInterval(checkInterval);
                    reject(new Error('OpenCC 加载超时'));
                }
            }, CONFIG.checkInterval);
        });
    }

    function createConverters(OpenCC) {
        const toTraditional = OpenCC.ConverterFactory(
            OpenCC.Locale.from.cn,
            OpenCC.Locale.to.tw.concat([CUSTOM_DICT])
        );

        const toSimplified = OpenCC.ConverterFactory(
            OpenCC.Locale.from.tw,
            OpenCC.Locale.to.cn
        );

        return { toTraditional, toSimplified };
    }

    function createInputHandler(converter) {
        return function handleInput(e) {
            if (!STATE.inputTraditional) return;
            if (!e?.target?.value) return;

            const cursorPosition = e.target.selectionStart;
            const text = e.target.value;

            requestAnimationFrame(() => {
                try {
                    const convertedText = converter.toTraditional(text);
                    if (text === convertedText) return;

                    e.target.value = convertedText;

                    if (typeof cursorPosition === 'number') {
                        e.target.setSelectionRange(cursorPosition, cursorPosition);
                    }

                    e.target.dispatchEvent(new Event('input', {
                        bubbles: true,
                        cancelable: true
                    }));
                } catch (error) {}
            });
        };
    }

    function convertPageText(converter, forceRestore = false) {
        if (!STATE.pageSimplified && !forceRestore) return;

        try {
            const elements = document.querySelectorAll(CONFIG.textSelector);
            if (!elements.length) return;

            elements.forEach(root => {
                try {
                    const walker = document.createTreeWalker(
                        root,
                        NodeFilter.SHOW_TEXT,
                        {
                            acceptNode: function(node) {
                                try {
                                    if (!node.textContent.trim()) return NodeFilter.FILTER_REJECT;

                                    const parent = node.parentNode;
                                    if (!parent) return NodeFilter.FILTER_REJECT;

                                    if (parent.closest?.(CONFIG.excludeSelector)) {
                                        return NodeFilter.FILTER_REJECT;
                                    }

                                    return NodeFilter.FILTER_ACCEPT;
                                } catch (error) {
                                    return NodeFilter.FILTER_REJECT;
                                }
                            }
                        }
                    );

                    let node;
                    while (node = walker.nextNode()) {
                        try {
                            const text = node.textContent.trim();
                            if (!text) continue;

                            if (!STATE.originalTexts.has(node)) {
                                STATE.originalTexts.set(node, text);
                            }

                            if (STATE.pageSimplified) {
                                const convertedText = converter.toSimplified(text);
                                if (text !== convertedText) {
                                    node.textContent = convertedText;
                                }
                            } else {
                                const originalText = STATE.originalTexts.get(node);
                                if (originalText && node.textContent !== originalText) {
                                    node.textContent = originalText;
                                }
                            }
                        } catch (error) {}
                    }
                } catch (error) {}
            });
        } catch (error) {}
    }

    function attachInputListener(handleInput) {
        try {
            const inputElements = document.querySelectorAll(CONFIG.inputSelector);

            inputElements.forEach(element => {
                try {
                    if (element?.dataset?.hasConverter) return;
                    element.addEventListener('input', handleInput);
                    element.dataset.hasConverter = 'true';
                } catch (error) {}
            });
        } catch (error) {}
    }

    function createObserver(handleInput, converter) {
        return new MutationObserver(mutations => {
            try {
                let needsTextConversion = false;

                for (const mutation of mutations) {
                    if (!mutation.addedNodes.length) continue;

                    try {
                        const hasNewInputs = Array.from(mutation.addedNodes).some(node => {
                            try {
                                return node.querySelectorAll?.(CONFIG.inputSelector)?.length > 0;
                            } catch (error) {
                                return false;
                            }
                        });

                        if (hasNewInputs) {
                            attachInputListener(handleInput);
                        }

                        needsTextConversion = true;
                    } catch (error) {}
                }

                if (needsTextConversion) {
                    setTimeout(() => convertPageText(converter), 100);
                }
            } catch (error) {}
        });
    }

    function createControls() {
        const exchangeTab = document.querySelector('.menu-exchange');
        if (!exchangeTab) return;

        const traditionalLi = document.createElement('li');
        traditionalLi.role = 'presentation';
        traditionalLi.className = 'menu-traditional';
        const traditionalLink = document.createElement('a');
        traditionalLink.href = '#';
        traditionalLink.innerHTML = `<span>${STATE.inputTraditional ? '取消输入繁体' : '开启输入繁体'}</span>`;
        traditionalLi.appendChild(traditionalLink);

        const simplifiedLi = document.createElement('li');
        simplifiedLi.role = 'presentation';
        simplifiedLi.className = 'menu-simplified';
        const simplifiedLink = document.createElement('a');
        simplifiedLink.href = '#';
        simplifiedLink.innerHTML = `<span>${STATE.pageSimplified ? '取消页面简体' : '开启页面简体'}</span>`;
        simplifiedLi.appendChild(simplifiedLink);

        simplifiedLink.addEventListener('click', function(e) {
            e.preventDefault();
            STATE.pageSimplified = !STATE.pageSimplified;
            GM_setValue('pageSimplified', STATE.pageSimplified);
            simplifiedLink.querySelector('span').textContent = 
                STATE.pageSimplified ? '取消页面简体' : '开启页面简体';
            convertPageText(window.converter, true);
        });

        traditionalLink.addEventListener('click', function(e) {
            e.preventDefault();
            STATE.inputTraditional = !STATE.inputTraditional;
            GM_setValue('inputTraditional', STATE.inputTraditional);
            traditionalLink.querySelector('span').textContent = 
                STATE.inputTraditional ? '取消输入繁体' : '开启输入繁体';
        });

        exchangeTab.parentNode.insertBefore(traditionalLi, exchangeTab.nextSibling);
        exchangeTab.parentNode.insertBefore(simplifiedLi, exchangeTab.nextSibling);
    }

    function watchSearchResults(converter) {
        let lastUrl = location.href;
        const urlObserver = setInterval(() => {
            if (location.href !== lastUrl) {
                lastUrl = location.href;
                STATE.originalTexts = new WeakMap();
                setTimeout(() => convertPageText(converter), 500);
            }
        }, 100);

        const resultObserver = new MutationObserver((mutations) => {
            let needsConversion = false;
            for (const mutation of mutations) {
                if (mutation.type === 'childList' || mutation.type === 'characterData') {
                    needsConversion = true;
                    break;
                }
            }
            if (needsConversion) {
                setTimeout(() => convertPageText(converter), 100);
            }
        });

        const resultsContainer = document.querySelector('.results-container');
        if (resultsContainer) {
            resultObserver.observe(resultsContainer, {
                childList: true,
                subtree: true,
                characterData: true
            });
        }
    }

    async function init() {
        try {
            await waitForElement('.search-bar');

            const OpenCC = await waitForOpenCC();
            const converter = createConverters(OpenCC);
            window.converter = converter;

            const handleInput = createInputHandler(converter);

            const observer = createObserver(handleInput, converter);
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });

            attachInputListener(handleInput);
            createControls();

            if (STATE.pageSimplified) {
                convertPageText(converter);
            }

            watchSearchResults(converter);

            setInterval(() => {
                if (STATE.pageSimplified) {
                    convertPageText(converter);
                }
            }, 1000);

        } catch (error) {}
    }

    setTimeout(init, 2000);
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址