mirror of
https://github.com/kristoferssolo/transmission-rpc.git
synced 2025-10-21 20:10:37 +00:00
309 lines
11 KiB
Rust
309 lines
11 KiB
Rust
// #[allow(unused_imports)]
|
|
// #[allow(dead_code)]
|
|
|
|
extern crate env_logger;
|
|
#[macro_use]
|
|
extern crate log;
|
|
extern crate reqwest;
|
|
|
|
use serde::de::DeserializeOwned;
|
|
use reqwest::header::CONTENT_TYPE;
|
|
|
|
pub mod types;
|
|
use types::BasicAuth;
|
|
use types::{Result, RpcResponse, RpcResponseArgument, RpcRequest, Nothing};
|
|
use types::SessionGet;
|
|
use types::{TorrentGetField, Torrents, Torrent, Id};
|
|
use types::TorrentAction;
|
|
use types::{TorrentAddArgs, TorrentAdded};
|
|
|
|
pub struct TransClient {
|
|
url: String,
|
|
auth: Option<BasicAuth>
|
|
}
|
|
|
|
impl TransClient {
|
|
/// Returns HTTP(S) client with configured Basic Auth
|
|
pub fn with_auth(url: &str, basic_auth: BasicAuth) -> TransClient {
|
|
TransClient {
|
|
url: url.to_string(),
|
|
auth: Some(basic_auth)
|
|
}
|
|
}
|
|
|
|
/// Returns HTTP(S) client
|
|
pub fn new(url: &str) -> TransClient {
|
|
TransClient {
|
|
url: url.to_string(),
|
|
auth: None
|
|
}
|
|
}
|
|
|
|
/// Prepares a request for provided server and auth
|
|
fn rpc_request(&self) -> reqwest::RequestBuilder {
|
|
let client = reqwest::Client::new();
|
|
if let Some(auth) = &self.auth {
|
|
client.post(&self.url)
|
|
.basic_auth(&auth.user, Some(&auth.password))
|
|
} else {
|
|
client.post(&self.url)
|
|
}
|
|
.header(CONTENT_TYPE, "application/json")
|
|
}
|
|
|
|
/// Performs session-get call and takes the x-transmission-session-id
|
|
/// header to perform calls, using it's value
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Panics if any IO error happens
|
|
async fn get_session_id(&self) -> String {
|
|
info!("Requesting session id info");
|
|
let response: reqwest::Response = self.rpc_request()
|
|
.json(&RpcRequest::session_get())
|
|
.send()
|
|
.await
|
|
.unwrap();
|
|
let session_id = response.headers()
|
|
.get("x-transmission-session-id")
|
|
.expect("Unable to get session id")
|
|
.to_str()
|
|
.unwrap()
|
|
.to_owned();
|
|
info!("Received session id: {}", session_id);
|
|
session_id
|
|
}
|
|
|
|
/// Performs a session get call
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Any IO Error or Deserialization error
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```
|
|
/// extern crate transmission_rpc;
|
|
///
|
|
/// use std::env;
|
|
/// use dotenv::dotenv;
|
|
/// use transmission_rpc::TransClient;
|
|
/// use transmission_rpc::types::{Result, RpcResponse, SessionGet, BasicAuth};
|
|
///
|
|
/// #[tokio::main]
|
|
/// async fn main() -> Result<()> {
|
|
/// dotenv().ok();
|
|
/// env_logger::init();
|
|
/// let url= env::var("TURL")?;
|
|
/// let basic_auth = BasicAuth{user: env::var("TUSER")?, password: env::var("TPWD")?};
|
|
/// let client = TransClient::with_auth(&url, basic_auth);
|
|
/// let response: Result<RpcResponse<SessionGet>> = client.session_get().await;
|
|
/// match response {
|
|
/// Ok(_) => println!("Yay!"),
|
|
/// Err(_) => panic!("Oh no!")
|
|
/// }
|
|
/// println!("Rpc reqsponse is ok: {}", response?.is_ok());
|
|
/// Ok(())
|
|
/// }
|
|
/// ```
|
|
pub async fn session_get(&self) -> Result<RpcResponse<SessionGet>> {
|
|
self.call(RpcRequest::session_get()).await
|
|
}
|
|
|
|
/// Performs a torrent get call
|
|
/// fileds - if None then ALL fields
|
|
/// ids - if None then All items
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Any IO Error or Deserialization error
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```
|
|
/// extern crate transmission_rpc;
|
|
///
|
|
/// use std::env;
|
|
/// use dotenv::dotenv;
|
|
/// use transmission_rpc::TransClient;
|
|
/// use transmission_rpc::types::{Result, RpcResponse, BasicAuth};
|
|
/// use transmission_rpc::types::{Torrents, Torrent, TorrentGetField, Id};
|
|
///
|
|
/// #[tokio::main]
|
|
/// async fn main() -> Result<()> {
|
|
/// dotenv().ok();
|
|
/// env_logger::init();
|
|
/// let url= env::var("TURL")?;
|
|
/// let basic_auth = BasicAuth{user: env::var("TUSER")?, password: env::var("TPWD")?};
|
|
/// let client = TransClient::with_auth(&url, basic_auth);
|
|
///
|
|
/// let res: RpcResponse<Torrents<Torrent>> = client.torrent_get(None, None).await?;
|
|
/// let names: Vec<&String> = res.arguments.torrents.iter().map(|it| it.name.as_ref().unwrap()).collect();
|
|
/// println!("{:#?}", names);
|
|
///
|
|
/// let res1: RpcResponse<Torrents<Torrent>> = client.torrent_get(Some(vec![TorrentGetField::Id, TorrentGetField::Name]), Some(vec![Id::Id(1), Id::Id(2), Id::Id(3)])).await?;
|
|
/// let first_three: Vec<String> = res1.arguments.torrents.iter().map(|it|
|
|
/// format!("{}. {}",&it.id.as_ref().unwrap(), &it.name.as_ref().unwrap())
|
|
/// ).collect();
|
|
/// println!("{:#?}", first_three);
|
|
///
|
|
///
|
|
/// let res2: RpcResponse<Torrents<Torrent>> = client.torrent_get(Some(vec![TorrentGetField::Id, TorrentGetField::HashString, TorrentGetField::Name]), Some(vec![Id::Hash(String::from("64b0d9a53ac9cd1002dad1e15522feddb00152fe"))])).await?;
|
|
/// let info: Vec<String> = res2.arguments.torrents.iter().map(|it|
|
|
/// format!("{:5}. {:^45} {}",
|
|
/// &it.id.as_ref().unwrap(),
|
|
/// &it.hash_string.as_ref().unwrap(),
|
|
/// &it.name.as_ref().unwrap())
|
|
/// ).collect();
|
|
/// println!("{:#?}", info);
|
|
///
|
|
/// Ok(())
|
|
/// }
|
|
/// ```
|
|
pub async fn torrent_get(&self, fields: Option<Vec<TorrentGetField>>, ids: Option<Vec<Id>>) -> Result<RpcResponse<Torrents<Torrent>>> {
|
|
self.call(RpcRequest::torrent_get(fields, ids)).await
|
|
}
|
|
|
|
/// Performs a torrent action call
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Any IO Error or Deserialization error
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```
|
|
/// extern crate transmission_rpc;
|
|
///
|
|
/// use std::env;
|
|
/// use dotenv::dotenv;
|
|
/// use transmission_rpc::TransClient;
|
|
/// use transmission_rpc::types::{Result, RpcResponse, BasicAuth};
|
|
/// use transmission_rpc::types::{TorrentAction, Nothing, Id};
|
|
///
|
|
/// #[tokio::main]
|
|
/// async fn main() -> Result<()> {
|
|
/// dotenv().ok();
|
|
/// env_logger::init();
|
|
/// let url= env::var("TURL")?;
|
|
/// let basic_auth = BasicAuth{user: env::var("TUSER")?, password: env::var("TPWD")?};
|
|
/// let client = TransClient::with_auth(&url, basic_auth);
|
|
/// let res1: RpcResponse<Nothing> = client.torrent_action(TorrentAction::Start, vec![Id::Id(1)]).await?;
|
|
/// println!("Start result: {:?}", &res1.is_ok());
|
|
/// let res2: RpcResponse<Nothing> = client.torrent_action(TorrentAction::Stop, vec![Id::Id(1)]).await?;
|
|
/// println!("Stop result: {:?}", &res2.is_ok());
|
|
///
|
|
/// Ok(())
|
|
/// }
|
|
/// ```
|
|
pub async fn torrent_action(&self, action: TorrentAction, ids: Vec<Id>) -> Result<RpcResponse<Nothing>> {
|
|
self.call(RpcRequest::torrent_action(action, ids)).await
|
|
}
|
|
|
|
/// Performs a torrent remove call
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Any IO Error or Deserialization error
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```
|
|
/// extern crate transmission_rpc;
|
|
///
|
|
/// use std::env;
|
|
/// use dotenv::dotenv;
|
|
/// use transmission_rpc::TransClient;
|
|
/// use transmission_rpc::types::{Result, RpcResponse, BasicAuth};
|
|
/// use transmission_rpc::types::{Nothing, Id};
|
|
///
|
|
/// #[tokio::main]
|
|
/// async fn main() -> Result<()> {
|
|
/// dotenv().ok();
|
|
/// env_logger::init();
|
|
/// let url= env::var("TURL")?;
|
|
/// let basic_auth = BasicAuth{user: env::var("TUSER")?, password: env::var("TPWD")?};
|
|
/// let client = TransClient::with_auth(&url, basic_auth);
|
|
/// let res: RpcResponse<Nothing> = client.torrent_remove(vec![Id::Id(1)], false).await?;
|
|
/// println!("Remove result: {:?}", &res.is_ok());
|
|
///
|
|
/// Ok(())
|
|
/// }
|
|
/// ```
|
|
pub async fn torrent_remove(&self, ids: Vec<Id>, delete_local_data: bool) -> Result<RpcResponse<Nothing>> {
|
|
self.call( RpcRequest::torrent_remove(ids, delete_local_data)).await
|
|
}
|
|
|
|
/// Performs a torrent add call
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Any IO Error or Deserialization error
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```
|
|
/// extern crate transmission_rpc;
|
|
///
|
|
/// use std::env;
|
|
/// use dotenv::dotenv;
|
|
/// use transmission_rpc::TransClient;
|
|
/// use transmission_rpc::types::{Result, RpcResponse, BasicAuth};
|
|
/// use transmission_rpc::types::{TorrentAddArgs, TorrentAdded};
|
|
///
|
|
/// #[tokio::main]
|
|
/// async fn main() -> Result<()> {
|
|
/// dotenv().ok();
|
|
/// env_logger::init();
|
|
/// let url= env::var("TURL")?;
|
|
/// let basic_auth = BasicAuth{user: env::var("TUSER")?, password: env::var("TPWD")?};
|
|
/// let client = TransClient::with_auth(&url, basic_auth);
|
|
/// let add: TorrentAddArgs = TorrentAddArgs {
|
|
/// filename: Some("https://releases.ubuntu.com/20.04/ubuntu-20.04-desktop-amd64.iso.torrent".to_string()),
|
|
/// ..TorrentAddArgs::default()
|
|
/// };
|
|
/// let res: RpcResponse<TorrentAdded> = client.torrent_add(add).await?;
|
|
/// println!("Add result: {:?}", &res.is_ok());
|
|
/// println!("response: {:?}", &res);
|
|
///
|
|
/// Ok(())
|
|
/// }
|
|
/// ```
|
|
pub async fn torrent_add(&self, add: TorrentAddArgs) -> Result<RpcResponse<TorrentAdded>> {
|
|
if add.metainfo == None && add.filename == None {
|
|
panic!("Metainfo or Filename should be provided")
|
|
}
|
|
self.call( RpcRequest::torrent_add(add)).await
|
|
}
|
|
|
|
/// Performs a JRPC call to the server
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Any IO Error or Deserialization error
|
|
async fn call<RS> (&self, request: RpcRequest) -> Result<RpcResponse<RS>>
|
|
where RS : RpcResponseArgument + DeserializeOwned + std::fmt::Debug
|
|
{
|
|
info!("Loaded auth: {:?}", &self.auth);
|
|
let rq: reqwest::RequestBuilder = self.rpc_request()
|
|
.header("X-Transmission-Session-Id", self.get_session_id().await)
|
|
.json(&request);
|
|
info!("Request body: {:?}", rq.try_clone().unwrap().body_string()?);
|
|
let resp: reqwest::Response = rq.send().await?;
|
|
let rpc_response: RpcResponse<RS> = resp.json().await?;
|
|
info!("Response body: {:#?}", rpc_response);
|
|
Ok(rpc_response)
|
|
}
|
|
}
|
|
|
|
trait BodyString {
|
|
fn body_string(self) -> Result<String>;
|
|
}
|
|
|
|
impl BodyString for reqwest::RequestBuilder {
|
|
fn body_string(self) -> Result<String> {
|
|
let rq = self.build()?;
|
|
let body = rq.body().unwrap().as_bytes().unwrap();
|
|
Ok(std::str::from_utf8(body)?.to_string())
|
|
}
|
|
} |