// ==UserScript== // @name Alt0501增强版链接收集器 · 可多选/批量打开/导入书签/可调整大小 // @version 3.0 // @description 按Alt键复制鼠标下链接,按B键复制B站视频,悬浮面板可移动/折叠/缩放,支持勾选链接,一键导出TXT、批量打开、导入书签 // @author You // @match *://*/* // @grant GM_setClipboard // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant window.open // @run-at document-end // @namespace https://greasyfork.org/users/702964 // @downloadURL https://update.greasyfork.icu/scripts/576173/Alt0501%E5%A2%9E%E5%BC%BA%E7%89%88%E9%93%BE%E6%8E%A5%E6%94%B6%E9%9B%86%E5%99%A8%20%C2%B7%20%E5%8F%AF%E5%A4%9A%E9%80%89%E6%89%B9%E9%87%8F%E6%89%93%E5%BC%80%E5%AF%BC%E5%85%A5%E4%B9%A6%E7%AD%BE%E5%8F%AF%E8%B0%83%E6%95%B4%E5%A4%A7%E5%B0%8F.user.js // @updateURL https://update.greasyfork.icu/scripts/576173/Alt0501%E5%A2%9E%E5%BC%BA%E7%89%88%E9%93%BE%E6%8E%A5%E6%94%B6%E9%9B%86%E5%99%A8%20%C2%B7%20%E5%8F%AF%E5%A4%9A%E9%80%89%E6%89%B9%E9%87%8F%E6%89%93%E5%BC%80%E5%AF%BC%E5%85%A5%E4%B9%A6%E7%AD%BE%E5%8F%AF%E8%B0%83%E6%95%B4%E5%A4%A7%E5%B0%8F.meta.js // ==/UserScript== (function() { 'use strict'; // ---------- 存储结构 ---------- let linksData = []; // 每个元素 { url, selected } let mouseX = 0, mouseY = 0; let isProcessing = false; // 加载已保存的链接 const saved = GM_getValue('links_data', '[]'); try { linksData = JSON.parse(saved); if (!Array.isArray(linksData)) linksData = []; // 确保每个对象有 selected 字段 linksData = linksData.map(item => ({ url: item.url, selected: item.selected === true })); } catch(e) { linksData = []; } // ---------- 辅助函数 ---------- function saveLinks() { GM_setValue('links_data', JSON.stringify(linksData)); } function renderList() { // 重新渲染带复选框的列表 listWrap.innerHTML = ''; linksData.forEach((item, idx) => { const row = document.createElement('div'); row.style.cssText = 'display: flex; align-items: center; padding: 6px 0; border-bottom: 1px solid #eee; gap: 6px;'; const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = item.selected; cb.style.margin = '0'; cb.onchange = (e) => { item.selected = e.target.checked; saveLinks(); updateSelectedCount(); }; const linkSpan = document.createElement('span'); linkSpan.style.cssText = 'flex:1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer;'; linkSpan.title = item.url; linkSpan.textContent = item.url; linkSpan.onclick = () => { // 单击链接可将它复制到剪贴板 GM_setClipboard(item.url); showToast('📋 已复制: ' + item.url); }; const delBtn = document.createElement('span'); delBtn.textContent = '🗑️'; delBtn.style.cursor = 'pointer'; delBtn.style.fontSize = '14px'; delBtn.onclick = (e) => { e.stopPropagation(); linksData.splice(idx, 1); saveLinks(); renderList(); updateSelectedCount(); showToast('已删除'); }; row.appendChild(cb); row.appendChild(linkSpan); row.appendChild(delBtn); listWrap.appendChild(row); }); updateSelectedCount(); // 如果没有链接,显示提示 if (linksData.length === 0) { const emptyDiv = document.createElement('div'); emptyDiv.textContent = '暂无链接,按 Alt 键或 B 键收集'; emptyDiv.style.padding = '20px'; emptyDiv.style.textAlign = 'center'; emptyDiv.style.color = '#999'; listWrap.appendChild(emptyDiv); } } function updateSelectedCount() { const count = linksData.filter(item => item.selected).length; if (count > 0) { dragBarTitle.innerHTML = `快捷链接收集 (${count}/${linksData.length})`; } else { dragBarTitle.innerHTML = `快捷链接收集 (${linksData.length})`; } } // 添加新链接 function addLink(url) { if (!url || !url.startsWith('http')) return false; // 去重(基于 url) const exists = linksData.some(item => item.url === url); if (!exists) { linksData.unshift({ url: url, selected: false }); saveLinks(); renderList(); showToast(`➕ 已添加: ${url}`); return true; } else { showToast(`⚠️ 链接已存在: ${url}`); return false; } } // 获取鼠标下方的链接 function getLinkUnderCursor() { const elements = document.elementsFromPoint(mouseX, mouseY); for (const el of elements) { const a = el.closest('a[href]'); if (a && a.href) { const href = a.href.trim(); if (href.startsWith('http')) return href; } } return null; } // Alt 键复制鼠标下链接 function altCopyLink() { if (isProcessing) return; isProcessing = true; let link = getLinkUnderCursor(); if (!link) { // 如果没有链接,则复制当前页面地址 link = window.location.href; } if (link) { GM_setClipboard(link); addLink(link); showToast(`📋 已复制并添加: ${link}`); } else { showToast('❌ 未找到链接', true); } setTimeout(() => { isProcessing = false; }, 300); } // B 键复制 B站视频链接 const BV_REG = /(?:video\/|bvid=)(BV[a-zA-Z0-9]+)/i; async function biliCopy() { if (isProcessing) return; isProcessing = true; let bv = null; const url = location.href; let m = url.match(BV_REG); if (m) bv = m[1]; if (!bv) { const elements = document.elementsFromPoint(mouseX, mouseY); for (const el of elements) { const a = el.closest('a[href*="video"]'); if (a && a.href) { m = a.href.match(BV_REG); if (m) { bv = m[1]; break; } } } } if (bv) { const link = `https://www.bilibili.com/video/${bv}`; GM_setClipboard(link); addLink(link); showToast('🎬 B站链接已复制并添加'); } else { showToast('❌ 未找到B站视频', true); } setTimeout(() => { isProcessing = false; }, 300); } // ---------- 批量操作 ---------- function getSelectedUrls() { return linksData.filter(item => item.selected).map(item => item.url); } function batchOpen() { const urls = getSelectedUrls(); if (urls.length === 0) { showToast('请至少勾选一个链接', true); return; } for (let url of urls) { window.open(url, '_blank'); } showToast(`🚀 已打开 ${urls.length} 个链接(注意浏览器可能拦截弹窗,请允许弹出窗口)`); } function batchExportTxt() { const urls = getSelectedUrls(); if (urls.length === 0) { showToast('请至少勾选一个链接', true); return; } const txt = urls.join('\n'); const blob = new Blob([txt], { type: 'text/plain' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `links_${Date.now()}.txt`; a.click(); URL.revokeObjectURL(a.href); showToast(`📄 已导出 ${urls.length} 条链接`); } // 导入书签(需要书签 API 权限,普通网页无法直接添加书签,只能通过 bookmark API,但油猴无法直接调用浏览器书签 API) // 替代方案:生成一个 HTML 书签文件供用户导入,或者引导用户手动添加。 // 更优雅:生成一个 data: 文本,提示用户保存为 .html 然后导入浏览器书签管理器。 function batchImportBookmarks() { const urls = getSelectedUrls(); if (urls.length === 0) { showToast('请至少勾选一个链接', true); return; } // 生成 Netscape 书签格式 let html = '\n'; html += '\n'; html += '
\n'; for (let url of urls) { let title = url.replace(/^https?:\/\//, '').substring(0, 50); html += `
'; const blob = new Blob([html], { type: 'text/html' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `bookmarks_${Date.now()}.html`; a.click(); URL.revokeObjectURL(a.href); showToast(`📑 已生成书签文件,请手动导入浏览器书签管理器(通常导入HTML)`); } // ---------- 创建可调整大小的悬浮面板 ---------- const container = document.createElement('div'); container.style.cssText = ` position: fixed; z-index: 999999; width: 320px; min-width: 200px; background: #fff; border-radius: 10px; box-shadow: 0 4px 20px rgba(0,0,0,0.25); font-size: 12px; font-family: system-ui, sans-serif; overflow: hidden; `; const savedWidth = GM_getValue('panel_width', 320); const savedHeight = GM_getValue('panel_height', null); container.style.width = savedWidth + 'px'; if (savedHeight) container.style.height = savedHeight + 'px'; const dragBar = document.createElement('div'); dragBar.style.cssText = ` background: #fb7299; color: white; padding: 8px 12px; font-weight: bold; cursor: move; display: flex; justify-content: space-between; align-items: center; user-select: none; `; const dragBarTitle = document.createElement('span'); dragBarTitle.textContent = '快捷链接收集'; const foldBtn = document.createElement('span'); foldBtn.textContent = '−'; foldBtn.style.cursor = 'pointer'; foldBtn.style.fontSize = '18px'; foldBtn.style.marginLeft = '8px'; dragBar.appendChild(dragBarTitle); dragBar.appendChild(foldBtn); const content = document.createElement('div'); content.style.padding = '10px'; content.style.overflow = 'auto'; // 如果保存了高度,内容区域高度需要减去标题栏 if (savedHeight) { content.style.maxHeight = (savedHeight - 40) + 'px'; } else { content.style.maxHeight = '300px'; } const listWrap = document.createElement('div'); listWrap.style.maxHeight = '200px'; listWrap.style.overflowY = 'auto'; listWrap.style.marginBottom = '8px'; const btnGroup = document.createElement('div'); btnGroup.style.display = 'flex'; btnGroup.style.flexWrap = 'wrap'; btnGroup.style.gap = '6px'; btnGroup.style.marginTop = '8px'; const openBtn = document.createElement('button'); openBtn.textContent = '后台打开'; openBtn.style.cssText = 'flex:1; padding:6px; background:#ff9800; color:#fff; border:none; border-radius:4px; cursor:pointer;'; const exportBtn = document.createElement('button'); exportBtn.textContent = '导出TXT'; exportBtn.style.cssText = 'flex:1; padding:6px; background:#2196f3; color:#fff; border:none; border-radius:4px; cursor:pointer;'; const bookmarkBtn = document.createElement('button'); bookmarkBtn.textContent = '导入书签'; bookmarkBtn.style.cssText = 'flex:1; padding:6px; background:#4caf50; color:#fff; border:none; border-radius:4px; cursor:pointer;'; const clearBtn = document.createElement('button'); clearBtn.textContent = '清空'; clearBtn.style.cssText = 'flex:1; padding:6px; background:#f44336; color:#fff; border:none; border-radius:4px; cursor:pointer;'; const copyAllBtn = document.createElement('button'); copyAllBtn.textContent = '复制全部'; copyAllBtn.style.cssText = 'flex:1; padding:6px; background:#9c27b0; color:#fff; border:none; border-radius:4px; cursor:pointer;'; btnGroup.append(openBtn, exportBtn, bookmarkBtn, clearBtn, copyAllBtn); content.append(listWrap, btnGroup); container.append(dragBar, content); document.body.appendChild(container); // 添加调整大小手柄(右下角) const resizeHandle = document.createElement('div'); resizeHandle.style.cssText = ` position: absolute; bottom: 0; right: 0; width: 15px; height: 15px; cursor: nw-resize; background: linear-gradient(135deg, transparent 50%, #aaa 50%); border-bottom-right-radius: 10px; z-index: 1000000; `; container.appendChild(resizeHandle); // 恢复位置 const savedTop = GM_getValue('panel_top', '100px'); const savedLeft = GM_getValue('panel_left', '20px'); container.style.top = savedTop; container.style.left = savedLeft; // 折叠状态 const isFolded = GM_getValue('panel_folded', false); if (isFolded) { content.style.display = 'none'; foldBtn.textContent = '+'; } // 折叠事件 foldBtn.onclick = () => { if (content.style.display === 'none') { content.style.display = 'block'; foldBtn.textContent = '−'; GM_setValue('panel_folded', false); } else { content.style.display = 'none'; foldBtn.textContent = '+'; GM_setValue('panel_folded', true); } }; // 拖拽移动(复用原逻辑) function makeDraggable(el, handle, onMove) { let pos = { x: 0, y: 0, mx: 0, my: 0 }; handle.onmousedown = e => { if (e.target === resizeHandle) return; e.preventDefault(); pos.mx = e.clientX; pos.my = e.clientY; document.onmousemove = move; document.onmouseup = up; }; function move(e) { pos.x = pos.mx - e.clientX; pos.y = pos.my - e.clientY; pos.mx = e.clientX; pos.my = e.clientY; let top = el.offsetTop - pos.y; let left = el.offsetLeft - pos.x; el.style.top = top + 'px'; el.style.left = left + 'px'; if (onMove) onMove(top, left); } function up() { document.onmousemove = null; document.onmouseup = null; } } makeDraggable(container, dragBar, (top, left) => { GM_setValue('panel_top', top + 'px'); GM_setValue('panel_left', left + 'px'); }); // 调整大小功能 let resizeStart = false, startX, startY, startW, startH; resizeHandle.onmousedown = (e) => { e.preventDefault(); e.stopPropagation(); resizeStart = true; startX = e.clientX; startY = e.clientY; startW = container.offsetWidth; startH = container.offsetHeight; document.onmousemove = doResize; document.onmouseup = stopResize; }; function doResize(e) { if (!resizeStart) return; let newW = startW + (e.clientX - startX); let newH = startH + (e.clientY - startY); if (newW < 200) newW = 200; if (newH < 150) newH = 150; container.style.width = newW + 'px'; container.style.height = newH + 'px'; content.style.maxHeight = (newH - 40) + 'px'; GM_setValue('panel_width', newW); GM_setValue('panel_height', newH); } function stopResize() { resizeStart = false; document.onmousemove = null; document.onmouseup = null; } // 按钮事件绑定 openBtn.onclick = batchOpen; exportBtn.onclick = batchExportTxt; bookmarkBtn.onclick = batchImportBookmarks; clearBtn.onclick = () => { if (confirm('确定清空所有链接吗?')) { linksData = []; saveLinks(); renderList(); showToast('已清空'); } }; copyAllBtn.onclick = () => { const allUrls = linksData.map(item => item.url).join('\n'); GM_setClipboard(allUrls); showToast(`📋 已复制全部 ${linksData.length} 条链接`); }; // 鼠标移动追踪 document.addEventListener('mousemove', (e) => { mouseX = e.clientX; mouseY = e.clientY; }); // 键盘监听:Alt 键(左Alt或右Alt)和 B 键 document.addEventListener('keydown', (e) => { const tag = e.target.tagName.toLowerCase(); if (tag === 'input' || tag === 'textarea' || e.target.isContentEditable) return; if (e.key === 'Alt' || e.code === 'AltLeft' || e.code === 'AltRight') { e.preventDefault(); altCopyLink(); } else if (e.key === 'b' || e.key === 'B') { e.preventDefault(); biliCopy(); } }); // 初始渲染 renderList(); // 简单的toast提示 function showToast(msg, isErr = false) { let toast = document.getElementById('customToast'); if (!toast) { toast = document.createElement('div'); toast.id = 'customToast'; toast.style.cssText = ` position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%); background: #333; color: #fff; padding: 8px 16px; border-radius: 6px; z-index: 10000000; font-size: 13px; opacity: 0; transition: 0.2s; pointer-events: none; `; document.body.appendChild(toast); } toast.textContent = msg; toast.style.background = isErr ? '#e74c3c' : '#2c3e50'; toast.style.opacity = '1'; setTimeout(() => { toast.style.opacity = '0'; }, 2000); } // 加上一些防冲突的样式 GM_addStyle(` #customToast { font-family: system-ui, sans-serif; } `); })();