fix(mobile): zapas hls4 dla sextapes, przycisk OK w klawiaturze PIN-u

sextapes: hls2 na zewnetrznym CDN dawal 502 albo byl blokowany filtrem DNS
(Failed to connect ... [::]); resolver sprawdza manifest i przechodzi na hls4
z domeny embedu (segmenty to TS z doklejonym PNG, ExoPlayer je gra; sprawdzone
na emulatorze). PIN: na TV w poziomie Cancel/Next byly pod krawedzia ekranu,
OK siedzi teraz w pustym polu klawiatury.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
goon-foss 2026-10-16 09:23:52 +02:00
parent 34138ff82f
commit 5e203e7e98
3 changed files with 68 additions and 9 deletions

View file

@ -35,6 +35,14 @@ export type ChangelogEntry = {
}; };
export const CHANGELOG: ChangelogEntry[] = [ export const CHANGELOG: ChangelogEntry[] = [
{
id: '2026-10-16a',
date: 'October 2026',
items: [
'xtapes movies fall back to a second stream when the first one fails or is blocked on your network.',
'TV: setting a PIN works with the remote. An OK key now sits in the keypad, the Next button was below the edge of the screen.',
],
},
{ {
id: '2026-10-15e', id: '2026-10-15e',
date: 'October 2026', date: 'October 2026',

View file

@ -102,23 +102,58 @@ const AD_RE = /\/(?:preroll|midroll|postroll|preplay|ads?|advert|promo)\d*\.(?:m
// (hls4) doklejony do domeny embedu. // (hls4) doklejony do domeny embedu.
const LINKS_ENTRY_RE = /["']?(hls\d+)["']?\s*:\s*["']([^"']+\.m3u8[^"']*)["']/gi; const LINKS_ENTRY_RE = /["']?(hls\d+)["']?\s*:\s*["']([^"']+\.m3u8[^"']*)["']/gi;
function linksObjectUrl(js: string, embedUrl: string): string | null { /** Kandydaci z obiektu `links`: najpierw pelne adresy (hls2, CDN), potem wzgledne (hls4,
let relative: string | null = null; * sciezka na domenie embedu) zamienione na pelne. */
function linksObjectCandidates(js: string, embedUrl: string): string[] {
const absolute: string[] = [];
const relative: string[] = [];
let m: RegExpExecArray | null; let m: RegExpExecArray | null;
LINKS_ENTRY_RE.lastIndex = 0; LINKS_ENTRY_RE.lastIndex = 0;
while ((m = LINKS_ENTRY_RE.exec(js)) !== null) { while ((m = LINKS_ENTRY_RE.exec(js)) !== null) {
const u = m[2]; const u = m[2];
if (AD_RE.test(u)) continue; if (AD_RE.test(u)) continue;
if (/^https?:\/\//i.test(u)) return u; if (/^https?:\/\//i.test(u)) absolute.push(u);
if (!relative && u.startsWith('/')) relative = u; else if (u.startsWith('/')) {
}
if (!relative) return null;
try { try {
return new URL(relative, embedUrl).toString(); relative.push(new URL(u, embedUrl).toString());
} catch { } catch {
return null; // pomijamy
} }
} }
}
return [...absolute, ...relative];
}
/** Czy manifest pod adresem odpowiada (krotki GET z limitem czasu). */
async function manifestReachable(url: string, referer: string): Promise<boolean> {
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), 4000);
try {
const r = await fetch(url, { headers: { 'User-Agent': UA, Referer: referer }, signal: ctrl.signal });
if (!r.ok) return false;
return (await r.text()).trimStart().startsWith('#EXTM3U');
} catch {
return false;
} finally {
clearTimeout(t);
}
}
/**
* Adres z obiektu `links`, sprawdzony z telefonu. sextapes (2026-10-16): hls2 lezy na
* zewnetrznym CDN (premilkyway, cdn-centaurus), ktory potrafi dac 502 albo byc zablokowany
* przez filtr reklam w DNS (blad "Failed to connect to ...premilkyway.com/[::]"). Wtedy
* bierzemy hls4 z domeny embedu (tam strona sama zaczyna). Segmenty hls4 to TS z doklejonym
* na poczatku 70-bajtowym PNG, ExoPlayer i tak znajduje synchronizacje TS.
*/
async function linksObjectUrl(js: string, embedUrl: string, referer: string): Promise<string | null> {
const candidates = linksObjectCandidates(js, embedUrl);
if (candidates.length <= 1) return candidates[0] || null;
for (const c of candidates) {
if (await manifestReachable(c, referer)) return c;
}
return candidates[0];
}
export async function resolvePackerHoster( export async function resolvePackerHoster(
embedUrl: string, embedUrl: string,
@ -164,7 +199,7 @@ export async function resolvePackerHoster(
} }
const um = unpacked.match(FILE_RE); const um = unpacked.match(FILE_RE);
if (um && !AD_RE.test(um[1])) videoUrl = um[1]; if (um && !AD_RE.test(um[1])) videoUrl = um[1];
if (!videoUrl) videoUrl = linksObjectUrl(unpacked, url); if (!videoUrl) videoUrl = await linksObjectUrl(unpacked, url, referer);
} }
if (!videoUrl) return { error: 'no_video_url' }; if (!videoUrl) return { error: 'no_video_url' };

View file

@ -44,7 +44,21 @@ export function PinEntry({ title, error, onSubmit, onCancel, footerAction }: Pro
<View style={styles.pad}> <View style={styles.pad}>
{KEYS.map((k, i) => {KEYS.map((k, i) =>
k === null ? ( k === null ? (
<View key={i} style={[styles.key, styles.keyEmpty]} /> // Zatwierdzenie tez w samej klawiaturze: na telewizorze w poziomie przyciski
// Cancel/Next lezaly pod dolna krawedzia ekranu i nie dalo sie przejsc dalej
// (zgloszenie 94305101, Fire TV AFTMM: "where is ok").
<Pressable
key={i}
disabled={pin.length < 4}
style={({ pressed }) => [
styles.key,
pin.length < 4 ? styles.keyEmpty : styles.keyOk,
pressed && styles.keyPressed,
]}
onPress={() => onSubmit(pin)}
>
{pin.length >= 4 ? <Text style={styles.keyOkText}>OK</Text> : null}
</Pressable>
) : ( ) : (
<Pressable <Pressable
key={i} key={i}
@ -114,6 +128,8 @@ const styles = StyleSheet.create({
justifyContent: 'center', justifyContent: 'center',
}, },
keyEmpty: { backgroundColor: 'transparent', borderColor: 'transparent' }, keyEmpty: { backgroundColor: 'transparent', borderColor: 'transparent' },
keyOk: { backgroundColor: theme.accent, borderColor: theme.accentText },
keyOkText: { color: theme.fg, fontSize: 20, fontWeight: '700' },
keyPressed: { backgroundColor: theme.bgElevated, borderColor: theme.borderFocus }, keyPressed: { backgroundColor: theme.bgElevated, borderColor: theme.borderFocus },
keyText: { color: theme.fg, fontSize: 26, fontWeight: '500' }, keyText: { color: theme.fg, fontSize: 26, fontWeight: '500' },
keyBack: { color: theme.muted, fontSize: 22 }, keyBack: { color: theme.muted, fontSize: 22 },