Fanbox Batch Downloader

Batch Download on creator, not post

当前为 2019-12-23 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Fanbox Batch Downloader
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.32
  5. // @description Batch Download on creator, not post
  6. // @author https://github.com/amarillys
  7. // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.2.2/jszip.min.js
  8. // @match https://www.pixiv.net/fanbox/creator/*
  9. // @grant GM_xmlhttpRequest
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. /**
  14. * Update Log
  15. * > 191223
  16. * Add support of files
  17. * Improve the detect of file extension
  18. * Change Download Request as await, for avoiding delaying.
  19. * Add manual package while click button use middle button of mouse
  20. * // 中文注释
  21. * 增加对附件下载的支持
  22. * 优化文件后缀名识别
  23. * 修改下载方式为按顺序下载,避免超时
  24. * 增加当鼠标中键点击时手动打包
  25. * */
  26.  
  27.  
  28. (function () {
  29. 'use strict';
  30.  
  31. let zip = new JSZip()
  32. const fetchOptions = {
  33. credentials: 'include',
  34. headers: {
  35. Accept: 'application/json, text/plain, */*'
  36. }
  37. }
  38.  
  39. window.onload = () => {
  40. let baseBtn = document.querySelector('[href="/fanbox/notification"]')
  41. let className = baseBtn.parentNode.className
  42. let parent = baseBtn.parentNode.parentNode
  43. let downloadBtn = document.createElement('div')
  44. downloadBtn.className = className
  45. downloadBtn.innerHTML = `
  46. <a href="javascript:void(0)">
  47. <div id="amarillys-download-progress"
  48. style="line-height: 32px;width: 100px;height: 32px;background-color: rgba(232, 12, 2, 0.96);;border-radius: 8px;color: #FFF;text-align: center;">
  49. Download/下载
  50. </div>
  51. </a>`
  52. downloadBtn.addEventListener('mousedown', event => {
  53. let creatorId = parseInt(document.URL.split('/')[5])
  54. if (event.button === 1) {
  55. zip.generateAsync({
  56. type: 'blob'
  57. }).then(zipBlob => saveBlob(zipBlob, `${creatorId}.zip`))
  58. } else {
  59. console.log('startDownloading');
  60. downloadByFanboxId(creatorId);
  61. }
  62. })
  63. parent.appendChild(downloadBtn)
  64. }
  65.  
  66. function gmRequireImage(url) {
  67. return new Promise(resolve => {
  68. GM_xmlhttpRequest({
  69. method: 'GET',
  70. url,
  71. responseType: 'blob',
  72. onload: res => {
  73. resolve(res.response)
  74. }
  75. })
  76. })
  77. }
  78.  
  79. async function downloadByFanboxId(creatorId) {
  80. let textDiv = document.querySelector('#amarillys-download-progress')
  81. textDiv.innerHTML = ` ...... `
  82. let creatorInfo = await getAllPostsByFanboxId(creatorId)
  83. let amount = 0
  84. let processed = 0
  85. let waittime = 0
  86. zip.file('cover.jpg', await gmRequireImage(creatorInfo.cover), {
  87. compression: "STORE"
  88. })
  89.  
  90. // count files amount
  91. for (let i = 0, p = creatorInfo.posts; i < p.length; ++i) {
  92. if (!p[i].body) continue
  93. let { files, images } = p[i].body
  94. amount += files ? files.length : 0
  95. amount += images ? images.length : 0
  96. }
  97. textDiv.innerHTML = ` ${ processed } / ${ amount } `
  98.  
  99. // start downloading
  100. for (let i = 0, p = creatorInfo.posts; i < p.length; ++i) {
  101. let folder = `${p[i].title}-${p[i].id}`
  102. if (!p[i].body) continue
  103. let { files, images } = p[i].body
  104. if (files) {
  105. for (let j = 0; j < files.length; ++j) {
  106. let extension = files[j].url.split('.').slice(-1)[0]
  107. let blob = await gmRequireImage(files[j].url)
  108. zip.folder(folder).file(`${folder}_${j}.${extension}`, blob, {
  109. compression: "STORE"
  110. })
  111. waittime--
  112. processed++
  113. textDiv.innerHTML = ` ${ processed } / ${ amount } `
  114. console.log(` Progress: ${ processed } / ${ amount }`)
  115. }
  116. }
  117. if (images) {
  118. for (let j = 0; j < images.length; ++j) {
  119. let extension = images[j].originalUrl.split('.').slice(-1)[0]
  120. textDiv.innerHTML = ` ${ processed } / ${ amount } `
  121. let blob = await gmRequireImage(images[j].originalUrl)
  122. zip.folder(folder).file(`${folder}_${j}.${extension}`, blob, {
  123. compression: "STORE"
  124. })
  125. waittime--
  126. processed++
  127. textDiv.innerHTML = ` ${ processed } / ${ amount } `
  128. console.log(` Progress: ${ processed } / ${ amount }`)
  129. }
  130. }
  131. }
  132. // generate zip to download
  133. let timer = setInterval(() => {
  134. waittime++
  135. if (amount === processed || waittime > 300) {
  136. zip.generateAsync({
  137. type: 'blob'
  138. }).then(zipBlob => saveBlob(zipBlob, `${creatorId}.zip`))
  139. clearInterval(timer)
  140. }
  141. }, 1000)
  142. }
  143.  
  144. async function getAllPostsByFanboxId(creatorId) {
  145. let fristUrl = `https://www.pixiv.net/ajax/fanbox/creator?userId=${ creatorId }`
  146. let creatorInfo = {
  147. cover: null,
  148. posts: []
  149. }
  150. let firstData = await (await fetch(fristUrl, fetchOptions)).json()
  151. let body = firstData.body
  152. creatorInfo.cover = body.creator.coverImageUrl
  153. creatorInfo.posts.push(...body.post.items)
  154. let nextPageUrl = body.post.nextUrl
  155. while (nextPageUrl) {
  156. let nextData = await (await fetch(nextPageUrl, fetchOptions)).json()
  157. creatorInfo.posts.push(...nextData.body.items)
  158. nextPageUrl = nextData.body.nextUrl
  159. }
  160. return creatorInfo
  161. }
  162.  
  163. function saveBlob(blob, fileName) {
  164. let downloadDom = document.createElement('a')
  165. downloadDom.id = 'fuck'
  166. document.body.appendChild(downloadDom)
  167. downloadDom.style = `display: none`
  168. let url = window.URL.createObjectURL(blob)
  169. downloadDom.href = url
  170. downloadDom.download = fileName
  171. downloadDom.click()
  172. window.URL.revokeObjectURL(url)
  173. }
  174. })();