Greasy Fork is available in English.
Batch downloader for ux.getuploader.com
// ==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) } })();