release: restore v2.1.19 baseline for v2.1.24
CI / verify (push) Has been cancelled

Restore the v2.1.19 application baseline and retain only the focused import preflight summary with duplicate, unavailable, destination, job, and size-limit visibility.
This commit is contained in:
Sucukdeluxe
2026-08-17 04:25:22 +02:00
parent 9a213a7395
commit d7c9f287e4
87 changed files with 2185 additions and 16125 deletions
+82 -225
View File
@@ -2,8 +2,6 @@ const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { request } = require('undici');
const { createTransportError, sanitizeRemoteText } = require('./hoster-transport-error');
const { normalizeRecoveryTitle } = require('./hosters');
const BASE_URL = 'https://voe.sx';
const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
@@ -16,9 +14,8 @@ const RESULT_POLL_DELAY_MS = 2000;
* Fallback when API-based upload fails or is unavailable.
*/
class VoeUploader {
constructor(recoveryClaim = null) {
constructor() {
this.cookies = new Map();
this.recoveryClaim = recoveryClaim;
}
_cookieHeader() {
@@ -163,58 +160,21 @@ class VoeUploader {
* Returns { server: "https://cdn-xxx.edgeon-bandwidth.com/node/u/01", session_id: "..." }
*/
async _getDeliveryNode(csrfToken) {
const endpoint = `${BASE_URL}/engine/delivery-node`;
let res;
try {
res = await this._fetch(endpoint, {
headers: {
'X-CSRF-TOKEN': csrfToken,
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json'
}
});
} catch {
throw createTransportError('VOE: Upload-Server konnte nicht geladen werden', {
phase: 'upload-server',
endpoint,
retryable: true,
transientNetwork: true
});
}
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 contentType = res.headers && typeof res.headers.get === 'function'
? res.headers.get('content-type')
: null;
if (res.status < 200 || res.status >= 300) {
throw createTransportError('VOE: Upload-Server konnte nicht geladen werden', {
phase: 'upload-server',
endpoint,
httpStatus: res.status,
contentType,
body,
retryable: res.status === 429 || res.status >= 500,
transientNetwork: res.status >= 500
});
}
let data;
try { data = JSON.parse(body); } catch {
throw createTransportError('VOE: Upload-Server Antwort war kein JSON', {
phase: 'upload-server',
endpoint,
httpStatus: res.status,
contentType,
body
});
throw new Error(`VOE: Upload-Server Antwort war kein JSON: ${body.slice(0, 200)}`);
}
if (!data || !data.success || !data.server) {
throw createTransportError('VOE: Kein Upload-Server erhalten von delivery-node', {
phase: 'upload-server',
endpoint,
httpStatus: res.status,
contentType,
body
});
throw new Error('VOE: Kein Upload-Server erhalten von delivery-node');
}
return { uploadServer: data.server, sessionId: data.session_id || '' };
@@ -223,54 +183,26 @@ class VoeUploader {
/**
* List current files via VOE API (for result polling fallback)
*/
async _fetchFileList(phase = 'recovery-poll') {
const endpoint = `${BASE_URL}/api2/my-files?sort=date&order=dsc&page=1&per_page=50`;
let res;
async _fetchFileList() {
try {
res = await this._fetch(endpoint);
const res = await this._fetch(`${BASE_URL}/api2/my-files?sort=date&order=dsc&page=1&per_page=50`);
const body = await res.text();
const data = JSON.parse(body);
if (data && Array.isArray(data.data)) return data.data;
if (data && Array.isArray(data.files)) return data.files;
return [];
} catch {
throw createTransportError('VOE: Dateiliste konnte nicht geladen werden', {
phase,
endpoint,
retryable: true,
transientNetwork: true
});
return [];
}
const body = await res.text();
const contentType = res.headers && typeof res.headers.get === 'function'
? res.headers.get('content-type')
: null;
if (res.status < 200 || res.status >= 300) {
throw createTransportError('VOE: Dateiliste konnte nicht geladen werden', {
phase,
endpoint,
httpStatus: res.status,
contentType,
body,
retryable: res.status === 429 || res.status >= 500,
transientNetwork: res.status >= 500
});
}
let data;
try {
data = JSON.parse(body);
} catch {
throw createTransportError('VOE: Dateiliste war kein JSON', {
phase,
endpoint,
httpStatus: res.status,
contentType,
body
});
}
if (data && Array.isArray(data.data)) return data.data;
if (data && Array.isArray(data.files)) return data.files;
return [];
}
async _captureFileCodes() {
const files = await this._fetchFileList('recovery-baseline');
return new Set(files.map(f => String(f.file_code || f.slug || '').trim()).filter(Boolean));
try {
const files = await this._fetchFileList();
return new Set(files.map(f => String(f.file_code || f.slug || '').trim()).filter(Boolean));
} catch {
return new Set();
}
}
/**
@@ -280,14 +212,7 @@ class VoeUploader {
async upload(filePath, onProgress, signal, throttle) {
const fileName = path.basename(filePath);
const fileSize = fs.statSync(filePath).size;
let baselineCodes = null;
let baselineError = null;
try {
baselineCodes = await this._captureFileCodes();
} catch (err) {
if (signal && signal.aborted) throw err;
baselineError = err;
}
const baselineCodes = await this._captureFileCodes();
// Step 1: Get CSRF token from upload page
const { csrfToken } = await this._getUploadParams();
@@ -333,57 +258,27 @@ class VoeUploader {
}
// Step 3: POST file to CDN upload server
let uploadResponse;
try {
uploadResponse = await request(uploadServer, {
method: 'POST',
body: generate(),
signal,
headers: {
'User-Agent': USER_AGENT,
'Cookie': this._cookieHeader(),
'Content-Type': `multipart/form-data; boundary=${boundary}`,
'Content-Length': String(totalSize),
'X-CSRF-TOKEN': csrfToken,
'X-Requested-With': 'XMLHttpRequest',
'Referer': `${BASE_URL}/file-upload`,
'Origin': BASE_URL
},
headersTimeout: UPLOAD_TIMEOUT,
bodyTimeout: UPLOAD_TIMEOUT
});
} catch (err) {
const error = signal && signal.aborted ? err : createTransportError('VOE Upload konnte nicht übertragen werden', {
phase: 'upload-request',
endpoint: uploadServer,
retryable: true,
transientNetwork: true
});
throw this._markRemoteCommitUncertain(error);
}
const { body, headers, statusCode } = uploadResponse;
const { body, headers } = await request(uploadServer, {
method: 'POST',
body: generate(),
signal,
headers: {
'User-Agent': USER_AGENT,
'Cookie': this._cookieHeader(),
'Content-Type': `multipart/form-data; boundary=${boundary}`,
'Content-Length': String(totalSize),
'X-CSRF-TOKEN': csrfToken,
'X-Requested-With': 'XMLHttpRequest',
'Referer': `${BASE_URL}/file-upload`,
'Origin': BASE_URL
},
headersTimeout: UPLOAD_TIMEOUT,
bodyTimeout: UPLOAD_TIMEOUT
});
this._parseCookiesFromHeaders(headers || {});
let rawBody;
try {
rawBody = await body.text();
} catch (err) {
throw this._markRemoteCommitUncertain(err);
}
if (statusCode < 200 || statusCode >= 300) {
const error = createTransportError('VOE Upload fehlgeschlagen', {
phase: 'upload-response',
endpoint: uploadServer,
httpStatus: statusCode,
contentType: headers && headers['content-type'],
body: rawBody,
retryable: statusCode === 429 || statusCode >= 500,
transientNetwork: statusCode >= 500
});
throw statusCode >= 500 ? this._markRemoteCommitUncertain(error) : error;
}
const rawBody = await body.text();
// Try JSON response
try {
@@ -400,48 +295,22 @@ class VoeUploader {
// Check for error
if (json.error || json.message) {
throw createTransportError(`VOE Upload-Fehler: ${sanitizeRemoteText(json.error || json.message)}`, {
phase: 'upload-result',
endpoint: uploadServer,
contentType: 'application/json',
body: rawBody
});
throw new Error(`VOE Upload-Fehler: ${json.error || json.message}`);
}
} catch (parseErr) {
if (parseErr && parseErr.diagnostic) throw parseErr;
if (parseErr.message.startsWith('VOE Upload-Fehler')) throw parseErr;
// Not JSON - might be a redirect or HTML response
}
// Fallback: poll the file list to find the newly uploaded file
if (baselineCodes) {
try {
const result = await this._resolveUploadedFile(fileName, baselineCodes, signal);
if (result) return result;
} catch (err) {
throw this._markRemoteCommitUncertain(err);
}
}
const result = await this._resolveUploadedFile(fileName, baselineCodes, signal);
if (result) return result;
if (baselineError) {
baselineError.hosterTransient = true;
throw this._markRemoteCommitUncertain(baselineError);
}
throw this._markRemoteCommitUncertain(createTransportError('VOE Upload: Kein file_code in der Antwort gefunden', {
phase: 'upload-result',
endpoint: uploadServer,
contentType: headers && headers['content-type'],
body: rawBody,
hosterTransient: true,
retryable: true
}));
throw new Error('VOE Upload: Kein file_code in der Antwort gefunden');
}
async _resolveUploadedFile(fileName, baselineCodes, signal) {
if (!(baselineCodes instanceof Set)) return null;
const expectedTitle = this._normalizeTitle(path.parse(fileName).name);
let lastPollError = null;
let successfulPoll = false;
for (let attempt = 0; attempt < RESULT_POLL_ATTEMPTS; attempt++) {
if (signal && signal.aborted) {
@@ -452,31 +321,29 @@ class VoeUploader {
let files = [];
try {
files = await this._fetchFileList('recovery-poll');
successfulPoll = true;
} catch (err) {
if (err && err.name === 'AbortError') throw err;
lastPollError = err;
}
files = await this._fetchFileList();
} catch { files = []; }
const withCode = files.filter(f => f && (f.file_code || f.slug));
const newFiles = withCode.filter(f => !baselineCodes.has(String(f.file_code || f.slug || '').trim()));
const matches = newFiles
.filter(file => {
const title = this._normalizeTitle(file.title || file.name || '');
return expectedTitle && title === expectedTitle;
})
.filter(file => {
const code = String(file.file_code || file.slug || '').trim();
return !this.recoveryClaim
|| typeof this.recoveryClaim.has !== 'function'
|| !this.recoveryClaim.has(code);
});
if (matches.length > 1) return null;
if (matches.length === 1) {
const code = matches[0].file_code || matches[0].slug;
return this._buildUrls(code, 'recovery-poll');
if (newFiles.length > 0) {
// Try to match by title
let best = null;
let bestScore = -1;
for (const file of newFiles) {
const score = this._scoreCandidate(file, expectedTitle);
if (score > bestScore) {
bestScore = score;
best = file;
}
}
if (best && (bestScore > 0 || newFiles.length === 1)) {
const code = best.file_code || best.slug;
return this._buildUrls(code);
}
}
if (attempt < RESULT_POLL_ATTEMPTS - 1) {
@@ -484,41 +351,31 @@ class VoeUploader {
}
}
if (!successfulPoll && lastPollError) throw lastPollError;
return null;
}
_normalizeTitle(value) {
return normalizeRecoveryTitle(value);
return String(value || '')
.toLowerCase()
.normalize('NFKD')
.replace(/[^a-z0-9]+/g, '');
}
_markRemoteCommitUncertain(error) {
if (this.recoveryClaim && typeof this.recoveryClaim.markUncertain === 'function') {
return this.recoveryClaim.markUncertain(error);
}
const uncertainError = error && typeof error === 'object'
? error
: new Error('VOE Upload-Ergebnis ist unsicher');
uncertainError.remoteCommitUncertain = true;
uncertainError.hosterTransient = true;
return uncertainError;
_scoreCandidate(file, expectedTitle) {
if (!file || !(file.file_code || file.slug)) return -1;
if (!expectedTitle) return 0;
const title = this._normalizeTitle(file.title || file.name || '');
if (!title) return -1;
if (title === expectedTitle) return 120;
if (title.startsWith(expectedTitle) || expectedTitle.startsWith(title)) return 90;
if (title.includes(expectedTitle) || expectedTitle.includes(title)) return 70;
return 0;
}
_buildUrls(fileCode, phase = 'upload-result') {
_buildUrls(fileCode) {
const code = String(fileCode || '').trim();
if (!code) return null;
if (this.recoveryClaim
&& typeof this.recoveryClaim.reserve === 'function'
&& !this.recoveryClaim.reserve(code)) {
const error = createTransportError('VOE Upload-Ergebnis ist bereits einem anderen Upload zugeordnet', {
phase,
endpoint: BASE_URL,
retryable: true,
hosterTransient: true
});
error.remoteIdentityClaimed = true;
throw this._markRemoteCommitUncertain(error);
}
return {
download_url: `${BASE_URL}/${code}`,
embed_url: `${BASE_URL}/e/${code}`,