2024-10-31 12:16:42 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum Error {
|
|
|
|
// FIXME: can't even return url's ParseError directly.........
|
|
|
|
UrlParseError(String),
|
|
|
|
ReqwestError(reqwest::Error),
|
|
|
|
SubsonicError(SubsonicError),
|
|
|
|
}
|
2024-10-30 09:06:10 +00:00
|
|
|
|
2024-10-31 12:16:42 +00:00
|
|
|
impl From<reqwest::Error> for Error {
|
|
|
|
fn from(err: reqwest::Error) -> Self {
|
|
|
|
Self::ReqwestError(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, serde::Deserialize)]
|
|
|
|
pub struct SubsonicError {
|
|
|
|
pub code: u32,
|
|
|
|
pub message: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, serde::Deserialize)]
|
|
|
|
pub struct SubsonicResponse<T> {
|
|
|
|
#[serde(rename = "subsonic-response")]
|
|
|
|
inner: SubsonicResponseInner<T>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, serde::Deserialize)]
|
|
|
|
#[serde(tag = "status")]
|
|
|
|
pub enum SubsonicResponseInner<T> {
|
|
|
|
#[serde(rename = "ok")]
|
|
|
|
Ok(T),
|
|
|
|
#[serde(rename = "failed")]
|
|
|
|
Failed { error: SubsonicError },
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> SubsonicResponseInner<T> {
|
|
|
|
fn fixup(self) -> Result<T, Error> {
|
|
|
|
match self {
|
|
|
|
Self::Ok(t) => Ok(t),
|
|
|
|
Self::Failed { error } => Err(Error::SubsonicError(error)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Client {
|
|
|
|
client: reqwest::Client,
|
|
|
|
base_url: reqwest::Url,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Client {
|
|
|
|
pub fn new(url: &str, username: &str, token: &str, salt: &str) -> Result<Self, Error> {
|
2024-10-31 20:54:33 +00:00
|
|
|
let base_url = reqwest::Url::parse_with_params(
|
|
|
|
url,
|
|
|
|
&[
|
|
|
|
("u", username),
|
|
|
|
("t", token),
|
|
|
|
("s", salt),
|
|
|
|
("v", "1.16.1"),
|
|
|
|
("c", "eu.callcc.audrey"),
|
|
|
|
("f", "json"),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
.map_err(|err| Error::UrlParseError(err.to_string()))?;
|
|
|
|
|
|
|
|
if base_url.scheme() != "http" && base_url.scheme() != "https" {
|
|
|
|
return Err(Error::UrlParseError("Url scheme is not HTTP(s)".into()));
|
|
|
|
}
|
|
|
|
|
2024-10-31 12:16:42 +00:00
|
|
|
Ok(Client {
|
|
|
|
client: reqwest::Client::builder()
|
|
|
|
.user_agent("audrey/linux") // Audrey.Const.user_agent
|
|
|
|
.build()?,
|
2024-10-31 20:54:33 +00:00
|
|
|
base_url,
|
2024-10-31 12:16:42 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn ping(&self) -> Result<(), Error> {
|
|
|
|
let mut url = self.base_url.clone();
|
|
|
|
url.path_segments_mut()
|
2024-10-31 20:41:02 +00:00
|
|
|
// literally can't fail
|
|
|
|
.unwrap_or_else(|_| unsafe { std::hint::unreachable_unchecked() })
|
2024-10-31 12:16:42 +00:00
|
|
|
.extend(&["rest", "ping"]);
|
|
|
|
|
|
|
|
self.client
|
|
|
|
.get(url)
|
|
|
|
.send()
|
|
|
|
.await?
|
|
|
|
.error_for_status()?
|
|
|
|
.json::<SubsonicResponse<()>>()
|
|
|
|
.await?
|
|
|
|
.inner
|
|
|
|
.fixup()
|
|
|
|
}
|
|
|
|
}
|