// ==UserScript== // @name 网页图片采集器 // @namespace http://tampermonkey.net/ // @version 3.9 // @description 无限制下载网页中的图片 // @author YourName // @match *://*/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_setClipboard // @grant GM_notification // @grant GM_download // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js // @icon https://cdn-icons-png.flaticon.com/512/2107/2107957.png // @license MIT // ==/UserScript== (function() { 'use strict'; const CONFIG = { buttonSize: 30, activeColor: '#e74c3c', hoverColor: '#c0392b', zIndex: 99999, positionOffset: 25, touchDelay: 300, supportFormats: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'tiff'], maxPreviewSize: 50, loadTimeout: 5000, maxTitleLength: 30 }; GM_addStyle(` .radar-container {position:fixed;z-index:${CONFIG.zIndex};cursor:move;transition:transform 0.2s;touch-action:none;} .radar-button {width:${CONFIG.buttonSize}px;height:${CONFIG.buttonSize}px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#e74c3c,#922b21);box-shadow:0 6px 18px rgba(0,0,0,0.3),0 0 0 4px rgba(255,255,255,0.15),inset 0 0 12px rgba(0,0,0,0.3);cursor:pointer;border:none;outline:none;position:relative;overflow:hidden;user-select:none;-webkit-tap-highlight-color:transparent;animation:pulse 2s infinite;transition:transform 0.3s,box-shadow 0.3s;} .radar-button:hover {transform:scale(1.05);box-shadow:0 8px 22px rgba(0,0,0,0.4),0 0 0 4px rgba(255,255,255,0.25),inset 0 0 15px rgba(0,0,0,0.4);} .radar-button:active {transform:scale(0.95);} .radar-icon {width:24px;height:24px;position:relative;display:flex;justify-content:center;align-items:center;filter:drop-shadow(0 0 2px rgba(255,255,255,0.5));animation:radar-scan 4s linear infinite;} .radar-icon svg {width:100%;height:100%;} #svgSnifferModal {display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:90%;max-width:900px;max-height:85vh;background:white;z-index:10000;border-radius:12px;box-shadow:0 10px 40px rgba(0,0,0,0.3);overflow:hidden;font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif;} .modal-header {background:linear-gradient(135deg,#e74c3c,#922b21);color:white;padding:18px 25px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid rgba(255,255,255,0.2);} .modal-header h2 {margin:0;font-size:1.4rem;font-weight:600;} .close-btn {background:none;border:none;color:white;font-size:1.8rem;cursor:pointer;width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;transition:background 0.2s;} .close-btn:hover {background:rgba(255,255,255,0.2);} .action-bar {display:flex;justify-content:space-between;align-items:center;padding:15px 25px;background:#f8f9fa;border-bottom:1px solid #e9ecef;} .select-all-control {display:flex;align-items:center;gap:10px;font-size:1rem;} .action-buttons {display:flex;gap:12px;} .action-btn {padding:10px 20px;border:none;border-radius:6px;cursor:pointer;font-weight:600;font-size:0.95rem;transition:all 0.2s;} .download-btn {background:#27ae60;color:white;} .download-btn:hover {background:#219653;transform:translateY(-2px);} .copy-btn {background:#2980b9;color:white;} .copy-btn:hover {background:#2573a7;transform:translateY(-2px);} .item-download-btn {padding:6px 12px;background:#e74c3c;color:white;border:none;border-radius:4px;cursor:pointer;font-size:0.85rem;transition:background 0.2s;white-space:nowrap;} .item-download-btn:hover {background:#c0392b;} .modal-content {padding:20px;overflow-y:auto;max-height:65vh;} .svg-item { display:flex; align-items:center; padding:15px; border-bottom:1px solid #eee; transition:background 0.2s; justify-content:space-between; flex-wrap:nowrap; min-width:0; } .svg-item:hover {background-color:#f8fafc;} .svg-checkbox {margin-right:20px;width:20px;height:20px;cursor:pointer;flex-shrink:0;} .svg-preview {width:${CONFIG.maxPreviewSize}px;height:${CONFIG.maxPreviewSize}px;margin-right:20px;border:1px solid #e0e0e0;display:flex;align-items:center;justify-content:center;border-radius:6px;background:#f9f9f9;box-shadow:0 2px 6px rgba(0,0,0,0.05);overflow:hidden;flex-shrink:0;} .svg-preview img,.svg-preview svg {max-width:100%;max-height:100%;object-fit:contain;} .svg-info { flex-grow:1; margin-right:15px; min-width:0; } .svg-name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:1.05rem; color:#2c3e50; margin-bottom:4px; max-width:100%; tooltip-delay: 300ms; } .svg-meta { font-size:0.85rem; color:#6e6e73; display:flex; gap:15px; flex-wrap:wrap; max-width:100%; } .overlay {position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:9999;display:none;} .loading {text-align:center;padding:30px;font-size:1.2rem;color:#666;} .copy-notification {position:fixed;top:30px;left:50%;transform:translateX(-50%);background-color:#27ae60;color:white;padding:12px 25px;border-radius:6px;z-index:100000;opacity:0;transition:opacity 0.5s;pointer-events:none;white-space:nowrap;font-weight:500;box-shadow:0 4px 12px rgba(0,0,0,0.15);} @keyframes radar-scan {0% {transform:rotate(0deg);} 100% {transform:rotate(360deg);}} @keyframes pulse {0% {box-shadow:0 0 0 0 rgba(231,76,60,0.6);} 70% {box-shadow:0 0 0 12px rgba(231,76,60,0);} 100% {box-shadow:0 0 0 0 rgba(231,76,60,0);}} .temp-visible-for-scan {display:block !important;visibility:visible !important;opacity:1 !important;position:absolute !important;top:-9999px !important;left:-9999px !important;width:auto !important;height:auto !important;} `); const radarContainer = document.createElement('div'); radarContainer.className = 'radar-container'; radarContainer.id = 'radarContainer'; const radarButton = document.createElement('div'); radarButton.className = 'radar-button'; radarButton.id = 'radarButton'; radarButton.innerHTML = `
`; radarContainer.appendChild(radarButton); document.body.appendChild(radarContainer); const svgModal = document.createElement('div'); svgModal.id = 'svgSnifferModal'; svgModal.innerHTML = `
`; document.body.appendChild(svgModal); const overlay = document.createElement('div'); overlay.className = 'overlay'; document.body.appendChild(overlay); const copyNotification = document.createElement('div'); copyNotification.className = 'copy-notification'; document.body.appendChild(copyNotification); let globalImageItems = []; let imageItemCache = new Map(); let isDragging = false; let startX, startY, startLeft, startTop; let dragStartTime = 0; let touchTimer = null; let blobUrls = []; let tempVisibleElements = []; function initRadarButton() { const domain = location.hostname.replace(/\./g, '-'); const positionKey = `radarPosition_${domain}`; const savedPosition = GM_getValue(positionKey); if (savedPosition) { radarContainer.style.left = `${savedPosition.x}px`; radarContainer.style.top = `${savedPosition.y}px`; } else { radarContainer.style.right = `${CONFIG.positionOffset}px`; radarContainer.style.bottom = `${CONFIG.positionOffset}px`; } radarContainer.addEventListener('mousedown', startDrag); radarContainer.addEventListener('touchstart', startDrag, { passive: false }); radarButton.addEventListener('click', (e) => { if (!isDragging && Date.now() - dragStartTime > CONFIG.touchDelay) { showImageList(); } }); } function startDrag(e) { e.preventDefault(); const clientX = e.clientX || e.touches[0].clientX; const clientY = e.clientY || e.touches[0].clientY; const computedStyle = window.getComputedStyle(radarContainer); startLeft = parseInt(computedStyle.left) || 0; startTop = parseInt(computedStyle.top) || 0; if (computedStyle.right !== 'auto') { const rightPos = parseInt(computedStyle.right); startLeft = window.innerWidth - rightPos - CONFIG.buttonSize; radarContainer.style.right = 'auto'; radarContainer.style.left = `${startLeft}px`; } startX = clientX; startY = clientY; dragStartTime = Date.now(); if (e.type === 'touchstart') { touchTimer = setTimeout(() => { isDragging = true; radarContainer.style.transition = 'none'; }, CONFIG.touchDelay); } else { isDragging = true; } document.addEventListener('mousemove', drag); document.addEventListener('touchmove', drag, { passive: false }); document.addEventListener('mouseup', endDrag); document.addEventListener('touchend', endDrag); } function drag(e) { if (!isDragging) return; e.preventDefault(); const clientX = e.clientX || e.touches[0].clientX; const clientY = e.clientY || e.touches[0].clientY; const dx = clientX - startX; const dy = clientY - startY; radarContainer.style.left = `${startLeft + dx}px`; radarContainer.style.top = `${startTop + dy}px`; radarContainer.style.right = 'auto'; } function endDrag(e) { if (touchTimer) { clearTimeout(touchTimer); touchTimer = null; } if (!isDragging) { if (Date.now() - dragStartTime < CONFIG.touchDelay) { showImageList(); } return; } isDragging = false; radarContainer.style.transition = ''; document.removeEventListener('mousemove', drag); document.removeEventListener('touchmove', drag); document.removeEventListener('mouseup', endDrag); document.removeEventListener('touchend', endDrag); const domain = location.hostname.replace(/\./g, '-'); const positionKey = `radarPosition_${domain}`; const rect = radarContainer.getBoundingClientRect(); GM_setValue(positionKey, { x: rect.left, y: rect.top }); } function tempShowHiddenElements() { tempVisibleElements = []; const hiddenSelectors = [ 'div[style*="display:none"]', 'div[style*="visibility:hidden"]', 'div[style*="opacity:0"]', '.errorpage[style*="display:none"]', '[class*="hidden"]', '[hidden]' ]; hiddenSelectors.forEach(selector => { const elements = document.querySelectorAll(selector); elements.forEach(el => { const originalStyle = { display: el.style.display, visibility: el.style.visibility, opacity: el.style.opacity, position: el.style.position, top: el.style.top, left: el.style.left, width: el.style.width, height: el.style.height, className: el.className }; tempVisibleElements.push({ el, originalStyle }); el.classList.add('temp-visible-for-scan'); el.style.display = ''; el.style.visibility = ''; el.style.opacity = ''; }); }); } function restoreHiddenElements() { tempVisibleElements.forEach(({ el, originalStyle }) => { el.classList.remove('temp-visible-for-scan'); el.style.display = originalStyle.display; el.style.visibility = originalStyle.visibility; el.style.opacity = originalStyle.opacity; el.style.position = originalStyle.position; el.style.top = originalStyle.top; el.style.left = originalStyle.left; el.style.width = originalStyle.width; el.style.height = originalStyle.height; el.className = originalStyle.className; }); tempVisibleElements = []; } async function collectImagesFromCss(cssUrl) { const imageUrls = []; try { const response = await fetch(cssUrl, { headers: { 'Accept': 'text/css,*/*;q=0.1' }, credentials: 'same-origin' }); if (!response.ok) throw new Error(`CSS请求失败: ${response.status}`); const cssText = await response.text(); const bgUrlRegex = /background-image\s*:\s*url\(["']?([^"']+)["']?\)/gi; let match; while ((match = bgUrlRegex.exec(cssText)) !== null) { if (match[1]) { const fullUrl = new URL(match[1], cssUrl).href; const ext = getFileExtension(fullUrl).toLowerCase(); if (CONFIG.supportFormats.includes(ext)) { imageUrls.push(fullUrl); } } } } catch (e) { console.warn('采集CSS中的图片失败:', cssUrl, e); } return imageUrls; } async function collectAllCssResources() { const cssUrls = []; const linkElements = document.querySelectorAll('link[rel="stylesheet"]'); linkElements.forEach(link => { const href = link.getAttribute('href'); if (href) { cssUrls.push(new URL(href, window.location.href).href); } }); const styleElements = document.querySelectorAll('style'); styleElements.forEach(style => { const bgUrlRegex = /background-image\s*:\s*url\(["']?([^"']+)["']?\)/gi; let match; while ((match = bgUrlRegex.exec(style.textContent)) !== null) { if (match[1]) { const fullUrl = new URL(match[1], window.location.href).href; const ext = getFileExtension(fullUrl).toLowerCase(); if (CONFIG.supportFormats.includes(ext)) { cssUrls.push(`inline:${fullUrl}`); } } } }); const allImageUrls = []; for (const cssUrl of cssUrls) { if (cssUrl.startsWith('inline:')) { allImageUrls.push(cssUrl.replace('inline:', '')); } else { const imagesFromCss = await collectImagesFromCss(cssUrl); allImageUrls.push(...imagesFromCss); } } return allImageUrls; } function truncateTitle(title) { if (!title || title.length <= CONFIG.maxTitleLength) { return title; } return title.substring(0, CONFIG.maxTitleLength) + '...'; } async function collectImages() { const imageItems = []; const processedUrls = new Set(); let cssImageUrls = []; try { cssImageUrls = await collectAllCssResources(); } catch (e) { console.warn('CSS图片采集异常:', e); } tempShowHiddenElements(); try { const imgElements = document.querySelectorAll('img'); for (const img of imgElements) { try { let imgUrl = img.src || img.dataset.src || img.dataset.original; if (!imgUrl || processedUrls.has(imgUrl)) continue; const fullUrl = new URL(imgUrl, window.location.href).href; const ext = getFileExtension(fullUrl).toLowerCase(); if (!CONFIG.supportFormats.includes(ext)) continue; const imgInfo = await new Promise((resolve) => { const timer = setTimeout(() => { resolve({ id: `img-${Date.now()}-${Math.random().toString(36).slice(2)}`, url: fullUrl, name: getImageName(fullUrl, img.alt), format: ext, width: img.width || '未知', height: img.height || '未知', type: 'img-tag', preview: fullUrl }); }, CONFIG.loadTimeout); if (img.complete) { clearTimeout(timer); resolve({ id: `img-${Date.now()}-${Math.random().toString(36).slice(2)}`, url: fullUrl, name: getImageName(fullUrl, img.alt), format: ext, width: img.naturalWidth || img.width || '未知', height: img.naturalHeight || img.height || '未知', type: 'img-tag', preview: fullUrl }); } else { img.onload = () => { clearTimeout(timer); resolve({ id: `img-${Date.now()}-${Math.random().toString(36).slice(2)}`, url: fullUrl, name: getImageName(fullUrl, img.alt), format: ext, width: img.naturalWidth || img.width || '未知', height: img.naturalHeight || img.height || '未知', type: 'img-tag', preview: fullUrl }); }; img.onerror = () => { clearTimeout(timer); resolve(null); }; } }); if (imgInfo) { processedUrls.add(imgUrl); imageItems.push(imgInfo); } } catch (e) { console.warn('采集标签失败:', e); } } const elementsWithBg = document.querySelectorAll('*'); for (const el of elementsWithBg) { try { const bgStyle = window.getComputedStyle(el).backgroundImage; if (!bgStyle || bgStyle === 'none' || processedUrls.has(bgStyle)) continue; const bgUrls = bgStyle.match(/url\(["']?([^"']+)["']?\)/g); if (!bgUrls) continue; for (const bgUrl of bgUrls) { try { const match = bgUrl.match(/url\(["']?([^"']+)["']?\)/); if (!match || !match[1]) continue; let imgUrl = match[1]; if (processedUrls.has(imgUrl)) continue; const fullUrl = new URL(imgUrl, window.location.href).href; const ext = getFileExtension(fullUrl).toLowerCase(); if (!CONFIG.supportFormats.includes(ext)) continue; const imgInfo = { id: `bg-${Date.now()}-${Math.random().toString(36).slice(2)}`, url: fullUrl, name: `背景图-${el.tagName.toLowerCase()}-${Date.now().toString().slice(-4)}`, format: ext, width: '背景图', height: '背景图', type: 'background', preview: fullUrl }; processedUrls.add(imgUrl); imageItems.push(imgInfo); } catch (e) { console.warn('采集背景图失败:', e); } } } catch (e) { console.warn('处理背景图样式失败:', e); } } for (const imgUrl of cssImageUrls) { try { if (!imgUrl || processedUrls.has(imgUrl)) continue; const fullUrl = new URL(imgUrl, window.location.href).href; const ext = getFileExtension(fullUrl).toLowerCase(); if (!CONFIG.supportFormats.includes(ext)) continue; const imgInfo = { id: `css-${Date.now()}-${Math.random().toString(36).slice(2)}`, url: fullUrl, name: `CSS图片-${Date.now().toString().slice(-4)}`, format: ext, width: 'CSS引用', height: 'CSS引用', type: 'css-image', preview: fullUrl }; processedUrls.add(imgUrl); imageItems.push(imgInfo); } catch (e) { console.warn('采集CSS图片失败:', e); } } const svgElements = document.querySelectorAll('svg'); for (const svg of svgElements) { try { const svgId = `svg-${Date.now()}-${Math.random().toString(36).slice(2)}`; const svgContent = svg.outerHTML; const svgUrl = URL.createObjectURL(new Blob([svgContent], { type: 'image/svg+xml' })); blobUrls.push(svgUrl); const imgInfo = { id: svgId, url: svgUrl, name: `SVG图片-${Date.now().toString().slice(-4)}`, format: 'svg', width: svg.naturalWidth || svg.width.baseVal.value || '自适应', height: svg.naturalHeight || svg.height.baseVal.value || '自适应', type: 'svg-tag', preview: svgUrl, svgContent: svgContent }; imageItems.push(imgInfo); } catch (e) { console.warn('采集SVG标签失败:', e); } } } catch (e) { console.error('图片采集主流程异常:', e); } finally { restoreHiddenElements(); } return imageItems; } function getFileExtension(url) { const path = new URL(url).pathname; const lastPart = path.split('/').pop(); const extMatch = lastPart.match(/\.([^.]+)$/); return extMatch ? extMatch[1] : ''; } function getImageName(url, altText) { if (altText && altText.trim()) { return altText.trim(); } const path = new URL(url).pathname; const fileName = path.split('/').pop().split('?')[0].split('#')[0]; return fileName || `未知图片-${Date.now().toString().slice(-6)}`; } async function showImageList() { const modal = document.getElementById('svgSnifferModal'); const svgList = document.getElementById('svgList'); const imageCountEl = document.getElementById('imageCount'); svgList.innerHTML = '
正在扫描页面图片资源(含CSS/隐藏元素)...
'; modal.style.display = 'block'; overlay.style.display = 'block'; try { const imageItems = await collectImages(); globalImageItems = imageItems; imageItemCache.clear(); imageCountEl.textContent = imageItems.length; imageItems.forEach(item => { imageItemCache.set(item.id, item); }); if (imageItems.length === 0) { svgList.innerHTML = '
没有找到任何图片资源(已尝试采集隐藏元素和CSS)
'; return; } svgList.innerHTML = ''; imageItems.forEach(item => { const itemElement = document.createElement('div'); itemElement.className = 'svg-item'; const truncatedTitle = truncateTitle(item.name); let previewHtml = ''; if (item.format === 'svg' && item.svgContent) { previewHtml = `${item.svgContent}`; } else { previewHtml = `${truncatedTitle}`; } itemElement.innerHTML = `
${previewHtml}
${truncatedTitle}
格式: ${item.format} 类型: ${item.type === 'img-tag' ? '图片标签' : item.type === 'background' ? '背景图' : item.type === 'css-image' ? 'CSS图片' : 'SVG标签'}
`; svgList.appendChild(itemElement); const itemDownloadBtn = itemElement.querySelector('.item-download-btn'); itemDownloadBtn.addEventListener('click', (e) => { e.stopPropagation(); const imgId = e.currentTarget.dataset.imgId; const imgItem = imageItemCache.get(imgId); if (imgItem) { downloadSingleImage(imgItem); } else { showNotification('未找到该图片资源,请刷新重试', 'error'); } }); }); } catch (error) { console.error('图片扫描错误:', error); svgList.innerHTML = `
扫描错误: ${error.message}(已尝试恢复隐藏元素)
`; } } function downloadSingleImage(imgItem) { try { const cleanName = sanitizeFileName(`${imgItem.name}.${imgItem.format}`); if (imgItem.format === 'svg' && imgItem.svgContent) { const svgContent = `${imgItem.svgContent}`; const blob = new Blob([svgContent], { type: 'image/svg+xml;charset=utf-8' }); saveAs(blob, cleanName); showNotification(`单独下载成功: ${cleanName}`, 'success'); return; } if (typeof GM_download !== 'undefined') { GM_download({ url: imgItem.url, name: cleanName, mimetype: `image/${imgItem.format}`, onload: () => showNotification(`单独下载成功: ${cleanName}`, 'success'), onerror: (error) => { console.warn('GM_download失败,尝试备用方案:', error); fallbackImageDownload(imgItem.url, cleanName); } }); return; } fallbackImageDownload(imgItem.url, cleanName); } catch (error) { console.error('单独下载失败:', error); showNotification('单独下载失败,请检查浏览器权限', 'error'); } } function fallbackImageDownload(imgUrl, fileName) { try { const link = document.createElement('a'); link.href = imgUrl; link.download = fileName; link.style.display = 'none'; if (imgUrl.includes('http') && !imgUrl.includes(window.location.hostname)) { const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = function() { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); canvas.toBlob((blob) => { const blobUrl = URL.createObjectURL(blob); blobUrls.push(blobUrl); link.href = blobUrl; document.body.appendChild(link); link.click(); setTimeout(() => { document.body.removeChild(link); URL.revokeObjectURL(blobUrl); blobUrls = blobUrls.filter(u => u !== blobUrl); }, 100); }, `image/${fileName.split('.').pop()}`); }; img.src = imgUrl; } else { document.body.appendChild(link); link.click(); setTimeout(() => document.body.removeChild(link), 100); } showNotification(`单独下载成功(备用): ${fileName}`, 'success'); } catch (error) { console.error('备用下载失败:', error); showNotification('下载失败,建议直接复制图片链接', 'error'); } } async function downloadSelectedImages() { const checkboxes = document.querySelectorAll('.svg-checkbox:checked'); if (checkboxes.length === 0) { showNotification('请至少选择一个图片!', 'warning'); return; } const selectedItems = []; checkboxes.forEach(checkbox => { const id = checkbox.dataset.id; const item = imageItemCache.get(id) || globalImageItems.find(i => i.id === id); if (item) { selectedItems.push(item); } }); if (selectedItems.length === 0) { showNotification('没有找到选中的图片项目!', 'warning'); return; } if (selectedItems.length === 1) { downloadSingleImage(selectedItems[0]); return; } const zip = new JSZip(); const timestamp = new Date().toISOString().slice(0, 19).replace(/[:]/g, '-'); const zipName = `网页图片合集_${timestamp}.zip`; let completedCount = 0; const svgList = document.getElementById('svgList'); svgList.innerHTML = `
正在打包图片(${completedCount}/${selectedItems.length})...
`; for (const [index, item] of selectedItems.entries()) { try { const cleanName = sanitizeFileName(`${index + 1}_${item.name}.${item.format}`); if (item.format === 'svg' && item.svgContent) { zip.file(cleanName, `${item.svgContent}`); completedCount++; updateZipProgress(completedCount, selectedItems.length); continue; } const response = await fetch(item.url, { credentials: 'same-origin', timeout: CONFIG.loadTimeout }); if (!response.ok) throw new Error(`HTTP错误: ${response.status}`); const blob = await response.blob(); const arrayBuffer = await blob.arrayBuffer(); zip.file(cleanName, arrayBuffer, { binary: true }); } catch (err) { console.warn(`添加图片失败: ${item.name}`, err); zip.file(`加载失败_${cleanName}.txt`, `图片加载失败: ${item.url}\n错误原因: ${err.message}`); } completedCount++; updateZipProgress(completedCount, selectedItems.length); } try { const content = await zip.generateAsync({ type: 'blob', compression: 'DEFLATE', compressionOptions: { level: 6 } }); saveAs(content, zipName); showNotification(`批量下载成功: ${zipName}(共${selectedItems.length}张)`, 'success'); showImageList(); } catch (error) { console.error('ZIP创建失败:', error); showNotification('创建压缩包失败,建议分批下载', 'error'); showImageList(); } } function updateZipProgress(completed, total) { const svgList = document.getElementById('svgList'); svgList.innerHTML = `
正在打包图片(${completed}/${total})...
`; } function copySelectedImageUrls() { const checkboxes = document.querySelectorAll('.svg-checkbox:checked'); if (checkboxes.length === 0) { showNotification('请至少选择一个图片!', 'warning'); return; } let urls = []; checkboxes.forEach(checkbox => { const id = checkbox.dataset.id; const item = imageItemCache.get(id) || globalImageItems.find(i => i.id === id); if (item) { urls.push(`${item.name}: ${item.url}`); } }); const urlText = urls.join('\n\n'); try { GM_setClipboard(urlText, 'text'); showNotification(`已复制 ${checkboxes.length} 个图片链接`, 'success'); } catch (e) { const textArea = document.createElement('textarea'); textArea.value = urlText; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); showNotification(`已复制 ${checkboxes.length} 个图片链接 (备用方法)`, 'success'); } } function setupEventListeners() { document.querySelector('.close-btn').addEventListener('click', () => { document.getElementById('svgSnifferModal').style.display = 'none'; overlay.style.display = 'none'; clearBlobUrls(); imageItemCache.clear(); restoreHiddenElements(); }); overlay.addEventListener('click', () => { document.getElementById('svgSnifferModal').style.display = 'none'; overlay.style.display = 'none'; clearBlobUrls(); imageItemCache.clear(); restoreHiddenElements(); }); document.getElementById('batchDownloadBtn').addEventListener('click', downloadSelectedImages); document.querySelector('.copy-btn').addEventListener('click', copySelectedImageUrls); document.getElementById('selectAll').addEventListener('change', (e) => { const checkboxes = document.querySelectorAll('.svg-checkbox'); checkboxes.forEach(checkbox => { checkbox.checked = e.target.checked; }); }); window.addEventListener('beforeunload', () => { clearBlobUrls(); imageItemCache.clear(); restoreHiddenElements(); }); } function clearBlobUrls() { blobUrls.forEach(url => { try { URL.revokeObjectURL(url); } catch (e) { console.warn('清理Blob URL失败:', e); } }); blobUrls = []; } function sanitizeFileName(name) { return name .replace(/[\\/:*?"<>|]/g, '_') .replace(/\s+/g, '_') .substring(0, 80) .trim() || 'unnamed_image'; } function showNotification(message, type = 'info') { const colors = { info: '#3498db', success: '#27ae60', warning: '#f39c12', error: '#e74c3c' }; copyNotification.textContent = message; copyNotification.style.backgroundColor = colors[type] || colors.info; copyNotification.style.opacity = '1'; setTimeout(() => { copyNotification.style.opacity = '0'; }, 3000); } function init() { initRadarButton(); setupEventListeners(); setTimeout(() => { radarButton.style.display = 'flex'; }, 100); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();