- // ==UserScript==
- // @name UX Batch Downloader
- // @namespace http://tampermonkey.net/
- // @version 0.11
- // @description Batch downloader for ux.getuploader.com
- // @author Amarillys
- // @match https://ux.getuploader.com/*
- // @icon https://www.google.com/s2/favicons?domain=getuploader.com
- // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.2.2/jszip.min.js
- // @require https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.6/dat.gui.min.js
- // @grant GM_xmlhttpRequest
- // @grant unsafeWindow
- // @license MIT
- // ==/UserScript==
-
-
- (function() {
- 'use strict';
- const G = init()
- window = unsafeWindow
- // const DL_PATH = 'https://downloadx.getuploader.com/g/'
- const user = location.pathname.match(/\/(.*?)\//)[1]
- let status = G.initStatus()
- const statusTextCtl = initGUI(G)
-
- function init() {
- const G = {}
- G.Zip = class {
- 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 >= G.Zip.MAX_SIZE)
- this.pack()
- this.zip.folder(G.purifyName(folder)).file(G.purifyName(name), blob, {
- compression: 'STORE'
- })
- this.size += blob.size
- }
- pack(callback) {
- if (this.size === 0) return
- let index = this.partIndex
- this.zip
- .generateAsync({
- type: 'blob',
- compression: 'STORE'
- })
- .then(zipBlob => {
- G.saveBlob(zipBlob, `${this.title}-${index}.zip`)
- typeof callback === 'function' && callback()
- })
- this.partIndex++
- this.zip = new JSZip()
- this.size = 0
- }
- }
- G.Zip.MAX_SIZE = 850000000
-
- G.setProgress = status => {
- G.progressCtl.setValue(status.processed / status.amount * 100)
- }
-
- G.gmRequireImage = function (url, status) {
- const callback = () => {
- status.progressList[status.amount] = 1
- status.processed++
- G.setProgress(status)
- }
- return new Promise((resolve, reject) =>
- GM_xmlhttpRequest({
- method: 'GET',
- url,
- overrideMimeType: 'application/octet-stream',
- responseType: 'blob',
- asynchrouns: true,
- onload: res => {
- callback()
- resolve(res.response)
- },
- onprogress: () => {
- G.setProgress(status)
- },
- onerror: () =>
- GM_xmlhttpRequest({
- method: 'GET',
- url,
- overrideMimeType: 'application/octet-stream',
- responseType: 'arraybuffer',
- onload: res => {
- callback()
- resolve(new Blob([res.response]))
- },
- onprogress: res => {
- status.progressList[status.amount] = res.done / res.total
- G.setProgress(status)
- },
- onerror: res => reject(res)
- })
- })
- )
- }
-
- G.initStatus = function (user, addition) {
- const zip = new G.Zip(`${user}-${addition}`)
- return {
- amount: 1,
- processed: 0,
- progress: 0,
- failed: 0,
- start: Math.min(...document.documentElement.innerHTML.match(/download\/\d+/g).map(r => +r.slice(9))),
- end: +document.documentElement.innerHTML.match(/download\/(\d+)/)[1],
- progressList: [],
- zip
- }
- }
-
- G.purifyName = function (filename) {
- return filename.replaceAll(':', '').replaceAll('/', '').replaceAll('\\', '').replaceAll('>', '').replaceAll('<', '')
- .replaceAll('*:', '').replaceAll('|', '').replaceAll('?', '').replaceAll('"', '')
- }
-
- G.saveBlob = function (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)
- }
-
- G.sleep = function (ms) {
- return new Promise(resolve => {
- setTimeout(resolve, ms)
- })
- }
-
- return G
- }
-
- function initGUI(G) {
- const gui = new dat.GUI({
- autoPlace: false,
- useLocalStorage: false
- })
- const clickHandler = {
- text() {},
- download() { downloadAll() },
- downloadById() { downloadAll(+status.start, +status.end) }
- }
- G.label = gui.add(clickHandler, 'text').name('v0.11')
- gui.add(clickHandler, 'download').name('Download All')
- G.progressCtl = gui.add(status, 'progress', 0, 100, 0.01).name('Progress')
- gui.add(status, 'start', 1, 1000, 1).name('Start ID')
- gui.add(status, 'end', 1, 1000, 1).name('End ID')
- gui.add(clickHandler, 'downloadById').name('DL By Id')
- const statusTextCtl = gui.add(clickHandler, 'text').name('Initialized.')
- gui.domElement.style.position = 'fixed'
- gui.domElement.style.top = '10%'
- gui.domElement.style.opacity = 0.75
- document.body.appendChild(gui.domElement)
-
- gui.open()
- return statusTextCtl
- }
-
- async function downloadAll(startID, endID) {
- const zipName = (startID && endID) ? `${startID}-${endID}` : 'all'
- status = G.initStatus(user, zipName)
- statusTextCtl.name('Starting...')
- let index = 1
- do {
- const html = await (await fetch(`https://ux.getuploader.com/${user}/index/date/desc/${index++}`)).text()
- status.amount = (startID && endID) ? (endID - startID + 1 - status.failed) : +html.match(/<td>(\d+) ファイル<\/td>/)[1]
- let files = html.match(/<a href="https:\/\/ux\.getuploader\.com\/\w+\/download\/\d+.*?">.*?<\/a>/g)?.slice(0, 15).filter(i => !i.includes('<img'))
- if (!files || files.length === 0) {
- console.warn('cannot get any files.')
- statusTextCtl.name('Packing...')
- status.zip.pack(() => statusTextCtl.name('Packed.'))
- break
- }
- files = files.map(f => f.replace('<a href="', '').replace('" title="', '/').slice(0, -2))
- for (let i = 0; i < files.length; ++i) {
- let file = files[i].slice(0, files[i].indexOf('"'))
- .replace('https://ux.getuploader.com', 'https://downloadx.getuploader.com/g').replace('download/', '')
- let filename = file.slice(35).match(/.+\/(.*?\.\w+)$/)[1]
- let fileIndex = parseInt(file.slice(35).split('/')[2])
- if (startID && endID) {
- if (fileIndex < startID || fileIndex > endID) continue
- }
- await(300)
- try {
- const blob = await G.gmRequireImage(file, status)
- status.zip.add(user, `${fileIndex}-${filename}`, blob)
- } catch (e) {
- status.failed++
- console.log(`Failed to download: ${file} ${e}`)
- }
- }
- } while (true)
- }
- })();