[archived] Sulkta fork of codeberg.org/ThetaDev/rustypipe. Was powering Straw Phase U; replaced 2026-05-24 by Sulkta-Coop/strawcore (Rust port of NewPipeExtractor). Kept for history.
This repository has been archived on 2026-05-27. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
Find a file
Kayos 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
.forgejo/workflows ci: disable renovate 2025-02-22 23:02:15 +00:00
cli chore(release): release rustypipe-cli v0.7.2 2025-03-16 18:20:32 +01:00
codegen fix: A/B test 22: commandExecutorCommand for playlist continuations 2025-03-16 19:45:14 +01:00
docs docs: porting plan — NPE sig/nsig pipeline + globalVar indirection (M1) 2026-05-24 11:43:20 -07:00
downloader chore(deps): update rust crate rand to 0.9.0 2025-04-03 11:08:18 +00:00
notes fix: A/B test 22: commandExecutorCommand for playlist continuations 2025-03-16 19:45:14 +01:00
src audit-fix sprint: all 13 findings (CRIT/HIGH/MED/LOW) 2026-05-24 12:20:14 -07:00
testfiles fix: deobfuscator: handle 1-char long global variables, find nsig fn (player 6450230e) 2025-04-23 17:22:22 +02:00
tests audit-fix sprint: all 13 findings (CRIT/HIGH/MED/LOW) 2026-05-24 12:20:14 -07:00
.editorconfig fix: add new attributed_text description 2022-09-24 18:41:27 +02:00
.gitignore feat: add support for rustypipe-botguard to get PO tokens 2025-02-05 15:56:14 +01:00
.pre-commit-config.yaml feat!: add userdata feature for all personal data queries (playback history, subscriptions) 2025-02-07 13:21:12 +01:00
Cargo.toml audit-fix sprint: all 13 findings (CRIT/HIGH/MED/LOW) 2026-05-24 12:20:14 -07:00
CHANGELOG.md chore(release): release rustypipe v0.11.4 2025-04-23 21:30:33 +02:00
cliff.toml chore: change repo URL to Codeberg 2024-08-18 03:03:15 +02:00
DEVELOPMENT.md docs: update README 2025-01-16 03:45:12 +01:00
Justfile feat!: add userdata feature for all personal data queries (playback history, subscriptions) 2025-02-07 13:21:12 +01:00
LICENSE chore: add readme and license 2022-09-19 01:15:42 +02:00
README.md doc: add Botguard info to README 2025-02-07 23:15:34 +01:00
renovate.json ci: renovate: preserveSemverRanges 2025-02-02 16:53:31 +01:00

RustyPipe

Current crates.io version License Docs CI status

RustyPipe is a fully featured Rust client for the public YouTube / YouTube Music API (Innertube), inspired by NewPipe.

Features

YouTube

  • Player (video/audio streams, subtitles)
  • VideoDetails (metadata, comments, recommended videos)
  • Playlist
  • Channel (videos, shorts, livestreams, playlists, info, search)
  • ChannelRSS
  • Search (with filters)
  • Search suggestions
  • Trending
  • URL resolver
  • Subscriptions
  • Playback history

YouTube Music

  • Playlist
  • Album
  • Artist
  • Search
  • Search suggestions
  • Radio
  • Track details (lyrics, recommendations)
  • Moods/Genres
  • Charts
  • New (albums, music videos)
  • Saved items
  • Playback history

Getting started

The RustyPipe library works as follows: at first you have to instantiate a RustyPipe client. You can either create it with default options or use the RustyPipe::builder() to customize it.

For fetching data you have to start with a new RustyPipe query object (rp.query()). The query object holds options for an individual query (e.g. content language or country). You can adjust these options with setter methods. Finally call your query method to fetch the data you need.

All query methods are async, you need the tokio runtime to execute them.

let rp = RustyPipe::new();
let rp = RustyPipe::builder().storage_dir("/app/data").build().unwrap();
let channel = rp.query().lang(Language::De).channel_videos("UCl2mFZoRqjw_ELax4Yisf6w").await.unwrap();

Here are a few examples to get you started:

Cargo.toml

[dependencies]
rustypipe = "0.1.3"
tokio = { version = "1.20.0", features = ["macros", "rt-multi-thread"] }

Watch a video

use std::process::Command;

use rustypipe::{client::RustyPipe, param::StreamFilter};

#[tokio::main]
async fn main() {
    // Create a client
    let rp = RustyPipe::new();
    // Fetch the player
    let player = rp.query().player("pPvd8UxmSbQ").await.unwrap();
    // Select the best streams
    let (video, audio) = player.select_video_audio_stream(&StreamFilter::default());

    // Open mpv player
    let mut args = vec![video.expect("no video stream").url.to_owned()];
    if let Some(audio) = audio {
        args.push(format!("--audio-file={}", audio.url));
    }
    Command::new("mpv").args(args).output().unwrap();
}

Get a playlist

use rustypipe::client::RustyPipe

#[tokio::main]
async fn main() {
    // Create a client
    let rp = RustyPipe::new();
    // Get the playlist
    let playlist = rp
        .query()
        .playlist("PL2_OBreMn7FrsiSW0VDZjdq0xqUKkZYHT")
        .await
        .unwrap();
    // Get all items (maximum: 1000)
    playlist.videos.extend_limit(rp.query(), 1000).await.unwrap();

    println!("Name: {}", playlist.name);
    println!("Author: {}", playlist.channel.unwrap().name);
    println!("Last update: {}", playlist.last_update.unwrap());

    playlist
        .videos
        .items
        .iter()
        .for_each(|v| println!("[{}] {} ({}s)", v.id, v.name, v.length));
}

Output:

