const { EventEmitter } = require('events'); const path = require('path'); const fs = require('fs'); const crypto = require('crypto'); const { uploadFile } = require('./hosters'); const VidmolyUploader = require('./vidmoly-upload'); class UploadManager extends EventEmitter { constructor() { super(); this.abortController = new AbortController(); this.running = false; } async startBatch(tasks) { this.running = true; this.abortController = new AbortController(); const { signal } = this.abortController; const batchId = `batch-${Date.now()}`; const results = new Map(); // fileName -> { name, size, results: [] } // Initialize result map per file for (const task of tasks) { const fileName = path.basename(task.file); if (!results.has(task.file)) { let size = 0; try { size = fs.statSync(task.file).size; } catch {} results.set(task.file, { name: fileName, size, results: [] }); } } // Build upload promises const promises = tasks.map(async (task) => { const uploadId = crypto.randomBytes(8).toString('hex'); const fileName = path.basename(task.file); let fileSize = 0; try { fileSize = fs.statSync(task.file).size; } catch {} // Emit initial status this.emit('progress', { uploadId, fileName, hoster: task.hoster, status: 'getting-server', progress: 0, bytesUploaded: 0, bytesTotal: fileSize, error: null, result: null }); try { let result; const progressCb = (bytesUploaded, bytesTotal) => { this.emit('progress', { uploadId, fileName, hoster: task.hoster, status: 'uploading', progress: bytesTotal > 0 ? bytesUploaded / bytesTotal : 0, bytesUploaded, bytesTotal, error: null, result: null }); }; if (task.hoster === 'vidmoly.me' && task.username) { // Vidmoly: login-based upload const vidmoly = new VidmolyUploader(); await vidmoly.login(task.username, task.password); result = await vidmoly.upload(task.file, progressCb, signal); } else { result = await uploadFile(task.hoster, task.file, task.apiKey, progressCb, signal); } this.emit('progress', { uploadId, fileName, hoster: task.hoster, status: 'done', progress: 1, bytesUploaded: fileSize, bytesTotal: fileSize, error: null, result }); results.get(task.file).results.push({ hoster: task.hoster, status: 'done', ...result }); } catch (err) { const errorMsg = err.name === 'AbortError' ? 'Abgebrochen' : err.message; this.emit('progress', { uploadId, fileName, hoster: task.hoster, status: 'error', progress: 0, bytesUploaded: 0, bytesTotal: fileSize, error: errorMsg, result: null }); results.get(task.file).results.push({ hoster: task.hoster, status: 'error', error: errorMsg, download_url: null, embed_url: null, file_code: null }); } }); await Promise.allSettled(promises); this.running = false; const files = Array.from(results.values()); const total = tasks.length; const succeeded = files.reduce((n, f) => n + f.results.filter(r => r.status === 'done').length, 0); const summary = { id: batchId, timestamp: new Date().toISOString(), total, succeeded, failed: total - succeeded, files }; this.emit('batch-done', summary); } cancel() { if (this.running) { this.abortController.abort(); this.running = false; } } } module.exports = UploadManager;