diff --git a/mobile/src/lib/getfileResolver.ts b/mobile/src/lib/getfileResolver.ts new file mode 100644 index 0000000..9af8bb5 --- /dev/null +++ b/mobile/src/lib/getfileResolver.ts @@ -0,0 +1,62 @@ +/** + * Mobile-side get_file → final CDN resolver. + * + * Tuby typu hdporn.gg / fullmovies.xxx serwują `` = `.../get_file/...mp4`, + * które 302-redirectuje na fpvcdn.com z IP FETCHERA wbitym w URL (`ip=`). + * Backend NIE może tego zresolwować (związałby fpvcdn z IP VPS → telefon 403), więc + * oddaje get_file URL niezresolwowany. Ale ExoPlayer wywala się na tym cross-domain + * redirekcie (gubi Referer / nie domyka) → fallback na proxy → „mignięcie". + * + * Fix: resolvujemy redirect TU, w JS na telefonie (fpvcdn bindje się do IP telefonu), + * i podajemy ExoPlayerowi finalny CDN URL — bez redirectu, bez błędu, bez migotania, + * bez proxy. Ten sam wzorzec co doodstream.ts (`redirect: 'manual'` + Location header, + * potwierdzone że działa na Android RN). + */ +const UA = + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36'; + +/** get_file URL który wymaga in-session resolve (nie nasz proxy URL). */ +export function isGetFileUrl(url: string): boolean { + return /\/get_file\//.test(url) && !/\/proxy\//.test(url); +} + +/** + * Follow get_file 302 → finalny CDN URL (z IP telefonu). Zwraca finalny URL gdy + * dojdzie do nie-get_file Location, oryginalny URL gdy get_file serwuje direct (2xx), + * lub null przy błędzie (caller wtedy gra oryginał = obecne zachowanie z fallbackiem). + */ +export async function resolveGetFile(url: string, referer?: string): Promise { + let cur = url; + for (let hop = 0; hop < 4; hop++) { + let r: Response; + try { + r = await fetch(cur, { + method: 'GET', + redirect: 'manual', + headers: { + 'User-Agent': UA, + Range: 'bytes=0-1', + ...(referer ? { Referer: referer } : {}), + }, + }); + } catch { + return null; + } + if (r.status >= 300 && r.status < 400) { + const loc = r.headers.get('location'); + if (!loc) return null; + try { + cur = new URL(loc, cur).toString(); + } catch { + return null; + } + if (!isGetFileUrl(cur)) return cur; // dotarliśmy do finalnego CDN + continue; // kolejny get_file hop (rzadkie) + } + if (r.status >= 200 && r.status < 300) { + return cur; // get_file serwuje direct, bez redirectu — grywalne + } + return null; // 4xx/5xx + } + return cur; +} diff --git a/mobile/src/screens/SceneDetailScreen.tsx b/mobile/src/screens/SceneDetailScreen.tsx index 94696ae..8ee5c72 100644 --- a/mobile/src/screens/SceneDetailScreen.tsx +++ b/mobile/src/screens/SceneDetailScreen.tsx @@ -20,6 +20,7 @@ import { import { Image } from 'expo-image'; import { LinearGradient } from 'expo-linear-gradient'; import { useClient } from '../ClientContext'; +import { isGetFileUrl, resolveGetFile } from '../lib/getfileResolver'; import type { RootStackParamList } from '../navigation'; import { theme } from '../theme'; import type { PlaybackSource, SceneOut, StreamLink } from '../types'; @@ -474,8 +475,20 @@ function PlaybackButton({ } // Preferuj direct CDN URL (0 VPS bandwidth) → fallback proxy URL (jeśli direct // fails). Backend dostarcza oba w StreamLink. Headers tylko dla direct path. - const initialUrl = link.direct_url || link.stream_url!; + let initialUrl = link.direct_url || link.stream_url!; const isDirect = !!link.direct_url && initialUrl === link.direct_url; + + // hdporn.gg/fullmovies.xxx: direct_url to `.../get_file/...` które 302-redirectuje + // na fpvcdn z IP fetchera. ExoPlayer wywala się na tym cross-domain redirekcie → + // fallback proxy → „mignięcie". Resolvujemy redirect TU (na telefonie → fpvcdn z IP + // telefonu) i podajemy ExoPlayerowi finalny URL — bez błędu/migotania/proxy. Fail = + // gramy oryginał (obecne zachowanie z fallbackiem). ~100-300ms (get_file 302 szybki). + if (isDirect && isGetFileUrl(initialUrl)) { + const ref = link.headers?.Referer || (refererHost ? `https://${refererHost}/` : undefined); + const resolved = await resolveGetFile(initialUrl, ref); + if (resolved) initialUrl = resolved; + } + nav.navigate('Player', { url: initialUrl, sceneId,