fix: update Doodstream web authentication flow
CI / verify (push) Canceled after 0s

This commit is contained in:
Sucukdeluxe
2026-09-12 14:19:40 +02:00
parent 38df2b24d5
commit b40411a7b0
3 changed files with 75 additions and 52 deletions
+36 -32
View File
@@ -127,9 +127,8 @@ class DoodstreamUploader {
await homeRes.text();
}
// POST login via AJAX (op in body, XHR header required for JSON response)
// GET login via AJAX (XHR header required for JSON response)
const loginData = new URLSearchParams({
op: 'login_ajax',
login: username,
password: password,
loginotp: otp || ''
@@ -137,7 +136,6 @@ class DoodstreamUploader {
// Use raw fetch with redirect: 'manual' to detect success redirects
const headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Referer': BASE_URL + '/',
'X-Requested-With': 'XMLHttpRequest',
'User-Agent': USER_AGENT
@@ -146,9 +144,8 @@ class DoodstreamUploader {
headers['Cookie'] = this._cookieHeader();
}
const res = await fetch(BASE_URL + '/', {
method: 'POST',
body: loginData.toString(),
const res = await fetch(`${BASE_URL}/?op=login_ajax&${loginData.toString()}`, {
method: 'GET',
headers,
redirect: 'manual'
});
@@ -164,11 +161,11 @@ class DoodstreamUploader {
let json;
try { json = JSON.parse(body); } catch { json = null; }
if (json && json.status === 'success') {
if (json && ['success', 'redirect'].includes(json.status)) {
// Explicit success response
} else if (json && json.message && /otp/i.test(json.message)) {
} else if (json && (json.status === 'otp_sent' || (json.message && /otp|verification code/i.test(json.message)))) {
// OTP required — signal caller to collect OTP from user
const err = new Error(`Doodstream Login: ${json.message}`);
const err = new Error(`Doodstream Login: ${json.message || 'OTP erforderlich'}`);
err.otpRequired = true;
throw err;
} else if (json && json.status === 'fail') {
@@ -188,46 +185,53 @@ class DoodstreamUploader {
async _extractSessId() {
const res = await this._fetch(BASE_URL + '/?op=upload');
const html = await res.text();
const sessId = this._findSessId(html);
// Hidden input: <input type="hidden" name="sess_id" value="xxx">
const hiddenMatch = html.match(/name=["']sess_id["'][^>]*value=["']([a-zA-Z0-9]+)["']/);
if (hiddenMatch) {
this.sessId = hiddenMatch[1];
return;
}
// Vue component prop or JS: sess_id: "xxx" or sess_id="xxx"
const sessMatch = html.match(/sess_id['":\s]+['"]([a-zA-Z0-9]+)['"]/);
if (sessMatch) {
this.sessId = sessMatch[1];
return;
}
// Assignment: sess_id = 'xxx'
const altMatch = html.match(/sess_id\s*=\s*['"]([a-zA-Z0-9]+)['"]/);
if (altMatch) {
this.sessId = altMatch[1];
if (sessId) {
this.sessId = sessId;
return;
}
throw new Error('Doodstream: sess_id nicht gefunden nach Login');
}
_findSessId(html) {
if (!html) return '';
const fields = this._extractHiddenFields(html);
if (fields.sess_id) return String(fields.sess_id).trim();
const decoded = String(html)
.replace(/&quot;|&#34;|&#x22;/gi, '"')
.replace(/&#39;|&#x27;|&apos;/gi, "'")
.replace(/&amp;/gi, '&');
const match = decoded.match(/(?:["'])?\bsess_id\b(?:["'])?\s*[:=]\s*(["'])([^"']+)\1/i);
return match ? match[2].trim() : '';
}
/**
* Get upload server URL from web interface
*/
async _getUploadServer() {
// Use the standard upload server endpoint
const res = await this._fetch(BASE_URL + '/?op=upload_server');
const res = await this._fetch(BASE_URL + '/?op=upload_get_srv', {
headers: {
'Referer': BASE_URL + '/?op=upload',
'X-Requested-With': 'XMLHttpRequest'
}
});
const text = await res.text();
const ctype = (res.headers && res.headers.get) ? (res.headers.get('content-type') || '') : '';
_debugLog(`upload_server: status=${res.status} ctype=${ctype} body(800)=${(text || '').slice(0, 800)}`);
_debugLog(`upload_get_srv: status=${res.status} ctype=${ctype} body(800)=${(text || '').slice(0, 800)}`);
let json;
try { json = JSON.parse(text); } catch { json = null; }
if (json && json.result && /^https?:\/\//i.test(json.result)) {
return json.result;
}
if (json && json.server && /^https?:\/\//i.test(json.server.srv_url || '') && json.server.disk_id !== undefined) {
const server = String(json.server.srv_url).replace(/\/$/, '');
const diskId = encodeURIComponent(String(json.server.disk_id));
return `${server}/upload/${diskId}?t=${Date.now()}`;
}
// Fallback: try fetching from upload page HTML
const pageRes = await this._fetch(BASE_URL + '/?op=upload');
@@ -246,9 +250,9 @@ class DoodstreamUploader {
const actionMatch = html.match(/action=["'](https?:\/\/[^"']+\/upload\/[^"']*)["']/i);
if (actionMatch) {
const url = actionMatch[1].replace(/&amp;/g, '&'); // un-escape HTML entities in query
const freshSess = html.match(/name=["']sess_id["'][^>]*value=["']([a-zA-Z0-9]+)["']/);
const freshSess = this._findSessId(html);
if (freshSess) {
this.sessId = freshSess[1];
this.sessId = freshSess;
} else {
_debugLog('upload_server: form action found but no sess_id on page; keeping existing sessId');
}
@@ -272,7 +276,7 @@ class DoodstreamUploader {
_debugLog(`upload_server: NO SERVER. upload-page html(2000)=${(html || '').slice(0, 2000)}`);
throw new Error(
`Doodstream: konnte Upload-Server nicht ermitteln (Endpoint geändert?). ` +
`op=upload_server status=${res.status} ctype=${ctype} body=${(text || '').slice(0, 300)} ` +
`op=upload_get_srv status=${res.status} ctype=${ctype} body=${(text || '').slice(0, 300)} ` +
`| upload-page URL-Treffer: ${urlHints || 'keine'}`
);
}