From 70eb3f9d729e9eef072316697e124566eb5e7f7e Mon Sep 17 00:00:00 2001 From: Roderick van Domburg Date: Wed, 3 Aug 2022 21:27:07 +0200 Subject: [PATCH] Add more HTTP endpoints and migrate `playlist` --- core/src/spclient.rs | 83 ++++++++++++++++++++++++++++++++++- core/src/spotify_id.rs | 2 +- metadata/src/playlist/list.rs | 56 +---------------------- 3 files changed, 85 insertions(+), 56 deletions(-) diff --git a/core/src/spclient.rs b/core/src/spclient.rs index 2f98eb39..479c86e0 100644 --- a/core/src/spclient.rs +++ b/core/src/spclient.rs @@ -386,7 +386,7 @@ impl SpClient { } pub async fn get_lyrics(&self, track_id: SpotifyId) -> SpClientResult { - let endpoint = format!("/color-lyrics/v1/track/{}", track_id.to_base62()?); + let endpoint = format!("/color-lyrics/v2/track/{}", track_id.to_base62()?); self.request_as_json(&Method::GET, &endpoint, None, None) .await @@ -407,6 +407,87 @@ impl SpClient { .await } + pub async fn get_playlist(&self, playlist_id: SpotifyId) -> SpClientResult { + let endpoint = format!("/playlist/v2/playlist/{}", playlist_id); + + self.request(&Method::GET, &endpoint, None, None).await + } + + pub async fn get_user_profile( + &self, + username: String, + playlist_limit: Option, + artist_limit: Option, + ) -> SpClientResult { + let mut endpoint = format!("/user-profile-view/v3/profile/{}", username); + + if playlist_limit.is_some() || artist_limit.is_some() { + let _ = write!(endpoint, "?"); + + if let Some(limit) = playlist_limit { + let _ = write!(endpoint, "playlist_limit={}", limit); + if artist_limit.is_some() { + let _ = write!(endpoint, "&"); + } + } + + if let Some(limit) = artist_limit { + let _ = write!(endpoint, "artist_limit={}", limit); + } + } + + self.request_as_json(&Method::GET, &endpoint, None, None) + .await + } + + pub async fn get_user_followers(&self, username: String) -> SpClientResult { + let endpoint = format!("/user-profile-view/v3/profile/{}/followers", username); + + self.request_as_json(&Method::GET, &endpoint, None, None) + .await + } + + pub async fn get_user_following(&self, username: String) -> SpClientResult { + let endpoint = format!("/user-profile-view/v3/profile/{}/following", username); + + self.request_as_json(&Method::GET, &endpoint, None, None) + .await + } + + pub async fn get_radio_for_track(&self, track_id: SpotifyId) -> SpClientResult { + let endpoint = format!( + "/inspiredby-mix/v2/seed_to_playlist/{}?response-format=json", + track_id.to_uri()? + ); + + self.request_as_json(&Method::GET, &endpoint, None, None) + .await + } + + pub async fn get_apollo_station( + &self, + context: SpotifyId, + count: u32, + previous_tracks: Vec, + autoplay: bool, + ) -> SpClientResult { + let previous_track_str = previous_tracks + .iter() + .map(|track| track.to_uri()) + .collect::, _>>()? + .join(","); + let endpoint = format!( + "/radio-apollo/v3/stations/{}?count={}&prev_tracks={}&autoplay={}", + context.to_uri()?, + count, + previous_track_str, + autoplay, + ); + + self.request_as_json(&Method::GET, &endpoint, None, None) + .await + } + // TODO: Find endpoint for newer canvas.proto and upgrade to that. pub async fn get_canvases(&self, request: EntityCanvazRequest) -> SpClientResult { let endpoint = "/canvaz-cache/v0/canvases"; diff --git a/core/src/spotify_id.rs b/core/src/spotify_id.rs index 227fa526..66e3ac36 100644 --- a/core/src/spotify_id.rs +++ b/core/src/spotify_id.rs @@ -163,7 +163,7 @@ impl SpotifyId { /// can be arbitrary while `{id}` is a 22-character long, base62 encoded Spotify ID. /// /// Note that this should not be used for playlists, which have the form of - /// `spotify:user:{owner_username}:playlist:{id}`. + /// `spotify:playlist:{id}`. /// /// [Spotify URI]: https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids pub fn from_uri(src: &str) -> SpotifyIdResult { diff --git a/metadata/src/playlist/list.rs b/metadata/src/playlist/list.rs index 300c0c09..414e1fda 100644 --- a/metadata/src/playlist/list.rs +++ b/metadata/src/playlist/list.rs @@ -4,10 +4,8 @@ use std::{ ops::{Deref, DerefMut}, }; -use protobuf::Message; - use crate::{ - request::{MercuryRequest, RequestResult}, + request::RequestResult, util::{impl_deref_wrapped, impl_from_repeated_copy, impl_try_from_repeated}, Metadata, }; @@ -55,11 +53,6 @@ pub struct Playlists(pub Vec); impl_deref_wrapped!(Playlists, Vec); -#[derive(Debug, Clone)] -pub struct RootPlaylist(pub SelectedListContent); - -impl_deref_wrapped!(RootPlaylist, SelectedListContent); - #[derive(Debug, Clone)] pub struct SelectedListContent { pub revision: Vec, @@ -80,31 +73,6 @@ pub struct SelectedListContent { } impl Playlist { - #[allow(dead_code)] - async fn request_for_user( - session: &Session, - username: &str, - playlist_id: SpotifyId, - ) -> RequestResult { - let uri = format!( - "hm://playlist/user/{}/playlist/{}", - username, - playlist_id.to_base62()? - ); - ::request(session, &uri).await - } - - #[allow(dead_code)] - pub async fn get_for_user( - session: &Session, - username: &str, - playlist_id: SpotifyId, - ) -> Result { - let response = Self::request_for_user(session, username, playlist_id).await?; - let msg = ::Message::parse_from_bytes(&response)?; - Self::parse(&msg, playlist_id) - } - pub fn tracks(&self) -> impl ExactSizeIterator { let tracks = self.contents.items.iter().map(|item| &item.id); @@ -125,15 +93,12 @@ impl Playlist { } } -impl MercuryRequest for Playlist {} - #[async_trait] impl Metadata for Playlist { type Message = protocol::playlist4_external::SelectedListContent; async fn request(session: &Session, playlist_id: SpotifyId) -> RequestResult { - let uri = format!("hm://playlist/v2/playlist/{}", playlist_id.to_base62()?); - ::request(session, &uri).await + session.spclient().get_playlist(playlist_id).await } fn parse(msg: &Self::Message, id: SpotifyId) -> Result { @@ -161,23 +126,6 @@ impl Metadata for Playlist { } } -impl MercuryRequest for RootPlaylist {} - -impl RootPlaylist { - #[allow(dead_code)] - async fn request_for_user(session: &Session, username: &str) -> RequestResult { - let uri = format!("hm://playlist/user/{}/rootlist", username,); - ::request(session, &uri).await - } - - #[allow(dead_code)] - pub async fn get_root_for_user(session: &Session, username: &str) -> Result { - let response = Self::request_for_user(session, username).await?; - let msg = protocol::playlist4_external::SelectedListContent::parse_from_bytes(&response)?; - Ok(Self(SelectedListContent::try_from(&msg)?)) - } -} - impl TryFrom<&::Message> for SelectedListContent { type Error = librespot_core::Error; fn try_from(playlist: &::Message) -> Result {