Greasy Fork

来自缓存

Greasy Fork is available in English.

Wallhaven一键下载

在Wallhaven缩略图上添加下载按钮和复选框,支持单张下载、逐个下载和打包下载。

当前为 2025-02-25 提交的版本,查看 最新版本

// ==UserScript==
// @name         Wallhaven一键下载
// @version      1.3
// @description  在Wallhaven缩略图上添加下载按钮和复选框,支持单张下载、逐个下载和打包下载。
// @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
// @namespace 这是干啥的,不是很懂
// ==/UserScript==

(function() {
    'use strict';

    // 注入CSS样式
    GM_addStyle(`
        .download-btn {
            position: absolute;
            top: 5px;
            left: 6px;
            z-index: 1000;
            background: #4CAF50;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            padding: 5px 10px;
        }
        .download-checkbox {
            position: absolute !important;
            top: 11px !important;
            left: 42px !important;
            z-index: 1000 !important;
            width: 15px !important;
            height: 15px !important;
            visibility: visible !important;
        }
        .control-btn {
            background: #4CAF50;
            color: white;
            padding: 5px 10px;
            border-radius: 5px;
            cursor: pointer;
            margin-left: 5px;
        }
        .thumb { position: relative !important; }
    `);

    // 获取图片URL
    const getImageUrl = (id, isPng) => {
        const format = isPng ? 'png' : 'jpg';
        return `https://w.wallhaven.cc/full/${id.slice(0, 2)}/wallhaven-${id}.${format}`;
    };

    // 添加按钮和复选框
    const addButtons = (container) => {
        container.querySelectorAll('.thumb:not([data-processed])').forEach(thumb => {
            const id = thumb.dataset.wallpaperId;
            if (!id) return;

            thumb.dataset.processed = 'true';
            const isPng = !!thumb.querySelector('.thumb-info .png');
            const url = getImageUrl(id, isPng);

            // 下载按钮
            const dlBtn = document.createElement('button');
            dlBtn.className = 'download-btn';
            dlBtn.innerHTML = '<i class="fas fa-download"></i>'; // 下载图标
            dlBtn.onclick = async () => {
                const response = await fetch(url);
                const blob = await response.blob();
                saveAs(blob, url.split('/').pop());
            };
            thumb.appendChild(dlBtn);

            // 复选框
            const checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.className = 'download-checkbox';
            checkbox.value = url;
            checkbox.onclick = updateRatio;
            thumb.appendChild(checkbox);
        });
        updateRatio();
    };

    // 批量下载
    const batchDownload = async () => {
        const selected = document.querySelectorAll('.download-checkbox:checked');
        if (!selected.length) return showProgress('未选中任何图片!');
        for (const cb of selected) {
            const response = await fetch(cb.value);
            const blob = await response.blob();
            saveAs(blob, cb.value.split('/').pop());
        }
    };

    // 打包下载
    const packDownload = async () => {
        const selected = document.querySelectorAll('.download-checkbox:checked');
        if (!selected.length) return showProgress('未选中任何图片!');

        const zip = new JSZip();
        for (let i = 0; i < selected.length; i++) {
            const url = selected[i].value;
            const response = await fetch(url);
            const blob = await response.blob();
            zip.file(url.split('/').pop(), blob);
            showProgress(`正在下载:${Math.round((i + 1) / selected.length * 100)}% (${i + 1}/${selected.length})`);
        }

        zip.generateAsync({ type: 'blob' }, ({ percent }) => {
            showProgress(`正在打包:${Math.round(percent)}%`);
        }).then(blob => {
            saveAs(blob, 'images.zip');
            showProgress('打包完成!');
        }).catch(err => {
            console.error(err);
            showProgress('打包失败,请重试。');
        });
    };

    // 更新选择比例
    const updateRatio = () => {
        const all = document.querySelectorAll('.download-checkbox').length;
        const selected = document.querySelectorAll('.download-checkbox:checked').length;
        ratioDisplay.textContent = `已选择 ${selected} / ${all}`;
    };

    // 全选/取消
    const toggleAll = () => {
        const checkboxes = document.querySelectorAll('.download-checkbox');
        const allChecked = Array.from(checkboxes).every(cb => cb.checked);
        checkboxes.forEach(cb => cb.checked = !allChecked);
        updateRatio();
    };

    // 显示进度
    const showProgress = (text) => progressDisplay.textContent = text;

    // 初始化控制面板
    const initControls = () => {
        const toolbar = document.querySelector('.expanded') || document.body;
        [ratioDisplay, selectAllButton, batchDownloadButton, packDownloadButton, progressDisplay] = [
            document.createElement('span'),
            document.createElement('button'),
            document.createElement('button'),
            document.createElement('button'),
            document.createElement('span')
        ];

        ratioDisplay.style.color = 'white';
        selectAllButton.textContent = '全选/取消';
        selectAllButton.className = 'control-btn';
        selectAllButton.onclick = (e) => { e.preventDefault(); toggleAll(); };
        batchDownloadButton.textContent = '逐个下载';
        batchDownloadButton.className = 'control-btn';
        batchDownloadButton.onclick = (e) => { e.preventDefault(); batchDownload(); };
        packDownloadButton.textContent = '打包下载';
        packDownloadButton.className = 'control-btn';
        packDownloadButton.onclick = (e) => { e.preventDefault(); packDownload(); };
        progressDisplay.style.color = 'white';

        [ratioDisplay, selectAllButton, batchDownloadButton, packDownloadButton, progressDisplay]
            .forEach((el, i) => {
                el.style.marginLeft = `${5 + i * 5}px`;
                toolbar.appendChild(el);
            });
    };

    // 主逻辑
    let ratioDisplay, selectAllButton, batchDownloadButton, packDownloadButton, progressDisplay;
    window.addEventListener('load', () => {
        initControls();
        const listingPage = document.querySelector('.thumb-listing-page');
        if (listingPage) addButtons(listingPage);

        const observer = new MutationObserver(mutations => {
            mutations.forEach(m => {
                m.addedNodes.forEach(node => {
                    if (node.classList?.contains('thumb-listing-page')) addButtons(node);
                });
            });
        });
        observer.observe(document.querySelector('.thumb-listing') || document.body, { childList: true, subtree: true });
    });
})();