dzlib-rs/src/client.rs

269 lines
7.8 KiB
Rust
Raw Normal View History

2021-07-14 22:10:10 +00:00
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()
}
}