goon/mobile/src/lib/porndoeResolver.ts
goon-foss 6c5d409fcc 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 <noreply@anthropic.com>
2026-10-02 14:58:19 +02:00

60 lines
2.3 KiB
TypeScript

/**
* Mobile-side porndoe.com resolver.
*
* Linki do pliku (mp4 `?ip=<adres>&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<StreamLink[]> {
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');
}