From e38c55988c46c0fc14ed1f3f0260b2661e7c6176 Mon Sep 17 00:00:00 2001 From: Administrator Date: Wed, 11 Mar 2026 03:38:44 +0100 Subject: [PATCH] fix: doodstream upload, start selected, UI improvements - Fix DoodStream upload: parse + const ta = /]*name=['"]([^'"]+)['"][^>]*>([\s\S]*?)<\/textarea>/gi; + let m; + while ((m = ta.exec(html)) !== null) fields[m[1]] = m[2].trim(); + // Input hidden fields + const p1 = /]*type=['"]hidden['"][^>]*name=['"]([^'"]+)['"][^>]*value=['"]([^'"]*)['"]/gi; + while ((m = p1.exec(html)) !== null) { if (!fields[m[1]]) fields[m[1]] = m[2]; } + const p2 = /]*name=['"]([^'"]+)['"][^>]*value=['"]([^'"]*)['"]/gi; + while ((m = p2.exec(html)) !== null) { if (!fields[m[1]]) fields[m[1]] = m[2]; } + const p3 = /]*value=['"]([^'"]*)['"]\s[^>]*name=['"]([^'"]+)['"]/gi; + while ((m = p3.exec(html)) !== null) { if (!fields[m[2]]) fields[m[2]] = m[1]; } + return fields; + } - // Try JSON from follow response - let followPayload; - try { followPayload = JSON.parse(followText); } catch {} - if (followPayload) { - payload = followPayload; - } else { - // Try filecode from follow response HTML - const followCode = followText.match(/filecode['":\s]+['"]([a-zA-Z0-9]+)['"]/i); - if (followCode) { - return { - download_url: `https://doodstream.com/d/${followCode[1]}`, - embed_url: `https://doodstream.com/e/${followCode[1]}`, - file_code: followCode[1] - }; - } - throw new Error(`Doodstream Upload: Redirect-Antwort ungueltig (${followText.slice(0, 150)})`); - } - } else { - throw new Error(`Doodstream Upload: Keine gueltige Antwort (HTTP ${statusCode}, Body: ${resText.slice(0, 150)})`); - } + /** + * Parse filecode from upload server response (JSON or HTML) + */ + async _parseUploadResponse(resText) { + // 1. Try JSON + let payload; + try { payload = JSON.parse(resText); } catch {} + + if (payload) { + return this._extractFromJson(payload); } + // 2. Try filecode directly in HTML + const code = this._findFilecodeInHtml(resText); + if (code) { + _debugLog(`Found filecode in HTML: ${code}`); + return this._buildResult(code); + } + + // 3. Parse HTML form (XFileSharing two-step upload) + const hiddenFields = this._extractHiddenFields(resText); + _debugLog(`Hidden fields: ${JSON.stringify(hiddenFields)}`); + + // Check if filecode is already in hidden fields + const fnCode = hiddenFields.fn || hiddenFields.filecode || hiddenFields.file_code; + if (fnCode && fnCode.length >= 8) { + _debugLog(`Filecode from hidden field 'fn': ${fnCode}`); + // We still need to submit the form so doodstream registers the file + // But the filecode is the 'fn' value + } + + // XFileSharing standard: form with op=upload_result, fn, st + // Always submit to doodstream.com, not to CDN + if (hiddenFields.fn || hiddenFields.op === 'upload_result') { + // Ensure op=upload_result is set + if (!hiddenFields.op) hiddenFields.op = 'upload_result'; + + _debugLog(`Submitting upload_result to ${BASE_URL}/ with fields: ${JSON.stringify(hiddenFields)}`); + const formData = new URLSearchParams(hiddenFields); + const followRes = await this._fetch(BASE_URL + '/', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Referer': BASE_URL + '/' + }, + body: formData.toString() + }); + const followText = await followRes.text(); + _debugLog(`upload_result response (first 500): ${followText.slice(0, 500)}`); + + // Try to find filecode in result page + const resultCode = this._findFilecodeInHtml(followText); + if (resultCode) { + return this._buildResult(resultCode); + } + + // If we had fn from hidden fields, use that as filecode + if (fnCode && fnCode.length >= 8) { + return this._buildResult(fnCode); + } + + // Try download URL pattern in result page + const dlMatch = followText.match(/https?:\/\/[a-z0-9.]+\/d\/([a-zA-Z0-9]+)/i); + if (dlMatch) { + return this._buildResult(dlMatch[1]); + } + + throw new Error(`Doodstream Upload: upload_result Seite hat keinen filecode (${followText.slice(0, 150)})`); + } + + // 4. Fallback: follow form action as-is (for non-XFS forms) + const formAction = resText.match(/]*action=['"]([^'"]+)['"]/i); + if (formAction) { + _debugLog(`Fallback: following form action ${formAction[1]}`); + const formData = new URLSearchParams(hiddenFields); + const followRes = await this._fetch(formAction[1], { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Referer': BASE_URL + '/' + }, + body: formData.toString() + }); + const followText = await followRes.text(); + _debugLog(`Fallback response (first 500): ${followText.slice(0, 500)}`); + + const fallbackCode = this._findFilecodeInHtml(followText); + if (fallbackCode) return this._buildResult(fallbackCode); + + // Check if fn was in original hidden fields + if (fnCode && fnCode.length >= 8) return this._buildResult(fnCode); + + throw new Error(`Doodstream Upload: Redirect-Antwort ungueltig (${followText.slice(0, 150)})`); + } + + throw new Error(`Doodstream Upload: Keine gueltige Antwort (Body: ${resText.slice(0, 150)})`); + } + + /** + * Search for filecode patterns in HTML + */ + _findFilecodeInHtml(html) { + // filecode: "xxx" or filecode = "xxx" + const m1 = html.match(/filecode['":\s]+['"]([a-zA-Z0-9]{8,})['"]/i); + if (m1) return m1[1]; + // file_code: "xxx" + const m2 = html.match(/file_code['":\s]+['"]([a-zA-Z0-9]{8,})['"]/i); + if (m2) return m2[1]; + // Download URL pattern: /d/FILECODE + const m3 = html.match(/\/d\/([a-zA-Z0-9]{8,})/); + if (m3) return m3[1]; + return null; + } + + /** + * Extract result from JSON payload + */ + _extractFromJson(payload) { if (payload.status && Number(payload.status) !== 200 && payload.msg) { throw new Error(`Doodstream Upload: ${payload.msg}`); } - // Parse result let item = null; const result = payload.result; if (Array.isArray(result) && result.length > 0) { @@ -359,13 +434,20 @@ class DoodstreamUploader { } const fileCode = item.filecode || item.file_code || ''; - return { download_url: item.download_url || item.protected_dl || (fileCode ? `https://doodstream.com/d/${fileCode}` : null), embed_url: item.protected_embed || (fileCode ? `https://doodstream.com/e/${fileCode}` : null), file_code: fileCode }; } + + _buildResult(fileCode) { + return { + download_url: `https://doodstream.com/d/${fileCode}`, + embed_url: `https://doodstream.com/e/${fileCode}`, + file_code: fileCode + }; + } } module.exports = DoodstreamUploader; diff --git a/main.js b/main.js index ef42c01..2462d33 100644 --- a/main.js +++ b/main.js @@ -425,6 +425,7 @@ function createWindow() { } }); + mainWindow.webContents.setBackgroundThrottling(false); mainWindow.loadFile(path.join(__dirname, 'renderer', 'index.html')); } diff --git a/renderer/app.js b/renderer/app.js index 6aa481f..3e88b76 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -476,8 +476,10 @@ function updateQueueActionButtons() { const hasSelection = selectedJobIds.size > 0; const hasUploadSelection = queueJobs.some((job) => selectedJobIds.has(job.id) && ['done', 'error', 'aborted', 'skipped'].includes(job.status)); const hasAbortSelection = queueJobs.some((job) => selectedJobIds.has(job.id) && ['preview', 'queued', 'getting-server', 'uploading', 'retrying'].includes(job.status)); + const hasStartableSelection = queueJobs.some((job) => selectedJobIds.has(job.id) && ['preview', 'queued'].includes(job.status)); const hasMovableSelection = hasSelection && !uploading; + const startSelectedBtn = document.getElementById('startSelectedBtn'); const reuploadBtn = document.getElementById('reuploadSelectedBtn'); const abortSelectedBtn = document.getElementById('abortSelectedBtn'); const finishStopBtn = document.getElementById('finishStopBtn'); @@ -487,6 +489,7 @@ function updateQueueActionButtons() { const moveDownBtn = document.getElementById('moveDownBtn'); const moveBottomBtn = document.getElementById('moveBottomBtn'); + if (startSelectedBtn) startSelectedBtn.disabled = uploading || !hasStartableSelection || getSelectedHosters().length === 0; if (reuploadBtn) reuploadBtn.disabled = !hasUploadSelection; if (abortSelectedBtn) abortSelectedBtn.disabled = !hasAbortSelection; if (finishStopBtn) finishStopBtn.disabled = !uploading; @@ -921,6 +924,64 @@ async function startUpload() { } } +async function startSelectedUpload() { + if (healthCheckRunning || uploading) return; + + const hosters = getSelectedHosters(); + if (hosters.length === 0) { alert('Bitte mindestens einen Hoster auswählen.'); return; } + + const jobsToStart = queueJobs.filter((job) => selectedJobIds.has(job.id) && (job.status === 'preview' || job.status === 'queued')); + if (jobsToStart.length === 0) return; + + // Auto health check + if (autoHealthCheckEnabled) { + const checkHosters = hosters.filter(name => name === 'doodstream.com' || name === 'vidmoly.me' || name === 'voe.sx' || name === 'byse.sx'); + if (checkHosters.length > 0) { + healthCheckRunning = true; + try { + const rows = await executeHealthCheck(checkHosters, 'auto'); + const errors = rows.filter(r => r.status === 'error'); + if (errors.length > 0) { + alert(`Auto-Check fehlgeschlagen:\n${errors.map(r => `${r.hoster}: ${r.message}`).join('\n')}\n\nUpload wurde nicht gestartet.`); + return; + } + } catch (err) { + alert(`Auto-Check fehlgeschlagen: ${err.message}\nUpload wurde nicht gestartet.`); + return; + } finally { + healthCheckRunning = false; + } + } + } + + uploading = true; + jobsToStart.forEach(j => { + if (j.status === 'preview') j.status = 'queued'; + }); + updateQueueActionButtons(); + renderQueueTable(); + updateStatusBar(); + + const uploadPayload = { + hosters, + jobs: jobsToStart.map((job) => ({ + id: job.id, + file: job.file, + fileName: job.fileName, + hoster: job.hoster + })) + }; + const result = await window.api.startUpload(uploadPayload); + persistQueueStateSoon(); + + if (result && result.error) { + alert(result.error); + uploading = false; + updateQueueActionButtons(); + updateStatusBar(); + } +} + async function cancelUpload() { await window.api.cancelUpload(); uploading = false; @@ -1373,6 +1434,10 @@ function renderSettings() {
+
+ + +
@@ -1501,6 +1566,16 @@ async function saveSettings(options = {}) { globalMaxSpeedKbs: Math.max(0, Math.round((parseFloat(document.getElementById('globalMaxSpeedMbsInput')?.value || '0') || 0) * 1024)) }; + // Always on top setting + const aotCheckbox = document.getElementById('alwaysOnTopInput'); + if (aotCheckbox) { + const newAot = !!aotCheckbox.checked; + if (newAot !== alwaysOnTopState) { + alwaysOnTopState = newAot; + await window.api.setAlwaysOnTop(alwaysOnTopState); + } + } + for (const name of HOSTERS) { const hs = { ...(hosterSettings[name] || {}) }; document.querySelectorAll(`.hs-input[data-hoster="${name}"]`).forEach(input => { @@ -1845,7 +1920,7 @@ async function loadHistory() { const dt = formatDateTime(batch.timestamp || new Date()); for (const file of (batch.files || [])) { for (const result of (file.results || [])) { - if (result.status === 'aborted') continue; + if (result.status === 'aborted' || result.status === 'error') continue; const isError = result.status === 'error'; historyRowsData.push({ date: dt.text, dateTs: dt.ts, @@ -1965,6 +2040,7 @@ function setupListeners() { document.getElementById('addFilesBtn').addEventListener('click', pickFiles); document.getElementById('chooseHostersBtn').addEventListener('click', openHosterModal); document.getElementById('startUploadBtn').addEventListener('click', startUpload); + document.getElementById('startSelectedBtn').addEventListener('click', startSelectedUpload); document.getElementById('reuploadSelectedBtn').addEventListener('click', retrySelectedJobs); document.getElementById('abortSelectedBtn').addEventListener('click', abortSelectedJobs); document.getElementById('finishStopBtn').addEventListener('click', finishUploadsInProgress); diff --git a/renderer/index.html b/renderer/index.html index 1c0531c..730d0a7 100644 --- a/renderer/index.html +++ b/renderer/index.html @@ -40,9 +40,12 @@