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<suffix>?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) <noreply@anthropic.com>
This commit is contained in:
parent
83918e9a8d
commit
77323d23e6
1 changed files with 36 additions and 0 deletions
|
|
@ -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<ResolveResult> {
|
||||
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<ResolveResult> {
|
||||
// /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<suffix>?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}` };
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue