// ==UserScript== // @name B站视频封面高级提取助手 // @namespace http://tampermonkey.net/ // @version 3.3 // @description 可视化选择、折叠重复封面、显示分辨率格式、以“标题+BV/AV号”命名并一键打包ZIP下载,支持一键刷新抓取。 // @author Assistant // @match https://*.bilibili.com/* // @icon https://www.bilibili.com/favicon.ico // @grant none // @require https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js // ==/UserScript== (function() { 'use strict'; // 1. 创建右下角悬浮控制按钮 const floatBtn = document.createElement('div'); floatBtn.innerHTML = '📥 提取封面'; floatBtn.style.cssText = ` position: fixed; bottom: 30px; right: 30px; z-index: 99999; background-color: #00aeec; color: white; padding: 10px 16px; border-radius: 20px; cursor: pointer; font-family: Arial, sans-serif; font-size: 13px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); user-select: none; transition: background 0.2s; `; floatBtn.onmouseover = () => floatBtn.style.backgroundColor = '#00b5e5'; floatBtn.onmouseout = () => floatBtn.style.backgroundColor = '#00aeec'; document.body.appendChild(floatBtn); // 2. 创建可视化交互弹窗面板 const panel = document.createElement('div'); panel.style.cssText = ` position: fixed; bottom: 80px; right: 30px; width: 380px; max-height: 520px; background: #f4f5f7; border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 6px 20px rgba(0,0,0,0.2); z-index: 100000; display: none; flex-direction: column; font-family: Arial, sans-serif; padding: 12px; box-sizing: border-box; `; panel.innerHTML = `

B站封面高级提取工具

