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