1use tokio::fs::{read_to_string, write};
2use std::time::{SystemTime, UNIX_EPOCH};
3
4use failure::{ensure, format_err, Error};
5use hmac::{Hmac, Mac};
6use oauth2::basic::BasicClient;
7use oauth2::{AuthUrl, ClientId, ClientSecret, PkceCodeVerifier, RedirectUrl, TokenUrl};
8use reqwest::header::HeaderMap;
9use reqwest::{header::AUTHORIZATION, Method, RequestBuilder, Response, StatusCode};
10use sha1::Sha1;
11use url::Url;
12
13use crate::auth::{get_oauth_url, perform_oauth, request_token};
14use crate::models::album::Album;
15use crate::models::all_playlists::Playlist;
16use crate::models::all_playlists::{GetAllPlaylistsRequest, GetAllPlaylistsResponse};
17use crate::models::all_tracks::Track;
18use crate::models::all_tracks::{GetAllTracksRequest, GetAllTracksResponse};
19use crate::models::artist::Artist;
20use crate::models::device_management_info::{
21 DeviceManagementInfo, GetDeviceManagementInfoResponse,
22};
23use crate::models::playlist_entries::PlaylistEntry;
24use crate::models::playlist_entries::{GetPlaylistEntriesRequest, GetPlaylistEntriesResponse};
25use crate::models::search_results::{SearchResultCluster, SearchResultResponse};
26use crate::token::AuthToken;
27
28static BASE_URL: &str = "https://mclients.googleapis.com/sj/v2.5/";
29static STREAM_URL: &str = "https://mclients.googleapis.com/music/mplay";
30pub static CODE_REDIRECT_URI: &str = "urn:ietf:wg:oauth:2.0:oob";
31
32#[derive(Debug, Clone)]
33pub struct GoogleMusicApi {
34 auth_token: AuthToken,
35 device_id: Option<String>,
36 client: GoogleMusicApiClient,
37}
38
39#[derive(Debug, Clone)]
40pub(crate) struct GoogleMusicApiClient {
41 pub id: String,
42 pub secret: String,
43 oauth_client: BasicClient,
44}
45
46struct Headers(Vec<(&'static str, String)>);
47
48impl Headers {
49 fn new() -> Self {
50 Headers(Vec::new())
51 }
52
53 fn append<S: Into<String>>(mut self, key: &'static str, value: S) -> Self {
54 self.0.push((key, value.into()));
55 self
56 }
57
58 fn into_inner(self) -> Vec<(&'static str, String)> {
59 self.0
60 }
61}
62
63impl GoogleMusicApi {
64 pub fn new(
65 client_id: String,
66 client_secret: String,
67 redirect_uri: Option<&str>,
68 ) -> Result<GoogleMusicApi, Error> {
69 let oauth_client = BasicClient::new(
70 ClientId::new(client_id.clone()),
71 Some(ClientSecret::new(client_secret.clone())),
72 AuthUrl::new("https://accounts.google.com/o/oauth2/v2/auth".to_string())?,
73 Some(TokenUrl::new(
74 "https://www.googleapis.com/oauth2/v3/token".to_string(),
75 )?),
76 )
77 .set_redirect_url(RedirectUrl::new(
78 redirect_uri.unwrap_or(CODE_REDIRECT_URI).to_string(),
79 )?);
80
81 Ok(GoogleMusicApi {
82 client: GoogleMusicApiClient {
83 id: client_id,
84 secret: client_secret,
85 oauth_client,
86 },
87 auth_token: AuthToken::new(),
88 device_id: None,
89 })
90 }
91
92 pub async fn login<H>(&self, handler: H) -> Result<(), Error>
111 where
112 H: Fn(String) -> String,
113 {
114 let token = perform_oauth(&self.client.oauth_client, handler).await?;
115 self.auth_token.set_token(token).await;
116 Ok(())
117 }
118
119 pub fn get_oauth_url(&self) -> (String, String) {
120 let (url, verifier) = get_oauth_url(&self.client.oauth_client);
121
122 (url, verifier.secret().clone())
123 }
124
125 pub async fn request_token(&mut self, code: String, verifier: String) -> Result<(), Error> {
126 let verifier = PkceCodeVerifier::new(verifier);
127
128 let token = request_token(&self.client.oauth_client, code, verifier).await?;
129 self.auth_token.set_token(token).await;
130
131 Ok(())
132 }
133
134 pub fn has_token(&self) -> bool {
135 self.auth_token.has_token()
136 }
137
138 pub async fn store_token(&self) -> Result<(), Error> {
142 ensure!(self.auth_token.has_token(), "No token available to persist");
143 let token = serde_json::to_string(&self.auth_token.get_token().await?)?;
144 write(".google-auth.json", token).await?; Ok(())
146 }
147
148 pub async fn load_token(&self) -> Result<(), Error> {
152 let token = read_to_string(".google-auth.json").await?;
153 let token = serde_json::from_str(&token)?;
154 self.auth_token.set_token(token).await;
155 Ok(())
156 }
157
158 pub async fn get_all_tracks(&self) -> Result<Vec<Track>, Error> {
163 let body = GetAllTracksRequest::new();
164 let url = format!("{}trackfeed", BASE_URL);
165 let res: GetAllTracksResponse = self
166 .api_post(url, &body, Headers::new(), Headers::new())
167 .await?
168 .json()
169 .await?;
170
171 Ok(res.data.items)
172 }
173
174 pub async fn get_all_playlists(&self) -> Result<Vec<Playlist>, Error> {
179 let body = GetAllPlaylistsRequest::new();
180 let url = format!("{}playlistfeed", BASE_URL);
181 let res: GetAllPlaylistsResponse = self
182 .api_post(url, &body, Headers::new(), Headers::new())
183 .await?
184 .json()
185 .await?;
186
187 Ok(res.data.items)
188 }
189
190 pub async fn get_device_management_info(&self) -> Result<Vec<DeviceManagementInfo>, Error> {
195 let url = format!("{}devicemanagementinfo", BASE_URL);
196 let res: GetDeviceManagementInfoResponse = self
197 .api_get(url, Headers::new(), Headers::new())
198 .await?
199 .json()
200 .await?;
201
202 Ok(res.data.items)
203 }
204
205 pub async fn get_playlist_entries(&self) -> Result<Vec<PlaylistEntry>, Error> {
209 let mut items = Vec::new();
210
211 let mut res = self.get_playlist_entries_page(None).await?;
212 items.append(&mut res.data.items);
213
214 let mut next_page_token = res.next_page_token;
215 while next_page_token.is_some() {
216 let mut res = self.get_playlist_entries_page(next_page_token).await?;
217 items.append(&mut res.data.items);
218 next_page_token = res.next_page_token;
219 }
220
221 Ok(items)
222 }
223
224 async fn get_playlist_entries_page(
225 &self,
226 page: Option<String>,
227 ) -> Result<GetPlaylistEntriesResponse, Error> {
228 let url = format!("{}plentryfeed", BASE_URL);
229 let request = GetPlaylistEntriesRequest {
230 max_results: Some(String::from("20000")),
231 start_token: page,
232 };
233 let mut res: GetPlaylistEntriesResponse = self
234 .api_post(url, &request, Headers::new(), Headers::new())
235 .await?
236 .json()
237 .await?;
238
239 for entry in &mut res.data.items {
240 if let Some(mut track) = entry.track.as_mut() {
241 track.id = entry.track_id.clone()
242 }
243 }
244
245 Ok(res)
246 }
247
248 pub async fn get_store_track(&self, track_id: &str) -> Result<Track, Error> {
249 ensure!(track_id.starts_with("T"), "track_id is not a store id");
250 let params = Headers::new().append("alt", "json").append("nid", track_id);
251 let url = format!("{}fetchtrack", BASE_URL);
252 let track: Track = self
253 .api_get(url, Headers::new(), params)
254 .await?
255 .json()
256 .await?;
257
258 Ok(track)
259 }
260
261 pub async fn get_album(&self, album_id: &str) -> Result<Album, Error> {
262 let params = Headers::new()
263 .append("alt", "json")
264 .append("nid", album_id)
265 .append("include-tracks", "true");
266 let url = format!("{}fetchalbum", BASE_URL);
267 let album: Album = self
268 .api_get(url, Headers::new(), params)
269 .await?
270 .json()
271 .await?;
272
273 Ok(album)
274 }
275
276 pub async fn get_artist(&self, artist_id: &str) -> Result<Artist, Error> {
277 let params = Headers::new()
278 .append("alt", "json")
279 .append("nid", artist_id)
280 .append("include-albums", "true")
281 .append("num-top-tracks", "20");
282 let url = format!("{}fetchartist", BASE_URL);
283 let artist: Artist = self
284 .api_get(url, Headers::new(), params)
285 .await?
286 .json()
287 .await?;
288
289 Ok(artist)
290 }
291
292 pub async fn get_stream_url(&self, id: &str, device_id: &str) -> Result<Url, Error> {
298 let (sig, salt) = GoogleMusicApi::get_signature(id)?;
299 let mut params = Headers::new()
300 .append("opt", "hi")
301 .append("net", "mob")
302 .append("pt", "e")
303 .append("slt", &salt)
304 .append("sig", &sig);
305 if id.starts_with("T") {
306 params = params.append("mjck", id);
307 } else {
308 params = params.append("songid", id);
309 }
310 let headers = Headers::new().append("X-Device-ID", device_id);
311 let res = self.api_get(STREAM_URL, headers, params).await?;
312
313 Ok(res.url().clone())
314 }
315
316 fn get_signature(id: &str) -> Result<(String, String), Error> {
317 let key_1 = base64::decode("VzeC4H4h+T2f0VI180nVX8x+Mb5HiTtGnKgH52Otj8ZCGDz9jRWyHb6QXK0JskSiOgzQfwTY5xgLLSdUSreaLVMsVVWfxfa8Rw==")?;
318 let key_2 = base64::decode("ZAPnhUkYwQ6y5DdQxWThbvhJHN8msQ1rqJw0ggKdufQjelrKuiGGJI30aswkgCWTDyHkTGK9ynlqTkJ5L4CiGGUabGeo8M6JTQ==")?;
319
320 let key: Vec<u8> = key_1.iter().zip(key_2.iter()).map(|(a, b)| a ^ b).collect();
321
322 let salt = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs_f64() * 1000f64;
323 let salt = salt.floor();
324 let salt = format!("{}", salt);
325
326 let mut mac = Hmac::<Sha1>::new_varkey(&key)
327 .map_err(|err| format_err!("Invalid key length {:?}", err))?;
328 mac.input(id.as_bytes());
329 mac.input(salt.as_bytes());
330
331 let signature = base64::encode(&mac.result().code())
333 .replace("+", "-")
334 .replace("/", "_")
335 .replace("=", ".");
336
337 Ok((signature, salt.to_string()))
338 }
339
340 pub async fn search(
341 &self,
342 query: &str,
343 max_results: Option<u64>,
344 ) -> Result<Vec<SearchResultCluster>, Error> {
345 let url = format!("{}query", BASE_URL);
346 let max_results = max_results.unwrap_or(50);
347 let max_results = format!("{}", max_results);
348 let params = Headers::new()
349 .append("ct", "1,2,3,4,5,6,7,8,9")
350 .append("ic", "true")
351 .append("q", query)
352 .append("max-results", &max_results);
353 let res: SearchResultResponse = self
354 .api_get(url, Headers::new(), params)
355 .await?
356 .json()
357 .await?;
358
359 Ok(res.cluster_detail)
360 }
361
362 async fn api_get<S: Into<String>>(
363 &self,
364 url: S,
365 headers: Headers,
366 params: Headers,
367 ) -> Result<Response, Error> {
368 self.request::<()>(url.into(), Method::GET, None, headers, params)
369 .await
370 }
371
372 async fn api_post<S: Into<String>, B>(
373 &self,
374 url: S,
375 body: &B,
376 headers: Headers,
377 params: Headers,
378 ) -> Result<Response, Error>
379 where
380 B: serde::Serialize,
381 {
382 self.request(url.into(), Method::POST, Some(body), headers, params)
383 .await
384 }
385
386 async fn request<'a, B>(
387 &self,
388 url: String,
389 method: Method,
390 body: Option<&B>,
391 headers: Headers,
392 params: Headers,
393 ) -> Result<Response, Error>
394 where
395 B: serde::Serialize,
396 {
397 if self.auth_token.requires_new_token().await {
398 self.auth_token.refresh(&self.client.oauth_client).await?;
399 }
400 let client = reqwest::Client::new();
401 let mut url = Url::parse(&url)?;
402 {
403 let mut query_pairs = url.query_pairs_mut();
404 for (key, value) in GoogleMusicApi::default_params() {
405 query_pairs.append_pair(key, value);
406 }
407 for (key, value) in params.into_inner() {
408 query_pairs.append_pair(key, value.as_str());
409 }
410 }
411 let mut req = client.request(method, url);
412 let mut header_map = HeaderMap::new();
413 for (key, value) in headers.into_inner() {
414 header_map.insert(key, value.parse()?);
415 }
416 req = req.headers(header_map);
417 if let Some(body) = body {
418 req = req.json(&body);
419 }
420 let res = req
421 .try_clone()
422 .unwrap()
423 .header(AUTHORIZATION, self.auth_token.get_auth_header().await?)
424 .send()
425 .await?
426 .error_for_status();
427 if let Err(err) = res {
428 self.retry_request(req, err).await
429 } else {
430 let res = res.unwrap();
431 Ok(res)
432 }
433 }
434
435 async fn retry_request(
436 &self,
437 req: RequestBuilder,
438 err: reqwest::Error,
439 ) -> Result<Response, Error> {
440 if let Some(StatusCode::UNAUTHORIZED) = err.status() {
441 self.auth_token.refresh(&self.client.oauth_client).await?;
442 let res = req
443 .header(AUTHORIZATION, self.auth_token.get_auth_header().await?)
444 .send()
445 .await?
446 .error_for_status()?;
447 Ok(res)
448 } else {
449 Err(err.into())
450 }
451 }
452
453 fn default_params() -> Vec<(&'static str, &'static str)> {
454 vec![("dv", "0"), ("hl", "en_US"), ("tier", "aa")]
455 }
456}