From 77323d23e65420d61a2f5e9611fffe890c6d01c6 Mon Sep 17 00:00:00 2001 From: jtrzupek Date: Sat, 6 Jun 2026 21:14:26 +0200 Subject: [PATCH] fix(playback): retry DoodStream/playmogo resolve, handle "RELOAD" token response porndish scenes resolve only to playmogo.com embeds, which are DoodStream clones (doodcdn.io + pass_md5 + Cloudflare Turnstile). The mobile resolver already supported playmogo, but DoodStream is flaky from a single shot: the embed is sometimes Turnstile-gated (no pass_md5), and the pass_md5 endpoint intermittently returns the literal string "RELOAD" (stale/consumed token) instead of a base URL. The old code built "RELOAD?token=..." -> ExoPlayer "no extractors" -> WebView -> loading forever (bug 62e78c9a). Wrap resolveDoodStream in a 3-attempt retry that re-fetches the embed (fresh token) on retryable failures (gate / RELOAD / empty / stale token), and reject a non-http pass_md5 body as retryable instead of building a garbage URL. Verified cross-IP that the pass_md5 -> base -> final flow yields 206 video/mp4 when not gated; real carrier IPs are gated far less than the test proxy. Strict improvement: worst case is the existing WebView fallback, best case native play. Co-Authored-By: Claude Opus 4.8 (1M context) --- mobile/src/lib/doodstream.ts | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) 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}` }; }