Greasy Fork is available in English.

Wallhaven一键下载

在Wallhaven主页外的每张略缩图上增添下载按钮和勾选框,可以直接点“下载”按钮对原图进行下载,也可以勾选想要下载的图片,然后点击“逐个下载”或“打包下载”。想要略缩图以原图比例显示的话,不需要脚本就可以实现,把网站设置中的“Thumb Size”改成“Original”即可。

// ==UserScript==
// @name         Wallhaven一键下载
// @namespace    这是干啥的,不是很懂
// @version      1.2
// @description  在Wallhaven主页外的每张略缩图上增添下载按钮和勾选框,可以直接点“下载”按钮对原图进行下载,也可以勾选想要下载的图片,然后点击“逐个下载”或“打包下载”。想要略缩图以原图比例显示的话,不需要脚本就可以实现,把网站设置中的“Thumb Size”改成“Original”即可。
// @match        *://wallhaven.cc/*
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// @grant        GM_addStyle
// @license      MIT
// @author       EPC_SG

// ==/UserScript==

(function() {

    GM_addStyle(`
        @import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css');
        .download-btn {
            display: inline-block;
            background: none;
            border: none;
            cursor: pointer;
            font-size: 12px; /* 调整图标大小 */
        }
    `);

    // 添加下载按钮的函数
    function addDownloadButtons(thumbnailPage) {
        // 选择页面中的每个壁纸缩略图
        const thumbnails = thumbnailPage.querySelectorAll('.thumb');

        // 为每个缩略图添加下载按钮
        thumbnails.forEach((thumbnail, index) => {

            // 获取壁纸ID
            const wallpaperId = thumbnail.dataset.wallpaperId;
            if (!wallpaperId) return;

            // 检查 thumb-info 中是否有 PNG 标签
            const isPng = thumbnail.querySelector('.thumb-info .png') !== null;

            // 根据格式构造原图下载链接
            const imageFormat = isPng ? 'png' : 'jpg';
            const imageUrl = `https://w.wallhaven.cc/full/${wallpaperId.substring(0, 2)}/wallhaven-${wallpaperId}.${imageFormat}`;

            // 创建复选框
            const checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.className = 'download-checkbox';
            checkbox.style.position = 'absolute'; // 设置为绝对定位
            checkbox.style.visibility = 'visible'; // 设置为可见
            checkbox.style.zIndex = '1000'; // 确保在最顶层
            checkbox.style.top = '11px'; // 根据需要设置位置
            checkbox.style.left = '42px'; // 根据需要设置位置
            checkbox.style.width = '15px';
            checkbox.style.height = '15px';
            checkbox.value = imageUrl; // 保存图片链接
            checkbox.onclick = updateSelectionRatio;

            // 创建下载按钮
            const button = document.createElement('button');
            //button.innerText = '下载';
            button.className = 'download-btn';
            button.innerHTML = '<i class="fas fa-download"></i>'; // 下载图标
            button.style.position = 'absolute'; // 设置为绝对定位
            button.style.zIndex = '1000'; // 确保在最顶层
            button.style.top = '5px'; // 根据需要设置位置
            button.style.left = '6px'; // 根据需要设置位置
            button.style.padding = '5px 10px';
            button.style.backgroundColor = '#4CAF50';
            button.style.color = 'white';
            button.style.border = 'none';
            button.style.borderRadius = '5px';
            button.style.cursor = 'pointer';
            button.href = imageUrl;
            button.download = imageUrl.split('/').pop();

            // 添加点击事件,下载图片
            button.onclick = async function() {
                const imageUrl = button.href; // 图片的 URL
                const fileName = button.download; // 下载的文件名

                // 获取图片的 Blob 数据
                const response = await fetch(imageUrl);
                const blob = await response.blob();

                //下载
                saveAs(blob, fileName);
            };
            thumbnail.appendChild(button);
            thumbnail.appendChild(checkbox);
        });
    }

    async function batchDownload() {
        const checkboxes = Array.from(document.querySelectorAll('.download-checkbox:checked'));

        // 判断是否有选中的图片
        if (checkboxes.length === 0) {
            progressDisplay.textContent ='未选中任何图片!'; // 提示用户
            return; // 直接返回
        }

        for (const checkbox of checkboxes) {
            const imageUrl = checkbox.value; // 获取图片链接
            const fileName = imageUrl.split('/').pop(); // 从链接中提取文件名

            // 获取图片的 Blob 数据
            const response = await fetch(imageUrl);
            const blob = await response.blob();
            //逐个下载
            saveAs(blob, fileName)
        }
    }

    async function packDownload() {
        const checkboxes = Array.from(document.querySelectorAll('.download-checkbox:checked'));
        const totalFiles = checkboxes.length; // 选中的文件总数
        const zip = new JSZip(); // 创建 JSZip 实例

        // 判断是否有选中的图片
        if (checkboxes.length === 0) {
            progressDisplay.textContent ='未选中任何图片!'; // 提示用户
            return; // 直接返回
        }

        for (let i = 0; i < totalFiles; i++) {
            const checkbox = checkboxes[i];
            const imageUrl = checkbox.value; // 获取图片链接
            const fileName = imageUrl.split('/').pop(); // 从链接中提取文件名

            // 获取图片的 Blob 数据
            const response = await fetch(imageUrl);
            const blob = await response.blob();

            // 将 Blob 添加到 ZIP 文件中
            zip.file(fileName, blob);

            // 更新进度显示
            const progress = Math.round(((i + 1) / totalFiles) * 100); // 计算百分比
            progressDisplay.textContent = `正在下载:${progress}% (${i + 1}/${totalFiles})`; // 显示进度
        }
        // 生成 ZIP 文件并提供下载,添加进度回调
        zip.generateAsync({ type: 'blob', compression: 'STORE' }, (metadata) => {
            // 更新进度显示
            const progress = Math.round(metadata.percent);
            progressDisplay.textContent = `正在打包:${progress}%`;
        }).then(function(content) {
            saveAs(content, "images.zip"); // 下载 ZIP 文件
            progressDisplay.textContent = '打包完成!';
        }).catch(function(err) {
            console.error(err);
            progressDisplay.textContent = '打包失败,请重试。';
        });
    }

    function toggleSelectAll() {
        const checkboxes = document.querySelectorAll('.download-checkbox');
        const allChecked = Array.from(checkboxes).every(checkbox => checkbox.checked);

        checkboxes.forEach(checkbox => {
            checkbox.checked = !allChecked; // 切换状态
        });

        updateSelectionRatio(); // 更新比例显示
    }

    function updateSelectionRatio() {
        const checkboxes = document.querySelectorAll('.download-checkbox');
        const total = checkboxes.length;
        const selected = Array.from(checkboxes).filter(checkbox => checkbox.checked).length;
        ratioDisplay.textContent = `已选择 ${selected} / ${total}`;
    }

    const ratioDisplay = document.createElement('div');
    const selectAllButton = document.createElement('button');
    const batchDownloadButton = document.createElement('button');
    const packDownloadButton = document.createElement('button');
    const progressDisplay = document.createElement('div');
    // 等待页面加载完成
    window.addEventListener('load', function() {
        const searchbar = document.querySelector('.expanded')

        addDownloadButtons(document.querySelector('.thumb-listing-page'));
        updateSelectionRatio()

        // 创建比例显示元素
        ratioDisplay.style.color = 'white';
        ratioDisplay.style.position = 'relative';
        ratioDisplay.style.left = '5px';
        searchbar.appendChild(ratioDisplay);

        // 创建全选按钮
        selectAllButton.textContent = '全选/取消';
        selectAllButton.onclick = (event) => {
            event.preventDefault(); // 阻止默认行为
            toggleSelectAll();
        };
        selectAllButton.style.backgroundColor = '#4CAF50';
        selectAllButton.style.color = 'white';
        selectAllButton.style.padding = '5px 10px';
        selectAllButton.style.borderRadius = '5px';
        selectAllButton.style.cursor = 'pointer';
        selectAllButton.style.position = 'relative';
        selectAllButton.style.left = '10px';
        searchbar.appendChild(selectAllButton);

        // 创建逐个下载按钮
        batchDownloadButton.textContent = '逐个下载';
        batchDownloadButton.onclick = (event) => {
            event.preventDefault(); // 阻止默认行为
            batchDownload();
        };
        batchDownloadButton.style.backgroundColor = '#4CAF50';
        batchDownloadButton.style.color = 'white';
        batchDownloadButton.style.padding = '5px 10px';
        batchDownloadButton.style.borderRadius = '5px';
        batchDownloadButton.style.cursor = 'pointer';
        batchDownloadButton.style.position = 'relative';
        batchDownloadButton.style.left = '15px';
        searchbar.appendChild(batchDownloadButton);

        // 创建打包下载按钮
        packDownloadButton.textContent = '打包下载';
        packDownloadButton.onclick = (event) => {
            event.preventDefault(); // 阻止默认行为
            packDownload();
        };
        packDownloadButton.style.backgroundColor = '#4CAF50';
        packDownloadButton.style.color = 'white';
        packDownloadButton.style.padding = '5px 10px';
        packDownloadButton.style.borderRadius = '5px';
        packDownloadButton.style.cursor = 'pointer';
        packDownloadButton.style.position = 'relative';
        packDownloadButton.style.left = '20px';
        searchbar.appendChild(packDownloadButton);

        // 创建进度显示元素
        progressDisplay.style.position = 'relative';
        progressDisplay.style.left = '25px';
        searchbar.appendChild(progressDisplay);

        // 监视DOM变化,动态添加按钮
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (mutation.addedNodes.length) {
                    mutation.addedNodes.forEach((node) => {
                        if (node.classList && node.classList.contains('thumb-listing-page')) {
                            // 调用 addDownloadButtons 处理最新的 thumb-listing-page
                            addDownloadButtons(node);
                            updateSelectionRatio();
                        }
                    });
                }
            });
        });

        // 监视父级元素 thumbs
        const config = { childList: true, subtree: true };
        const targetNode = document.querySelector('.thumb-listing'); // 监视 thumb-listing
        if (targetNode) {
            observer.observe(targetNode, config);
        }
    });
})();