diff --git a/Cargo.toml b/Cargo.toml index 466065e..23cd4fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,7 @@ repository = "https://github.com/trivernis/hydrus-api-rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -serde = "^1.0" -serde_derive = "^1.0" +serde = {version = "^1.0", features = ["derive"]} reqwest = {version = "0.11.4", features = ["json"]} log = "0.4.14" diff --git a/src/client.rs b/src/client.rs index 83c1bcc..1b2eddb 100644 --- a/src/client.rs +++ b/src/client.rs @@ -7,6 +7,10 @@ use crate::endpoints::adding_files::{ DeleteFilesRequest, UnarchiveFiles, UnarchiveFilesRequest, UndeleteFiles, UndeleteFilesRequest, }; use crate::endpoints::adding_tags::{AddTags, AddTagsRequest, CleanTags, CleanTagsResponse}; +use crate::endpoints::adding_urls::{ + AddUrl, AddUrlRequest, AddUrlResponse, AssociateUrl, AssociateUrlRequest, GetUrlFiles, + GetUrlFilesResponse, GetUrlInfo, GetUrlInfoResponse, +}; use crate::endpoints::common::{FileIdentifier, FileRecord}; use crate::endpoints::searching_and_fetching_files::{ FileMetadata, FileMetadataResponse, FileSearchLocation, GetFile, SearchFiles, @@ -241,4 +245,49 @@ impl Client { Ok(FileRecord { bytes, mime_type }) } + + /// Returns all files associated with the given url + pub async fn get_url_files>(&mut self, url: S) -> Result { + self.get_and_parse::(&[("url", url.as_ref())]) + .await + } + + /// Returns information about the given url + pub async fn get_url_info>(&mut self, url: S) -> Result { + self.get_and_parse::(&[("url", url.as_ref())]) + .await + } + + /// Adds an url to hydrus, optionally with additional tags and a destination page + pub async fn add_url(&mut self, request: AddUrlRequest) -> Result { + self.post_and_parse::(request).await + } + + /// Associates urls with the given file hashes + pub async fn associate_urls(&mut self, urls: Vec, hashes: Vec) -> Result<()> { + self.post::(AssociateUrlRequest { + hashes, + urls_to_add: urls, + urls_to_delete: vec![], + }) + .await?; + + Ok(()) + } + + /// Disassociates urls with the given file hashes + pub async fn disassociate_urls( + &mut self, + urls: Vec, + hashes: Vec, + ) -> Result<()> { + self.post::(AssociateUrlRequest { + hashes, + urls_to_add: vec![], + urls_to_delete: urls, + }) + .await?; + + Ok(()) + } } diff --git a/src/endpoints/adding_urls.rs b/src/endpoints/adding_urls.rs new file mode 100644 index 0000000..5b3068b --- /dev/null +++ b/src/endpoints/adding_urls.rs @@ -0,0 +1,187 @@ +use crate::endpoints::Endpoint; +use serde::Serialize; +use std::collections::HashMap; + +pub static URL_TYPE_POST: u8 = 0; +pub static URL_TYPE_FILE: u8 = 1; +pub static URL_TYPE_GALLERY: u8 = 2; +pub static URL_TYPE_WATCHABLE: u8 = 4; +pub static URL_TYPE_UNKNOWN: u8 = 5; + +#[derive(Clone, Debug, Deserialize)] +pub struct GetUrlFilesResponse { + pub normalised_url: String, + pub url_file_statuses: Vec, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct UrlFileStatus { + pub status: u32, + pub hash: String, + pub note: String, +} + +pub struct GetUrlFiles; + +impl Endpoint for GetUrlFiles { + type Request = (); + type Response = GetUrlFilesResponse; + + fn get_path() -> String { + String::from("add_urls/get_url_files") + } +} + +#[derive(Clone, Debug, Deserialize)] +pub struct GetUrlInfoResponse { + pub normalised_url: String, + pub url_type: u8, + pub url_type_string: String, + pub match_name: String, + pub can_parse: bool, +} + +pub struct GetUrlInfo; + +impl Endpoint for GetUrlInfo { + type Request = (); + type Response = GetUrlInfoResponse; + + fn get_path() -> String { + String::from("add_urls/get_url_info") + } +} + +#[derive(Clone, Debug, Serialize)] +pub struct AddUrlRequest { + pub url: String, + + #[serde(skip_serializing_if = "Option::is_none")] + pub destination_page_key: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub destination_page_name: Option, + + pub show_destination_page: bool, + pub service_names_to_additional_tags: HashMap>, + pub filterable_tags: Vec, +} + +/// A request builder that can be used to create a request for adding urls +/// without having to fill a huge struct manually +/// +/// Example: +/// ``` +/// use hydrus_api::endpoints::adding_urls::AddUrlRequestBuilder; +/// +/// let request = AddUrlRequestBuilder::default() +/// .url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") +/// .add_tags("my tags", vec!["ark mage".to_string(), "grinning".to_string()]) +/// .show_destination_page(true) +/// .destination_page_name("Rusty Url Import") +/// .build(); +/// ``` +pub struct AddUrlRequestBuilder { + inner: AddUrlRequest, +} + +impl Default for AddUrlRequestBuilder { + fn default() -> Self { + Self { + inner: AddUrlRequest { + url: String::new(), + destination_page_key: None, + destination_page_name: None, + show_destination_page: false, + service_names_to_additional_tags: Default::default(), + filterable_tags: vec![], + }, + } + } +} + +impl AddUrlRequestBuilder { + pub fn url(mut self, url: S) -> Self { + self.inner.url = url.to_string(); + + self + } + + pub fn destination_page_key(mut self, page_key: S) -> Self { + self.inner.destination_page_key = Some(page_key.to_string()); + + self + } + + pub fn destination_page_name(mut self, page_name: S) -> Self { + self.inner.destination_page_name = Some(page_name.to_string()); + + self + } + + pub fn show_destination_page(mut self, show: bool) -> Self { + self.inner.show_destination_page = show; + + self + } + + pub fn add_tags>(mut self, service: S, mut tags: Vec) -> Self { + if let Some(entry) = self + .inner + .service_names_to_additional_tags + .get_mut(service.as_ref()) + { + entry.append(&mut tags); + } else { + self.inner + .service_names_to_additional_tags + .insert(service.as_ref().to_string(), tags); + } + + self + } + + pub fn add_filter_tags(mut self, mut filter_tags: Vec) -> Self { + self.inner.filterable_tags.append(&mut filter_tags); + + self + } + + pub fn build(self) -> AddUrlRequest { + self.inner + } +} + +#[derive(Clone, Debug, Deserialize)] +pub struct AddUrlResponse { + pub human_result_text: String, + pub normalised_url: String, +} + +pub struct AddUrl; + +impl Endpoint for AddUrl { + type Request = AddUrlRequest; + type Response = AddUrlResponse; + + fn get_path() -> String { + String::from("add_urls/add_url") + } +} + +#[derive(Clone, Debug, Serialize)] +pub struct AssociateUrlRequest { + pub urls_to_add: Vec, + pub urls_to_delete: Vec, + pub hashes: Vec, +} +pub struct AssociateUrl; + +impl Endpoint for AssociateUrl { + type Request = AssociateUrlRequest; + type Response = (); + + fn get_path() -> String { + String::from("add_urls/associate_url") + } +} diff --git a/src/endpoints/mod.rs b/src/endpoints/mod.rs index 4adb327..13cda47 100644 --- a/src/endpoints/mod.rs +++ b/src/endpoints/mod.rs @@ -4,6 +4,7 @@ use serde::Serialize; pub mod access_management; pub mod adding_files; pub mod adding_tags; +pub mod adding_urls; pub mod common; pub mod searching_and_fetching_files; diff --git a/src/lib.rs b/src/lib.rs index a62cf9d..068f010 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ //! ``` #[macro_use] -extern crate serde_derive; +extern crate serde; pub mod client; pub mod endpoints; diff --git a/tests/test_adding_urls.rs b/tests/test_adding_urls.rs new file mode 100644 index 0000000..f8672ef --- /dev/null +++ b/tests/test_adding_urls.rs @@ -0,0 +1,71 @@ +use hydrus_api::endpoints::adding_urls::{AddUrlRequestBuilder, URL_TYPE_POST}; + +mod common; + +#[tokio::test] +async fn it_returns_files_for_an_url() { + let mut client = common::get_client(); + let response = client + .get_url_files("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") + .await + .unwrap(); + + assert!(response.normalised_url.len() > 0); +} + +#[tokio::test] +async fn it_returns_url_information() { + let mut client = common::get_client(); + let info = client + .get_url_info("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") + .await + .unwrap(); + assert!(info.normalised_url.len() > 0); + assert_eq!(info.url_type, URL_TYPE_POST); +} + +#[tokio::test] +async fn it_adds_urls() { + let mut client = common::get_client(); + let request = AddUrlRequestBuilder::default() + .url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") + .add_tags( + "my tags", + vec!["ark mage".to_string(), "grinning".to_string()], + ) + .show_destination_page(true) + .destination_page_name("Rusty Url Import") + .build(); + let response = client.add_url(request).await.unwrap(); + assert!(response.normalised_url.len() > 0); +} + +#[tokio::test] +async fn it_associates_urls() { + let mut client = common::get_client(); + client + .associate_urls( + vec![ + "https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium" + .to_string(), + ], + vec!["0000000000000000000000000000000000000000000000000000000000000000".to_string()], + ) + .await + .unwrap(); +} + +#[tokio::test] +async fn it_disassociates_urls() { + let mut client = common::get_client(); + client + .disassociate_urls( + vec![ + "https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium" + .to_string(), + ], + vec!["0000000000000000000000000000000000000000000000000000000000000000".to_string()], + ) + .await + .unwrap(); +}