From b19d041f09921c01b9579b2968401e92f6b6fcc1 Mon Sep 17 00:00:00 2001 From: goon-foss Date: Thu, 15 Oct 2026 12:51:14 +0200 Subject: [PATCH] perf(mobile): rule34video rozwiazywany od razu na telefonie Wczesniej resolve na serwerze, a potem telefon i tak pobieral cala strone jeszcze raz (token zwiazany z IP) i robil przeskok przez get_file. Teraz jedno pobranie strony na telefonie i wszystkie jakosci z flashvars, przekierowanie get_file robi ExoPlayer. Limity czasu na pobrania. Emulator: wybor jakosci -> odtwarzacz 3,1 s -> 0,1 s, -> dzwiek 7,2 s -> 2,9 s. Co-Authored-By: Claude Opus 5.5 --- mobile/src/changelog.ts | 7 +++ mobile/src/lib/getfileResolver.ts | 70 +++++++++++++++++++++++- mobile/src/screens/PlayerScreen.tsx | 2 + mobile/src/screens/SceneDetailScreen.tsx | 9 ++- 4 files changed, 83 insertions(+), 5 deletions(-) diff --git a/mobile/src/changelog.ts b/mobile/src/changelog.ts index 559c467..79a2b67 100644 --- a/mobile/src/changelog.ts +++ b/mobile/src/changelog.ts @@ -35,6 +35,13 @@ export type ChangelogEntry = { }; export const CHANGELOG: ChangelogEntry[] = [ + { + id: '2026-10-15c', + date: 'October 2026', + items: [ + 'rule34video starts playing more than twice as fast: the app reads the video straight from the page instead of loading it twice.', + ], + }, { id: '2026-10-15b', date: 'October 2026', diff --git a/mobile/src/lib/getfileResolver.ts b/mobile/src/lib/getfileResolver.ts index 233d762..dbb4f39 100644 --- a/mobile/src/lib/getfileResolver.ts +++ b/mobile/src/lib/getfileResolver.ts @@ -12,9 +12,22 @@ * bez proxy. Ten sam wzorzec co doodstream.ts (`redirect: 'manual'` + Location header, * potwierdzone że działa na Android RN). */ +import type { StreamLink } from '../types'; + 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'; +/** fetch z limitem czasu: bez niego wiszaca strona trzymala ekran "resolving" bez konca. */ +async function fetchWithTimeout(url: string, init: RequestInit, ms: number): Promise { + const ctrl = new AbortController(); + const t = setTimeout(() => ctrl.abort(), ms); + try { + return await fetch(url, { ...init, signal: ctrl.signal }); + } finally { + clearTimeout(t); + } +} + /** 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); @@ -49,7 +62,7 @@ export async function resolveGetFilePage( ): Promise { let html: string; try { - const r = await fetch(pageUrl, { headers: { 'User-Agent': UA, Accept: 'text/html' } }); + const r = await fetchWithTimeout(pageUrl, { headers: { 'User-Agent': UA, Accept: 'text/html' } }, 10000); if (!r.ok) return null; html = await r.text(); } catch { @@ -118,7 +131,7 @@ export async function resolveGetFile(url: string, referer?: string): Promise boomio-cdn robi sam ExoPlayer + * (302, potem 206 bez Referera, sprawdzone z IP domowego). + */ +export async function resolveRule34Page(pageUrl: string): Promise { + let html: string; + try { + const r = await fetchWithTimeout(pageUrl, { headers: { 'User-Agent': UA, Accept: 'text/html' } }, 10000); + if (!r.ok) return []; + html = await r.text(); + } catch { + return []; + } + const labels = new Map(); + let lm: RegExpExecArray | null; + _FLASHVAR_LABEL_RE.lastIndex = 0; + while ((lm = _FLASHVAR_LABEL_RE.exec(html)) !== null) labels.set(lm[1], lm[2].trim()); + let origin = ''; + try { + origin = new URL(pageUrl).origin; + } catch { + // zostaje pusty + } + const rank = (q: string) => ({ '1080p': 0, '720p': 1, '480p': 2, '360p': 3 }[q.toLowerCase()] ?? 99); + const out: StreamLink[] = []; + const seen = new Set(); + let fm: RegExpExecArray | null; + _FLASHVAR_URL_RE.lastIndex = 0; + while ((fm = _FLASHVAR_URL_RE.exec(html)) !== null) { + const q = labels.get(fm[1]) || ''; + if (_SKIP_Q.test(q)) continue; + let u = fm[2]; + if (u.startsWith('//')) u = 'https:' + u; + if (seen.has(u)) continue; + seen.add(u); + out.push({ + stream_url: u, + direct_url: u, + headers: { 'User-Agent': UA, ...(origin ? { Referer: origin + '/' } : {}) }, + quality: q || null, + type: 'mp4', + } as StreamLink); + } + out.sort((a, b) => rank(a.quality || '') - rank(b.quality || '')); + return out; +} diff --git a/mobile/src/screens/PlayerScreen.tsx b/mobile/src/screens/PlayerScreen.tsx index 646adcf..8d533c8 100644 --- a/mobile/src/screens/PlayerScreen.tsx +++ b/mobile/src/screens/PlayerScreen.tsx @@ -525,6 +525,8 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) { links = await (await import('../lib/fpoxxxResolver')).resolveFpoxxxPage(resolvePageUrl); } else if (resolvePageUrl && playOrigin === 'tube:porndoecom') { links = await (await import('../lib/porndoeResolver')).resolvePorndoePage(resolvePageUrl); + } else if (resolvePageUrl && playOrigin === 'tube:rule34video') { + links = await (await import('../lib/getfileResolver')).resolveRule34Page(resolvePageUrl); } else if (canBackendReresolve && sceneId && params.playbackId) { // Świeży resolve przez backend (VPS re-runuje extractor KVS → nowy nod + token). const res = await client.resolvePlayback(sceneId, params.playbackId); diff --git a/mobile/src/screens/SceneDetailScreen.tsx b/mobile/src/screens/SceneDetailScreen.tsx index eaf07fa..3ee4594 100644 --- a/mobile/src/screens/SceneDetailScreen.tsx +++ b/mobile/src/screens/SceneDetailScreen.tsx @@ -26,6 +26,7 @@ import { resolveSxyprnPage } from '../lib/sxyprnResolver'; import { resolveEpornerPage } from '../lib/epornerResolver'; import { resolveFpoxxxPage } from '../lib/fpoxxxResolver'; import { resolvePorndoePage } from '../lib/porndoeResolver'; +import { resolveRule34Page } from '../lib/getfileResolver'; import { prefersPageMode } from '../lib/pageMode'; import type { RootStackParamList } from '../navigation'; import { fonts, theme } from '../theme'; @@ -640,7 +641,9 @@ function PlaybackButton({ // hdporn.gg/fullmovies.xxx: backendowy get_file jest zbindowany do IP VPS (page-loader) // → telefon dostaje 410 → fallback proxy (mignięcie). Re-fetchujemy STRONĘ na telefonie // (phone-bound get_file) → fpvcdn z IP telefonu → ExoPlayer gra direct (bez proxy/migotania). - if (isDirect && isGetFileUrl(initialUrl)) { + // Link z resolvera na telefonie jest juz zwiazany z IP telefonu, drugie pobranie strony + // tylko opoznialo start (rule34video, 008e261d). + if (isDirect && via !== 'phone' && isGetFileUrl(initialUrl)) { const ref = link.headers?.Referer || (refererHost ? `https://${refererHost}/` : undefined); const resolved = await resolveGetFilePage(source.page_url, link.quality, ref); if (resolved) initialUrl = resolved; @@ -648,7 +651,7 @@ function PlaybackButton({ // IP-bound tuby resolwowane phone-side: przekaż page_url, by Player mógł // re-resolve'ować świeży token gdy native padnie na initial-load (zmiana IP / TTL). - const PHONE_RESOLVE_ORIGINS = ['tube:sxyprncom', 'tube:epornercom', 'tube:fpoxxx', 'tube:porndoecom']; + const PHONE_RESOLVE_ORIGINS = ['tube:sxyprncom', 'tube:epornercom', 'tube:fpoxxx', 'tube:porndoecom', 'tube:rule34video']; nav.navigate('Player', { url: initialUrl, sceneId, @@ -729,6 +732,8 @@ function PlaybackButton({ ? resolvePornxpPage : source.origin === 'tube:porndoecom' ? resolvePorndoePage + : source.origin === 'tube:rule34video' + ? resolveRule34Page : // hqfap zdjęty 2026-07-28: strona wdrożyła Cloudflare z JS-challenge na // /watch/ (403 dla każdego zwykłego fetcha, także z pełnym kompletem // nagłówków przeglądarki), więc phone-resolve tylko padał i zrzucał usera