use super::http::{BaseHttpClient, Headers, HttpClient, Query};
use reqwest::header;
use regex::Regex;
use serde_json::Value;
use crate::{API_BASE_URL, ASSET_URL_REGEX, BASE_URL, CLIENT_ID_REGEX, ClientResult, DEFAULT_USER_AGENT, errors::ClientError};
pub struct SoundCloudClient {
http_client: HttpClient,
client_id: String,
auth_token: Option<String>,
authorization: Option<String>,
}
impl SoundCloudClient {
pub async fn default() -> ClientResult<Self> {
Self::new(None, None).await
}
pub async fn new(client_id: Option<String>, auth_token: Option<String>) -> ClientResult<Self> {
Self::with_http_client(HttpClient::default(), client_id, auth_token).await
}
pub async fn with_http_client(
http_client: HttpClient,
client_id: Option<String>,
auth_token: Option<String>,
) -> ClientResult<Self> {
let client_id = match client_id {
Some(id) => id,
None => Self::generate_client_id_with(&http_client).await?,
};
let mut instance = SoundCloudClient {
http_client,
client_id,
auth_token: None,
authorization: None,
};
instance.set_auth_token(auth_token);
Ok(instance)
}
fn get_headers(&self) -> Headers {
let mut headers = Headers::new();
headers.insert(header::USER_AGENT.to_string(), DEFAULT_USER_AGENT.to_string());
if let Some(ref authorization) = self.authorization {
headers.insert(header::AUTHORIZATION.to_string(), authorization.to_string());
}
headers
}
fn get_params(&self, params: Query) -> Query {
let mut params = params;
params.insert("client_id".to_string(), self.client_id.clone().to_string());
params
}
fn get_url(&self, url: &str) -> String {
let mut base = API_BASE_URL.to_string();
if !base.ends_with('/') && !url.starts_with('/') {
base.push('/');
}
base + url
}
pub fn is_authenticated(&self) -> bool {
self.auth_token.is_some()
}
pub async fn api_get(&self, url: &str, query_params: Query) -> ClientResult<String> {
let url = self.get_url(url);
let headers = self.get_headers();
Ok(self.http_client.get(&url, Some(&headers), &self.get_params(query_params)).await?)
}
pub async fn api_post(&self, url: &str, query_params: Query, body: &Value) -> ClientResult<String> {
let url = self.get_url(url);
let headers = self.get_headers();
Ok(self.http_client.post(&url, Some(&headers), &self.get_params(query_params), body).await?)
}
pub async fn api_put(&self, url: &str, body: &Value) -> ClientResult<String> {
let url = self.get_url(url);
let headers = self.get_headers();
Ok(self.http_client.put(&url, Some(&headers), body).await?)
}
pub async fn api_delete(&self, url: &str) -> ClientResult<String> {
let url = self.get_url(url);
let headers = self.get_headers();
Ok(self.http_client.delete(&url, Some(&headers)).await?)
}
pub fn set_auth_token(&mut self, auth_token: Option<String>) {
if let Some(mut token) = auth_token {
if token.starts_with("OAuth") {
token = token.split_whitespace().last().unwrap_or("").to_string();
}
self.authorization = Some(format!("OAuth {}", token));
self.auth_token = Some(token);
} else {
self.authorization = None;
self.auth_token = None;
}
}
pub async fn generate_client_id() -> ClientResult<String> {
let http_client = HttpClient::default();
Self::generate_client_id_with(&http_client).await
}
pub async fn generate_client_id_with(http_client: &HttpClient) -> ClientResult<String> {
let text = http_client
.get(BASE_URL, None, &Query::new())
.await?;
let asset_regex = Regex::new(ASSET_URL_REGEX)
.map_err(|_| ClientError::ClientIDGenerationFailed)?;
let client_id_regex = Regex::new(CLIENT_ID_REGEX)
.map_err(|_| ClientError::ClientIDGenerationFailed)?;
let asset_urls: Vec<String> = asset_regex
.captures_iter(&text)
.filter_map(|caps| caps.get(1).map(|m| m.as_str().to_string()))
.collect();
if asset_urls.is_empty() {
return Err(ClientError::ClientIDGenerationFailed.into());
}
for asset_url in asset_urls {
let text = http_client.get(&asset_url, None, &Query::new()).await?;
if let Some(caps) = client_id_regex.captures(&text) {
if let Some(m) = caps.get(1) {
return Ok(m.as_str().to_string());
}
}
}
Err(ClientError::ClientIDGenerationFailed.into())
}
}