// ==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/opencc-js@1.0.5/dist/umd/full.min.js // @run-at document-idle // @noframes true // @downloadURL none // ==/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 = `${STATE.inputTraditional ? '取消输入繁体' : '开启输入繁体'}`; traditionalLi.appendChild(traditionalLink); const simplifiedLi = document.createElement('li'); simplifiedLi.role = 'presentation'; simplifiedLi.className = 'menu-simplified'; const simplifiedLink = document.createElement('a'); simplifiedLink.href = '#'; simplifiedLink.innerHTML = `${STATE.pageSimplified ? '取消页面简体' : '开启页面简体'}`; 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); })();