Commit graph

818 commits

Author SHA1 Message Date
7d20913d56 player: preserve Deobfuscation error class through map_url wrapping
Followup to audit CRIT-1. The original audit added Deobfuscation to
the switch_client whitelist but missed that map_url re-wraps the
cipher_to_url_params error into ExtractionError::InvalidData on line
~727 — InvalidData is NOT switchable, so when a cipher stream appears
on Mobile/Desktop (where our sig fn is unavailable), the player chain
still died instead of falling through to the next client.

Caught by the full integration suite: get_player_from_client::case_2_mobile
panicked with InvalidData wrapping our "sig fn unavailable" error.
Now stays as Deobfuscation through the wrap so switch_client trips
and iOS / Tv handle the request instead.
2026-05-24 12:36:17 -07:00
1211f601df deobfuscate: silence dead_code on Deobfuscator::has_sig/has_nsig
Public API methods exposed for downstream consumers (e.g. straw can
call deobf.has_sig() to skip cipher streams without observing an Err).
The internal short-circuit uses the struct field directly so the
methods register as unused at compile time.
2026-05-24 12:21:55 -07:00
8126cc0da5 audit-fix sprint: all 13 findings (CRIT/HIGH/MED/LOW)
CRIT-1: ExtractionError::Deobfuscation is now switchable.
        Deobfuscator gains has_sig()/has_nsig() — deobfuscate_sig/_nsig
        short-circuit with a recognisable error class so cipher streams
        on the wrong client fall through to the next client in the chain
        instead of killing the whole call.

CRIT-2: Soft-failed DeobfData now caches with a 1-hour retry instead of
        living for 24h. Re-extraction kicks in automatically once YT
        rotates back to a player.js shape we recognise — no more
        wall-clock-day-of-poisoned-cache.

HIGH-1: Reporter now emits a Level::WRN `extract_deobf_soft_fail` report
        on partial extraction. straw / torttube get an artefact when
        sig/nsig regex starts missing.

HIGH-2: player_client_order branches on opts.auth. With botguard
        + authed-cookie users, Desktop is now position 2 (where their
        cookie maps to an OAuth session) instead of position 4.

HIGH-3: Android dropped from the default order. needs_po_token doesn't
        flag Android, so requests were firing unsigned and tripping
        YT's bot-check rejection — which is also not switchable.
        Re-add when a real po_token strategy lands.

MED-1: Comment in needs_deobf softened — the iOS/Android-no-deobf
        property is a current YT behaviour, not a permanent protocol.

MED-2: Cargo.toml workspace pin bumped 0.11.4 → 0.11.5 so it matches
        the package version (avoids future 0.12.x bump surprises).

MED-3: Smoke test fixture uses an isolated per-process scratch dir
        instead of the repo root, avoiding cache-race with
        tests/youtube.rs (which uses CARGO_MANIFEST_DIR and could
        wipe OAuth tokens).

LOW-1: Misleading "dead-code fallback" comment in extract_fns replaced
        with the actual behaviour description.

LOW-2: get_deobf_data uses read-then-write — concurrent player calls
        on warm cache no longer serialise on the write lock.

LOW-3: Smoke test catches IpBan via exact UnavailabilityReason match
        instead of substring "Sign in/IpBan/bot" — a real regression
        won't silently pass anymore.

LOW-4: TV smoke test now asserts !audio_streams.is_empty() too,
        matching iOS / default-order tests.

LOW-5: needs_deobf comment notes YT's historical n= experiments on
        Android — sets expectation for future review passes.
2026-05-24 12:20:14 -07:00
8d7f6b4455 release: 0.11.5 — semver-compatible version bump for fork
Use 0.11.5 instead of 0.11.4-sulkta.1 so the in-workspace
rustypipe-downloader / rustypipe-cli crates (which require
`rustypipe = ^0.11.4`) keep resolving. The original upstream rev
on codeberg is at 0.11.4; we tag this internal release as 0.11.5
to keep cargo happy without needing to bump dependents.
2026-05-24 11:58:30 -07:00
84bb666bb2 release: 0.11.4-sulkta.1 — soft-fail sig + iOS-first default order
Smoke-tested against current YT player c2f7551f (May 2026):

  test ios_player_returns_streams ........... ok
  test default_client_order_returns_streams . ok  (audio Range-GET 206 Partial Content, 1024 bytes)
  test tv_player_returns_streams ............ ok (or env-skipped on IP-banned egress)

