- 楼主博主
油猴脚本内容如下,可以直接使用:
如果你有其他脚本,不喜欢太多,可以让 AI 把它们合并到一起。
// ==UserScript== // @name sbsb 域名插件 // @namespace yee.cx // @version 1.1.0 // @description 在烧饼论坛帖子正文中识别域名,域名右侧显示放大镜,点击弹出该域名的基础信息(注册状态/注册商/注册时间/到期时间),来自 yee.cx // @author Evan // @match https://sb.sb/* // @grant GM_xmlhttpRequest // @connect yee.cx // @run-at document-idle // @license MIT // ==/UserScript== (function () { 'use strict'; var API_ENRICH = 'https://yee.cx/api/enrich'; var BASE_URL = 'https://yee.cx/'; var CACHE = Object.create(null); // 域名 -> Promise var SKIP_HOSTS = { 'nodeloc.com': 1, 'sb.sb': 1, 'sb.sb.sb': 1 }; // sb.sb.sb 为附件域名 var SKIP_TLDS = { local: 1, localhost: 1, test: 1, invalid: 1, example: 1, lan: 1, internal: 1, onion: 1 }; /* ---------------- 工具函数 ---------------- */ function isPlainDomain(d) { if (!d) return false; if (/^\d{1,3}(\.\d{1,3}){3}$/.test(d)) return false; // 纯 IP var parts = d.split('.'); var tld = parts[parts.length - 1]; if (SKIP_TLDS[tld]) return false; return true; } // 常见多级公共后缀表(多级公共后缀下的注册域,如 evan.com.cn 属于注册域而非子域) var PUBLIC_SUFFIXES = [ 'com.cn', 'net.cn', 'org.cn', 'gov.cn', 'edu.cn', 'ac.cn', 'com.ee', 'pri.ee', 'org.ee', 'co.uk', 'org.uk', 'me.uk', 'ac.uk', 'gov.uk', 'com.au', 'net.au', 'org.au', 'co.jp', 'ne.jp', 'or.jp', 'ac.jp', 'go.jp', 'com.hk', 'com.tw', 'org.tw', 'com.kr', 'co.kr', 'or.kr', 'com.sg', 'com.my', 'com.vn', 'co.nz', 'com.br', 'com.mx', 'com.tr', 'com.co', 'co.in', 'ac.in', 'com.ph', 'com.ua', 'com.pl', 'com.ru', 'com.ar', 'com.pe', 'co.id', 'com.th' ]; // 匹配最长公共后缀(多级后缀优先,等价于从长到短尝试) function matchPublicSuffix(d) { var best = ''; for (var i = 0; i < PUBLIC_SUFFIXES.length; i++) { var s = PUBLIC_SUFFIXES[i]; if (s.length > best.length && d.slice(-(s.length + 1)) === '.' + s) best = s; } return best; } // 判断是否为真子域(不支持 yee.cx 查询): // 域名去掉末尾公共后缀后,剩余标签恰好 1 个即为注册域(允许查询); // 剩余标签 >1 才判定为真子域(如 demo.cocaapi.com 仍不支持查询) function isSubDomain(d) { if (!d) return false; var ps = matchPublicSuffix(d); var rest = ps ? d.slice(0, d.length - ps.length - 1) : d.slice(0, d.lastIndexOf('.')); if (!rest) return false; return rest.split('.').length > 1; } function normalizeDomain(d) { var v = String(d || '').toLowerCase(); v = v.replace(/^https?:\/\//i, '').replace(/[\/?#].*$/, '').trim(); if (v.indexOf('www.') === 0) v = v.slice(4); try { var host = new URL('http://' + v).hostname; if (host && host.indexOf('.') > 0) v = host; } catch (e) {} return v; } function esc(s) { var d = document.createElement('div'); d.textContent = s == null ? '' : String(s); return d.innerHTML; } /* ---------------- 放大镜图标 ---------------- */ var MAG_SVG = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>'; function makeMagnifier(domain) { var a = document.createElement('a'); a.className = 'nlc-mag'; a.href = 'javascript:void(0)'; a.title = '查询域名信息:' + domain; a.setAttribute('data-nlc-domain', domain); a.innerHTML = MAG_SVG; a.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); showPopup(domain, a); }); return a; } /* ---------------- 小窗 ---------------- */ var activePopup = null; function closePopup() { if (activePopup) { if (activePopup.parentNode) activePopup.parentNode.removeChild(activePopup); activePopup = null; } document.removeEventListener('keydown', onDocKey); } function onDocKey(e) { if (e.key === 'Escape') closePopup(); } function showPopup(domain, anchor) { closePopup(); var pop = document.createElement('div'); pop.className = 'nlc-pop'; var head = document.createElement('div'); head.className = 'nlc-pop-head'; var title = document.createElement('span'); title.className = 'nlc-pop-title'; title.textContent = domain; var closeBtn = document.createElement('button'); closeBtn.className = 'nlc-pop-close'; closeBtn.type = 'button'; closeBtn.textContent = '×'; closeBtn.addEventListener('click', closePopup); head.appendChild(title); head.appendChild(closeBtn); var body = document.createElement('div'); body.className = 'nlc-pop-body'; body.innerHTML = '<div class="nlc-loading"><span class="nlc-spinner"></span>加载中…</div>'; var foot = document.createElement('div'); foot.className = 'nlc-pop-foot'; var moreLabel = document.createTextNode('更多信息 '); var more = document.createElement('a'); more.className = 'nlc-pop-more'; more.href = BASE_URL + encodeURIComponent(domain); more.target = '_blank'; more.rel = 'noopener'; more.textContent = BASE_URL + domain; foot.appendChild(moreLabel); foot.appendChild(more); pop.appendChild(head); pop.appendChild(body); pop.appendChild(foot); document.body.appendChild(pop); // 定位:图标下方,视口溢出则翻转 var r = anchor.getBoundingClientRect(); var pw = pop.offsetWidth; var ph = pop.offsetHeight; var left = r.left; var maxLeft = window.innerWidth - pw - 8; if (left > maxLeft) left = maxLeft; if (left < 8) left = 8; var top = r.bottom + 6; if (top + ph > window.innerHeight - 8) top = r.top - ph - 6; if (top < 8) top = 8; pop.style.left = left + 'px'; pop.style.top = top + 'px'; activePopup = pop; document.addEventListener('keydown', onDocKey); if (isSubDomain(domain)) { body.innerHTML = '<div class="nlc-err">不支持二级域名查询</div>'; } else { fetchEnrich(domain).then(function (data) { if (activePopup !== pop) return; renderBody(body, domain, data); }).catch(function (err) { if (activePopup !== pop) return; body.innerHTML = '<div class="nlc-err">查询失败</div>'; if (err) console.error('[nlc-magnifier]', domain, err); }); } setTimeout(function () { document.addEventListener('click', function once(e) { if (!activePopup || !activePopup.contains(e.target) && !(e.target.closest && e.target.closest('.nlc-mag'))) { closePopup(); } document.removeEventListener('click', once); }); }, 0); } function fetchEnrich(domain) { if (CACHE[domain]) return CACHE[domain]; CACHE[domain] = new Promise(function (resolve, reject) { GM_xmlhttpRequest({ method: 'POST', url: API_ENRICH, headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ domain: domain }), timeout: 12000, onload: function (res) { var d; try { d = JSON.parse(res.responseText); } catch (e) { reject(e); return; } if (d && d.success && d.data) resolve(d.data); else reject(new Error((d && d.error) || 'empty response')); }, onerror: reject, ontimeout: function () { reject(new Error('timeout')); } }); }); return CACHE[domain]; } // 降级:whois 原始数据接口 function fetchWhois(domain) { var key = 'whois:' + domain; if (CACHE[key]) return CACHE[key]; CACHE[key] = new Promise(function (resolve, reject) { GM_xmlhttpRequest({ method: 'POST', url: API_WHOIS, headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ domain: domain }), timeout: 12000, onload: function (res) { var d; try { d = JSON.parse(res.responseText); } catch (e) { reject(e); return; } if (d && d.success && d.data && d.data.raw) resolve(d.data.raw); else reject(new Error((d && d.error) || 'empty whois response')); }, onerror: reject, ontimeout: function () { reject(new Error('timeout')); } }); }); return CACHE[key]; } // 解析 whois 原始文本,提取注册商 / 注册时间 / 到期时间(日期取前 10 位) function parseWhoisRaw(raw) { var text = raw || ''; var out = { registrar: '', registered: '', expires: '' }; var m; m = text.match(/^registrar:\s*(.+)$/im); if (m) out.registrar = m[1].trim(); m = text.match(/^(?:creation\s*date|registry\s*creation\s*date):\s*(\S+)/im); if (m) out.registered = String(m[1]).slice(0, 10); m = text.match(/^(?:registry\s*expiry\s*date|expiry\s*date):\s*(\S+)/im); if (m) out.expires = String(m[1]).slice(0, 10); return out; } function renderBody(body, domain, data) { var p = (data && data.parsed) || {}; var state = '未知'; if (data.type === 0) state = '未注册(可注册)'; else if (data.type === 1) state = '已注册'; function d10(v) { return v ? String(v).slice(0, 10) : '—'; } var rows = [ ['域名状态', state], ['注册商', (p.registrar && p.registrar.name) || '—'], ['注册时间', d10(p.registered)], ['到期时间', d10(p.expires)] ]; var html = '<div class="nlc-row"><span class="nlc-label">域名</span><span class="nlc-value">' + esc(domain) + '</span></div>'; rows.forEach(function (r) { html += '<div class="nlc-row"><span class="nlc-label">' + esc(r[0]) + '</span><span class="nlc-value">' + esc(r[1]) + '</span></div>'; }); body.innerHTML = html; } /* ---------------- 正文扫描 ---------------- */ var DOMAIN_RE = /(^|[^a-z0-9.-])((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63})(?=$|[^a-z0-9_.-])/gi; // 单个链接:外部绝对 URL,在其后插放大镜(由调用方限定扫描区域) function handleAnchor(a) { if (a.dataset.nlcDone) return; var href = a.getAttribute('href') || ''; if (!/^https?:\/\//i.test(href)) return; var host = ''; try { host = new URL(href, location.href).hostname.toLowerCase(); } catch (e) { return; } if (SKIP_HOSTS[host]) return; var dom = normalizeDomain(host); if (!isPlainDomain(dom)) return; a.dataset.nlcDone = '1'; var mag = makeMagnifier(dom); a.parentNode.insertBefore(mag, a.nextSibling); } // 区域内链接:仅处理外部 http(s) 链接,跳过图片附件 a.post-image-link 与 @提及 a.mention function handleLinks(scope) { var anchors = scope.querySelectorAll ? scope.querySelectorAll('a[href]') : []; for (var i = 0; i < anchors.length; i++) { var a = anchors[i]; if (a.dataset.nlcDone) continue; if (a.classList && (a.classList.contains('post-image-link') || a.classList.contains('mention'))) continue; handleAnchor(a); } } // 文本节点:匹配域名,拆分为 [前文][域名span][放大镜][后文] function handleTextNode(node) { var text = node.nodeValue; if (!text || !/\./.test(text)) return; DOMAIN_RE.lastIndex = 0; var m; var hits = []; while ((m = DOMAIN_RE.exec(text))) { var dom = normalizeDomain(m[2]); if (!isPlainDomain(dom) || SKIP_HOSTS[dom]) continue; var domStart = m.index + m[0].indexOf(m[2]); hits.push({ dom: dom, start: domStart, len: m[2].length }); } if (!hits.length) return; var frag = document.createDocumentFragment(); var pos = 0; hits.forEach(function (h) { if (h.start < pos) return; // 防重叠 frag.appendChild(document.createTextNode(text.slice(pos, h.start))); var span = document.createElement('span'); span.className = 'nlc-dom'; span.setAttribute('data-nlc-domain', h.dom); span.textContent = text.substr(h.start, h.len); frag.appendChild(span); frag.appendChild(makeMagnifier(h.dom)); pos = h.start + h.len; }); frag.appendChild(document.createTextNode(text.slice(pos))); node.parentNode.replaceChild(frag, node); } // 文本兜底扫描:遍历区域内文本节点(跳过 a / pre / code / script / style) function scanTextTree(root) { var walker = document.createTreeWalker( root, NodeFilter.SHOW_TEXT, { acceptNode: function (n) { var p = n.parentNode; while (p && p !== root) { if (p.tagName === 'A' || p.tagName === 'PRE' || p.tagName === 'CODE' || p.tagName === 'SCRIPT' || p.tagName === 'STYLE' || p.tagName === 'TEXTAREA' || p.classList && p.classList.contains('nlc-mag')) { return NodeFilter.FILTER_REJECT; } p = p.parentNode; } return NodeFilter.FILTER_ACCEPT; } } ); var nodes = []; while (walker.nextNode()) nodes.push(walker.currentNode); for (var j = 0; j < nodes.length; j++) handleTextNode(nodes[j]); } // 楼层:扫描 .post-content(正文)与 .floor-box .floor-box-body(引用块) function scanPost(root) { if (!root || root.nodeType !== 1) return; // 自身已扫描过则跳过(避免 MutationObserver 反复传入同一容器时重复注入) if (root.getAttribute && root.getAttribute('data-nlc-scanned') === '1') return; root.setAttribute('data-nlc-scanned', '1'); // 1. 正文 .post-content:外链放大镜 + 裸域名兜底 var content = root.querySelector ? root.querySelector('.post-content') : null; if (content) { handleLinks(content); scanTextTree(content); } // 2. 引用块 .floor-box .floor-box-body var floors = root.querySelectorAll ? root.querySelectorAll('.floor-box .floor-box-body') : []; for (var i = 0; i < floors.length; i++) { handleLinks(floors[i]); scanTextTree(floors[i]); } } /* ---------------- 注入与监听 ---------------- */ function injectStyles() { var css = [ '.nlc-mag{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;margin:0 2px;vertical-align:middle;color:#8b949e;cursor:pointer;border-radius:4px;text-decoration:none;opacity:.75;transition:opacity .15s,color .15s,background .15s}', '.nlc-mag:hover{opacity:1;color:#2f81f7;background:rgba(47,129,247,.12)}', '.nlc-mag svg{display:block}', '.nlc-pop{position:fixed;z-index:2147483646;width:290px;max-width:calc(100vw - 24px);background:#161b22;color:#e6edf3;border:1px solid #30363d;border-radius:10px;box-shadow:0 10px 34px rgba(0,0,0,.42);font:13px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;overflow:hidden}', '.nlc-pop-head{display:flex;align-items:center;justify-content:space-between;padding:9px 12px;border-bottom:1px solid #30363d;background:#0d1117}', '.nlc-pop-title{font-size:14px;font-weight:600;color:#fff;word-break:break-all;padding-right:8px}', '.nlc-pop-close{display:flex;align-items:center;justify-content:center;padding:0!important;margin:0!important;line-height:1!important;font-size:20px!important;width:24px!important;height:24px!important;flex:0 0 auto!important;border:0!important;background:transparent;color:#8b949e;cursor:pointer;border-radius:6px}', '.nlc-pop-close:hover{color:#fff;background:rgba(255,255,255,.08)}', '.nlc-pop-body{padding:8px 12px}', '.nlc-row{display:flex;gap:10px;padding:4px 0;border-bottom:1px dashed rgba(48,54,61,.6)}', '.nlc-row:last-child{border-bottom:0}', '.nlc-label{flex:0 0 58px;color:#8b949e;white-space:nowrap}', '.nlc-value{color:#e6edf3;word-break:break-all;min-width:0}', '.nlc-loading{display:flex;align-items:center;justify-content:center;gap:8px;padding:10px 0;color:#8b949e;text-align:center}', '.nlc-spinner{display:inline-block;width:15px;height:15px;flex:0 0 auto;border:2px solid rgba(139,148,158,.25);border-top-color:#2f81f7;border-radius:50%;animation:nlc-spin .8s linear infinite!important}', '@keyframes nlc-spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}', '.nlc-err{padding:10px 0;color:#f85149;text-align:center}', '.nlc-pop-foot{padding:8px 12px 10px;border-top:1px solid #30363d;background:#0d1117;text-align:left;display:flex;align-items:center;justify-content:flex-start;gap:6px;color:#8b949e;font-size:13px;white-space:nowrap;overflow:hidden}', '.nlc-pop-more{color:#2f81f7;text-decoration:none;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:0 1 auto;min-width:0}', '.nlc-pop-more:hover{color:#79b8ff;text-decoration:none}' ].join(''); var style = document.createElement('style'); style.textContent = css; (document.head || document.documentElement).appendChild(style); } function init() { injectStyles(); var outlet = document.body; var posts = outlet.querySelectorAll('li.post-item'); for (var i = 0; i < posts.length; i++) scanPost(posts[i]); var observer = new MutationObserver(function (muts) { for (var i = 0; i < muts.length; i++) { var nodes = muts[i].addedNodes; for (var j = 0; j < nodes.length; j++) { var n = nodes[j]; if (n.nodeType !== 1) continue; var list = n.classList && n.classList.contains('post-item') ? [n] : (n.querySelectorAll ? Array.prototype.slice.call(n.querySelectorAll('li.post-item')) : []); for (var k = 0; k < list.length; k++) scanPost(list[k]); } } }); observer.observe(outlet, { childList: true, subtree: true }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();收藏支持反对打赏


