ytmapi_rs/auth/
browser.rs1use super::private::Sealed;
2use super::AuthToken;
3use crate::client::Client;
4use crate::error::{Error, Result};
5use crate::parse::ProcessedResult;
6use crate::query::PostQuery;
7use crate::{client, utils};
8use crate::{
9 process::RawResult,
10 query::Query,
11 utils::constants::{USER_AGENT, YTM_API_URL, YTM_PARAMS, YTM_PARAMS_KEY, YTM_URL},
12};
13use serde::{Deserialize, Serialize};
14use serde_json::json;
15use std::fmt::Debug;
16use std::path::Path;
17
18#[derive(Clone, Serialize, Deserialize)]
19pub struct BrowserToken {
20 sapisid: String,
21 client_version: String,
22 cookies: String,
23}
24
25impl Sealed for BrowserToken {}
26impl AuthToken for BrowserToken {
27 async fn raw_query_post<'a, Q: PostQuery + Query<Self>>(
28 &self,
29 client: &client::Client,
30 query: &'a Q,
31 ) -> Result<RawResult<'a, Q, BrowserToken>> {
32 let url = format!("{YTM_API_URL}{}{YTM_PARAMS}{YTM_PARAMS_KEY}", query.path());
34 let mut body = json!({
35 "context" : {
36 "client" : {
37 "clientName" : "WEB_REMIX",
38 "clientVersion" : self.client_version,
39 },
40 },
41 });
42 if let Some(body) = body.as_object_mut() {
43 body.append(&mut query.header());
44 } else {
45 unreachable!("Body created in this function as an object")
46 };
47 let hash = utils::hash_sapisid(&self.sapisid);
48 let headers = [
49 ("X-Origin", YTM_URL.into()),
50 ("Content-Type", "application/json".into()),
51 ("Authorization", format!("SAPISIDHASH {hash}").into()),
52 ("Cookie", self.cookies.as_str().into()),
53 ];
54 let result = client
55 .post_query(url, headers, &body, &query.params())
56 .await?;
57 let result = RawResult::from_raw(result, query);
58 Ok(result)
59 }
60 async fn raw_query_get<'a, Q: crate::query::GetQuery + Query<Self>>(
61 &self,
62 client: &Client,
63 query: &'a Q,
64 ) -> Result<RawResult<'a, Q, Self>> {
65 let hash = utils::hash_sapisid(&self.sapisid);
67 let headers = [
68 ("X-Origin", YTM_URL.into()),
69 ("Content-Type", "application/json".into()),
70 ("Authorization", format!("SAPISIDHASH {hash}").into()),
71 ("Cookie", self.cookies.as_str().into()),
72 ];
73 let result = client
74 .get_query(query.url(), headers, &query.params())
75 .await?;
76 let result = RawResult::from_raw(result, query);
77 Ok(result)
78 }
79 fn deserialize_json<Q: Query<Self>>(
80 raw: RawResult<Q, Self>,
81 ) -> Result<crate::parse::ProcessedResult<Q>> {
82 let processed = ProcessedResult::try_from(raw)?;
83 if let Some(error) = processed.get_json().pointer("/error") {
86 let Some(code) = error.pointer("/code").and_then(|v| v.as_u64()) else {
87 return Err(Error::response("API reported an error but no code"));
89 };
90 let message = error
91 .pointer("/message")
92 .and_then(|s| s.as_str())
93 .map(|s| s.to_string())
94 .unwrap_or_default();
95 match code {
96 401 => return Err(Error::browser_authentication_failed()),
100 other => return Err(Error::other_code(other, message)),
101 }
102 }
103 Ok(processed)
104 }
105}
106
107impl BrowserToken {
108 pub async fn from_str(cookie_str: &str, client: &Client) -> Result<Self> {
109 let cookies = cookie_str.trim().to_string();
110 let user_agent = USER_AGENT;
111 let headers = [
112 ("User-Agent", user_agent.into()),
113 ("Cookie", cookies.as_str().into()),
114 ];
115 let response = client.get_query(YTM_URL, headers, &()).await?;
116 if response.contains("Sorry, YouTube Music is not optimised for your browser. Check for updates or try Google Chrome.") {
118 return Err(Error::invalid_user_agent(user_agent));
119 };
120 let client_version = response
122 .split_once("INNERTUBE_CLIENT_VERSION\":\"")
123 .ok_or(Error::header())?
124 .1
125 .split_once('\"')
126 .ok_or(Error::header())?
127 .0
128 .to_string();
129 let sapisid = cookies
130 .split_once("SAPISID=")
131 .ok_or(Error::header())?
132 .1
133 .split_once(';')
134 .ok_or(Error::header())?
135 .0
136 .to_string();
137 Ok(Self {
138 sapisid,
139 client_version,
140 cookies,
141 })
142 }
143 pub async fn from_cookie_file<P>(path: P, client: &Client) -> Result<Self>
144 where
145 P: AsRef<Path>,
146 {
147 let contents = tokio::fs::read_to_string(path).await?;
148 BrowserToken::from_str(&contents, client).await
149 }
150}
151
152impl Debug for BrowserToken {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 write!(f, "Private BrowserToken")
157 }
158}