UX Batch Downloader

Batch downloader for ux.getuploader.com

目前為 2022-04-05 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name UX Batch Downloader
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.11
  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. const statusTextCtl = 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(callback) {
  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 => {
  58. G.saveBlob(zipBlob, `${this.title}-${index}.zip`)
  59. typeof callback === 'function' && callback()
  60. })
  61. this.partIndex++
  62. this.zip = new JSZip()
  63. this.size = 0
  64. }
  65. }
  66. G.Zip.MAX_SIZE = 850000000
  67.  
  68. G.setProgress = status => {
  69. G.progressCtl.setValue(status.processed / status.amount * 100)
  70. }
  71.  
  72. G.gmRequireImage = function (url, status) {
  73. const callback = () => {
  74. status.progressList[status.amount] = 1
  75. status.processed++
  76. G.setProgress(status)
  77. }
  78. return new Promise((resolve, reject) =>
  79. GM_xmlhttpRequest({
  80. method: 'GET',
  81. url,
  82. overrideMimeType: 'application/octet-stream',
  83. responseType: 'blob',
  84. asynchrouns: true,
  85. onload: res => {
  86. callback()
  87. resolve(res.response)
  88. },
  89. onprogress: () => {
  90. G.setProgress(status)
  91. },
  92. onerror: () =>
  93. GM_xmlhttpRequest({
  94. method: 'GET',
  95. url,
  96. overrideMimeType: 'application/octet-stream',
  97. responseType: 'arraybuffer',
  98. onload: res => {
  99. callback()
  100. resolve(new Blob([res.response]))
  101. },
  102. onprogress: res => {
  103. status.progressList[status.amount] = res.done / res.total
  104. G.setProgress(status)
  105. },
  106. onerror: res => reject(res)
  107. })
  108. })
  109. )
  110. }
  111.  
  112. G.initStatus = function (user, addition) {
  113. const zip = new G.Zip(`${user}-${addition}`)
  114. return {
  115. amount: 1,
  116. processed: 0,
  117. progress: 0,
  118. failed: 0,
  119. start: Math.min(...document.documentElement.innerHTML.match(/download\/\d+/g).map(r => +r.slice(9))),
  120. end: +document.documentElement.innerHTML.match(/download\/(\d+)/)[1],
  121. progressList: [],
  122. zip
  123. }
  124. }
  125.  
  126. G.purifyName = function (filename) {
  127. return filename.replaceAll(':', '').replaceAll('/', '').replaceAll('\\', '').replaceAll('>', '').replaceAll('<', '')
  128. .replaceAll('*:', '').replaceAll('|', '').replaceAll('?', '').replaceAll('"', '')
  129. }
  130.  
  131. G.saveBlob = function (blob, fileName) {
  132. let downloadDom = document.createElement('a')
  133. document.body.appendChild(downloadDom)
  134. downloadDom.style = `display: none`
  135. let url = window.URL.createObjectURL(blob)
  136. downloadDom.href = url
  137. downloadDom.download = fileName
  138. downloadDom.click()
  139. window.URL.revokeObjectURL(url)
  140. }
  141.  
  142. G.sleep = function (ms) {
  143. return new Promise(resolve => {
  144. setTimeout(resolve, ms)
  145. })
  146. }
  147. return G
  148. }
  149.  
  150. function initGUI(G) {
  151. const gui = new dat.GUI({
  152. autoPlace: false,
  153. useLocalStorage: false
  154. })
  155. const clickHandler = {
  156. text() {},
  157. download() { downloadAll() },
  158. downloadById() { downloadAll(+status.start, +status.end) }
  159. }
  160. G.label = gui.add(clickHandler, 'text').name('v0.11')
  161. gui.add(clickHandler, 'download').name('Download All')
  162. G.progressCtl = gui.add(status, 'progress', 0, 100, 0.01).name('Progress')
  163. gui.add(status, 'start', 1, 1000, 1).name('Start ID')
  164. gui.add(status, 'end', 1, 1000, 1).name('End ID')
  165. gui.add(clickHandler, 'downloadById').name('DL By Id')
  166. const statusTextCtl = gui.add(clickHandler, 'text').name('Initialized.')
  167. gui.domElement.style.position = 'fixed'
  168. gui.domElement.style.top = '10%'
  169. gui.domElement.style.opacity = 0.75
  170. document.body.appendChild(gui.domElement)
  171. gui.open()
  172. return statusTextCtl
  173. }
  174.  
  175. async function downloadAll(startID, endID) {
  176. const zipName = (startID && endID) ? `${startID}-${endID}` : 'all'
  177. status = G.initStatus(user, zipName)
  178. statusTextCtl.name('Starting...')
  179. let index = 1
  180. do {
  181. const html = await (await fetch(`https://ux.getuploader.com/${user}/index/date/desc/${index++}`)).text()
  182. status.amount = (startID && endID) ? (endID - startID + 1 - status.failed) : +html.match(/<td>(\d+) ファイル<\/td>/)[1]
  183. let files = html.match(/<a href="https:\/\/ux\.getuploader\.com\/\w+\/download\/\d+.*?">.*?<\/a>/g)?.slice(0, 15).filter(i => !i.includes('<img'))
  184. if (!files || files.length === 0) {
  185. console.warn('cannot get any files.')
  186. statusTextCtl.name('Packing...')
  187. status.zip.pack(() => statusTextCtl.name('Packed.'))
  188. break
  189. }
  190. files = files.map(f => f.replace('<a href="', '').replace('" title="', '/').slice(0, -2))
  191. for (let i = 0; i < files.length; ++i) {
  192. let file = files[i].slice(0, files[i].indexOf('"'))
  193. .replace('https://ux.getuploader.com', 'https://downloadx.getuploader.com/g').replace('download/', '')
  194. let filename = file.slice(35).match(/.+\/(.*?\.\w+)$/)[1]
  195. let fileIndex = parseInt(file.slice(35).split('/')[2])
  196. if (startID && endID) {
  197. if (fileIndex < startID || fileIndex > endID) continue
  198. }
  199. await(300)
  200. try {
  201. const blob = await G.gmRequireImage(file, status)
  202. status.zip.add(user, `${fileIndex}-${filename}`, blob)
  203. } catch (e) {
  204. status.failed++
  205. console.log(`Failed to download: ${file} ${e}`)
  206. }
  207. }
  208. } while (true)
  209. }
  210. })();