ytmapi_rs/auth/
browser.rs1use super::{AuthToken, RawResult};
2use crate::client::Client;
3use crate::error::{Error, Result};
4use crate::parse::ProcessedResult;
5use crate::utils;
6use crate::utils::constants::{USER_AGENT, YTM_URL};
7use serde::{Deserialize, Serialize};
8use std::borrow::Cow;
9use std::fmt::Debug;
10use std::path::Path;
11
12#[derive(Clone, Serialize, Deserialize)]
13pub struct BrowserToken {
14 sapisid: String,
15 client_version: String,
16 cookies: String,
17}
18
19impl AuthToken for BrowserToken {
20 fn client_version(&self) -> Cow<'_, str> {
21 (&self.client_version).into()
22 }
23 fn deserialize_response<Q>(
24 raw: RawResult<Q, Self>,
25 ) -> Result<crate::parse::ProcessedResult<Q>> {
26 let processed = ProcessedResult::try_from(raw)?;
27 if let Some(error) = processed.get_json().pointer("/error") {
31 let Some(code) = error.pointer("/code").and_then(|v| v.as_u64()) else {
32 return Err(Error::response("API reported an error but no code"));
34 };
35 let message = error
36 .pointer("/message")
37 .and_then(|s| s.as_str())
38 .map(|s| s.to_string())
39 .unwrap_or_default();
40 return Err(Error::other_code(code, message));
41 }
42 Ok(processed)
43 }
44 fn headers(&self) -> Result<impl IntoIterator<Item = (&str, Cow<'_, str>)>> {
45 let hash = utils::hash_sapisid(&self.sapisid);
46 Ok([
47 ("X-Origin", YTM_URL.into()),
48 ("Origin", YTM_URL.into()),
49 ("Content-Type", "application/json".into()),
50 ("Authorization", format!("SAPISIDHASH {hash}").into()),
51 ("Cookie", self.cookies.as_str().into()),
52 ("Accept", "*/*".into()),
53 ("Accept-Encoding", "gzip, deflate".into()),
54 ])
55 }
56}
57
58impl BrowserToken {
59 pub async fn from_str(cookie_str: &str, client: &Client) -> Result<Self> {
60 let cookies = cookie_str.trim().to_string();
61 let user_agent = USER_AGENT;
62 let initial_headers = [
64 ("User-Agent", user_agent.into()),
65 ("Cookie", cookies.as_str().into()),
66 ];
67 let response_text = client.get_query(YTM_URL, initial_headers, &()).await?.text;
68 if response_text.contains("Sorry, YouTube Music is not optimised for your browser. Check for updates or try Google Chrome.") {
70 return Err(Error::invalid_user_agent(user_agent));
71 };
72 let client_version = response_text
74 .split_once("INNERTUBE_CLIENT_VERSION\":\"")
75 .ok_or(Error::header())?
76 .1
77 .split_once('\"')
78 .ok_or(Error::header())?
79 .0
80 .to_string();
81 let sapisid = cookies
82 .split_once("SAPISID=")
83 .ok_or(Error::header())?
84 .1
85 .split_once(';')
86 .ok_or(Error::header())?
87 .0
88 .to_string();
89 Ok(Self {
90 sapisid,
91 client_version,
92 cookies,
93 })
94 }
95 pub async fn from_cookie_file<P>(path: P, client: &Client) -> Result<Self>
96 where
97 P: AsRef<Path>,
98 {
99 let contents = tokio::fs::read_to_string(path).await?;
100 BrowserToken::from_str(&contents, client).await
101 }
102}
103
104impl Debug for BrowserToken {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 write!(f, "Private BrowserToken")
109 }
110}