// ==UserScript== // @name Web+ // @namespace WebPlus // @description 综合性网页体验增强脚本。模块化设计,按需启用,精准匹配,性能优先。 // @version 1.0.1 // @license MIT // @author Qiu Zongman // @homepageURL https://gitee.com/qiuzongman/WebPlus // @match *://*/* // @connect edge.microsoft.com // @connect api-edge.cognitive.microsofttranslator.com // @connect * // @grant GM_xmlhttpRequest // @grant GM.xmlHttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @run-at document-end // @icon data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTI5OSIgaGVpZ2h0PSIxMzAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWw6c3BhY2U9InByZXNlcnZlIiBvdmVyZmxvdz0iaGlkZGVuIj48ZGVmcz48Y2xpcFBhdGggaWQ9ImNsaXAwIj48cmVjdCB4PSIzMTAxIiB5PSI0ODIiIHdpZHRoPSIxMjk5IiBoZWlnaHQ9IjEzMDAiLz48L2NsaXBQYXRoPjwvZGVmcz48ZyBjbGlwLXBhdGg9InVybCgjY2xpcDApIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzEwMSAtNDgyKSI+PHJlY3QgeD0iMzEwMSIgeT0iNDgzIiB3aWR0aD0iMTI5OSIgaGVpZ2h0PSIxMjk5IiBmaWxsPSIjRkZGRkZGIi8+PHJlY3QgeD0iMzEwMSIgeT0iMTE5NyIgd2lkdGg9IjU4NCIgaGVpZ2h0PSI1ODUiIGZpbGw9IiMzNEE4NTMiLz48cmVjdCB4PSIzMTAxIiB5PSI0ODIiIHdpZHRoPSI1ODQiIGhlaWdodD0iNTg0IiBmaWxsPSIjRUE0MzM1Ii8+PHJlY3QgeD0iMzgxNSIgeT0iMTE5NyIgd2lkdGg9IjU4NSIgaGVpZ2h0PSI1ODUiIGZpbGw9IiNGQkJDMDQiLz48cmVjdCB4PSIzODE1IiB5PSI3MDkiIHdpZHRoPSI1ODUiIGhlaWdodD0iMTMwIiBmaWxsPSIjNDI4NUY0Ii8+PHJlY3QgeD0iNDA0MyIgeT0iNDg0IiB3aWR0aD0iMTMwIiBoZWlnaHQ9IjU4NSIgZmlsbD0iIzQyODVGNCIvPjwvZz48L3N2Zz4= // ==/UserScript== (function () { 'use strict'; if (window.top !== window.self) return; // =================================================================== // 核心框架 — 设置管理 // =================================================================== const STORAGE_KEY = 'wp_settings'; const DEFAULT_SETTINGS = { lastTab: 4, pageToggleKeys: { translate: '', }, pageEnabled: { translate: true, global: true, other: true, visual: true, speed: true, about: true, }, // 各模块专属设置 modules: { translate: { blockedSites: '', selTranslate: false, autoTranslate: false, btnMode: 'hide', pageShortcut: 'Alt+A', selShortcut: 'Alt+S', bilingualKey: '', }, bilibili: { showIp: true, colorMode: '默认', cleanLinks: true, directLink: true, preloadBoost: true, hoverDelay: 65, cleanSources: {}, highlightEnabled: false, highlightKeywords: '', highlightColor: '#FF0000', scrollbarStyle: 'default', scrollbarColor: '#FF0000', backToTop: false, spacingEnabled: false, unlockEnabled: false, } }, // 全局设置 openSettingsKey: '', }; let settings = {}; function loadSettings() { try { const raw = typeof GM_getValue === 'function' ? GM_getValue(STORAGE_KEY, null) : null; settings = raw ? { ...DEFAULT_SETTINGS, ...JSON.parse(raw) } : { ...DEFAULT_SETTINGS }; if (!settings.pageEnabled) settings.pageEnabled = { ...DEFAULT_SETTINGS.pageEnabled }; if (!settings.modules) settings.modules = { ...DEFAULT_SETTINGS.modules }; // 确保每个模块默认值存在 for (const key in DEFAULT_SETTINGS.modules) { if (!settings.modules[key]) settings.modules[key] = { ...DEFAULT_SETTINGS.modules[key] }; else for (const dk in DEFAULT_SETTINGS.modules[key]) { if (settings.modules[key][dk] === undefined) settings.modules[key][dk] = DEFAULT_SETTINGS.modules[key][dk]; } } } catch (e) { settings = { ...DEFAULT_SETTINGS }; } } function saveSettings() { try { if (typeof GM_setValue === 'function') { GM_setValue(STORAGE_KEY, JSON.stringify(settings)); } } catch (e) { console.error('wp saveSettings error:', e); } } // =================================================================== // 核心框架 — 页面定义 // =================================================================== const PAGES = [ { id: 'translate', icon: '🌐', label: '网页翻译' }, { id: 'visual', icon: '🎨', label: '视觉效果' }, { id: 'speed', icon: '⚡', label: '优化提速' }, { id: 'other', icon: '🧩', label: '零散工具' }, { id: 'global', icon: '⚙️', label: '全局设置' }, { id: 'about', icon: '📋', label: '关于' }, ]; // =================================================================== // 核心框架 — UI 面板 // =================================================================== function buildPageContent(pageId) { // 由各模块覆盖扩展 return '
功能待开发
'; } function openSettings() { const existing = document.getElementById('wp-settings-panel'); if (existing) { existing.remove(); return; } const panel = document.createElement('div'); panel.id = 'wp-settings-panel'; panel.innerHTML = buildHTML(); document.body.appendChild(panel); bindEvents(panel); // 通知各模块刷新 UI(如翻译快捷键输入框的重置) document.dispatchEvent(new CustomEvent('wp-panel-open')); } function buildHTML() { const esc = (v) => String(v).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); const navItems = PAGES.map((p, i) => ` `).join(''); const pageContents = PAGES.map(p => { return ` `; }).join(''); return `
${pageContents}
`; } function bindEvents(panel) { const navBtns = panel.querySelectorAll('.wp-nav-btn'); const pages = panel.querySelectorAll('.wp-page'); const toggles = panel.querySelectorAll('.wp-page-toggle'); function switchPage(index) { const pageId = PAGES[index].id; navBtns.forEach((btn, i) => btn.classList.toggle('active', i === index)); pages.forEach(p => { p.style.display = p.dataset.pageId === pageId ? 'flex' : 'none'; }); settings.lastTab = index; saveSettings(); } navBtns.forEach((btn, i) => { btn.addEventListener('click', () => switchPage(i)); }); const initIndex = Math.min(settings.lastTab || 0, PAGES.length - 1); switchPage(initIndex); // 页面开关 — 即时保存 toggles.forEach(t => { t.addEventListener('change', function () { const label = this.nextElementSibling.nextElementSibling; if (this.checked) { label.textContent = '已启用'; label.style.color = 'var(--wp-toggle-on, #4CAF50)'; } else { label.textContent = '已关闭'; label.style.color = 'var(--wp-text-muted, #888)'; } settings.pageEnabled[this.dataset.page] = this.checked; saveSettings(); document.dispatchEvent(new CustomEvent('wp-module-toggle', { detail: { page: this.dataset.page, enabled: this.checked } })); }); }); // 监听外部(如快捷键)触发的模块切换,同步 UI document.addEventListener('wp-module-toggle', function syncToggle(e) { var cb = panel.querySelector('.wp-page-toggle[data-page="' + e.detail.page + '"]'); if (!cb) return; cb.checked = e.detail.enabled; var label = cb.nextElementSibling.nextElementSibling; if (label) { label.textContent = e.detail.enabled ? '已启用' : '已关闭'; label.style.color = e.detail.enabled ? 'var(--wp-toggle-on, #4CAF50)' : 'var(--wp-text-muted, #888)'; } }); // 全局设置 — 网页翻译开关 var globalTrans = panel.querySelector('#wp-global-translate'); if (globalTrans) { globalTrans.querySelectorAll('.wp-dual-btn').forEach(function(btn) { btn.addEventListener('click', function() { var v = this.dataset.value === 'true'; settings.pageEnabled.translate = v; saveSettings(); globalTrans.querySelectorAll('.wp-dual-btn').forEach(function(x) { x.classList.toggle('active', x.dataset.value === String(v)); }); document.dispatchEvent(new CustomEvent('wp-module-toggle', { detail: { page: 'translate', enabled: v } })); }); }); } // 关闭 panel.querySelector('#wp-close').addEventListener('click', () => panel.remove()); // 恢复默认(在全局页面底部) panel.querySelector('#wp-reset-global').addEventListener('click', () => { settings = { ...DEFAULT_SETTINGS }; saveSettings(); panel.remove(); location.reload(); }); // 快捷键捕获 document.querySelectorAll('.wp-key-input').forEach(function(inp) { inp.addEventListener('focus', function() { console.log('wp-key focus on', this.id); this.value = ''; }); inp.addEventListener('keydown', function(e) { console.log('wp-key keydown on', this.id, 'key:', e.key); if (e.key === 'Escape' || e.key === 'Delete' || e.key === 'Backspace') { this.value = ''; saveSettings(); this.blur(); e.preventDefault(); return; } if (['Control','Alt','Shift','Meta'].indexOf(e.key) >= 0) return; var k = e.key === ' ' ? 'Space' : e.key; if (k.length > 1 && !/^F\d+$/.test(k)) return; if (e.ctrlKey) k = 'Ctrl+' + k; if (e.altKey) k = 'Alt+' + k; if (e.shiftKey) k = 'Shift+' + k; this.value = k; saveSettings(); this.blur(); e.preventDefault(); }); inp.addEventListener('blur', function() { var val = this.value; if (this.id === 'wp-openSettingsKey') settings.openSettingsKey = val; else if (this.id === 'wp-toggle-key-translate') { if (!settings.pageToggleKeys) settings.pageToggleKeys = {}; settings.pageToggleKeys.translate = val; } else if (this.classList.contains('wp-trans-key')) { settings.modules.translate[this.dataset.key] = val; } else if (this.dataset.key) { var p = this.dataset.key.split('.'); var o = settings; for (var pi = 0; pi < p.length - 1; pi++) { if (!o[p[pi]]) o[p[pi]] = {}; o = o[p[pi]]; } o[p[p.length - 1]] = val; } saveSettings(); }); }); function onEsc(e) { if (e.key === 'Escape') { panel.remove(); document.removeEventListener('keydown', onEsc); } } document.addEventListener('keydown', onEsc); // 更新所有外部规则 var updateBtn = document.getElementById('wp-update-rules'); var updateStatus = document.getElementById('wp-update-rules-status'); if (updateBtn && updateStatus) { updateBtn.addEventListener('click', function() { updateBtn.disabled = true; updateBtn.textContent = '更新中…'; var srcs = settings.modules.bilibili && settings.modules.bilibili.cleanSources || {}; var urls = Object.keys(srcs); if (!urls.length) { updateStatus.textContent = '没有已加载的外部规则。'; updateBtn.disabled = false; updateBtn.textContent = '🔄 更新所有外部链接文件'; return; } var done = 0, total = urls.length; urls.forEach(function(url) { try { BILI_MODULE.loadCleanSource(url, function() { done++; updateStatus.textContent = '更新中 ' + done + '/' + total; if (done >= total) { updateStatus.textContent = '✅ 更新完成'; updateBtn.disabled = false; updateBtn.textContent = '🔄 更新所有外部链接文件'; } }); } catch(e) { done++; if (done >= total) { updateStatus.textContent = '更新完成'; updateBtn.disabled = false; updateBtn.textContent = '🔄 更新所有外部链接文件'; } } }); }); } // 通知各模块绑定面板事件 try { TRANS_MODULE.bindPanelEvents(); } catch(e) {} try { BILI_MODULE.bindPanelEvents(); } catch(e) {} } // =================================================================== // 翻译模块 // =================================================================== const TRANS_MODULE = (function() { 'use strict'; if (window.top !== window.self) return; const AUTH = 'https://edge.microsoft.com/translate/auth'; const API = 'https://api-edge.cognitive.microsofttranslator.com/translate?api-version=3.0&to=zh-Hans&textType=plain'; const API_HTML = 'https://api-edge.cognitive.microsofttranslator.com/translate?api-version=3.0&to=zh-Hans&textType=html'; const MAX_UNITS = 5000, MAX_CHARS = 500000, CHUNK_NODES = 25, CHUNK_CHARS = 8000, CONCURRENCY = 6, CACHE_LIMIT = 5000; let token = '', tokenTime = 0, running = false; let cache = {}, revCache = {}; let cacheKeys = []; let isTranslated = false, translateInProgress = false, translateCount = 0; let showRestore = false; function mod(){return settings.modules.translate||{}} function mod(){return settings.modules.translate||{}} let everTranslated = false; const ZH_RE = /[\u4e00-\u9fff]/; const HTML_PREFIX = '__MS_AUTO_ZH_HTML__'; function htmlKey(s) { return HTML_PREFIX + s; } function stripHtmlKey(s) { return s.startsWith(HTML_PREFIX) ? s.slice(HTML_PREFIX.length) : s; } function setCache(src, dst) { if (!src || !dst || cache[src]) return; dst = dst.trim(); cache[src] = dst; // 多对一映射:相同译文可对应多份原文,存数组 if (revCache[dst] === undefined) { revCache[dst] = src; } else if (Array.isArray(revCache[dst])) { revCache[dst].push(src); } else if (revCache[dst] !== src) { revCache[dst] = [revCache[dst], src]; } cacheKeys.push(src); if (cacheKeys.length > CACHE_LIMIT) { const old = cacheKeys.shift(); const oldDst = cache[old]; if (oldDst) { if (Array.isArray(revCache[oldDst])) { revCache[oldDst] = revCache[oldDst].filter(v => v !== old); if (revCache[oldDst].length === 0) delete revCache[oldDst]; else if (revCache[oldDst].length === 1) revCache[oldDst] = revCache[oldDst][0]; } else { delete revCache[oldDst]; } } delete cache[old]; } } // revCache 读值辅助(兼容多对一) function revGet(dst) { const v = revCache[dst]; if (v === undefined) return null; return Array.isArray(v) ? v[0] : v; } // ---------- 扫描渲染函数 ---------- const MODE_ORIG = 0, MODE_TRANS = 1, MODE_BI = 2; let currentMode = MODE_ORIG; function escHtml(s) { return String(s).replace(/&/g,'&').replace(//g,'>'); } function cleanupBiSpans() { let list = document.querySelectorAll('.ms-bi'); let iter = 0; while (list.length > 0 && iter < 10) { for (let i = list.length - 1; i >= 0; i--) { const w = list[i]; if (!w.parentNode) continue; const biType = w.dataset.biType || 'text'; const transEl = w.querySelector('.ms-bi-trans'); const transText = transEl ? (biType === 'block' ? transEl.innerHTML : transEl.textContent) : (w.dataset.trans || ''); if (biType === 'block') { w.parentNode.innerHTML = transText; } else { w.parentNode.replaceChild(document.createTextNode(transText), w); } } list = document.querySelectorAll('.ms-bi'); iter++; } } function renderOriginalFromCache() { cleanupBiSpans(); const all = document.querySelectorAll('body *'); for (let i = 0; i < all.length; i++) { const el = all[i]; const cur = el.innerHTML; var orig = revGet(cur); if (orig) { el.innerHTML = stripHtmlKey(orig); } } const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false); let n; while ((n = walker.nextNode())) { const v = n.nodeValue; const trimmed = v.trim(); var origText = revGet(trimmed); if (origText) { n.nodeValue = v.split(trimmed).join(origText); } } currentMode = MODE_ORIG; } function renderTranslationFromCache() { cleanupBiSpans(); const all = document.querySelectorAll('body *'); for (let i = 0; i < all.length; i++) { const el = all[i]; const key = htmlKey(el.innerHTML); if (cache[key]) { el.innerHTML = cache[key]; } } const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false); let n; while ((n = walker.nextNode())) { const v = n.nodeValue; const trimmed = v.trim(); if (cache[trimmed]) { n.nodeValue = v.split(trimmed).join(cache[trimmed]); } } currentMode = MODE_TRANS; } function renderBilingual() { cleanupBiSpans(); const processedEls = new WeakSet(); const nodesToReplace = []; const allEls = document.querySelectorAll('body *'); for (const el of allEls) { const curHtml = el.innerHTML; const key = htmlKey(curHtml); let origHtml, transHtml; if (cache[key]) { origHtml = stripHtmlKey(key); transHtml = cache[key]; } else { var origFromHtml = revGet(curHtml); if (origFromHtml) { transHtml = curHtml; origHtml = stripHtmlKey(origFromHtml); } else continue; } processedEls.add(el); nodesToReplace.push({ type: 'block', el, origHtml, transHtml }); } const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false); let n; while ((n = walker.nextNode())) { if (n.parentElement && processedEls.has(n.parentElement)) continue; const v = n.nodeValue; const trimmed = v.trim(); let orig, trans; if (cache[trimmed]) { orig = trimmed; trans = cache[trimmed]; } else { var origTextFromRev = revGet(trimmed); if (origTextFromRev) { orig = origTextFromRev; trans = trimmed; } else continue; } const idx = v.indexOf(trimmed); if (idx < 0) continue; const head = v.slice(0, idx); const tail = v.slice(idx + trimmed.length); nodesToReplace.push({ type: 'text', node: n, head, orig, trans, tail }); } for (const item of nodesToReplace) { if (item.type === 'block') { const wrapper = document.createElement('span'); wrapper.className = 'ms-bi'; wrapper.dataset.biType = 'block'; wrapper.dataset.trans = item.transHtml; wrapper.innerHTML = `${item.origHtml}${item.transHtml}`; item.el.innerHTML = ''; item.el.appendChild(wrapper); } else { const wrapper = document.createElement('span'); wrapper.className = 'ms-bi'; wrapper.dataset.biType = 'text'; wrapper.dataset.trans = item.trans; wrapper.innerHTML = `${escHtml(item.orig)}${escHtml(item.trans)}`; const parent = item.node.parentNode; if (!parent) continue; parent.insertBefore(document.createTextNode(item.head), item.node); parent.insertBefore(wrapper, item.node); parent.insertBefore(document.createTextNode(item.tail), item.node); parent.removeChild(item.node); } } currentMode = MODE_BI; } // ---------- 翻译收集与 API 调用 ---------- const EXT_RE = /\.(apk|zip|7z|rar|tar|gz|iso|json|xml|yaml|txt|md|exe|dll|js|css|html)$/i; const URL_RE = /^https?:\/\//i; const TECH_STR_RE = /^[A-Za-z0-9._+@#:/\\()[\]-]+$/; const HEX_RE = /^[a-f0-9]{7,40}$/i; const VERSION_RE = /^v?\d+(\.\d+){1,4}([-+][A-Za-z0-9._-]+)?$/i; const CONST_RE = /^[A-Z0-9_]{2,12}$/; const CAMEL_RE = /^[A-Za-z]+[A-Z][A-Za-z0-9_.$-]*$/; const PUNCT_CODE_RE = /[,:;()]/; const METHOD_RE = /\b[A-Za-z_$][A-Za-z0-9_$]*\s*\(\s*\)/g; const ID_RE = /\b[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*\b/g; const COMMON_EN_WORDS = new Set(['the','a','an','is','are','was','were','be','been','have','has','had','do','does','did','will','would','shall','should','may','might','can','could','of','in','on','at','to','for','with','and','or','but','not','this','that','these','those','it','they','we','you','he','she','i','from','by','about','as','into','like','through','after','over','between','out','against','during','without','before','under','around','among']); function hasNaturalLanguage(s) { let count = 0; const words = s.toLowerCase().split(/\s+/); for (const w of words) if (COMMON_EN_WORDS.has(w) && ++count >= 2) return true; return false; } function req(o, ok, bad) { const opt = { method: o.method || 'GET', url: o.url, headers: o.headers || {}, data: o.data, timeout: o.timeout || 16000 }; if (typeof GM !== 'undefined' && GM.xmlHttpRequest) GM.xmlHttpRequest(opt).then(ok).catch(e => bad(e?.message || String(e))); else if (typeof GM_xmlhttpRequest === 'function') GM_xmlhttpRequest({ method: opt.method, url: opt.url, headers: opt.headers, data: opt.data, timeout: opt.timeout, onload: ok, onerror: () => bad('请求失败'), ontimeout: () => bad('请求超时') }); else fetch(opt.url, { method: opt.method, headers: opt.headers, body: opt.data }) .then(r => r.text().then(t => ok({ status: r.status, responseText: t }))) .catch(e => bad(e?.message || String(e))); } function cleanup(s) { s = String(s || '').trim(); s = s.replace(/([\u4e00-\u9fff])\s+(?=[\u4e00-\u9fff])/g, '$1'); s = s.replace(/\s+([,。!?:;、)】》])/g, '$1'); s = s.replace(/([(【《])\s+/g, '$1'); s = s.replace(/([\u4e00-\u9fff])\s+([,。!?:;、])/g, '$1$2'); return s; } function isCJK(cp) { return cp >= 0x4E00 && cp <= 0x9FFF; } function isLatin(cp) { return (cp >= 0x41 && cp <= 0x5A) || (cp >= 0x61 && cp <= 0x7A); } function isLetter(cp) { return (cp >= 0x41 && cp <= 0x5A) || (cp >= 0x61 && cp <= 0x7A) || (cp >= 0xC0 && cp <= 0x24F) || (cp >= 0x370 && cp <= 0x3FF) || (cp >= 0x400 && cp <= 0x52F) || (cp >= 0x590 && cp <= 0x5FF) || (cp >= 0x600 && cp <= 0x6FF) || (cp >= 0x900 && cp <= 0xDFF) || (cp >= 0xE00 && cp <= 0xE7F) || (cp >= 0x1000 && cp <= 0x109F) || (cp >= 0x10A0 && cp <= 0x10FF) || (cp >= 0x1200 && cp <= 0x137F) || (cp >= 0x1780 && cp <= 0x17FF) || (cp >= 0x3040 && cp <= 0x309F) || (cp >= 0x30A0 && cp <= 0x30FF) || (cp >= 0xAC00 && cp <= 0xD7AF); } function countLetters(t, nonLatinOnly) { return Array.from(String(t || '')).filter(c => { const cp = c.charCodeAt(0); if (isCJK(cp)) return false; if (nonLatinOnly && isLatin(cp)) return false; return isLetter(cp); }).length; } function should(t) { if (!t || t.length < 2) return false; if (protect(t)) return false; let zhCount = 0; for (let i = 0; i < t.length; i++) if (isCJK(t.charCodeAt(i))) zhCount++; if (zhCount > 0) return countLetters(t, true) >= 1; return countLetters(t, false) >= 1; } function protect(t) { const s = String(t || '').trim(); if (!s || s.length < 2) return true; if (EXT_RE.test(s)) return true; if (URL_RE.test(s)) return true; if (TECH_STR_RE.test(s) && (s.match(/[._@#:/\\()[\]-]/g) || []).length >= 2) return true; if (HEX_RE.test(s)) return true; if (VERSION_RE.test(s)) return true; if (CONST_RE.test(s) && !/^(ALL|TOP|NEW|YES|NO|OK)$/.test(s)) return true; if (/^\.[a-zA-Z]/.test(s)) return true; if (/^[a-zA-Z0-9][a-zA-Z0-9._-]*\.[a-zA-Z0-9]{2,6}$/.test(s)) return true; if (CAMEL_RE.test(s)) return true; const methodCalls = s.match(METHOD_RE) || []; if (methodCalls.length >= 2) return true; const ids = s.match(ID_RE) || []; let codeIds = 0; for (const id of ids) { if (/[a-z][A-Z]|[_$]/.test(id) || /^[A-Z][A-Za-z0-9_$]*[A-Z][A-Za-z0-9_$]*$/.test(id)) codeIds++; } if (methodCalls.length >= 1 && codeIds >= 1) return true; if (codeIds >= 3 && PUNCT_CODE_RE.test(s)) return !hasNaturalLanguage(s); let hasAnyLetter = false; for (let i = 0; i < s.length; i++) { const cp = s.charCodeAt(i); if (isCJK(cp) || isLetter(cp)) { hasAnyLetter = true; break; } } if (!hasAnyLetter || /^[A-Z]$/.test(s)) return true; return false; } function rectEl(el) { if (!el || el.nodeType !== 1) return null; const r = el.getBoundingClientRect(); if (!r || (r.width === 0 && r.height === 0)) return { top: 1e9, bottom: 0, left: 0, right: 0, width: 0, height: 0 }; return r; } function simpleRichBlock(el) { if (!el || !el.querySelector) return false; if (el.querySelector('pre,code,kbd,samp,var,script,style,textarea,input,select,button,svg,canvas,math,table')) return false; const forbidden = el.querySelectorAll('*:not(a,span,b,strong,i,em,u,mark,small,sub,sup,br,ul,ol,li,nav,aside,header,footer)'); return forbidden.length === 0; } function collectShadowRoots(root) { const roots = []; const all = root.querySelectorAll('*'); for (const el of all) { if (el.shadowRoot) { roots.push(el.shadowRoot); roots.push(...collectShadowRoots(el.shadowRoot)); } } return roots; } let pendingWrites = [], rafId = null; function writeUnit(unit, dst) { if (!unit) return; if (unit.type === 'html') { if (unit.el?.isConnected) unit.el.innerHTML = dst; } else if (unit.type === 'el') { if (unit.el?.isConnected) unit.el.textContent = dst; } else if (unit.type === 'attr') { if (unit.el?.isConnected) unit.el.setAttribute(unit.attr, dst); } else { if (unit.node?.parentNode) unit.node.nodeValue = unit.head + dst + unit.tail; } } function scheduleWrite(unit, dst) { pendingWrites.push({ unit, dst }); if (!rafId) { rafId = requestAnimationFrame(() => { const writes = pendingWrites; pendingWrites = []; rafId = null; for (const w of writes) writeUnit(w.unit, w.dst); }); } } function collectUnits() { let cacheHits = 0; const list = []; const processedEls = new Set(); const processedNodes = new WeakSet(); function walkTextNodes(root) { const w = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false); let n; while ((n = w.nextNode())) { if (n.parentElement?.closest('pre,code,kbd,samp,var')) continue; if (processedNodes.has(n)) continue; const raw = n.nodeValue || ''; const text = raw.trim(); if (!should(text)) continue; const r = rectEl(n.parentElement); if (!r) continue; const trimmedStart = raw.trimStart(); const head = raw.slice(0, raw.length - trimmedStart.length); const trimmedEnd = raw.trimEnd(); const tail = raw.slice(trimmedEnd.length); if (cache[text]) { // 立即写入已缓存内容,恢复初版的速度感 writeUnit({ type: 'node', node: n, raw, text, head, tail, top: r.top }, cache[text]); cacheHits++; continue; } list.push({ type: 'node', node: n, raw, text, head, tail, top: r.top }); processedNodes.add(n); if (list.length >= MAX_UNITS) return; } } function walkAttrs(root) { const ATTR_NAMES = ['title', 'placeholder', 'aria-label']; const all = root.querySelectorAll('*'); for (const el of all) { for (const attr of ATTR_NAMES) { const val = (el.getAttribute(attr) || '').trim(); if (!val || !should(val)) continue; if (cache[val]) { // 属性立即写入 el.setAttribute(attr, cache[val]); cacheHits++; continue; } list.push({ type: 'attr', el, attr, text: val, top: 0 }); if (list.length >= MAX_UNITS) return; } if (list.length >= MAX_UNITS) return; } } const blocks = document.querySelectorAll('p,blockquote,dd,figcaption,summary,h1,h2,h3,h4,h5,h6'); for (const el of blocks) { if (processedEls.has(el)) continue; if (!simpleRichBlock(el)) continue; const trimmed = el.textContent.trim(); if (!trimmed || trimmed.length > 1600) continue; if (!should(trimmed) && countLetters(trimmed, true) < 2) continue; const html = el.innerHTML.trim(); const key = htmlKey(html); if (cache[key]) { const r = rectEl(el); if (r) { // 立即写入已缓存的 HTML 块 writeUnit({ type: 'html', el, text: key, top: r.top }, cache[key]); cacheHits++; processedEls.add(el); } continue; } const r = rectEl(el); if (!r) continue; processedEls.add(el); list.push({ type: 'html', el, text: key, top: r.top }); const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false); let node; while ((node = walker.nextNode())) processedNodes.add(node); if (list.length >= MAX_UNITS) break; } const elements = document.querySelectorAll('p,li,h1,h2,h3,h4,h5,h6,dt,dd,figcaption,summary,blockquote,a'); for (const el of elements) { if (processedEls.has(el)) continue; if (el.tagName === 'A' && el.children.length > 0) { if (el.querySelector('img,svg,canvas') || el.children.length > 3) continue; } else if (el.children.length > 0) continue; const trimmed = el.textContent.trim(); if (!should(trimmed) || trimmed.length > 1200) continue; if (cache[trimmed]) { const r = rectEl(el); if (r) { // 立即写入已缓存的纯文本元素 writeUnit({ type: 'el', el, text: trimmed, top: r.top }, cache[trimmed]); cacheHits++; processedEls.add(el); } continue; } const r = rectEl(el); if (!r) continue; processedEls.add(el); list.push({ type: 'el', el, text: trimmed, top: r.top }); if (list.length >= MAX_UNITS) break; } walkTextNodes(document.body); walkAttrs(document.body); const shadowRoots = collectShadowRoots(document.body); for (const shadow of shadowRoots) { walkTextNodes(shadow); walkAttrs(shadow); if (list.length >= MAX_UNITS) break; } return { list, cacheHits }; } function collect() { return new Promise(resolve => { const doCollect = () => { const result = collectUnits(); result.list.sort((a, b) => { const av = a.top >= 0 && a.top <= innerHeight ? 0 : 1; const bv = b.top >= 0 && b.top <= innerHeight ? 0 : 1; if (av !== bv) return av - bv; return Math.abs(a.top - innerHeight / 2) - Math.abs(b.top - innerHeight / 2); }); const out = []; let chars = 0; for (const unit of result.list) { out.push(unit); chars += unit.text.length; if (out.length >= MAX_UNITS || chars >= MAX_CHARS) break; } resolve({ list: out, cacheHits: result.cacheHits }); }; if (typeof requestIdleCallback === 'function') { requestIdleCallback(doCollect, { timeout: 200 }); } else { setTimeout(doCollect, 0); } }); } function group(list) { const map = {}; const texts = []; for (const unit of list) { const t = unit.text; if (!map[t]) { map[t] = []; texts.push(t); } map[t].push(unit); } return { map, texts }; } function chunks(texts) { const out = []; let cur = [], len = 0, mode = ''; for (const t of texts) { const nextMode = t.startsWith(HTML_PREFIX) ? 'html' : 'plain'; if (cur.length && (nextMode !== mode || cur.length >= CHUNK_NODES || len + (nextMode === 'html' ? stripHtmlKey(t).length : t.length) > CHUNK_CHARS)) { out.push(cur); cur = []; len = 0; mode = ''; } if (!cur.length) mode = nextMode; cur.push(t); len += nextMode === 'html' ? stripHtmlKey(t).length : t.length; } if (cur.length) out.push(cur); return out; } function traceId() { return Date.now() + '-' + Math.random().toString(16).slice(2); } function trans(arr, retry, cb) { const isHtml = arr.length && arr[0].startsWith(HTML_PREFIX); const url = isHtml ? API_HTML : API; const body = arr.map(t => ({ Text: isHtml ? stripHtmlKey(t) : t })); req({ method: 'POST', url, headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token, 'X-ClientTraceId': traceId() }, data: JSON.stringify(body) }, r => { if ((r.status === 401 || r.status === 403) && retry) { token = ''; tokenTime = 0; getToken(err => { if (err) { cb(err); return; } trans(arr, false, cb); }); return; } if (r.status < 200 || r.status >= 300) { cb('翻译请求失败'); return; } let data; try { data = JSON.parse(r.responseText); } catch (e) { cb('返回格式错误'); return; } if (!Array.isArray(data)) { cb('返回格式错误'); return; } for (let i = 0; i < arr.length; i++) { const dst = data[i]?.translations?.[0]?.text; if (dst) setCache(arr[i], dst); } cb(''); }, e => { cb(e); }); } function apply(g) { let n = 0; for (const src in g.map) { const dst = cache[src]; if (!dst) continue; for (const unit of g.map[src]) { scheduleWrite(unit, dst); n++; } } return n; } function getToken(cb) { if (token && Date.now() - tokenTime < 8 * 60 * 1000) { cb(''); return; } req({ method: 'GET', url: AUTH }, r => { const t = String(r.responseText || '').trim(); if (r.status < 200 || r.status >= 300 || !t) { cb('获取令牌失败'); return; } token = t; tokenTime = Date.now(); cb(''); }, cb); } async function startManual(callback) { if (running) { callback?.(false, '翻译进行中'); return; } running = true; translateInProgress = true; const { list, cacheHits } = await collect(); if (!list.length) { running = false; translateInProgress = false; if (cacheHits > 0) { isTranslated = true; everTranslated = true; callback?.(true, cacheHits); } else { callback?.(false, '没有需要翻译的内容'); } return; } const g = group(list); const cs = chunks(g.texts); let index = 0, active = 0, changed = cacheHits; const total = cs.length; let done = 0; updateBtnProgress(0, total); function doneTranslate() { const finalize = () => { running = false; translateInProgress = false; if (changed > cacheHits) { isTranslated = true; everTranslated = true; callback?.(true, changed); } else { callback?.(false, '翻译失败'); } }; if (rafId) requestAnimationFrame(finalize); else finalize(); } getToken(err => { if (err) { running = false; translateInProgress = false; callback?.(false, err); return; } function next() { if (!document.body) { running = false; translateInProgress = false; callback?.(false, '已停止'); return; } while (active < CONCURRENCY && index < cs.length) { active++; trans(cs[index++], true, e => { active--; done++; if (e) { if (index >= cs.length && active === 0) doneTranslate(); return; } changed += apply(g); updateBtnProgress(done, total); if (index >= cs.length && active === 0) doneTranslate(); else next(); }); } } next(); }); } function restoreManual() { if (running || translateInProgress) return; renderOriginalFromCache(); isTranslated = false; showRestore = false; } // ---------- 界面 ---------- function updateBtnProgress(num, total) { const btn = document.getElementById('ms-manual-trans-btn'); if (!btn) return; let pn = btn.querySelector('.ms-bp'); if (!pn) { pn = document.createElement('span'); pn.className = 'ms-bp'; btn.appendChild(pn); } pn.textContent = num + '/' + total; } function clearBtnProgress() { const btn = document.getElementById('ms-manual-trans-btn'); btn?.querySelector('.ms-bp')?.remove(); } let selBtn = null, selResult = null; function createSelectionUI() { if (selBtn) return; selBtn = document.createElement('button'); selBtn.id = 'ms-sel-trans-btn'; selBtn.textContent = '译'; Object.assign(selBtn.style, { position: 'fixed', display: 'none', zIndex: '999998', padding: '4px 10px', backgroundColor: '#4285f4', color: 'white', border: 'none', borderRadius: '4px' }); document.body.appendChild(selBtn); selResult = document.createElement('div'); selResult.id = 'ms-sel-trans-result'; Object.assign(selResult.style, { position: 'fixed', display: 'none', zIndex: '999998', padding: '10px 14px', backgroundColor: 'white', color: '#333', border: '1px solid #999', maxWidth: '420px' }); document.body.appendChild(selResult); } function getSelText() { const sel = window.getSelection(); return sel ? sel.toString().trim() : ''; } function showSelBtn(x, y) { if (!selBtn || !mod().selTranslate) return; const t = getSelText(); if (!t || t.length < 2 || ZH_RE.test(t)) { hideSelBtn(); return; } selBtn.style.left = x + 'px'; selBtn.style.top = y + 'px'; selBtn.style.display = 'block'; } function hideSelBtn() { if (selBtn) selBtn.style.display = 'none'; } function showSelResult(text, x, y) { if (!selResult) return; selResult.textContent = text || ''; selResult.style.left = x + 'px'; selResult.style.top = y + 'px'; selResult.style.display = 'block'; } function hideSelResult() { if (selResult) selResult.style.display = 'none'; } function translateSelection() { const text = getSelText(); if (!text) return; showSelResult('翻译中…', parseInt(selResult.style.left) || 100, parseInt(selResult.style.top) || 100); if (cache[text]) { selResult.textContent = cache[text]; return; } trans([text], true, err => { selResult.textContent = err ? '翻译失败' : (cache[text] || text); }); } function createButton() { if (document.getElementById('ms-manual-trans-btn')) return; // 容器 — 主按钮 + 禁止按钮作为一个整体 var box = document.createElement('div'); box.id = 'ms-btn-box'; Object.assign(box.style, { position: 'fixed', top: '50%', transform: 'translateY(-50%)', zIndex: '999999', display: 'flex', flexDirection: 'column', gap: '1px', left: '0px', transition: 'left 0.3s ease' }); const btn = document.createElement('button'); btn.id = 'ms-manual-trans-btn'; btn.textContent = '翻译'; Object.assign(btn.style, { fontSize: 'medium', padding: '10px 18px', backgroundColor: '#4285f4', color: 'white', border: 'none', borderRadius: '0 6px 0 0', cursor: 'pointer', fontFamily: 'sans-serif' }); var blockBtn = document.createElement('button'); blockBtn.id = 'ms-block-btn'; blockBtn.textContent = '禁止此站'; Object.assign(blockBtn.style, { fontSize: '12px', padding: '4px 10px', backgroundColor: '#f5f5f5', color: '#c62828', border: '1px solid #e0e0e0', borderTop: 'none', borderRadius: '0 0 6px 0', cursor: 'pointer', fontFamily: 'sans-serif', whiteSpace: 'nowrap' }); var hideLeft = '0px'; btn._isHovered = false; btn.addEventListener('click', () => { if (translateInProgress) return; if (showRestore) { restoreManual(); btn.textContent = '翻译'; btn.style.backgroundColor = '#4285f4'; syncPosition(); } else if (translateCount >= 2) { renderTranslationFromCache(); isTranslated = true; showRestore = true; btn.textContent = '恢复'; btn.style.backgroundColor = '#db4437'; syncPosition(); } else { if (!everTranslated) updateBtnProgress(0, 0); btn.textContent = '翻译中'; btn.style.backgroundColor = '#f0ad4e'; syncPosition(); startManual((success, result) => { clearBtnProgress(); if (success) { translateCount++; showRestore = true; btn.textContent = '恢复'; btn.style.backgroundColor = '#db4437'; } else { btn.textContent = '翻译'; btn.style.backgroundColor = '#4285f4'; console.error('翻译失败:', result); const tip = document.createElement('div'); tip.textContent = '翻译失败: ' + (result || '未知错误'); Object.assign(tip.style, { position: 'fixed', bottom: '90px', left: '20px', backgroundColor: 'rgba(0,0,0,0.7)', color: 'white', padding: '6px 12px', borderRadius: '6px', fontSize: '12px', zIndex: '999999' }); document.body.appendChild(tip); setTimeout(() => tip.remove(), 2000); } syncPosition(); }); } }); // 块按钮事件 blockBtn.addEventListener('mouseenter', function() { blockBtn.style.backgroundColor = '#e0e0e0'; }); blockBtn.addEventListener('mouseleave', function() { blockBtn.style.backgroundColor = '#f5f5f5'; }); blockBtn.addEventListener('click', function(e) { e.stopPropagation(); if (blockBtn._blocked) return; blockBtn._blocked = true; var host = location.hostname; var cur = (mod().blockedSites || '').split('\n').map(function(s) { return s.trim(); }).filter(Boolean); var wildcard = '*.' + host.split('.').slice(-2).join('.'); if (cur.some(function(b) { return host === b || (b.startsWith('*.') && host.endsWith(b.slice(2))); })) { blockBtn.textContent = '✅ 已禁止'; setTimeout(function() { blockBtn.textContent = '禁止此站'; blockBtn._blocked = false; }, 1500); return; } cur.push(wildcard); settings.modules.translate.blockedSites = cur.join('\n'); saveSettings(); blockBtn.textContent = '✅ 已禁止'; setTimeout(function() { blockBtn.textContent = '禁止此站'; blockBtn._blocked = false; }, 1500); }); box.appendChild(btn); box.appendChild(blockBtn); document.body.appendChild(box); var hideLeft = '0px'; function syncPosition() { if (showRestore || translateInProgress || (mod().btnMode === 'show' && everTranslated)) { box.style.left = '0px'; return; } box.style.left = btn._isHovered ? '0px' : hideLeft; } function updateHideLeft() { const showEdge = 10; hideLeft = -(box.offsetWidth - showEdge) + 'px'; syncPosition(); } requestAnimationFrame(function(){updateHideLeft();}); if (typeof ResizeObserver !== 'undefined') new ResizeObserver(function(){updateHideLeft();}).observe(box); box.addEventListener('mouseenter', function(){btn._isHovered = true; syncPosition();}); box.addEventListener('mouseleave', function(){btn._isHovered = false; syncPosition();}); } function initSelectionEvents() { document.addEventListener('mouseup', function(e) { if (e.target.id === 'ms-manual-trans-btn' || e.target.id === 'ms-sel-trans-btn' || selResult?.contains(e.target)) return; setTimeout(() => { if (!mod().selTranslate) { hideSelBtn(); return; } const text = getSelText(); if (!text || text.length < 2 || ZH_RE.test(text)) { hideSelBtn(); return; } const range = window.getSelection().getRangeAt(0); const rect = range.getBoundingClientRect(); if (rect && (rect.width > 0 || rect.height > 0)) showSelBtn(rect.right + 4, rect.top - 30); }, 10); }); document.addEventListener('mousedown', function(e) { if (e.target.id === 'ms-sel-trans-btn' || selResult?.contains(e.target)) return; hideSelBtn(); hideSelResult(); }); selBtn?.addEventListener('click', function(e) { e.stopPropagation(); const rect = selBtn.getBoundingClientRect(); showSelResult('翻译中…', rect.left, rect.bottom + 4); translateSelection(); }); } function matchShortcut(combo, e) { if (!combo) return false; const parts = combo.split('+'); const key = parts.pop(); const mods = { Ctrl: false, Alt: false, Shift: false }; for (const p of parts) mods[p] = true; if (mods.Ctrl !== e.ctrlKey || mods.Alt !== e.altKey || mods.Shift !== e.shiftKey) return false; if (/^[A-Z]$/.test(key)) return e.code === 'Key' + key; if (/^\d$/.test(key)) return e.code === 'Digit' + key; return e.code === key || e.key === key; } function initShortcuts() { document.addEventListener('keydown', function(e) { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) return; if (matchShortcut(mod().pageShortcut, e)) { e.preventDefault(); document.getElementById('ms-manual-trans-btn')?.click(); return; } if (matchShortcut(mod().selShortcut, e)) { if (!mod().selTranslate) return; e.preventDefault(); const text = getSelText(); if (text && text.length >= 2 && !ZH_RE.test(text)) { if (!selBtn) { createSelectionUI(); initSelectionEvents(); } selResult.textContent = '翻译中…'; selResult.style.display = 'block'; const rect = window.getSelection().getRangeAt(0).getBoundingClientRect(); selResult.style.left = rect.left + 'px'; selResult.style.top = (rect.bottom + 4) + 'px'; translateSelection(); } return; } }); } function tryAutoTranslate() { if (!mod().autoTranslate) return; const doit = () => { setTimeout(() => { const btn = document.getElementById('ms-manual-trans-btn'); if (btn && !isTranslated) btn.click(); }, 1500); }; if (document.readyState === 'complete') doit(); else window.addEventListener('load', doit); } function init() { if (!settings.pageEnabled.translate) return; var blocked = (mod().blockedSites || '').split('\n').map(function(s) { return s.trim(); }).filter(Boolean); for (var i = 0; i < blocked.length; i++) { if (location.hostname === blocked[i] || (blocked[i].startsWith('*.') && location.hostname.endsWith(blocked[i].slice(1)))) return; } if (!document.getElementById('ms-bi-style')) { var s = document.createElement('style'); s.id = 'ms-bi-style'; s.textContent = '.ms-bi-gap{color:#aaa}.ms-bi-trans{background:#fffbe6;border-radius:2px;padding:0 2px;color:#d4380d;font-weight:500}'; document.head.appendChild(s); } getToken(function(){}); createButton(); if (mod().selTranslate) { createSelectionUI(); initSelectionEvents(); } initShortcuts(); tryAutoTranslate(); } function destroy() { var bx = document.getElementById('ms-btn-box'); if (bx) bx.remove(); if (selBtn) { selBtn.remove(); selResult.remove(); selBtn = null; selResult = null; } if (isTranslated) renderOriginalFromCache(); isTranslated = false; translateInProgress = false; everTranslated = false; } function buildTranslatePage() { var m = mod(); var esc = function(v) { return String(v).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); }; function dual(key, t, f, val) { return ''; } return '\
⚙️ 翻译
' + dual('selTranslate','开启','关闭',m.selTranslate) + '
' + dual('autoTranslate','开启','关闭',m.autoTranslate) + '
自动翻译:页面加载完成后自动翻译全文。
\
🔘 翻译按钮
左侧边缘悬停显示翻译按钮。
\
⌨️ 快捷键
\
每行一个域名。禁止后翻译按钮不会出现在这些网站上。
'; } function bindPanelEvents() { document.querySelectorAll('.wp-trans-sel').forEach(function(el) { el.addEventListener('change', function() { var v = this.value === 'true' ? true : (this.value === 'false' ? false : this.value); settings.modules.translate[this.dataset.key] = v; saveSettings(); }); }); document.querySelectorAll('.wp-dual[data-key]').forEach(function(c) { c.querySelectorAll('.wp-dual-btn').forEach(function(b) { b.addEventListener('click', function() { var v = this.dataset.value === 'true'; settings.modules.translate[c.dataset.key] = v; saveSettings(); c.querySelectorAll('.wp-dual-btn').forEach(function(x) { x.classList.toggle('active', x.dataset.value === String(v)); }); }); }); }); document.querySelectorAll('.wp-trans-key').forEach(function(inp) { inp.addEventListener('focus', function() { this.value = ''; this._cap = true; }); inp.addEventListener('blur', function() { this._cap = false; }); }); var bt = document.getElementById('wp-trans-blocked'); if (bt) bt.addEventListener('input', function() { settings.modules.translate.blockedSites = this.value; saveSettings(); }); var bb = document.getElementById('wp-trans-block-site'); if (bb && bt) bb.addEventListener('click', function() { var h = location.hostname; var cur = bt.value; var lines = cur.split('\n').map(function(s) { return s.trim(); }).filter(Boolean); var newLines = lines.filter(function(b) { return h !== b && !(b.startsWith('*.') && h.endsWith(b.slice(1))); }); if (newLines.length < lines.length) { bt.value = newLines.join('\n'); settings.modules.translate.blockedSites = bt.value; saveSettings(); } }); } document.addEventListener('wp-module-toggle', function(e) { if (e.detail.page === 'translate') { if (e.detail.enabled) init(); else destroy(); } }); // ─── 导出 ─── return { init: init, destroy: destroy, buildPage: buildTranslatePage, bindPanelEvents: bindPanelEvents, toggleBilingual: function() { if (!isTranslated) return; if (currentMode === MODE_BI) renderTranslationFromCache(); else renderBilingual(); } }; })(); const BILI_MODULE = (function() { function mod() { return settings.modules.bilibili || {}; } // ─── IP 属地显示 ─── function startIP() { if (mod().showIp === false) return; var code = '(' + function() { var origFetch = window.fetch; window.fetch = function() { var url = (typeof arguments[0] === 'string') ? arguments[0] : (arguments[0] && arguments[0].url) || ''; if (url.indexOf('/x/v2/reply/wbi/main') === -1 && url.indexOf('/x/v2/reply/reply') === -1) return origFetch.apply(this, arguments); return origFetch.apply(this, arguments).then(function(r) { try { var clone = r.clone(); return clone.json().then(function(json) { if (!json || !json.data) return r; function process(c) { if (c && c.reply_control && c.reply_control.location && c.member) { var loc = c.reply_control.location.replace(/IP属地:?/ig, '').trim(); if (loc) c.member.uname += ' (' + loc + ')'; } if (c && c.replies) for (var ri = 0; ri < c.replies.length; ri++) process(c.replies[ri]); } var lists = [json.data.top_replies, json.data.replies, json.data.root]; for (var li = 0; li < lists.length; li++) { if (Array.isArray(lists[li])) for (var ci = 0; ci < lists[li].length; ci++) process(lists[li][ci]); } return new Response(JSON.stringify(json), { status: r.status, statusText: r.statusText, headers: r.headers }); }); } catch(e) { return r; } }); }; } + ')();'; var s = document.createElement('script'); s.textContent = code; (document.head || document.documentElement).appendChild(s); s.remove(); } // ─── 干净链接 ─── function startClean() { if (mod().cleanLinks === false) return; var P = ['utm_source','utm_medium','utm_campaign','utm_term','utm_content','utm_id','fbclid','gclid','msclkid','twclid','_ga','_gl','ref','source','via','share','from','spm']; var S = {}; // 合并外部来源规则 var ext = mod().cleanSources || {}; for (var url in ext) { var src = ext[url]; if (!src || !src.enabled || !src.data) continue; var providers = src.data.providers || src.data; for (var key in providers) { var rule = providers[key]; if (rule.rules) { if (!S[key]) S[key] = []; for (var ri = 0; ri < rule.rules.length; ri++) { var p = rule.rules[ri].replace(/^\(?:\?%3F\)?\?/,''); if (S[key].indexOf(p) === -1) S[key].push(p); } } } } function getParams(h) { var a = P.slice(); for (var k in S) { var ds = k.split(','); for (var i = 0; i < ds.length; i++) { if (h.indexOf(ds[i]) !== -1) { a = a.concat(S[k]); break; } } } return a; } function clean(u) { try { var o = new URL(u, location.href); var ps = getParams(o.hostname); var c = false; for (var i = 0; i < ps.length; i++) { if (o.searchParams.has(ps[i])) { o.searchParams.delete(ps[i]); c = true; } } return c ? o.toString() : u; } catch(e) { return u; } } // 拦截 history.pushState 和 replaceState(B站通过它加 ?vd_source=) var origPush = history.pushState; history.pushState = function(s, t, u) { return origPush.call(this, s, t, u ? clean(String(u)) : u); }; var origReplace = history.replaceState; history.replaceState = function(s, t, u) { return origReplace.call(this, s, t, u ? clean(String(u)) : u); }; // 定期检查地址栏(B站可能通过 location.href 跳转) var lastUrl = location.href; setInterval(function() { try { var cur = clean(location.href); if (cur !== location.href) { history.replaceState(null, '', cur); lastUrl = cur; } } catch(e) {} }, 500); // 清理所有链接 function cleanAll() { var links = document.querySelectorAll('a[href]'); for (var i = 0; i < links.length; i++) { try { var h = links[i].getAttribute('href'); var ch = clean(h); if (ch !== h) links[i].setAttribute('href', ch); } catch(e) {} } } cleanAll(); new MutationObserver(function() { cleanAll(); }).observe(document.body, { childList: true, subtree: true }); document.addEventListener('click', function(e) { var t = e.target; while (t && t.tagName !== 'A') t = t.parentNode; if (!t) return; var h = t.getAttribute('href'); var ch = clean(h); if (ch !== h) t.setAttribute('href', ch); }, true); } function loadCleanSource(url, cb) { if (!url) { cb('请输入 URL'); return; } function extractName(url) { var source = ''; if (url.indexOf('raw.githubusercontent.com') > -1) source = 'github - '; else if (url.indexOf('gitee.com') > -1) source = 'gitee - '; var m; m = url.match(/raw\.githubusercontent\.com\/([^/]+)\/([^/]+)\/(.+)/); if (m) return source + m[1] + '/' + m[2] + '/' + m[3]; m = url.match(/gitee\.com\/([^/]+)\/([^/]+)\/raw\/([^/]+)\/(.+)/); if (m) return source + m[1] + '/' + m[2] + '/' + m[4]; return source + url.split('/').pop(); } function onOk(t) { // 去掉开头的注释行(// 前缀的整行) var lines = t.split('\n'); if (lines.length > 1 && lines[0].trim().indexOf('//') === 0) { lines.shift(); t = lines.join('\n'); } try { var data = JSON.parse(t); } catch(e) { cb('JSON 解析失败'); return; } var srcs = settings.modules.bilibili.cleanSources || {}; srcs[url] = { name: extractName(url), enabled: true, data: data }; settings.modules.bilibili.cleanSources = srcs; saveSettings(); cb(null, Object.keys(data.providers || data).length); } if (typeof GM_xmlhttpRequest === 'function') { GM_xmlhttpRequest({ method: 'GET', url: url + (url.indexOf('?') > -1 ? '&' : '?') + '_t=' + Date.now(), onload: function(r) { if (r.status >= 200 && r.status < 300) onOk(r.responseText); else cb('HTTP ' + r.status); }, onerror: function() { cb('网络错误(检查 @connect 授权)'); }, ontimeout: function() { cb('请求超时'); }, timeout: 12000 }); } else if (typeof GM !== 'undefined' && GM.xmlHttpRequest) { GM.xmlHttpRequest({ method: 'GET', url: url + (url.indexOf('?') > -1 ? '&' : '?') + '_t=' + Date.now() }).then(function(r) { if (r.status >= 200 && r.status < 300) onOk(r.responseText); else cb('HTTP ' + r.status); }).catch(function() { cb('网络错误'); }); } else { fetch(url + (url.indexOf('?') > -1 ? '&' : '?') + '_t=' + Date.now()).then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); }).then(onOk).catch(function(e) { cb('加载失败: ' + (e.message || '网络错误')); }); } } function startDirectLink() { if (mod().directLink === false) return; var rules = [ [/link\.zhihu\.com.*target=/, function() { var m = location.href.match(/target=([^&]+)/); if (m) location.href = decodeURIComponent(m[1]); }], [/link\.csdn\.net.*target=/, function() { var m = location.href.match(/target=([^&]+)/); if (m) location.href = decodeURIComponent(m[1]); }], [/jianshu\.com\/go-wild.*url=/, function() { var m = location.href.match(/url=([^&]+)/); if (m) location.href = decodeURIComponent(m[1]); }], [/link\.juejin\.cn.*target=/, function() { var m = location.href.match(/target=([^&]+)/); if (m) location.href = decodeURIComponent(m[1]); }], [/mail\.qq\.com.*gourl=/, function() { var p = new URL(location.href).searchParams.get('gourl'); if (p) location.href = decodeURIComponent(p); }], [/\.google\.com\/url\?/, function() { var p = new URL(location.href).searchParams.get('url') || new URL(location.href).searchParams.get('q'); if (p) location.href = p; }], ]; for (var i = 0; i < rules.length; i++) { if (rules[i][0].test(location.href)) { rules[i][1](); return; } } } var _hlObserver = null; function removeHighlight() { if (_hlObserver) { _hlObserver.disconnect(); _hlObserver = null; } document.querySelectorAll('.wp-hl').forEach(function(el) { var p = el.parentNode; if (p) { var txt = document.createTextNode(el.textContent); p.replaceChild(txt, el); p.normalize(); } }); var st = document.getElementById('wp-hl-style'); if (st) st.remove(); } function startHighlight() { if (!mod().highlightEnabled) return; var kw = mod().highlightKeywords || ''; var clr = mod().highlightColor || '#FF0000'; if (!kw) return; var keywords = kw.split(',').map(function(s) { return s.trim(); }).filter(Boolean); if (!keywords.length) return; // 样式 var st = document.getElementById('wp-hl-style'); if (!st) { st = document.createElement('style'); st.id = 'wp-hl-style'; document.head.appendChild(st); } st.textContent = '.wp-hl{background:' + clr + '!important;border-radius:2px;padding:0 1px}'; // 高亮单个文本节点 function highlightNode(n) { if (!n || n.nodeType !== 3 || !n.parentNode) return; if (n.parentNode.closest('pre,code,kbd,samp,var,script,style,textarea,.wp-hl')) return; var text = n.nodeValue; for (var ki = 0; ki < keywords.length; ki++) { try { var kw = keywords[ki]; var re; if (kw.startsWith('/') && kw.lastIndexOf('/') > 0) { var lastSlash = kw.lastIndexOf('/'); var pattern = kw.substring(1, lastSlash); var flags = kw.substring(lastSlash + 1) || 'gi'; re = new RegExp('(' + pattern + ')', flags); } else { re = new RegExp('(' + kw.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') + ')', 'gi'); } if (re.test(text)) { re.lastIndex = 0; text = text.replace(re, '$1'); n.parentNode.replaceChild(function(){var s=document.createElement('span');s.innerHTML=text;return s;}(), n); return; } } catch(e) {} } } function walk() { var w = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false); var nodes = []; while (w.nextNode()) nodes.push(w.currentNode); for (var i = 0; i < nodes.length; i++) highlightNode(nodes[i]); } // 断开旧 observer if (_hlObserver) _hlObserver.disconnect(); walk(); _hlObserver = new MutationObserver(function() { walk(); }); _hlObserver.observe(document.body, { childList: true, subtree: true }); } var _preloadReady = false; function startPreload() { if (mod().preloadBoost === false) return; if (_preloadReady) return; _preloadReady = true; // 1. 图片预加载:将懒加载图片改为立即加载 document.querySelectorAll('img[loading="lazy"]').forEach(function(img) { img.loading = 'eager'; }); document.querySelectorAll('img').forEach(function(img) { var attrs = ['data-src','data-srcset','data-lazy-src','data-lazy-srcset','data-original','data-original-src','data-bg']; for (var ai = 0; ai < attrs.length; ai++) { if (img.hasAttribute(attrs[ai])) { var v = img.getAttribute(attrs[ai]); if (v && (img.src.indexOf('data:image') === 0 || img.src !== v || !img.complete)) { if (attrs[ai].indexOf('srcset') > -1) img.srcset = v; else img.src = v; } img.removeAttribute(attrs[ai]); break; } } }); // MutationObserver 监控新图片 new MutationObserver(function() { document.querySelectorAll('img[loading="lazy"]').forEach(function(img) { img.loading = 'eager'; }); document.querySelectorAll('img').forEach(function(img) { var attrs2 = ['data-src','data-srcset','data-lazy-src','data-original','data-bg']; for (var ai2 = 0; ai2 < attrs2.length; ai2++) { if (img.hasAttribute(attrs2[ai2])) { var v2 = img.getAttribute(attrs2[ai2]); if (v2) { if (attrs2[ai2].indexOf('srcset') > -1) img.srcset = v2; else img.src = v2; } img.removeAttribute(attrs2[ai2]); break; } } }); }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['loading','src','data-src'] }); // 2. 悬停预读 var delay = mod().hoverDelay || 100; var timer = null; document.addEventListener('mouseover', function(e) { var a = e.target.closest('a'); if (!a || !a.href || a.href.indexOf('http') !== 0) return; if (/login|logout|register|signin|signup|pay|download|delete/.test(a.pathname)) return; if (timer) { clearTimeout(timer); timer = null; } timer = setTimeout(function() { var link = document.querySelector('link[rel="prefetch"][href="' + a.href.replace(/"/g,'') + '"]'); if (!link) { var l = document.createElement('link'); l.rel = 'prefetch'; l.href = a.href; document.head.appendChild(l); } }, delay); }, { passive: true }); document.addEventListener('mouseout', function() { if (timer) { clearTimeout(timer); timer = null; } }, { passive: true }); } var colorStyleId = 'wp-color-mode'; var colorModes = { '默认': '', '夜间模式': 'html{filter:invert(0.88)hue-rotate(180deg)!important}img,video,canvas,svg,iframe,[style*="background-image"]{filter:invert(1)hue-rotate(180deg)!important}', '护眼模式': 'html{filter:sepia(0.35)hue-rotate(340deg)!important}', '灰度模式': 'html{filter:grayscale(1)!important}', '高对比度': 'html{filter:contrast(1.5)brightness(1.1)!important}', '复古模式': 'html{filter:sepia(0.4)contrast(0.9)brightness(0.95)!important}', }; function applyColorMode() { var mode = mod().colorMode || '默认'; var existing = document.getElementById(colorStyleId); if (existing) existing.remove(); if (mode === '默认') return; if (colorModes[mode]) { var s = document.createElement('style'); s.id = colorStyleId; s.textContent = colorModes[mode]; document.documentElement.appendChild(s); } } var _scrollbarStyleId = 'wp-sb-style'; function applyScrollbar() { var style = mod().scrollbarStyle || 'default'; var old = document.getElementById(_scrollbarStyleId); if (style === 'default') { if (old) old.remove(); return; } var thumb = mod().scrollbarColor || '#FF0000'; var css = '::-webkit-scrollbar{width:8px;height:8px}' + '::-webkit-scrollbar-thumb{background:' + thumb + ';border-radius:4px}' + '::-webkit-scrollbar-track{background:transparent}' + 'html{scrollbar-color:' + thumb + ' transparent;scrollbar-width:thin}'; var st = old || document.createElement('style'); st.id = _scrollbarStyleId; st.textContent = css; if (!old) document.head.appendChild(st); } function startScrollToTop() { if (!mod().backToTop) return; if (document.getElementById('wp-scroll-btns')) return; var div = document.createElement('div'); div.id = 'wp-scroll-btns'; div.innerHTML = '' + ''; div.style.cssText = 'position:fixed;bottom:20px;right:20px;z-index:9999;display:flex;flex-direction:column;gap:4px'; // 全屏时隐藏 function fsHandler() { div.style.display = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement ? 'none' : 'flex'; } document.addEventListener('fullscreenchange', fsHandler); document.addEventListener('webkitfullscreenchange', fsHandler); document.addEventListener('mozfullscreenchange', fsHandler); var btns = div.children; btns[0].addEventListener('click', function() { window.scrollTo({ top: 0, behavior: 'smooth' }); }); btns[1].addEventListener('click', function() { window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'smooth' }); }); document.body.appendChild(div); } var _spacingNodes = null; function applySpacing() { // 清除之前的修改 if (_spacingNodes) { for (var si = 0; si < _spacingNodes.length; si++) { var sn = _spacingNodes[si]; if (sn.el && sn.el.parentNode && sn.orig !== undefined) sn.el.nodeValue = sn.orig; } } _spacingNodes = null; if (!mod().spacingEnabled) return; var saved = []; var w = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false); var n, re1 = /([\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff])([^\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff\s\.\,\;\:\!\?\(\)\[\]\{\}\u3000-\u303f\uff00-\uffef\u2000-\u206f])/g, re2 = /([^\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff\s\.\,\;\:\!\?\(\)\[\]\{\}\u3000-\u303f\uff00-\uffef\u2000-\u206f])([\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff])/g; while ((n = w.nextNode())) { if (n.parentElement && n.parentElement.closest('pre,code,kbd,samp,var,script,style')) continue; var old = n.nodeValue; var nw = old.replace(re1, '$1 $2').replace(re2, '$1 $2'); if (nw !== old) { n.nodeValue = nw; saved.push({ el: n, orig: old }); } } _spacingNodes = saved; } var _unlockStyle = null, _unlockApplied = false; function applyUnlock() { if (_unlockApplied) return; _unlockApplied = true; if (!mod().unlockEnabled) { _unlockApplied = false; return; } // 拦截事件(capture 阶段) ['contextmenu','copy','cut','dragstart'].forEach(function(ev) { document.addEventListener(ev, function(e) { e.stopPropagation(); e.preventDefault(); }, true); }); // 覆盖 DOM0 事件 document.oncontextmenu = null; document.onselectstart = null; document.oncopy = null; // 覆盖 CSS user-select if (!_unlockStyle) { _unlockStyle = document.createElement('style'); _unlockStyle.id = 'wp-unlock-style'; document.head.appendChild(_unlockStyle); } _unlockStyle.textContent = '* { user-select: auto !important; -webkit-user-select: auto !important; } body, body * { -webkit-touch-callout: default !important; }'; // 移除内联 user-select 样式 document.querySelectorAll('[style*="user-select"],[style*="user-select"]').forEach(function(el) { el.style.setProperty('user-select', 'auto', 'important'); el.style.setProperty('-webkit-user-select', 'auto', 'important'); }); } function init() { applyColorMode(); applyScrollbar(); startDirectLink(); startPreload(); startHighlight(); startScrollToTop(); applySpacing(); applyUnlock(); var defaultUrls = ['https://gitee.com/qiuzongman/WebPlus/raw/master/ClearURLs/me.json','https://gitee.com/qiuzongman/WebPlus/raw/master/ClearURLs/clearURLs.json']; var srcs = mod().cleanSources || {}; var needLoad = false; if (mod().cleanLinks !== false) { for (var ui = 0; ui < defaultUrls.length; ui++) { if (!srcs[defaultUrls[ui]]) { needLoad = true; loadCleanSource(defaultUrls[ui], function() { startClean(); }); } } } if (!needLoad) startClean(); if (location.hostname.indexOf('bilibili.com') === -1) return; startIP(); } function refreshCleanSourceList(list) { if (!list) return; var srcs = mod().cleanSources || {}; var esc = function(v) { return String(v).replace(/&/g,'&').replace(//g,'>'); }; var html = ''; for (var url in srcs) { var src = srcs[url]; var count = src.data ? Object.keys(src.data.providers || src.data).length : 0; html += '
'; } if (!html) html = '
暂无外部规则。建议加载 ClearURLs 规则。
'; list.innerHTML = html; // 绑定事件 list.querySelectorAll('.wp-clean-source-enable').forEach(function(cb) { cb.addEventListener('change', function() { var srcs = settings.modules.bilibili.cleanSources || {}; if (srcs[this.dataset.url]) { srcs[this.dataset.url].enabled = this.checked; saveSettings(); } }); }); list.querySelectorAll('.wp-clean-source-remove').forEach(function(b) { b.addEventListener('click', function() { var srcs = settings.modules.bilibili.cleanSources || {}; delete srcs[this.dataset.url]; settings.modules.bilibili.cleanSources = srcs; saveSettings(); refreshCleanSourceList(list); }); }); } function bindPanelEvents() { function bindDual(id, key) { var c = document.getElementById(id); if (!c) return; c.querySelectorAll('.wp-dual-btn').forEach(function(b) { b.addEventListener('click', function() { var v = this.dataset.value === 'true'; settings.modules.bilibili[key] = v; saveSettings(); c.querySelectorAll('.wp-dual-btn').forEach(function(x) { x.classList.toggle('active', x.dataset.value === String(v)); }); }); }); } bindDual('wp-bili-ip', 'showIp'); bindDual('wp-direct-link', 'directLink'); bindDual('wp-preload', 'preloadBoost'); var hd = document.getElementById('wp-hover-delay'); if (hd) hd.addEventListener('change', function() { settings.modules.bilibili.hoverDelay = parseInt(this.value) || 100; saveSettings(); }); var cm = document.getElementById('wp-color-mode-select'); if (cm) cm.addEventListener('change', function() { settings.modules.bilibili.colorMode = this.value; saveSettings(); applyColorMode(); }); // 自定义高亮开关,需要即时生效 (function() { var c = document.getElementById('wp-hl-switch'); if (!c) return; c.querySelectorAll('.wp-dual-btn').forEach(function(b) { b.addEventListener('click', function() { var v = this.dataset.value === 'true'; settings.modules.bilibili.highlightEnabled = v; saveSettings(); c.querySelectorAll('.wp-dual-btn').forEach(function(x) { x.classList.toggle('active', x.dataset.value === String(v)); }); removeHighlight(); if (v) startHighlight(); }); }); })(); var hlKeywords = document.getElementById('wp-hl-keywords'); if (hlKeywords) hlKeywords.addEventListener('change', function() { settings.modules.bilibili.highlightKeywords = this.value; saveSettings(); removeHighlight(); startHighlight(); }); var hlColor = document.getElementById('wp-hl-color'); if (hlColor) hlColor.addEventListener('change', function() { settings.modules.bilibili.highlightColor = this.value; saveSettings(); removeHighlight(); startHighlight(); }); // 中英间距 (function() { var c = document.getElementById('wp-spacing'); if (c) c.querySelectorAll('.wp-dual-btn').forEach(function(b) { b.addEventListener('click', function() { var v = this.dataset.value === 'true'; settings.modules.bilibili.spacingEnabled = v; saveSettings(); c.querySelectorAll('.wp-dual-btn').forEach(function(x) { x.classList.toggle('active', x.dataset.value === String(v)); }); applySpacing(); }); }); })(); bindDual('wp-clean-links', 'cleanLinks'); var sc = document.getElementById('wp-scrollbar-color'); if (sc) sc.addEventListener('change', function() { settings.modules.bilibili.scrollbarColor = this.value; saveSettings(); applyScrollbar(); }); // 滚动条滑块颜色 (function() { var c = document.getElementById('wp-scrollbar-style'); if (c) c.querySelectorAll('.wp-dual-btn').forEach(function(b) { b.addEventListener('click', function() { var v = this.dataset.value; settings.modules.bilibili.scrollbarStyle = v; saveSettings(); c.querySelectorAll('.wp-dual-btn').forEach(function(x) { x.classList.toggle('active', x.dataset.value === v); }); var cp = document.getElementById('wp-scrollbar-color'); if (cp) cp.style.opacity = v === 'default' ? '0.4' : '1'; applyScrollbar(); }); }); })(); // 顶底按钮 (function() { var c = document.getElementById('wp-scrolltop-switch'); if (c) c.querySelectorAll('.wp-dual-btn').forEach(function(b) { b.addEventListener('click', function() { var v = this.dataset.value === 'true'; settings.modules.bilibili.backToTop = v; saveSettings(); c.querySelectorAll('.wp-dual-btn').forEach(function(x) { x.classList.toggle('active', x.dataset.value === String(v)); }); if (v) startScrollToTop(); else { var el = document.getElementById('wp-scroll-btns'); if (el) el.remove(); } }); }); })(); // 解除文本限制 (function() { var c = document.getElementById('wp-unlock'); if (c) c.querySelectorAll('.wp-dual-btn').forEach(function(b) { b.addEventListener('click', function() { var v = this.dataset.value === 'true'; settings.modules.bilibili.unlockEnabled = v; saveSettings(); c.querySelectorAll('.wp-dual-btn').forEach(function(x) { x.classList.toggle('active', x.dataset.value === String(v)); }); if (v) applyUnlock(); else { _unlockApplied = false; var us = document.getElementById('wp-unlock-style'); if (us) us.remove(); } }); }); })(); // 加载外部清洗规则 var cleanLoadBtn = document.getElementById('wp-clean-load'); var cleanUrlInput = document.getElementById('wp-clean-source-url'); var cleanList = document.getElementById('wp-clean-source-list'); if (cleanLoadBtn && cleanUrlInput) { cleanLoadBtn.addEventListener('click', function() { var url = cleanUrlInput.value.trim(); if (!url) return; cleanLoadBtn.disabled = true; cleanLoadBtn.textContent = '…'; loadCleanSource(url, function(err, count) { cleanLoadBtn.disabled = false; cleanLoadBtn.textContent = '加载'; if (err) { alert(err); return; } refreshCleanSourceList(cleanList); }); }); } if (cleanList) refreshCleanSourceList(cleanList); } document.addEventListener('wp-panel-open', function() { bindPanelEvents(); }); return { init: init, destroy: function() {}, buildPage: function(){return'';}, bindPanelEvents: bindPanelEvents, loadCleanSource: loadCleanSource }; })(); // =================================================================== var _origBuild = buildPageContent; var _esc = function(v) { return String(v).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); }; buildPageContent = function(pageId) { if (pageId === 'translate') return TRANS_MODULE.buildPage(); if (pageId === 'other') { var m = settings.modules.bilibili || {}; return '\
\ 📺 B站\
\
拦截评论 API 响应,在用户名后显示 IP 属地。
\
\
\ 🔓 解除文本限制\
\
解除禁止复制、选择文本、右键菜单的限制。
\
'; } if (pageId === 'visual') { var m = settings.modules.bilibili || {}; return '\
\ 🔍 关键词高亮\
\
\
\
\
\ 🔤 文本混排间隔\
\
在中外文字/数字/符号之间自动添加空格,提升阅读体验。
\
\
\ 🎨 网页颜色\
\
\
\ 🖱️ 滚动条\
\
\
'; } if (pageId === 'speed') { var m = settings.modules.bilibili || {}; return '\
\ ⚡ 网页预加载\
\
ms
\
图片懒加载提前 + 鼠标悬停预读链接(点击即开)。
\
\
\ 🔗 链接直达\
\
绕过知乎等网站的中转页面,直达目标链接。
\
\
\ 🔗 链接净化\
\
清除 URL 中的跟踪参数。
\
载入外部规则(从 Gitee/GitHub):
\
\ \ \
\
\
'; } if (pageId === 'global') return buildGlobalPage(); if (pageId === 'about') { return '\
\
\

Web+ v1.0.1

\
邱宗满
\
qiuzongman@foxmail.com
\
MIT
\
Gitee
\
Reasonix + Deepseek
\
\
\
\ 📦 脚本推荐\
\
视频控制器
\
自动无缝翻页
\
Picviewer CE+
\
LinkSwift
\
GitHub搜索净化
\
GitHub高速下载
\
\
\
\ 🫶 支援我买 Token 继续改进代码\
\

微信

\ \

支付宝

\ \
\
'; } return _origBuild(pageId); }; function buildGlobalPage() { var esc = function(v) { return String(v).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); }; return `
⌨️ 快捷键
🌐 子页面开关
⚙️ 设置
将清空所有模块的配置,恢复为初始状态。此操作不可撤销。
`; } function matchShortcut(combo, e) { if (!combo) return false; var parts = combo.split('+'), key = parts.pop(), mods = { Ctrl: false, Alt: false, Shift: false }; parts.forEach(function(p) { mods[p] = true; }); if (mods.Ctrl !== e.ctrlKey || mods.Alt !== e.altKey || mods.Shift !== e.shiftKey) return false; if (/^[A-Z]$/.test(key)) return e.code === 'Key' + key; if (/^\d$/.test(key)) return e.code === 'Digit' + key; return e.code === key || e.key === key; } // =================================================================== // 初始化 // =================================================================== function init() { loadSettings(); // 设置入口 — 始终注册 if (typeof GM_registerMenuCommand === 'function') { GM_registerMenuCommand('设置', openSettings); } // 全局快捷键 — 打开设置面板 document.addEventListener('keydown', function(e) { if (e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable)) return; // 用户自定义快捷键 if (settings.openSettingsKey && matchShortcut(settings.openSettingsKey, e)) { e.preventDefault(); openSettings(); } // 备用快捷键 Ctrl+Shift+. 始终可用 if (e.ctrlKey && e.shiftKey && e.key === '.') { e.preventDefault(); openSettings(); } // 双语对照快捷键 var bk = settings.modules.translate && settings.modules.translate.bilingualKey; if (bk && matchShortcut(bk, e)) { e.preventDefault(); if (typeof TRANS_MODULE !== 'undefined') TRANS_MODULE.toggleBilingual(); } }); // 初始化各模块 TRANS_MODULE.init(); BILI_MODULE.init(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();