const { app, BrowserWindow, ipcMain, dialog, clipboard } = require('electron'); const path = require('path'); const ConfigStore = require('./lib/config-store'); const UploadManager = require('./lib/upload-manager'); const { HOSTER_CONFIGS } = require('./lib/hosters'); const VidmolyUploader = require('./lib/vidmoly-upload'); let mainWindow; const configStore = new ConfigStore(app); let uploadManager = null; const HEALTH_CHECK_TIMEOUT = 25000; function withTimeout(promise, timeoutMs, label) { return new Promise((resolve, reject) => { const timer = setTimeout(() => { reject(new Error(`${label} Timeout`)); }, timeoutMs); promise .then((result) => { clearTimeout(timer); resolve(result); }) .catch((err) => { clearTimeout(timer); reject(err); }); }); } function normalizeApiError(payload, fallback) { if (!payload || typeof payload !== 'object') return fallback; const msg = String(payload.msg || payload.message || '').trim(); if (msg) return msg; if (payload.status) return `API Status ${payload.status}`; return fallback; } async function checkDoodstreamHealth(hosterConfig) { const apiKey = hosterConfig && hosterConfig.apiKey ? String(hosterConfig.apiKey).trim() : ''; if (!apiKey) { return { status: 'error', message: 'API Key fehlt' }; } const apiBase = HOSTER_CONFIGS['doodstream.com'].apiBase; const accountRes = await fetch(`${apiBase}/api/account/info?key=${encodeURIComponent(apiKey)}`, { method: 'GET', redirect: 'follow' }); const accountPayload = await accountRes.json().catch(() => null); if (!accountPayload || typeof accountPayload !== 'object') { return { status: 'error', message: 'Account-Check lieferte kein gueltiges JSON' }; } if (Number(accountPayload.status || 0) !== 200) { return { status: 'error', message: normalizeApiError(accountPayload, 'Account-Check fehlgeschlagen') }; } const serverRes = await fetch(`${apiBase}/api/upload/server?key=${encodeURIComponent(apiKey)}`, { method: 'GET', redirect: 'follow' }); const serverPayload = await serverRes.json().catch(() => null); if (!serverPayload || typeof serverPayload !== 'object') { return { status: 'warn', message: 'Upload-Server-Check lieferte kein gueltiges JSON' }; } const serverResult = serverPayload.result; if (typeof serverResult === 'string' && /^https?:\/\//i.test(serverResult.trim())) { return { status: 'ok', message: 'API Key gueltig, Upload-Server verfuegbar' }; } const serverMsg = String(serverPayload.msg || serverPayload.message || '').trim(); if (/no servers available/i.test(serverMsg)) { return { status: 'warn', message: 'API Key gueltig, aktuell kein Server von API (Uploader nutzt Fallback)' }; } return { status: 'warn', message: serverMsg || 'API Key gueltig, Upload-Server aktuell nicht geliefert' }; } async function checkVidmolyHealth(hosterConfig) { const username = hosterConfig && hosterConfig.username ? String(hosterConfig.username).trim() : ''; const password = hosterConfig && hosterConfig.password ? String(hosterConfig.password).trim() : ''; if (!username || !password) { return { status: 'error', message: 'Username oder Passwort fehlt' }; } const uploader = new VidmolyUploader(); await uploader.login(username, password); const { uploadUrl, fileFieldName } = await uploader.getUploadParams(); if (!uploadUrl || !/^https?:\/\//i.test(uploadUrl)) { return { status: 'error', message: 'Upload-URL wurde nicht erkannt' }; } return { status: 'ok', message: `Login ok, Upload-Form bereit (Dateifeld: ${fileFieldName || 'file'})` }; } async function runHosterHealthCheck(config, requestedHosters) { const allowed = ['doodstream.com', 'vidmoly.me']; const source = Array.isArray(requestedHosters) && requestedHosters.length > 0 ? requestedHosters : allowed; const hosters = source .map((name) => String(name || '').trim()) .filter((name, index, arr) => name && arr.indexOf(name) === index); const checks = hosters.map(async (hoster) => { if (!allowed.includes(hoster)) { return { hoster, status: 'skipped', message: 'Kein Health-Check fuer diesen Hoster' }; } const hosterConfig = config && config.hosters ? config.hosters[hoster] : null; try { if (hoster === 'doodstream.com') { const result = await withTimeout( checkDoodstreamHealth(hosterConfig), HEALTH_CHECK_TIMEOUT, 'Doodstream-Check' ); return { hoster, ...result }; } if (hoster === 'vidmoly.me') { const result = await withTimeout( checkVidmolyHealth(hosterConfig), HEALTH_CHECK_TIMEOUT, 'Vidmoly-Check' ); return { hoster, ...result }; } return { hoster, status: 'skipped', message: 'Kein Health-Check fuer diesen Hoster' }; } catch (err) { return { hoster, status: 'error', message: err && err.message ? err.message : 'Health-Check fehlgeschlagen' }; } }); const results = await Promise.all(checks); return { checkedAt: new Date().toISOString(), results }; } function createWindow() { mainWindow = new BrowserWindow({ width: 1100, height: 750, minWidth: 800, minHeight: 550, backgroundColor: '#0f0f1a', autoHideMenuBar: true, webPreferences: { contextIsolation: true, nodeIntegration: false, preload: path.join(__dirname, 'preload.js') } }); mainWindow.loadFile(path.join(__dirname, 'renderer', 'index.html')); } app.whenReady().then(createWindow); app.on('window-all-closed', () => { app.quit(); }); // --- IPC Handlers --- ipcMain.handle('get-config', () => { return configStore.load(); }); ipcMain.handle('save-config', (_event, config) => { configStore.save(config); return true; }); ipcMain.handle('get-history', () => { return configStore.loadHistory(); }); ipcMain.handle('run-health-check', async (_event, payload) => { const config = configStore.load(); const hosters = payload && Array.isArray(payload.hosters) ? payload.hosters : []; return runHosterHealthCheck(config, hosters); }); ipcMain.handle('select-files', async () => { const result = await dialog.showOpenDialog(mainWindow, { properties: ['openFile', 'multiSelections'], filters: [ { name: 'Alle Dateien', extensions: ['*'] }, { name: 'Videos', extensions: ['mp4', 'mkv', 'avi', 'mov', 'wmv', 'flv', 'webm'] } ] }); return result.canceled ? null : result.filePaths; }); ipcMain.handle('start-upload', (_event, payload) => { const config = configStore.load(); const { files, hosters } = payload; // Build tasks with credentials const tasks = []; for (const file of files) { for (const hoster of hosters) { const hosterConfig = config.hosters[hoster]; if (!hosterConfig) continue; if (hoster === 'vidmoly.me') { // Vidmoly uses username/password login if (!hosterConfig.username || !hosterConfig.password) continue; tasks.push({ file, hoster, username: hosterConfig.username, password: hosterConfig.password }); } else { // Other hosters use API key if (!hosterConfig.apiKey) continue; tasks.push({ file, hoster, apiKey: hosterConfig.apiKey }); } } } if (tasks.length === 0) return { error: 'Keine gueltigen Zugangsdaten fuer die gewaehlten Hoster.' }; uploadManager = new UploadManager(); uploadManager.on('progress', (data) => { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send('upload-progress', data); } }); uploadManager.on('batch-done', (summary) => { configStore.appendHistory(summary); if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send('upload-batch-done', summary); } }); uploadManager.startBatch(tasks); return { started: true, taskCount: tasks.length }; }); ipcMain.handle('cancel-upload', () => { if (uploadManager) { uploadManager.cancel(); uploadManager = null; } return true; }); ipcMain.handle('clear-history', () => { configStore.clearHistory(); return true; }); ipcMain.handle('copy-to-clipboard', (_event, text) => { clipboard.writeText(text); return true; });