// ==UserScript== // @name 网页阅读助手 // @name:zh-CN 网页阅读助手 // @name:en Web Reading Tools // @description 增强功能:按 F8 显示/隐藏按钮,提供护眼模式、黑夜模式、繁简转换、目录导航、字体转换等功能。 // @description:zh-CN 增强功能:按 F8 显示/隐藏按钮,提供护眼模式、黑夜模式、繁简转换、目录导航、字体转换等功能 // @description:en Enhanced Features: Press F8 to show/hide buttons. Provides Eye-care Mode, Dark Mode, Chinese Conversion, TOC navigation, Font Switching and more. // @namespace https://www.runningcheese.com/bookmarklets // @author RunningCheese // @version 1.0 // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NzYiIGhlaWdodD0iNTEyIiB2aWV3Qm94PSIwIDAgNTc2IDUxMiI+Cgk8cGF0aCBmaWxsPSIjMDA3RkU5IiBkPSJNNTQyLjIyIDMyLjA1Yy01NC44IDMuMTEtMTYzLjcyIDE0LjQzLTIzMC45NiA1NS41OWMtNC42NCAyLjg0LTcuMjcgNy44OS03LjI3IDEzLjE3djM2My44N2MwIDExLjU1IDEyLjYzIDE4Ljg1IDIzLjI4IDEzLjQ5YzY5LjE4LTM0LjgyIDE2OS4yMy00NC4zMiAyMTguNy00Ni45MmMxNi44OS0uODkgMzAuMDItMTQuNDMgMzAuMDItMzAuNjZWNjIuNzVjLjAxLTE3LjcxLTE1LjM1LTMxLjc0LTMzLjc3LTMwLjdNMjY0LjczIDg3LjY0QzE5Ny41IDQ2LjQ4IDg4LjU4IDM1LjE3IDMzLjc4IDMyLjA1QzE1LjM2IDMxLjAxIDAgNDUuMDQgMCA2Mi43NVY0MDAuNmMwIDE2LjI0IDEzLjEzIDI5Ljc4IDMwLjAyIDMwLjY2YzQ5LjQ5IDIuNiAxNDkuNTkgMTIuMTEgMjE4Ljc3IDQ2Ljk1YzEwLjYyIDUuMzUgMjMuMjEtMS45NCAyMy4yMS0xMy40NlYxMDAuNjNjMC01LjI5LTIuNjItMTAuMTQtNy4yNy0xMi45OSIgLz4KPC9zdmc+ // @match *://*/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @license MIT // ==/UserScript== (function() { 'use strict'; let ttPolicy; if (window.trustedTypes && window.trustedTypes.createPolicy) { try { ttPolicy = window.trustedTypes.createPolicy('web-assistant-' + Math.random().toString(36).substring(2, 9), { createHTML: (string) => string }); } catch (e) { ttPolicy = { createHTML: (string) => string }; } } else { ttPolicy = { createHTML: (string) => string }; } // 简化的元素创建工具 const elements = { createAs(nodeType, config, appendTo) { const element = document.createElement(nodeType); if (config) { Object.entries(config).forEach(([key, value]) => { if (key === 'innerHTML') { element.innerHTML = ttPolicy.createHTML(value); } else { element[key] = value; } }); } if (appendTo) appendTo.appendChild(element); return element; }, getAs(selector) { return document.body.querySelector(selector); } }; // 添加CSS样式 const style = elements.createAs('style', { textContent: ` .web-icon-btn { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; border-radius: 50%; cursor: pointer; transition: background-color 0.3s, transform 0.2s; background-color: #fff; color: #555; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.12); text-decoration: none; } .web-icon-btn:hover { background-color: #d0d0d0; transform: scale(1.1); } .web-icon-btn svg { width: 20px; height: 20px; fill: #555; } ` }, document.head); // 网页助手主体 const webViewer = { buttonAdded: false, buttonCheckInterval: null, visible: false, menuCommandIds: [], buttonConfigs: [ { id: 'web-bookmarklet-btn', key: 'autoRunBtn1', label: '护眼模式' }, { id: 'web-bookmarklet-btn-2', key: 'autoRunBtn2', label: '黑夜模式' }, { id: 'web-bookmarklet-btn-3', key: 'autoRunBtn3', label: '繁简转换' }, { id: 'web-bookmarklet-btn-4', key: 'autoRunBtn4', label: '目录导航' }, { id: 'web-bookmarklet-btn-5', key: 'autoRunBtn5', label: '网页字体' }, ], runButtonAction(id) { switch (id) { case 'web-bookmarklet-btn': this.runEyesCareMode(); break; case 'web-bookmarklet-btn-2': this.runDarkMode(); break; case 'web-bookmarklet-btn-3': this.runCharConvert(); break; case 'web-bookmarklet-btn-4': this.runTOC(); break; case 'web-bookmarklet-btn-5': this.runFontChange(); break; } }, // 运行“护眼模式”功能 runEyesCareMode() { var night = function(w) { (function(d) { var css = 'html{filter: brightness(0.9);}'; var s = d.getElementsByTagName('style'); for (var i = 0, si; si = s[i]; i++) { if (si.innerHTML == css) { si.parentNode.removeChild(si); return; } } var heads = d.getElementsByTagName('head'); if (heads.length) { var node = d.createElement('style'); node.type = 'text/css'; node.appendChild(d.createTextNode(css)); heads[0].appendChild(node); } })(w.document); for (var i = 0, f; f = w.frames[i]; i++) { try { arguments.callee(f); } catch (e) {} } }; night(window); }, // 运行“黑夜模式”功能 runDarkMode() { var d = document.documentElement; if (d.hasAttribute('data-dark-mode')) { var e = document.querySelectorAll('[data-original-style]'); e.forEach(function(n) { n.style.cssText = n.getAttribute('data-original-style') || ''; n.removeAttribute('data-original-style'); }); d.removeAttribute('data-dark-mode'); } else { d.setAttribute('data-dark-mode', 'true'); function R(c) { var r = c[0], g = c[1], b = c[2], M = Math.max(Math.max(r, g), b), m = Math.min(Math.min(r, g), b), s = M + m, f = M - m, l = s / 2, h = 0, S = 0; if (M != m) { S = l <= .5 ? f / s : f / (2 - s); var Rd = r / 6 / f, Gd = g / 6 / f, Bd = b / 6 / f; h = r == M ? Gd - Bd : g == M ? 1 / 3 + Bd - Rd : 2 / 3 + Rd - Gd; if (h < 0) h += 1; if (h > 1) h -= 1; } return [h, S, l]; } function C(n, p) { var r = getComputedStyle(n, null).getPropertyValue(p); if (/rgb\((\d+),\s(\d+),\s(\d+)\)/.exec(r)) return [parseInt(RegExp.$1, 10) / 255, parseInt(RegExp.$2, 10) / 255, parseInt(RegExp.$3, 10) / 255]; return r; } function H(h) { if (h[2] < .1) return 'hsl(220,20%,15%)'; return 'hsl(' + Math.round(h[0] * 360) + ',' + Math.round(h[1] * 100) + '%,' + Math.round(h[2] * 100) + '%)'; } function I(n) { return n.tagName === 'BUTTON' || n.tagName === 'INPUT' || n.classList.contains('icon') || n.classList.contains('btn') || n.getAttribute('role') === 'button' || /\b(icon|btn|button)\b/i.test(n.className); } var P = ['color', 'background-color', 'border-left-color', 'border-right-color', 'border-top-color', 'border-bottom-color'], P2 = ['color', 'backgroundColor', 'borderLeftColor', 'borderRightColor', 'borderTopColor', 'borderBottomColor']; if (typeof C(d, 'background-color') == 'string') d.style.backgroundColor = 'white'; (function L(n) { if (n.nodeType == Node.ELEMENT_NODE) { if (!n.hasAttribute('data-original-style')) n.setAttribute('data-original-style', n.style.cssText); for (var i = 0, x; x = n.childNodes[i]; ++i) L(x); if (!I(n)) for (i = 0; x = P[i]; ++i) { var c = C(n, x); if (typeof c != 'string') { var h = R(c); h[2] = 1 - h[2]; n.style[P2[i]] = H(h); } } } })(d); } }, // 运行“目录导航”功能 runTOC() { var toc = function() { var selectors = ["article", "section", "main", "content", "#article", ".article", "#main", ".main", "#content", ".content", "#post", ".post", "body"]; var headingSelector = "h1, h2, h3, h4, h5, h6"; var shadow; var buildList = function() { var headings = []; var index = 0; for (var s = 0; s < selectors.length; s++) { if (document.querySelectorAll(selectors[s]).length) { document.querySelectorAll(selectors[s]).forEach(function(container) { container.querySelectorAll(headingSelector).forEach(function(el) { if (el.innerText.trim()) { headings.push(el); if (el.id) { el.dataset.headerMark = el.id; } else { el.dataset.headerMark = "toc_index_" + index++; el.id = el.dataset.headerMark; } } }); }); break; } } var html = ""; if (headings.length) { var prevLevel = +headings[0].tagName.replace(/H/g, ""); var indent = 0; headings.forEach(function(el) { var level = +el.tagName.replace(/H/g, ""); indent = indent + level - prevLevel; indent = indent < 0 ? 0 : indent; prevLevel = level; var span = document.createElement("span"); span.innerText = el.innerText; html += '
  • ' + span.innerHTML + '
  • '; }); } else { html += '
  • [空]
  • '; } return html; }; (function() { var host = document.createElement("div"); shadow = host.attachShadow({ mode: "open" }); host.id = "toc_menu_root"; shadow.innerHTML = '
    ▼ 目录导航
    '; document.querySelector("html").appendChild(host); var menuRoot = shadow.querySelector("#toc_menu_root"); var menuList = shadow.querySelector("#toc_menu_list"); var header = shadow.querySelector("#toc_header"); menuList.innerHTML = buildList(); var isDragging = false; var offsetX, offsetY; header.addEventListener("mousedown", function(e) { if (e.target.id === "close_button") return; isDragging = true; offsetX = e.clientX - e.currentTarget.getBoundingClientRect().left; offsetY = e.clientY - e.currentTarget.getBoundingClientRect().top; }); document.addEventListener("mousemove", function(e) { if (!isDragging) return; var x = e.clientX - offsetX; var y = e.clientY - offsetY; e.preventDefault(); menuRoot.style.left = x + "px"; menuRoot.style.top = y + "px"; menuRoot.style.right = "auto"; menuRoot.style.transform = "none"; }); document.addEventListener("mouseup", function() { isDragging = false; }); shadow.querySelector("#close_button").addEventListener("click", function() { host.remove(); }); document.addEventListener("keydown", function(evt) { if (evt.key === "Escape") { host.remove(); } }); })(); }; toc(); }, // 显示提示消息 showToast(message) { const toast = elements.createAs("div", { style: ` position: fixed; top: 12%; left: 50%; transform: translate(-50%, -50%); background-color: #ff2442; color: white; padding: 15px 20px; border-radius: 8px; font-size: 14px; font-weight: bold; z-index: 1000000000; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); `, textContent: message }, document.body); setTimeout(() => { if (document.body.contains(toast)) { document.body.removeChild(toast); } }, 3000); }, // 添加按钮到页面浮动容器 addButtons() { let buttonContainer = elements.getAs('#web-helper-buttons'); if (!buttonContainer) { buttonContainer = elements.createAs('div', { id: 'web-helper-buttons', style: ` position: fixed; top: 50%; right: 30px; transform: translateY(-50%); display: flex; flex-direction: column; align-items: center; gap: 15px; z-index: 999999; ` }, document.body); } buttonContainer.style.display = this.visible ? 'flex' : 'none'; // 创建小书签按钮 if (!elements.getAs('#web-bookmarklet-btn')) { elements.createAs('a', { id: 'web-bookmarklet-btn', href: 'javascript:void(0);', className: 'web-icon-btn', title: '护眼模式', innerHTML: '', onclick: () => { webViewer.runEyesCareMode(); } }, buttonContainer); } if (!elements.getAs('#web-bookmarklet-btn-2')) { elements.createAs('a', { id: 'web-bookmarklet-btn-2', href: 'javascript:void(0);', className: 'web-icon-btn', title: '黑夜模式', innerHTML: '', onclick: () => { webViewer.runDarkMode(); } }, buttonContainer); } if (!elements.getAs('#web-bookmarklet-btn-3')) { elements.createAs('a', { id: 'web-bookmarklet-btn-3', href: 'javascript:void(0);', className: 'web-icon-btn', title: '繁简转换', innerHTML: '', onclick: () => { webViewer.runCharConvert(); } }, buttonContainer); } if (!elements.getAs('#web-bookmarklet-btn-4')) { elements.createAs('a', { id: 'web-bookmarklet-btn-4', href: 'javascript:void(0);', className: 'web-icon-btn', title: '目录导航', innerHTML: '', onclick: () => { webViewer.runTOC(); } }, buttonContainer); } if (!elements.getAs('#web-bookmarklet-btn-5')) { elements.createAs('a', { id: 'web-bookmarklet-btn-5', href: 'javascript:void(0);', className: 'web-icon-btn', title: '网页字体', innerHTML: '', onclick: () => { webViewer.runFontChange(); } }, buttonContainer); } this.buttonAdded = true; }, runCharConvert() { javascript:(function(){var s = document.getElementById('tongwenlet_script');if(s != null){document.body.removeChild(s);s = document.createElement('script');s.language = 'javascript';s.type = 'text/javascript';if(window.lastTongwenType === 'cn'){s.src = 'https://gcore.jsdelivr.net/gh/runningcheese/Awesome-Bookmarklets/libs/bookmarklet_tw.js';window.lastTongwenType = 'tw';}else{s.src = 'https://gcore.jsdelivr.net/gh/runningcheese/Awesome-Bookmarklets/libs/bookmarklet_cn.js';window.lastTongwenType = 'cn';}}else{s = document.createElement('script');s.language = 'javascript';s.type = 'text/javascript';s.src = 'https://gcore.jsdelivr.net/gh/runningcheese/Awesome-Bookmarklets/libs/bookmarklet_cn.js';window.lastTongwenType = 'cn';}s.id = 'tongwenlet_script';document.body.appendChild(s);})(); }, runFontChange() { javascript:(function(){const e=document.createElement('div');e.style.cssText='position:fixed;top:20%;right:20%;transform:translate(-50%,0);background:#fff;border:none;border-radius:8px;padding:4px;width:200px;box-shadow:0 4px 12px rgba(0,0,0,0.25);z-index:999999;cursor:move;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif';let t=0,n=0,o=0,i=0,s=!1;e.onmousedown=l=>{s=!0;t=l.clientX;n=l.clientY;o=e.offsetLeft;i=e.offsetTop;e.style.transform='translateX(-50%)'};document.onmousemove=l=>{if(!s)return;const d=l.clientX-t,r=l.clientY-n;e.style.left=o+d+'px';e.style.top=i+r+'px'};document.onmouseup=()=>s=!1;let lastEscTime=0;document.addEventListener('keydown',function(evt){if(evt.key==='Escape'){const now=Date.now();if(now-lastEscTime<=300){e.remove()}lastEscTime=now}});const l=[{title:'▼ 修改网页字体 ×',isTitle:true},{title:'1、衬线字体',code:` document.body.style.fontFamily = "Georgia,Times,Times New Roman,serif";const allElements = document.querySelectorAll('*');allElements.forEach(el =>{if(el !== e && !e.contains(el)){el.style.fontFamily = "Georgia,Times,Times New Roman,serif";}});`},{title:'2、无衬线字体',code:` document.body.style.fontFamily = "Arial,Helvetica,sans-serif";const allElements = document.querySelectorAll('*');allElements.forEach(el =>{if(el !== e && !e.contains(el)){el.style.fontFamily = "Arial,Helvetica,sans-serif";}});`},{title:'3、思源宋体',code:` document.body.style.fontFamily = "Source Han Serif SC,Source Han Serif CN,Source Han Serif,serif";const allElements = document.querySelectorAll('*');allElements.forEach(el =>{if(el !== e && !e.contains(el)){el.style.fontFamily = "Source Han Serif SC,Source Han Serif CN,Source Han Serif,serif";}});`},{title:'4、思源黑体',code:` document.body.style.fontFamily = "Source Han Sans SC,Source Han Sans CN,Source Han Sans,sans-serif";const allElements = document.querySelectorAll('*');allElements.forEach(el =>{if(el !== e && !e.contains(el)){el.style.fontFamily = "Source Han Sans SC,Source Han Sans CN,Source Han Sans,sans-serif";}});`},{title:'5、Seravek 字体',code:` document.body.style.fontFamily = "Seravek,-apple-system,BlinkMacSystemFont,sans-serif";const allElements = document.querySelectorAll('*');allElements.forEach(el =>{if(el !== e && !e.contains(el)){el.style.fontFamily = "Seravek,-apple-system,BlinkMacSystemFont,sans-serif";}});`},{title:'6、苹方字体',code:` document.body.style.fontFamily = "PingFang SC,PingFang TC,PingFang HK,sans-serif";const allElements = document.querySelectorAll('*');allElements.forEach(el =>{if(el !== e && !e.contains(el)){el.style.fontFamily = "PingFang SC,PingFang TC,PingFang HK,sans-serif";}});`},{title:'7、等宽字体',code:` document.body.style.fontFamily = "Menlo,Monaco,Consolas,'Courier New',monospace";const allElements = document.querySelectorAll('*');allElements.forEach(el =>{if(el !== e && !e.contains(el)){el.style.fontFamily = "Menlo,Monaco,Consolas,'Courier New',monospace";}});`},{title:'8、楷体',code:` document.body.style.fontFamily = "KaiTi,楷体,STKaiti,华文楷体,serif";const allElements = document.querySelectorAll('*');allElements.forEach(el =>{if(el !== e && !e.contains(el)){el.style.fontFamily = "KaiTi,楷体,STKaiti,华文楷体,serif";}});`},{title:'9、宋体',code:` document.body.style.fontFamily = "SimSun,宋体,STSong,华文宋体,serif";const allElements = document.querySelectorAll('*');allElements.forEach(el =>{if(el !== e && !e.contains(el)){el.style.fontFamily = "SimSun,宋体,STSong,华文宋体,serif";}});`},{title:'10、恢复默认',code:` document.body.style.fontFamily = "";const allElements = document.querySelectorAll('*');allElements.forEach(el =>{if(el !== e && !e.contains(el)){el.style.fontFamily = "";}});`}];l.forEach((t,idx)=>{const n=document.createElement('div');n.style.cssText=t.isTitle?'display:flex;justify-content:space-between;align-items:center;padding:4px 10px;font-size:14px;color:#333;border-bottom:1px solid #eee;margin-bottom:2px':'cursor:pointer;padding:0px 10px;height:32px;transition:all 0.2s ease;border-radius:6px;font-size:14px;color:#333;display:flex;align-items:center';if(t.isTitle){const o=document.createElement('span');o.textContent='✕';o.style.cssText='cursor:pointer;color:#999;width:20px;height:20px;display:flex;align-items:center;justify-content:center;border-radius:50%;transition:all 0.2s ease';o.onmouseover=()=>o.style.backgroundColor='#dadada';o.onmouseout=()=>o.style.backgroundColor='';o.onclick=()=>e.remove();n.textContent=t.title.replace(' ×','');n.appendChild(o)}else{n.textContent=t.title;n.onmouseover=()=>n.style.backgroundColor='#dadada';n.onmouseout=()=>n.style.backgroundColor='';if(t.url||t.code){n.onclick=o=>{o.preventDefault();if(t.url){window.open(t.url+encodeURIComponent(window.getSelection().toString()||''))}else if(t.code){eval(t.code);}}}}e.appendChild(n)});document.body.appendChild(e)})(); this.showToast('已切换阅读字体'); }, // 启动按钮检查 startButtonCheck() { if (this.buttonCheckInterval) { clearInterval(this.buttonCheckInterval); } this.buttonCheckInterval = setInterval(() => { this.addButtons(); }, 1500); }, // 切换按钮显示/隐藏 toggleVisibility() { this.visible = !this.visible; const container = elements.getAs('#web-helper-buttons'); if (container) { container.style.display = this.visible ? 'flex' : 'none'; } else if (this.visible) { this.addButtons(); } }, // 切换是否自动运行 toggleAutoRun(key, label) { const newState = !GM_getValue(key, false); GM_setValue(key, newState); this.showToast((newState ? '已开启' : '已关闭') + '自动运行' + label); this.registerMenu(); }, // 注册脚本菜单命令 registerMenu() { this.menuCommandIds.forEach(id => { try { GM_unregisterMenuCommand(id); } catch (e) {} }); this.menuCommandIds = []; this.buttonConfigs.forEach(config => { const enabled = GM_getValue(config.key, false); const id = GM_registerMenuCommand( config.label + ' ' + (enabled ? '✅' : '❌'), () => { this.toggleAutoRun(config.key, config.label); } ); this.menuCommandIds.push(id); }); }, // 初始化 init() { this.addButtons(); this.startButtonCheck(); this.buttonConfigs.forEach(config => { if (GM_getValue(config.key, false)) { this.runButtonAction(config.id); } }); this.registerMenu(); console.log('网页助手初始化成功'); // F8 切换按钮显示/隐藏 document.addEventListener('keydown', (e) => { if (e.key === 'F8') { e.preventDefault(); this.toggleVisibility(); } }); // 监听页面变化 let lastUrl = location.href; new MutationObserver(() => { if (lastUrl !== location.href) { lastUrl = location.href; // 延迟处理,等待页面元素加载 setTimeout(() => { this.addButtons(); }, 800); } }).observe(document.body, { childList: true, subtree: true }); } }; webViewer.init(); })();