From 6c5d409fcc2ddc41768b37440842f27a0e8b8997 Mon Sep 17 00:00:00 2001 From: goon-foss Date: Fri, 2 Oct 2026 14:58:19 +0200 Subject: [PATCH] fix(porndoe): link do pliku pobierany na telefonie, nie przez WebView Linki porndoe (mp4 z ip= i HLS z tokenem w sciezce) sa podpisane pod adres, z ktorego przyszlo /service/index.json: token z IPv4 gra tylko przez IPv4, z IPv6 tylko przez IPv6, z VPS 403. Token pobieral WebView (Chromium), a plik ExoPlayer (OkHttp), wiec na sieciach dual-stack i za CGNAT wychodzily innymi adresami: 164 bledy 403 na 278 udanych w tydzien. Nowy resolver po stronie telefonu robi jedno zapytanie (film rozpoznawany po Refererze) zwyklym fetch, ta sama droga co odtwarzacz; mp4 przed HLS, ponowne rozwiazanie przy bledzie jak eporner/sxyprn. Pusto = dotychczasowy WebView. Emulator: gra bez WebView. Co-Authored-By: Claude Opus 5.5 --- mobile/src/changelog.ts | 7 +++ mobile/src/lib/porndoeResolver.ts | 60 ++++++++++++++++++++++++ mobile/src/screens/PlayerScreen.tsx | 2 + mobile/src/screens/SceneDetailScreen.tsx | 5 +- 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 mobile/src/lib/porndoeResolver.ts diff --git a/mobile/src/changelog.ts b/mobile/src/changelog.ts index 660b6ab..91a0bc5 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-02c', + date: 'October 2026', + items: [ + 'Porndoe: many plays ended with "Response code: 403". The video link is tied to the address that asked for it, and the page and the player could reach the site through different addresses. The app now asks for the link itself, the same way the player connects, and skips the web page.', + ], + }, { id: '2026-10-02b', date: 'October 2026', diff --git a/mobile/src/lib/porndoeResolver.ts b/mobile/src/lib/porndoeResolver.ts new file mode 100644 index 0000000..3fdd67a --- /dev/null +++ b/mobile/src/lib/porndoeResolver.ts @@ -0,0 +1,60 @@ +/** + * Mobile-side porndoe.com resolver. + * + * Linki do pliku (mp4 `?ip=&token=` i HLS z tokenem w sciezce) sa podpisane pod + * adres, z ktorego przyszlo zapytanie `/service/index.json`. Sprawdzone 2026-10-02: token + * pobrany przez IPv4 gra tylko przez IPv4, przez IPv6 tylko przez IPv6, z VPS 403. + * + * Wczesniej porndoe szedl przez WebView: token pobieral Chromium, a plik ExoPlayer + * (OkHttp). Na sieciach z IPv4 i IPv6 albo za CGNAT z pula adresow te dwa stosy potrafia + * wyjsc innymi adresami i plik dostawal 403 (164 bledy na 278 udanych w tydzien). + * Tu token pobiera zwykly `fetch` (tez OkHttp), wiec idzie ta sama droga co odtwarzacz. + * + * Jedno zapytanie wystarcza: serwis rozpoznaje film po Refererze, ciasteczka niepotrzebne. + */ +import type { StreamLink } from '../types'; + +const UA = + 'Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36'; + +type PorndoeSource = { + name?: string; + height?: string | number; + link?: string; + type?: string; +}; + +export async function resolvePorndoePage(pageUrl: string): Promise { + let origin: string; + try { + origin = new URL(pageUrl).origin; + } catch { + return []; + } + let data: { payload?: { player?: { sources?: { mp4?: unknown; hls?: unknown } } } } | null; + try { + const r = await fetch(`${origin}/service/index.json?device=mobile`, { + headers: { 'User-Agent': UA, Referer: pageUrl, Accept: 'application/json' }, + }); + if (!r.ok) return []; + data = await r.json(); + } catch { + return []; + } + const sources = data?.payload?.player?.sources; + // mp4 przed HLS: jeden plik z przewijaniem. 1080p ma `type: 'link'` (reklama konta + // premium zamiast adresu), dlatego bierzemy tylko `type: 'video'`. + const pick = (list: unknown, kind: 'mp4' | 'm3u8'): StreamLink[] => + (Array.isArray(list) ? (list as PorndoeSource[]) : []) + .filter((s) => s.type === 'video' && /^https?:\/\//.test(s.link || '')) + .sort((a, b) => Number(b.height || 0) - Number(a.height || 0)) + .map((s) => ({ + stream_url: s.link!, + direct_url: s.link!, + headers: { Referer: `${origin}/`, 'User-Agent': UA }, + quality: s.name || (s.height ? `${s.height}p` : null), + type: kind, + })); + const mp4 = pick(sources?.mp4, 'mp4'); + return mp4.length > 0 ? mp4 : pick(sources?.hls, 'm3u8'); +} diff --git a/mobile/src/screens/PlayerScreen.tsx b/mobile/src/screens/PlayerScreen.tsx index 848de04..ebf6dba 100644 --- a/mobile/src/screens/PlayerScreen.tsx +++ b/mobile/src/screens/PlayerScreen.tsx @@ -505,6 +505,8 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) { links = await (await import('../lib/epornerResolver')).resolveEpornerPage(resolvePageUrl); } else if (resolvePageUrl && playOrigin === 'tube:fpoxxx') { links = await (await import('../lib/fpoxxxResolver')).resolveFpoxxxPage(resolvePageUrl); + } else if (resolvePageUrl && playOrigin === 'tube:porndoecom') { + links = await (await import('../lib/porndoeResolver')).resolvePorndoePage(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 d44624a..ca08471 100644 --- a/mobile/src/screens/SceneDetailScreen.tsx +++ b/mobile/src/screens/SceneDetailScreen.tsx @@ -25,6 +25,7 @@ import { resolvePornxpPage } from '../lib/pornxpResolver'; import { resolveSxyprnPage } from '../lib/sxyprnResolver'; import { resolveEpornerPage } from '../lib/epornerResolver'; import { resolveFpoxxxPage } from '../lib/fpoxxxResolver'; +import { resolvePorndoePage } from '../lib/porndoeResolver'; import type { RootStackParamList } from '../navigation'; import { fonts, theme } from '../theme'; import { HeaderIconButton } from '../ui'; @@ -643,7 +644,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']; + const PHONE_RESOLVE_ORIGINS = ['tube:sxyprncom', 'tube:epornercom', 'tube:fpoxxx', 'tube:porndoecom']; nav.navigate('Player', { url: initialUrl, sceneId, @@ -696,6 +697,8 @@ function PlaybackButton({ ? resolveFpoxxxPage : source.origin === 'tube:pornxpph' ? resolvePornxpPage + : source.origin === 'tube:porndoecom' + ? resolvePorndoePage : // 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