// ==UserScript== // @name URL Sniffer // @name:zh-CN URL 嗅探器 // @namespace https://gera2ld.space/ // @description Sniff URLs in HTML // @description:zh-CN 从 HTML 中嗅探 URL // @match *://*/* // @grant GM_addStyle // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_setClipboard // @version 0.1.0 // @author Gerald // @require https://cdn.jsdelivr.net/combine/npm/@violentmonkey/dom@1,npm/@violentmonkey/ui@0.5 // @downloadURL none // ==/UserScript== (function () { 'use strict'; var styles = {"root":"style-module_root__3T7HH","toast":"style-module_toast__3PP-f"}; var stylesheet=".style-module_root__3T7HH{position:fixed;top:0;right:0;bottom:0;left:0;z-index:10000;background:rgba(0,0,0,.53333)}.style-module_root__3T7HH>*{position:absolute;top:0;left:0;border:2px solid}.style-module_toast__3PP-f{z-index:10001!important}"; const STYLE_CURRENT = { stroke: '#0f08', fill: '#0f02' }; const STYLE_SELECTION = { stroke: '#bbf8', fill: '#bbf2' }; const STYLE_SELECTED = { stroke: '#ff08', fill: '#ff02' }; const STYLE_TO_DESELECT = { stroke: '#88d8', fill: '#88d2' }; const STYLE_TO_SELECT = { stroke: '#bb08', fill: '#bb02' }; const mask = VM.getHostElement(false); mask.addStyle(stylesheet); mask.root.className = styles.root; mask.root.addEventListener('mousedown', handleMouseDown); mask.root.addEventListener('mouseup', handleMouseUp); mask.root.addEventListener('mousemove', handleMouseMove); mask.root.addEventListener('click', handleClick); GM_registerMenuCommand('Toggle sniffer', toggle); let context; function toggle() { if (context) close();else start(); } function start() { if (context) return; const items = Array.from(document.querySelectorAll('a[href]')).filter(a => { const href = a.getAttribute('href'); return href && !/^(?:#|javascript:)/.test(href); }).map(el => ({ el })); context = { items, index: -1, active: null }; update(); mask.show(); document.addEventListener('scroll', update); document.addEventListener('resize', update); GM_registerMenuCommand('Copy URLs', handleCopy); } function close() { if (!context) return; mask.root.innerHTML = ''; mask.hide(); context = null; document.removeEventListener('scroll', update); document.removeEventListener('resize', update); GM_unregisterMenuCommand('Copy URLs'); } function update() { context.items.forEach(item => { const rect = item.el.getBoundingClientRect(); item.pos = { x: rect.left, y: rect.top, w: rect.width, h: rect.height }; }); render(); } function render() { renderActive(); renderSelected(); } function updateStyle(el, style) { el.style.borderColor = style.stroke; el.style.background = style.fill; } function updatePosition(el, pos, padding = 2) { Object.assign(el.style, { width: `${pos.w + padding * 2}px`, height: `${pos.h + padding * 2}px`, transform: `translate(${pos.x - padding}px,${pos.y - padding}px)` }); } function renderActive() { const activeItem = !context.dragging && context.items[context.index]; if (!activeItem) { if (context.active) { context.active.remove(); context.active = null; } } else { if (!context.active) { context.active = VM.createElement(mask.id, null); updateStyle(context.active, STYLE_CURRENT); mask.root.append(context.active); } updatePosition(context.active, activeItem.pos); } } function renderSelected() { context.items.forEach(item => { if (item.rect) updatePosition(item.rect, item.pos); }); } function setItemRect(item, style) { if (style) { if (!item.rect) { item.rect = VM.createElement(mask.id, null); mask.root.append(item.rect); } updateStyle(item.rect, style); updatePosition(item.rect, item.pos); } else if (item.rect) { item.rect.remove(); item.rect = null; } } function handleClick() { const activeItem = context.items[context.index]; if (activeItem) { activeItem.selected = !activeItem.selected; setItemRect(activeItem, activeItem.selected && STYLE_SELECTED); } } function handleMouseDown(e) { if (context.dragging) return; const x = e.clientX; const y = e.clientY; context.dragging = { x, y }; } function handleMouseMove(e) { const x = e.clientX; const y = e.clientY; if (context.dragging) { if (!context.dragging.rect) { const rect = VM.createElement(mask.id, null); updateStyle(rect, STYLE_SELECTION); mask.root.append(rect); context.dragging.rect = rect; } context.index = -1; let x0 = context.dragging.x; let y0 = context.dragging.y; const w = Math.abs(x - x0); const h = Math.abs(y - y0); x0 = Math.min(x0, x); y0 = Math.min(y0, y); updatePosition(context.dragging.rect, { x: x0, y: y0, w, h }, 0); context.items.forEach(item => { item.inSelection = item.pos.x >= x0 && item.pos.x + item.pos.w <= x0 + w && item.pos.y >= y0 && item.pos.y + item.pos.h <= y0 + h; const state = (item.inSelection ? 2 : 0) + (item.selected ? 1 : 0); setItemRect(item, { 1: STYLE_SELECTED, 2: STYLE_TO_SELECT, 3: STYLE_TO_DESELECT }[state]); }); } else { context.index = context.items.findIndex(({ pos }) => x >= pos.x && x <= pos.x + pos.w && y >= pos.y && y <= pos.y + pos.h); } render(); } function handleMouseUp() { if (!context.dragging) return; if (context.dragging.rect) { context.dragging.rect.remove(); context.items.forEach(item => { if (item.inSelection) { item.inSelection = false; item.selected = !item.selected; setItemRect(item, item.selected && STYLE_SELECTED); } }); } context.dragging = null; } function handleCopy() { const urls = context.items.filter(item => item.selected).map(item => item.el.href); GM_setClipboard(urls.join('\n')); VM.showToast('URLs copied', { shadow: false, className: styles.toast }); } }());