// ==UserScript== // @name Fanbox Batch Downloader // @namespace http://tampermonkey.net/ // @version 0.41 // @description Batch Download on creator, not post // @author https://github.com/amarillys // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.2.2/jszip.min.js // @match https://www.pixiv.net/fanbox/creator/* // @grant GM_xmlhttpRequest // @run-at document-end // @license MIT // @downloadURL none // ==/UserScript== /** * Update Log * > 191226 * Support downloading by batch(default: 100 files per batch) * Support donwloading by specific index * // 中文注释 * 新增支持分批下载的功能(默认100个文件一个批次) * 新增支持按索引下载的功能 * > 191223 * Add support of files * Improve the detect of file extension * Change Download Request as await, for avoiding delaying. * Add manual package while click button use middle button of mouse * // 中文注释 * 增加对附件下载的支持 * 优化文件后缀名识别 * 修改下载方式为按顺序下载,避免超时 * 增加当鼠标中键点击时手动打包 * */ (function () { 'use strict'; let zip = null let amount = 0 let uiInited = false const fetchOptions = { credentials: 'include', headers: { Accept: 'application/json, text/plain, */*' } } let init = async () => { let baseBtn = document.querySelector('[href="/fanbox/notification"]') let className = baseBtn.parentNode.className let parent = baseBtn.parentNode.parentNode let inputDiv = document.createElement('div') let creatorId = parseInt(document.URL.split('/')[5]) inputDiv.innerHTML = ` -> / 分批/Batch: ` parent.appendChild(inputDiv) let downloadBtn = document.createElement('div') downloadBtn.id = 'FanboxDownloadBtn' downloadBtn.className = className downloadBtn.innerHTML = `
Initilizing/初始化中...
` parent.appendChild(downloadBtn) uiInited = true let creatorInfo = await getAllPostsByFanboxId(creatorId) // count files amount for (let i = 0, p = creatorInfo.posts; i < p.length; ++i) { if (!p[i].body) continue let { files, images } = p[i].body amount += images ? images.length : 0 amount += files ? files.length : 0 } document.querySelector('#amarillys-download-progress').innerHTML = ` Download/下载 ` document.querySelector('#dlEnd').value = amount downloadBtn.addEventListener('mousedown', event => { if (event.button === 1) { zip.generateAsync({ type: 'blob' }).then(zipBlob => saveBlob(zipBlob, `${creatorId}.zip`)) } else { console.log('startDownloading'); downloadByFanboxId(creatorInfo, creatorId); } }) } window.onload = () => { init() let timer = setInterval(() => { if (!uiInited && document.querySelector('#FanboxDownloadBtn') === null) init() else clearInterval(timer) }, 3000) } function gmRequireImage(url) { return new Promise(resolve => { GM_xmlhttpRequest({ method: 'GET', url, responseType: 'blob', onload: res => { resolve(res.response) } }) }) } async function downloadByFanboxId(creatorInfo, creatorId) { zip = new JSZip() let processed = 0 let ptr = 0 let start = document.getElementById('dlStart').value - 1 let end = document.getElementById('dlEnd').value - 1 amount = end - start + 1 let stepped = 0 let STEP = parseInt(document.querySelector('#dlStep').value) let textDiv = document.querySelector('#amarillys-download-progress') zip.file('cover.jpg', await gmRequireImage(creatorInfo.cover), { compression: "STORE" }) const detectFinish = () => { if (amount === processed) { zip.generateAsync({ type: 'blob' }).then(zipBlob => { saveBlob(zipBlob, `${creatorId}-${end - stepped + 2}-${end + 1}.zip`) textDiv.innerHTML = ` Okayed/完成 ` }) } else { if (stepped >= STEP) { zip.generateAsync({ type: 'blob' }).then(zipBlob => { saveBlob(zipBlob, `${creatorId}-${ptr}-${ptr + stepped}.zip`) }) zip = new JSZip() stepped = 0 } } } // start downloading for (let i = 0, p = creatorInfo.posts; i < p.length; ++i) { let folder = `${p[i].title}-${p[i].id}` if (!p[i].body) continue let { files, images } = p[i].body if (files) { for (let j = 0; j < files.length; ++j, ++ptr) { if (ptr < start) continue if (ptr > end) break let extension = files[j].url.split('.').slice(-1)[0] let blob = await gmRequireImage(files[j].url) saveBlob(blob, `${creatorId} - ${folder}_${j}.${extension}`) processed++ textDiv.innerHTML = ` ${ processed } / ${ amount } ` console.log(` Progress: ${ processed } / ${ amount }`) } detectFinish() } if (images) { for (let j = 0; j < images.length; ++j, ++ptr) { if (ptr < start) continue if (ptr > end) break let extension = images[j].originalUrl.split('.').slice(-1)[0] textDiv.innerHTML = ` ${ processed } / ${ amount } ` let blob = await gmRequireImage(images[j].originalUrl) zip.folder(folder).file(`${folder}_${j}.${extension}`, blob, { compression: "STORE" }) processed++ stepped++ textDiv.innerHTML = ` ${ processed } / ${ amount } ` console.log(` Progress: ${ processed } / ${ amount }`) detectFinish() } } } } async function getAllPostsByFanboxId(creatorId) { let fristUrl = `https://www.pixiv.net/ajax/fanbox/creator?userId=${ creatorId }` let creatorInfo = { cover: null, posts: [] } let firstData = await (await fetch(fristUrl, fetchOptions)).json() let body = firstData.body creatorInfo.cover = body.creator.coverImageUrl creatorInfo.posts.push(...body.post.items) let nextPageUrl = body.post.nextUrl while (nextPageUrl) { let nextData = await (await fetch(nextPageUrl, fetchOptions)).json() creatorInfo.posts.push(...nextData.body.items) nextPageUrl = nextData.body.nextUrl } return creatorInfo } function saveBlob(blob, fileName) { let downloadDom = document.createElement('a') document.body.appendChild(downloadDom) downloadDom.style = `display: none` let url = window.URL.createObjectURL(blob) downloadDom.href = url downloadDom.download = fileName downloadDom.click() window.URL.revokeObjectURL(url) } })();