diff --git a/lib/voe-upload.js b/lib/voe-upload.js index c925a1c..ec2479a 100644 --- a/lib/voe-upload.js +++ b/lib/voe-upload.js @@ -141,7 +141,7 @@ class VoeUploader { } /** - * Get the upload page and extract CSRF token + any upload params + * Get the upload page and extract CSRF token */ async _getUploadParams() { const res = await this._fetch(`${BASE_URL}/file-upload`); @@ -155,6 +155,28 @@ class VoeUploader { return { csrfToken }; } + /** + * Get upload server URL from /engine/delivery-node + * Returns { server: "https://cdn-xxx.edgeon-bandwidth.com/node/u/01", session_id: "..." } + */ + async _getDeliveryNode(csrfToken) { + const res = await this._fetch(`${BASE_URL}/engine/delivery-node`, { + headers: { + 'X-CSRF-TOKEN': csrfToken, + 'X-Requested-With': 'XMLHttpRequest', + 'Accept': 'application/json' + } + }); + const body = await res.text(); + const data = JSON.parse(body); + + if (!data || !data.success || !data.server) { + throw new Error('VOE: Kein Upload-Server erhalten von delivery-node'); + } + + return { uploadServer: data.server, sessionId: data.session_id || '' }; + } + /** * List current files via VOE API (for result polling fallback) */ @@ -182,18 +204,29 @@ class VoeUploader { /** * Upload a file to VOE.sx via login session + * Flow: GET delivery-node → POST file to CDN server */ async upload(filePath, onProgress, signal, throttle) { const fileName = path.basename(filePath); const fileSize = fs.statSync(filePath).size; const baselineCodes = await this._captureFileCodes(); + // Step 1: Get CSRF token from upload page const { csrfToken } = await this._getUploadParams(); + // Step 2: Get CDN upload server from delivery-node + const { uploadServer, sessionId } = await this._getDeliveryNode(csrfToken); + const boundary = '----FormBoundary' + crypto.randomBytes(16).toString('hex'); // Build multipart body let preamble = ''; + // Include session_id if provided + if (sessionId) { + preamble += `--${boundary}\r\n`; + preamble += `Content-Disposition: form-data; name="session_id"\r\n\r\n`; + preamble += `${sessionId}\r\n`; + } preamble += `--${boundary}\r\n`; preamble += `Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n`; preamble += `Content-Type: application/octet-stream\r\n\r\n`; @@ -219,10 +252,8 @@ class VoeUploader { yield epilogueBuf; } - // POST to /engine/delivery-node - const uploadUrl = `${BASE_URL}/engine/delivery-node`; - - const { body, statusCode, headers } = await request(uploadUrl, { + // Step 3: POST file to CDN upload server + const { body, statusCode, headers } = await request(uploadServer, { method: 'POST', body: generate(), signal, diff --git a/main.js b/main.js index 090cce2..273259f 100644 --- a/main.js +++ b/main.js @@ -202,8 +202,51 @@ async function checkVidmolyHealth(hosterConfig) { }; } +async function checkVoeHealth(hosterConfig) { + const username = hosterConfig && hosterConfig.username + ? String(hosterConfig.username).trim() + : ''; + const password = hosterConfig && hosterConfig.password + ? String(hosterConfig.password).trim() + : ''; + + if (!username || !password) { + // Fall back to API key check if no login + const apiKey = hosterConfig && hosterConfig.apiKey + ? String(hosterConfig.apiKey).trim() + : ''; + if (!apiKey) { + return { status: 'error', message: 'Login oder API Key fehlt' }; + } + // Quick API check + const res = await fetch(`https://voe.sx/api/upload/server?key=${encodeURIComponent(apiKey)}`, { method: 'GET' }); + const data = await res.json().catch(() => null); + if (data && data.result && typeof data.result === 'string' && /^https?:\/\//i.test(data.result.trim())) { + return { status: 'ok', message: 'API Key gueltig, Upload-Server verfuegbar' }; + } + const msg = data && (data.msg || data.message) ? String(data.msg || data.message).trim() : ''; + if (/no servers/i.test(msg)) { + return { status: 'warn', message: 'API Key gueltig, aktuell kein Server verfuegbar' }; + } + return { status: 'error', message: msg || 'API Key ungueltig oder Server nicht erreichbar' }; + } + + const uploader = new VoeUploader(); + await uploader.login(username, password); + const { csrfToken } = await uploader._getUploadParams(); + + if (!csrfToken) { + return { status: 'error', message: 'Login ok, aber Upload-Seite liefert kein CSRF-Token' }; + } + + return { + status: 'ok', + message: 'Login ok, Upload-Seite bereit' + }; +} + async function runHosterHealthCheck(config, requestedHosters) { - const allowed = ['doodstream.com', 'vidmoly.me']; + const allowed = ['doodstream.com', 'vidmoly.me', 'voe.sx']; const source = Array.isArray(requestedHosters) && requestedHosters.length > 0 ? requestedHosters : allowed; @@ -238,6 +281,15 @@ async function runHosterHealthCheck(config, requestedHosters) { return { hoster, ...result }; } + if (hoster === 'voe.sx') { + const result = await withTimeout( + checkVoeHealth(hosterConfig), + HEALTH_CHECK_TIMEOUT, + 'VOE-Check' + ); + return { hoster, ...result }; + } + return { hoster, status: 'skipped', message: 'Kein Health-Check fuer diesen Hoster' }; } catch (err) { return { diff --git a/renderer/app.js b/renderer/app.js index 5cdb250..3c012b6 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -652,7 +652,7 @@ async function startUpload() { // Auto health check if (autoHealthCheckEnabled) { - const checkHosters = hosters.filter(name => name === 'doodstream.com' || name === 'vidmoly.me'); + const checkHosters = hosters.filter(name => name === 'doodstream.com' || name === 'vidmoly.me' || name === 'voe.sx'); if (checkHosters.length > 0) { healthCheckRunning = true; try { @@ -850,9 +850,9 @@ async function executeHealthCheck(hosters, mode) { async function runHealthCheck() { if (healthCheckRunning || uploading) return; - const hosters = getSelectedHosters().filter(n => n === 'doodstream.com' || n === 'vidmoly.me'); + const hosters = getSelectedHosters().filter(n => n === 'doodstream.com' || n === 'vidmoly.me' || n === 'voe.sx'); if (hosters.length === 0) { - const allHosters = ['doodstream.com', 'vidmoly.me'].filter(n => hosterHasCredentials(n, config.hosters[n] || {})); + const allHosters = ['doodstream.com', 'vidmoly.me', 'voe.sx'].filter(n => hosterHasCredentials(n, config.hosters[n] || {})); if (allHosters.length === 0) { alert('Keine Hoster mit Zugangsdaten fuer Health-Check.'); return; } hosters.push(...allHosters); }