// ==UserScript== // @name 链接高亮可点击【全站适用】 // @namespace https://viayoo.com/ // @version 1.1 // @description 自动高亮网页上的各种文本链接并可点击跳转,智能识别绝大多数链接变种 // @author ChatGPT, cumt-feng // @match *://*/* // @run-at document-idle // @grant none // ==/UserScript== (function() { 'use strict'; // 常见可变体识别正则(含http, https, www, ftp, mailto, 特殊端口等) const urlPatterns = [ // 带协议 /((?:https?|ftp):\/\/[^\s"'“”‘’,,。<>【】《》{}()\[\]()<>\u4e00-\u9fa5]{3,})/gi, // www.类型的 /((?:www\.)[^\s"'“”‘’,,。<>【】《》{}()\[\]()<>\u4e00-\u9fa5]{3,})/gi, // 邮箱 /([\w\.-]+@[\w\.-]+\.[a-zA-Z]{2,})/gi, // 特殊直链(如thunder、ed2k、magnet) /((?:thunder|ed2k|magnet):\/\/[^\s"'“”‘’,,。<>【】《》{}()\[\]()<>\u4e00-\u9fa5]{3,})/gi ]; // 页面高亮样式 const HIGHLIGHT_CSS = ` .auto-link-highlight { color: #1565c0 !important; background: #e3f2fd !important; border-radius: 3px; padding: 0 2px; text-decoration: underline !important; cursor: pointer; transition: background 0.2s; } .auto-link-highlight:hover { background: #90caf9 !important; } `; // 注入高亮CSS function addHighlightStyle() { if(document.getElementById('auto-link-highlight-style')) return; const style = document.createElement('style'); style.id = 'auto-link-highlight-style'; style.textContent = HIGHLIGHT_CSS; document.head.appendChild(style); } // 将www.x.com类型转为可点http链接、邮箱转mailto function normalizeUrl(str) { if(/^www\./i.test(str)) { return 'https://' + str; } if(/^[\w\.-]+@[\w\.-]+\.[a-zA-Z]{2,}$/.test(str)) { return 'mailto:' + str; } return str; } // 判断节点在非跳过区域 function eligibleNode(node) { // 跳过脚本/样式/表单/已是链接 的节点 let p = node.parentNode; while (p) { const tag = p.nodeName; if(['A','SCRIPT','STYLE','TEXTAREA','BUTTON','SELECT','CODE','PRE','INPUT'].includes(tag)) return false; p = p.parentNode; } return true; } // 主处理:遍历文本节点高亮 function processTextNode(textNode) { if (!textNode.textContent || !eligibleNode(textNode)) return; let hasLink = false; let newHtml = textNode.textContent; // 依次匹配多种模式,为避免重复,将所有命中的区间记下,倒序替换 let links = []; for (const pattern of urlPatterns) { let match; while((match = pattern.exec(textNode.textContent)) !== null) { // 边界检查:前无英文/数字,后无英文/数字/_- const st = match.index, ed = st + match[0].length; const prev = st>0 ? textNode.textContent[st-1]:"", next = edb.start-a.start); // 倒序,避免位置偏移 for(const {start,end,raw} of links) { const url = normalizeUrl(raw); // 安全编码显示原文 const display = raw.replace(//g,">"); // 生成替换 const anchor = `${display}`; // 用substring分割拼接 newHtml = newHtml.slice(0, start) + anchor + newHtml.slice(end); } // 仅内容有变化才替换 if(newHtml!==textNode.textContent) { const span = document.createElement('span'); span.innerHTML = newHtml; textNode.parentNode.replaceChild(span, textNode); } } // 获取所有纯文本节点 function getTextNodes(root) { const walker = document.createTreeWalker( root, NodeFilter.SHOW_TEXT, { acceptNode: function(node) { if(!node.textContent.trim() || !eligibleNode(node)) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } } ); let nodes = []; let node; while(node = walker.nextNode()) nodes.push(node); return nodes; } // 处理全页面 function highlightLinks() { getTextNodes(document.body).forEach(processTextNode); } // 监听新增DOM自动高亮 function observeDom() { const ob = new MutationObserver(muts=>{ for(const m of muts) { for(const node of m.addedNodes) { if(node.nodeType===Node.TEXT_NODE) processTextNode(node); else if(node.nodeType===Node.ELEMENT_NODE) getTextNodes(node).forEach(processTextNode); } } }); ob.observe(document.body, {childList:true,subtree:true}); } // 执行 addHighlightStyle(); highlightLinks(); observeDom(); })();