Some checks are pending
Backend tests / test (push) Waiting to run
hqfap.com's Cloudflare blocks the VPS (Hetzner) IP — the server times out fetching the page (0 bytes), so backend resolve returns None (503) or a stale/IP-bound URL that fails on the phone (35 player_errors / 3 days, the top error source). But the page loads fine from residential IPs (200, 0.7s) and its okcdn/vstor/cdnde mp4 is portable cross-IP (srcIp not enforced, verified 206 from residential). Fix: phone-side resolve, same pattern as sxyprn/eporner/fpoxxx. New hqfapResolver.ts (fetch page on device -> JSON-LD contentUrl -> direct mp4, mirror of hqfap.py) wired into the proactive phone-resolve chain in SceneDetail and the on-error re-resolve in PlayerScreen; hqfapcom added to PHONE_RESOLVE_ORIGINS. Backend hqfap.extract stays as a last-resort fallback. Note: sxyprn/eporner were checked and are NOT broken (95-96% play success; their "gone" events are real deletions), so no change there. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
52 lines
2.1 KiB
TypeScript
52 lines
2.1 KiB
TypeScript
/**
|
|
* Mobile-side hqfap.com resolver.
|
|
*
|
|
* hqfap.com Cloudflare blokuje IP VPS (Hetzner: timeout, 0 bajtów) ale strona odpowiada
|
|
* 200 z residential IP (audit 2026-07-14: local 200/0.7s, VPS + Bright Data proxy oba
|
|
* timeout). Backend resolve więc pada (None → 503) albo oddaje URL zbindowany do kontekstu
|
|
* VPS → player_error. Strumień (okcdn.ru / vstor.top / cdnde, direct mp4 z JSON-LD
|
|
* contentUrl) jest PORTABLE cross-IP (srcIp NIEegzekwowany, zweryfikowane 206 z residential).
|
|
*
|
|
* Fix: telefon sam pobiera stronę (residential IP) → JSON-LD contentUrl → gra direct, zero
|
|
* VPS. Mirror app/extractors/tubes/hqfap.py. Zwraca [] gdy brak (caller spada na backend).
|
|
*/
|
|
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';
|
|
|
|
const _CONTENT_URL_RE = /"contentUrl"\s*:\s*"([^"]+)"/;
|
|
const _QUALITY_RE = /_(\d{3,4})p\.mp4/i;
|
|
|
|
/** True dla page_url hqfap (origin tube:hqfapcom). */
|
|
export function isHqfapUrl(url: string | null | undefined): boolean {
|
|
return !!url && /(?:^|\/\/|\.)hqfap\.com\//i.test(url);
|
|
}
|
|
|
|
export async function resolveHqfapPage(pageUrl: string): Promise<StreamLink[]> {
|
|
let html: string;
|
|
try {
|
|
const r = await fetch(pageUrl, { headers: { 'User-Agent': UA, Accept: 'text/html' } });
|
|
if (!r.ok) return [];
|
|
html = await r.text();
|
|
} catch {
|
|
return [];
|
|
}
|
|
const m = _CONTENT_URL_RE.exec(html);
|
|
let contentUrl = m ? m[1].trim() : '';
|
|
contentUrl = contentUrl.replace(/\\\//g, '/'); // JSON-LD bywa z escaped slash
|
|
if (!contentUrl.startsWith('http')) return [];
|
|
// Stub guard: `/upload/videos/video_down.mp4` = stały "server down" placeholder
|
|
// (powód usunięcia strony 2026-06-25). Lepiej żadne źródło niż stub.
|
|
if (contentUrl.includes('/upload/videos/video_down.mp4')) return [];
|
|
const qm = _QUALITY_RE.exec(contentUrl);
|
|
return [
|
|
{
|
|
stream_url: contentUrl,
|
|
direct_url: contentUrl,
|
|
headers: { Referer: 'https://hqfap.com/', 'User-Agent': UA },
|
|
quality: qm ? `${qm[1]}p` : 'auto',
|
|
type: 'mp4',
|
|
},
|
|
];
|
|
}
|