您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Batch Download on creator, not post
当前为
- // ==UserScript==
- // @name Fanbox Batch Downloader
- // @namespace http://tampermonkey.net/
- // @version 0.56
- // @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
- // ==/UserScript==
- /**
- * Update Log
- * > 200222
- * Bug Fixed - Psd files download failure <Change download type from blob to arraybuffer, which cause low performence>
- * Bug Fixed - Display incorrect on partial download
- * > 200222
- * Bug Fixed - Post with '/' cause deep path in zip
- * > 200102
- * Bug Fixed - Caused by empty cover
- * > 191228
- * Bug Fixed
- * Correct filenames
- * > 191227
- * Code Reconstruct
- * Support downloading of artice
- * Correct filenames
- *
- * // 中文注释
- * 代码重构
- * 新增对文章的下载支持
- * > 200222
- * 偷懒,以后不加中文注释
- * > 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
- * // 中文注释
- * 增加对附件下载的支持
- * 优化文件后缀名识别
- * 修改下载方式为按顺序下载,避免超时
- * 增加当鼠标中键点击时手动打包
- **/
- /* global JSZip GM_xmlhttpRequest */
- (function () {
- 'use strict'
- let zip = null
- let amount = 0
- let uiInited = false
- const fetchOptions = {
- credentials: "include",
- headers: {
- Accept: "application/json, text/plain, */*"
- }
- }
- class Zip {
- constructor(title) {
- this.title = title
- this.zip = new JSZip()
- this.size = 0
- this.partIndex = 0
- }
- file(filename, blob) {
- this.zip.file(filename, blob, {
- compression: "STORE"
- })
- this.size += blob.size
- }
- add(folder, name, blob) {
- if (this.size + blob.size >= Zip.MAX_SIZE) {
- let index = this.partIndex
- this.zip.generateAsync({ type: "blob" }).then(zipBlob =>
- saveBlob(zipBlob, `${this.title}-${index}.zip`))
- this.partIndex++
- this.zip = new JSZip()
- this.size = 0
- }
- this.zip.folder(folder).file(name, blob, {
- compression: "STORE"
- })
- this.size += blob.size
- }
- pack() {
- if (this.size === 0) return
- let index = this.partIndex
- this.zip.generateAsync({ type: "blob" }).then(zipBlob =>
- saveBlob(zipBlob, `${this.title}-${index}.zip`))
- this.partIndex++
- this.zip = new JSZip()
- this.size = 0
- }
- }
- Zip.MAX_SIZE = 1048576000
- 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 = `
- <input id="dlStart" style="width: 3rem" type="text" value="1"> -> <input id="dlEnd" style="width: 3rem" type="text">
- | 分批/Batch: <input id="dlStep" style="width: 3rem" type="text" value="100">`
- parent.appendChild(inputDiv)
- let downloadBtn = document.createElement("div")
- downloadBtn.id = "FanboxDownloadBtn"
- downloadBtn.className = className
- downloadBtn.innerHTML = `
- <a href="javascript:void(0)">
- <div id="amarillys-download-progress"
- style="line-height: 32px;width: 8rem;height: 32px;background-color: rgba(232, 12, 2, 0.96);border-radius: 8px;color: #FFF;text-align: center">
- Initilizing/初始化中...
- </div>
- </a>`
- parent.appendChild(downloadBtn)
- uiInited = true
- let creatorInfo = await getAllPostsByFanboxId(creatorId)
- amount = creatorInfo.posts.length
- document.querySelector(
- "#amarillys-download-progress"
- ).innerHTML = ` Download/下载 `
- document.querySelector("#dlEnd").value = amount
- downloadBtn.addEventListener("mousedown", event => {
- if (event.button === 1) {
- zip.pack()
- } 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, reject) => {
- GM_xmlhttpRequest({
- method: "GET",
- url,
- overrideMimeType: "application/octet-stream",
- responseType: "arraybuffer",
- onload: res => resolve(new Blob([res.response])),
- onerror: res => reject(res)
- })
- })
- }
- async function downloadByFanboxId(creatorInfo, creatorId) {
- let processed = 0
- let start = +document.getElementById("dlStart").value - 1
- let end = +document.getElementById("dlEnd").value
- zip = new Zip(`${creatorId}-${creatorInfo.name}-${start + 1}-${end}`)
- let stepped = 0
- amount = end - start
- let STEP = parseInt(document.querySelector("#dlStep").value)
- let textDiv = document.querySelector("#amarillys-download-progress")
- if (creatorInfo.cover)
- zip.file("cover.jpg", await gmRequireImage(creatorInfo.cover))
- textDiv.innerHTML = ` ${processed} / ${amount} `
- // start downloading
- for (let i = start, p = creatorInfo.posts; i < end; ++i) {
- let folder = `${p[i].title.replace(/\//g, '-')}-${p[i].id}`
- if (!p[i].body) continue
- let { blocks, imageMap, fileMap, files, images } = p[i].body
- let picIndex = 0
- let imageList = []
- let fileList = []
- if (p[i].type === "article") {
- let article = `# ${p[i].title}\n`
- for (let j = 0; j < blocks.length; ++j) {
- switch (blocks[j].type) {
- case "p": {
- article += `${blocks[j].text}\n\n`
- break
- }
- case "image": {
- picIndex++
- let image = imageMap[blocks[j].imageId]
- imageList.push(image)
- article += `![${p[i].title} - P${picIndex}](${folder}_${j}.${image.extension})\n\n`
- break
- }
- case "file": {
- let file = fileMap[blocks[j].fileId]
- fileList.push(file)
- article += `[${p[i].title} - ${file.name}](${creatorId}-${folder}-${file.name}.${file.extension})\n\n`
- break
- }
- }
- }
- zip.add(folder, 'article.md', new Blob([article]))
- for (let j = 0; j < imageList.length; ++j) {
- zip.add(folder, `${folder}_${j}.${imageList[j].extension}`,
- await gmRequireImage(imageList[j].originalUrl))
- }
- for (let j = 0; j < fileList.length; ++j)
- saveBlob(await gmRequireImage(fileList[j].url),
- `${creatorId}-${folder}_${j}-${fileList[j].name}.${fileList[j].extension}`)
- }
- if (files) {
- for (let j = 0; j < files.length; ++j) {
- let extension = files[j].url.split(".").slice(-1)[0]
- try {
- let blob = await gmRequireImage(files[j].url)
- saveBlob(blob, `${creatorId}-${creatorInfo.name}-${folder}_${j}.${extension}`)
- } catch(e) {
- console.log(`Failed to download: ${ files[j].url }`)
- }
- }
- }
- if (images) {
- for (let j = 0; j < images.length; ++j) {
- let extension = images[j].originalUrl.split(".").slice(-1)[0]
- textDiv.innerHTML = ` ${processed} / ${amount} `
- zip.add(folder, `${folder}_${j}.${extension}`, await gmRequireImage(images[j].originalUrl))
- }
- }
- processed++
- stepped++
- textDiv.innerHTML = ` ${processed} / ${amount} `
- console.log(` Progress: ${processed} / ${amount}`)
- if (stepped >= STEP) {
- zip.pack()
- stepped = 0
- }
- }
- zip.pack()
- textDiv.innerHTML = ` Okayed/完成 `
- }
- 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.name = body.creator.user.name
- creatorInfo.posts.push(...body.post.items.filter(p => p.body))
- let nextPageUrl = body.post.nextUrl
- while (nextPageUrl) {
- let nextData = await (await fetch(nextPageUrl, fetchOptions)).json()
- creatorInfo.posts.push(...nextData.body.items.filter(p => p.body))
- 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)
- }
- })()