fix: preserve explicit Doodstream web login and reuse OTP sessions
CI / verify (push) Canceled after 0s
CI / verify (push) Canceled after 0s
This commit is contained in:
@@ -30,8 +30,8 @@ function _doodstreamLogPath() {
|
||||
let _debugVerbose = false;
|
||||
function setDebugVerbose(v) { _debugVerbose = !!v; }
|
||||
|
||||
function _debugLog(msg) {
|
||||
if (!_debugVerbose) return;
|
||||
function _debugLog(msg, force = false) {
|
||||
if (!_debugVerbose && !force) return;
|
||||
try {
|
||||
const logPath = _doodstreamLogPath();
|
||||
maybeRotateLogFile(logPath, _DOODSTREAM_LOG_MAX_BYTES, _DOODSTREAM_LOG_MAX_BACKUPS);
|
||||
@@ -53,6 +53,20 @@ class DoodstreamUploader {
|
||||
.join('; ');
|
||||
}
|
||||
|
||||
cloneSession() {
|
||||
const uploader = new DoodstreamUploader();
|
||||
uploader.cookies = new Map(this.cookies);
|
||||
uploader.sessId = this.sessId;
|
||||
return uploader;
|
||||
}
|
||||
|
||||
_isAuthenticatedPage(html) {
|
||||
return /href=["']\/settings["']/i.test(html)
|
||||
&& /href=["']\/videos["']/i.test(html)
|
||||
&& /href=["'][^"']*(?:op=logout|\/logout)["']/i.test(html)
|
||||
&& !/<input\b[^>]*\bname=["'](?:login|password|loginotp)["']/i.test(html);
|
||||
}
|
||||
|
||||
_parseCookiesFromHeaders(headers) {
|
||||
let setCookies;
|
||||
if (typeof headers.getSetCookie === 'function') {
|
||||
@@ -175,8 +189,9 @@ class DoodstreamUploader {
|
||||
throw err;
|
||||
} else if (json && json.status === 'fail') {
|
||||
throw new Error(`Doodstream Login: ${json.message || 'Login fehlgeschlagen'}`);
|
||||
} else if (body.includes('Dashboard')) {
|
||||
// Got dashboard HTML directly — login worked
|
||||
} else if (res.status === 200 && this._isAuthenticatedPage(body)) {
|
||||
this.sessId = this._findSessId(body);
|
||||
return;
|
||||
} else {
|
||||
const msg = (json && json.message) || 'Login fehlgeschlagen';
|
||||
throw new Error(`Doodstream Login: ${msg}`);
|
||||
@@ -190,8 +205,9 @@ class DoodstreamUploader {
|
||||
}
|
||||
const landing = await this._fetch(target.href, { allowedOrigin: BASE_URL });
|
||||
const landingHtml = await landing.text();
|
||||
this._diagnoseSessionPage('landing', landing, landingHtml);
|
||||
const sessId = this._findSessId(landingHtml);
|
||||
if (landing.status === 200 && sessId) {
|
||||
if (landing.status === 200 && (sessId || this._isAuthenticatedPage(landingHtml))) {
|
||||
this.sessId = sessId;
|
||||
return;
|
||||
}
|
||||
@@ -203,6 +219,7 @@ class DoodstreamUploader {
|
||||
async _extractSessId() {
|
||||
const res = await this._fetch(BASE_URL + '/?op=upload');
|
||||
const html = await res.text();
|
||||
this._diagnoseSessionPage('upload', res, html);
|
||||
const sessId = this._findSessId(html);
|
||||
if (res.status === 200 && sessId) {
|
||||
this.sessId = sessId;
|
||||
@@ -210,7 +227,8 @@ class DoodstreamUploader {
|
||||
}
|
||||
this.sessId = '';
|
||||
const guest = /utype\s*:\s*['"]anon['"]/.test(html);
|
||||
throw new Error(`Doodstream: sess_id nicht gefunden nach Login (HTTP ${res.status}; guest=${guest}; sessionField=${/sess_id/.test(html)}; cookies=${this.cookies.size})`);
|
||||
_debugLog(`session-missing HTTP=${res.status} guest=${guest} cookies=${this.cookies.size}`, true);
|
||||
throw new Error('Doodstream: sess_id nicht gefunden nach Login');
|
||||
}
|
||||
|
||||
_findSessId(html) {
|
||||
@@ -225,6 +243,32 @@ class DoodstreamUploader {
|
||||
return match ? match[2].trim() : '';
|
||||
}
|
||||
|
||||
_diagnoseSessionPage(stage, response, html) {
|
||||
const routes = new Set();
|
||||
for (const match of html.matchAll(/\bhref=["']([^"']+)["']/gi)) {
|
||||
try {
|
||||
const url = new URL(match[1].replace(/&/g, '&'), BASE_URL);
|
||||
if (url.origin !== BASE_URL) continue;
|
||||
const op = url.searchParams.get('op') || '';
|
||||
if (/^[a-z_]+$/i.test(op)) routes.add(`op=${op}`);
|
||||
if (/^\/[a-z_/-]*$/i.test(url.pathname)) routes.add(url.pathname);
|
||||
} catch {}
|
||||
}
|
||||
const fields = [...html.matchAll(/<(?:input|textarea)\b[^>]*\bname=["']([a-z_]+)["']/gi)].map(match => match[1]);
|
||||
const components = [...html.matchAll(/<([a-z]+-[a-z-]+)\b/g)].map(match => match[1]);
|
||||
const scripts = [...html.matchAll(/<script\b[^>]*\bsrc=["']([^"']+)["']/gi)]
|
||||
.map(match => match[1].split('?')[0].split('/').pop())
|
||||
.filter(name => /^[a-z_.-]+\.js$/i.test(name));
|
||||
_debugLog(`session-page ${JSON.stringify({
|
||||
stage, status: response.status, bytes: html.length,
|
||||
challenge: /cf-chl-|challenge-platform|Just a moment|Checking your browser/i.test(html),
|
||||
sessionField: /sess_id/.test(html),
|
||||
cookies: [...this.cookies.keys()],
|
||||
routes: [...routes].slice(0, 60), fields: [...new Set(fields)],
|
||||
components: [...new Set(components)], scripts: [...new Set(scripts)]
|
||||
})}`, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get upload server URL from web interface
|
||||
*/
|
||||
@@ -242,6 +286,12 @@ class DoodstreamUploader {
|
||||
let json;
|
||||
try { json = JSON.parse(text); } catch { json = null; }
|
||||
|
||||
if (json && (json.status === 'fail' || json.success === false)) {
|
||||
const error = new Error(`Doodstream Upload: ${json.message || json.msg || 'No servers available for uploads'}`);
|
||||
error.hosterTransient = /no servers|unavailable|temporar/i.test(error.message);
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (json && json.result && /^https?:\/\//i.test(json.result)) {
|
||||
return json.result;
|
||||
}
|
||||
@@ -337,6 +387,7 @@ class DoodstreamUploader {
|
||||
|
||||
// Get upload server
|
||||
const uploadUrl = await this._getUploadServer();
|
||||
if (!this.sessId) await this._extractSessId();
|
||||
// Remember which CDN node handled this upload so a later parse failure can
|
||||
// report it — failures sometimes correlate with a specific node.
|
||||
this._lastUploadUrl = uploadUrl;
|
||||
@@ -702,10 +753,11 @@ class DoodstreamUploader {
|
||||
async deriveApiKey() {
|
||||
if (this.apiKey) return this.apiKey;
|
||||
let html = '';
|
||||
for (const page of ['/?op=my_account', '/settings', '/?op=profile']) {
|
||||
for (const page of ['/settings', '/?op=my_account', '/?op=profile']) {
|
||||
try {
|
||||
const res = await this._fetch(BASE_URL + page);
|
||||
const text = await res.text();
|
||||
this._diagnoseSessionPage('settings', res, text);
|
||||
if (text && /api[\s_-]?key/i.test(text)) { html = text; break; }
|
||||
if (text && !html) html = text;
|
||||
} catch { /* try next page */ }
|
||||
@@ -715,11 +767,11 @@ class DoodstreamUploader {
|
||||
for (const key of candidates.slice(0, 15)) {
|
||||
if (await this._validateApiKey(key)) {
|
||||
this.apiKey = key;
|
||||
_debugLog(`api-key derive: validated key (len ${key.length})`);
|
||||
_debugLog('api-key derive: account API verification succeeded', true);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
_debugLog(`api-key derive: ${candidates.length} candidate(s), none validated. settings html(2500)=${(html || '').slice(0, 2500)}`);
|
||||
_debugLog(`api-key derive: ${candidates.length} candidate(s), none validated`, true);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user