diff --git a/mobile/src/lib/doodstream.ts b/mobile/src/lib/doodstream.ts index ee51fec..e92ec3e 100644 --- a/mobile/src/lib/doodstream.ts +++ b/mobile/src/lib/doodstream.ts @@ -68,12 +68,44 @@ function randomString(n: number): string { return out; } +// Błędy które warto powtórzyć (świeży fetch embeda = nowy token / inny CF routing). +// playmogo/dood są FLAKY: ten sam URL daje raz Turnstile-gate (brak pass_md5), raz +// pass_md5='RELOAD' (token zużyty/wygasł), raz pełny player. Zweryfikowane 2026-06-06 +// cross-IP (Bright Data): 5 prób = 2× gate, 1× RELOAD, 1× zły token (200 text/html), +// 1× 206 video/mp4. Bez retry mobile trafiał na fail → WebView „loading w nieskończoność" +// (bug-report 62e78c9a porndish). Realne IP telefonu (carrier) gating'uje rzadziej niż +// Bright Data, więc 3 próby zwykle łapią sukces. +const _RETRYABLE = new Set([ + 'no_pass_md5_in_html', + 'pass_md5_empty_response', + 'pass_md5_reload', + 'no_splash_error_or_token', + 'captcha_gate (mobile IP also blocked?)', +]); + +function _isRetryable(err?: string): boolean { + return !!err && _RETRYABLE.has(err); +} + export async function resolveDoodStream( embedUrl: string, sourceUrl?: string, ): Promise { if (!isDoodStream(embedUrl)) return { error: 'not_doodstream' }; + let last: ResolveResult = { error: 'no_attempt' }; + for (let attempt = 0; attempt < 3; attempt++) { + last = await _resolveDoodStreamOnce(embedUrl, sourceUrl); + if (last.url) return last; // sukces + if (!_isRetryable(last.error)) return last; // permanentny błąd (deleted, http 4xx/5xx) — nie retry + // retryable (gate/RELOAD/stale token) → kolejna próba pobiera embed na nowo (nowy token) + } + return last; +} +async function _resolveDoodStreamOnce( + embedUrl: string, + sourceUrl?: string, +): Promise { // /d/ (download page) → /e/ (embed player page). Embed ma JS z pass_md5, // download page ma countdown + button. Porn-app rs0.java: linia 38. const url = embedUrl.replace('/d/', '/e/'); @@ -186,6 +218,10 @@ async function fetchFinalUrl( if (!r.ok) return { error: `pass_md5 http ${r.status}` }; baseUrl = (await r.text()).trim(); if (!baseUrl) return { error: 'pass_md5_empty_response' }; + // DoodStream zwraca literalnie "RELOAD" (lub inny nie-URL) gdy token pass_md5 + // jest zużyty/wygasł — trzeba przeładować embed po świeży token. Bez tej walidacji + // budowaliśmy `RELOAD?token=...` → ExoPlayer „no extractors" → WebView. + if (!/^https?:\/\//i.test(baseUrl)) return { error: 'pass_md5_reload' }; } catch (e: any) { return { error: `pass_md5 fetch fail: ${e?.message || e}` }; }