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}