// ==UserScript== // @name 双击下载当前页面所有图片并打包为压缩包 // @namespace http://tampermonkey.net/ // @version 0.2 // @description 双击一键下载当前页面所有图片并打包成压缩包 // @author qingtian1 // @include * // @icon https://www.google.com/s2/favicons?sz=64&domain=bbs.tampermonkey.net.cn // @require https://code.jquery.com/jquery-3.7.1.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js // @grant GM_xmlhttpRequest // @connect * // @grant GM_registerMenuCommand // ==/UserScript== (function() { 'use strict' let isDownloading = false // 防止重复触发 function getFormattedDate() { const now = new Date() const year = now.getFullYear() const month = String(now.getMonth() + 1).padStart(2, '0') const day = String(now.getDate()).padStart(2, '0') // const hours = String(now.getHours()).padStart(2, '0') // const minutes = String(now.getMinutes()).padStart(2, '0') // const seconds = String(now.getSeconds()).padStart(2, '0') return `${year}-${month}-${day}` // return `${year}-${month}-${day} ${hours}_${minutes}_${seconds}` } function getFormattedDateAndTime() { const now = new Date() const year = now.getFullYear() const month = String(now.getMonth() + 1).padStart(2, '0') const day = String(now.getDate()).padStart(2, '0') const hours = String(now.getHours()).padStart(2, '0') const minutes = String(now.getMinutes()).padStart(2, '0') const seconds = String(now.getSeconds()).padStart(2, '0') return `${year}_${month}_${day}_${hours}_${minutes}_${seconds}` } // 创建进度提示框 function createProgressIndicator() { const indicator = document.createElement('div') indicator.id = 'progress-indicator' indicator.style.position = 'fixed' indicator.style.top = '50%' indicator.style.left = '50%' indicator.style.transform = 'translate(-50%, -50%)' indicator.style.padding = '20px 30px' indicator.style.backgroundColor = 'rgba(0, 0, 0, 0.8)' indicator.style.color = '#fff' indicator.style.borderRadius = '8px' indicator.style.fontSize = '18px' indicator.style.zIndex = '9999' indicator.style.display = 'none' indicator.innerHTML = '正在准备下载...' document.body.appendChild(indicator) return indicator } const progressIndicator = createProgressIndicator() function updateProgressIndicator(downloaded, total) { progressIndicator.innerHTML = `下载进度: ${downloaded}/${total}` if (!progressIndicator.style.display || progressIndicator.style.display === 'none') { progressIndicator.style.display = 'block' } } function hideProgressIndicator() { progressIndicator.style.display = 'none' } // 获取文件名 function getFileNameByUrl(url) { // console.log('domain', domain) const date = getFormattedDate() // console.log('date', date) const prefix = `images-${date}` // console.log('prefix', prefix) // https://i.pinimg.com/550x/87/34/11/873411778259085744902e7f346474ba.jpg let fileName if (url.endsWith('.jpg') || url.endsWith('.png') || url.endsWith('.gif') || url.endsWith('.webp') || url.endsWith('.svg')) { fileName = url.replace('http://', '') fileName = fileName.replace('https://', '') fileName = fileName.replaceAll('/', '_') fileName = fileName.substring(0, fileName.lastIndexOf('.')) } else { fileName = 'image_' + Math.random().toString(36).substring(2, 11) } // console.log('fileName', fileName) return `${prefix}-${fileName}` } function getFileExtensionByContentType(url, contentType) { // 优先从 Content-Type 中获取扩展名 if (contentType) { const mimeMap = { 'image/jpeg': 'jpg', 'image/png': 'png', 'image/gif': 'gif', 'image/webp': 'webp', 'image/svg+xml': 'svg' } const extFromType = mimeMap[contentType.toLowerCase()] if (extFromType) return extFromType } // 从 URL 中提取扩展名 const urlParts = url.split('?')[0] // 去掉 URL 中的参数 const extMatch = urlParts.match(/\.(\w+)$/) // 匹配最后的 .扩展名 return extMatch ? extMatch[1] : 'unknown' // 默认返回 unknown } function downloadImag(imgSrc, name, callback) { // 使用 GM_xmlhttpRequest 发送跨域请求 GM_xmlhttpRequest({ method: 'GET', url: imgSrc, responseType: 'blob', // 确保返回的是 Blob 数据 anonymous: true, // 设置匿名,避免携带 cookies,绕过跨域验证 onload: function(response) { // 将返回的 Blob 对象创建为 URL const blob = response.response const contentType = response.responseHeaders .split('\r\n') .find(header => header.toLowerCase().startsWith('content-type:')) ?.split(':')[1] ?.trim() // 获取文件扩展名 const ext = getFileExtensionByContentType(imgSrc, contentType) // 拼接完整文件名 const fullName = `${name}.${ext}` console.log('fullName', fullName) console.log('Image downloaded successfully!') callback(fullName, blob) }, onerror: function(error) { console.error('Failed to fetch image:', error) } }) } // 下载所有图片 function downloadAllImages() { if (isDownloading) { console.log('正在下载中,请稍候...') return } isDownloading = true console.log('开始下载所有图片...') progressIndicator.innerHTML = '正在准备下载...' progressIndicator.style.display = 'block' const images = document.querySelectorAll('img') const imagesArr = [] images.forEach((img) => { const imageSrc = img.src if (!imagesArr.includes(imageSrc)) { imagesArr.push(imageSrc) } }) const totalImages = imagesArr.length let downloadedCount = 0 const zip = new JSZip() // 创建一个新的 ZIP 实例 const encodedUrl = encodeURIComponent(window.location.href) const folderName = `images-${encodedUrl}-${getFormattedDateAndTime()}` // 创建文件夹名 imagesArr.forEach((imageSrc) => { const name = getFileNameByUrl(imageSrc) console.log(`准备下载图片: ${name}`) downloadImag(imageSrc, name, (fileName, blob) => { zip.folder(folderName).file(fileName, blob) // 将图片文件添加到 ZIP 中 downloadedCount++ console.log(`已下载 ${downloadedCount}/${totalImages} 张图片`) updateProgressIndicator(downloadedCount, totalImages) // 当所有图片下载完成时,恢复下载状态 if (downloadedCount === totalImages) { zip.generateAsync({ type: 'blob' }).then(function(content) { // 生成压缩包文件并提供下载 const objectURL = URL.createObjectURL(content) const a = document.createElement('a') a.href = objectURL a.download = `images-${folderName}.zip` a.click() console.log('所有图片已打包并下载完成!') progressIndicator.innerHTML = '下载完成!' setTimeout(hideProgressIndicator, 2000) isDownloading = false }) } }) }) } function createDownloadButton() { const button = document.createElement('button') button.textContent = '双击下载所有图片并打包成压缩包' button.style.position = 'absolute' button.style.padding = '10px 20px' button.style.backgroundColor = '#007bff' button.style.color = '#fff' button.style.border = 'none' button.style.borderRadius = '5px' button.style.cursor = 'pointer' button.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.2)' button.style.zIndex = '9999' // 从 localStorage 加载按钮位置 const savedPosition = JSON.parse(localStorage.getItem('buttonPosition')) if (savedPosition) { button.style.top = savedPosition.top button.style.left = savedPosition.left } else { button.style.top = '10px' button.style.left = '10px' } // 添加拖拽功能 let isDragging = false let offsetX, offsetY button.addEventListener('mousedown', (e) => { isDragging = true offsetX = e.offsetX offsetY = e.offsetY button.style.cursor = 'grabbing' }) document.addEventListener('mousemove', (e) => { if (isDragging) { const top = `${e.clientY - offsetY}px` const left = `${e.clientX - offsetX}px` button.style.top = top button.style.left = left } }) document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false button.style.cursor = 'pointer' // 保存按钮位置到 localStorage const position = { top: button.style.top, left: button.style.left } localStorage.setItem('buttonPosition', JSON.stringify(position)) } }) // 点击按钮触发下载 button.addEventListener('dblclick', downloadAllImages) document.body.appendChild(button) } // 初始化按钮 $(function() { createDownloadButton() }) // 添加右键菜单选项 GM_registerMenuCommand('下载该页面的图片并打包', () => { const confirmDownload = confirm('是否下载该页面的所有图片并打包成 ZIP 文件?') if (confirmDownload) { downloadAllImages() } }) })()