网页图片下载

生成一个批量下载网页图片的按钮,支持选择性下载。目前支持网站:1. 微信公众号网页 2. 微博网页 3.其他网页使用通用方法下载

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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 = '&times;';
            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();
    });

})();