Greasy Fork

网页图片下载器

下载网页中的图片,按顺序排列好,你可以直接转换为pdf,方便查看

// ==UserScript==
// @name         网页图片下载器
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  下载网页中的图片,按顺序排列好,你可以直接转换为pdf,方便查看
// @author       etng
// @match        https://mp.weixin.qq.com/s/*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jspdf.umd.min.js
// @grant        none
// @run-at       document-end
// ==/UserScript==

(async function() {
    'use strict';

    // 创建按钮和样式
    const btnDownloadImages = createButton('下载为图片');
    const btnDownloadPdf = createButton('下载为PDF');
    const qrCodeImg = document.createElement('img');
    qrCodeImg.src = 'https://github.com/etng/user.js/raw/main/appraise.jpg'; // 替换为二维码图片的实际URL
    qrCodeImg.style.width = '160px';
    qrCodeImg.style.height = '160px';
    const downloadBtnContainer = document.createElement('div');

    // 按钮容器的样式设置
    Object.assign(downloadBtnContainer.style, {
        borderTop: '1px solid #fff',

        display: 'none',
        flexDirection: 'column',
        alignItems: 'center'
    });
    const ui = document.createElement('div');
    Object.assign(ui.style, {
        position: 'fixed',
        right: '20px',
        top: '50%',
        transform: 'translateY(-50%)',
        padding: '10px',
        backgroundColor: '#007bff',
        color: '#fff',
        border: '1px solid #fff',
        borderRadius: '5px',
        cursor: 'pointer',
        fontSize: '16px',
        zIndex: '1001'  
    });
    const toggleBtn = document.createElement('div');
    toggleBtn.innerText = '页面图片处理工具';
    Object.assign(toggleBtn.style, {
        padding: '5px',
        backgroundColor: '#007bff',
        color: '#fff',
        // border: '1px solid #fff',
        // borderRadius: '5px',
        cursor: 'pointer',
        fontSize: '16px',
    });
    const tipsDiv = document.createElement('ul');
    ;['点击下载按钮后等待图片下载完成','然后剔除不想要的广告内图片即可(后续会考虑自动去掉)'].forEach(line=>{
        let li = document.createElement('li');
        li.innerText = line;
        tipsDiv.appendChild(li);
    })
    Object.assign(tipsDiv.style, {
        padding: '5px',
        backgroundColor: '#000',
        color: '#eee',
        fontSize: '12px',
    });
    const imageCntSpan = document.createElement('span');
    // 将按钮和二维码图片添加到容器
    downloadBtnContainer.appendChild(btnDownloadImages);
    // downloadBtnContainer.appendChild(btnDownloadPdf);
    downloadBtnContainer.appendChild(tipsDiv);
    downloadBtnContainer.appendChild(imageCntSpan);
    downloadBtnContainer.appendChild(qrCodeImg);

    // 将容器和切换按钮添加到页面
    ui.appendChild(toggleBtn);
    ui.appendChild(downloadBtnContainer);
    document.body.appendChild(ui);

    // 添加切换功能
    toggleBtn.addEventListener('click', () => {
        downloadBtnContainer.style.display = (downloadBtnContainer.style.display === 'none' || !downloadBtnContainer.style.display) ? 'flex' : 'none';
    });

    // 创建基本样式对象
    const baseStyle = {
        position: 'fixed',
        top: '0',
        left: '50%',
        transform: 'translateX(-50%)',
        color: 'white',
        padding: '5px',
        zIndex: '1002', // 确保比 toggleBtn 更高的 z-index
        display: 'none'
    };

    // 错误提示元素
    const errorDiv = document.createElement('div');
    Object.assign(errorDiv.style, baseStyle, { backgroundColor: 'red' });
    document.body.appendChild(errorDiv);

    // 信息提示元素
    const infoDiv = document.createElement('div');
    Object.assign(infoDiv.style, baseStyle, { backgroundColor: 'blue' });
    document.body.appendChild(infoDiv);

    // 显示错误提示
    function showError(message) {
        errorDiv.innerText = message;
        errorDiv.style.display = 'block';
        setTimeout(() => { errorDiv.style.display = 'none'; }, 5000);
    }

    // 显示信息提示
    function showInfo(message) {
        infoDiv.innerText = message;
        infoDiv.style.display = 'block';
        setTimeout(() => { infoDiv.style.display = 'none'; }, 5000);
    }

    // 创建按钮的函数
    function createButton(text) {
        const btn = document.createElement('button');
        btn.innerText = text;
        Object.assign(btn.style, {
            // padding: '10px 20px',
            backgroundColor: '#007bff',
            color: '#fff',
            border: 'none',
            borderRadius: '5px',
            cursor: 'pointer',
            fontSize: '12px',
            display: 'block',
            marginBottom: '5px'
        });
        return btn;
    }

    // 获取当前时间并格式化
    function getFormattedTime() {
        const now = new Date();
        return `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}` +
               `${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`;
    }

    // 获取活动名称
    function getActivityName() {
        const activityNameNode = document.getElementById('activity-name');
        return activityNameNode ? activityNameNode.innerText.trim() : document.title.trim();
    }

    // 获取图片下载链接
    function getDownloadLinks() {
        const pageContent = document.getElementById('page-content');
        if (!pageContent) {
            showError('找不到 id 为 page-content 的元素');
            return [];
        }

        const images = Array.from(pageContent.querySelectorAll('img'));
        if (images.length === 0) {
            showError('没有找到图片元素');
            return [];
        }

        const downloadLinks = [];
        images.forEach((img) => {
            const src = img.getAttribute('data-src') || img.getAttribute('src');
            if (src) {
                const match = src.match(/\/(sz_)?mmbiz_(\w+)\//);
                if (match && match[2]) {
                    downloadLinks.push({ url: src, extension: match[2] });
                } else {
                    console.log(`无法从地址提取文件后缀: ${src}`);
                }
            } else {
                showInfo('图片元素缺少有效的 src 或 data-src 属性');
            }
        });
        imageCntSpan.innerText=`当前图片数量: ${downloadLinks.length}`
        return downloadLinks;
    }

    // 下载图片的函数
    async function downloadImages() {
        const formattedTime = getFormattedTime();
        const activityName = getActivityName();
        const downloadLinks = getDownloadLinks();

        if (downloadLinks.length === 0) {
            showError("无图片可下载")
            return;
        }

        for (let i = 0; i < downloadLinks.length; i++) {
            setTimeout(async () => {
                try {
                    const response = await fetch(downloadLinks[i].url);
                    if (!response.ok) throw new Error(`网络响应失败: ${response.statusText}`);
                    const blob = await response.blob();
                    const url = window.URL.createObjectURL(blob);
                    const a = document.createElement('a');
                    a.style.display = 'none';
                    a.href = url;
                    a.download = `${activityName}_${formattedTime}_${i + 1}.${downloadLinks[i].extension}`;
                    document.body.appendChild(a);
                    a.click();
                    window.URL.revokeObjectURL(url);
                    document.body.removeChild(a);
                    showInfo(`下载图片: ${activityName}_${formattedTime}_${i + 1}.${downloadLinks[i].extension}`);
                } catch (error) {
                    showError(`下载图片时发生错误: ${error.message}`);
                }
            }, i * 1000);
        }

        showInfo('图片下载链接已生成并开始下载');
    }

    // 下载PDF的函数
    async function downloadPdf() {
        if (typeof window.jspdf === 'undefined') {
            // 如果 window.jspdf 仍然未定义,动态加载一次脚本
            // await loadJsPdfAsync(jsPdfBase64);
            showError(`PDF库加载失败,您可以直接在下载好的目录批量转换`);
            return
        }
        await proceedWithPdfDownload();
    }


    async function proceedWithPdfDownload() {
        const { jsPDF } = window.jspdf;
        const formattedTime = getFormattedTime();
        const activityName = getActivityName();
        const downloadLinks = getDownloadLinks();

        if (downloadLinks.length === 0) return;

        const pdfDoc = new jsPDF();
        let firstPage = true;

        for (let i = 0; i < downloadLinks.length; i++) {
            try {
                const response = await fetch(downloadLinks[i].url);
                if (!response.ok) throw new Error(`网络响应失败: ${response.statusText}`);
                const blob = await response.blob();
                const base64 = await blobToBase64(blob);

                if (!firstPage) {
                    pdfDoc.addPage();
                }

                pdfDoc.addImage(base64, 'JPEG', 10, 10, 190, 0);
                firstPage = false;
                showInfo(`处理图片: ${activityName}_${formattedTime}_${i + 1}.${downloadLinks[i].extension}`);
            } catch (error) {
                showError(`下载图片时发生错误: ${error.message}`);
            }
        }
        pdfDoc.save(`${activityName}_${formattedTime}.pdf`);
        showInfo('图片下载PDF已生成并开始下载');
    }

    function blobToBase64(blob) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onloadend = () => resolve(reader.result.split(',')[1]);
            reader.onerror = reject;
            reader.readAsDataURL(blob);
        });
    }

    // 按钮点击事件处理函数
    btnDownloadImages.addEventListener('click', async () => {
        btnDownloadImages.disabled = true; // 禁用按钮
        await downloadImages();
        btnDownloadImages.disabled = false; // 所有操作完成后重新启用按钮
    });

    btnDownloadPdf.addEventListener('click', async () => {
        btnDownloadPdf.disabled = true; // 禁用按钮
        await downloadPdf();
        btnDownloadPdf.disabled = false; // 所有操作完成后重新启用按钮
    });

})();