From 3d6de5354599ea691351e0ca161154e53f2e0b41 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sat, 27 Jul 2024 02:36:45 +0200 Subject: [PATCH] feat: add http_client method to RustyPipe and user_agent method to RustyPipeQuery For downloading, the http client as well as the user agent used by RustyPipe should be available. --- src/client/mod.rs | 67 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 17 deletions(-) diff --git a/src/client/mod.rs b/src/client/mod.rs index 3091db6..e6761e8 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -225,6 +225,7 @@ struct RustyPipeRef { n_http_retries: u32, cache: CacheHolder, default_opts: RustyPipeOpts, + user_agent: Cow<'static, str>, } #[derive(Clone)] @@ -455,8 +456,13 @@ impl RustyPipeBuilder { /// Create a new, configured RustyPipe instance using a Reqwest client builder. pub fn build_with_client(self, mut client_builder: ClientBuilder) -> Result { + let user_agent = self + .user_agent + .map(Cow::Owned) + .unwrap_or(Cow::Borrowed(DEFAULT_UA)); + client_builder = client_builder - .user_agent(self.user_agent.unwrap_or_else(|| DEFAULT_UA.to_owned())) + .user_agent(user_agent.as_ref()) .gzip(true) .brotli(true) .redirect(reqwest::redirect::Policy::none()); @@ -503,6 +509,7 @@ impl RustyPipeBuilder { deobf: RwLock::new(cdata.deobf), }, default_opts: self.default_opts, + user_agent, }), }) } @@ -710,6 +717,14 @@ impl RustyPipe { } } + /// Get the internal HTTP client + /// + /// Can be used for downloading videos or custom YT requests. + #[must_use] + pub fn http_client(&self) -> &Client { + &self.inner.http + } + /// Execute the given http request. async fn http_request(&self, request: &Request) -> Result { let mut last_resp = None; @@ -963,7 +978,14 @@ impl RustyPipe { /// visitor data is extracted from the html page. async fn get_visitor_data(&self) -> Result { tracing::debug!("getting YT visitor data"); - let resp = self.inner.http.get(YOUTUBE_MUSIC_HOME_URL).send().await?; + let resp = self + .inner + .http + .get(YOUTUBE_MUSIC_HOME_URL) + .header(header::ORIGIN, YOUTUBE_MUSIC_HOME_URL) + .header(header::REFERER, YOUTUBE_MUSIC_HOME_URL) + .send() + .await?; let vdata = resp .headers() @@ -972,7 +994,10 @@ impl RustyPipe { .find_map(|c| { if let Ok(cookie) = c.to_str() { if let Some(after) = cookie.strip_prefix("__Secure-YEC=") { - return after.split_once(';').map(|s| s.0.to_owned()); + return after + .split_once(';') + .map(|s| s.0.to_owned()) + .filter(|s| !s.is_empty()); } } None @@ -1065,6 +1090,27 @@ impl RustyPipeQuery { self } + /// Get the user agent for the given client type + /// + /// This can be used for additional HTTP requests (e.g. downloading/streaming) + pub fn user_agent(&self, ctype: ClientType) -> Cow<'_, str> { + match ctype { + ClientType::Desktop | ClientType::DesktopMusic | ClientType::TvHtml5Embed => { + Cow::Borrowed(&self.client.inner.user_agent) + } + ClientType::Android => format!( + "com.google.android.youtube/{} (Linux; U; Android 12; {}) gzip", + MOBILE_CLIENT_VERSION, self.opts.country + ) + .into(), + ClientType::Ios => format!( + "com.google.ios.youtube/{} ({}; U; CPU iOS 15_4 like Mac OS X; {})", + MOBILE_CLIENT_VERSION, IOS_DEVICE_MODEL, self.opts.country + ) + .into(), + } + } + /// Create a new context object, which is included in every request to /// the YouTube API and contains language, country and device parameters. /// @@ -1227,13 +1273,6 @@ impl RustyPipeQuery { .post(format!( "{YOUTUBEI_V1_GAPIS_URL}{endpoint}?{DISABLE_PRETTY_PRINT_PARAMETER}" )) - .header( - header::USER_AGENT, - format!( - "com.google.android.youtube/{} (Linux; U; Android 12; {}) gzip", - MOBILE_CLIENT_VERSION, self.opts.country - ), - ) .header("X-Goog-Api-Format-Version", "2"), ClientType::Ios => self .client @@ -1242,15 +1281,9 @@ impl RustyPipeQuery { .post(format!( "{YOUTUBEI_V1_GAPIS_URL}{endpoint}?{DISABLE_PRETTY_PRINT_PARAMETER}" )) - .header( - header::USER_AGENT, - format!( - "com.google.ios.youtube/{} ({}; U; CPU iOS 15_4 like Mac OS X; {})", - MOBILE_CLIENT_VERSION, IOS_DEVICE_MODEL, self.opts.country - ), - ) .header("X-Goog-Api-Format-Version", "2"), }; + r = r.header(header::USER_AGENT, self.user_agent(ctype).as_ref()); if let Some(vdata) = self.opts.visitor_data.as_deref().or(visitor_data) { r = r.header("X-Goog-EOM-Visitor-Id", vdata); }