// ==UserScript== // @name Bungie.net术语替换_可扩展到其他网站 // @namespace your-namespace // @version 1.0.1 // @description 在网页中替换文本,支持三种显示模式和术语表替换 // @match https://www.bungie.net/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_xmlhttpRequest // @connect 20xiji.github.io // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; const ITEM_LIST_URL = 'https://20xiji.github.io/Destiny-item-list/item-list-250126_simplee.json'; let replacementHistory = []; let termMap = new Map(); let currentMode = 1; let dialogVisible = false; GM_addStyle(` #textReplacerDialog { position: fixed; top: 20px; right: 20px; background: #1a1a1a; padding: 15px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.25); z-index: 9999; width: 260px; font-family: Arial, sans-serif; color: #fff; display: none; } #modeButtons { display: grid; gap: 8px; margin: 12px 0; } .mode-btn { padding: 8px; border: none; border-radius: 4px; background: #333; color: #888; cursor: pointer; transition: all 0.2s; } .mode-btn.active { background: #4CAF50; color: #fff; box-shadow: 0 2px 4px rgba(0,0,0,0.2); } #actionButtons { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; } #actionButtons button { flex: 1; padding: 8px; border: none; border-radius: 4px; background: #4CAF50; color: white; cursor: pointer; min-width: 80px; } #actionButtons button:disabled { background: #666; cursor: not-allowed; } #termCount { font-size: 12px; color: #888; margin-left: 8px; } #btnClearCache { background: #f44336 !important; } `); const dialog = document.createElement('div'); dialog.id = 'textReplacerDialog'; dialog.innerHTML = `

文本替换工具 (加载中...)

`; document.body.appendChild(dialog); const elements = { modeButtons: dialog.querySelectorAll('.mode-btn'), btnApplyAll: dialog.querySelector('#btnApplyAll'), btnUndo: dialog.querySelector('#btnUndo'), btnClearCache: dialog.querySelector('#btnClearCache'), termCount: dialog.querySelector('#termCount') }; elements.modeButtons.forEach(btn => btn.addEventListener('click', handleModeChange)); elements.btnApplyAll.addEventListener('click', applyAllRules); elements.btnUndo.addEventListener('click', undoReplace); elements.btnClearCache.addEventListener('click', clearCache); document.addEventListener('keydown', (e) => { if (e.ctrlKey && e.altKey && e.key.toLowerCase() === 'k') { dialogVisible = !dialogVisible; dialog.style.display = dialogVisible ? 'block' : 'none'; updateButtonStates(); } }); initTerminology(); updateButtonStates(); async function clearCache() { try { // 强制删除现有缓存 GM_deleteValue('cachedTerms'); GM_deleteValue('cacheTime'); // 立即触发重新加载 const freshData = await fetchTerms(); termMap = new Map(Object.entries(freshData)); GM_setValue('cachedTerms', freshData); GM_setValue('cacheTime', Date.now()); updateTermCount(); alert('✅ 缓存已清除并重新加载成功\n当前种类:武器、护甲、技能、模组\n已加载条目数:' + termMap.size); } catch (error) { console.error('缓存清除失败:', error); alert('❌ 缓存清除失败:' + error.message); termMap.clear(); updateTermCount(); } } async function initTerminology() { const CACHE_DAYS = 1; const cachedData = GM_getValue('cachedTerms'); const cacheTime = GM_getValue('cacheTime', 0); try { // 缓存过期或不存在时强制更新 if (!cachedData || Date.now() - cacheTime > 86400000 * CACHE_DAYS) { const freshData = await fetchTerms(); termMap = new Map(Object.entries(freshData)); GM_setValue('cachedTerms', freshData); GM_setValue('cacheTime', Date.now()); } else { termMap = new Map(Object.entries(cachedData)); } } catch (error) { console.error('术语表初始化失败:', error); if (cachedData) { termMap = new Map(Object.entries(cachedData)); } } updateTermCount(); } function updateTermCount() { elements.termCount.textContent = termMap.size > 0 ? `(已加载${termMap.size}条)` : '(未加载数据)'; } function fetchTerms() { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: ITEM_LIST_URL, timeout: 15000, onload: (res) => { if (res.status >= 200 && res.status < 300) { try { const data = JSON.parse(res.responseText); if (Object.keys(data).length > 0) { resolve(data); } else { reject(new Error('获取到空数据')); } } catch (e) { reject(new Error('数据解析失败')); } } else { reject(new Error(`HTTP ${res.status}`)); } }, onerror: (err) => { reject(new Error(`网络错误: ${err}`)); }, ontimeout: () => { reject(new Error('请求超时(15秒)')); } }); }); } // 其他保持不变的功能函数 function handleModeChange(e) { currentMode = parseInt(e.target.dataset.mode); updateButtonStates(); } function updateButtonStates() { elements.modeButtons.forEach(btn => { btn.classList.toggle('active', parseInt(btn.dataset.mode) === currentMode); }); } function applyAllRules() { const termRules = Array.from(termMap).map(([en, cn]) => { switch (currentMode) { case 1: return [en, cn]; case 2: return [en, `${en} | ${cn}`]; case 3: return [en, `${cn}(${en})`]; default: return [en, cn]; } }); performReplace(termRules); } function performReplace(rules) { const regex = buildRegex(rules); const replaceMap = new Map(rules); const snapshot = []; const walker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT, null, false ); while (walker.nextNode()) { const node = walker.currentNode; const original = node.nodeValue; const replaced = original.replace(regex, (m) => { const foundKey = Array.from(replaceMap.keys()).find(k => k.toLowerCase() === m.toLowerCase() ); return foundKey ? replaceMap.get(foundKey) : m; }); if (replaced !== original) { snapshot.push({ node, text: original }); node.nodeValue = replaced; } } if (snapshot.length) { replacementHistory.push(snapshot); elements.btnUndo.disabled = false; } } function buildRegex(rules) { const sortedKeys = [...new Set(rules.map(([k]) => k))] .sort((a, b) => b.length - a.length) .map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); return new RegExp(`\\b(${sortedKeys.join('|')})\\b`, 'gi'); } function undoReplace() { if (replacementHistory.length) { const last = replacementHistory.pop(); last.forEach(({ node, text }) => { if (node.parentNode) node.nodeValue = text; }); elements.btnUndo.disabled = !replacementHistory.length; } } })();