strawcore/src/youtube/js/mod.rs
Cobb 6df61c1f52
Some checks failed
gitleaks / scan (push) Failing after 4s
youtube: instrument the extractor with diagnostic logging
The crate had zero logging — every failure, fallback, cache reset, and
empty-parse was silent by construction, which made the 2026 bot-wall outage
nearly undiagnosable. Add `log = "0.4"` and 52 statements across the extractor
following a one-INFO-per-outcome / WARN-on-every-failure / DEBUG-for-internals
policy, so a future break shows up in a log grep instead of a debugger.

Highlights: nsig->throttled-URL fallback (aggregated one WARN per extraction,
never per-format, via a threaded UrlProcessStats counter); the nsig
identity-output trap now returns DeobfError::NsigIdentity and is NOT cached
(previously it cached and permanently throttled); player.js build/eval/install/
StillBad lifecycle; the ANDROID->VISIONOS cascade outcome; visitorData decline +
reset; HTTP 429 bot-flag; channel/search layout-change empties; per-request
DEBUG (query stripped). Plus a `BotDetected` error variant (Display byte-
identical to the old string) so the wall is greppable.

No secrets: only videoId/channel ids, error Displays (already URL/token-scrubbed
at the exceptions.rs choke points), query-stripped endpoints/player.js URLs,
counts, and lengths are logged — never token/poToken/visitorData/signature
values or response bodies.
2026-08-06 08:16:42 -07:00

69 lines
2.5 KiB
Rust

// JS deobfuscator subsystem — mirrors NPE's player.js / sig / nsig pipeline.
//
// Production callers go through `player_manager` (mirrors NPE's
// YoutubeJavaScriptPlayerManager):
// * signature_timestamp(video_id)
// * deobfuscate_signature(video_id, obfuscated)
// * url_with_throttling_parameter_deobfuscated(video_id, url)
// * throttling_parameter_cache_size()
// * clear_all_caches()
// * clear_throttling_parameters_cache()
//
// The other submodules (runtime / lexer / extractor / signature / nsig) are
// kept `pub` because `tests/js_phase2_offline.rs` exercises them directly
// from outside the crate. If the integration test gets folded into the
// inline `#[cfg(test)]` blocks, these can drop to `pub(crate)`.
pub mod extractor;
pub mod lexer;
pub mod nsig;
pub mod player_manager;
pub mod runtime;
pub mod signature;
use thiserror::Error;
// Clone: PlayerManager's failure memo replays the original extraction
// error on calls suppressed by the cooldown (mirrors NPE's cached
// ParsingException fields).
#[derive(Debug, Clone, Error)]
pub enum DeobfError {
#[error("could not fetch iframe_api: {0}")]
FetchIframe(String),
#[error("could not fetch embed page: {0}")]
FetchEmbed(String),
#[error("could not extract player.js URL")]
PlayerUrlMissing,
#[error("could not fetch player.js: {0}")]
FetchPlayerCode(String),
#[error("invalid player.js URL: {0}")]
InvalidPlayerUrl(String),
#[error("could not find sig deobf function via any pattern")]
SigFuncNotFound,
#[error("could not parse sig deobf function body: {0}")]
SigBodyParseFailed(String),
#[error("could not find sig helper object")]
SigHelperMissing,
#[error("could not find sig global array")]
SigGlobalArrayMissing,
#[error("could not extract signature timestamp")]
SigTimestampMissing,
#[error("could not find nsig deobf function via any pattern")]
NsigFuncNotFound,
#[error("could not parse nsig deobf function body: {0}")]
NsigBodyParseFailed(String),
#[error("nsig array indirection failed: {0}")]
NsigArrayLookupFailed(String),
#[error("js compile failed: {0}")]
JsCompileFailed(String),
#[error("js runtime failed: {0}")]
JsRuntimeFailed(String),
#[error("nsig output was empty (function neutered?)")]
NsigEmpty,
#[error("nsig output equalled its input (identity — deobfuscator neutered?)")]
NsigIdentity,
#[error("downloader not initialized")]
DownloaderMissing,
}
pub use player_manager::PlayerManager;