diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3549fae --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +.env \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..cdc7ec6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "transmission-rpc" +version = "0.1.0" +authors = ["red "] +edition = "2018" + + +[dependencies] +dotenv = "0.15.0" +reqwest = { version = "0.10.4", features = ["json", "rustls-tls"] } +tokio = { version = "0.2", features = ["full"] } +tokio-postgres = "0.5.2" +serde = { version = "1.0", features = ["derive"] } +futures = "0.3.3" +serde_json = "1.0" +ajson = "0.2" + +log = "0.4.8" +env_logger = "0.7.1" + +bb8-postgres = "0.4.0" +bb8 = "0.4.1" + +rustc-serialize = "0.3.24" diff --git a/README.md b/README.md new file mode 100644 index 0000000..00fa651 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +Library to communicate with transmission rpc + +spec: https://github.com/transmission/transmission/blob/master/extras/rpc-spec.txt + +Supported Methods: + +- [ ] torrent-set +- [ ] torrent-get +- [ ] torrent-add +- [ ] torrent-remove +- [ ] torrent-set-location +- [ ] torrent-rename-path +- [ ] session-set +- [X] session-get +- [ ] session-stats +- [ ] blocklist-update +- [ ] port-test +- [ ] session-close +- [ ] free-space \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..7e4801e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,109 @@ +#[allow(unused_imports)] +#[allow(dead_code)] + +extern crate ajson; +extern crate bb8; +extern crate bb8_postgres; +extern crate env_logger; +#[macro_use] +extern crate log; +extern crate reqwest; +extern crate tokio_postgres; + +use std::env; +use dotenv::dotenv; +use std::collections::HashMap; +use reqwest::header::CONTENT_TYPE; + +pub type Result = std::result::Result>; + +#[derive(Debug)] +struct BasicAuth { + user: String, + password: String, +} + +mod models; +use models::request::SessionGet; +use models::response::RpcResponse; +use models::entity::SessionInfo; + +#[tokio::main] +async fn main() -> Result<()> { + not_main().await +} + +async fn not_main() -> Result<()> { + dotenv().ok(); + env_logger::init(); + + let url= env::var("URL")?; + let basic_auth = Some(BasicAuth{user: env::var("TUSER")?, password: env::var("TPWD")?}); + info!("Loaded auth: {:?}", &basic_auth); + let rq: reqwest::RequestBuilder = rpc_request(url.as_ref(), &basic_auth) + .header("X-Transmission-Session-Id", get_session_id(url.as_ref(), &basic_auth).await) + .json(&SessionGet::default()); + debug!("Request body: {:?}", rq.try_clone().unwrap().body_string()?); + + // let p2 = Point{ x: 34, ..Default::default() }; + + let resp: reqwest::Response = rq.send().await?; + // print!("{:?}", resp.text().await); + let rpc_response: RpcResponse = resp.json().await?; + info!("{:#?}", rpc_response); + Ok(()) +} + +trait BodyString { + fn body_string(self) -> Result; +} + +impl BodyString for reqwest::RequestBuilder { + fn body_string(self) -> Result { + let rq = self.build()?; + let body = rq.body().unwrap().as_bytes().unwrap(); + Ok(std::str::from_utf8(body)?.to_string()) + } +} + +fn rpc_request(url: &str, basic_auth: &Option) -> reqwest::RequestBuilder { + let client = reqwest::Client::new(); + if let Some(auth) = basic_auth { + client.post(url.clone()) + .basic_auth(&auth.user, Some(&auth.password)) + } else { + client.post(url.clone()) + } + .header(CONTENT_TYPE, "application/json") +} + +async fn get_session_id(url: &str, basic_auth: &Option) -> String { + let mut map = HashMap::new(); + map.insert("method", "session-get"); + info!("Requesting session id info"); + let response: reqwest::Response = rpc_request(url, basic_auth) + .json(&map) + .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 +} + +#[cfg(test)] +mod tests { + use crate::not_main; + use crate::Result; + + #[tokio::test] + async fn it_works() -> Result<()> { + dotenv().ok(); + not_main().await + } +} diff --git a/src/models/entity.rs b/src/models/entity.rs new file mode 100644 index 0000000..a187bb8 --- /dev/null +++ b/src/models/entity.rs @@ -0,0 +1,14 @@ +use serde::Deserialize; +#[derive(Deserialize, Debug)] +pub struct SessionInfo { + #[serde(rename="blocklist-enabled")] + blocklist_enabled: bool, + #[serde(rename="download-dir")] + download_dir: String, + encryption: String, + #[serde(rename="rpc-version")] + rpc_version: i32, + #[serde(rename="rpc-version-minimum")] + rpc_version_minimum: i32, + version: String, +} \ No newline at end of file diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..cc24545 --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1,4 @@ + +pub mod request; +pub mod response; +pub mod entity; \ No newline at end of file diff --git a/src/models/request.rs b/src/models/request.rs new file mode 100644 index 0000000..60fca26 --- /dev/null +++ b/src/models/request.rs @@ -0,0 +1,12 @@ +use serde::Serialize; + +#[derive(Serialize, Debug, RustcEncodable)] +pub struct SessionGet { + method: String +} + +impl Default for SessionGet{ + fn default() -> SessionGet { + SessionGet { method: String::from("session-get") } + } +} \ No newline at end of file diff --git a/src/models/response.rs b/src/models/response.rs new file mode 100644 index 0000000..641adc0 --- /dev/null +++ b/src/models/response.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; +#[derive(Deserialize, Debug)] +pub struct RpcResponse { + arguments: T, + result: String +} \ No newline at end of file