点击按钮开始加载封面...
准备就绪
`; document.body.appendChild(panel); let mainGroups = []; // 点击悬浮按钮开关面板并触发抓取 floatBtn.onclick = () => { if (panel.style.display === 'flex') { panel.style.display = 'none'; } else { panel.style.display = 'flex'; fetchAndRenderCovers(); } }; // 绑定刷新按钮事件 document.getElementById('tm-refresh').onclick = () => { fetchAndRenderCovers(); }; // 抓取页面视频封面 async function fetchAndRenderCovers() { const statusDiv = document.getElementById('tm-status'); const coverListDiv = document.getElementById('tm-cover-list'); statusDiv.textContent = '正在重新获取页面视频与封面...'; coverListDiv.innerHTML = '
正在重新扫描页面卡片...
'; const selectors = [ '.bili-video-card', '.feed-card', '.video-card-common', '.video-page-card-small', '.related-card', '.card-box', '.rank-item' ]; const cards = document.querySelectorAll(selectors.join(',')); const rawCovers = []; cards.forEach(card => { const imgElement = card.querySelector('img'); const titleElement = card.querySelector('.bili-video-card__info--tit, .bili-video-card__title, .title, .video-name'); const linkElement = card.querySelector('a'); if (imgElement) { let url = imgElement.getAttribute('data-src') || imgElement.getAttribute('src') || imgElement.src; let title = 'bilibili_video'; if (titleElement) { title = titleElement.getAttribute('title') || titleElement.innerText.trim(); } else if (imgElement.alt) { title = imgElement.alt; } let bvid = ''; let href = linkElement ? linkElement.href : ''; const bvMatch = href.match(/(BV[1-9a-zA-Z]{10})/); const avMatch = href.match(/(av\d+)/i); if (bvMatch) { bvid = bvMatch[1]; } else if (avMatch) { bvid = avMatch[1]; } if (url && !url.startsWith('data:image')) { rawCovers.push({ url, title, bvid }); } } }); if (rawCovers.length === 0) { coverListDiv.innerHTML = '
未在当前页面找到视频封面
'; statusDiv.textContent = '未找到封面'; return; } statusDiv.textContent = '正在分析图片分辨率与格式...'; const urlMap = new Map(); for (const item of rawCovers) { let cleanUrl = item.url; if (cleanUrl.startsWith('//')) cleanUrl = 'https:' + cleanUrl; cleanUrl = cleanUrl.split('@')[0]; const meta = await getImageMeta(cleanUrl); const fullItem = { ...item, cleanUrl, ...meta }; if (!urlMap.has(cleanUrl)) { urlMap.set(cleanUrl, { primary: fullItem, duplicates: [] }); } else { urlMap.get(cleanUrl).duplicates.push(fullItem); } } mainGroups = Array.from(urlMap.values()); renderList(mainGroups); statusDiv.textContent = `刷新完成:发现 ${mainGroups.length} 个独立封面`; } // 异步加载图片获取分辨率和格式 function getImageMeta(url) { return new Promise((resolve) => { const img = new Image(); img.crossOrigin = "anonymous"; img.onload = () => { let format = 'JPG'; if (url.includes('.webp')) format = 'WEBP'; else if (url.includes('.png')) format = 'PNG'; resolve({ width: img.naturalWidth, height: img.naturalHeight, format }); }; img.onerror = () => resolve({ width: 0, height: 0, format: 'Unknown' }); img.src = url; }); } // 渲染列表结构 function renderList(groups) { const coverListDiv = document.getElementById('tm-cover-list'); coverListDiv.innerHTML = ''; groups.forEach((group, groupIndex) => { const item = group.primary; const groupDiv = document.createElement('div'); groupDiv.className = 'cover-group'; groupDiv.style.cssText = 'border-bottom: 1px solid #eee; padding: 4px 0;'; const primaryItemDiv = document.createElement('div'); primaryItemDiv.className = 'cover-item'; primaryItemDiv.style.cssText = 'display: flex; align-items: center; gap: 8px; padding: 4px; font-size: 11px;'; primaryItemDiv.innerHTML = ` 封面
${item.title}
${item.bvid ? `${item.bvid}` : ''} ${item.width}x${item.height} | ${item.format}
`; groupDiv.appendChild(primaryItemDiv); if (group.duplicates.length > 0) { const toggleDiv = document.createElement('div'); toggleDiv.style.cssText = 'font-size: 10px; color: #666; background: #fafafa; padding: 4px 8px; cursor: pointer; border: 1px dashed #ddd; border-radius: 3px; margin: 2px 0 4px 28px; text-align: center; user-select: none;'; toggleDiv.textContent = `📂 还有 ${group.duplicates.length} 个相似/重复来源(点击展开折叠)`; const containerDiv = document.createElement('div'); containerDiv.style.cssText = 'display: none; padding-left: 20px; background: #fdfdfd;'; group.duplicates.forEach((dup, dupIndex) => { const dupItemDiv = document.createElement('div'); dupItemDiv.style.cssText = 'display: flex; align-items: center; gap: 8px; padding: 4px; font-size: 11px; border-top: 1px solid #f5f5f5;'; dupItemDiv.innerHTML = ` 重复封面
${dup.title}
${dup.width}x${dup.height} | ${dup.format}
`; containerDiv.appendChild(dupItemDiv); }); toggleDiv.addEventListener('click', () => { if (containerDiv.style.display === 'block') { containerDiv.style.display = 'none'; toggleDiv.textContent = toggleDiv.textContent.replace('点击收起', '点击展开折叠'); } else { containerDiv.style.display = 'block'; toggleDiv.textContent = toggleDiv.textContent.replace('点击展开折叠', '点击收起'); } }); groupDiv.appendChild(toggleDiv); groupDiv.appendChild(containerDiv); } coverListDiv.appendChild(groupDiv); }); } // 按钮交互事件 document.getElementById('tm-selectAll').onclick = () => { document.querySelectorAll('#tm-cover-list input[type="checkbox"]').forEach(chk => chk.checked = true); }; document.getElementById('tm-deselectAll').onclick = () => { document.querySelectorAll('#tm-cover-list input[type="checkbox"]').forEach(chk => chk.checked = false); }; // 打包下载 ZIP document.getElementById('tm-download').onclick = async () => { const checkboxes = document.querySelectorAll('#tm-cover-list input[type="checkbox"]:checked'); if (checkboxes.length === 0) { alert('请至少选择一个封面!'); return; } const statusDiv = document.getElementById('tm-status'); statusDiv.textContent = `正在打包 ${checkboxes.length} 张图片,请稍候...`; const zip = new JSZip(); const folder = zip.folder("bilibili_covers"); for (const chk of checkboxes) { const gIndex = chk.getAttribute('data-group'); const type = chk.getAttribute('data-type'); let targetItem; if (type === 'primary') { targetItem = mainGroups[gIndex].primary; } else { const dIndex = chk.getAttribute('data-dupindex'); targetItem = mainGroups[gIndex].duplicates[dIndex]; } let idStr = targetItem.bvid ? `_${targetItem.bvid}` : ''; let cleanTitle = targetItem.title.replace(/[/\\?%*:|"<>]/g, '').slice(0, 30); let fileName = `${cleanTitle}${idStr}.${targetItem.format.toLowerCase()}`; try { const response = await fetch(targetItem.cleanUrl); const blob = await response.blob(); folder.file(fileName, blob); } catch (e) { console.error('下载图片失败:', targetItem.cleanUrl); } } const content = await zip.generateAsync({ type: "blob" }); const url = URL.createObjectURL(content); const a = document.createElement('a'); a.href = url; a.download = `bili_covers_${Date.now()}.zip`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); statusDiv.textContent = '打包下载成功!'; }; })();