// ==UserScript== // @name Pixiv 图片打包下载器 // @namespace pixiv-batch-downloader // @version 1.0 // @description 在Pixiv作品页面添加下载按钮,一键下载所有图片并打包为ZIP // @author WorkBuddy // @match https://www.pixiv.net/artworks/* // @grant GM_xmlhttpRequest // @connect i.pximg.net // @connect www.pixiv.net // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js // @license MIT // ==/UserScript== (function() { 'use strict'; function createButton() { const btn = document.createElement('button'); btn.id = 'pxiv-dl-btn'; btn.innerHTML = '📥 打包下载'; btn.style.cssText = ` position: fixed; top: 80px; right: 20px; z-index: 99999; padding: 12px 24px; background: #0096fa; color: white; border: none; border-radius: 10px; font-size: 15px; font-weight: bold; cursor: pointer; box-shadow: 0 4px 12px rgba(0,150,250,0.4); transition: all 0.2s; `; btn.onmouseenter = () => btn.style.background = '#0078d4'; btn.onmouseleave = () => btn.style.background = '#0096fa'; return btn; } function createProgress() { const div = document.createElement('div'); div.id = 'pxiv-dl-progress'; div.style.cssText = ` position: fixed; top: 130px; right: 20px; z-index: 99999; padding: 12px 20px; background: rgba(0,0,0,0.8); color: white; border-radius: 8px; font-size: 13px; display: none; min-width: 200px; `; return div; } function sanitizeFilename(name) { return name.replace(/[<>:"/\\|?*]/g, '_').trim() || 'pixiv_artwork'; } async function downloadAll() { const btn = document.getElementById('pxiv-dl-btn'); const progress = document.getElementById('pxiv-dl-progress'); btn.disabled = true; btn.style.opacity = '0.6'; progress.style.display = 'block'; try { const match = window.location.href.match(/artworks\/(\d+)/); if (!match) { progress.textContent = '❌ 无法获取作品ID'; return; } const illustId = match[1]; let title = illustId; const h1 = document.querySelector('h1'); if (h1 && h1.textContent.trim()) { title = h1.textContent.trim(); } title = sanitizeFilename(title); progress.textContent = '⏳ 获取图片列表...'; const resp = await fetch(`https://www.pixiv.net/ajax/illust/${illustId}/pages`, { credentials: 'include' }); const data = await resp.json(); if (data.error || !data.body) { progress.textContent = '❌ 获取失败: ' + (data.message || '未知错误'); return; } const pages = data.body; const total = pages.length; if (total === 0) { progress.textContent = '❌ 没有找到图片'; return; } progress.textContent = `⏳ 下载中 0/${total}...`; const zip = new JSZip(); const folder = zip.folder(title); for (let i = 0; i < total; i++) { const url = pages[i].urls.original; const ext = url.split('.').pop(); const filename = `${String(i + 1).padStart(3, '0')}.${ext}`; progress.textContent = `⏳ 下载中 ${i + 1}/${total}...`; const blob = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'Referer': 'https://www.pixiv.net/' }, responseType: 'blob', onload: function(response) { resolve(response.response); }, onerror: function(err) { console.error('Failed:', url, err); reject(new Error('图片 ' + (i + 1) + ' 下载失败')); }, ontimeout: function() { reject(new Error('图片 ' + (i + 1) + ' 下载超时')); } }); }); folder.file(filename, blob); } progress.textContent = '📦 正在打包ZIP...'; const zipBlob = await zip.generateAsync({ type: 'blob', compression: 'STORE' }); const a = document.createElement('a'); a.href = URL.createObjectURL(zipBlob); a.download = title + '.zip'; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(() => URL.revokeObjectURL(a.href), 5000); progress.textContent = '✅ 完成!' + total + ' 张图片已打包下载'; setTimeout(() => { progress.style.display = 'none'; }, 5000); } catch (e) { progress.textContent = '❌ 出错: ' + e.message; console.error(e); } finally { btn.disabled = false; btn.style.opacity = '1'; } } function init() { if (document.getElementById('pxiv-dl-btn')) return; const btn = createButton(); const progress = createProgress(); btn.onclick = downloadAll; document.body.appendChild(btn); document.body.appendChild(progress); } setInterval(() => { if (window.location.href.includes('/artworks/') && !document.getElementById('pxiv-dl-btn')) { init(); } }, 1000); })();