UX Batch Downloader

Batch downloader for ux.getuploader.com

当前为 2021-12-14 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name UX Batch Downloader
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1
  5. // @description Batch downloader for ux.getuploader.com
  6. // @author Amarillys
  7. // @match https://ux.getuploader.com/*
  8. // @icon https://www.google.com/s2/favicons?domain=getuploader.com
  9. // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.2.2/jszip.min.js
  10. // @require https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.6/dat.gui.min.js
  11. // @grant GM_xmlhttpRequest
  12. // @grant unsafeWindow
  13. // @license MIT
  14. // ==/UserScript==
  15.  
  16.  
  17. (function() {
  18. 'use strict';
  19. const G = init()
  20. window = unsafeWindow
  21. // const DL_PATH = 'https://downloadx.getuploader.com/g/'
  22. const user = location.pathname.match(/\/(.*?)\//)[1]
  23. let status = G.initStatus()
  24. initGUI(G)
  25.  
  26. function init() {
  27. const G = {}
  28. G.Zip = class {
  29. constructor(title) {
  30. this.title = title
  31. this.zip = new JSZip()
  32. this.size = 0
  33. this.partIndex = 0
  34. }
  35. file(filename, blob) {
  36. this.zip.file(filename, blob, {
  37. compression: 'STORE'
  38. })
  39. this.size += blob.size
  40. }
  41. add(folder, name, blob) {
  42. if (this.size + blob.size >= G.Zip.MAX_SIZE)
  43. this.pack()
  44. this.zip.folder(G.purifyName(folder)).file(G.purifyName(name), blob, {
  45. compression: 'STORE'
  46. })
  47. this.size += blob.size
  48. }
  49. pack() {
  50. if (this.size === 0) return
  51. let index = this.partIndex
  52. this.zip
  53. .generateAsync({
  54. type: 'blob',
  55. compression: 'STORE'
  56. })
  57. .then(zipBlob => G.saveBlob(zipBlob, `${this.title}-${index}.zip`))
  58. this.partIndex++
  59. this.zip = new JSZip()
  60. this.size = 0
  61. }
  62. }
  63. G.Zip.MAX_SIZE = 850000000
  64.  
  65. G.setProgress = status => {
  66. G.progressCtl.setValue(status.processed / status.amount * 100)
  67. }
  68.  
  69. G.gmRequireImage = function (url, status) {
  70. const callback = () => {
  71. status.progressList[status.amount] = 1
  72. status.processed++
  73. G.setProgress(status)
  74. }
  75. return new Promise((resolve, reject) =>
  76. GM_xmlhttpRequest({
  77. method: 'GET',
  78. url,
  79. overrideMimeType: 'application/octet-stream',
  80. responseType: 'blob',
  81. asynchrouns: true,
  82. onload: res => {
  83. callback()
  84. resolve(res.response)
  85. },
  86. onprogress: () => {
  87. G.setProgress(status)
  88. },
  89. onerror: () =>
  90. GM_xmlhttpRequest({
  91. method: 'GET',
  92. url,
  93. overrideMimeType: 'application/octet-stream',
  94. responseType: 'arraybuffer',
  95. onload: res => {
  96. callback()
  97. resolve(new Blob([res.response]))
  98. },
  99. onprogress: res => {
  100. status.progressList[status.amount] = res.done / res.total
  101. G.setProgress(status)
  102. },
  103. onerror: res => reject(res)
  104. })
  105. })
  106. )
  107. }
  108.  
  109. G.initStatus = function (user, addition) {
  110. const zip = new G.Zip(`${user}-${addition}`)
  111. return {
  112. amount: 1,
  113. processed: 0,
  114. progress: 0,
  115. failed: 0,
  116. start: Math.min(...document.documentElement.innerHTML.match(/download\/\d+/g).map(r => +r.slice(9))),
  117. end: +document.documentElement.innerHTML.match(/download\/(\d+)/)[1],
  118. progressList: [],
  119. zip
  120. }
  121. }
  122.  
  123. G.purifyName = function (filename) {
  124. return filename.replaceAll(':', '').replaceAll('/', '').replaceAll('\\', '').replaceAll('>', '').replaceAll('<', '')
  125. .replaceAll('*:', '').replaceAll('|', '').replaceAll('?', '').replaceAll('"', '')
  126. }
  127.  
  128. G.saveBlob = function (blob, fileName) {
  129. let downloadDom = document.createElement('a')
  130. document.body.appendChild(downloadDom)
  131. downloadDom.style = `display: none`
  132. let url = window.URL.createObjectURL(blob)
  133. downloadDom.href = url
  134. downloadDom.download = fileName
  135. downloadDom.click()
  136. window.URL.revokeObjectURL(url)
  137. }
  138.  
  139. G.sleep = function (ms) {
  140. return new Promise(resolve => {
  141. setTimeout(resolve, ms)
  142. })
  143. }
  144. return G
  145. }
  146.  
  147. function initGUI(G) {
  148. const gui = new dat.GUI({
  149. autoPlace: false,
  150. useLocalStorage: false
  151. })
  152. const clickHandler = {
  153. text() {},
  154. download() { downloadAll() },
  155. downloadById() { downloadAll(+status.start, +status.end) }
  156. }
  157. G.label = gui.add(clickHandler, 'text').name('v0.1')
  158. gui.add(clickHandler, 'download').name('Download All')
  159. G.progressCtl = gui.add(status, 'progress', 0, 100, 0.01).name('Progress')
  160. gui.add(status, 'start').name('Start ID')
  161. gui.add(status, 'end').name('End ID')
  162. gui.add(clickHandler, 'downloadById').name('DL By Id')
  163. gui.domElement.style.position = 'fixed'
  164. gui.domElement.style.top = '10%'
  165. gui.domElement.style.opacity = 0.75
  166. document.body.appendChild(gui.domElement)
  167. gui.open()
  168. }
  169.  
  170. async function downloadAll(startID, endID) {
  171. const zipName = (startID && endID) ? `${startID}-${endID}` : 'all'
  172. status = G.initStatus(user, zipName)
  173. let index = 1
  174. do {
  175. const html = await (await fetch(`https://ux.getuploader.com/${user}/index/date/desc/${index++}`)).text()
  176. status.amount = (startID && endID) ? (endID - startID + 1 - status.failed) : +html.match(/<td>(\d+) ファイル<\/td>/)[1]
  177. let files = html.match(/<a href="https:\/\/ux\.getuploader\.com\/\w+\/download\/\d+.*?">.*?<\/a>/g)?.slice(0, 15).filter(i => !i.includes('<img'))
  178. if (!files || files.length === 0) {
  179. console.warn('cannot get any files.')
  180. status.zip.pack()
  181. break
  182. }
  183. files = files.map(f => f.replace('<a href="', '').replace('" title="', '/').slice(0, -2))
  184. for (let i = 0; i < files.length; ++i) {
  185. let file = files[i].slice(0, files[i].indexOf('"'))
  186. .replace('https://ux.getuploader.com', 'https://downloadx.getuploader.com/g').replace('download/', '')
  187. let filename = file.slice(35).match(/.+\/(.*?\.\w+)/)[1]
  188. let fileIndex = file.slice(35).match(/\/(\d+)\//)[1]
  189. if (startID && endID) {
  190. if (fileIndex < startID || fileIndex > endID) continue
  191. }
  192. await(300)
  193. try {
  194. const blob = await G.gmRequireImage(file, status)
  195. status.zip.add(user, `${fileIndex}-${filename}`, blob)
  196. } catch (e) {
  197. status.failed++
  198. console.log(`Failed to download: ${file} ${e}`)
  199. }
  200. }
  201. } while (true)
  202. }
  203. })();