Greasy Fork is available in English.
从网页中批量复制、打开链接,选择复选框
当前为
// ==UserScript==
// @name Snap Links Mod
// @description 从网页中批量复制、打开链接,选择复选框
// @name:en Snap Links Mod
// @description:en snap Links(open, copy), radios, chenkboxs, images from website
// @author Griever, ywzhaiqi, lastdream2013, Hanchy Hill
// @namespace http://minhill.com/
// @homepageURL http://greasyfork.icu/en/scripts/25051/
// @include http*
// @include https*
// @version 2025.03.28
// @license The MIT License (MIT); http://opensource.org/licenses/MIT
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_openInTab
// @grant GM_deleteValue
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @grant GM_setClipboard
// @grant GM_log
// @compatible firefox
// @compatible chrome
// @compatible edge
// @icon http://minhill.com/blog/wp-content/uploads/2012/03/favicon.ico
// @note 2025/03/28 添加开关控制,修复菜单显示和重复使用问题
// @run-at document-end
// ==/UserScript==
// 开关状态
var scriptEnabled = false;
// 从存储中读取开关状态
scriptEnabled = GM_getValue("enabled", false);
var snapLinks = {
timer: null,
button: 0,
active: false, // 是否已激活
inited: false, // 是否已初始化菜单DOM
init: function() {
// 如果开关未开启,不激活
if (!scriptEnabled) return;
// 防止重复初始化
if (snapLinks.active) return;
this.win = window;
this.doc = this.win.document;
this.body = this.doc.body;
if (!this.body) {
console.log("Can not snap links.");
return false;
}
this.root = snapLinks.doc.documentElement;
this.popup = document.getElementById("snapLinksMenupopup");
// 保存原始光标样式
this.bodyCursor = this.body.style.cursor;
this.rootCursor = this.root.style.cursor;
this.body.style.setProperty("cursor", "crosshair", "important");
this.root.style.setProperty("cursor", "crosshair", "important");
this.highlights = [];
this.elements = [];
// 添加事件监听
this.doc.addEventListener("mousedown", snapLinks.handleEvent, true);
this.doc.addEventListener("pagehide", snapLinks.handleEvent, true);
snapLinks.active = true;
},
uninit: function() {
if (!snapLinks.active) return;
snapLinks.doc.removeEventListener("mousedown", snapLinks.handleEvent, true);
snapLinks.doc.removeEventListener("mousemove", snapLinks.handleEvent, true);
snapLinks.doc.removeEventListener("pagehide", snapLinks.handleEvent, true);
snapLinks.doc.removeEventListener("mouseup", snapLinks.handleEvent, true);
setTimeout(function(self){
if (self.doc) self.doc.removeEventListener("click", snapLinks.handleEvent, true);
}, 10, snapLinks);
// 移除选框
if (snapLinks.box && snapLinks.box.parentNode)
snapLinks.box.parentNode.removeChild(snapLinks.box);
snapLinks.box = null;
// 恢复光标
if (this.body) this.body.style.cursor = this.bodyCursor;
if (this.root) this.root.style.cursor = this.rootCursor;
snapLinks.active = false;
},
deactivate: function() {
// 完全停用,清理所有,不再自动激活
snapLinks.uninit();
snapLinks.lowlightAll();
// 确保菜单隐藏
var sslpop = document.getElementById("snapLinksMenupopup");
if (sslpop) {
sslpop.classList.remove("trigger_popup");
sslpop.classList.add("hidden_popup");
}
document.removeEventListener("click", snapLinks.destroy, false);
snapLinks.active = false;
},
destroy: function() {
snapLinks.uninit();
snapLinks.lowlightAll();
var sslpop = document.getElementById("snapLinksMenupopup");
if (sslpop) {
sslpop.classList.remove("trigger_popup");
sslpop.classList.add("hidden_popup");
}
document.removeEventListener("click", snapLinks.destroy, false);
// 如果开关开启,重新激活
if (scriptEnabled) {
snapLinks.init();
}
},
handleEvent: function(event) {
switch(event.type){
case "mousedown":
// 仅响应左键,且无修饰键
if (event.button != 0 || event.ctrlKey || event.shiftKey || event.altKey) return;
event.preventDefault();
event.stopPropagation();
// 重置状态
snapLinks.elements = [];
snapLinks.draw(event);
break;
case "mousemove":
event.preventDefault();
event.stopPropagation();
var moveX = event.pageX;
var moveY = event.pageY;
if (snapLinks.downX > moveX) snapLinks.box.style.left = moveX + "px";
if (snapLinks.downY > moveY) snapLinks.box.style.top = moveY + "px";
snapLinks.box.style.width = Math.abs(moveX - snapLinks.downX) + "px";
snapLinks.box.style.height = Math.abs(moveY - snapLinks.downY) + "px";
if (snapLinks.timer) {
clearTimeout(snapLinks.timer);
snapLinks.timer = null;
}
var timeStamp = new Date().getTime();
if (timeStamp - snapLinks.lastHiglightedTime > 150) {
snapLinks.boxRect = snapLinks.box.getBoundingClientRect();
snapLinks.highlightAll();
} else {
var self = snapLinks;
snapLinks.timer = setTimeout(function() {
if (self.box) {
self.boxRect = self.box.getBoundingClientRect();
self.highlightAll();
}
}, 200);
}
break;
case "mouseup":
if (event.button != snapLinks.button || event.ctrlKey || event.shiftKey) return;
event.preventDefault();
event.stopPropagation();
if (snapLinks.timer) {
clearTimeout(snapLinks.timer);
snapLinks.timer = null;
}
if (snapLinks.box) {
snapLinks.boxRect = snapLinks.box.getBoundingClientRect();
snapLinks.highlightAll();
}
// 收集高亮元素
for (let e of snapLinks.highlights) {
if (e instanceof HTMLImageElement) {
let link = snapLinks.doc.evaluate(
'ancestor::*[@href]', e, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (link && snapLinks.highlights.indexOf(link) === -1) {
snapLinks.elements.push(link);
}
continue;
}
snapLinks.elements.push(e);
}
snapLinks.uninit();
// 如果有选中元素则显示菜单,否则直接重新激活
if (snapLinks.elements.length > 0) {
snapLinks.showPopup(event);
} else {
snapLinks.lowlightAll();
if (scriptEnabled) snapLinks.init(); // 无元素时且开关开启则重新激活
}
break;
case "click":
event.preventDefault();
event.stopPropagation();
break;
case "pagehide":
snapLinks.destroy();
break;
}
},
draw: function(aEvent) {
this.lastHiglightedTime = new Date().getTime();
this.downX = aEvent.pageX;
this.downY = aEvent.pageY;
this.box = this.doc.createElement("div");
this.box.id = "snap-links-box";
this.box.style.cssText = [
'background-color: rgba(0,128,255,.1) !important;',
'border: 1px solid rgb(255,255,0) !important;',
'box-sizing: border-box !important;',
'-moz-box-sizing: border-box !important;',
'position: absolute !important;',
'z-index: 2147483647 !important;',
'top:' + this.downY + 'px;',
'left:' + this.downX + 'px;',
'cursor: crosshair !important;',
'margin: 0px !important;',
'padding: 0px !important;',
'outline: none !important;',
].join(" ");
this.body.appendChild(this.box);
this.doc.removeEventListener("mousedown", this.handleEvent, true);
this.doc.addEventListener("mousemove", this.handleEvent, true);
this.doc.addEventListener("mouseup", this.handleEvent, true);
this.doc.addEventListener("click", this.handleEvent, true);
},
highlightAll: function() {
var a = '[href]:not([href^="javascript:"]):not([href^="mailto:"]):not([href^="#"])';
var selector = a + ', ' + a + ' img, input[type="checkbox"], input[type="radio"]';
selector += ', a.b-in-blk.input-cbx[href^="javascript:"]';
var contains = this.getContainsElements();
contains.reverse();
var matches = [];
for (let e of contains) {
if (e.nodeType !== 1 || !e.matches(selector))
continue;
if (e.hasAttribute('href')) {
let imgs = Array.prototype.slice.call(e.getElementsByTagName('img'));
if (imgs[0]) {
[].push.apply(contains, imgs);
continue;
}
}
if (!("defStyle" in e))
this.highlight(e);
matches.push(e);
}
this.highlights.forEach(function(e, i, a){
if (matches.indexOf(e) === -1)
this.lowlight(e);
}, this);
this.highlights = matches;
this.lastHiglightedTime = new Date().getTime();
},
lowlightAll: function() {
this.highlights.forEach(function(e){
this.lowlight(e);
}, this);
this.highlights = [];
},
highlight: function(elem) {
if (!('defStyle' in elem))
elem.defStyle = elem.getAttribute('style');
elem.style.setProperty('outline', '2px solid #ff0000', null);
elem.style.setProperty('outline-offset', '-1px', null);
},
lowlight: function(elem) {
if ("defStyle" in elem) {
elem.defStyle ?
elem.style.cssText = elem.defStyle :
elem.removeAttribute("style");
delete elem.defStyle;
}
},
getContainsElements: function() {
if (!this.boxRect) return [];
var a = '[href]:not([href^="javascript:"]):not([href^="mailto:"]):not([href^="#"])';
var selector = a + ', ' + a + ' img, input[type="checkbox"], input[type="radio"]';
selector += ', a.b-in-blk.input-cbx[href^="javascript:"]';
var nodes = document.querySelectorAll(selector);
var arraynode = [];
for (let i = 0; i < nodes.length; i++) {
if(this.inSelect(nodes[i])) arraynode.push(nodes[i]);
}
return arraynode;
},
inSelect: function(node) {
var boxPos = snapLinks.boxRect;
var xmin = boxPos.left, xmax = boxPos.right, ymin = boxPos.top, ymax = boxPos.bottom;
var pos = this.getOffset(node);
var left = pos.x, right = pos.x + pos.width;
var top = pos.y, bottom = pos.y + pos.height;
var xOverlap = (left <= xmax && right >= xmin);
var yOverlap = (top <= ymax && bottom >= ymin);
return xOverlap && yOverlap;
},
getOffset: function(node) {
var rect = node.getBoundingClientRect();
return {
x: rect.left,
y: rect.top,
width: rect.width,
height: rect.height
};
},
showPopup: function(aEvent) {
var cls = [];
var linkcount = 0;
var specialLinkCount = 0;
var imagecount = 0;
var checkboxcount = 0;
var radiocount = 0;
for (let elem of this.elements) {
if (elem instanceof HTMLAnchorElement) {
if (elem.href.indexOf('javascript:') == 0)
specialLinkCount++;
else
linkcount++;
}
}
for (let elem of this.elements) {
if (elem instanceof HTMLAnchorElement && /\.(jpe?g|png|gif|bmp)$/i.test(elem.href))
imagecount++;
}
for (let elem of this.elements) {
if (elem instanceof HTMLInputElement && elem.type === 'checkbox')
checkboxcount++;
}
for (let elem of this.elements) {
if (elem instanceof HTMLInputElement && elem.type === 'radio')
radiocount++;
}
if (linkcount > 0) cls.push("hasLink");
if (imagecount > 0) cls.push("hasImageLink");
if (checkboxcount > 0) cls.push("hasCheckbox");
if (radiocount > 0) cls.push("hasRadio");
if (specialLinkCount > 0) cls.push("hasSpecialLink");
var setCount = function(id, label){
let currentEntry = document.getElementById(id);
if(currentEntry)
currentEntry.innerHTML = label;
};
var data = {
"SnapLinksOpenLinks": "在新标签打开所有链接 (" + linkcount + ")",
"SnapLinksCopyLinks": "复制所有链接URL (" + linkcount + ")",
"SnapLinksCopyLinksReverse": "复制所有链接URL (" + linkcount + ") (反向)",
"SnapLinksCopyLinksAndTitles": "复制所有链接标题 + URL (" + linkcount + ")",
"SnapLinksCopyLinksAndTitlesMD": "复制所有链接标题 + URL (" + linkcount + ") (MD)",
"SnapLinksCopyLinksAndTitlesBBS": "复制所有链接标题 + URL (" + linkcount + ") (BBS)",
"SnapLinksCopyLinksRegExp": "复制所有链接标题 + URL (" + linkcount + ") (筛选)",
"SnapLinksCopyLinksSetFormat": "复制所有链接标题 + URL (" + linkcount + ") (设置复制格式)",
"SnapLinksOpenImageLinks": "在新标签页打开所有图片链接 (" + imagecount + ")",
"SnapLinksImageLinksOnePage": "在一个标签页显示所有图片链接 (" + imagecount + ")",
"SnapLinksCheckBoxSelect": "复选框 - 选中 (" + checkboxcount + ")",
"SnapLinksCheckBoxCancel": "复选框 - 取消 (" + checkboxcount + ")",
"SnapLinksCheckBoxTaggle": "复选框 - 反选 (" + checkboxcount + ")",
"SnapLinksRadioSelect": "单选框 - 选中 (" + radiocount + ")",
"SnapLinksRadioCancel": "单选框 - 取消 (" + radiocount + ")",
"SnapLinksClickLinks": "特殊单选框 - 选中 (" + specialLinkCount + ")",
};
for(let id in data){
setCount(id, data[id]);
}
var setStyleNode = function(showList){
var setList = ["hasLink","hasImageLink","hasCheckbox","hasRadio","hasSpecialLink"];
setList.forEach(function(elist){
let elements = document.getElementsByClassName(elist);
if(elements){
for(var i=0; i<elements.length; i++){
if(showList.indexOf(elist) == -1){
elements[i].style.display = "none";
} else {
elements[i].style.display = "block";
}
}
}
});
}
if (cls.length > 0) {
setStyleNode(cls);
this.openPopupAtScreen(aEvent.clientX, aEvent.clientY);
} else {
this.lowlightAll();
if (scriptEnabled) this.init(); // 无有效菜单项时且开关开启则重新激活
}
},
openPopupAtScreen: function(clientX, clientY) {
var popMenu = document.getElementById("snapLinksMenupopup");
if (!popMenu) return;
// 先临时显示菜单以获取真实尺寸
popMenu.classList.remove("hidden_popup");
popMenu.classList.add("temp_show");
popMenu.style.visibility = "hidden";
popMenu.style.display = "block";
var menuWidth = popMenu.offsetWidth;
var menuHeight = popMenu.offsetHeight;
// 计算最佳位置(优先右下角,超出则调整)
var left = clientX + 5;
var top = clientY + 5;
var viewportWidth = window.innerWidth;
var viewportHeight = window.innerHeight;
if (left + menuWidth > viewportWidth) {
left = clientX - menuWidth - 5;
}
if (top + menuHeight > viewportHeight) {
top = clientY - menuHeight - 5;
}
// 确保不超出左/上边界
left = Math.max(5, left);
top = Math.max(5, top);
// 设置最终样式
popMenu.style.left = left + "px";
popMenu.style.top = top + "px";
popMenu.style.visibility = "visible";
popMenu.classList.remove("temp_show");
popMenu.classList.add("trigger_popup");
// 添加销毁监听
document.addEventListener("click", snapLinks.destroy, false);
},
openLinks: function(regexp) {
var obj = {};
for (let elem of this.elements) {
if (!elem.href || /^(?:javascript:|mailto:|#)/i.test(elem.href)) continue;
if (!regexp || regexp.test(elem.href))
obj[elem.href] = true;
}
for (let key in obj) {
GM_openInTab(key);
}
},
clickLinks: function() {
for (let elem of this.elements) {
if (!elem.href || /^(?:javascript:|mailto:|#)/i.test(elem.href)) {
elem.click();
}
}
},
copyLinks: function(regexp, reverse, format) {
var links = this.elements.filter(function(elem){
return elem instanceof HTMLAnchorElement && (!regexp || regexp.test(elem.href))
});
var num = 1,
numReverse = links.length;
links = links.map(function(e) {
if (format) {
return format.replace(/%t/g, e.textContent)
.replace(/%u/g, e.href)
.replace(/%r/g, numReverse--)
.replace(/%n/g, num++);
}
return e.href;
});
links = snapLinks.unique(links);
if(reverse)
links = links.reverse();
if (links.length){
GM_setClipboard(links.join('\n'));
}
},
imageOnePage: function() {
var htmlsrc = [
'<style>',
'img { max-width: 100%; max-height: 100%; }',
'</style>'
].join('');
for (let elem of this.elements) {
if (elem instanceof HTMLAnchorElement && /\.(jpe?g|png|gif|bmp)$/i.test(elem.href))
htmlsrc += '\n<img src="' + elem.href + '">'
}
GM_openInTab("data:text/html;charset=utf-8," +
'<html><head><title>' + snapLinks.doc.domain + ' 图象列表</title><body>' +
encodeURIComponent(htmlsrc));
},
checkbox: function(bool) {
for (let elem of this.elements) {
if (elem instanceof HTMLInputElement && elem.type === 'checkbox') {
elem.checked = arguments.length == 0 ?
!elem.checked :
bool;
}
}
},
radio: function(bool) {
for (let elem of this.elements) {
if (elem instanceof HTMLInputElement && elem.type === 'radio') {
elem.checked = arguments.length == 0 ?
!elem.checked :
bool;
}
}
},
unique: function(a) {
var o = {},
r = [],
t;
for (var i = 0, l = a.length; i < l; i++) {
t = a[i];
if(!o[t]){
o[t] = true;
r.push(t);
}
}
return r;
}
};
// 切换开关状态
function toggleScript() {
scriptEnabled = !scriptEnabled;
GM_setValue("enabled", scriptEnabled);
if (scriptEnabled) {
// 开启:激活脚本
snapLinks.init();
} else {
// 关闭:停用脚本,清理所有状态
snapLinks.deactivate();
}
// 更新菜单项名称(可选)
// 由于GM_registerMenuCommand不支持动态修改,这里不实现
}
// 创建菜单项
GM_registerMenuCommand(scriptEnabled ? "禁用 Snap Links" : "启用 Snap Links", toggleScript);
function begin() {
// 避免重复创建菜单
if (document.getElementById("snapLinksMenupopup")) return;
var ibody = document.getElementsByTagName("body")[0];
if (!ibody) return;
var popup = document.createElement("div");
popup.setAttribute("id", "snapLinksMenupopup");
popup.setAttribute("class", "hidden_popup");
popup.innerHTML = '<div>' +
'<div id="SnapLinksOpenLinks" class="hasLink">在新标签打开所有链接</div>' +
'<div id="SnapLinksCopyLinks" class="hasLink">复制所有链接URL</div>' +
'<div id="SnapLinksCopyLinksReverse" class="hasLink">复制所有链接URL(反向)</div>' +
'<div id="SnapLinksCopyLinksAndTitles" class="hasLink">复制所有链接标题 + URL</div>' +
'<div id="SnapLinksCopyLinksAndTitlesMD" class="hasLink">复制所有链接标题 + URL (MD)</div>' +
'<div id="SnapLinksCopyLinksAndTitlesBBS" class="hasLink">复制所有链接标题 + URL (BBS)</div>' +
'<div id="SnapLinksCopyLinksRegExp" class="hasLink">复制所有链接标题 + URL (筛选)</div>' +
'<div id="SnapLinksCopyLinksSetFormat" class="hasLink">复制所有链接标题 + URL (设置复制格式)</div>' +
'<div id="SnapLinksOpenImageLinks" class="hasImageLink">在新标签页打开所有图片链接</div>' +
'<div id="SnapLinksImageLinksOnePage" class="hasImageLink">在一个标签页显示所有图片链接</div>' +
'<div id="SnapLinksCheckBoxSelect" class="hasCheckbox">复选框 - 选中</div>' +
'<div id="SnapLinksCheckBoxCancel" class="hasCheckbox">复选框 - 取消</div>' +
'<div id="SnapLinksCheckBoxTaggle" class="hasCheckbox">复选框 - 反选</div>' +
'<div id="SnapLinksRadioSelect" class="hasRadio">单选框 - 选中</div>' +
'<div id="SnapLinksRadioCancel" class="hasRadio">单选框 - 取消</div>' +
'<div id="SnapLinksClickLinks" class="hasSpecialLink">特殊单选框 - 选中</div>' +
'</div>';
ibody.appendChild(popup);
// 绑定菜单项事件
document.getElementById("SnapLinksOpenLinks").addEventListener("click", function(){ snapLinks.openLinks(); snapLinks.destroy(); }, false);
document.getElementById("SnapLinksCopyLinks").addEventListener("click", function(){ snapLinks.copyLinks(); snapLinks.destroy(); }, false);
document.getElementById("SnapLinksCopyLinksReverse").addEventListener("click", function(){ snapLinks.copyLinks(null, true); snapLinks.destroy(); }, false);
document.getElementById("SnapLinksCopyLinksAndTitles").addEventListener("click", function(){ snapLinks.copyLinks(null, false, '%t\n%u'); snapLinks.destroy(); }, false);
document.getElementById("SnapLinksCopyLinksAndTitlesMD").addEventListener("click", function(){ snapLinks.copyLinks(null, false, '[%t](%u)'); snapLinks.destroy(); }, false);
document.getElementById("SnapLinksCopyLinksAndTitlesBBS").addEventListener("click", function(){ snapLinks.copyLinks(null, false, '[url=%u]%t[/url]'); snapLinks.destroy(); }, false);
document.getElementById("SnapLinksCopyLinksRegExp").addEventListener("click", function(){ var reg=prompt('请输入需要筛选的 RegExp', ''); if(reg) snapLinks.copyLinks(new RegExp(reg)); snapLinks.destroy(); }, false);
document.getElementById("SnapLinksOpenImageLinks").addEventListener("click", function(){ snapLinks.openLinks(/\.(jpe?g|png|gif|bmp)$/i); snapLinks.destroy(); }, false);
document.getElementById("SnapLinksImageLinksOnePage").addEventListener("click", function(){ snapLinks.imageOnePage(); snapLinks.destroy(); }, false);
document.getElementById("SnapLinksCheckBoxSelect").addEventListener("click", function(){ snapLinks.checkbox(true); snapLinks.destroy(); }, false);
document.getElementById("SnapLinksCheckBoxCancel").addEventListener("click", function(){ snapLinks.checkbox(false); snapLinks.destroy(); }, false);
document.getElementById("SnapLinksCheckBoxTaggle").addEventListener("click", function(){ snapLinks.checkbox(); snapLinks.destroy(); }, false);
document.getElementById("SnapLinksRadioSelect").addEventListener("click", function(){ snapLinks.radio(true); snapLinks.destroy(); }, false);
document.getElementById("SnapLinksRadioCancel").addEventListener("click", function(){ snapLinks.radio(false); snapLinks.destroy(); }, false);
document.getElementById("SnapLinksClickLinks").addEventListener("click", function(){ snapLinks.clickLinks(); snapLinks.destroy(); }, false);
GM_addStyle(`
.hidden_popup { display: none !important; }
.trigger_popup {
display: block !important;
position: fixed !important;
z-index: 99999 !important;
background-color: rgb(45,53,63) !important;
border: 1px solid rgb(22,25,28) !important;
border-radius: 4px !important;
padding: 5px !important;
cursor: pointer !important;
box-shadow: 0 1px 0 rgba(162,184,204,0.25) inset, 0 0 4px hsla(0,0%,0%,0.95) !important;
}
.trigger_popup div {
color: white !important;
padding: 2px 8px !important;
white-space: nowrap !important;
}
.trigger_popup > div > div:hover {
color: rgb(51,159,255) !important;
background-color: transparent !important;
background-image: linear-gradient(to bottom, rgb(37,46,54), rgb(36,40,45)) !important;
}
.temp_show {
display: block !important;
position: fixed !important;
visibility: hidden !important;
}
`);
}
begin();
// 根据开关状态决定是否初始化
if (scriptEnabled) {
snapLinks.init();
}