fix(hqfap): resolve on the device (site blocks the server IP)
Some checks are pending
Backend tests / test (push) Waiting to run
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>
This commit is contained in:
parent
7e2807b42e
commit
92adaccf1b
4 changed files with 66 additions and 2 deletions
|
|
@ -16,6 +16,13 @@ export type ChangelogEntry = {
|
|||
};
|
||||
|
||||
export const CHANGELOG: ChangelogEntry[] = [
|
||||
{
|
||||
id: '2026-07-14',
|
||||
date: 'July 2026',
|
||||
items: [
|
||||
'hqfap: videos play reliably again. The site started blocking our server, so the app now resolves hqfap links directly on your device, fixing the cases that only showed ads or failed to start.',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '2026-07-11',
|
||||
date: 'July 2026',
|
||||
|
|
|
|||
52
mobile/src/lib/hqfapResolver.ts
Normal file
52
mobile/src/lib/hqfapResolver.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* 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',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -261,6 +261,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:hqfapcom') {
|
||||
links = await (await import('../lib/hqfapResolver')).resolveHqfapPage(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);
|
||||
|
|
|
|||
|
|
@ -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 { resolveHqfapPage } from '../lib/hqfapResolver';
|
||||
import type { RootStackParamList } from '../navigation';
|
||||
import { theme } from '../theme';
|
||||
import type { PlaybackSource, SceneOut, StreamLink } from '../types';
|
||||
|
|
@ -570,7 +571,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:hqfapcom'];
|
||||
nav.navigate('Player', {
|
||||
url: initialUrl,
|
||||
sceneId,
|
||||
|
|
@ -613,7 +614,9 @@ function PlaybackButton({
|
|||
? resolveFpoxxxPage
|
||||
: source.origin === 'tube:pornxpph'
|
||||
? resolvePornxpPage
|
||||
: null;
|
||||
: source.origin === 'tube:hqfapcom'
|
||||
? resolveHqfapPage
|
||||
: null;
|
||||
if (phoneResolver) {
|
||||
setResolving(true);
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue