Commit graph

2 commits

Author SHA1 Message Date
Sulkta
1533157485 Phase 5 — PoTokenProvider trait + stream_extractor wiring
Mirrors NPE PoTokenProvider.java + PoTokenResult.java; defines the
host-injection surface for BotGuard attestation. The Rust crate stays
out of the BotGuard business — embedders (Straw on Android, future
Sulkta CLI via Browserless, etc.) supply their own impl.

src/youtube/potoken/mod.rs
  * PoTokenResult { player_request_po_token, streaming_data_po_token,
                    visitor_data }  + ::new + ::single constructors
  * PoTokenError (Unavailable, MintFailed) — FIX vs NPE: split 'declined'
    (Ok(None)) from 'errored' (Err) so callers can react differently
  * trait PoTokenProvider with 4 client-scoped methods; default impl
    returns Ok(None) so embedders can override just what they support
  * set_po_token_provider / clear_po_token_provider / po_token_provider
    static registration via RwLock<Option<Arc<dyn PoTokenProvider>>>

src/youtube/potoken/noop.rs
  * NoopPoTokenProvider — safe default

src/youtube/stream_extractor.rs
  * resolve_po_token via options-first-then-provider helper
    (options_or_provider)
  * Android branch: pulls player_request_po_token + visitor_data into
    /player body, streams streaming_data_po_token through to URL &pot=
  * iOS branch: same shape, gated on fetch_ios_client AND non-empty
    provider result

Kotlin side (PoTokenWebView lift into Straw via UniFFI's foreign-trait
bridge) is separate work — strawcore just owns the contract.

Tests: 77 lib unit pass (+4 since Phase 4) + 7 Phase 2 offline + 7
Phase 4 offline = 91 green.
2026-05-24 17:10:13 -07:00
Sulkta
0d08f12b3b Phase 4 (complete) — stream_extractor orchestrator
Wire the Android-primary fetch path + JSON-walking + URL post-processing
into a single stream_info(video_id) entry point. Mirrors NPE
YoutubeStreamExtractor.onFetchPage() per audit Track C §1.2.

src/youtube/stream_extractor.rs
  * stream_info(video_id) + stream_info_with(video_id, options)
  * fetch_android — reel endpoint (anonymous) OR /player (with po_token)
  * check_playability_status — maps to ContentUnavailable variants
    (AgeRestricted, GeoRestricted, Paid, Private, YoutubeMusicPremium,
    AccountTerminated, Other)
  * is_player_response_not_valid — decoy-video detection
  * populate_video_details + populate_microformat + populate_streams +
    populate_manifests + populate_captions
  * process_url — sig deobf path (signatureCipher → JS function call)
    + unconditional nsig deobf + cpn append + pot append
  * build_video_progressive / build_video_only / build_audio +
    push_*_dedup helpers (FIX: NPE bug — dedup by itag id, not by
    mediaFormat.id which collides 140/141)

Consolidated stream_helper's local ExtractionError into the crate-wide
exceptions::ExtractionError with a new DownloaderMissing variant.

Tests: 73 lib unit pass (+9 since Phase 3) + 7 new Phase 4 offline
integration tests = 80 lib green. Live YT end-to-end smoke deferred
to Straw integration; the code path is in place.
2026-05-24 17:08:04 -07:00