Fork changes since upstream v0.11.4:
- client::ClientType::needs_deobf: skip player.js deobf for Android too
- client::player::player_client_order: prefer iOS first (no botguard),
  iOS/Android/Tv/Desktop (with botguard)
- deobfuscate::DeobfData::extract_fns: soft-fail sig_fn/nsig_fn extraction
  so Tv/Desktop callers keep working when YT rotates player.js to a shape
  our regex doesn't recognise — only the load-bearing sig_timestamp is
  required for the request payload
- tests/sulkta_smoke.rs: end-to-end sanity covering iOS, Tv, default-order
  and a Range-GET probe to confirm YT actually serves the audio bytes
2026-05-24 11:57:46 -07:00
947f67834a tests: smoke — switch HEAD to Range GET + iOS UA
YouTube googlevideo CDN 403s HEAD requests + 403s requests with a
non-client User-Agent. Use the iOS client UA on the probe so the CDN
treats it as the same client that requested the URL.
2026-05-24 11:56:41 -07:00
b50f04d565 tests: sulkta smoke — iOS / TV / default-order player_from_client + HEAD probe
Exercises the patched default client_order + soft-fail DeobfData
end-to-end against current YouTube. Verifies:
1. iOS player_from_client returns streams (no deobf path).
2. TV player_from_client returns streams (deobf path with soft-fail).
3. default-clients player() picks iOS primary and a returned audio
   URL HEADs to a 2xx/3xx (i.e. YouTube CDN accepts it).

Lives alongside the upstream tests/youtube.rs so we don't fork their
big snapshot-based test suite, but stays standalone so a single
`cargo test --test sulkta_smoke` exercises just the load-bearing
playback path for our consumers (straw, future torttube).
2026-05-24 11:54:18 -07:00
bda0fea193 deobfuscate: soft-fail sig_fn/nsig_fn extraction
When YouTube rotates player.js to a shape our six sig/nsig regex
patterns don't recognise (eg. c2f7551f, May 2026), the whole player
path used to die at extract_fns even for clients that don't need the
sig fn at all (iOS, Android, Tv all get pre-signed stream URLs).

Now sig_fn / nsig_fn extraction is best-effort. Only the signature
timestamp is required — every `needs_deobf` client needs sts in
the request payload, but the actual deobfuscation functions are only
consumed by map_url when a stream URL carries `&s=` or `&n=`.
On failure we log a warning and store an empty string; Deobfuscator
then skips the JS eval, and any deobfuscate_sig/deobfuscate_nsig
call will fail loudly with "sig fn unavailable" instead of crashing
the player.

Keeps the Tv fallback alive even when sig deobf regex breaks.
2026-05-24 11:53:12 -07:00
a6df2ff7f4 client: prefer iOS over Android in default order
Android-only path requires Google device attestation (po_token /
botguard signing). iOS path has neither attestation nor sig deobf
requirements, so it's the cleanest "just works" default. Keep
Android in the rotation only when botguard is wired.
2026-05-24 11:50:56 -07:00
765a90e808 client: skip player.js deobf for Android + prefer Android-first client order
YouTube's Android InnerTube path returns pre-signed stream URLs (no `s=`
cipher param, no `n=` throttling param) just like the iOS path. Mark
Android as deobf-exempt and put it first in the default player client
order so the typical playback path stops fetching player.js entirely.
Avoids the `could not extract sig fn name` failure on YouTube's newer
player.js shapes (eg. c2f7551f).

