diff options
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | examples/playlists/main.rs (renamed from examples/access_token/main.rs) | 13 | ||||
| -rw-r--r-- | src/auth/mod.rs | 1 | ||||
| -rw-r--r-- | src/client.rs | 133 | ||||
| -rw-r--r-- | src/creator.rs | 13 | ||||
| -rw-r--r-- | src/errors/client.rs | 28 | ||||
| -rw-r--r-- | src/errors/mod.rs | 1 | ||||
| -rw-r--r-- | src/lib.rs | 4 | ||||
| -rw-r--r-- | src/playlist.rs | 166 | ||||
| -rw-r--r-- | src/user.rs | 153 | 
10 files changed, 494 insertions, 20 deletions
| @@ -7,8 +7,10 @@ edition = "2021"  actix-web = { version = "4.1.0", default-features = false, features = ["macros"] }  tokio = { version = "1.21.0", features = ["macros", "rt-multi-thread"] }  serde = { version = "1.0.144", features = ["derive"] } +serde_json = "1.0.85"  reqwest = { version = "0.11.11", features = ["json"] }  thiserror = "1.0.33" +hyper = "0.14.20"  [dev_dependencies]  config = "0.13.2" diff --git a/examples/access_token/main.rs b/examples/playlists/main.rs index d9b4d79..fed5b73 100644 --- a/examples/access_token/main.rs +++ b/examples/playlists/main.rs @@ -3,6 +3,7 @@ use std::error::Error;  use anyhow::Context;  use config::Config;  use deez::auth::{request_access_token, AuthPromptHandler}; +use deez::client::DeezerClient;  use serde::Deserialize;  #[derive(Deserialize)] @@ -19,7 +20,7 @@ struct Settings  async fn main() -> Result<(), Box<dyn Error + Send + Sync>>  {      let settings = Config::builder() -        .add_source(config::File::with_name("examples/access_token/Settings")) +        .add_source(config::File::with_name("examples/playlists/Settings"))          .build()          .with_context(|| "Failed to read settings")?          .try_deserialize::<Settings>() @@ -48,5 +49,15 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>>          access_token.expires.as_secs()      ); +    let client = DeezerClient::new(access_token); + +    let me = client.get_me().await?; + +    println!("{:#?}", me); + +    let playlists = client.get_user_playlists(me.id).await?; + +    println!("Playlists: {:#?}", playlists); +      Ok(())  } diff --git a/src/auth/mod.rs b/src/auth/mod.rs index c5beeb3..fae0383 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -96,6 +96,7 @@ impl AuthPromptHandler  }  /// A Deezer access token. +#[derive(Debug)]  pub struct AccessToken  {      /// The access token. diff --git a/src/client.rs b/src/client.rs index 5c6858b..0699545 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,50 +1,145 @@  //! Deezer client. -use std::error::Error; +use std::fmt::Debug; +use hyper::client::{Client, HttpConnector}; +use hyper::Uri;  use serde::Deserialize; -/// A user. +use crate::auth::AccessToken; +use crate::errors::client::DeezerClientError; +use crate::playlist::Playlist; +use crate::user::{User, UserPlaylist, UserPlaylists}; + +/// Deezer API error.  #[derive(Debug, Deserialize)] -pub struct User +pub struct DeezerError  { -    /// The user ID. -    pub id: u32, +    /// Error type. +    #[serde(rename = "type")] +    pub err_type: String, + +    /// Error message. +    pub message: String, -    /// The user name. -    pub name: String, +    /// Error code. +    pub code: u32, +} + +#[derive(Debug, Deserialize)] +struct ErrorResponseBody +{ +    error: DeezerError,  }  /// Deezer client. -#[derive(Default)]  pub struct DeezerClient  { -    client: reqwest::Client, +    client: Client<HttpConnector>,      api_url: &'static str, +    access_token: AccessToken,  }  impl DeezerClient  {      /// Creates a new Deezer client.      #[must_use] -    pub fn new() -> Self +    pub fn new(access_token: AccessToken) -> Self      {          Self { -            client: reqwest::Client::new(), -            api_url: "https://api.deezer.com", +            client: Client::new(), +            api_url: "api.deezer.com", +            access_token,          }      }      /// Returns the authenticated user. -    pub async fn get_me(&self) -> Result<User, Box<dyn Error + Send + Sync>> +    /// +    /// # Errors +    /// Will return Err if either sending the request or parsing the response fails. +    pub async fn get_me(&self) -> Result<User, DeezerClientError> +    { +        let response = self +            .client +            .get(self.build_endpoint_uri(&"user/me".to_string())?) +            .await?; + +        let body_buf = &*hyper::body::to_bytes(response).await?; + +        let err_body_result: Result<ErrorResponseBody, _> = +            serde_json::from_slice(body_buf); + +        if let Ok(err_body) = err_body_result { +            return Err(DeezerClientError::ReceivedErrorResponse(err_body.error)); +        } + +        serde_json::from_slice(body_buf).map_err(DeezerClientError::ParseResponseFailed) +    } + +    /// Returns the playlists of a user. +    /// +    /// # Errors +    /// Will return Err if either sending the request or parsing the response fails. +    pub async fn get_user_playlists( +        &self, +        user_id: u64, +    ) -> Result<Vec<UserPlaylist>, DeezerClientError> +    { +        let response = self +            .client +            .get(self.build_endpoint_uri(&format!("user/{}/playlists", user_id))?) +            .await?; + +        let body_buf = &*hyper::body::to_bytes(response).await?; + +        let err_body_result: Result<ErrorResponseBody, _> = +            serde_json::from_slice(body_buf); + +        if let Ok(err_body) = err_body_result { +            return Err(DeezerClientError::ReceivedErrorResponse(err_body.error)); +        } + +        let user_playlists: UserPlaylists = serde_json::from_slice(body_buf) +            .map_err(DeezerClientError::ParseResponseFailed)?; + +        Ok(user_playlists.data) +    } + +    /// Returns a playlist. +    /// +    /// # Errors +    /// Will return Err if either sending the request or parsing the response fails. +    pub async fn get_playlist( +        &self, +        playlist_id: u64, +    ) -> Result<Playlist, DeezerClientError>      { -        let user = self +        let response = self              .client -            .get(format!("{}/user/me", self.api_url)) -            .send() -            .await? -            .json::<User>() +            .get(self.build_endpoint_uri(&format!("playlist/{}", playlist_id))?)              .await?; -        Ok(user) +        let body_buf = &*hyper::body::to_bytes(response).await?; + +        let err_body_result: Result<ErrorResponseBody, _> = +            serde_json::from_slice(body_buf); + +        if let Ok(err_body) = err_body_result { +            return Err(DeezerClientError::ReceivedErrorResponse(err_body.error)); +        } + +        serde_json::from_slice(body_buf).map_err(DeezerClientError::ParseResponseFailed) +    } + +    fn build_endpoint_uri(&self, endpoint: &String) -> Result<Uri, DeezerClientError> +    { +        Uri::builder() +            .scheme("http") +            .authority(self.api_url) +            .path_and_query(format!( +                "/{}?access_token={}", +                endpoint, self.access_token.access_token +            )) +            .build() +            .map_err(|_| DeezerClientError::BuildAPIEndpointURIFailed)      }  } diff --git a/src/creator.rs b/src/creator.rs new file mode 100644 index 0000000..81e3797 --- /dev/null +++ b/src/creator.rs @@ -0,0 +1,13 @@ +//! Creator. +use serde::Deserialize; + +/// A user that created another object. +#[derive(Debug, Deserialize)] +pub struct Creator +{ +    /// User ID. +    pub id: u64, + +    /// User name. +    pub name: String, +} diff --git a/src/errors/client.rs b/src/errors/client.rs new file mode 100644 index 0000000..c3397e3 --- /dev/null +++ b/src/errors/client.rs @@ -0,0 +1,28 @@ +//! Error types for [`DeezerClient`]. + +use crate::client::DeezerError; + +/// Error type for [`DeezerClient`]. +#[derive(Debug, thiserror::Error)] +pub enum DeezerClientError +{ +    /// Failed to send a HTTP request. +    #[error("Failed to send HTTP request")] +    SendRequestFailed(#[from] hyper::Error), + +    /// Failed to parse a response from the Deezer API. +    #[error("Failed to parse response from the Deezer API")] +    ParseResponseFailed(#[from] serde_json::Error), + +    /// Received a error response from the Deezer API. +    #[error("Received a error response from the Deezer API")] +    ReceivedErrorResponse(DeezerError), + +    /// Failed to build API endpoint URI. +    #[error("Failed to build API endpoint URI")] +    BuildAPIEndpointURIFailed, + +    /// Received a invalid response body from the Deezer API. +    #[error("Received a invalid response body from the Deezer API")] +    InvalidResponseBody, +} diff --git a/src/errors/mod.rs b/src/errors/mod.rs index ea85f4a..ad4da47 100644 --- a/src/errors/mod.rs +++ b/src/errors/mod.rs @@ -1,3 +1,4 @@  //! Error types.  pub mod auth; +pub mod client; @@ -5,4 +5,8 @@  #![allow(clippy::module_name_repetitions)]  pub mod auth; +pub mod client; +pub mod creator;  pub mod errors; +pub mod playlist; +pub mod user; diff --git a/src/playlist.rs b/src/playlist.rs new file mode 100644 index 0000000..b918282 --- /dev/null +++ b/src/playlist.rs @@ -0,0 +1,166 @@ +//! Playlist. +use serde::Deserialize; + +use crate::creator::Creator; + +/// A playlist. +#[derive(Debug, Deserialize)] +pub struct Playlist +{ +    /// The playlist's Deezer id. +    pub id: u64, + +    /// The playlist's title. +    pub title: String, + +    /// The playlist description. +    pub description: String, + +    /// The playlist's duration (seconds). +    pub duration: u64, + +    /// If the playlist is public or not. +    pub public: bool, + +    /// If the playlist is the love tracks playlist. +    pub is_loved_track: bool, + +    /// If the playlist is collaborative or not. +    pub collaborative: bool, + +    /// Nb tracks in the playlist. +    pub nb_tracks: u64, + +    /// Nb tracks not seen. +    pub unseen_track_count: Option<u64>, + +    /// The number of playlist's fans. +    pub fans: u64, + +    /// The url of the playlist on Deezer. +    pub link: String, + +    /// The share link of the playlist on Deezer. +    pub share: String, + +    /// The url of the playlist's cover. Add 'size' parameter to the url to change size. +    /// Can be 'small', 'medium', 'big', 'xl'. +    pub picture: String, + +    /// The url of the playlist's cover in size small. +    pub picture_small: String, + +    /// The url of the playlist's cover in size medium. +    pub picture_medium: String, + +    /// The url of the playlist's cover in size big. +    pub picture_big: String, + +    /// The url of the playlist's cover in size xl. +    pub picture_xl: String, + +    /// The checksum for the track list. +    pub checksum: String, + +    /// The creator of the playlist. +    pub creator: Creator, + +    /// List of tracks. +    pub tracks: PlaylistTracks, +} + +/// A track in a playlist. +#[derive(Debug, Deserialize)] +pub struct PlaylistTrack +{ +    /// The track's Deezer id +    pub id: u64, + +    /// true if the track is readable in the player for the current user +    pub readable: bool, + +    /// The track's fulltitle +    pub title: String, + +    /// The track's short title +    pub title_short: String, + +    /// The track version +    pub title_version: String, + +    /// The track unseen status +    pub unseen: Option<bool>, + +    /// The url of the track on Deezer +    pub link: String, + +    /// The track's duration in seconds +    pub duration: u64, + +    /// The track's Deezer rank +    pub rank: u64, + +    /// Whether the track contains explicit lyrics +    pub explicit_lyrics: bool, + +    /// The url of track's preview file. This file contains the first 30 seconds of the +    /// track +    pub preview: String, + +    /// The time when the track has been added to the playlist +    pub time_add: u64, + +    /// The track's artist. +    pub artist: PlaylistTrackArtist, + +    /// The track's album. +    pub album: PlaylistTrackAlbum, +} + +/// The artist of a playlist track. +#[derive(Debug, Deserialize)] +pub struct PlaylistTrackArtist +{ +    /// Artist id. +    pub id: u64, + +    /// Artist name. +    pub name: String, + +    /// Artist url. +    pub link: String, +} + +/// The album of a playlist track. +#[derive(Debug, Deserialize)] +pub struct PlaylistTrackAlbum +{ +    /// Album id. +    pub id: u64, + +    /// Album title. +    pub title: String, + +    /// Album cover. +    pub cover: String, + +    /// Album cover in size small. +    pub cover_small: String, + +    /// Album cover in size medium. +    pub cover_medium: String, + +    /// Album cover in size big. +    pub cover_big: String, + +    /// Album cover in side xl. +    pub cover_xl: String, +} + +/// Tracks in a playlist. +#[derive(Debug, Deserialize)] +pub struct PlaylistTracks +{ +    /// Tracks. +    pub data: Vec<PlaylistTrack>, +} diff --git a/src/user.rs b/src/user.rs new file mode 100644 index 0000000..ee22f5a --- /dev/null +++ b/src/user.rs @@ -0,0 +1,153 @@ +//! Deezer user. + +/* +pub enum ExplicitContentLevel +{ +    explicit_display, +    explicit_no_recommendation, +    explicit_hide, +} +*/ +use serde::Deserialize; + +use crate::creator::Creator; + +/// A Deezer user. +#[derive(Debug, Deserialize)] +pub struct User +{ +    /// The user's Deezer ID +    pub id: u64, + +    /// The user's Deezer nickname +    pub name: String, + +    /// The user's last name +    pub lastname: String, + +    /// The user's first name +    pub firstname: String, + +    /// The user's email +    pub email: Option<String>, + +    /// The user's status +    pub status: u32, + +    /// The user's birthday +    pub birthday: String, + +    /// The user's inscription date +    pub inscription_date: String, + +    /// The user's gender : F or M +    pub gender: String, + +    /// The String of the profil for the user on Deezer +    pub link: String, + +    /// The String of the user's profil picture. Add 'size' parameter to the String to +    /// change size. Can be 'small', 'medium', 'big', 'xl' +    pub picture: String, + +    /// The String of the user's profil picture in size small. +    pub picture_small: String, + +    /// The String of the user's profil picture in size medium. +    pub picture_medium: String, + +    /// The String of the user's profil picture in size big. +    pub picture_big: String, + +    /// The String of the user's profil picture in size xl. +    pub picture_xl: String, + +    /// The user's country +    pub country: String, + +    /// The user's language +    pub lang: String, + +    /// If the user is a kid or not +    pub is_kid: bool, + +    /// The user's explicit content level according to his country +    pub explicit_content_level: String, + +    /// The user's available explicit content levels according to his country. Possible +    /// values are: explicit_display, explicit_no_recommendation and explicit_hide +    pub explicit_content_levels_available: Vec<String>, + +    /// API Link to the flow of this user +    pub tracklist: String, + +    /// Object type. +    #[serde(rename = "type")] +    pub object_type: String, +} + +/// A user's playlist. +#[derive(Debug, Deserialize)] +pub struct UserPlaylist +{ +    /// The playlist's Deezer id +    pub id: u64, + +    /// The playlist's title +    pub title: String, + +    /// The playlist's duration (seconds) +    pub duration: u64, + +    /// If the playlist is public or not +    pub public: bool, + +    /// If the playlist is the love tracks playlist +    pub is_loved_track: bool, + +    /// If the playlist is collaborative or not +    pub collaborative: bool, + +    /// Nb tracks in the playlist +    pub nb_tracks: u64, + +    /// The number of playlist's fans +    pub fans: u64, + +    /// The url of the playlist on Deezer +    pub link: String, + +    /// The url of the playlist's cover. Add 'size' parameter to the url to change size. +    /// Can be 'small', 'medium', 'big', 'xl' +    pub picture: String, + +    /// The url of the playlist's cover in size small. +    pub picture_small: String, + +    /// The url of the playlist's cover in size medium. +    pub picture_medium: String, + +    /// The url of the playlist's cover in size big. +    pub picture_big: String, + +    /// The url of the playlist's cover in size xl. +    pub picture_xl: String, + +    /// The checksum for the track list +    pub checksum: String, + +    /// The time when the playlist has been added +    pub time_add: u64, + +    /// The time when the playlist has been updated +    pub time_mod: u64, + +    /// The creator of the playlist. +    pub creator: Creator, +} + +#[derive(Debug, Deserialize)] +pub(crate) struct UserPlaylists +{ +    pub data: Vec<UserPlaylist>, +} | 
