// ==UserScript==
// @name POE2 Trade ST工具箱
// @namespace http://tampermonkey.net/
// @version 2.2.0
// @description 自动转换简繁中文(页面转简体,输入转繁体)- stomtian
// @author stomtian
// @match https://www.pathofexile.com/trade*
// @match https://pathofexile.com/trade*
// @grant GM_getValue
// @grant GM_setValue
// @grant unsafeWindow
// @license MIT
// @require https://cdn.jsdelivr.net/npm/opencc-js@1.0.5/dist/umd/full.min.js
// @run-at document-end
// @noframes true
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
console.log('POE2 Trade ST工具箱已加载');
const STATE = {
pageSimplified: GM_getValue('pageSimplified', true),
inputTraditional: GM_getValue('inputTraditional', true),
originalTexts: new WeakMap(),
configs: GM_getValue('savedConfigs', {}), // 保存的配置
autoLoadEnabled: GM_getValue('autoLoadEnabled', false), // 自动加载开关
matchedCards: [], // 添加匹配的卡片列表
currentMatchIndex: -1, // 添加当前匹配索引
showOnlyMatched: GM_getValue('showOnlyMatched', false) // 添加新的状态
};
const CUSTOM_DICT = [
['回覆', '回復'],
['恢覆', '恢復'],
];
const CONFIG = {
maxAttempts: 50,
checkInterval: 100,
inputSelector: 'input[type="text"]:not(#config-name):not(#config-category), 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 || element?.dataset?.isSearchInput) 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 createConfigModal() {
const modalHtml = `
ST工具箱
功能开关
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
// 添加遮罩
const overlay = document.createElement('div');
overlay.id = 'config-modal-overlay';
overlay.style.cssText = `
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 9999;
`;
document.body.appendChild(overlay);
// 添加样式
const style = document.createElement('style');
style.textContent = `
.modal-tab.active {
background: #4a90e2 !important;
}
.modal-tab:hover {
background: #357abd !important;
}
.panel {
transition: opacity 0.3s ease;
}
#config-list::-webkit-scrollbar {
width: 8px;
}
#config-list::-webkit-scrollbar-track {
background: #1a1a1a;
}
#config-list::-webkit-scrollbar-thumb {
background: #444;
border-radius: 4px;
}
#config-list::-webkit-scrollbar-thumb:hover {
background: #555;
}
`;
document.head.appendChild(style);
setupConfigModalEvents();
updateConfigList();
setupCategoryDropdown();
}
function setupCategoryDropdown() {
const categoryInput = document.getElementById('config-category');
const dropdown = document.getElementById('category-dropdown');
let isDropdownVisible = false;
function updateDropdown() {
const categories = Object.keys(STATE.configs);
const inputValue = categoryInput.value.toLowerCase();
dropdown.innerHTML = '';
categories
.filter(category => category.toLowerCase().includes(inputValue))
.forEach(category => {
const item = document.createElement('div');
item.className = 'dropdown-item';
item.textContent = category;
item.onclick = () => {
categoryInput.value = category;
hideDropdown();
};
dropdown.appendChild(item);
});
if (categories.length === 0) {
const item = document.createElement('div');
item.className = 'dropdown-item';
item.textContent = '无已有分类';
item.style.color = '#666';
dropdown.appendChild(item);
}
}
function showDropdown() {
updateDropdown();
dropdown.style.display = 'block';
isDropdownVisible = true;
}
function hideDropdown() {
dropdown.style.display = 'none';
isDropdownVisible = false;
}
categoryInput.addEventListener('focus', showDropdown);
categoryInput.addEventListener('input', updateDropdown);
// 点击外部区域时隐藏下拉列表
document.addEventListener('click', (e) => {
const isClickInside = categoryInput.contains(e.target) || dropdown.contains(e.target);
if (!isClickInside && isDropdownVisible) {
hideDropdown();
}
});
// 阻止事件冒泡,避免点击下拉列表时触发外部点击事件
dropdown.addEventListener('click', (e) => {
e.stopPropagation();
});
}
function setupConfigModalEvents() {
const modal = document.getElementById('config-modal');
const overlay = document.getElementById('config-modal-overlay');
const closeBtn = document.getElementById('close-config-modal');
const saveBtn = document.getElementById('save-config');
const togglePageBtn = document.getElementById('toggle-page-simplified');
const toggleInputBtn = document.getElementById('toggle-input-traditional');
const toggleAutoLoadBtn = document.getElementById('toggle-auto-load');
const exportBtn = document.getElementById('export-configs');
const importBtn = document.getElementById('import-configs');
const importFile = document.getElementById('import-file');
const tabConfigs = document.getElementById('tab-configs');
const tabSettings = document.getElementById('tab-settings');
const panelConfigs = document.getElementById('panel-configs');
const panelSettings = document.getElementById('panel-settings');
// 标签切换
tabConfigs.addEventListener('click', () => {
tabConfigs.classList.add('active');
tabSettings.classList.remove('active');
tabSettings.style.background = '#3d3d3d';
panelConfigs.style.display = 'block';
panelSettings.style.display = 'none';
});
tabSettings.addEventListener('click', () => {
tabSettings.classList.add('active');
tabConfigs.classList.remove('active');
tabConfigs.style.background = '#3d3d3d';
panelSettings.style.display = 'block';
panelConfigs.style.display = 'none';
});
closeBtn.addEventListener('click', () => {
modal.style.display = 'none';
overlay.style.display = 'none';
});
overlay.addEventListener('click', () => {
modal.style.display = 'none';
overlay.style.display = 'none';
});
togglePageBtn.addEventListener('click', () => {
STATE.pageSimplified = !STATE.pageSimplified;
GM_setValue('pageSimplified', STATE.pageSimplified);
togglePageBtn.textContent = STATE.pageSimplified ? '✓ 页面简体' : '✗ 页面简体';
togglePageBtn.style.backgroundColor = STATE.pageSimplified ? '#4a90e2' : '#3d3d3d';
convertPageText(window.converter, true);
});
toggleInputBtn.addEventListener('click', () => {
STATE.inputTraditional = !STATE.inputTraditional;
GM_setValue('inputTraditional', STATE.inputTraditional);
toggleInputBtn.textContent = STATE.inputTraditional ? '✓ 输入繁体' : '✗ 输入繁体';
toggleInputBtn.style.backgroundColor = STATE.inputTraditional ? '#4a90e2' : '#3d3d3d';
});
toggleAutoLoadBtn.addEventListener('click', () => {
STATE.autoLoadEnabled = !STATE.autoLoadEnabled;
GM_setValue('autoLoadEnabled', STATE.autoLoadEnabled);
toggleAutoLoadBtn.textContent = STATE.autoLoadEnabled ? '✓ 自动加载' : '✗ 自动加载';
toggleAutoLoadBtn.style.backgroundColor = STATE.autoLoadEnabled ? '#4a90e2' : '#3d3d3d';
});
saveBtn.addEventListener('click', saveCurrentConfig);
// 导出配置
exportBtn.addEventListener('click', () => {
const configData = {
version: '2.0.0',
timestamp: new Date().toISOString(),
configs: STATE.configs
};
const blob = new Blob([JSON.stringify(configData, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `poe2_trade_configs_${new Date().toISOString().slice(0,10)}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
// 导入配置按钮点击
importBtn.addEventListener('click', () => {
importFile.click();
});
// 处理文件导入
importFile.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
try {
const importedData = JSON.parse(event.target.result);
// 验证导入的数据
if (!importedData.version || !importedData.configs) {
throw new Error('无效的配置文件格式');
}
// 确认导入
if (confirm(`确定要导入这些配置吗?\n这将会覆盖同名的现有配置。`)) {
// 合并配置
Object.keys(importedData.configs).forEach(category => {
if (!STATE.configs[category]) {
STATE.configs[category] = {};
}
Object.assign(STATE.configs[category], importedData.configs[category]);
});
GM_setValue('savedConfigs', STATE.configs);
updateConfigList();
alert('配置导入成功!');
}
} catch (error) {
alert('导入失败:' + error.message);
}
// 清除文件选择,允许重复导入同一个文件
importFile.value = '';
};
reader.readAsText(file);
});
}
function saveCurrentConfig() {
const name = document.getElementById('config-name').value.trim();
const category = document.getElementById('config-category').value.trim();
if (!name) {
alert('请输入配置名称');
return;
}
if (!category) {
alert('请输入分类名称');
return;
}
if (!STATE.configs[category]) {
STATE.configs[category] = {};
}
STATE.configs[category][name] = {
url: window.location.href,
timestamp: new Date().toISOString()
};
GM_setValue('savedConfigs', STATE.configs);
updateConfigList();
updateCategoryDatalist();
document.getElementById('config-name').value = '';
document.getElementById('config-category').value = '';
}
function updateConfigList() {
const configList = document.getElementById('config-list');
const categoryTabs = document.getElementById('category-tabs');
configList.innerHTML = '';
categoryTabs.innerHTML = '';
// 获取所有分类
const categories = Object.keys(STATE.configs);
// 如果没有配置,显示提示信息
if (categories.length === 0) {
configList.innerHTML = '暂无保存的配置
';
return;
}
// 创建标签
categories.forEach((category, index) => {
const tabButton = document.createElement('button');
tabButton.textContent = category;
tabButton.style.cssText = `
background: ${index === 0 ? '#4a90e2' : '#3d3d3d'};
border: none;
color: #fff;
padding: 5px 15px;
cursor: pointer;
border-radius: 3px;
transition: background-color 0.2s;
margin-right: 10px;
`;
tabButton.dataset.category = category;
tabButton.title = '双击删除分类';
tabButton.addEventListener('click', (e) => {
document.querySelectorAll('#category-tabs button[data-category]').forEach(btn => {
btn.style.backgroundColor = '#3d3d3d';
});
tabButton.style.backgroundColor = '#4a90e2';
showCategoryConfigs(category);
});
tabButton.addEventListener('dblclick', (e) => {
e.stopPropagation();
deleteCategory(category);
});
categoryTabs.appendChild(tabButton);
});
// 默认显示第一个分类的配置
showCategoryConfigs(categories[0]);
}
function deleteCategory(category) {
const configCount = Object.keys(STATE.configs[category]).length;
if (confirm(`确定要删除分类 "${category}" 及其包含的 ${configCount} 个配置吗?`)) {
delete STATE.configs[category];
GM_setValue('savedConfigs', STATE.configs);
updateConfigList();
}
}
function showCategoryConfigs(category) {
const configList = document.getElementById('config-list');
configList.innerHTML = '';
const configs = STATE.configs[category];
Object.entries(configs).forEach(([name, data]) => {
const configItem = document.createElement('div');
configItem.style.cssText = `
display: grid;
grid-template-columns: 1fr auto auto auto;
align-items: center;
padding: 8px;
margin: 5px 0;
background: #3d3d3d;
border-radius: 4px;
gap: 10px;
`;
const nameSpan = document.createElement('span');
nameSpan.textContent = name;
nameSpan.style.cssText = `
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
const loadBtn = document.createElement('button');
loadBtn.textContent = '读取';
loadBtn.style.cssText = `
background: #4a90e2;
border: none;
color: #fff;
padding: 3px 12px;
cursor: pointer;
border-radius: 3px;
transition: background-color 0.2s;
`;
loadBtn.onclick = () => loadConfig(data.url);
const updateBtn = document.createElement('button');
updateBtn.textContent = '更新';
updateBtn.style.cssText = `
background: #27ae60;
border: none;
color: #fff;
padding: 3px 12px;
cursor: pointer;
border-radius: 3px;
transition: background-color 0.2s;
`;
updateBtn.onclick = (e) => {
e.stopPropagation();
updateConfig(category, name);
};
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '删除';
deleteBtn.style.cssText = `
background: #e74c3c;
border: none;
color: #fff;
padding: 3px 12px;
cursor: pointer;
border-radius: 3px;
transition: background-color 0.2s;
`;
deleteBtn.onclick = (e) => {
e.stopPropagation();
deleteConfig(category, name);
};
configItem.appendChild(nameSpan);
configItem.appendChild(loadBtn);
configItem.appendChild(updateBtn);
configItem.appendChild(deleteBtn);
configList.appendChild(configItem);
});
}
function loadConfig(url) {
window.location.href = url;
}
function deleteConfig(category, name) {
if (confirm(`确定要删除配置 "${name}" 吗?`)) {
delete STATE.configs[category][name];
if (Object.keys(STATE.configs[category]).length === 0) {
delete STATE.configs[category];
}
GM_setValue('savedConfigs', STATE.configs);
updateConfigList();
}
}
function createConfigButton() {
const floatingButton = document.createElement('div');
floatingButton.style.cssText = `
position: fixed;
right: 20px;
top: 50%;
transform: translateY(-50%);
width: 50px;
height: 50px;
background: linear-gradient(135deg, #3c3c28 0%, #2a2a1c 100%);
border-radius: 25px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: #ffd700;
font-weight: bold;
font-family: 'Fontin SmallCaps', Arial, sans-serif;
font-size: 18px;
box-shadow: 0 0 20px rgba(0,0,0,0.5),
inset 0 0 8px rgba(255, 215, 0, 0.3),
0 0 30px rgba(255, 215, 0, 0.2);
border: 1px solid rgba(255, 215, 0, 0.4);
z-index: 9998;
transition: all 0.3s ease;
user-select: none;
touch-action: none;
text-shadow: 0 0 10px rgba(255, 215, 0, 0.6);
animation: normalGlowing 2s ease-in-out infinite;
`;
floatingButton.textContent = 'ST';
floatingButton.title = 'ST工具箱 (按住可拖动)';
// 添加悬停效果
floatingButton.addEventListener('mouseenter', () => {
if (!isDragging) {
floatingButton.style.transform = 'scale(1.1)';
floatingButton.style.boxShadow = `
0 0 25px rgba(0,0,0,0.5),
inset 0 0 12px rgba(255, 215, 0, 0.5),
0 0 40px rgba(255, 215, 0, 0.3)
`;
floatingButton.style.color = '#ffe44d';
floatingButton.style.textShadow = '0 0 15px rgba(255, 215, 0, 0.8)';
floatingButton.style.border = '1px solid rgba(255, 215, 0, 0.6)';
floatingButton.style.animation = 'none';
if (isHidden) {
showButton();
}
}
});
floatingButton.addEventListener('mouseleave', () => {
if (!isDragging) {
floatingButton.style.transform = 'scale(1)';
floatingButton.style.boxShadow = `
0 0 20px rgba(0,0,0,0.5),
inset 0 0 8px rgba(255, 215, 0, 0.3),
0 0 30px rgba(255, 215, 0, 0.2)
`;
floatingButton.style.color = '#ffd700';
floatingButton.style.textShadow = '0 0 10px rgba(255, 215, 0, 0.6)';
floatingButton.style.border = '1px solid rgba(255, 215, 0, 0.4)';
floatingButton.style.animation = 'normalGlowing 2s ease-in-out infinite';
checkAndHideButton();
}
});
// 添加拖拽功能
let isDragging = false;
let startX, startY;
let lastX = GM_getValue('floatingButtonX', window.innerWidth - 70);
let lastY = GM_getValue('floatingButtonY', window.innerHeight / 2);
let dragDistance = 0;
let mouseDownTime = 0;
let isHidden = false;
function dragStart(e) {
isDragging = true;
dragDistance = 0;
mouseDownTime = Date.now();
const rect = floatingButton.getBoundingClientRect();
startX = e.clientX - rect.left;
startY = e.clientY - rect.top;
floatingButton.style.transition = 'none';
floatingButton.style.transform = 'scale(1)';
}
function drag(e) {
if (!isDragging) return;
e.preventDefault();
const x = e.clientX - startX;
const y = e.clientY - startY;
// 计算拖动距离
const dx = x - lastX;
const dy = y - lastY;
dragDistance += Math.sqrt(dx * dx + dy * dy);
// 限制拖动范围
const maxX = window.innerWidth - floatingButton.offsetWidth;
const maxY = window.innerHeight - floatingButton.offsetHeight;
lastX = Math.max(0, Math.min(x, maxX));
lastY = Math.max(0, Math.min(y, maxY));
floatingButton.style.left = lastX + 'px';
floatingButton.style.top = lastY + 'px';
floatingButton.style.right = 'auto';
}
function dragEnd(e) {
if (!isDragging) return;
const dragDuration = Date.now() - mouseDownTime;
isDragging = false;
floatingButton.style.transition = 'all 0.3s ease';
// 调整位置,使按钮居中对齐边缘
const buttonWidth = floatingButton.offsetWidth;
const buttonHeight = floatingButton.offsetHeight;
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
const threshold = 20;
if (lastX < threshold) {
lastX = 0;
} else if (lastX + buttonWidth > windowWidth - threshold) {
lastX = windowWidth - buttonWidth;
}
if (lastY < threshold) {
lastY = 0;
} else if (lastY + buttonHeight > windowHeight - threshold) {
lastY = windowHeight - buttonHeight;
}
floatingButton.style.left = lastX + 'px';
floatingButton.style.top = lastY + 'px';
// 保存位置
GM_setValue('floatingButtonX', lastX);
GM_setValue('floatingButtonY', lastY);
// 检查是否需要隐藏按钮
checkAndHideButton();
// 如果拖动距离小于5像素且时间小于200ms,则认为是点击
if (dragDistance < 5 && dragDuration < 200) {
document.getElementById('config-modal').style.display = 'block';
document.getElementById('config-modal-overlay').style.display = 'block';
}
}
function checkAndHideButton() {
const threshold = 20; // 距离边缘多少像素时触发隐藏
const buttonWidth = floatingButton.offsetWidth;
const buttonHeight = floatingButton.offsetHeight;
const buttonRight = lastX + buttonWidth;
const buttonBottom = lastY + buttonHeight;
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
// 检查各个边缘
if (buttonRight > windowWidth - threshold) {
// 右边缘
hideButton('right');
} else if (lastX < threshold) {
// 左边缘
hideButton('left');
} else if (lastY < threshold) {
// 上边缘
hideButton('top');
} else if (buttonBottom > windowHeight - threshold) {
// 下边缘
hideButton('bottom');
}
}
function hideButton(direction) {
isHidden = true;
floatingButton.style.transition = 'all 0.3s ease';
// 添加金光动画
floatingButton.style.animation = 'none';
floatingButton.offsetHeight; // 触发重绘
floatingButton.style.animation = 'glowing 1.5s ease-in-out infinite';
floatingButton.style.background = 'linear-gradient(135deg, #5a5a42 0%, #3a3a2c 100%)';
switch (direction) {
case 'right':
floatingButton.style.transform = 'translateY(-50%) translateX(60%)';
floatingButton.style.borderRadius = '25px 0 0 25px';
break;
case 'left':
floatingButton.style.transform = 'translateY(-50%) translateX(-60%)';
floatingButton.style.borderRadius = '0 25px 25px 0';
break;
case 'top':
floatingButton.style.transform = 'translateX(-50%) translateY(-60%)';
floatingButton.style.borderRadius = '0 0 25px 25px';
break;
case 'bottom':
floatingButton.style.transform = 'translateX(-50%) translateY(60%)';
floatingButton.style.borderRadius = '25px 25px 0 0';
break;
}
}
function showButton() {
isHidden = false;
floatingButton.style.transition = 'all 0.3s ease';
floatingButton.style.animation = 'normalGlowing 2s ease-in-out infinite';
floatingButton.style.background = 'linear-gradient(135deg, #3c3c28 0%, #2a2a1c 100%)';
floatingButton.style.transform = 'scale(1)';
floatingButton.style.borderRadius = '25px';
}
// 添加金光动画样式
const glowingStyle = document.createElement('style');
glowingStyle.textContent = `
@keyframes normalGlowing {
0% {
box-shadow: 0 0 20px rgba(0,0,0,0.5),
inset 0 0 8px rgba(255, 215, 0, 0.3),
0 0 30px rgba(255, 215, 0, 0.2);
border-color: rgba(255, 215, 0, 0.4);
color: #ffd700;
text-shadow: 0 0 10px rgba(255, 215, 0, 0.6);
}
50% {
box-shadow: 0 0 25px rgba(0,0,0,0.5),
inset 0 0 12px rgba(255, 215, 0, 0.4),
0 0 40px rgba(255, 215, 0, 0.3),
0 0 60px rgba(255, 215, 0, 0.2);
border-color: rgba(255, 215, 0, 0.5);
color: #ffe44d;
text-shadow: 0 0 15px rgba(255, 215, 0, 0.7);
}
100% {
box-shadow: 0 0 20px rgba(0,0,0,0.5),
inset 0 0 8px rgba(255, 215, 0, 0.3),
0 0 30px rgba(255, 215, 0, 0.2);
border-color: rgba(255, 215, 0, 0.4);
color: #ffd700;
text-shadow: 0 0 10px rgba(255, 215, 0, 0.6);
}
}
@keyframes glowing {
0% {
box-shadow: 0 0 20px rgba(0,0,0,0.5),
inset 0 0 8px rgba(255, 215, 0, 0.5),
0 0 30px rgba(255, 215, 0, 0.4),
0 0 50px rgba(255, 215, 0, 0.2);
border-color: rgba(255, 215, 0, 0.6);
color: #ffd700;
text-shadow: 0 0 15px rgba(255, 215, 0, 0.8);
}
50% {
box-shadow: 0 0 30px rgba(0,0,0,0.6),
inset 0 0 20px rgba(255, 215, 0, 0.8),
0 0 60px rgba(255, 215, 0, 0.6),
0 0 100px rgba(255, 215, 0, 0.4),
0 0 150px rgba(255, 215, 0, 0.2);
border-color: rgba(255, 223, 0, 1);
color: #ffe44d;
text-shadow: 0 0 25px rgba(255, 215, 0, 1),
0 0 35px rgba(255, 215, 0, 0.7),
0 0 45px rgba(255, 215, 0, 0.4);
}
100% {
box-shadow: 0 0 20px rgba(0,0,0,0.5),
inset 0 0 8px rgba(255, 215, 0, 0.5),
0 0 30px rgba(255, 215, 0, 0.4),
0 0 50px rgba(255, 215, 0, 0.2);
border-color: rgba(255, 215, 0, 0.6);
color: #ffd700;
text-shadow: 0 0 15px rgba(255, 215, 0, 0.8);
}
}
`;
document.head.appendChild(glowingStyle);
// 监听窗口大小变化
window.addEventListener('resize', () => {
if (!isDragging) {
// 确保按钮不会超出窗口
const maxX = window.innerWidth - floatingButton.offsetWidth;
const maxY = window.innerHeight - floatingButton.offsetHeight;
lastX = Math.min(lastX, maxX);
lastY = Math.min(lastY, maxY);
floatingButton.style.left = lastX + 'px';
floatingButton.style.top = lastY + 'px';
// 检查是否需要隐藏按钮
checkAndHideButton();
}
});
floatingButton.addEventListener('mousedown', dragStart);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', dragEnd);
// 恢复上次保存的位置或使用默认位置
const savedX = GM_getValue('floatingButtonX', window.innerWidth - 70);
const savedY = GM_getValue('floatingButtonY', window.innerHeight / 2);
lastX = savedX;
lastY = savedY;
floatingButton.style.right = 'auto';
floatingButton.style.top = lastY + 'px';
floatingButton.style.left = lastX + 'px';
floatingButton.style.transform = 'scale(1)';
// 检查初始位置是否需要隐藏
setTimeout(checkAndHideButton, 100);
return floatingButton;
}
function createControls() {
const floatingButton = createConfigButton();
document.body.appendChild(floatingButton);
// 创建搜索框但不立即显示
createSearchBox();
// 添加快捷键监听
document.addEventListener('keydown', (e) => {
if (e.altKey && !e.ctrlKey && !e.shiftKey && e.key.toLowerCase() === 'f') {
e.preventDefault(); // 阻止默认行为
toggleSearchBox();
}
});
}
// 切换搜索框显示状态
function toggleSearchBox() {
const searchBox = document.querySelector('.search-box-container');
if (searchBox) {
searchBox.style.display = searchBox.style.display === 'none' ? 'flex' : 'none';
if (searchBox.style.display === 'flex') {
// 当显示搜索框时,自动聚焦到输入框
const searchInput = searchBox.querySelector('input');
if (searchInput) {
searchInput.focus();
}
}
}
}
// 创建搜索框
function createSearchBox() {
const searchBoxContainer = document.createElement('div');
searchBoxContainer.className = 'search-box-container';
searchBoxContainer.style.cssText = `
position: fixed;
top: 10px;
left: 20px;
z-index: 9999;
background: rgba(28, 28, 28, 0.95);
padding: 15px 10px 10px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
border: 1px solid #444;
display: none;
flex-direction: column;
gap: 8px;
`;
const searchRow = document.createElement('div');
searchRow.style.cssText = `
display: flex;
gap: 8px;
align-items: center;
`;
const navigationRow = document.createElement('div');
navigationRow.style.cssText = `
display: flex;
gap: 8px;
align-items: center;
`;
const searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.placeholder = '输入关键词(用;分隔)';
searchInput.dataset.isSearchInput = 'true';
searchInput.style.cssText = `
width: 250px;
padding: 5px 10px;
border: 1px solid #666;
border-radius: 4px;
background: #2d2d2d;
color: #fff;
`;
// 添加输入转换处理
searchInput.addEventListener('input', (e) => {
if (!e?.target?.value) return;
// 当页面为简体时,不转换输入
// 当页面为繁体时,转换输入为繁体
if (!STATE.pageSimplified) {
const cursorPosition = e.target.selectionStart;
const text = e.target.value;
requestAnimationFrame(() => {
try {
const convertedText = window.converter.toTraditional(text);
if (text === convertedText) return;
e.target.value = convertedText;
if (typeof cursorPosition === 'number') {
e.target.setSelectionRange(cursorPosition, cursorPosition);
}
} catch (error) {}
});
}
});
const searchButton = document.createElement('button');
searchButton.textContent = '搜索';
searchButton.style.cssText = `
padding: 5px 15px;
background: #4a90e2;
border: none;
border-radius: 4px;
color: #fff;
cursor: pointer;
transition: background 0.2s;
`;
const prevButton = document.createElement('button');
prevButton.textContent = '上一个';
prevButton.style.cssText = `
padding: 5px 15px;
background: #2d2d2d;
border: 1px solid #666;
border-radius: 4px;
color: #fff;
cursor: pointer;
transition: background 0.2s;
flex: 1;
`;
const nextButton = document.createElement('button');
nextButton.textContent = '下一个';
nextButton.style.cssText = `
padding: 5px 15px;
background: #2d2d2d;
border: 1px solid #666;
border-radius: 4px;
color: #fff;
cursor: pointer;
transition: background 0.2s;
flex: 1;
`;
const matchCounter = document.createElement('span');
matchCounter.className = 'search-counter'; // 添加类名以便后续查找
matchCounter.style.cssText = `
color: #fff;
font-size: 12px;
padding: 0 10px;
min-width: 60px;
text-align: center;
`;
// 添加搜索事件
const performSearch = () => {
// 使用;或;作为分隔符
const keywords = searchInput.value.trim().toLowerCase().split(/[;;]/);
// 过滤掉空字符串
const filteredKeywords = keywords.filter(k => k.trim());
if (!filteredKeywords.length) {
clearHighlights();
matchCounter.textContent = '';
return;
}
searchAndHighlight(filteredKeywords, matchCounter);
};
searchInput.addEventListener('keyup', (e) => {
if (e.key === 'Enter') {
performSearch();
}
});
searchButton.addEventListener('click', performSearch);
// 添加导航事件
prevButton.addEventListener('click', () => {
navigateHighlight('prev');
});
nextButton.addEventListener('click', () => {
navigateHighlight('next');
});
// 添加键盘快捷键
document.addEventListener('keydown', (e) => {
if (e.key === 'F3' || (e.ctrlKey && e.key === 'g')) {
e.preventDefault();
if (e.shiftKey) {
navigateHighlight('prev');
} else {
navigateHighlight('next');
}
}
});
// 添加hover效果
[searchButton, prevButton, nextButton].forEach(button => {
button.addEventListener('mouseover', () => {
button.style.background = button === searchButton ? '#357abd' : '#3d3d3d';
});
button.addEventListener('mouseout', () => {
button.style.background = button === searchButton ? '#4a90e2' : '#2d2d2d';
});
});
searchRow.appendChild(searchInput);
searchRow.appendChild(searchButton);
navigationRow.appendChild(prevButton);
navigationRow.appendChild(matchCounter);
navigationRow.appendChild(nextButton);
searchBoxContainer.appendChild(searchRow);
searchBoxContainer.appendChild(navigationRow);
document.body.appendChild(searchBoxContainer);
// 添加关闭按钮
const closeButton = document.createElement('button');
closeButton.textContent = '×';
closeButton.style.cssText = `
position: absolute;
top: -10px;
right: -10px;
width: 20px;
height: 20px;
line-height: 1;
padding: 0;
background: #2d2d2d;
border: 1px solid #666;
border-radius: 50%;
color: #999;
font-size: 16px;
font-weight: bold;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
z-index: 10000;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
`;
closeButton.addEventListener('mouseover', () => {
closeButton.style.color = '#fff';
closeButton.style.background = '#3d3d3d';
closeButton.style.borderColor = '#999';
});
closeButton.addEventListener('mouseout', () => {
closeButton.style.color = '#999';
closeButton.style.background = '#2d2d2d';
closeButton.style.borderColor = '#666';
});
closeButton.addEventListener('click', () => {
searchBoxContainer.style.display = 'none';
// 清除搜索结果
clearHighlights();
searchInput.value = '';
matchCounter.textContent = '';
});
searchBoxContainer.insertBefore(closeButton, searchBoxContainer.firstChild);
const optionsRow = document.createElement('div');
optionsRow.style.cssText = `
display: flex;
gap: 8px;
align-items: center;
padding: 0 5px;
`;
const showOnlyMatchedLabel = document.createElement('label');
showOnlyMatchedLabel.style.cssText = `
display: flex;
align-items: center;
gap: 5px;
color: #fff;
font-size: 12px;
cursor: pointer;
`;
const showOnlyMatchedCheckbox = document.createElement('input');
showOnlyMatchedCheckbox.type = 'checkbox';
showOnlyMatchedCheckbox.checked = STATE.showOnlyMatched;
showOnlyMatchedCheckbox.style.cssText = `
margin: 0;
cursor: pointer;
`;
showOnlyMatchedLabel.appendChild(showOnlyMatchedCheckbox);
showOnlyMatchedLabel.appendChild(document.createTextNode('只显示匹配项'));
showOnlyMatchedCheckbox.addEventListener('change', (e) => {
STATE.showOnlyMatched = e.target.checked;
GM_setValue('showOnlyMatched', STATE.showOnlyMatched);
// 如果有搜索结果,立即应用新设置
if (STATE.matchedCards.length > 0) {
updateCardVisibility();
}
});
optionsRow.appendChild(showOnlyMatchedLabel);
searchBoxContainer.insertBefore(optionsRow, navigationRow);
}
// 清除所有高亮
function clearHighlights() {
const highlights = document.querySelectorAll('.st-highlight');
highlights.forEach(highlight => {
const parent = highlight.parentNode;
parent.replaceChild(document.createTextNode(highlight.textContent), highlight);
});
// 清除所有高亮样式
document.querySelectorAll('.current-highlight, .matched-card').forEach(card => {
card.classList.remove('current-highlight', 'matched-card');
});
// 重置导航状态
STATE.matchedCards = [];
STATE.currentMatchIndex = -1;
// 恢复所有卡片的可见性
const allCards = document.querySelectorAll('.row[data-id]');
allCards.forEach(card => {
card.style.display = '';
});
}
// 搜索和高亮显示
function searchAndHighlight(keywords, matchCounter) {
clearHighlights();
const itemCards = document.querySelectorAll('.row[data-id]');
STATE.matchedCards = [];
let hasMatch = false;
// 预处理关键词,处理特殊字符
const processedKeywords = keywords.map(keyword => {
return keyword.trim().replace(/\+/g, '[++]');
}).filter(k => k); // 过滤空字符串
itemCards.forEach(card => {
const cardText = card.textContent.toLowerCase();
const matches = processedKeywords.map(keyword => {
const regex = new RegExp(keyword, 'i');
return regex.test(cardText);
});
const allMatch = matches.every(match => match);
if (allMatch) {
hasMatch = true;
STATE.matchedCards.push(card);
highlightKeywords(card, keywords);
// 如果不是只显示匹配项,则添加背景高亮
if (!STATE.showOnlyMatched) {
card.classList.add('matched-card');
}
} else if (STATE.showOnlyMatched) {
card.style.display = 'none';
}
});
if (!hasMatch) {
alert('未找到匹配的结果');
if (matchCounter) {
matchCounter.textContent = '0/0';
}
} else {
STATE.currentMatchIndex = 0;
updateHighlightNavigation();
}
}
// 高亮关键词
function highlightKeywords(element, keywords) {
const walker = document.createTreeWalker(
element,
NodeFilter.SHOW_TEXT,
{
acceptNode: function(node) {
// 排除脚本和样式节点,以及已经高亮的文本
if (node.parentNode.nodeName === 'SCRIPT' ||
node.parentNode.nodeName === 'STYLE' ||
node.parentNode.classList.contains('st-highlight')) {
return NodeFilter.FILTER_REJECT;
}
// 检查文本是否包含任何关键词
const text = node.textContent.toLowerCase();
const containsAnyKeyword = keywords.some(keyword => {
const processedKeyword = keyword.replace(/\+/g, '[++]');
const regex = new RegExp(processedKeyword, 'i');
return regex.test(text);
});
return containsAnyKeyword ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
}
}
);
const nodes = [];
let node;
while (node = walker.nextNode()) {
nodes.push(node);
}
nodes.forEach(textNode => {
let text = textNode.textContent;
let tempText = text;
keywords.forEach(keyword => {
// 创建一个包含所有可能的加号变体的正则表达式
const processedKeyword = keyword.replace(/\+/g, '[++]');
const regex = new RegExp(`(${processedKeyword})`, 'gi');
if (regex.test(text)) {
tempText = tempText.replace(regex, '$1');
}
});
if (tempText !== text) {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = tempText;
const fragment = document.createDocumentFragment();
while (tempDiv.firstChild) {
fragment.appendChild(tempDiv.firstChild);
}
textNode.parentNode.replaceChild(fragment, textNode);
}
});
}
// 更新高亮导航
function updateHighlightNavigation() {
const matchCounter = document.querySelector('.search-counter');
if (!matchCounter) return;
// 更新计数器
matchCounter.textContent = `${STATE.currentMatchIndex + 1}/${STATE.matchedCards.length}`;
// 移除之前的当前高亮
const prevHighlight = document.querySelector('.current-highlight');
if (prevHighlight) {
prevHighlight.classList.remove('current-highlight');
}
// 添加新的当前高亮
const currentCard = STATE.matchedCards[STATE.currentMatchIndex];
if (currentCard) {
currentCard.classList.add('current-highlight');
// 滚动到当前卡片
currentCard.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}
}
// 导航到上一个/下一个高亮
function navigateHighlight(direction) {
if (STATE.matchedCards.length === 0) return;
if (direction === 'next') {
STATE.currentMatchIndex = (STATE.currentMatchIndex + 1) % STATE.matchedCards.length;
} else {
STATE.currentMatchIndex = (STATE.currentMatchIndex - 1 + STATE.matchedCards.length) % STATE.matchedCards.length;
}
updateHighlightNavigation();
}
// 修改样式
const style = document.createElement('style');
style.textContent = `
.current-highlight {
background-color: rgba(255, 215, 0, 0.3) !important;
}
.matched-card {
background-color: rgba(255, 215, 0, 0.1) !important;
}
.st-highlight {
background-color: #ffd700;
color: #000;
border-radius: 2px;
padding: 0 2px;
}
`;
document.head.appendChild(style);
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
});
}
}
function findReactInstance(element) {
const key = Object.keys(element).find(key => key.startsWith('__reactFiber$'));
return key ? element[key] : null;
}
function findLoadMoreHandler() {
const loadMoreBtn = document.querySelector('.load-more-btn');
if (!loadMoreBtn) {
console.log('未找到加载更多按钮');
return null;
}
// 尝试获取React实例
const instance = findReactInstance(loadMoreBtn);
if (!instance) {
console.log('未找到React实例');
return null;
}
// 遍历查找onClick处理函数
let current = instance;
while (current) {
if (current.memoizedProps && current.memoizedProps.onClick) {
return current.memoizedProps.onClick;
}
current = current.return;
}
console.log('未找到onClick处理函数');
return null;
}
function clickLoadMoreIfExists() {
// 使用正确的选择器
const loadMoreBtn = document.querySelector('.btn.load-more-btn');
if (!loadMoreBtn) {
console.log('未找到加载更多按钮');
return false;
}
const results = document.querySelectorAll('.resultset, .trade-result, [class*="result-item"]');
const currentResultCount = results.length;
if (currentResultCount >= 100) {
return false;
}
try {
// 尝试多种方式触发点击
// 1. 原生点击
loadMoreBtn.click();
// 2. 模拟鼠标事件序列
['mousedown', 'mouseup', 'click'].forEach(eventType => {
const event = new MouseEvent(eventType, {
bubbles: true,
cancelable: true,
buttons: 1
});
loadMoreBtn.dispatchEvent(event);
});
// 3. 尝试点击内部的span
const spanInButton = loadMoreBtn.querySelector('span');
if (spanInButton) {
spanInButton.click();
['mousedown', 'mouseup', 'click'].forEach(eventType => {
const event = new MouseEvent(eventType, {
bubbles: true,
cancelable: true,
buttons: 1
});
spanInButton.dispatchEvent(event);
});
}
// 4. 使用 HTMLElement 的 click 方法
HTMLElement.prototype.click.call(loadMoreBtn);
return true;
} catch (error) {
console.log('触发加载更多时出错:', error);
return false;
}
}
function autoLoadAllResults() {
let attempts = 0;
const maxAttempts = 20;
let lastResultCount = 0;
function tryLoadMore() {
const results = document.querySelectorAll('.resultset');
const currentResultCount = results.length;
if (currentResultCount >= 100 || attempts >= maxAttempts ||
(currentResultCount === lastResultCount && attempts > 0)) {
return;
}
if (clickLoadMoreIfExists()) {
lastResultCount = currentResultCount;
attempts++;
setTimeout(tryLoadMore, 1000); // 增加间隔时间到1秒
}
}
setTimeout(tryLoadMore, 1000);
}
// 检查URL是否是搜索结果页面
function isSearchResultPage() {
const isPOE2Trade = window.location.href.includes('pathofexile.com/trade2/search/poe2');
const hasResults = document.querySelector('.results-container, .trade-results, .search-results, [class*="results"]') !== null;
return isPOE2Trade && hasResults;
}
async function init() {
try {
await new Promise(resolve => setTimeout(resolve, 100));
// 监听URL变化
let lastUrl = location.href;
const urlCheckInterval = setInterval(() => {
const currentUrl = location.href;
if ((currentUrl !== lastUrl || currentUrl.includes('pathofexile.com/trade2/search/poe2')) && STATE.autoLoadEnabled) {
lastUrl = currentUrl;
setTimeout(() => {
if (isSearchResultPage()) {
autoLoadAllResults();
}
}, 100);
}
}, 100);
// 初始检查
setTimeout(() => {
if (isSearchResultPage() && STATE.autoLoadEnabled) {
autoLoadAllResults();
}
}, 100);
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);
createConfigModal();
createControls();
if (STATE.pageSimplified) {
convertPageText(converter);
}
watchSearchResults(converter);
setInterval(() => {
if (STATE.pageSimplified) {
convertPageText(converter);
}
}, 1000);
} catch (error) {
console.log('初始化时出错:', error);
}
}
function updateConfig(category, name) {
if (confirm(`确定要用当前页面更新配置 "${name}" 吗?`)) {
STATE.configs[category][name] = {
url: window.location.href,
timestamp: new Date().toISOString()
};
GM_setValue('savedConfigs', STATE.configs);
updateConfigList();
}
}
setTimeout(init, 2000);
})();