Desktop stays in the rotation behind botguard for completeness; it
will still try to deobf and may fail, but it's only consulted as a
fallback for botguard-signed sessions now.
2026-05-24 11:50:15 -07:00
a25907b494 docs: porting plan — NPE sig/nsig pipeline + globalVar indirection (M1) 2026-05-24 11:43:20 -07:00
ThetaDev
6035e6db4e
fix: parse channel subscriber/video count correctly 2025-06-18 15:35:47 +02:00
ThetaDev
e7e389a316
feat: add unavailable field for music tracks
fix: handling albums with unavailable tracks
2025-06-18 15:34:05 +02:00
ThetaDev
412cd37840
test: fix isrc_search_languages (use quoted query) 2025-06-18 13:25:13 +02:00
ta3pks
71712e4eda remove unwrap trying to fetch visitor data (#60)
Co-authored-by: nikos efthias <nikos@mugsoft.io>
Reviewed-on: https://codeberg.org/ThetaDev/rustypipe/pulls/60
Co-authored-by: ta3pks <ta3pks@noreply.codeberg.org>
Co-committed-by: ta3pks <ta3pks@noreply.codeberg.org>
2025-06-17 13:29:52 +02:00
ThetaDev
1f4c9c85b9
chore(release): release rustypipe v0.11.4 2025-04-23 21:30:33 +02:00
ThetaDev
f0477ea3a9
test: add sig deobf test case 2025-04-23 21:29:51 +02:00
ThetaDev
be6da5e7e3
feat: player: handle VPN ban and captcha required error messages 2025-04-23 21:21:23 +02:00
ThetaDev
d675987654
fix: deobfuscator: handle 1-char long global variables, find nsig fn (player 6450230e) 2025-04-23 17:22:22 +02:00
ThetaDev
c6abd89087
test: fix tests 2025-04-18 16:38:44 +02:00
ThetaDev
703f350b6b
chore(release): release rustypipe v0.11.3 2025-04-03 13:39:28 +02:00
ThetaDev
af415ddf8f chore(deps): update rust crate rand to 0.9.0 2025-04-03 11:08:18 +00:00
ThetaDev
daf3d035be
fix: handle music artist not found 2025-03-31 18:11:14 +02:00
ThetaDev
187bf1c9a0
fix: switch client if no adaptive stream URLs were returned 2025-03-26 02:44:08 +01:00
ThetaDev
ea80717f69
fix: handle music playlist/album not found 2025-03-26 02:35:03 +01:00
ThetaDev
939a7aea61
fix: deobfuscator: handle global functions as well 2025-03-26 02:12:18 +01:00
ThetaDev
47bea4eed2
test: update music_artist_basic snapshot 2025-03-26 01:38:35 +01:00
ThetaDev
189ba81a42
fix: extractor: small simplification 2025-03-26 01:38:12 +01:00
ThetaDev
ac44e95a88
fix: extractor: global variable extraction fixed 2025-03-26 01:20:35 +01:00
ThetaDev
23c8775326
chore(release): release rustypipe v0.11.2 2025-03-24 01:50:53 +01:00
ThetaDev
07db7b1166
fix: handle player returning no adaptive stream URLs 2025-03-24 01:28:07 +01:00
ThetaDev
4ce6746be5
fix: extract deobf data with global strings variable 2025-03-24 01:12:01 +01:00
ThetaDev
e8acbfbbcf
fix: A/B test 22: commandExecutorCommand for playlist continuations 2025-03-16 19:45:14 +01:00
ThetaDev
fcf27aa3b2
chore(release): release rustypipe-cli v0.7.2 2025-03-16 18:20:32 +01:00
ThetaDev
64ed3b14e3
chore(release): release rustypipe v0.11.1 2025-03-16 18:13:55 +01:00
ThetaDev
63a6f50a8b
fix: always skip failed clients 2025-03-16 16:51:43 +01:00
ThetaDev
8342caeb0f
fix: desktop client: generate PO token from user_syncid when authenticated 2025-03-16 01:56:29 +01:00
ThetaDev
c04b60604d
fix: simplify get_player_from_clients logic 2025-03-16 01:24:54 +01:00
ThetaDev
2f18efa1cf
fix: log download URL 2025-03-16 01:21:29 +01:00
ThetaDev
b8f61c9bae
test: skip android client test 2025-03-04 22:50:33 +01:00
ThetaDev
9ed1306f3a
chore(deps): update rust crate rstest to 0.25.0 2025-03-04 22:48:10 +01:00
ThetaDev
6d481c16d0
update smartcrop2 to v0.4.0, remove black borders from album covers 2025-03-04 22:38:01 +01:00
ThetaDev
144a670da1
chore(release): release rustypipe-cli v0.7.1 2025-02-26 19:48:12 +01:00
ThetaDev
035c07f170
chore(deps): update rustypipe to 0.11.0 2025-02-26 19:47:42 +01:00
ThetaDev
9bfd3ee1ba
chore(release): release rustypipe-downloader v0.3.1 2025-02-26 19:45:43 +01:00
ThetaDev
1adcb12932
chore(release): release rustypipe v0.11.0 2025-02-26 19:41:36 +01:00
ThetaDev
e7ef067f43
small doc fix 2025-02-26 19:40:10 +01:00
ThetaDev
f3057b4d63
chore: remove commented-out debug statements 2025-02-26 19:32:46 +01:00
ThetaDev
6737512f5f
fix: A/B test 21: music album recommendations 2025-02-26 15:21:47 +01:00
ThetaDev
544782f8de
feat: add original album track count, fix fetching albums with more than 200 tracks 2025-02-26 15:21:47 +01:00