use anyhow::Context as _;
pub struct QbtHttp {
http: reqwest::Client,
url: url::Url,
}
impl QbtHttp {
pub async fn new(
mut url: url::Url,
headers: reqwest::header::HeaderMap,
) -> anyhow::Result<Self> {
let username = url.username().to_string();
let password = url.password().unwrap_or("").to_string();
url.set_username("")
.map_err(|()| anyhow::Error::msg("Invalid qbt_url"))?;
url.set_password(None)
.map_err(|()| anyhow::Error::msg("Invalid qbt_url"))?;
let http = reqwest::Client::builder()
.user_agent("qbt-clean/0")
.connect_timeout(std::time::Duration::from_secs(60))
.default_headers(headers)
.timeout(std::time::Duration::from_secs(600))
.cookie_store(true)
.build()?;
http.post(url.join("/api/v2/auth/login")?)
.form(&[
("username", &username),
("password", &password),
])
.send().await.context("Sending login request")?
.error_for_status().context("Login request status")?;
Ok(Self { http, url })
}
}
impl crate::Qbt for QbtHttp {
async fn logout(self) -> anyhow::Result<()> {
self.http.post(self.url.join("/api/v2/auth/logout")?)
.send().await?
.error_for_status()?;
Ok(())
}
async fn server_state(&self) -> anyhow::Result<crate::ServerState> {
#[derive(serde::Deserialize)]
struct Sync {
server_state: crate::ServerState,
}
Ok(self.http.get(self.url.join("/api/v2/sync/maindata")?)
.send().await?
.error_for_status()?
.json::<Sync>().await?
.server_state)
}
async fn torrents_info(&self) -> anyhow::Result<Vec<crate::Torrent>> {
Ok(self.http.get(self.url.join("/api/v2/torrents/info")?)
.send().await?
.error_for_status()?
.json().await?)
}
async fn torrent_files(&self, hash: crate::InfoHash)
-> anyhow::Result<Vec<crate::TorrentFile>>
{
Ok(self.http.get(self.url.join("/api/v2/torrents/files")?)
.query(&[("hash", hash)])
.send().await?
.error_for_status()?
.json().await?)
}
async fn torrent_trackers(&self, hash: crate::InfoHash)
-> anyhow::Result<Vec<crate::Tracker>>
{
Ok(self.http.get(self.url.join("/api/v2/torrents/trackers")?)
.query(&[("hash", hash)])
.send().await?
.error_for_status()?
.json().await?)
}
async fn delete_torrents(&self, hashes: &[crate::InfoHash]) -> anyhow::Result<()> {
let s = hashes.iter()
.map(|h| h.to_string())
.collect::<Vec<_>>()
.join("|");
self.http.post(self.url.join("/api/v2/torrents/delete")?)
.form(&[
("deleteFiles", "true"),
("hashes", &s),
])
.send().await?
.error_for_status()?;
Ok(())
}
}