feat(diag): sxyprn resolve reason in bug-report auto-context
Some checks failed
Backend tests / test (push) Has been cancelled

'sxyprn ładuje reklamy' jest niereprodukowalny z naszych IP (strona zwraca
data-vnfo → resolver OK) → podejrzenie region/IP-gate u usera. resolveSxyprnPage
zapamiętuje powód ostatniego resolve (http_<status> / challenge_<len> /
no_vnfo_<len> / post_not_found / vnfo_parse_err / ok_N) i BugReportFAB dokleja go
jako sxyprn=<reason> do auto-contextu. Przy kolejnym zgłoszeniu zobaczymy CO sxyprn
realnie zwrócił temu userowi, bez pytania. Non-behavioral (nie rusza resolve).
This commit is contained in:
goon-foss 2026-07-22 11:25:11 +02:00
parent 2d1800a461
commit 33553729f8
2 changed files with 45 additions and 3 deletions

View file

@ -35,6 +35,7 @@ import { captureScreen } from 'react-native-view-shot';
import { GoonClient } from '../api';
import { theme } from '../theme';
import { formatLastPlayback } from '../lib/lastPlayback';
import { getSxyprnDiag } from '../lib/sxyprnResolver';
interface Props {
client: GoonClient | null;
@ -200,6 +201,10 @@ export function BugReportFAB({ client, appVersion, navRef }: Props) {
// się rozjeżdża", bo mówi KTÓRE źródło i gdzie, niezależnie od ekranu zgłoszenia.
const play = formatLastPlayback();
if (play) extraContext.push(`play=${play}`);
// Diagnostyka sxyprn "ładuje reklamy" (niereprodukowalny z naszych IP → region-gate?).
// Powód ostatniego phone-resolve sxyprn (http status / challenge / brak vnfo / ok_N).
const sxy = getSxyprnDiag();
if (sxy) extraContext.push(`sxyprn=${sxy}`);
const sceneId =
rawSceneId && /^[0-9a-f-]{36}$/.test(rawSceneId) ? rawSceneId : null;
const finalMessage = extraContext.length > 0

View file

@ -24,6 +24,22 @@ export function isSxyprnUrl(url: string | null | undefined): boolean {
return !!url && /(?:^|\/\/|\.)sxyprn\.com\//i.test(url);
}
// Diagnostyka: bug-report "sxyprn ładuje reklamy zamiast wideo" jest NIEreprodukowalny
// z naszych IP (strona zwraca data-vnfo → resolver działa), więc podejrzenie na
// region/IP-gate po stronie usera. Zapamiętujemy powód OSTATNIEGO resolve i doklejamy go
// do auto-contextu zgłoszenia (BugReportFAB) — przy kolejnym raporcie zobaczymy CO sxyprn
// realnie zwrócił temu userowi (http status / challenge / brak vnfo / ok_N), bez pytania.
let _lastDiag: { at: number; reason: string } | null = null;
function _diag(reason: string): void {
_lastDiag = { at: Date.now(), reason };
}
/** Powód ostatniego resolve sxyprn + wiek w sekundach (null gdy nic jeszcze). */
export function getSxyprnDiag(): string | null {
if (!_lastDiag) return null;
const ageS = Math.round((Date.now() - _lastDiag.at) / 1000);
return `${_lastDiag.reason} ${ageS}s ago`;
}
/** Suma cyfr w stringu (ssut51 z main2.js). */
function ssut51(s: string): number {
let sum = 0;
@ -66,18 +82,38 @@ export async function resolveSxyprnPage(pageUrl: string): Promise<StreamLink[]>
let html: string;
try {
const r = await fetch(pageUrl, { headers: { 'User-Agent': UA, Accept: 'text/html' } });
if (!r.ok) return [];
if (!r.ok) {
_diag(`http_${r.status}`);
return [];
}
html = await r.text();
} catch {
_diag('fetch_error');
return [];
}
// Challenge / ad-gate: gdy user dostaje CF/turnstile albo interstitial zamiast strony
// z playerem, resolve pada i coś w fallbacku pokazuje reklamę. To najbardziej
// podejrzana ścieżka dla "ładuje reklamy" (region/IP-specific).
if (/cf-challenge|challenge-platform|turnstile|Just a moment|Attention Required|Checking your browser/i.test(html)) {
_diag(`challenge_${html.length}b`);
return [];
}
if (html.includes('Post Not Found')) {
_diag('post_not_found'); // usunięty post
return [];
}
if (html.includes('Post Not Found')) return []; // usunięty post
const m = _VNFO_RE.exec(html);
if (!m) return [];
if (!m) {
// Brak data-vnfo: albo zmiana struktury, albo user dostał inną stronę (ad-gate).
// Długość HTML rozróżnia "pełna strona bez vnfo" od "krótki interstitial".
_diag(`no_vnfo_${html.length}b`);
return [];
}
let vnfo: Record<string, unknown>;
try {
vnfo = JSON.parse(m[1]);
} catch {
_diag('vnfo_parse_err');
return [];
}
const links: StreamLink[] = [];
@ -100,5 +136,6 @@ export async function resolveSxyprnPage(pageUrl: string): Promise<StreamLink[]>
type: 'mp4',
});
}
_diag(links.length > 0 ? `ok_${links.length}` : 'vnfo_empty');
return links;
}