joscha gay
This commit is contained in:
commit
e30854304f
24 changed files with 3073 additions and 0 deletions
269
src/client.rs
Normal file
269
src/client.rs
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
|
||||
use reqwest::Method;
|
||||
|
||||
use serde::{Deserialize, Deserializer, de};
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::objects::*;
|
||||
|
||||
use md5::{Digest, Md5};
|
||||
|
||||
use crate::errors::{ErrorKind, DzErr};
|
||||
|
||||
const API_URL: &str = "https://api.deezer.com";
|
||||
// client id and secret are from deezer's chromecast app
|
||||
const CLIENT_ID: &str = "119915";
|
||||
const CLIENT_SECRET: &str = "2f5b4c9785ddc367975b83d90dc46f5c";
|
||||
|
||||
fn from_str<'de, T, D>(deserializer: D) -> Result<T, D::Error>
|
||||
where T: FromStr,
|
||||
T::Err: Display,
|
||||
D: Deserializer<'de>
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
T::from_str(&s).map_err(de::Error::custom)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct TokenResp {
|
||||
access_token: String,
|
||||
#[serde(deserialize_with = "from_str")]
|
||||
expires: u64,
|
||||
}
|
||||
|
||||
// needed because deezer's API returns an integer on "expires" instead of a string when logging in
|
||||
// thanks baguette assholes
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct LoginTokenResp {
|
||||
access_token: String,
|
||||
expires: u64,
|
||||
}
|
||||
|
||||
pub struct AccessToken {
|
||||
pub token: String,
|
||||
expires: u64,
|
||||
instant: Instant
|
||||
}
|
||||
|
||||
impl AccessToken {
|
||||
fn new(token: String, expires: u64) -> AccessToken {
|
||||
AccessToken {
|
||||
token,
|
||||
expires,
|
||||
instant: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
enum DzRespType<T> {
|
||||
Err { error: DzErr },
|
||||
Ok(T),
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
client: reqwest::Client,
|
||||
pub access_token: Option<AccessToken>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
access_token: None,
|
||||
client: reqwest::Client::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_token(&mut self) -> Result<(), ErrorKind> {
|
||||
let resp = self.client.request(Method::GET, "https://connect.deezer.com/oauth/access_token.php")
|
||||
.query(&[
|
||||
("grant_type", "client_credentials"),
|
||||
("client_id", CLIENT_ID),
|
||||
("client_secret", CLIENT_SECRET),
|
||||
("output", "json"),
|
||||
])
|
||||
.send().await.map_err(|e| ErrorKind::Reqwest(e))?
|
||||
.json::<DzRespType<TokenResp>>().await.map_err(|e| ErrorKind::Reqwest(e))?;
|
||||
|
||||
match resp {
|
||||
DzRespType::Ok(token) => {
|
||||
self.access_token = Some(AccessToken::new(token.access_token, token.expires));
|
||||
Ok(())
|
||||
},
|
||||
DzRespType::Err { error } => Err(ErrorKind::API(error)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn check_token(&mut self) -> Result<(), ErrorKind> {
|
||||
match &self.access_token {
|
||||
Some(token) => {
|
||||
if token.instant.elapsed().as_secs() >= token.expires && token.expires != 0 {
|
||||
self.get_token().await
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
None => self.get_token().await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn login(&mut self, email: &str, password: &str) -> Result<(), ErrorKind> {
|
||||
let password = format!("{:x}", Md5::digest(password.as_bytes()));
|
||||
|
||||
let hash = [
|
||||
CLIENT_ID.as_bytes(),
|
||||
email.as_bytes(),
|
||||
password.as_bytes(),
|
||||
CLIENT_SECRET.as_bytes(),
|
||||
].concat();
|
||||
let hash = format!("{:x}", Md5::digest(&hash));
|
||||
|
||||
let url = format!("{}/auth/token", API_URL);
|
||||
let resp = self.client.request(Method::GET, &url)
|
||||
.query(&[
|
||||
("app_id", CLIENT_ID),
|
||||
("login", email),
|
||||
("password", &password),
|
||||
("hash", &hash),
|
||||
])
|
||||
.send().await.map_err(|e| ErrorKind::Reqwest(e))?
|
||||
.json::<DzRespType<LoginTokenResp>>().await.map_err(|e| ErrorKind::Reqwest(e))?;
|
||||
|
||||
match resp {
|
||||
DzRespType::Ok(token) => {
|
||||
self.access_token = Some(AccessToken::new(token.access_token, token.expires));
|
||||
Ok(())
|
||||
},
|
||||
DzRespType::Err { error } => Err(ErrorKind::API(error)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn login_token(&mut self, token: &str) -> Result<(), ErrorKind> {
|
||||
self.access_token = Some(AccessToken::new(token.to_string(), 0));
|
||||
match self.infos().await {
|
||||
Err(e) => {
|
||||
self.access_token = None;
|
||||
Err(e)
|
||||
},
|
||||
Ok(i) => {
|
||||
match i.user_token {
|
||||
Some(_) => Ok(()),
|
||||
None => {
|
||||
self.access_token = None;
|
||||
Err(ErrorKind::NotUserToken)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn api_call<T, Q>(&mut self, method: reqwest::Method, path: &str, query: &Q) -> Result<T, ErrorKind>
|
||||
where T: de::DeserializeOwned,
|
||||
Q: serde::ser::Serialize + ?Sized,
|
||||
{
|
||||
self.check_token().await.map_err(|e| ErrorKind::Token(Box::new(e)))?;
|
||||
let url = format!("{}/{}", API_URL, path);
|
||||
|
||||
let resp = self.client.request(method, &url)
|
||||
.query(&[("access_token", &self.access_token.as_ref().unwrap().token)])
|
||||
.query(query)
|
||||
.body("\n") // needed otherwise some POST requests fail
|
||||
.send().await.map_err(|e| ErrorKind::Reqwest(e))?
|
||||
.json::<DzRespType<T>>().await.map_err(|e| ErrorKind::Reqwest(e))?;
|
||||
|
||||
match resp {
|
||||
DzRespType::Ok(r) => Ok(r),
|
||||
DzRespType::Err { error } => Err(ErrorKind::API(error)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn track(&mut self, id: i64) -> Result<Track, ErrorKind> {
|
||||
Track::get(self, id).await
|
||||
}
|
||||
|
||||
pub async fn album(&mut self, id: u64) -> Result<Album, ErrorKind> {
|
||||
Album::get(self, id).await
|
||||
}
|
||||
|
||||
pub async fn artist(&mut self, id: u64) -> Result<Artist, ErrorKind> {
|
||||
Artist::get(self, id).await
|
||||
}
|
||||
|
||||
pub async fn playlist(&mut self, id: u64) -> Result<Playlist, ErrorKind> {
|
||||
Playlist::get(self, id).await
|
||||
}
|
||||
|
||||
pub async fn podcast(&mut self, id: u64) -> Result<Podcast, ErrorKind> {
|
||||
Podcast::get(self, id).await
|
||||
}
|
||||
|
||||
pub async fn episode(&mut self, id: u64) -> Result<Episode, ErrorKind> {
|
||||
Episode::get(self, id).await
|
||||
}
|
||||
|
||||
pub async fn chart(&mut self, id: u64) -> Result<Chart, ErrorKind> {
|
||||
Chart::get(self, id).await
|
||||
}
|
||||
|
||||
pub async fn radio(&mut self, id: u64) -> Result<Radio, ErrorKind> {
|
||||
Radio::get(self, id).await
|
||||
}
|
||||
|
||||
pub async fn radio_list(&mut self) -> Result<DzArray<Radio>, ErrorKind> {
|
||||
Radio::list(self).await
|
||||
}
|
||||
|
||||
pub async fn search_track(&mut self, query: &str) -> Result<DzArray<Track>, ErrorKind> {
|
||||
search::track(self, query).await
|
||||
}
|
||||
|
||||
pub async fn search_album(&mut self, query: &str) -> Result<DzArray<Album>, ErrorKind> {
|
||||
search::album(self, query).await
|
||||
}
|
||||
|
||||
pub async fn search_artist(&mut self, query: &str) -> Result<DzArray<Artist>, ErrorKind> {
|
||||
search::artist(self, query).await
|
||||
}
|
||||
|
||||
pub async fn search_playlist(&mut self, query: &str) -> Result<DzArray<Playlist>, ErrorKind> {
|
||||
search::playlist(self, query).await
|
||||
}
|
||||
|
||||
pub async fn genre(&mut self, id: u64) -> Result<Genre, ErrorKind> {
|
||||
Genre::get(self, id).await
|
||||
}
|
||||
|
||||
pub async fn editorial(&mut self, id: u64) -> Result<Editorial, ErrorKind> {
|
||||
Editorial::get(self, id).await
|
||||
}
|
||||
|
||||
pub async fn lyrics(&mut self, id: u64) -> Result<Lyrics, ErrorKind> {
|
||||
Lyrics::get(self, id).await
|
||||
}
|
||||
|
||||
pub async fn user(&mut self, id: u64) -> Result<User, ErrorKind> {
|
||||
User::get(self, id).await
|
||||
}
|
||||
|
||||
pub async fn user_self(&mut self) -> Result<User, ErrorKind> {
|
||||
User::get_self(self).await
|
||||
}
|
||||
|
||||
pub async fn infos(&mut self) -> Result<Infos, ErrorKind> {
|
||||
Infos::get(self).await
|
||||
}
|
||||
|
||||
pub async fn options(&mut self) -> Result<Options, ErrorKind> {
|
||||
Options::get(self).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Client {
|
||||
fn default() -> Self {
|
||||
Client::new()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue