综合性网页体验增强脚本。模块化设计,按需启用,精准匹配,性能优先。
// ==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 '<div class="wp-demo-hint">功能待开发</div>';
}
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,'>').replace(/"/g,'"');
const navItems = PAGES.map((p, i) => `
<button class="wp-nav-btn" data-page="${p.id}" data-index="${i}">
<span class="wp-nav-icon">${p.icon}</span>
<span class="wp-nav-label">${p.label}</span>
</button>
`).join('');
const pageContents = PAGES.map(p => {
return `
<div class="wp-page" data-page-id="${p.id}" style="display:none">
<div class="wp-page-body">
${buildPageContent(p.id)}
</div>
</div>
`;
}).join('');
return `
<style>
/* ===================================================================
🎨 web+ 设计系统
=================================================================== */
#wp-settings-panel {
--wp-panel-width: 550px;
--wp-panel-max-w: 90vw;
--wp-panel-max-h: 85vh;
--wp-panel-h: 50vh;
--wp-bg: #fff;
--wp-border-clr: #ddd;
--wp-shadow: 0 8px 40px rgba(0,0,0,0.18);
--wp-text: #333;
--wp-text-secondary: #555;
--wp-text-muted: #888;
--wp-text-hint: #999;
--wp-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
--wp-fz-base: 13px;
--wp-fz-body: 13px;
--wp-fz-sm: 12px;
--wp-fz-note: 12px;
--wp-fz-nav-label: 12px;
--wp-fz-icon: 18px;
--wp-fz-title: 16px;
--wp-lh-tight: 1.2;
--wp-lh-base: 1.6;
--wp-fw-title: 600;
--wp-fw-active: 600;
--wp-gap-2: 2px;
--wp-gap-8: 8px;
--wp-gap-10: 10px;
--wp-gap-12: 12px;
--wp-gap-14: 14px;
--wp-pad-nav: 8px 0;
--wp-pad-nav-btn: 10px 4px 8px;
--wp-pad-page: 16px 20px;
--wp-pad-footer: 12px 20px;
--wp-pad-section: 14px 16px;
--wp-pad-input: 5px 10px;
--wp-pad-btn: 8px 18px;
--wp-mr-nav-btn: 0 6px;
--wp-mr-section-btm: 12px;
--wp-mr-row-btm: 8px;
--wp-mr-label-btm: 8px;
--wp-mr-header-btm: 14px;
--wp-mr-header-pad-btm: 10px;
--wp-mr-hint-top: 4px;
--wp-bdr-w: 1px;
--wp-bdr-style: solid;
--wp-bdr-dashed: dashed;
--wp-radius-panel: 12px;
--wp-radius-btn: 6px;
--wp-radius-section: 8px;
--wp-radius-input: 4px;
--wp-t-fast: 0.15s;
--wp-t-normal: 0.2s;
--wp-hover-brightness: 0.85;
--wp-nav-w: 88px;
--wp-nav-bg: #f5f7fa;
--wp-nav-bdr-clr: #e8ecf1;
--wp-nav-btn-hover-bg: #e3ecf7;
--wp-nav-btn-hover-clr: #000;
--wp-nav-btn-active-bg: #90caf9;
--wp-nav-btn-active-clr: #000;
--wp-nav-indicator-clr: #1565c0;
--wp-nav-indicator-w: 3px;
--wp-nav-indicator-h: 28px;
--wp-nav-indicator-left: -6px;
--wp-nav-indicator-rad: 0 2px 2px 0;
--wp-title-clr: #222;
--wp-header-bdr-clr: #eee;
--wp-body-clr: #555;
--wp-toggle-on: #4CAF50;
--wp-toggle-off: #ccc;
--wp-toggle-w: 36px;
--wp-toggle-h: 20px;
--wp-toggle-knob: 16px;
--wp-toggle-knob-top: 2px;
--wp-toggle-knob-left: 2px;
--wp-toggle-knob-shadow: 0 1px 3px rgba(0,0,0,0.2);
--wp-toggle-label-min-w: 3em;
--wp-btn-reset-bg: #ff9800;
--wp-btn-close-bg: #f44336;
--wp-section-bg: #f8f9fb;
--wp-section-bdr-clr: #d0d5dd;
--wp-input-bdr-clr: #ccc;
--wp-input-max-w: 200px;
--wp-input-label-min-w: 60px;
--wp-footer-bg: #fafbfc;
--wp-footer-bdr-clr: #e8ecf1;
all: initial;
position: fixed; top: 50%; left: 50%;
transform: translate(-50%, -50%);
z-index: 2147483647;
width: var(--wp-panel-width); max-width: var(--wp-panel-max-w);
height: var(--wp-panel-h); max-height: var(--wp-panel-max-h);
background: var(--wp-bg);
border: var(--wp-bdr-w) var(--wp-bdr-style) var(--wp-border-clr);
border-radius: var(--wp-radius-panel);
box-shadow: var(--wp-shadow);
font-family: var(--wp-font);
font-size: var(--wp-fz-base);
color: var(--wp-text);
display: flex;
flex-direction: column;
overflow: hidden;
pointer-events: auto;
box-sizing: border-box;
}
#wp-settings-panel * { box-sizing: border-box; }
.wp-body { display: flex; flex: 1; min-height: 0; }
/* ---- 导航 ---- */
.wp-nav {
width: var(--wp-nav-w); flex-shrink: 0;
background: var(--wp-nav-bg);
border-right: var(--wp-bdr-w) var(--wp-bdr-style) var(--wp-nav-bdr-clr);
padding: var(--wp-pad-nav);
display: flex; flex-direction: column; gap: var(--wp-gap-2); overflow-y: auto;
}
.wp-nav-btn {
display: flex; flex-direction: column; align-items: center; justify-content: center;
gap: var(--wp-gap-2); padding: var(--wp-pad-nav-btn); border: none;
background: transparent; cursor: pointer; margin: var(--wp-mr-nav-btn);
border-radius: var(--wp-radius-btn); color: var(--wp-text-secondary);
transition: all var(--wp-t-fast) ease; font-family: inherit;
font-size: var(--wp-fz-note); line-height: var(--wp-lh-tight); position: relative;
}
.wp-nav-btn:hover { background: var(--wp-nav-btn-hover-bg); color: var(--wp-nav-btn-hover-clr); }
.wp-nav-btn.active {
background: var(--wp-nav-btn-active-bg); color: var(--wp-nav-btn-active-clr);
font-weight: var(--wp-fw-active);
}
.wp-nav-btn.active::before {
content: ''; position: absolute; left: var(--wp-nav-indicator-left);
top: 50%; transform: translateY(-50%);
width: var(--wp-nav-indicator-w); height: var(--wp-nav-indicator-h);
background: var(--wp-nav-indicator-clr); border-radius: var(--wp-nav-indicator-rad);
}
.wp-nav-icon { font-size: var(--wp-fz-icon); line-height: 1; }
.wp-nav-label { font-size: var(--wp-fz-nav-label); line-height: var(--wp-lh-tight); white-space: nowrap; }
/* ---- 内容 ---- */
.wp-content { flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; padding: 0; }
.wp-page { flex: 1; display: flex; flex-direction: column; min-height: 0; padding: var(--wp-pad-page); overflow-y: auto; scrollbar-gutter: stable; }
/* 自定义滚动条 — 始终可见 */
.wp-page::-webkit-scrollbar { width: 7px; }
.wp-page::-webkit-scrollbar-track { background: transparent; }
.wp-page::-webkit-scrollbar-thumb {
background: #c0c8d4;
border-radius: 4px;
min-height: 40px;
}
.wp-page::-webkit-scrollbar-thumb:hover { background: #90a0b4; }
.wp-page-body { flex: 1; color: var(--wp-body-clr); font-size: var(--wp-fz-base); line-height: var(--wp-lh-base); }
/* ---- 开关 ---- */
.wp-toggle { display: inline-flex; align-items: center; gap: var(--wp-gap-8); cursor: pointer; user-select: none; }
.wp-toggle input { display: none; }
.wp-toggle-slider {
position: relative; width: var(--wp-toggle-w); height: var(--wp-toggle-h);
background: var(--wp-toggle-off); border-radius: calc(var(--wp-toggle-h) / 2);
transition: background var(--wp-t-normal);
}
.wp-toggle-slider::after {
content: ''; position: absolute;
top: var(--wp-toggle-knob-top); left: var(--wp-toggle-knob-left);
width: var(--wp-toggle-knob); height: var(--wp-toggle-knob);
background: #fff; border-radius: 50%; transition: transform var(--wp-t-normal);
box-shadow: var(--wp-toggle-knob-shadow);
}
.wp-toggle input:checked + .wp-toggle-slider { background: var(--wp-toggle-on); }
.wp-toggle input:checked + .wp-toggle-slider::after {
transform: translateX(calc(var(--wp-toggle-w) - var(--wp-toggle-knob) - 4px));
}
.wp-toggle-label { font-size: var(--wp-fz-sm); color: var(--wp-text-muted); min-width: var(--wp-toggle-label-min-w); }
.wp-toggle input:checked ~ .wp-toggle-label { color: var(--wp-toggle-on); }
/* ---- 底部 ---- */
.wp-footer {
display: flex; justify-content: flex-end; align-items: center;
gap: var(--wp-gap-10); padding: var(--wp-pad-footer);
border-top: var(--wp-bdr-w) var(--wp-bdr-style) var(--wp-footer-bdr-clr);
background: var(--wp-footer-bg);
}
.wp-btn {
padding: var(--wp-pad-btn); border: none; border-radius: var(--wp-radius-btn);
cursor: pointer; font-size: var(--wp-fz-base); font-family: inherit;
transition: filter var(--wp-t-fast);
}
.wp-btn:hover { filter: brightness(var(--wp-hover-brightness)); }
.wp-btn-reset { background: var(--wp-btn-reset-bg); color: #fff; margin-right: auto; }
.wp-btn-close { background: var(--wp-btn-close-bg); color: #fff; }
/* ---- 表单控件 ---- */
.wp-section {
background: var(--wp-section-bg);
border: var(--wp-bdr-w) var(--wp-bdr-dashed) var(--wp-section-bdr-clr);
border-radius: var(--wp-radius-section); padding: var(--wp-pad-section);
margin-bottom: var(--wp-mr-section-btm);
}
.wp-section-label {
font-size: var(--wp-fz-base); font-weight: normal; color: var(--wp-text);
margin-bottom: var(--wp-mr-label-btm); display: block;
}
.wp-row {
display: flex; align-items: center; gap: var(--wp-gap-10); margin-bottom: var(--wp-mr-row-btm);
}
.wp-row label { min-width: var(--wp-input-label-min-w); color: var(--wp-text-secondary); }
.wp-row select, .wp-row input[type="text"], .wp-row input[type="number"] {
padding: var(--wp-pad-input);
border: var(--wp-bdr-w) var(--wp-bdr-style) var(--wp-input-bdr-clr);
border-radius: var(--wp-radius-input); font-size: var(--wp-fz-base);
background: var(--wp-bg); flex: 1; max-width: var(--wp-input-max-w);
}
.wp-hint { font-size: var(--wp-fz-sm); color: var(--wp-text-hint); margin-top: var(--wp-mr-hint-top); }
/* ---- 快捷键捕获输入框 ---- */
.wp-row input.wp-key-input,
input.wp-key-input {
padding: var(--wp-pad-input);
border: var(--wp-bdr-w) var(--wp-bdr-style) #90caf9;
border-radius: var(--wp-radius-input); font-size: var(--wp-fz-base);
background: #90caf9; color: #000; text-align: center; cursor: pointer;
flex: 1; max-width: var(--wp-input-max-w);
transition: border-color var(--wp-t-fast), box-shadow var(--wp-t-fast);
}
.wp-row input.wp-key-input:focus,
input.wp-key-input:focus {
outline: none;
border-color: #1976d2;
box-shadow: 0 0 0 2px rgba(25,118,210,0.25);
background: #90caf9;
}
/* ---- 双选项按钮 ---- */
.wp-dual {
display: inline-flex; gap: 0;
flex: 1; max-width: var(--wp-input-max-w);
border-radius: var(--wp-radius-input);
overflow: hidden;
border: var(--wp-bdr-w) var(--wp-bdr-style) var(--wp-nav-btn-active-bg);
}
.wp-dual-btn {
flex: 1; padding: 5px 0;
border: none; cursor: pointer;
font-size: var(--wp-fz-sm); font-family: inherit;
background: #e3f2fd; color: #000;
transition: background var(--wp-t-fast), color var(--wp-t-fast);
}
.wp-dual-btn:first-child { border-right: var(--wp-bdr-w) var(--wp-bdr-style) var(--wp-nav-btn-active-bg); }
.wp-dual-btn.active {
background: #90caf9; color: #000;
}
</style>
<div class="wp-body">
<nav class="wp-nav">${navItems}</nav>
<div class="wp-content">${pageContents}</div>
</div>
<div class="wp-footer">
<button class="wp-btn wp-btn-close" id="wp-close">关闭</button>
</div>
`;
}
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,'<').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 = `<span class="ms-bi-orig">${item.origHtml}</span><span class="ms-bi-gap"> | </span><span class="ms-bi-trans">${item.transHtml}</span>`;
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 = `<span class="ms-bi-orig">${escHtml(item.orig)}</span><span class="ms-bi-gap"> | </span><span class="ms-bi-trans">${escHtml(item.trans)}</span>`;
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,'>').replace(/"/g,'"'); };
function dual(key, t, f, val) { return '<span class="wp-dual" data-key="' + key + '"><button type="button" class="wp-dual-btn' + (val ? ' active' : '') + '" data-value="true">' + t + '</button><button type="button" class="wp-dual-btn' + (val ? '' : ' active') + '" data-value="false">' + f + '</button></span>'; }
return '\
<div class="wp-section"><span class="wp-section-label">⚙️ 翻译</span><div class="wp-row"><label>划词翻译</label>' + dual('selTranslate','开启','关闭',m.selTranslate) + '</div><div class="wp-row"><label>自动翻译</label>' + dual('autoTranslate','开启','关闭',m.autoTranslate) + '</div><div class="wp-hint">自动翻译:页面加载完成后自动翻译全文。</div></div>\
<div class="wp-section"><span class="wp-section-label">🔘 翻译按钮</span><div class="wp-row"><label>按钮模式</label><select class="wp-trans-sel" data-key="btnMode" style="padding:5px 10px;border:1px solid #90caf9;border-radius:4px;background:#90caf9;color:#000;font-size:12px;cursor:pointer"><option value="hide"' + (m.btnMode === 'hide' ? ' selected' : '') + '>原文下自动隐藏(默认)</option><option value="show"' + (m.btnMode === 'show' ? ' selected' : '') + '>翻译后总是显示</option></select></div><div class="wp-hint">左侧边缘悬停显示翻译按钮。</div></div>\
<div class="wp-section"><span class="wp-section-label">⌨️ 快捷键</span><div class="wp-row"><label>页面翻译</label><input type="text" class="wp-key-input wp-trans-key" data-key="pageShortcut" value="' + esc(m.pageShortcut) + '" readonly placeholder="点击后按键"></div><div class="wp-row"><label>划词翻译</label><input type="text" class="wp-key-input wp-trans-key" data-key="selShortcut" value="' + esc(m.selShortcut) + '" readonly placeholder="点击后按键"></div><div class="wp-row"><label>双语对照</label><input type="text" class="wp-key-input wp-trans-key" data-key="bilingualKey" value="' + esc(m.bilingualKey) + '" readonly placeholder="点击后按键"></div></div>\
<div class="wp-section"><div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px"><span class="wp-section-label" style="margin-bottom:0">🚫 禁止站点</span><button id="wp-trans-block-site" style="padding:4px 12px;border:none;border-radius:4px;background:#90caf9;color:#000;cursor:pointer;font-size:12px">移除当前网站</button></div><textarea id="wp-trans-blocked" style="width:100%;min-height:50px;resize:vertical;padding:5px 10px;border:1px solid #ccc;border-radius:4px;font-size:13px;box-sizing:border-box">' + esc(m.blockedSites || '') + '</textarea><div class="wp-hint">每行一个域名。禁止后翻译按钮不会出现在这些网站上。</div></div>';
}
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, '<span class="wp-hl">$1</span>');
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 = '<button style="display:block;width:36px;height:36px;border:none;border-radius:6px;background:#90caf9;color:#000;font-size:16px;cursor:pointer;margin-bottom:4px;box-shadow:0 2px 6px rgba(0,0,0,0.15)">▲</button>' +
'<button style="display:block;width:36px;height:36px;border:none;border-radius:6px;background:#90caf9;color:#000;font-size:16px;cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,0.15)">▼</button>';
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,'<').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 += '<div style="display:flex;align-items:center;margin:2px 0;gap:4px"><label style="cursor:pointer;font-size:12px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"><input type="checkbox" class="wp-clean-source-enable" data-url="' + esc(url) + '"' + (src.enabled ? ' checked' : '') + '> ' + esc(src.name || url) + ' (' + count + ')</label> <button class="wp-clean-source-remove" data-url="' + esc(url) + '" style="background:none;border:none;color:#f44336;cursor:pointer;font-size:14px;padding:0 2px;flex-shrink:0">✕</button></div>';
}
if (!html) html = '<div style="font-size:12px;color:#888">暂无外部规则。建议加载 ClearURLs 规则。</div>';
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,'>').replace(/"/g,'"'); };
buildPageContent = function(pageId) {
if (pageId === 'translate') return TRANS_MODULE.buildPage();
if (pageId === 'other') {
var m = settings.modules.bilibili || {};
return '\
<div class="wp-section">\
<span class="wp-section-label">📺 B站</span>\
<div class="wp-row"><label>显示IP</label><span class="wp-dual" id="wp-bili-ip"><button type="button" class="wp-dual-btn' + (m.showIp ? ' active' : '') + '" data-value="true">开启</button><button type="button" class="wp-dual-btn' + (m.showIp ? '' : ' active') + '" data-value="false">关闭</button></span></div>\
<div class="wp-hint">拦截评论 API 响应,在用户名后显示 IP 属地。</div>\
</div>\
<div class="wp-section">\
<span class="wp-section-label">🔓 解除文本限制</span>\
<div class="wp-row"><label>开关</label><span class="wp-dual" id="wp-unlock"><button type="button" class="wp-dual-btn' + (m.unlockEnabled ? ' active' : '') + '" data-value="true">开启</button><button type="button" class="wp-dual-btn' + (m.unlockEnabled ? '' : ' active') + '" data-value="false">关闭</button></span></div>\
<div class="wp-hint">解除禁止复制、选择文本、右键菜单的限制。</div>\
</div>';
}
if (pageId === 'visual') {
var m = settings.modules.bilibili || {};
return '\
<div class="wp-section">\
<span class="wp-section-label">🔍 关键词高亮</span>\
<div class="wp-row"><label>开关</label><span class="wp-dual" id="wp-hl-switch"><button type="button" class="wp-dual-btn' + (m.highlightEnabled ? ' active' : '') + '" data-value="true">开启</button><button type="button" class="wp-dual-btn' + (m.highlightEnabled ? '' : ' active') + '" data-value="false">关闭</button></span></div>\
<div class="wp-row"><label>关键词</label><input type="text" id="wp-hl-keywords" value="' + _esc(m.highlightKeywords || '') + '" placeholder="用逗号分隔,支持正则 /pattern/i" style="flex:1;padding:4px 8px;border:1px solid #90caf9;border-radius:4px;font-size:12px"></div>\
<div class="wp-row"><label>颜色</label><input type="color" id="wp-hl-color" value="' + (m.highlightColor || '#FF0000') + '" style="width:40px;height:28px;padding:1px;border:1px solid #90caf9;border-radius:4px;cursor:pointer;background:none"></div>\
</div>\
<div class="wp-section">\
<span class="wp-section-label">🔤 文本混排间隔</span>\
<div class="wp-row"><label>开关</label><span class="wp-dual" id="wp-spacing"><button type="button" class="wp-dual-btn' + (m.spacingEnabled ? ' active' : '') + '" data-value="true">开启</button><button type="button" class="wp-dual-btn' + (m.spacingEnabled ? '' : ' active') + '" data-value="false">关闭</button></span></div>\
<div class="wp-hint">在中外文字/数字/符号之间自动添加空格,提升阅读体验。</div>\
</div>\
<div class="wp-section">\
<span class="wp-section-label">🎨 网页颜色</span>\
<div class="wp-row"><label>模式</label><select id="wp-color-mode-select" style="padding:5px 10px;border:1px solid #90caf9;border-radius:4px;background:#90caf9;color:#000;font-size:12px;cursor:pointer">\
<option ' + (m.colorMode === '默认' ? 'selected' : '') + '>默认</option>\
<option ' + (m.colorMode === '夜间模式' ? 'selected' : '') + '>夜间模式</option>\
<option ' + (m.colorMode === '护眼模式' ? 'selected' : '') + '>护眼模式</option>\
<option ' + (m.colorMode === '灰度模式' ? 'selected' : '') + '>灰度模式</option>\
<option ' + (m.colorMode === '高对比度' ? 'selected' : '') + '>高对比度</option>\
<option ' + (m.colorMode === '复古模式' ? 'selected' : '') + '>复古模式</option>\
</select></div>\
</div>\
<div class="wp-section">\
<span class="wp-section-label">🖱️ 滚动条</span>\
<div class="wp-row"><label>滑块颜色</label><span class="wp-dual" id="wp-scrollbar-style"><button type="button" class="wp-dual-btn' + (m.scrollbarStyle === 'default' ? ' active' : '') + '" data-value="default">默认</button><button type="button" class="wp-dual-btn' + (m.scrollbarStyle === 'custom' ? ' active' : '') + '" data-value="custom">自定义</button></span><input type="color" id="wp-scrollbar-color" value="' + (m.scrollbarColor || '#FF0000') + '" style="width:36px;height:26px;padding:1px;border:1px solid #90caf9;border-radius:4px;cursor:pointer;background:none' + (m.scrollbarStyle === 'default' ? ';opacity:0.4' : '') + '"></div>\
<div class="wp-row"><label>顶底按钮</label><span class="wp-dual" id="wp-scrolltop-switch"><button type="button" class="wp-dual-btn' + (m.backToTop ? ' active' : '') + '" data-value="true">开启</button><button type="button" class="wp-dual-btn' + (m.backToTop ? '' : ' active') + '" data-value="false">关闭</button></span></div>\
</div>';
}
if (pageId === 'speed') {
var m = settings.modules.bilibili || {};
return '\
<div class="wp-section">\
<span class="wp-section-label">⚡ 网页预加载</span>\
<div class="wp-row"><label>开关</label><span class="wp-dual" id="wp-preload"><button type="button" class="wp-dual-btn' + (m.preloadBoost ? ' active' : '') + '" data-value="true">开启</button><button type="button" class="wp-dual-btn' + (m.preloadBoost ? '' : ' active') + '" data-value="false">关闭</button></span></div>\
<div class="wp-row"><label>悬停预读</label><input type="number" id="wp-hover-delay" value="' + (m.hoverDelay || 65) + '" min="50" max="2000" step="10" style="width:80px;padding:4px 8px;border:1px solid #90caf9;border-radius:4px;background:#90caf9;color:#000;font-size:12px;text-align:center"> <span style="font-size:12px;color:#888">ms</span></div>\
<div class="wp-hint">图片懒加载提前 + 鼠标悬停预读链接(点击即开)。</div>\
</div>\
<div class="wp-section">\
<span class="wp-section-label">🔗 链接直达</span>\
<div class="wp-row"><label>开关</label><span class="wp-dual" id="wp-direct-link"><button type="button" class="wp-dual-btn' + (m.directLink ? ' active' : '') + '" data-value="true">开启</button><button type="button" class="wp-dual-btn' + (m.directLink ? '' : ' active') + '" data-value="false">关闭</button></span></div>\
<div class="wp-hint">绕过知乎等网站的中转页面,直达目标链接。</div>\
</div>\
<div class="wp-section">\
<span class="wp-section-label">🔗 链接净化</span>\
<div class="wp-row"><label>开关</label><span class="wp-dual" id="wp-clean-links"><button type="button" class="wp-dual-btn' + (m.cleanLinks ? ' active' : '') + '" data-value="true">开启</button><button type="button" class="wp-dual-btn' + (m.cleanLinks ? '' : ' active') + '" data-value="false">关闭</button></span></div>\
<div class="wp-hint">清除 URL 中的跟踪参数。</div>\
<div style="margin-top:6px;font-size:12px;color:#888">载入外部规则(从 Gitee/GitHub):</div>\
<div style="margin-top:4px">\
<input type="text" id="wp-clean-source-url" value="https://gitee.com/qiuzongman/WebPlus/raw/master/ClearURLs/me.json" placeholder="https://..." style="width:calc(100% - 60px);padding:4px 8px;border:1px solid #ccc;border-radius:4px;font-size:12px">\
<button id="wp-clean-load" style="padding:4px 12px;border:none;border-radius:4px;background:#90caf9;color:#000;cursor:pointer;font-size:12px;margin-left:4px">加载</button>\
</div>\
<div id="wp-clean-source-list" style="margin-top:4px;font-size:12px"></div>\
</div>';
}
if (pageId === 'global') return buildGlobalPage();
if (pageId === 'about') {
return '\
<div class="wp-section">\
<div style="margin-top:4px;line-height:1.3;font-size:14px">\
<p style="margin-bottom:8px"><strong>Web+ v1.0.1</strong></p>\
<div class="wp-row"><label style="min-width:80px">作者</label><span><a href="https://space.bilibili.com/423767625" target="_blank" style="color:#1565c0;text-decoration:none">邱宗满</a></span></div>\
<div class="wp-row"><label style="min-width:80px">邮箱</label><span>[email protected]</span></div>\
<div class="wp-row"><label style="min-width:80px">许可证</label><span>MIT</span></div>\
<div class="wp-row"><label style="min-width:80px">项目地址</label><span><a href="https://gitee.com/qiuzongman/WebPlus" target="_blank" style="color:#1565c0;text-decoration:none">Gitee</a></span></div>\
<div class="wp-row"><label style="min-width:80px">开发工具</label><span><a href="https://reasonix.io/" target="_blank" style="color:#1565c0;text-decoration:none">Reasonix</a> + <a href="https://www.deepseek.com/" target="_blank" style="color:#1565c0;text-decoration:none">Deepseek</a></span></div>\
</div>\
</div>\
<div class="wp-section">\
<span class="wp-section-label">📦 脚本推荐</span>\
<div style="margin-top:6px;line-height:2;font-size:13px">\
<div><a href="https://scriptcat.org/zh-CN/script-show-page/6583" target="_blank" style="color:#1565c0;text-decoration:none">视频控制器</a></div>\
<div><a href="https://greasyfork.org/zh-CN/scripts/419215-autopager" target="_blank" style="color:#1565c0;text-decoration:none">自动无缝翻页</a></div>\
<div><a href="https://greasyfork.org/zh-CN/scripts/24204-picviewer-ce" target="_blank" style="color:#1565c0;text-decoration:none">Picviewer CE+</a></div>\
<div><a href="https://scriptcat.org/zh-CN/script-show-page/1604" target="_blank" style="color:#1565c0;text-decoration:none">LinkSwift</a></div>\
<div><a href="https://greasyfork.org/zh-CN/scripts/473912-github%E6%90%9C%E7%B4%A2%E5%87%80%E5%8C%96" target="_blank" style="color:#1565c0;text-decoration:none">GitHub搜索净化</a></div>\
<div><a href="https://greasyfork.org/zh-CN/scripts/412245-github-enhancement-high-speed-download" target="_blank" style="color:#1565c0;text-decoration:none">GitHub高速下载</a></div>\
</div>\
</div>\
<div class="wp-section">\
<span class="wp-section-label">🫶 支援我买 Token 继续改进代码</span>\
<div style="margin-top:6px">\
<p style="font-size:13px;font-weight:bold;margin-bottom:4px">微信</p>\
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NTAiIGhlaWdodD0iNDUwIiBzaGFwZS1yZW5kZXJpbmc9ImNyaXNwRWRnZXMiIHZpZXdCb3g9IjAgMCA0NTAgNDUwIj4KICA8cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZmZmIi8+CiAgPHBhdGggZD0iTTAgMGg3MHYxMEgwem04MCAwaDEwdjEwSDgwem0zMCAwaDQwdjEwaC00MHptNTAgMGgyMHYxMGgtMjB6bTQwIDBoMTB2MjBoLTEwem0zMCAwaDIwdjIwaC0yMHptMzAgMGgyMHYxMGgtMjB6bTQwIDBoNDB2MTBoLTQwem02MCAwaDEwdjEwaC0xMHptMjAgMGg3MHYxMGgtNzB6TTAgMTBoMTB2NjBIMHptNjAgMGgxMHY2MEg2MHptNDAgMGg0MHYxMGgtNDB6bTUwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjEwaC0xMHptNDAgMGgyMHYxMGgtMjB6bTUwIDBoMTB2NDBoLTEwem05MCAwaDEwdjQwaC0xMHptMzAgMGgxMHY2MGgtMTB6bTYwIDBoMTB2NjBoLTEwek0yMCAyMGgzMHYzMEgyMHptODAgMGgxMHYxMGgtMTB6bTMwIDBoMTB2MjBoLTEwem01MCAwaDEwdjIwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTMwIDBoMjB2MTBoLTIwem01MCAwaDIwdjEwaC0yMHptMTEwIDBoMzB2MzBoLTMwek05MCAzMGgxMHYzMEg5MHptMzAgMGgxMHYyMGgtMTB6bTMwIDBoMTB2MzBoLTEwem0yMCAwaDEwdjIwaC0xMHptNjAgMGgxMHYyMGgtMTB6bTQwIDBoMjB2MTBoLTIwem00MCAwaDMwdjEwaC0zMHptNTAgMGgxMHYyMGgtMTB6TTgwIDQwaDEwdjMwSDgwem02MCAwaDEwdjMwaC0xMHptMjAgMGgxMHYxMGgtMTB6bTQwIDBoMzB2MTBoLTMwem00MCAwaDIwdjEwaC0yMHptNDAgMGgxMHYxMGgtMTB6bTQwIDBoMzB2MTBoLTMwek0xODAgNTBoMzB2MTBoLTMwem02MCAwaDEwdjgwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTMwIDBoNDB2MTBoLTQwek0xMCA2MGg1MHYxMEgxMHptOTAgMGgxMHYyMGgtMTB6bTIwIDBoMTB2NTBoLTEwem00MCAwaDEwdjMwaC0xMHptMjAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MzBoLTEwem0yMCAwaDEwdjEwaC0xMHptNDAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjMwaC0xMHptMjAgMGgxMHYzMGgtMTB6bTIwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjYwaC0xMHptMzAgMGg1MHYxMGgtNTB6TTkwIDcwaDEwdjEwSDkwem00MCAwaDEwdjEwaC0xMHptNjAgMGgxMHY3MGgtMTB6bTYwIDBoMTB2NjBoLTEwem00MCAwaDEwdjEwaC0xMHptNjAgMGgxMHYyMGgtMTB6TTEwIDgwaDcwdjEwSDEwem0xMzAgMGgyMHYxMGgtMjB6bTMwIDBoMjB2MTBoLTIwem00MCAwaDMwdjEwaC0zMHptNTAgMGgyMHYxMGgtMjB6bTEzMCAwaDIwdjEwaC0yMHptNTAgMGgxMHYyMGgtMTB6TTEwIDkwaDIwdjIwSDEwem00MCAwaDEwdjIwSDUwem0yMCAwaDEwdjEwSDcwem00MCAwaDEwdjcwaC0xMHptNDAgMGgxMHYyMGgtMTB6bTYwIDBoMjB2MTBoLTIwem03MCAwaDEwdjEwaC0xMHptMzAgMGgxMHYyMGgtMTB6bTIwIDBoMTB2MzBoLTEwem01MCAwaDEwdjEwaC0xMHptMjAgMGg0MHYxMGgtNDB6TTAgMTAwaDEwdjEwSDB6bTMwIDBoMTB2NzBIMzB6bTMwIDBoMTB2MTBINjB6bTIwIDBoMjB2MjBIODB6bTUwIDBoMjB2MTBoLTIwem0zMCAwaDIwdjEwaC0yMHptNDAgMGgxMHYyMGgtMTB6bTIwIDBoMTB2NDBoLTEwem00MCAwaDIwdjEwaC0yMHptMzAgMGgyMHYyMGgtMjB6bTUwIDBoMjB2MTBoLTIwem03MCAwaDEwdjEwaC0xMHptMjAgMGgxMHYxMGgtMTB6TTEwIDExMGgxMHYxMEgxMHptMzAgMGgxMHYzMEg0MHptNjAgMGgxMHYyMGgtMTB6bTMwIDBoMTB2MTBoLTEwem00MCAwaDEwdjE5MGgtMTB6bTYwIDBoMTB2MzBoLTEwem05MCAwaDEwdjEwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MTBoLTEwem0yMCAwaDIwdjEwaC0yMHptMzAgMGgxMHYzMGgtMTB6bTIwIDBoMTB2MTBoLTEwek0wIDEyMGgxMHYzMEgwem0yMCAwaDEwdjIwSDIwem00MCAwaDIwdjEwSDYwem05MCAwaDEwdjE4MGgtMTB6bTYwIDBoMTB2MjBoLTEwem04MCAwaDEwdjEwaC0xMHptOTAgMGgxMHYyMGgtMTB6bTMwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjEwaC0xMHpNNzAgMTMwaDEwdjEwSDcwem0yMCAwaDEwdjEwSDkwem03MCAwaDEwdjEwaC0xMHptMjAgMGgxMHYxOTBoLTEwem0yMCAwaDEwdjEwaC0xMHptNjAgMGgzMHYxMGgtMzB6bTQwIDBoMTB2NDBoLTEwem0yMCAwaDIwdjEwaC0yMHptMzAgMGgyMHYxMGgtMjB6bTUwIDBoMTB2MTBoLTEwek0xMCAxNDBoMTB2MTBIMTB6bTUwIDBoMTB2MTBINjB6bTIwIDBoMTB2MTBIODB6bTIwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjIwaC0xMHptMTQwIDBoMTB2MTcwaC0xMHptMzAgMGgxMHYxOTBoLTEwem0yMCAwaDEwdjEwaC0xMHptNDAgMGgxMHYyMGgtMTB6bTkwIDBoMTB2MTBoLTEwek0yMCAxNTBoMTB2MjBIMjB6bTIwIDBoMjB2MTBINDB6bTEyMCAwaDEwdjE1MGgtMTB6bTMwIDBoNzB2MTUwaC03MHptODAgMGgyMHYxNTBoLTIwem02MCAwaDEwdjIwaC0xMHptMzAgMGgyMHYxMGgtMjB6bTMwIDBoMjB2MTBoLTIwem0zMCAwaDEwdjEwaC0xMHpNMCAxNjBoMjB2MTBIMHptNTAgMGgyMHYxMEg1MHptOTAgMGgxMHYxMGgtMTB6bTE4MCAwaDEwdjEwaC0xMHptMjAgMGgxMHYzMGgtMTB6bTMwIDBoMTB2MzBoLTEwem0yMCAwaDEwdjIwaC0xMHptNDAgMGgxMHYxMGgtMTB6TTAgMTcwaDEwdjEwSDB6bTgwIDBoMTB2ODBIODB6bTMzMCAwaDIwdjEwaC0yMHpNMTAgMTgwaDIwdjIwSDEwem00MCAwaDIwdjEwSDUwem00MCAwaDEwdjEwSDkwem01MCAwaDEwdjEwaC0xMHptMTcwIDBoMTB2MzBoLTEwem00MCAwaDEwdjEwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTUwIDBoMTB2MTBoLTEwek0zMCAxOTBoMTB2MTBIMzB6bTIwIDBoMTB2MjBINTB6bTYwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjEwaC0xMHptMTcwIDBoMTB2NTBoLTEwem02MCAwaDEwdjYwaC0xMHptMzAgMGgyMHYyMGgtMjB6bTUwIDBoMTB2MjBoLTEwek0wIDIwMGgyMHYyMEgwem00MCAwaDEwdjUwSDQwem0yMCAwaDIwdjEwSDYwem02MCAwaDEwdjMwaC0xMHptMjAwIDBoMzB2MTBoLTMwem01MCAwaDIwdjEwaC0yMHpNMjAgMjEwaDEwdjIwSDIwem0xMTAgMGgxMHYxMGgtMTB6bTE5MCAwaDIwdjEwaC0yMHptODAgMGg0MHYxMGgtNDB6TTYwIDIyMGgxMHYxMEg2MHptMzAgMGgxMHY0MEg5MHptMjQwIDBoMzB2MTBoLTMwem01MCAwaDEwdjEwaC0xMHptMjAgMGgxMHYzMGgtMTB6bTIwIDBoMTB2MTBoLTEwek0wIDIzMGgxMHYzMEgwem0xMTAgMGgxMHYxMGgtMTB6bTMwIDBoMTB2MTBoLTEwem0xODAgMGgxMHYxMGgtMTB6bTMwIDBoMTB2MTBoLTEwem05MCAwaDEwdjEwaC0xMHpNMTAgMjQwaDIwdjEwSDEwem00MCAwaDMwdjEwSDUwem01MCAwaDEwdjIwaC0xMHptMjAgMGgxMHYyMGgtMTB6bTE5MCAwaDEwdjEwaC0xMHptMjAgMGgyMHYxMGgtMjB6bTQwIDBoMzB2MTBoLTMwem02MCAwaDEwdjQwaC0xMHpNMjAgMjUwaDEwdjEwSDIwem0zMCAwaDEwdjMwSDUwem02MCAwaDEwdjEwaC0xMHptMzAgMGgxMHYyMGgtMTB6bTE2MCAwaDEwdjEwaC0xMHptMjAgMGgxMHYxMGgtMTB6bTMwIDBoMTB2NDBoLTEwem0yMCAwaDIwdjEwaC0yMHptNDAgMGgyMHYzMGgtMjB6bTMwIDBoMTB2MTBoLTEwek00MCAyNjBoMTB2MzBINDB6bTIwIDBoMTB2MTBINjB6bTIwIDBoMTB2MTBIODB6bTUwIDBoMTB2NDBoLTEwem0xODAgMGgxMHYyMGgtMTB6bTMwIDBoMTB2MTBoLTEwem00MCAwaDEwdjEwaC0xMHpNMTAgMjcwaDMwdjEwSDEwem02MCAwaDEwdjEwSDcwem0yMCAwaDEwdjIwSDkwem0yMCAwaDIwdjEwaC0yMHptMjUwIDBoMjB2MjBoLTIwem04MCAwaDEwdjEwaC0xMHpNMTAgMjgwaDIwdjEwSDEwem01MCAwaDEwdjEwSDYwem00MCAwaDEwdjMwaC0xMHptMjAgMGgxMHYxMGgtMTB6bTIxMCAwaDEwdjEwaC0xMHptNjAgMGgxMHYzMGgtMTB6bTIwIDBoMTB2MjBoLTEwek0yMCAyOTBoMTB2MTBIMjB6bTUwIDBoMjB2MTBINzB6bTcwIDBoMTB2MzBoLTEwem0xNzAgMGgxMHYxMGgtMTB6bTMwIDBoMTB2MjBoLTEwem0zMCAwaDEwdjEwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MTBoLTEwek0xMCAzMDBoMTB2MTBIMTB6bTMwIDBoMzB2MTBINDB6bTQwIDBoMTB2MjBIODB6bTMwIDBoMjB2MTBoLTIwem0xMjAgMGgzMHYxMGgtMzB6bTQwIDBoMTB2NDBoLTEwem0zMCAwaDEwdjIwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTEwMCAwaDEwdjEwaC0xMHpNMCAzMTBoMTB2MzBIMHptMjAgMGgxMHYxMEgyMHptMjAgMGgyMHYxMEg0MHptNTAgMGgxMHYxMEg5MHptMzAgMGgyMHYxMGgtMjB6bTQwIDBoMjB2MjBoLTIwem0zMCAwaDIwdjEwaC0yMHptOTAgMGgxMHYxMGgtMTB6bTMwIDBoMjB2MTBoLTIwem00MCAwaDEwdjEwaC0xMHptMjAgMGgyMHYxMGgtMjB6bTQwIDBoMjB2MTBoLTIwek0xMCAzMjBoMTB2MjBIMTB6bTIwIDBoMTB2MTBIMzB6bTMwIDBoMjB2MTBINjB6bTE0MCAwaDIwdjMwaC0yMHptNjAgMGgxMHYzMGgtMTB6bTgwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjEwaC0xMHptNjAgMGgyMHYxMGgtMjB6TTQwIDMzMGgyMHYxMEg0MHptNDAgMGg0MHYxMEg4MHptNjAgMGgxMHY3MGgtMTB6bTMwIDBoMTB2MTBoLTEwem01MCAwaDIwdjEwaC0yMHptNjAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MTBoLTEwem0yMCAwaDIwdjEwaC0yMHptMzAgMGgxMHYxMGgtMTB6bTQwIDBoNDB2MTBoLTQwem01MCAwaDEwdjEwaC0xMHpNNDAgMzQwaDEwdjMwSDQwem0yMCAwaDEwdjEwSDYwem0zMCAwaDMwdjEwSDkwem0xNDAgMGgzMHYxMGgtMzB6bTgwIDBoMTB2MjBoLTEwem0zMCAwaDEwdjQwaC0xMHptNzAgMGgzMHYxMGgtMzB6TTEwIDM1MGgzMHYxMEgxMHptODAgMGgxMHYxMEg5MHptMjAgMGgzMHYxMGgtMzB6bTUwIDBoMzB2MTBoLTMwem00MCAwaDEwdjEwMGgtMTB6bTIwIDBoMzB2MjBoLTMwem01MCAwaDQwdjEwaC00MHptNTAgMGgxMHYzMGgtMTB6bTQwIDBoMTB2NjBoLTEwem01MCAwaDIwdjEwaC0yMHptMzAgMGgxMHYxMGgtMTB6TTAgMzYwaDEwdjEwSDB6bTMwIDBoMTB2MTBIMzB6bTMwIDBoMTB2MTBINjB6bTIwIDBoMTB2ODBIODB6bTIwIDBoMTB2MzBoLTEwem0yMCAwaDIwdjIwaC0yMHptMzAgMGgxMHYxMGgtMTB6bTQwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjEwaC0xMHptNDAgMGgxMHYyMGgtMTB6bTIwIDBoMjB2MjBoLTIwem0zMCAwaDEwdjEwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MjBoLTEwem0yMCAwaDQwdjEwaC00MHptNTAgMGgyMHYxMGgtMjB6TTkwIDM3MGgxMHYyMEg5MHptMjAgMGgxMHY0MGgtMTB6bTEzMCAwaDEwdjcwaC0xMHptMjAgMGgxMHY1MGgtMTB6bTMwIDBoMTB2MTBoLTEwem0xMTAgMGgzMHYxMGgtMzB6bTQwIDBoMTB2MTBoLTEwek0wIDM4MGg3MHYxMEgwem0xMzAgMGgxMHYxMGgtMTB6bTMwIDBoMTB2MzBoLTEwem02MCAwaDEwdjEwaC0xMHptODAgMGgyMHYyMGgtMjB6bTMwIDBoMTB2MzBoLTEwem01MCAwaDEwdjEwaC0xMHptMjAgMGgxMHY0MGgtMTB6bTIwIDBoMTB2MTBoLTEwek0wIDM5MGgxMHY2MEgwem02MCAwaDEwdjYwSDYwem02MCAwaDEwdjMwaC0xMHptMzAgMGgxMHYyMGgtMTB6bTQwIDBoMTB2MzBoLTEwem02MCAwaDEwdjMwaC0xMHptMjAgMGgxMHYyMGgtMTB6bTIwIDBoMTB2MjBoLTEwem02MCAwaDEwdjEwaC0xMHptODAgMGgyMHYxMGgtMjB6TTIwIDQwMGgzMHYzMEgyMHptNzAgMGgxMHY0MEg5MHptOTAgMGgxMHY0MGgtMTB6bTMwIDBoMzB2MTBoLTMwem05MCAwaDEwdjEwaC0xMHptNDAgMGgxMHYzMGgtMTB6bTMwIDBoMzB2MTBoLTMwem0tMjMwIDEwaDEwdjMwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTExMCAwaDEwdjEwaC0xMHptMzAgMGgyMHYxMGgtMjB6bTYwIDBoMTB2MjBoLTEwem00MCAwaDMwdjEwaC0zMHptLTMxMCAxMGgyMHYxMGgtMjB6bTUwIDBoMjB2MTBoLTIwem0xMjAgMGgxMHYyMGgtMTB6bTMwIDBoMjB2MTBoLTIwem02MCAwaDEwdjEwaC0xMHptMjAgMGgxMHYxMGgtMTB6bTQwIDBoMTB2MTBoLTEwem0tMzEwIDEwaDIwdjEwaC0yMHptNDAgMGgxMHYyMGgtMTB6bTIwIDBoMTB2MjBoLTEwem00MCAwaDEwdjEwaC0xMHptODAgMGgxMHYxMGgtMTB6bTIwIDBoMjB2MjBoLTIwem00MCAwaDEwdjIwaC0xMHptNTAgMGgxMHYxMGgtMTB6bTMwIDBoMjB2MTBoLTIwek0xMCA0NDBoNTB2MTBIMTB6bTkwIDBoMTB2MTBoLTEwem0yMCAwaDIwdjEwaC0yMHptNDAgMGgxMHYxMGgtMTB6bTMwIDBoMTB2MTBoLTEwem00MCAwaDEwdjEwaC0xMHptMjAgMGgyMHYxMGgtMjB6bTMwIDBoMTB2MTBoLTEwem01MCAwaDIwdjEwaC0yMHptMzAgMGgxMHYxMGgtMTB6bTcwIDBoMTB2MTBoLTEweiIvPgo8L3N2Zz4K" style="width:240px;height:240px;display:block;margin-bottom:16px">\
<p style="font-size:13px;font-weight:bold;margin-bottom:4px">支付宝</p>\
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MTAiIGhlaWdodD0iNDEwIiBzaGFwZS1yZW5kZXJpbmc9ImNyaXNwRWRnZXMiIHZpZXdCb3g9IjAgMCA0MTAgNDEwIj4KICA8cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZmZmIi8+CiAgPHBhdGggZD0iTTAgMGg3MHYxMEgwem05MCAwaDIwdjEwSDkwem00MCAwaDcwdjEwaC03MHptODAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MTBoLTEwem01MCAwaDIwdjEwaC0yMHptNjAgMGg3MHYxMGgtNzB6TTAgMTBoMTB2NjBIMHptNjAgMGgxMHY2MEg2MHptODAgMGgyMHYxMGgtMjB6bTMwIDBoMzB2MTBoLTMwem0xMzAgMGgzMHYxMGgtMzB6bTQwIDBoMTB2NjBoLTEwem02MCAwaDEwdjYwaC0xMHpNMjAgMjBoMzB2MzBIMjB6bTYwIDBoMjB2MjBIODB6bTQwIDBoMjB2MTBoLTIwem0zMCAwaDEwdjMwaC0xMHptMjAgMGgyMHYxMGgtMjB6bTMwIDBoMjB2MTBoLTIwem0zMCAwaDIwdjEwaC0yMHptNDAgMGgxMHYxMGgtMTB6bTQwIDBoMjB2MTBoLTIwem01MCAwaDMwdjMwaC0zMHpNMTEwIDMwaDEwdjIwaC0xMHptNzAgMGgzMHYxMGgtMzB6bTQwIDBoMjB2MTBoLTIwem04MCAwaDEwdjEwaC0xMHptMjAgMGgxMHYxMGgtMTB6TTEwMCA0MGgxMHYzMGgtMTB6bTMwIDBoMjB2MTBoLTIwem00MCAwaDMwdjEwaC0zMHptNjAgMGg0MHYxMGgtNDB6bTYwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjEwaC0xMHpNOTAgNTBoMTB2MTBIOTB6bTQwIDBoMTB2MTBoLTEwem0zMCAwaDIwdjEwaC0yMHptMzAgMGgzMHYxMGgtMzB6bTQwIDBoMzB2MTBoLTMwem01MCAwaDEwdjQwaC0xMHptMjAgMGgxMHYyMGgtMTB6TTEwIDYwaDUwdjEwSDEwem03MCAwaDEwdjEwSDgwem00MCAwaDEwdjMwaC0xMHptMjAgMGgxMHYyMGgtMTB6bTIwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjQwaC0xMHptMjAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjQwaC0xMHptMjAgMGgxMHYxMGgtMTB6bTYwIDBoMTB2MTBoLTEwem0zMCAwaDUwdjEwaC01MHpNMTEwIDcwaDEwdjIwaC0xMHptODAgMGgxMHY0MGgtMTB6bTIwIDBoMTB2MTBoLTEwem00MCAwaDEwdjEwaC0xMHpNMzAgODBoMjB2MTBIMzB6bTMwIDBoMjB2MTBINjB6bTMwIDBoMTB2NDBIOTB6bTYwIDBoMzB2MTBoLTMwem03MCAwaDEwdjEwaC0xMHptNDAgMGgyMHYxMGgtMjB6bTMwIDBoMTB2MjBoLTEwem0yMCAwaDIwdjEwaC0yMHptNjAgMGgyMHYxMGgtMjB6TTIwIDkwaDEwdjIwSDIwem0yMCAwaDEwdjEwSDQwem0xMDAgMGgyMHYzMGgtMjB6bTMwIDBoMTB2MTBoLTEwem0zMCAwaDEwdjIwaC0xMHptNTAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MjBoLTEwem00MCAwaDEwdjEwaC0xMHptNDAgMGgyMHYxMGgtMjB6bTUwIDBoMTB2MTBoLTEwek0xMCAxMDBoMTB2MjBIMTB6bTIwIDBoMTB2MjBIMzB6bTIwIDBoMjB2MTBINTB6bTMwIDBoMTB2NDBIODB6bTMwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjEwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTcwIDBoMTB2MTBoLTEwem03MCAwaDEwdjEwaC0xMHptMzAgMGgxMHY4MGgtMTB6bTMwIDBoMTB2MTBoLTEwem0yMCAwaDIwdjEwaC0yMHpNNDAgMTEwaDIwdjEwSDQwem04MCAwaDEwdjEwaC0xMHptNTAgMGgxMHYyMGgtMTB6bTQwIDBoMTB2NTBoLTEwem00MCAwaDIwdjEwaC0yMHptNDAgMGgxMHYxMGgtMTB6bTIwIDBoMjB2MTBoLTIwem0zMCAwaDIwdjIwaC0yMHptMzAgMGgxMHYxMGgtMTB6bTMwIDBoMTB2NTBoLTEwek02MCAxMjBoMjB2MTBINjB6bTQwIDBoMjB2MTBoLTIwem01MCAwaDIwdjEwaC0yMHptMzAgMGgxMHYxMGgtMTB6bTQwIDBoMjB2MTBoLTIwem00MCAwaDEwdjEwaC0xMHptNjAgMGgxMHYxMGgtMTB6bTcwIDBoMTB2MTBoLTEwek0yMCAxMzBoMzB2MTBIMjB6bTUwIDBoMTB2MzBINzB6bTIwIDBoMTB2MzBIOTB6bTIwIDBoMTB2MTBoLTEwem0yMCAwaDIwdjEwaC0yMHptMzAgMGgxMHYyMGgtMTB6bTMwIDBoMTB2MTBoLTEwem0zMCAwaDEwdjEwaC0xMHptNzAgMGgxMHYyMGgtMTB6bTIwIDBoMTB2MTBoLTEwem00MCAwaDQwdjEwaC00MHpNMzAgMTQwaDIwdjEwSDMwem0zMCAwaDEwdjEwSDYwem02MCAwaDIwdjIwaC0yMHptNTAgMGgyMHYxMGgtMjB6bTcwIDBoMTB2MTBoLTEwem0yMCAwaDIwdjEwaC0yMHptNDAgMGgxMHYyMGgtMTB6bTQwIDBoMjB2MTBoLTIwek0wIDE1MGgxMHYxMEgwem0yMCAwaDEwdjIwSDIwem02MCAwaDEwdjIwSDgwem0yMCAwaDIwdjEwaC0yMHptNTAgMGgxMHYzMGgtMTB6bTIwIDBoMTB2OTBoLTEwem0yMCAwaDIwdjEwaC0yMHptMzAgMGgyMHYxMGgtMjB6bTQwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjIwaC0xMHptMzAgMGgxMHY1MGgtMTB6bTUwIDBoNDB2MTBoLTQwek0xMCAxNjBoMTB2NDBIMTB6bTIwIDBoMTB2MzBIMzB6bTMwIDBoMTB2MTBINjB6bTUwIDBoMTB2MjBoLTEwem01MCAwaDEwdjEwaC0xMHptMzAgMGgxMHY4MGgtMTB6bTMwIDBoMTB2OTBoLTEwem0zMCAwaDEwdjYwaC0xMHptMjAgMGgxMHYzMGgtMTB6bTEwMCAwaDEwdjEwaC0xMHptMjAgMGgxMHYxMGgtMTB6TTAgMTcwaDEwdjEwSDB6bTQwIDBoMjB2MjBINDB6bTgwIDBoMzB2MTBoLTMwem02MCAwaDEwdjExMGgtMTB6bTIwIDBoMjB2ODBoLTIwem0zMCAwaDIwdjMwaC0yMHptMzAgMGgxMHYzMGgtMTB6bTQwIDBoMTB2MTBoLTEwem02MCAwaDEwdjQwaC0xMHptMjAgMGgxMHYxMGgtMTB6TTIwIDE4MGgxMHYxMEgyMHptNDAgMGg1MHYxMEg2MHptMTAwIDBoMTB2MzBoLTEwem0xMjAgMGgxMHYyMGgtMTB6bTQwIDBoMTB2MjBoLTEwem0zMCAwaDEwdjEwaC0xMHptMjAgMGgxMHYyMGgtMTB6bTMwIDBoMTB2MTBoLTEwek0wIDE5MGgxMHY2MEgwem00MCAwaDEwdjEwSDQwem03MCAwaDEwdjEwaC0xMHptMjAgMGgzMHYxMGgtMzB6bTE2MCAwaDIwdjEwaC0yMHptNDAgMGgyMHYxMGgtMjB6bTUwIDBoMTB2MjBoLTEwek0yMCAyMDBoMjB2MjBIMjB6bTMwIDBoMjB2MTBINTB6bTMwIDBoMTB2MjBIODB6bTIwIDBoMTB2MzBoLTEwem0yMCAwaDEwdjIwaC0xMHptMzAgMGgxMHYyMGgtMTB6bTgwIDBoMTB2NDBoLTEwem00MCAwaDEwdjcwaC0xMHptNzAgMGgxMHYxMGgtMTB6bTUwIDBoMjB2MTBoLTIwek0xMCAyMTBoMTB2MTBIMTB6bTMwIDBoMjB2MTBINDB6bTkwIDBoMjB2MjBoLTIwem0xMTAgMGgxMHYxMGgtMTB6bTQwIDBoMTB2MTBoLTEwem0yMCAwaDIwdjEwaC0yMHptNTAgMGgxMHYyMGgtMTB6bTIwIDBoMTB2MTBoLTEwek0zMCAyMjBoMTB2MTBIMzB6bTMwIDBoMTB2MTBINjB6bTMwIDBoMTB2NDBIOTB6bTcwIDBoMTB2MjBoLTEwem0xNDAgMGgxMHY3MGgtMTB6bTQwIDBoMTB2NDBoLTEwek03MCAyMzBoMTB2MTBINzB6bTUwIDBoMjB2MTBoLTIwem0xMjAgMGgzMHYxMGgtMzB6bTUwIDBoMTB2MTBoLTEwem0yMCAwaDMwdjEwaC0zMHptNjAgMGg0MHYxMGgtNDB6TTIwIDI0MGgyMHYxMEgyMHptMzAgMGgyMHYxMEg1MHptMzAgMGgxMHYxMEg4MHptNDAgMGgxMHYxMGgtMTB6bTMwIDBoMTB2MTBoLTEwem0xMzAgMGgxMHYyMGgtMTB6bTQwIDBoMTB2MzBoLTEwem0zMCAwaDEwdjIwaC0xMHptMjAgMGgyMHYxMGgtMjB6TTEwIDI1MGgxMHYxMEgxMHptMjAgMGgyMHYxMEgzMHptNzAgMGgyMHYxMGgtMjB6bTEzMCAwaDIwdjIwaC0yMHptNjAgMGgxMHYxMGgtMTB6bTcwIDBoMTB2MTBoLTEwem0yMCAwaDMwdjEwaC0zMHpNNTAgMjYwaDIwdjEwSDUwem05MCAwaDEwdjIwaC0xMHptODAgMGgxMHYyMGgtMTB6bTMwIDBoMjB2MTBoLTIwem02MCAwaDEwdjMwaC0xMHptNjAgMGgxMHYzMGgtMTB6bTIwIDBoMjB2MTBoLTIwek0wIDI3MGgzMHYxMEgwem00MCAwaDIwdjEwSDQwem02MCAwaDEwdjEwaC0xMHptOTAgMGgxMHY3MGgtMTB6bTIwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjIwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MTBoLTEwem03MCAwaDIwdjEwaC0yMHptMzAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MjBoLTEwek0wIDI4MGgxMHY1MEgwem00MCAwaDEwdjEwSDQwem0yMCAwaDIwdjEwSDYwem0zMCAwaDEwdjEwSDkwem0yMCAwaDMwdjEwaC0zMHptNDAgMGgzMHYyMGgtMzB6bTkwIDBoMTB2MjBoLTEwem0zMCAwaDEwdjEwaC0xMHptNzAgMGgyMHYxMGgtMjB6bTUwIDBoMTB2NDBoLTEwek0zMCAyOTBoMTB2MTBIMzB6bTIwIDBoMTB2MTBINTB6bTIwIDBoMTB2NDBINzB6bTMwIDBoMjB2MTBoLTIwem00MCAwaDEwdjIwaC0xMHptNjAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MjBoLTEwem0zMCAwaDIwdjMwaC0yMHptOTAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjUwaC0xMHpNMTAgMzAwaDEwdjMwSDEwem01MCAwaDEwdjEwSDYwem0yMCAwaDIwdjEwSDgwem0zMCAwaDEwdjIwaC0xMHptMjAgMGgxMHYxMGgtMTB6bTUwIDBoMTB2MjBoLTEwem0zMCAwaDEwdjIwaC0xMHptNzAgMGgyMHYxMGgtMjB6bTMwIDBoMTB2NTBoLTEwem0yMCAwaDEwdjEwaC0xMHptMjAgMGgxMHYzMGgtMTB6bTUwIDBoMTB2MzBoLTEwek00MCAzMTBoMjB2MTBINDB6bTQwIDBoMTB2MTBIODB6bTIwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjIwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTUwIDBoMTB2MTBoLTEwem0zMCAwaDIwdjEwaC0yMHptNDAgMGgyMHYxMGgtMjB6bTUwIDBoMTB2ODBoLTEwem0yMCAwaDEwdjIwaC0xMHptMzAgMGgxMHY3MGgtMTB6TTQwIDMyMGgxMHYxMEg0MHptMjAgMGgxMHYxMEg2MHptMzAgMGgxMHYxMEg5MHptMTMwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjEwaC0xMHptMjAgMGgxMHYzMGgtMTB6bTIwIDBoMjB2MTBoLTIwem01MCAwaDEwdjEwaC0xMHptMzAgMGgxMHY1MGgtMTB6TTgwIDMzMGgxMHYyMEg4MHptNjAgMGgyMHYxMGgtMjB6bTMwIDBoMTB2MjBoLTEwem0zMCAwaDIwdjIwaC0yMHptMzAgMGgxMHY1MGgtMTB6bTQwIDBoMTB2MzBoLTEwem0zMCAwaDEwdjIwaC0xMHptOTAgMGgxMHYxMGgtMTB6TTAgMzQwaDcwdjEwSDB6bTEwMCAwaDQwdjEwaC00MHptNTAgMGgxMHYyMGgtMTB6bTMwIDBoMTB2MTBoLTEwem00MCAwaDEwdjEwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTMwIDBoMjB2MTBoLTIwem02MCAwaDEwdjEwaC0xMHpNMCAzNTBoMTB2NjBIMHptNjAgMGgxMHY2MEg2MHptNTAgMGgyMHYxMGgtMjB6bTMwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjIwaC0xMHptNDAgMGgxMHYyMGgtMTB6bTkwIDBoMTB2NDBoLTEwem0xMDAgMGgyMHYxMGgtMjB6TTIwIDM2MGgzMHYzMEgyMHptNjAgMGgzMHYxMEg4MHptNTAgMGgxMHY0MGgtMTB6bTUwIDBoMjB2MTBoLTIwem00MCAwaDEwdjIwaC0xMHptMzAgMGgyMHYxMGgtMjB6bTUwIDBoMjB2MTBoLTIwem0zMCAwaDMwdjEwaC0zMHptNjAgMGgxMHYxMGgtMTB6TTgwIDM3MGgxMHYxMEg4MHptNzAgMGgxMHYxMGgtMTB6bTQwIDBoMTB2MTBoLTEwem01MCAwaDEwdjEwaC0xMHptMjAgMGgyMHYyMGgtMjB6bTkwIDBoMTB2MjBoLTEwem0zMCAwaDEwdjEwaC0xMHptMjAgMGgxMHYzMGgtMTB6bS0zMDAgMTBoMTB2MTBoLTEwem0yMCAwaDEwdjEwaC0xMHptNTAgMGgxMHYxMGgtMTB6bTMwIDBoMjB2MTBoLTIwem01MCAwaDEwdjEwaC0xMHptNjAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MjBoLTEwem0zMCAwaDEwdjMwaC0xMHptMzAgMGgxMHYxMGgtMTB6bS0yODAgMTBoMTB2MTBoLTEwem0zMCAwaDMwdjIwaC0zMHptNTAgMGgxMHYxMGgtMTB6bTcwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjEwaC0xMHptMjAgMGgxMHYxMGgtMTB6bTgwIDBoMTB2MjBoLTEwek0xMCA0MDBoNTB2MTBIMTB6bTE2MCAwaDEwdjEwaC0xMHptMzAgMGgzMHYxMGgtMzB6bTUwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjEwaC0xMHptNTAgMGgxMHYxMGgtMTB6bTUwIDBoMTB2MTBoLTEweiIvPgo8L3N2Zz4K" style="width:240px;height:240px;display:block">\
</div>\
</div>';
}
return _origBuild(pageId);
};
function buildGlobalPage() {
var esc = function(v) { return String(v).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); };
return `
<div class="wp-section">
<span class="wp-section-label">⌨️ 快捷键</span>
<div class="wp-row">
<label>进入设置</label>
<input type="text" class="wp-key-input wp-global-key" id="wp-openSettingsKey" value="${esc(settings.openSettingsKey)}" readonly placeholder="点击后按键">
</div>
</div>
<div class="wp-section">
<span class="wp-section-label">🌐 子页面开关</span>
<div class="wp-row">
<label>网页翻译</label>
<span class="wp-dual" id="wp-global-translate">
<button type="button" class="wp-dual-btn${settings.pageEnabled.translate !== false ? ' active' : ''}" data-value="true">开启</button>
<button type="button" class="wp-dual-btn${settings.pageEnabled.translate === false ? ' active' : ''}" data-value="false">关闭</button>
</span>
</div>
</div>
<div class="wp-section">
<span class="wp-section-label">⚙️ 设置</span>
<div style="margin-top:6px">
<button id="wp-update-rules" style="padding:8px 18px;border:none;border-radius:4px;background:#90caf9;color:#000;cursor:pointer;font-size:13px">🔄 更新所有外部链接</button>
<div id="wp-update-rules-status" class="wp-hint" style="margin-top:6px"></div>
</div>
<div style="margin-top:12px;padding-top:12px;border-top:1px solid var(--wp-section-bdr-clr, #d0d5dd)">
<button class="wp-btn wp-btn-reset" id="wp-reset-global" style="padding:8px 18px;border:none;border-radius:4px;background:#e53935;color:#fff;cursor:pointer;font-size:13px">⚠️ 所有设置恢复默认</button>
<div class="wp-hint" style="margin-top:4px">将清空所有模块的配置,恢复为初始状态。此操作不可撤销。</div>
</div>
</div>
`;
}
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();
}
})();