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;
|
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(
|
export async function resolveDoodStream(
|
||||||
embedUrl: string,
|
embedUrl: string,
|
||||||
sourceUrl?: string,
|
sourceUrl?: string,
|
||||||
): Promise<ResolveResult> {
|
): Promise<ResolveResult> {
|
||||||
if (!isDoodStream(embedUrl)) return { error: 'not_doodstream' };
|
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,
|
// /d/ (download page) → /e/ (embed player page). Embed ma JS z pass_md5,
|
||||||
// download page ma countdown + button. Porn-app rs0.java: linia 38.
|
// download page ma countdown + button. Porn-app rs0.java: linia 38.
|
||||||
const url = embedUrl.replace('/d/', '/e/');
|
const url = embedUrl.replace('/d/', '/e/');
|
||||||
|
|
@ -186,6 +218,10 @@ async function fetchFinalUrl(
|
||||||
if (!r.ok) return { error: `pass_md5 http ${r.status}` };
|
if (!r.ok) return { error: `pass_md5 http ${r.status}` };
|
||||||
baseUrl = (await r.text()).trim();
|
baseUrl = (await r.text()).trim();
|
||||||
if (!baseUrl) return { error: 'pass_md5_empty_response' };
|
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) {
|
} catch (e: any) {
|
||||||
return { error: `pass_md5 fetch fail: ${e?.message || e}` };
|
return { error: `pass_md5 fetch fail: ${e?.message || e}` };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue