您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
生成一个批量下载网页图片的按钮,支持选择性下载。目前支持网站:1. 微信公众号网页 2. 微博网页 3.其他网页使用通用方法下载
// ==UserScript== // @name 网页图片下载 // @version v2.1 // @description 生成一个批量下载网页图片的按钮,支持选择性下载。目前支持网站:1. 微信公众号网页 2. 微博网页 3.其他网页使用通用方法下载 // @author nixingshiguang // @match http*://mp.weixin.qq.com/s* // @match https://m.weibo.cn/* // @match https://weibo.com/* // @match https://www.threads.com/* // @icon https://cftc.160621.xyz/file/8d23583061c79384c94e0.png // @grant GM_download // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js // @namespace https://greasyfork.org/users/943170 // ==/UserScript== (function () { 'use strict'; /** * 图片下载工具类 */ class ImageDownloader { constructor() { this.imgElements = null; this.formattedDate = this.getFormattedDate(); this.downloadButton = null; this.selectButton = null; this.modal = null; this.selectedImages = new Set(); this.imageData = []; this.init(); } /** * 初始化 */ init() { this.createButtons(); this.createModal(); this.setupDownloadHandler(); } /** * 获取格式化的日期字符串 * @returns {string} 格式化的日期 "yyyy-mm-dd" */ getFormattedDate() { const today = new Date(); const year = today.getFullYear(); const month = ('0' + (today.getMonth() + 1)).slice(-2); const day = ('0' + today.getDate()).slice(-2); return `${year}-${month}-${day}`; } /** * 生成随机字符串 * @param {number} length 字符串长度 * @returns {string} 随机字符串 */ generateRandomString(length = 4) { const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; let result = ''; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * characters.length)); } return result; } /** * 创建按钮 */ createButtons() { // 创建按钮容器 const buttonContainer = document.createElement('div'); Object.assign(buttonContainer.style, { position: 'fixed', top: '100px', right: '10px', zIndex: '9999', display: 'flex', flexDirection: 'column', gap: '10px' }); // 创建直接下载按钮 this.downloadButton = document.createElement('button'); Object.assign(this.downloadButton.style, { borderRadius: '10px', padding: '10px', border: '0', color: 'black', backgroundColor: 'white', boxShadow: '0 0 5px 5px skyblue', opacity: '0.8', cursor: 'pointer', fontSize: '14px' }); this.downloadButton.textContent = '直接下载'; // 创建选择下载按钮 this.selectButton = document.createElement('button'); Object.assign(this.selectButton.style, { borderRadius: '10px', padding: '10px', border: '0', color: 'white', backgroundColor: '#007bff', boxShadow: '0 0 5px 5px rgba(0,123,255,0.3)', opacity: '0.8', cursor: 'pointer', fontSize: '14px' }); this.selectButton.textContent = '选择下载'; buttonContainer.appendChild(this.downloadButton); buttonContainer.appendChild(this.selectButton); document.body.appendChild(buttonContainer); // 绑定选择下载按钮事件 this.selectButton.addEventListener('click', () => { this.showImageSelector(); }); console.log('添加下载按钮完成'); } /** * 创建模态弹窗 */ createModal() { this.modal = document.createElement('div'); this.modal.style.cssText = ` display: none; position: fixed; z-index: 10000; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); `; const modalContent = document.createElement('div'); modalContent.style.cssText = ` background-color: white; margin: 2% auto; padding: 20px; border-radius: 10px; width: 90%; max-width: 800px; max-height: 90%; overflow-y: auto; position: relative; `; // 模态框标题 const title = document.createElement('h2'); title.textContent = '选择要下载的图片'; title.style.cssText = ` margin: 0 0 20px 0; color: #333; text-align: center; `; // 关闭按钮 const closeBtn = document.createElement('span'); closeBtn.innerHTML = '×'; closeBtn.style.cssText = ` position: absolute; right: 15px; top: 15px; font-size: 28px; font-weight: bold; cursor: pointer; color: #aaa; `; closeBtn.onclick = () => this.hideModal(); // 控制按钮区域 const controlArea = document.createElement('div'); controlArea.style.cssText = ` display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 10px; `; // 全选按钮 const selectAllBtn = document.createElement('button'); selectAllBtn.textContent = '全选'; selectAllBtn.style.cssText = ` padding: 8px 16px; background: #28a745; color: white; border: none; border-radius: 5px; cursor: pointer; `; selectAllBtn.onclick = () => this.selectAll(); // 反选按钮 const invertBtn = document.createElement('button'); invertBtn.textContent = '反选'; invertBtn.style.cssText = ` padding: 8px 16px; background: #ffc107; color: black; border: none; border-radius: 5px; cursor: pointer; `; invertBtn.onclick = () => this.invertSelection(); // 选中计数 const countLabel = document.createElement('span'); countLabel.id = 'selectedCount'; countLabel.style.cssText = ` font-weight: bold; color: #007bff; `; controlArea.appendChild(selectAllBtn); controlArea.appendChild(invertBtn); controlArea.appendChild(countLabel); // 图片网格容器 const imageGrid = document.createElement('div'); imageGrid.id = 'imageGrid'; imageGrid.style.cssText = ` display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 15px; margin-bottom: 20px; `; // 下载按钮区域 const downloadArea = document.createElement('div'); downloadArea.style.cssText = ` text-align: center; border-top: 1px solid #eee; padding-top: 20px; `; const downloadSelectedBtn = document.createElement('button'); downloadSelectedBtn.textContent = '下载选中图片'; downloadSelectedBtn.style.cssText = ` padding: 12px 24px; background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 16px; margin-right: 10px; `; downloadSelectedBtn.onclick = () => this.downloadSelected(); const downloadZipBtn = document.createElement('button'); downloadZipBtn.textContent = '打包下载ZIP'; downloadZipBtn.style.cssText = ` padding: 12px 24px; background: #6c757d; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 16px; `; downloadZipBtn.onclick = () => this.downloadAsZip(); // 进度条 const progressContainer = document.createElement('div'); progressContainer.id = 'progressContainer'; progressContainer.style.cssText = ` display: none; margin-top: 20px; `; const progressBar = document.createElement('div'); progressBar.style.cssText = ` width: 100%; height: 20px; background-color: #f0f0f0; border-radius: 10px; overflow: hidden; `; const progressFill = document.createElement('div'); progressFill.id = 'progressFill'; progressFill.style.cssText = ` height: 100%; background-color: #007bff; width: 0%; transition: width 0.3s ease; `; const progressText = document.createElement('div'); progressText.id = 'progressText'; progressText.style.cssText = ` text-align: center; margin-top: 10px; font-weight: bold; `; progressBar.appendChild(progressFill); progressContainer.appendChild(progressBar); progressContainer.appendChild(progressText); downloadArea.appendChild(downloadSelectedBtn); downloadArea.appendChild(downloadZipBtn); downloadArea.appendChild(progressContainer); modalContent.appendChild(closeBtn); modalContent.appendChild(title); modalContent.appendChild(controlArea); modalContent.appendChild(imageGrid); modalContent.appendChild(downloadArea); this.modal.appendChild(modalContent); document.body.appendChild(this.modal); } /** * 通过XMLHttpRequest下载单个图片 * @param {string} url 图片URL * @param {string} filename 文件名 */ downloadImageByXHR(url, filename = null) { const xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.responseType = 'blob'; xhr.onload = () => { if (xhr.status === 200) { const blob = xhr.response; const link = document.createElement('a'); link.style.display = 'none'; document.body.appendChild(link); const blobUrl = window.URL.createObjectURL(blob); link.href = blobUrl; link.download = filename || `${this.formattedDate}_${this.generateRandomString()}.jpg`; link.click(); window.URL.revokeObjectURL(blobUrl); document.body.removeChild(link); } }; xhr.onerror = () => { console.error(`下载失败: ${url}`); }; xhr.send(); } /** * 通过GM_download下载图片 * @param {string} url 图片URL * @param {string} filename 文件名 */ downloadImageByGM(url, filename = null) { GM_download({ url: url, name: filename || `${this.formattedDate}_${this.generateRandomString()}.jpg` }); } /** * 批量下载图片(通用方法) * @param {NodeList} imgElements 图片元素列表 * @param {Function} urlProcessor URL处理函数 * @param {boolean} useXHR 是否使用XHR下载 */ batchDownload(imgElements, urlProcessor = null, useXHR = false) { console.group("图片下载脚本"); const imageUrls = []; let count = 1; imgElements.forEach(img => { let url = img.getAttribute('data-src') || img.getAttribute('src'); if (urlProcessor && typeof urlProcessor === 'function') { url = urlProcessor(url); } if (url) { console.log(`获取第${count}张图片链接: ${url}`); imageUrls.push(url); count++; } }); console.log("开始下载,共", imageUrls.length, "张图片"); imageUrls.forEach((url, index) => { const filename = `${this.formattedDate}_${this.generateRandomString()}.jpg`; if (useXHR) { this.downloadImageByXHR(url, filename); } else { this.downloadImageByGM(url, filename); } }); console.log("下载任务已启动"); console.groupEnd(); } /** * 显示图片选择器 */ async showImageSelector() { // 显示加载提示 this.showLoadingModal(); try { // 先自动滚动页面加载懒加载图片 await this.autoScrollAndLoadImages(); // 获取图片数据 await this.collectImageData(); if (this.imageData.length === 0) { this.hideLoadingModal(); alert('未找到可下载的图片'); return; } // 隐藏加载提示,显示选择界面 this.hideLoadingModal(); // 渲染图片网格 this.renderImageGrid(); // 显示模态框 this.modal.style.display = 'block'; // 更新选中计数 this.updateSelectedCount(); } catch (error) { this.hideLoadingModal(); console.error('加载图片时出错:', error); alert('加载图片时出错,请重试'); } } /** * 显示加载模态框 */ showLoadingModal() { // 创建加载遮罩 this.loadingModal = document.createElement('div'); this.loadingModal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); z-index: 10001; display: flex; justify-content: center; align-items: center; flex-direction: column; `; const loadingContent = document.createElement('div'); loadingContent.style.cssText = ` background: white; padding: 30px; border-radius: 10px; text-align: center; max-width: 400px; `; const spinner = document.createElement('div'); spinner.style.cssText = ` border: 4px solid #f3f3f3; border-top: 4px solid #007bff; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 0 auto 20px auto; `; // 添加旋转动画 const style = document.createElement('style'); style.textContent = ` @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `; document.head.appendChild(style); const loadingText = document.createElement('div'); loadingText.id = 'loadingText'; loadingText.style.cssText = ` font-size: 16px; color: #333; margin-bottom: 10px; `; loadingText.textContent = '正在加载页面图片...'; const progressText = document.createElement('div'); progressText.id = 'loadingProgress'; progressText.style.cssText = ` font-size: 14px; color: #666; `; progressText.textContent = '请稍候,正在滚动页面加载所有图片'; loadingContent.appendChild(spinner); loadingContent.appendChild(loadingText); loadingContent.appendChild(progressText); this.loadingModal.appendChild(loadingContent); document.body.appendChild(this.loadingModal); } /** * 隐藏加载模态框 */ hideLoadingModal() { if (this.loadingModal) { document.body.removeChild(this.loadingModal); this.loadingModal = null; } } /** * 自动滚动页面并加载懒加载图片 */ async autoScrollAndLoadImages() { const updateProgress = (message) => { const progressElement = document.getElementById('loadingProgress'); if (progressElement) { progressElement.textContent = message; } }; // 保存当前滚动位置 const originalScrollTop = window.pageYOffset || document.documentElement.scrollTop; updateProgress('正在扫描页面高度...'); // 获取页面总高度 const getPageHeight = () => Math.max( document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight ); let lastHeight = getPageHeight(); let scrollPosition = 0; const scrollStep = window.innerHeight * 0.8; // 每次滚动80%的视窗高度 let noNewContentCount = 0; const maxNoNewContentCount = 3; // 连续3次没有新内容就停止 updateProgress('开始自动滚动加载图片...'); while (scrollPosition < lastHeight && noNewContentCount < maxNoNewContentCount) { // 滚动到指定位置 window.scrollTo(0, scrollPosition); updateProgress(`正在滚动加载... (${Math.round((scrollPosition / lastHeight) * 100)}%)`); // 等待图片加载 await new Promise(resolve => setTimeout(resolve, 800)); // 触发懒加载 await this.triggerLazyLoad(); // 再等待一下让图片完全加载 await new Promise(resolve => setTimeout(resolve, 500)); // 检查页面高度是否有变化 const newHeight = getPageHeight(); if (newHeight > lastHeight) { lastHeight = newHeight; noNewContentCount = 0; // 重置计数器 } else { noNewContentCount++; } scrollPosition += scrollStep; } updateProgress('滚动完成,正在收集图片信息...'); // 最后滚动到底部确保所有内容都加载了 window.scrollTo(0, lastHeight); await new Promise(resolve => setTimeout(resolve, 1000)); // 再次触发懒加载 await this.triggerLazyLoad(); await new Promise(resolve => setTimeout(resolve, 500)); updateProgress('图片加载完成,正在生成选择界面...'); // 恢复原始滚动位置 window.scrollTo(0, originalScrollTop); } /** * 触发懒加载 */ async triggerLazyLoad() { // 触发滚动事件 window.dispatchEvent(new Event('scroll')); window.dispatchEvent(new Event('resize')); // 查找所有可能的懒加载图片并尝试加载 const lazyImages = document.querySelectorAll('img[data-src], img[data-original], img[loading="lazy"]'); lazyImages.forEach(img => { // 模拟图片进入视窗 if (img.dataset.src && !img.src) { img.src = img.dataset.src; } if (img.dataset.original && !img.src) { img.src = img.dataset.original; } // 触发 Intersection Observer (如果页面使用了的话) const rect = img.getBoundingClientRect(); if (rect.top < window.innerHeight && rect.bottom > 0) { // 图片在视窗内,触发加载 img.dispatchEvent(new Event('load')); } }); // 查找并点击可能的"加载更多"按钮 this.findAndClickLoadMoreButtons(); } /** * 查找并点击"加载更多"按钮 */ findAndClickLoadMoreButtons() { // 通过类名查找 const classSelectorButtons = document.querySelectorAll('.load-more, .show-more, .btn-load-more, .more-btn, .load-btn'); // 通过文本内容查找按钮和链接 const allButtons = document.querySelectorAll('button, a'); const textBasedButtons = Array.from(allButtons).filter(btn => { const text = btn.textContent.trim().toLowerCase(); return text.includes('加载更多') || text.includes('查看更多') || text.includes('显示更多') || text.includes('load more') || text.includes('show more') || text.includes('更多'); }); // 合并所有找到的按钮 const allLoadMoreButtons = [...classSelectorButtons, ...textBasedButtons]; // 去重并点击可见的按钮 const uniqueButtons = [...new Set(allLoadMoreButtons)]; uniqueButtons.forEach(btn => { if (btn.offsetParent !== null && !btn.disabled) { // 确保按钮可见且未禁用 try { btn.click(); console.log('点击了加载更多按钮:', btn.textContent.trim()); } catch (error) { console.warn('点击按钮时出错:', error); } } }); } /** * 收集图片数据 */ async collectImageData() { const currentUrl = window.location.href; this.imageData = []; let imgElements; let urlProcessor = null; // 根据网站类型获取图片元素 switch (true) { case currentUrl.includes("mp.weixin.qq.com/s"): const articleElement = document.querySelector("#js_article"); if (articleElement) { if (articleElement.classList.contains('share_content_page')) { imgElements = document.querySelectorAll('.swiper_item img'); } else { imgElements = document.querySelectorAll('#img-content img'); } } else { imgElements = document.querySelectorAll('img'); } break; case currentUrl.includes("https://weibo.com/"): imgElements = document.querySelectorAll(".Viewer_prevItem_McSJ4 img"); urlProcessor = (url) => url.replace("orj360", "large"); break; case currentUrl.includes("https://m.weibo.cn/"): imgElements = document.querySelectorAll("img[data-v-5deaae85]"); urlProcessor = (url) => url.replace("orj360", "large"); break; case currentUrl.includes("https://www.threads.com/"): const elements = document.querySelectorAll('picture.x87ps6o'); this.imageData = []; Array.from(elements).forEach((element, index) => { try { if (element.firstChild && element.firstChild.srcset) { const url = element.firstChild.srcset.split(' ')[0]; if (url && (url.startsWith('http://') || url.startsWith('https://'))) { this.imageData.push({ id: index, url: url, thumbnail: url }); } } } catch (error) { console.error('处理Threads图片时出错:', error); } }); return; default: imgElements = document.querySelectorAll('img'); } // 处理图片元素 if (imgElements) { Array.from(imgElements).forEach((img, index) => { let url = img.getAttribute('data-src') || img.getAttribute('src'); if (urlProcessor && typeof urlProcessor === 'function') { url = urlProcessor(url); } if (url && url.startsWith('http')) { this.imageData.push({ id: index, url: url, thumbnail: img.src || url }); } }); } } /** * 渲染图片网格 */ async renderImageGrid() { const imageGrid = document.getElementById('imageGrid'); imageGrid.innerHTML = ''; // 显示加载状态 const loadingDiv = document.createElement('div'); loadingDiv.textContent = '正在生成图片预览...'; loadingDiv.style.cssText = ` text-align: center; padding: 20px; color: #666; font-size: 16px; `; imageGrid.appendChild(loadingDiv); // 预加载所有图片的缩略图 await this.preloadThumbnails(); // 清除加载状态 imageGrid.innerHTML = ''; this.imageData.forEach((imageInfo) => { const imageItem = document.createElement('div'); imageItem.style.cssText = ` border: 2px solid #ddd; border-radius: 8px; padding: 10px; text-align: center; cursor: pointer; transition: all 0.3s ease; `; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = `img_${imageInfo.id}`; checkbox.style.cssText = ` margin-bottom: 10px; transform: scale(1.2); `; checkbox.onchange = () => this.toggleImageSelection(imageInfo.id); const imgContainer = document.createElement('div'); imgContainer.style.cssText = ` position: relative; width: 100%; height: 120px; margin-bottom: 10px; background: #f5f5f5; border-radius: 5px; display: flex; align-items: center; justify-content: center; `; const img = document.createElement('img'); img.style.cssText = ` max-width: 100%; max-height: 100%; object-fit: cover; border-radius: 5px; `; // 使用预加载的缩略图或原图 const thumbnailUrl = imageInfo.preloadedThumbnail || imageInfo.thumbnail || imageInfo.url; if (thumbnailUrl) { img.src = thumbnailUrl; } else { // 显示占位符 img.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIwIiBoZWlnaHQ9IjEyMCIgdmlld0JveD0iMCAwIDEyMCAxMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxMjAiIGhlaWdodD0iMTIwIiBmaWxsPSIjRjBGMEYwIi8+CjxwYXRoIGQ9Ik02MCA0MEw4MCA3MEg0MEw2MCA0MFoiIGZpbGw9IiNDQ0MiLz4KPC9zdmc+'; } img.onerror = () => { // 如果缩略图加载失败,尝试加载原图 if (img.src !== imageInfo.url && imageInfo.url) { img.src = imageInfo.url; } else { // 显示占位符 img.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIwIiBoZWlnaHQ9IjEyMCIgdmlld0JveD0iMCAwIDEyMCAxMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxMjAiIGhlaWdodD0iMTIwIiBmaWxsPSIjRjBGMEYwIi8+CjxwYXRoIGQ9Ik02MCA0MEw4MCA3MEg0MEw2MCA0MFoiIGZpbGw9IiNDQ0MiLz4KPC9zdmc+'; } }; imgContainer.appendChild(img); const label = document.createElement('label'); label.htmlFor = `img_${imageInfo.id}`; label.textContent = `图片 ${imageInfo.id + 1}`; label.style.cssText = ` display: block; font-size: 12px; color: #666; cursor: pointer; `; // 添加图片尺寸信息 const sizeInfo = document.createElement('div'); sizeInfo.style.cssText = ` font-size: 10px; color: #999; margin-top: 2px; `; // 尝试获取图片尺寸信息 if (imageInfo.dimensions) { sizeInfo.textContent = `${imageInfo.dimensions.width}×${imageInfo.dimensions.height}`; } else { sizeInfo.textContent = '获取尺寸中...'; this.getImageDimensions(imageInfo.url).then(dimensions => { if (dimensions) { sizeInfo.textContent = `${dimensions.width}×${dimensions.height}`; imageInfo.dimensions = dimensions; } else { sizeInfo.textContent = '未知尺寸'; } }); } imageItem.appendChild(checkbox); imageItem.appendChild(imgContainer); imageItem.appendChild(label); imageItem.appendChild(sizeInfo); // 点击整个项目来切换选择 imageItem.onclick = (e) => { if (e.target !== checkbox) { checkbox.checked = !checkbox.checked; this.toggleImageSelection(imageInfo.id); } }; imageGrid.appendChild(imageItem); }); } /** * 预加载缩略图 */ async preloadThumbnails() { const updateProgress = (current, total) => { const progressElement = document.getElementById('loadingProgress'); if (progressElement) { progressElement.textContent = `正在预加载图片缩略图... (${current}/${total})`; } }; const promises = this.imageData.map(async (imageInfo, index) => { try { updateProgress(index + 1, this.imageData.length); // 尝试创建缩略图 const thumbnailUrl = await this.createThumbnail(imageInfo.url); if (thumbnailUrl) { imageInfo.preloadedThumbnail = thumbnailUrl; } } catch (error) { console.warn(`预加载图片 ${imageInfo.url} 失败:`, error); } }); await Promise.all(promises); } /** * 创建图片缩略图 */ async createThumbnail(imageUrl, maxWidth = 200, maxHeight = 200) { return new Promise((resolve) => { const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = () => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // 计算缩略图尺寸 let { width, height } = img; const ratio = Math.min(maxWidth / width, maxHeight / height); if (ratio < 1) { width *= ratio; height *= ratio; } canvas.width = width; canvas.height = height; // 绘制缩略图 ctx.drawImage(img, 0, 0, width, height); // 转换为 data URL const thumbnailUrl = canvas.toDataURL('image/jpeg', 0.8); resolve(thumbnailUrl); } catch (error) { console.warn('创建缩略图失败:', error); resolve(imageUrl); // 返回原图URL } }; img.onerror = () => { resolve(imageUrl); // 返回原图URL }; img.src = imageUrl; // 设置超时 setTimeout(() => { resolve(imageUrl); }, 5000); }); } /** * 获取图片尺寸 */ async getImageDimensions(imageUrl) { return new Promise((resolve) => { const img = new Image(); img.onload = () => { resolve({ width: img.naturalWidth, height: img.naturalHeight }); }; img.onerror = () => { resolve(null); }; img.src = imageUrl; // 设置超时 setTimeout(() => { resolve(null); }, 3000); }); } /** * 切换图片选择状态 */ toggleImageSelection(imageId) { const checkbox = document.getElementById(`img_${imageId}`); const imageItem = checkbox.parentElement; if (checkbox.checked) { this.selectedImages.add(imageId); imageItem.style.borderColor = '#007bff'; imageItem.style.backgroundColor = '#f8f9fa'; } else { this.selectedImages.delete(imageId); imageItem.style.borderColor = '#ddd'; imageItem.style.backgroundColor = 'white'; } this.updateSelectedCount(); } /** * 全选 */ selectAll() { this.selectedImages.clear(); this.imageData.forEach(imageInfo => { this.selectedImages.add(imageInfo.id); const checkbox = document.getElementById(`img_${imageInfo.id}`); const imageItem = checkbox.parentElement; checkbox.checked = true; imageItem.style.borderColor = '#007bff'; imageItem.style.backgroundColor = '#f8f9fa'; }); this.updateSelectedCount(); } /** * 反选 */ invertSelection() { const newSelection = new Set(); this.imageData.forEach(imageInfo => { const checkbox = document.getElementById(`img_${imageInfo.id}`); const imageItem = checkbox.parentElement; if (!this.selectedImages.has(imageInfo.id)) { newSelection.add(imageInfo.id); checkbox.checked = true; imageItem.style.borderColor = '#007bff'; imageItem.style.backgroundColor = '#f8f9fa'; } else { checkbox.checked = false; imageItem.style.borderColor = '#ddd'; imageItem.style.backgroundColor = 'white'; } }); this.selectedImages = newSelection; this.updateSelectedCount(); } /** * 更新选中计数 */ updateSelectedCount() { const countLabel = document.getElementById('selectedCount'); countLabel.textContent = `已选择 ${this.selectedImages.size} / ${this.imageData.length} 张图片`; } /** * 下载选中的图片 */ async downloadSelected() { if (this.selectedImages.size === 0) { alert('请先选择要下载的图片'); return; } this.showProgress(); const selectedImageData = this.imageData.filter(img => this.selectedImages.has(img.id)); for (let i = 0; i < selectedImageData.length; i++) { const imageInfo = selectedImageData[i]; const filename = `${this.formattedDate}_${this.generateRandomString()}.jpg`; try { this.downloadImageByGM(imageInfo.url, filename); this.updateProgress(i + 1, selectedImageData.length, `正在下载第 ${i + 1} 张图片...`); // 添加延迟避免过快下载 await new Promise(resolve => setTimeout(resolve, 200)); } catch (error) { console.error(`下载图片失败: ${imageInfo.url}`, error); } } this.updateProgress(selectedImageData.length, selectedImageData.length, '下载完成!'); setTimeout(() => { this.hideProgress(); this.hideModal(); }, 2000); } /** * 打包下载为ZIP */ async downloadAsZip() { if (this.selectedImages.size === 0) { alert('请先选择要下载的图片'); return; } if (typeof JSZip === 'undefined') { alert('ZIP功能不可用,请使用普通下载'); return; } this.showProgress(); const zip = new JSZip(); const selectedImageData = this.imageData.filter(img => this.selectedImages.has(img.id)); for (let i = 0; i < selectedImageData.length; i++) { const imageInfo = selectedImageData[i]; const filename = `image_${i + 1}_${this.generateRandomString()}.jpg`; try { this.updateProgress(i + 1, selectedImageData.length, `正在处理第 ${i + 1} 张图片...`); const response = await fetch(imageInfo.url); const blob = await response.blob(); zip.file(filename, blob); } catch (error) { console.error(`处理图片失败: ${imageInfo.url}`, error); } } this.updateProgress(selectedImageData.length, selectedImageData.length, '正在生成ZIP文件...'); try { const zipBlob = await zip.generateAsync({type: 'blob'}); const link = document.createElement('a'); link.href = URL.createObjectURL(zipBlob); link.download = `images_${this.formattedDate}.zip`; link.click(); this.updateProgress(selectedImageData.length, selectedImageData.length, 'ZIP文件下载完成!'); } catch (error) { console.error('生成ZIP文件失败:', error); alert('生成ZIP文件失败,请使用普通下载'); } setTimeout(() => { this.hideProgress(); this.hideModal(); }, 2000); } /** * 显示进度条 */ showProgress() { const progressContainer = document.getElementById('progressContainer'); progressContainer.style.display = 'block'; } /** * 隐藏进度条 */ hideProgress() { const progressContainer = document.getElementById('progressContainer'); progressContainer.style.display = 'none'; } /** * 更新进度 */ updateProgress(current, total, message) { const progressFill = document.getElementById('progressFill'); const progressText = document.getElementById('progressText'); const percentage = Math.round((current / total) * 100); progressFill.style.width = `${percentage}%`; progressText.textContent = `${message} (${current}/${total})`; } /** * 隐藏模态框 */ hideModal() { this.modal.style.display = 'none'; this.selectedImages.clear(); } /** * 添加按钮点击事件监听器 * @param {Function} handler 点击处理函数 */ addClickListener(handler) { this.downloadButton.addEventListener('click', handler); } /** * 根据当前网站设置下载处理器 */ setupDownloadHandler() { const currentUrl = window.location.href; switch (true) { case currentUrl.includes("mp.weixin.qq.com/s"): this.setupWeixinHandler(); break; case currentUrl.includes("https://weibo.com/"): this.setupWeiboHandler(); break; case currentUrl.includes("https://m.weibo.cn/"): this.setupWeiboMobileHandler(); break; case currentUrl.includes("https://www.threads.com/"): this.setupThreadsHandler(); break; default: this.setupUniversalHandler(); } } /** * 微信公众号处理器 */ setupWeixinHandler() { // 移除二维码 const qrCode = document.getElementById('js_pc_qr_code'); if (qrCode) { qrCode.remove(); } this.addClickListener(() => { const articleElement = document.querySelector("#js_article"); if (articleElement) { if (articleElement.classList.contains('share_content_page')) { this.imgElements = document.querySelectorAll('.swiper_item img'); } else { this.imgElements = document.querySelectorAll('#img-content img'); } } else { this.imgElements = document.querySelectorAll('img'); } this.batchDownload(this.imgElements); }); } /** * 微博处理器 */ setupWeiboHandler() { this.addClickListener(() => { console.clear(); console.group("微博图片下载"); this.imgElements = document.querySelectorAll(".Viewer_prevItem_McSJ4 img"); // URL处理函数:替换为大图 const urlProcessor = (url) => url.replace("orj360", "large"); this.batchDownload(this.imgElements, urlProcessor, true); console.groupEnd(); }); } /** * 微博手机版处理器 */ setupWeiboMobileHandler() { this.addClickListener(() => { console.clear(); console.group("微博手机版图片下载"); this.imgElements = document.querySelectorAll("img[data-v-5deaae85]"); // URL处理函数:替换为大图 const urlProcessor = (url) => url.replace("orj360", "large"); this.batchDownload(this.imgElements, urlProcessor, true); console.groupEnd(); }); } /** * Threads处理器 */ setupThreadsHandler() { this.addClickListener(() => { console.clear(); console.group("Threads图片下载"); const elements = document.querySelectorAll('picture.x87ps6o'); const imageUrls = []; Array.from(elements).forEach(element => { try { if (element.firstChild && element.firstChild.srcset) { const url = element.firstChild.srcset.split(' ')[0]; if (url && (url.startsWith('http://') || url.startsWith('https://'))) { imageUrls.push(url); console.log(`已获取图片URL: ${url}`); } else { console.warn(`无效的图片URL: ${url}`); } } } catch (error) { console.error('处理元素时出错:', error); } }); // 下载所有图片 imageUrls.forEach(url => { this.downloadImageByXHR(url, `${this.formattedDate}_${this.generateRandomString()}.jpg`); }); console.log(`开始下载 ${imageUrls.length} 张图片`); console.groupEnd(); }); } /** * 通用处理器 */ setupUniversalHandler() { this.addClickListener(() => { this.imgElements = document.querySelectorAll('img'); this.batchDownload(this.imgElements); }); } } /** * 页面加载完成后初始化 */ window.addEventListener('load', () => { new ImageDownloader(); }); })();