Map the full token chain and prove it needs no browser

Playwright discovery against the live feed found every link, then the whole
chain was replayed in plain urllib with no browser at all:

  SSESS cookie -> GET /oauth/browser-token          -> JWT (90 min)
  JWT + x-api-key -> GET /v1/entry/{id}             -> hdnts master URL (~25s)
  master -> hdntl token (24h) -> ffmpeg

Both auth hops live off the Akamai CDN, so plain Python reaches them; ffmpeg
handles the CDN, which is the only host Python gets 403s from. Nothing needs
a fingerprint forged and Camoufox is now a fallback rather than the plan.

Two assumptions this disproved:
- The play click is not part of auth. Manifests arrive before any click, so
  a headless run needs no interaction to mint a token.
- The 90 minute JWT does not constrain a 2 hour recording. It is spent before
  ffmpeg starts; the 24h hdntl token carries the match.

discover.py drives the page with Playwright and identifies the mint and token
endpoints by what they RETURN rather than by guessing endpoint names, so it
survives the endpoints being renamed. It rides existing Firefox cookies and
needs no password. Verified working with --headless.

Sniffer v0.5 adds muted autoplay: the browser only has to trigger the manifest
fetch, ffmpeg records real audio separately, and muted autoplay is permitted
where audible is not. It stands down once playback starts, because the control
is a toggle and clicking twice would pause the recording.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kyt7QHQvLJtQRuiY4wAqKB
This commit is contained in:
Mikkeli Matlock
2026-09-01 11:45:04 +09:00
parent 79e82ef597
commit ce92e55255
2 changed files with 273 additions and 0 deletions
+70
View File
@@ -27,6 +27,13 @@
// once the endpoint is known -- it writes a lot.
const TRACE = true;
// Start playback without a human. The player needs a click, but the browser
// only has to trigger the manifest fetch -- ffmpeg records the real audio
// independently, so playing MUTED costs nothing and muted autoplay is
// permitted by Firefox policy where audible autoplay is not.
const AUTOPLAY = true;
const AUTOPLAY_TIMEOUT_MS = 180000; // pre-match margin is long; keep trying
const IDLE_OPACITY = 0.063; // 16/255
const MAX_BODY = 20000; // never log a whole media segment
@@ -188,4 +195,67 @@
panel.appendChild(row);
}
}
// --- autoplay -------------------------------------------------------------
// Runs in whichever frame owns the <video>, which may be the player iframe.
function findVideo() {
const v = document.querySelector('video');
return (v && v.isConnected) ? v : null;
}
function clickPlayButton() {
const sels = [
'.bmpui-ui-hugeplaybacktogglebutton', // Bitmovin centre button
'.bmpui-ui-playbacktogglebutton', // Bitmovin control bar
'button[aria-label*="play" i]',
'[class*="play-button"]', '[class*="playButton"]'
];
for (const s of sels) {
const el = document.querySelector(s);
if (el && el.offsetParent !== null) { // must be visible to be real
el.click();
console.log('[brfc-sniffer] autoplay: clicked', s);
return true;
}
}
return false;
}
function startAutoplay() {
if (!AUTOPLAY) return;
const deadline = Date.now() + AUTOPLAY_TIMEOUT_MS;
const timer = setInterval(() => {
const v = findVideo();
// Done once something is actually playing. Critically, we must stop
// here: the play control is a TOGGLE, so clicking it again would pause
// playback and silently end the recording.
if (v && !v.paused && !v.ended && v.readyState > 2) {
console.log('[brfc-sniffer] autoplay: playing, standing down');
clearInterval(timer);
return;
}
if (Date.now() > deadline) {
console.log('[brfc-sniffer] autoplay: gave up after',
AUTOPLAY_TIMEOUT_MS / 1000, 's');
clearInterval(timer);
return;
}
if (v) {
v.muted = true; // audible autoplay is blocked; muted is not
const p = v.play();
if (p && p.catch) p.catch(() => { clickPlayButton(); });
} else {
clickPlayButton(); // player chrome may exist before <video>
}
}, 2000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', startAutoplay);
} else {
startAutoplay();
}
})();