// ==UserScript== // @name Pixiv 图片批量下载器 // @namespace pixiv-batch-downloader // @version 2.1 // @description 在Pixiv作品页面添加下载按钮,选择文件夹后逐张下载所有图片到同一目录 // @author WorkBuddy // @match https://www.pixiv.net/artworks/* // @grant GM_xmlhttpRequest // @connect i.pximg.net // @connect www.pixiv.net // @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.85); color: white; border-radius: 8px; font-size: 13px; display: none; min-width: 240px; max-width: 340px; line-height: 1.6; `; 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'; progress.innerHTML = ''; // 进度文本行 const statusLine = document.createElement('div'); progress.appendChild(statusLine); // 进度条 const barWrapper = document.createElement('div'); barWrapper.style.cssText = 'margin-top:8px;width:100%;height:6px;background:rgba(255,255,255,0.2);border-radius:3px;overflow:hidden;'; const bar = document.createElement('div'); bar.style.cssText = 'height:100%;background:#00e676;width:0%;border-radius:3px;transition:width 0.3s;'; barWrapper.appendChild(bar); progress.appendChild(barWrapper); try { const match = window.location.href.match(/artworks\/(\d+)/); if (!match) { statusLine.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); // 获取图片列表 statusLine.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) { statusLine.textContent = '❌ 获取失败: ' + (data.message || '未知错误'); return; } const pages = data.body; const total = pages.length; if (total === 0) { statusLine.textContent = '❌ 没有找到图片'; return; } // 让用户选择文件夹 statusLine.textContent = '📁 请在弹出的对话框中选择保存文件夹...'; let dirHandle; try { dirHandle = await window.showDirectoryPicker({ mode: 'readwrite', id: 'pixiv-downloads', startIn: 'downloads' }); } catch (e) { statusLine.textContent = '❌ 未选择文件夹,已取消'; return; } // 在选定文件夹内创建以作品名命名的子文件夹 const subDir = await dirHandle.getDirectoryHandle(title, { create: true }); statusLine.textContent = '📦 共 ' + total + ' 张,开始下载到 ' + title + '/ 文件夹'; let successCount = 0; let failCount = 0; for (let i = 0; i < total; i++) { const pageNum = i + 1; const url = pages[i].urls.original; const ext = url.split('.').pop(); const filename = String(pageNum).padStart(3, '0') + '.' + ext; statusLine.textContent = '⏳ 下载中 ' + pageNum + '/' + total; bar.style.width = Math.round((i / total) * 100) + '%'; try { // 用 GM_xmlhttpRequest 下载图片(绕过CORS,带Referer) 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) { reject(err); }, ontimeout: function() { reject(new Error('timeout')); } }); }); // 写入文件夹 const fileHandle = await subDir.getFileHandle(filename, { create: true }); const writable = await fileHandle.createWritable(); await writable.write(blob); await writable.close(); successCount++; // 适当延迟,避免下载过快 await new Promise(r => setTimeout(r, 300)); } catch (err) { console.error('Failed:', url, err); failCount++; statusLine.textContent = '⚠️ 第 ' + pageNum + ' 张失败,跳过继续...'; await new Promise(r => setTimeout(r, 800)); } } bar.style.width = '100%'; let resultMsg = '✅ 完成!成功 ' + successCount + '/' + total; if (failCount > 0) { resultMsg += '(失败 ' + failCount + ' 张)'; } statusLine.innerHTML = resultMsg + '
图片已保存到你选择的文件夹下的「' + title + '」子文件夹中'; setTimeout(() => { progress.style.display = 'none'; }, 8000); } catch (e) { statusLine.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); })();