use crate::error::Error;
use crate::primitive::PlayParameters;
use crate::request::builder::MusicRequestBuilder;
use crate::request::context::ContextContainer;
use crate::request::paginated::paginate;
use crate::request::try_resource_response;
use crate::resource::artwork::Artwork;
use crate::resource::attributes::DescriptionAttribute;
use crate::resource::catalog::playlist::Playlist;
use crate::resource::relationship::Relationship;
use crate::resource::{ErrorResponse, Resource, ResourceHeader, ResourceInfo, ResourceType};
use crate::ApiClient;
use am_api_proc_macro::{Context, ResourceProperty};
use futures::Stream;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use time::OffsetDateTime;
#[derive(Context, Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
pub struct LibraryPlaylist {
#[context(skip)]
#[serde(flatten)]
pub header: ResourceHeader,
#[context(skip)]
#[serde(default)]
pub attributes: Option<LibraryPlaylistAttributes>,
#[serde(default)]
pub relationships: LibraryPlaylistRelationships,
}
impl LibraryPlaylist {
pub fn get<'a>() -> LibraryPlaylistGetRequestBuilder<'a> {
LibraryPlaylistGetRequestBuilder::default()
}
pub fn create<'localization>(name: &str) -> LibraryPlaylistCreateBuilder<'localization, '_> {
LibraryPlaylistCreateBuilder::new(name)
}
pub async fn add_tracks(&self, client: &ApiClient, tracks: &[&Resource]) -> Result<(), Error> {
let mut request = LibraryPlaylistAddRequest::default();
for track in tracks {
let supported = matches!(
track,
Resource::MusicVideo { .. }
| Resource::Song { .. }
| Resource::LibraryMusicVideo { .. }
| Resource::LibrarySong { .. }
);
if !supported {
return Err(Error::InvalidResourceType);
}
request.data.push(ThinRelationship {
id: track.get_header().id.clone(),
ty: track.get_type().to_string(),
});
}
let response = client
.post(&format!(
"/v1/me/library/playlists/{}/tracks",
self.header.id
))
.json(&request)
.send()
.await?;
if !response.status().is_success() {
let error_response: ErrorResponse = response.json().await?;
return Err(Error::MusicError(error_response));
}
Ok(())
}
}
#[derive(ResourceProperty, Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase", default)]
#[resource_property(
LibraryPlaylistAttributesExtension,
object = "library-playlists",
extension,
whitelist
)]
pub struct LibraryPlaylistAttributes {
pub artwork: Option<Artwork>,
pub can_edit: bool,
#[serde(with = "time::serde::iso8601::option")]
pub date_added: Option<OffsetDateTime>,
pub description: Option<DescriptionAttribute>,
pub has_catalog: bool,
pub name: String,
pub play_params: Option<PlayParameters>,
pub is_public: bool,
#[resource_property(whitelist, name = "trackTypes")]
pub track_types: Option<Vec<LibraryTrackTypes>>,
}
#[derive(
Context, ResourceProperty, Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, Hash,
)]
#[serde(rename_all = "camelCase", default)]
#[resource_property(
LibraryPlaylistRelationshipType,
object = "library-playlists",
relationship
)]
pub struct LibraryPlaylistRelationships {
pub catalog: Option<Relationship<Playlist>>,
pub tracks: Option<Relationship<Resource>>,
}
pub struct LibraryPlaylistRequestBuilder;
pub type LibraryPlaylistGetRequestBuilder<'a> =
MusicRequestBuilder<'a, LibraryPlaylistRequestBuilder>;
impl<'a> LibraryPlaylistGetRequestBuilder<'a> {
pub async fn one(
mut self,
client: &ApiClient,
id: &str,
) -> Result<Option<LibraryPlaylist>, Error> {
let request_context = Arc::new(self.get_request_context_drain(client));
let response = client
.get(&format!("/v1/me/library/playlists/{id}"))
.query(&request_context.query)
.send()
.await?;
let mut response = try_resource_response(response).await?;
response.data.set_context(request_context);
Ok(response.data.into_iter().next())
}
pub async fn many(
mut self,
client: &ApiClient,
ids: &[&str],
) -> Result<Vec<LibraryPlaylist>, Error> {
let mut request_context = self.get_request_context_drain(client);
request_context
.query
.push((String::from("ids"), ids.to_vec().join(",")));
let request_context = Arc::new(request_context);
let response = client
.get("/v1/me/library/playlists")
.query(&request_context.query)
.send()
.await?;
let mut response = try_resource_response(response).await?;
response.data.set_context(request_context);
Ok(response.data)
}
pub fn all(
mut self,
client: &ApiClient,
limit: usize,
offset: usize,
) -> impl Stream<Item = Result<LibraryPlaylist, Error>> {
let mut request_context = self.get_request_context_drain(client);
request_context
.query
.push((String::from("limit"), limit.to_string()));
paginate(
client.clone(),
String::from("/v1/me/library/playlists"),
request_context,
offset,
)
}
}
#[derive(Serialize, Clone, PartialEq, Eq)]
pub struct LibraryPlaylistCreateRequest<'a> {
attributes: LibraryPlaylistCreateRequestAttributes<'a>,
relationships: LibraryPlaylistCreateRequestRelationships,
}
#[derive(Serialize, Clone, PartialEq, Eq)]
struct LibraryPlaylistCreateRequestAttributes<'a> {
name: &'a str,
description: Option<String>,
#[serde(rename = "isPublic")]
public: bool,
}
#[derive(Serialize, Default, Clone, PartialEq, Eq)]
struct LibraryPlaylistCreateRequestRelationships {
#[serde(skip_serializing_if = "Option::is_none")]
tracks: Option<AddedTrackRelationships>,
#[serde(skip_serializing_if = "Option::is_none")]
parent: Option<ParentRelationship>,
}
#[derive(Serialize, Default, Clone, PartialEq, Eq)]
struct AddedTrackRelationships {
data: Vec<ThinRelationship>,
}
#[derive(Serialize, Clone, PartialEq, Eq)]
struct ParentRelationship {
data: [ThinRelationship; 1],
}
#[derive(Serialize, Clone, PartialEq, Eq)]
struct ThinRelationship {
id: String,
#[serde(rename = "type")]
ty: String,
}
pub type LibraryPlaylistCreateBuilder<'localization, 'name> = MusicRequestBuilder<
'localization,
LibraryPlaylistRequestBuilder,
LibraryPlaylistCreateRequest<'name>,
>;
impl<'localization, 'name> LibraryPlaylistCreateBuilder<'localization, 'name> {
pub fn new(name: &'name str) -> Self {
LibraryPlaylistCreateBuilder {
storefront_override: None,
localization_override: None,
extensions: Default::default(),
relationships: Default::default(),
views: Default::default(),
data: LibraryPlaylistCreateRequest {
attributes: LibraryPlaylistCreateRequestAttributes {
name,
description: None,
public: false,
},
relationships: Default::default(),
},
_marker: Default::default(),
}
}
pub fn description(mut self, desc: &str) -> Self {
self.data.attributes.description = Some(desc.to_string());
self
}
pub fn public(mut self, p: bool) -> Self {
self.data.attributes.public = p;
self
}
pub fn tracks(mut self, resources: &[&Resource]) -> Result<Self, Error> {
let tracks = self
.data
.relationships
.tracks
.get_or_insert_with(AddedTrackRelationships::default);
for resource in resources {
let supported = matches!(
resource,
Resource::MusicVideo { .. }
| Resource::Song { .. }
| Resource::LibraryMusicVideo { .. }
| Resource::LibrarySong { .. }
);
if !supported {
return Err(Error::InvalidResourceType);
}
let ty = resource.get_type();
tracks.data.push(ThinRelationship {
id: resource.get_header().id.clone(),
ty: ty.to_string(),
});
}
Ok(self)
}
pub fn parent_folder(mut self, parent_folder_id: &str) -> Self {
self.data.relationships.parent = Some(ParentRelationship {
data: [ThinRelationship {
id: parent_folder_id.to_string(),
ty: String::from("library-playlist-folders"),
}],
});
self
}
pub async fn create(mut self, client: &ApiClient) -> Result<Option<LibraryPlaylist>, Error> {
let request_context = Arc::new(self.get_request_context_drain(client));
let response = client
.post("/v1/me/library/playlists")
.query(&request_context.query)
.json(&self.data)
.send()
.await?;
let mut response = try_resource_response(response).await?;
response.data.set_context(request_context);
Ok(response.data.into_iter().next())
}
}
#[derive(Serialize, Default, Clone, PartialEq, Eq)]
struct LibraryPlaylistAddRequest {
data: Vec<ThinRelationship>,
}
#[derive(Context, Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
pub struct LibraryPlaylistFolder {
#[context(skip)]
#[serde(flatten)]
pub header: ResourceHeader,
#[context(skip)]
#[serde(default)]
pub attributes: Option<LibraryPlaylistFolderAttributes>,
#[serde(default)]
pub relationships: LibraryPlaylistFolderRelationships,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase", default)]
pub struct LibraryPlaylistFolderAttributes {
#[serde(with = "time::serde::iso8601::option")]
pub date_added: Option<OffsetDateTime>,
pub name: String,
}
#[derive(
Context, ResourceProperty, Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, Hash,
)]
#[serde(rename_all = "camelCase", default)]
#[resource_property(
LibraryPlaylistFolderRelationshipType,
object = "library-playlist-folders",
relationship
)]
pub struct LibraryPlaylistFolderRelationships {
pub catalog: Option<Relationship<Playlist>>,
pub tracks: Option<Relationship<Resource>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
pub enum LibraryTrackTypes {
#[serde(rename = "library-music-videos")]
MusicVideos,
#[serde(rename = "library-songs")]
Songs,
}
pub struct LibraryPlaylistFolderRequestBuilder;
pub type LibraryPlaylistFolderGetRequestBuilder<'a> =
MusicRequestBuilder<'a, LibraryPlaylistFolderRequestBuilder>;
impl<'a> LibraryPlaylistFolderGetRequestBuilder<'a> {
pub async fn one(
mut self,
client: &ApiClient,
id: &str,
) -> Result<Option<LibraryPlaylistFolder>, Error> {
let request_context = Arc::new(self.get_request_context_drain(client));
let response = client
.get(&format!("/v1/me/library/playlist-folders/{id}"))
.query(&request_context.query)
.send()
.await?;
let mut response = try_resource_response(response).await?;
response.data.set_context(request_context);
Ok(response.data.into_iter().next())
}
pub async fn many(
mut self,
client: &ApiClient,
ids: &[&str],
) -> Result<Vec<LibraryPlaylistFolder>, Error> {
let mut request_context = self.get_request_context_drain(client);
request_context
.query
.push((String::from("ids"), ids.to_vec().join(",")));
let request_context = Arc::new(request_context);
let response = client
.get("/v1/me/library/playlist-folders/")
.query(&request_context.query)
.send()
.await?;
let mut response = try_resource_response(response).await?;
response.data.set_context(request_context);
Ok(response.data)
}
}