From 5d25aceab57fa01c299fcc84ac2a9470342252b3 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 24 Jul 2021 15:58:57 +0200 Subject: [PATCH 1/4] Add functions to get and set cookies to low level api Signed-off-by: trivernis --- src/api_core/client.rs | 21 ++++- src/api_core/common.rs | 20 +++++ .../managing_cookies_and_http_headers.rs | 85 +++++++++++++++++++ src/api_core/mod.rs | 1 + tests/client/mod.rs | 1 + .../test_managing_cookies_and_http_headers.rs | 20 +++++ 6 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 src/api_core/managing_cookies_and_http_headers.rs create mode 100644 tests/client/test_managing_cookies_and_http_headers.rs diff --git a/src/api_core/client.rs b/src/api_core/client.rs index 13cd91d..81562a7 100644 --- a/src/api_core/client.rs +++ b/src/api_core/client.rs @@ -11,7 +11,10 @@ use crate::api_core::adding_urls::{ AddUrl, AddUrlRequest, AddUrlResponse, AssociateUrl, AssociateUrlRequest, GetUrlFiles, GetUrlFilesResponse, GetUrlInfo, GetUrlInfoResponse, }; -use crate::api_core::common::{FileIdentifier, FileMetadataInfo, FileRecord}; +use crate::api_core::common::{FileIdentifier, FileMetadataInfo, FileRecord, OptionalStringNumber}; +use crate::api_core::managing_cookies_and_http_headers::{ + GetCookies, GetCookiesResponse, SetCookies, SetCookiesRequest, +}; use crate::api_core::managing_pages::{ FocusPage, FocusPageRequest, GetPageInfo, GetPageInfoResponse, GetPages, GetPagesResponse, }; @@ -331,4 +334,20 @@ impl Client { Ok(()) } + + /// Returns all cookies for the given domain + pub async fn get_cookies>(&self, domain: S) -> Result { + self.get_and_parse::(&[("domain", domain.as_ref())]) + .await + } + + /// Sets some cookies for some websites. + /// Each entry needs to be in the format `[, , , , ]` + /// with the types `[String, String, String, String, u64]` + pub async fn set_cookies(&self, cookies: Vec<[OptionalStringNumber; 5]>) -> Result<()> { + self.post::(SetCookiesRequest { cookies }) + .await?; + + Ok(()) + } } diff --git a/src/api_core/common.rs b/src/api_core/common.rs index 20c2c6c..3a18ba5 100644 --- a/src/api_core/common.rs +++ b/src/api_core/common.rs @@ -60,3 +60,23 @@ pub struct PageInformation { #[serde(default = "Vec::new")] pub pages: Vec, } + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum OptionalStringNumber { + String(String), + Number(u64), + None, +} + +impl From for OptionalStringNumber { + fn from(value: u64) -> Self { + Self::Number(value) + } +} + +impl From for OptionalStringNumber { + fn from(value: String) -> Self { + Self::String(value) + } +} diff --git a/src/api_core/managing_cookies_and_http_headers.rs b/src/api_core/managing_cookies_and_http_headers.rs new file mode 100644 index 0000000..3488034 --- /dev/null +++ b/src/api_core/managing_cookies_and_http_headers.rs @@ -0,0 +1,85 @@ +use crate::api_core::common::OptionalStringNumber; +use crate::api_core::Endpoint; + +#[derive(Clone, Debug, Deserialize)] +pub struct GetCookiesResponse { + pub cookies: Vec<[OptionalStringNumber; 5]>, +} + +pub struct GetCookies; + +impl Endpoint for GetCookies { + type Request = (); + type Response = GetCookiesResponse; + + fn path() -> String { + String::from("manage_cookies/get_cookies") + } +} + +#[derive(Clone, Debug, Serialize)] +pub struct SetCookiesRequest { + pub cookies: Vec<[OptionalStringNumber; 5]>, +} + +pub struct SetCookies; + +impl Endpoint for SetCookies { + type Request = SetCookiesRequest; + type Response = (); + + fn path() -> String { + String::from("manage_cookies/set_cookies") + } +} + +pub struct CookieBuilder { + name: OptionalStringNumber, + value: OptionalStringNumber, + domain: OptionalStringNumber, + path: OptionalStringNumber, + expires: OptionalStringNumber, +} + +impl Default for CookieBuilder { + fn default() -> Self { + Self { + name: String::new().into(), + value: String::new().into(), + domain: String::new().into(), + path: String::new().into(), + expires: OptionalStringNumber::None, + } + } +} + +impl CookieBuilder { + pub fn name(mut self, name: S) -> Self { + self.name = name.to_string().into(); + self + } + + pub fn value(mut self, value: S) -> Self { + self.value = value.to_string().into(); + self + } + + pub fn domain(mut self, domain: S) -> Self { + self.domain = domain.to_string().into(); + self + } + + pub fn path(mut self, path: S) -> Self { + self.path = path.to_string().into(); + self + } + + pub fn expires(mut self, expires: u64) -> Self { + self.expires = expires.into(); + self + } + + pub fn build(self) -> [OptionalStringNumber; 5] { + [self.name, self.value, self.domain, self.path, self.expires] + } +} diff --git a/src/api_core/mod.rs b/src/api_core/mod.rs index f9e58a2..b046348 100644 --- a/src/api_core/mod.rs +++ b/src/api_core/mod.rs @@ -7,6 +7,7 @@ pub mod adding_tags; pub mod adding_urls; pub mod client; pub mod common; +pub mod managing_cookies_and_http_headers; pub mod managing_pages; pub mod searching_and_fetching_files; diff --git a/tests/client/mod.rs b/tests/client/mod.rs index 8f2f88c..8c61b9d 100644 --- a/tests/client/mod.rs +++ b/tests/client/mod.rs @@ -2,5 +2,6 @@ mod test_access_management; mod test_adding_files; mod test_adding_tags; mod test_adding_urls; +mod test_managing_cookies_and_http_headers; mod test_managing_pages; mod test_searching_and_fetching_files; diff --git a/tests/client/test_managing_cookies_and_http_headers.rs b/tests/client/test_managing_cookies_and_http_headers.rs new file mode 100644 index 0000000..e80203e --- /dev/null +++ b/tests/client/test_managing_cookies_and_http_headers.rs @@ -0,0 +1,20 @@ +use super::super::common; +use hydrus_api::api_core::managing_cookies_and_http_headers::CookieBuilder; + +#[tokio::test] +async fn it_returns_cookies_for_a_domain() { + let client = common::get_client(); + client.get_cookies("trivernis.net").await.unwrap(); +} + +#[tokio::test] +async fn it_sets_cookies_for_a_domain() { + let client = common::get_client(); + let cookie = CookieBuilder::default() + .name("my_cookie") + .value("my_value") + .domain("trivernis.net") + .path("/") + .build(); + client.set_cookies(vec![cookie]).await.unwrap(); +} From cd5de8ee1cf2cbcfad38947f4b911e9eb160d155 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 24 Jul 2021 18:24:42 +0200 Subject: [PATCH 2/4] Add function to set hydrus user agent Signed-off-by: trivernis --- src/api_core/client.rs | 13 ++++++++++++- .../managing_cookies_and_http_headers.rs | 17 +++++++++++++++++ .../test_managing_cookies_and_http_headers.rs | 9 +++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/api_core/client.rs b/src/api_core/client.rs index 81562a7..832be09 100644 --- a/src/api_core/client.rs +++ b/src/api_core/client.rs @@ -13,7 +13,8 @@ use crate::api_core::adding_urls::{ }; use crate::api_core::common::{FileIdentifier, FileMetadataInfo, FileRecord, OptionalStringNumber}; use crate::api_core::managing_cookies_and_http_headers::{ - GetCookies, GetCookiesResponse, SetCookies, SetCookiesRequest, + GetCookies, GetCookiesResponse, SetCookies, SetCookiesRequest, SetUserAgent, + SetUserAgentRequest, }; use crate::api_core::managing_pages::{ FocusPage, FocusPageRequest, GetPageInfo, GetPageInfoResponse, GetPages, GetPagesResponse, @@ -350,4 +351,14 @@ impl Client { Ok(()) } + + /// Sets the user agent that is being used for every request hydrus starts + pub async fn set_user_agent(&self, user_agent: S) -> Result<()> { + self.post::(SetUserAgentRequest { + user_agent: user_agent.to_string(), + }) + .await?; + + Ok(()) + } } diff --git a/src/api_core/managing_cookies_and_http_headers.rs b/src/api_core/managing_cookies_and_http_headers.rs index 3488034..1587657 100644 --- a/src/api_core/managing_cookies_and_http_headers.rs +++ b/src/api_core/managing_cookies_and_http_headers.rs @@ -83,3 +83,20 @@ impl CookieBuilder { [self.name, self.value, self.domain, self.path, self.expires] } } + +#[derive(Clone, Debug, Serialize)] +pub struct SetUserAgentRequest { + #[serde(rename = "user-agent")] + pub user_agent: String, +} + +pub struct SetUserAgent; + +impl Endpoint for SetUserAgent { + type Request = SetUserAgentRequest; + type Response = (); + + fn path() -> String { + String::from("manage_headers/set_user_agent") + } +} diff --git a/tests/client/test_managing_cookies_and_http_headers.rs b/tests/client/test_managing_cookies_and_http_headers.rs index e80203e..31e7b1e 100644 --- a/tests/client/test_managing_cookies_and_http_headers.rs +++ b/tests/client/test_managing_cookies_and_http_headers.rs @@ -18,3 +18,12 @@ async fn it_sets_cookies_for_a_domain() { .build(); client.set_cookies(vec![cookie]).await.unwrap(); } + +#[tokio::test] +async fn it_sets_the_user_agent() { + let client = common::get_client(); + client + .set_user_agent("Mozilla/5.0 (compatible; Hydrus Client)") + .await + .unwrap(); +} From d84eceb8408762f320efabfc6b4db901c18f80a8 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 24 Jul 2021 19:06:42 +0200 Subject: [PATCH 3/4] Add wrapper for addresses to set and get cookies Signed-off-by: trivernis --- src/api_core/common.rs | 18 +++++++ src/wrapper/address.rs | 99 +++++++++++++++++++++++++++++++++++ src/wrapper/hydrus.rs | 6 +++ src/wrapper/mod.rs | 1 + src/wrapper/url.rs | 11 ++++ tests/wrapper/mod.rs | 1 + tests/wrapper/test_address.rs | 31 +++++++++++ 7 files changed, 167 insertions(+) create mode 100644 src/wrapper/address.rs create mode 100644 tests/wrapper/test_address.rs diff --git a/src/api_core/common.rs b/src/api_core/common.rs index 3a18ba5..a23f9cb 100644 --- a/src/api_core/common.rs +++ b/src/api_core/common.rs @@ -80,3 +80,21 @@ impl From for OptionalStringNumber { Self::String(value) } } + +impl OptionalStringNumber { + pub fn string(&self) -> Option<&str> { + if let Self::String(s) = &self { + Some(s) + } else { + None + } + } + + pub fn number(&self) -> Option { + if let Self::Number(n) = &self { + Some(*n) + } else { + None + } + } +} diff --git a/src/wrapper/address.rs b/src/wrapper/address.rs new file mode 100644 index 0000000..b3f13c7 --- /dev/null +++ b/src/wrapper/address.rs @@ -0,0 +1,99 @@ +use crate::api_core::common::OptionalStringNumber; +use crate::api_core::managing_cookies_and_http_headers::CookieBuilder; +use crate::error::Result; +use crate::Client; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +pub struct Address { + client: Client, + domain: String, + path: String, +} + +impl Address { + pub(crate) fn from_str(client: Client, domain: &str) -> Self { + let (domain, path) = domain.split_once("/").unwrap_or((domain, "/")); + Self { + client, + domain: domain.to_string(), + path: path.to_string(), + } + } + + /// Returns the path after the domain name + pub fn path(&self) -> &str { + &self.path + } + + /// Sets the path of the domain that can be used for setting cookies + pub fn set_path(&mut self, path: S) { + self.path = path.to_string(); + } + + /// Sets cookies for the domain + pub async fn set_cookies(&self, cookies: Vec) -> Result<()> { + let cookies = cookies + .into_iter() + .map(|cookie| { + let mut builder = CookieBuilder::default() + .domain(&self.domain) + .path(&self.path) + .name(cookie.name) + .value(cookie.value); + if let Some(expires) = cookie.expires { + builder = + builder.expires(expires.duration_since(UNIX_EPOCH).unwrap().as_secs()); + } + builder.build() + }) + .collect(); + + self.client.set_cookies(cookies).await + } + + /// Returns all cookies stored for this domain + pub async fn get_cookies(&self) -> Result> { + let response = self.client.get_cookies(&self.domain).await?; + let cookies = response + .cookies + .into_iter() + .map(DomainCookie::from) + .collect(); + + Ok(cookies) + } +} + +#[derive(Clone, Debug)] +pub struct DomainCookie { + pub name: String, + pub value: String, + pub expires: Option, +} + +impl DomainCookie { + /// Creates a new cookie that will be expire after the given instant or only last for the session + pub fn new( + name: S1, + value: S2, + expires: Option, + ) -> Self { + Self { + name: name.to_string(), + value: value.to_string(), + expires, + } + } +} + +impl From<[OptionalStringNumber; 5]> for DomainCookie { + fn from(cookie_entry: [OptionalStringNumber; 5]) -> Self { + let name = cookie_entry[0].string().unwrap_or(""); + let value = cookie_entry[1].string().unwrap_or(""); + let expires = cookie_entry[4] + .number() + .map(|n| UNIX_EPOCH + Duration::from_secs(n)); + + Self::new(name, value, expires) + } +} diff --git a/src/wrapper/hydrus.rs b/src/wrapper/hydrus.rs index 33583b9..cc79b8c 100644 --- a/src/wrapper/hydrus.rs +++ b/src/wrapper/hydrus.rs @@ -2,6 +2,7 @@ use crate::api_core::common::FileIdentifier; use crate::api_core::searching_and_fetching_files::FileSearchLocation; use crate::error::Result; use crate::utils::tag_list_to_string_list; +use crate::wrapper::address::Address; use crate::wrapper::builders::import_builder::ImportBuilder; use crate::wrapper::builders::tagging_builder::TaggingBuilder; use crate::wrapper::hydrus_file::HydrusFile; @@ -47,6 +48,11 @@ impl Hydrus { } } + /// Returns the address as an object that can be used to get and set cookies + pub fn address>(&self, address: S) -> Address { + Address::from_str(self.client.clone(), address.as_ref()) + } + /// Returns information about a given url in an object that allows /// further operations with that url pub async fn url>(&self, url: S) -> Result { diff --git a/src/wrapper/mod.rs b/src/wrapper/mod.rs index 1b67080..32d5e62 100644 --- a/src/wrapper/mod.rs +++ b/src/wrapper/mod.rs @@ -1,3 +1,4 @@ +pub mod address; pub mod builders; pub mod hydrus; pub mod hydrus_file; diff --git a/src/wrapper/url.rs b/src/wrapper/url.rs index bd82056..dc7060f 100644 --- a/src/wrapper/url.rs +++ b/src/wrapper/url.rs @@ -2,6 +2,7 @@ use crate::api_core::adding_urls::{ URL_TYPE_FILE, URL_TYPE_GALLERY, URL_TYPE_POST, URL_TYPE_WATCHABLE, }; use crate::error::Result; +use crate::wrapper::address::Address; use crate::wrapper::builders::import_builder::UrlImportBuilder; use crate::wrapper::hydrus_file::HydrusFile; use crate::Client; @@ -59,6 +60,16 @@ impl Url { UrlImportBuilder::new(self.client.clone(), &self.url) } + /// Returns the address to manipulate cookies for this url + pub fn address(&self) -> Address { + let url = self + .normalised_url + .trim_start_matches("http://") + .trim_start_matches("https://"); + + Address::from_str(self.client.clone(), url) + } + /// Associates the url with a list of file hashes pub async fn associate(&mut self, hashes: Vec) -> Result<()> { self.client diff --git a/tests/wrapper/mod.rs b/tests/wrapper/mod.rs index 2133569..61e3aca 100644 --- a/tests/wrapper/mod.rs +++ b/tests/wrapper/mod.rs @@ -3,3 +3,4 @@ mod test_hydrus; mod test_import; mod test_url; mod test_page; +mod test_address; diff --git a/tests/wrapper/test_address.rs b/tests/wrapper/test_address.rs new file mode 100644 index 0000000..68b6f75 --- /dev/null +++ b/tests/wrapper/test_address.rs @@ -0,0 +1,31 @@ +use super::super::common; +use hydrus_api::wrapper::address::{Address, DomainCookie}; +use std::time::{Duration, SystemTime}; + +fn get_address() -> Address { + let hydrus = common::get_hydrus(); + + hydrus.address("trivernis.net/some/path") +} + +#[tokio::test] +async fn it_sets_cookies() { + let address = get_address(); + address + .set_cookies(vec![ + DomainCookie::new("name", "value", None), + DomainCookie::new( + "name2", + "value2", + Some(SystemTime::now() + Duration::from_secs(30)), + ), + ]) + .await + .unwrap(); +} + +#[tokio::test] +async fn it_retrieves_cookies() { + let address = get_address(); + address.get_cookies().await.unwrap(); +} From d48a5da4712a32e751840dd1d9891545b9758bf0 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 24 Jul 2021 19:09:01 +0200 Subject: [PATCH 4/4] Add function to set hydrus user agent to hydrus Signed-off-by: trivernis --- Cargo.toml | 2 +- src/wrapper/hydrus.rs | 5 +++++ tests/wrapper/test_hydrus.rs | 9 +++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 142146e..afd8a4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hydrus-api" -version = "0.3.1" +version = "0.3.2" authors = ["trivernis "] edition = "2018" license = "Apache-2.0" diff --git a/src/wrapper/hydrus.rs b/src/wrapper/hydrus.rs index cc79b8c..da49c01 100644 --- a/src/wrapper/hydrus.rs +++ b/src/wrapper/hydrus.rs @@ -121,4 +121,9 @@ impl Hydrus { pages_response.pages, )) } + + /// Sets the user agent hydrus uses for http requests + pub async fn set_user_agent(&self, user_agent: S) -> Result<()> { + self.client.set_user_agent(user_agent).await + } } diff --git a/tests/wrapper/test_hydrus.rs b/tests/wrapper/test_hydrus.rs index 794c347..605eeb1 100644 --- a/tests/wrapper/test_hydrus.rs +++ b/tests/wrapper/test_hydrus.rs @@ -60,3 +60,12 @@ async fn it_adds_tags() { .await .unwrap(); } + +#[tokio::test] +async fn it_sets_the_user_agent() { + let hydrus = common::get_hydrus(); + hydrus + .set_user_agent("Mozilla/5.0 (compatible; Hydrus Client)") + .await + .unwrap(); +}