1use super::private::Sealed;
2use super::AuthToken;
3use crate::client::Client;
4use crate::error::{Error, Result};
5use crate::parse::ProcessedResult;
6use crate::process::RawResult;
7use crate::query::{GetQuery, PostQuery};
8use crate::{
9 query::Query,
10 utils::constants::{
11 OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_CODE_URL, OAUTH_GRANT_URL, OAUTH_SCOPE,
12 OAUTH_TOKEN_URL, OAUTH_USER_AGENT, USER_AGENT, YTM_API_URL, YTM_PARAMS, YTM_PARAMS_KEY,
13 YTM_URL,
14 },
15};
16use reqwest::Url;
17use serde::{Deserialize, Serialize};
18use serde_json::json;
19use std::time::{SystemTime, UNIX_EPOCH};
20
21#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
26pub struct OAuthToken {
27 token_type: String,
28 access_token: String,
29 refresh_token: String,
30 expires_in: usize,
31 request_time: SystemTime,
32}
33#[derive(Clone, Deserialize)]
35pub struct OAuthDeviceCode(String);
36
37#[derive(Clone, Deserialize)]
38struct GoogleOAuthToken {
39 pub access_token: String,
40 pub expires_in: usize,
41 pub refresh_token: String,
42 #[allow(dead_code)]
44 pub scope: String,
45 pub token_type: String,
46}
47#[derive(Clone, Deserialize)]
48struct GoogleOAuthRefreshToken {
49 pub access_token: String,
50 pub expires_in: usize,
51 #[allow(dead_code)]
53 pub scope: String,
54 pub token_type: String,
55}
56#[derive(Clone, Deserialize)]
57pub struct OAuthTokenGenerator {
58 pub device_code: OAuthDeviceCode,
59 pub expires_in: usize,
60 pub interval: usize,
61 pub user_code: String,
62 pub verification_url: String,
63}
64
65impl OAuthToken {
66 fn from_google_refresh_token(
67 google_token: GoogleOAuthRefreshToken,
68 request_time: SystemTime,
69 refresh_token: String,
70 ) -> Self {
71 let GoogleOAuthRefreshToken {
73 access_token,
74 expires_in,
75 token_type,
76 ..
77 } = google_token;
78 Self {
79 token_type,
80 refresh_token,
81 access_token,
82 request_time,
83 expires_in,
84 }
85 }
86 fn from_google_token(google_token: GoogleOAuthToken, request_time: SystemTime) -> Self {
87 let GoogleOAuthToken {
89 access_token,
90 expires_in,
91 token_type,
92 refresh_token,
93 ..
94 } = google_token;
95 Self {
96 token_type,
97 refresh_token,
98 access_token,
99 request_time,
100 expires_in,
101 }
102 }
103}
104
105impl OAuthDeviceCode {
106 pub fn new(code: String) -> Self {
107 Self(code)
108 }
109 pub fn get_code(&self) -> &str {
110 &self.0
111 }
112}
113
114impl Sealed for OAuthToken {}
115impl AuthToken for OAuthToken {
116 async fn raw_query_post<'a, Q: PostQuery + Query<Self>>(
117 &self,
118 client: &Client,
119 query: &'a Q,
120 ) -> Result<RawResult<'a, Q, OAuthToken>> {
121 let url = format!("{YTM_API_URL}{}{YTM_PARAMS}{YTM_PARAMS_KEY}", query.path());
123 let now_datetime: chrono::DateTime<chrono::Utc> = SystemTime::now().into();
124 let client_version = format!("1.{}.01.00", now_datetime.format("%Y%m%d"));
125 let mut body = json!({
126 "context" : {
127 "client" : {
128 "clientName" : "WEB_REMIX",
129 "clientVersion" : client_version,
130 },
131 },
132 });
133 if let Some(body) = body.as_object_mut() {
134 body.append(&mut query.header());
135 } else {
136 unreachable!("Body created in this function as an object")
137 };
138 let request_time_unix = self.request_time.duration_since(UNIX_EPOCH)?.as_secs();
139 let now_unix = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
140 if now_unix + 3600 > request_time_unix + self.expires_in as u64 {
142 return Err(Error::oauth_token_expired(self));
143 }
144 let headers = [
145 ("User-Agent", USER_AGENT.into()),
147 ("X-Origin", YTM_URL.into()),
148 ("Content-Type", "application/json".into()),
149 (
150 "Authorization",
151 format!("{} {}", self.token_type, self.access_token).into(),
152 ),
153 ("X-Goog-Request-Time", request_time_unix.to_string().into()),
154 ];
155 let result = client
156 .post_query(url, headers, &body, &query.params())
157 .await?;
158 let result = RawResult::from_raw(result, query);
159 Ok(result)
160 }
161 async fn raw_query_get<'a, Q: GetQuery + Query<Self>>(
162 &self,
163 client: &Client,
164 query: &'a Q,
165 ) -> Result<RawResult<'a, Q, Self>> {
166 let url = Url::parse_with_params(query.url(), query.params())
168 .map_err(|e| Error::web(format!("{e}")))?;
169 let request_time_unix = self.request_time.duration_since(UNIX_EPOCH)?.as_secs();
170 let now_unix = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
171 if now_unix + 3600 > request_time_unix + self.expires_in as u64 {
173 return Err(Error::oauth_token_expired(self));
174 }
175 let headers = [
176 ("User-Agent", USER_AGENT.into()),
178 ("X-Origin", YTM_URL.into()),
179 ("Content-Type", "application/json".into()),
180 (
181 "Authorization",
182 format!("{} {}", self.token_type, self.access_token).into(),
183 ),
184 ("X-Goog-Request-Time", request_time_unix.to_string().into()),
185 ];
186 let result = client.get_query(url, headers, &query.params()).await?;
187 let result = RawResult::from_raw(result, query);
188 Ok(result)
189 }
190 fn deserialize_json<Q: Query<Self>>(
191 raw: RawResult<Q, Self>,
192 ) -> Result<crate::parse::ProcessedResult<Q>> {
193 let processed = ProcessedResult::try_from(raw)?;
194 if let Some(error) = processed.get_json().pointer("/error") {
197 let Some(code) = error.pointer("/code").and_then(|v| v.as_u64()) else {
198 return Err(Error::response("API reported an error but no code"));
200 };
201 let message = error
202 .pointer("/message")
203 .and_then(|s| s.as_str())
204 .map(|s| s.to_string())
205 .unwrap_or_default();
206 return Err(Error::other_code(code, message));
208 }
209 Ok(processed)
210 }
211}
212
213impl OAuthToken {
214 pub async fn from_code(client: &Client, code: OAuthDeviceCode) -> Result<OAuthToken> {
215 let body = json!({
216 "client_secret" : OAUTH_CLIENT_SECRET,
217 "grant_type" : OAUTH_GRANT_URL,
218 "code": code.get_code(),
219 "client_id" : OAUTH_CLIENT_ID
220 });
221 let headers = [("User-Agent", OAUTH_USER_AGENT.into())];
222 let result = client
223 .post_query(OAUTH_TOKEN_URL, headers, &body, &())
224 .await?;
225 let google_token: GoogleOAuthToken =
226 serde_json::from_str(&result).map_err(|_| Error::response(&result))?;
227 Ok(OAuthToken::from_google_token(
228 google_token,
229 SystemTime::now(),
230 ))
231 }
232 pub async fn refresh(&self, client: &Client) -> Result<OAuthToken> {
233 let body = json!({
234 "client_secret" : OAUTH_CLIENT_SECRET,
235 "grant_type" : "refresh_token",
236 "refresh_token" : self.refresh_token,
237 "client_id" : OAUTH_CLIENT_ID,
238 });
239 let headers = [("User-Agent", OAUTH_USER_AGENT.into())];
240 let result = client
241 .post_query(OAUTH_TOKEN_URL, headers, &body, &())
242 .await?;
243 let google_token: GoogleOAuthRefreshToken = serde_json::from_str(&result)
244 .map_err(|e| Error::unable_to_serialize_oauth(&result, e))?;
245 Ok(OAuthToken::from_google_refresh_token(
246 google_token,
247 SystemTime::now(),
248 self.refresh_token.clone(),
250 ))
251 }
252}
253
254impl OAuthTokenGenerator {
255 pub async fn new(client: &Client) -> Result<OAuthTokenGenerator> {
256 let body = json!({
257 "scope" : OAUTH_SCOPE,
258 "client_id" : OAUTH_CLIENT_ID
259 });
260 let headers = [("User-Agent", OAUTH_USER_AGENT.into())];
261 let result = client
262 .post_query(OAUTH_CODE_URL, headers, &body, &())
263 .await?;
264 serde_json::from_str(&result).map_err(|_| Error::response(&result))
265 }
266}
267impl std::fmt::Debug for OAuthToken {
271 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
272 write!(f, "Private BrowserToken")
273 }
274}