Name: Homelab
Author: Jeff Geerling
Last update: 2023-05-04
[cVWF3u-y-Zg] I put a computer in my computer (720s)
[ecdm3oA-QdQ] 6-in-1: Build a 6-node Ceph cluster on this Mini ITX Motherboard (783s)
[xvE4HNJZeIg] Scrapyard Server: Fastest all-SSD NAS! (733s)
[RvnG-ywF6_s] Nanosecond clock sync with a Raspberry Pi (836s)
[R2S2RMNv7OU] I made the Petabyte Raspberry Pi even faster! (572s)
[FG--PtrDmw4] Hiding Macs in my Rack! (515s)
...

Get a channel

use rustypipe::client::RustyPipe

#[tokio::main]
async fn main() {
    // Create a client
    let rp = RustyPipe::new();
    // Get the channel
    let channel = rp
        .query()
        .channel_videos("UCl2mFZoRqjw_ELax4Yisf6w")
        .await
        .unwrap();

    println!("Name: {}", channel.name);
    println!("Description: {}", channel.description);
    println!("Subscribers: {}", channel.subscriber_count.unwrap());

    channel
        .content
        .items
        .iter()
        .for_each(|v| println!("[{}] {} ({}s)", v.id, v.name, v.length.unwrap()));
}

Output:

Name: Louis Rossmann
Description: I discuss random things of interest to me. (...)
Subscribers: 1780000
[qBHgJx_rb8E] Introducing Rossmann senior, a genuine fossil 😃 (122s)
[TmV8eAtXc3s] Am I wrong about CompTIA? (592s)
[CjOJJc1qzdY] How FUTO projects loosen Google's grip on your life! (588s)
[0A10JtkkL9A] a private moment between a man and his kitten (522s)
[zbHq5_1Cd5U] Is Texas mandating auto repair shops use OEM parts? SB1083 analysis & breakdown; tldr, no. (645s)
[6Fv8bd9ICb4] Who owns this? (199s)
...

Crate features

Some features of RustyPipe are gated behind features to avoid compiling unneeded dependencies.

  • rss Fetch a channel's RSS feed, which is faster than fetching the channel page
  • userdata Add functions to fetch YouTube user data (watch history, subscriptions, music library)

You can also choose the TLS library used for making web requests using the same features as the reqwest crate (default-tls, native-tls, native-tls-alpn, native-tls-vendored, rustls-tls-webpki-roots, rustls-tls-native-roots).

Cache storage

The RustyPipe cache holds the current version numbers for all clients, the JavaScript code used to deobfuscate video URLs and the authentication token/cookies. Never share the contents of the cache if you are using authentication.

By default the cache is written to a JSON file named rustypipe_cache.json in the current working directory. This path can be changed with the storage_dir option of the RustyPipeBuilder. The RustyPipe CLI stores its cache in the userdata folder. The full path on Linux is ~/.local/share/rustypipe/rustypipe_cache.json.

You can integrate your own cache storage backend (e.g. database storage) by implementing the CacheStorage trait.

Reports

RustyPipe has a builtin error reporting system. If a YouTube response cannot be deserialized or parsed, the original response data along with some request metadata is written to a JSON file in the folder rustypipe_reports, located in RustyPipe's storage directory (current folder by default, ~/.local/share/rustypipe for the CLI).

When submitting a bug report to the RustyPipe project, you can share this report to help resolve the issue.

RustyPipe reports come in 3 severity levels:

  • DBG (no error occurred, report creation was enabled by the RustyPipeQuery::report query option)
  • WRN (parts of the response could not be deserialized/parsed, response data may be incomplete)
  • ERR (entire response could not be deserialized/parsed, RustyPipe returned an error)

PO tokens

Since August 2024 YouTube requires PO tokens to access streams from web-based clients (Desktop, Mobile). Otherwise streams will return a 403 error.

Generating PO tokens requires a simulated browser environment, which would be too large to include in RustyPipe directly.

Therefore, the PO token generation is handled by a seperate CLI application (rustypipe-botguard) which is called by the RustyPipe crate. RustyPipe automatically detects the rustypipe-botguard binary if it is located in PATH or the current working directory. If your rustypipe-botguard binary is located at a different path, you can specify it with the .botguard_bin(path) option.

Authentication

RustyPipe supports authenticating with your YouTube account to access age-restricted/private videos and user information. There are 2 supported authentication methods: OAuth and cookies.

To execute a query with authentication, use the .authenticated() query option. This option is enabled by default for queries that always require authentication like fetching user data. RustyPipe may automatically use authentication in case a video is age-restricted or your IP address is banned by YouTube. If you never want to use authentication, set the .unauthenticated() query option.

OAuth

OAuth is the authentication method used by the YouTube TV client. It is more user-friendly than extracting cookies, however it only works with the TV client. This means that you can only fetch videos and not access any user data.

To login using OAuth, you first have to get a new device code using the rp.user_auth_get_code() function. You can then enter the code on https://google.com/device and log in with your Google account. After generating the code, you can call the rp.user_auth_wait_for_login() function which waits until the user has logged in and stores the authentication token in the cache.

Cookies

Authenticating with cookies allows you to use the functionality of the YouTube/YouTube Music Desktop client. You can fetch your subscribed channels, playlists and your music collection. You can also fetch videos using the Desktop client, including private videos, as long as you have access to them.

To authenticate with cookies you have to log into YouTube in a fresh browser session (open Incognito/Private mode). Then extract the cookies from the developer tools or by using browser plugins like "Get cookies.txt LOCALLY" (Firefox) (Chromium). Close the browser window after extracting the cookies to prevent YouTube from rotating the cookies.

You can then add the cookies to your RustyPipe client using the user_auth_set_cookie or user_auth_set_cookie_txt function. The cookies are stored in the cache file. To log out, use the function user_auth_remove_cookie.