Add more HTTP endpoints and migrate playlist

This commit is contained in:
Roderick van Domburg 2022-08-03 21:27:07 +02:00
parent 922e927231
commit 70eb3f9d72
No known key found for this signature in database
GPG key ID: 87F5FDE8A56219F4
3 changed files with 85 additions and 56 deletions

View file

@ -386,7 +386,7 @@ impl SpClient {
} }
pub async fn get_lyrics(&self, track_id: SpotifyId) -> SpClientResult { 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) self.request_as_json(&Method::GET, &endpoint, None, None)
.await .await
@ -407,6 +407,87 @@ impl SpClient {
.await .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<u32>,
artist_limit: Option<u32>,
) -> 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<SpotifyId>,
autoplay: bool,
) -> SpClientResult {
let previous_track_str = previous_tracks
.iter()
.map(|track| track.to_uri())
.collect::<Result<Vec<_>, _>>()?
.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. // TODO: Find endpoint for newer canvas.proto and upgrade to that.
pub async fn get_canvases(&self, request: EntityCanvazRequest) -> SpClientResult { pub async fn get_canvases(&self, request: EntityCanvazRequest) -> SpClientResult {
let endpoint = "/canvaz-cache/v0/canvases"; let endpoint = "/canvaz-cache/v0/canvases";

View file

@ -163,7 +163,7 @@ impl SpotifyId {
/// can be arbitrary while `{id}` is a 22-character long, base62 encoded Spotify ID. /// 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 /// 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 /// [Spotify URI]: https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids
pub fn from_uri(src: &str) -> SpotifyIdResult { pub fn from_uri(src: &str) -> SpotifyIdResult {

View file

@ -4,10 +4,8 @@ use std::{
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
}; };
use protobuf::Message;
use crate::{ use crate::{
request::{MercuryRequest, RequestResult}, request::RequestResult,
util::{impl_deref_wrapped, impl_from_repeated_copy, impl_try_from_repeated}, util::{impl_deref_wrapped, impl_from_repeated_copy, impl_try_from_repeated},
Metadata, Metadata,
}; };
@ -55,11 +53,6 @@ pub struct Playlists(pub Vec<SpotifyId>);
impl_deref_wrapped!(Playlists, Vec<SpotifyId>); impl_deref_wrapped!(Playlists, Vec<SpotifyId>);
#[derive(Debug, Clone)]
pub struct RootPlaylist(pub SelectedListContent);
impl_deref_wrapped!(RootPlaylist, SelectedListContent);
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SelectedListContent { pub struct SelectedListContent {
pub revision: Vec<u8>, pub revision: Vec<u8>,
@ -80,31 +73,6 @@ pub struct SelectedListContent {
} }
impl Playlist { 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()?
);
<Self as MercuryRequest>::request(session, &uri).await
}
#[allow(dead_code)]
pub async fn get_for_user(
session: &Session,
username: &str,
playlist_id: SpotifyId,
) -> Result<Self, Error> {
let response = Self::request_for_user(session, username, playlist_id).await?;
let msg = <Self as Metadata>::Message::parse_from_bytes(&response)?;
Self::parse(&msg, playlist_id)
}
pub fn tracks(&self) -> impl ExactSizeIterator<Item = &SpotifyId> { pub fn tracks(&self) -> impl ExactSizeIterator<Item = &SpotifyId> {
let tracks = self.contents.items.iter().map(|item| &item.id); let tracks = self.contents.items.iter().map(|item| &item.id);
@ -125,15 +93,12 @@ impl Playlist {
} }
} }
impl MercuryRequest for Playlist {}
#[async_trait] #[async_trait]
impl Metadata for Playlist { impl Metadata for Playlist {
type Message = protocol::playlist4_external::SelectedListContent; type Message = protocol::playlist4_external::SelectedListContent;
async fn request(session: &Session, playlist_id: SpotifyId) -> RequestResult { async fn request(session: &Session, playlist_id: SpotifyId) -> RequestResult {
let uri = format!("hm://playlist/v2/playlist/{}", playlist_id.to_base62()?); session.spclient().get_playlist(playlist_id).await
<Self as MercuryRequest>::request(session, &uri).await
} }
fn parse(msg: &Self::Message, id: SpotifyId) -> Result<Self, Error> { fn parse(msg: &Self::Message, id: SpotifyId) -> Result<Self, Error> {
@ -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,);
<Self as MercuryRequest>::request(session, &uri).await
}
#[allow(dead_code)]
pub async fn get_root_for_user(session: &Session, username: &str) -> Result<Self, Error> {
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<&<Playlist as Metadata>::Message> for SelectedListContent { impl TryFrom<&<Playlist as Metadata>::Message> for SelectedListContent {
type Error = librespot_core::Error; type Error = librespot_core::Error;
fn try_from(playlist: &<Playlist as Metadata>::Message) -> Result<Self, Self::Error> { fn try_from(playlist: &<Playlist as Metadata>::Message) -> Result<Self, Self::Error> {