summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml2
-rw-r--r--examples/playlists/main.rs (renamed from examples/access_token/main.rs)13
-rw-r--r--src/auth/mod.rs1
-rw-r--r--src/client.rs133
-rw-r--r--src/creator.rs13
-rw-r--r--src/errors/client.rs28
-rw-r--r--src/errors/mod.rs1
-rw-r--r--src/lib.rs4
-rw-r--r--src/playlist.rs166
-rw-r--r--src/user.rs153
10 files changed, 494 insertions, 20 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 7493fcb..33db76b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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;
diff --git a/src/lib.rs b/src/lib.rs
index 1f2576f..6aebebd 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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>,
+}