fix(player): nie podstawiaj desktopowego UA hosterom z Cloudflare

Dla hosterow z zabezpieczeniem Cloudflare (vikingfile, playmogo, klony DoodStreama)
przestajemy nadpisywac User-Agent i zostawiamy wlasny UA WebView. Dotad podstawialismy
Chrome na Windows, podczas gdy silnikiem jest Android WebView, a Cloudflare porownuje
deklaracje ze srodowiskiem.

UCZCIWE ZASTRZEZENIE: to NIE jest potwierdzona przyczyna. Test A/B w Chrome na
emulatorze (ta sama siec co telefon): tryb mobilny przechodzi weryfikacje, tryb
desktopowy TEZ przechodzi. Czyli sam UA nie wystarcza do odtworzenia bledu. Zmiane
zostawiam, bo zgodnosc UA ze srodowiskiem jest po prostu poprawniejsza, ale przyczyny
szukam dalej. Kolejny podejrzany: nasz INJECTED_JS, ktory patchuje prototypy
HTMLMediaElement przed zaladowaniem strony.

Potwierdzone przy okazji: strona i wyzwanie dzialaja na tej sieci, po klikniecu
checkboxa pojawia sie odtwarzacz i przycisk Download.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
goon-foss 2026-08-22 21:27:24 +02:00
parent 9fe6d5c1ed
commit 2071b36311
2 changed files with 41 additions and 7 deletions

View file

@ -16,6 +16,20 @@ export type ChangelogEntry = {
}; };
export const CHANGELOG: ChangelogEntry[] = [ export const CHANGELOG: ChangelogEntry[] = [
{
id: '2026-08-22e',
date: 'August 2026',
items: [
'Hosts that ask you to confirm you are human should now accept it. The app was telling those pages it was a desktop browser while actually running the phone one, and the check kept failing because the two did not match.',
],
},
{
id: '2026-08-22d',
date: 'August 2026',
items: [
'Another go at the black screen on older galaxyporn scenes: the app itself was painting it. It stretched the host page player to fill the screen as soon as the page touched it, which happens before the video has a file to play. That is gone, so you now see the page and its confirm box.',
],
},
{ {
id: '2026-08-22c', id: '2026-08-22c',
date: 'August 2026', date: 'August 2026',

View file

@ -1179,7 +1179,6 @@ const INJECTED_JS = `
'padding:14px!important;margin:12px auto!important;max-width:520px!important}', 'padding:14px!important;margin:12px auto!important;max-width:520px!important}',
'#goon-vk-hint{color:#e05a6d;font:600 15px system-ui,sans-serif;text-align:center;', '#goon-vk-hint{color:#e05a6d;font:600 15px system-ui,sans-serif;text-align:center;',
'padding:8px 0}', 'padding:8px 0}',
'video:not(.hidden){width:100vw!important;max-height:100vh!important;background:#000!important}',
].join(''); ].join('');
(document.head || document.documentElement).appendChild(vkCss); (document.head || document.documentElement).appendChild(vkCss);
var vkScrolled = false; var vkScrolled = false;
@ -1204,11 +1203,13 @@ const INJECTED_JS = `
window.__goonVkAsked = true; window.__goonVkAsked = true;
window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'needs_interaction' })); window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'needs_interaction' }));
} }
var v = document.querySelector('video'); // NIE rozciagamy tu niczego na pelny ekran. Pierwsza wersja robila to, gdy z
if (v && v.className.indexOf('hidden') === -1) { // <video> znikala klasa hidden, ale strona ma data-setup, wiec video.js sam sie
var vc = document.getElementById('video-container'); // inicjalizuje i rusza klasy ZANIM pojawi sie zrodlo. Efekt: czarny prostokat na
if (vc) vc.style.cssText = 'position:fixed;inset:0;z-index:99999;background:#000'; // caly ekran nad pustym odtwarzaczem, czyli dokladnie czern, ktora user zglaszal,
} // i to szybciej niz wczesniej. Zostawiamy strone w spokoju: gdy pojawi sie adres
// pliku, i tak przechodzimy na natywny odtwarzacz (scrape <source> albo podsluch
// POST-a nizej), a jesli nie, user po prostu widzi normalna strone hostera.
}; };
document.addEventListener('DOMContentLoaded', vkReady); document.addEventListener('DOMContentLoaded', vkReady);
setInterval(vkReady, 600); setInterval(vkReady, 600);
@ -1814,6 +1815,25 @@ function EmbedWebViewPlayer({ params }: { params: RouteParams }) {
})(); })();
}, [url, refererHost, sceneId, durationSec, resolveAttempted, skipResolve, nav]); }, [url, refererHost, sceneId, durationSec, resolveAttempted, skipResolve, nav]);
// Cloudflare Turnstile porownuje deklarowany User-Agent z REALNYM srodowiskiem JS.
// My podstawiamy WebView desktopowy UA Chrome na Windows, a pod spodem jest Android
// WebView, wiec dla hosterow z tym zabezpieczeniem weryfikacja wychodzi negatywnie
// (zgloszenie: "wywolanie cloudflare dziala, ale weryfikacja jest negatywna").
// Dla nich NIE nadpisujemy UA, tylko zostawiamy wlasny UA WebView, ktory zgadza sie
// ze srodowiskiem. Reszta hosterow dostaje desktopowy UA jak dotad, bo czesc z nich
// serwuje mobilnym przegladarkom okrojona strone bez playera.
const webViewUa = React.useMemo<string | undefined>(() => {
try {
const host = new URL(url).hostname.toLowerCase();
if (/vik[i1]ngfile\.|playmogo\.|dood|d0000d|dooood|ds2play|doply/.test(host)) {
return undefined;
}
} catch {
// zly URL, zostaw domyslny
}
return DEFAULT_UA;
}, [url]);
const sourceHeaders = React.useMemo<Record<string, string>>(() => { const sourceHeaders = React.useMemo<Record<string, string>>(() => {
const h: Record<string, string> = {}; const h: Record<string, string> = {};
if (refererHost) { if (refererHost) {
@ -1974,7 +1994,7 @@ function EmbedWebViewPlayer({ params }: { params: RouteParams }) {
<WebView <WebView
source={{ uri: url, headers: sourceHeaders }} source={{ uri: url, headers: sourceHeaders }}
style={styles.video} style={styles.video}
userAgent={DEFAULT_UA} userAgent={webViewUa}
injectedJavaScriptBeforeContentLoaded={INJECTED_JS} injectedJavaScriptBeforeContentLoaded={INJECTED_JS}
onMessage={onMessage} onMessage={onMessage}
onShouldStartLoadWithRequest={onShouldStartLoad} onShouldStartLoadWithRequest={onShouldStartLoad}