Merge pull request #13 from Trivernis/develop

Develop
main
Julius Riegel 3 years ago committed by GitHub
commit 06be615b1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,24 @@
name: Publish a release
on:
push:
branches: [ main ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Login
env:
CRATES_IO_TOKEN: ${{secrets.CRATES_IO_TOKEN}}
run: cargo login "$CRATES_IO_TOKEN"
- name: Publish to crates.io
run: cargo publish --all-features

@ -1,6 +1,6 @@
[package]
name = "hydrus-api"
version = "0.7.0"
version = "0.7.1"
authors = ["trivernis <trivernis@protonmail.com>"]
edition = "2018"
license = "Apache-2.0"
@ -12,11 +12,11 @@ repository = "https://github.com/trivernis/hydrus-api-rs"
[dependencies]
serde = { version = "1.0.136", features = ["derive"] }
reqwest = { version = "0.11.9", features = ["json"] }
tracing = "0.1.31"
reqwest = { version = "0.11.10", features = ["json"] }
tracing = "0.1.32"
mime = "0.3.16"
chrono = "0.4.19"
regex = "1.5.4"
regex = "1.5.5"
lazy_static = "1.4.0"
[dev-dependencies]

@ -0,0 +1,73 @@
use crate::api_core::common::FileIdentifier;
use crate::api_core::Endpoint;
use std::collections::HashMap;
pub struct SetNotes;
impl Endpoint for SetNotes {
type Request = SetNotesRequest;
type Response = ();
fn path() -> String {
String::from("add_notes/set_notes")
}
}
#[derive(Serialize, Clone, Debug, Default)]
pub struct SetNotesRequest {
notes: HashMap<String, String>,
#[serde(skip_serializing_if = "Option::is_none")]
hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
file_id: Option<u64>,
}
impl SetNotesRequest {
pub fn new(id: FileIdentifier, notes: HashMap<String, String>) -> Self {
let mut request = Self {
notes,
..Default::default()
};
match id {
FileIdentifier::ID(id) => request.file_id = Some(id),
FileIdentifier::Hash(hash) => request.hash = Some(hash),
}
request
}
}
pub struct DeleteNotes;
impl Endpoint for DeleteNotes {
type Request = DeleteNotesRequest;
type Response = ();
fn path() -> String {
String::from("add_notes/delete_notes")
}
}
#[derive(Serialize, Clone, Debug, Default)]
pub struct DeleteNotesRequest {
note_names: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
file_id: Option<u64>,
}
impl DeleteNotesRequest {
pub fn new(id: FileIdentifier, note_names: Vec<String>) -> Self {
let mut request = Self {
note_names,
..Default::default()
};
match id {
FileIdentifier::ID(id) => request.file_id = Some(id),
FileIdentifier::Hash(hash) => request.hash = Some(hash),
}
request
}
}

@ -6,6 +6,7 @@ use crate::api_core::adding_files::{
AddFile, AddFileRequest, AddFileResponse, ArchiveFiles, ArchiveFilesRequest, DeleteFiles,
DeleteFilesRequest, UnarchiveFiles, UnarchiveFilesRequest, UndeleteFiles, UndeleteFilesRequest,
};
use crate::api_core::adding_notes::{DeleteNotes, DeleteNotesRequest, SetNotes, SetNotesRequest};
use crate::api_core::adding_tags::{AddTags, AddTagsRequest, CleanTags, CleanTagsResponse};
use crate::api_core::adding_urls::{
AddUrl, AddUrlRequest, AddUrlResponse, AssociateUrl, AssociateUrlRequest, GetUrlFiles,
@ -33,6 +34,7 @@ use crate::utils::{
use reqwest::Response;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::collections::HashMap;
use std::fmt::Debug;
static ACCESS_KEY_HEADER: &str = "Hydrus-Client-API-Access-Key";
@ -375,6 +377,28 @@ impl Client {
Ok(())
}
/// Sets the notes for the file
#[tracing::instrument(skip(self), level = "debug")]
pub async fn set_notes(
&self,
id: FileIdentifier,
notes: HashMap<String, String>,
) -> Result<()> {
self.post::<SetNotes>(SetNotesRequest::new(id, notes))
.await?;
Ok(())
}
/// Deletes the notes of a file
#[tracing::instrument(skip(self), level = "debug")]
pub async fn delete_notes(&self, id: FileIdentifier, note_names: Vec<String>) -> Result<()> {
self.post::<DeleteNotes>(DeleteNotesRequest::new(id, note_names))
.await?;
Ok(())
}
/// Returns all pages of the client
#[tracing::instrument(skip(self), level = "debug")]
pub async fn get_pages(&self) -> Result<GetPagesResponse> {

@ -80,6 +80,22 @@ impl FileIdentifier {
pub fn hash<S: ToString>(hash: S) -> Self {
Self::Hash(hash.to_string())
}
pub fn as_hash(&self) -> Option<&String> {
if let Self::Hash(h) = &self {
Some(h)
} else {
None
}
}
pub fn as_id(&self) -> Option<u64> {
if let Self::ID(id) = &self {
Some(*id)
} else {
None
}
}
}
#[derive(Clone)]

@ -12,6 +12,7 @@ pub mod common;
pub mod managing_cookies_and_http_headers;
pub mod managing_pages;
pub mod searching_and_fetching_files;
pub mod adding_notes;
pub use searching_and_fetching_files::file_sort_type;

@ -3,3 +3,4 @@ pub mod or_chain_builder;
pub mod search_builder;
pub mod tag_builder;
pub mod tagging_builder;
pub mod notes_builder;

@ -0,0 +1,47 @@
use crate::api_core::common::FileIdentifier;
use crate::error::Result;
use crate::Client;
use std::collections::HashMap;
/// Builder to create a request for adding notes to a given file
pub struct AddNotesBuilder {
client: Client,
file: FileIdentifier,
notes: HashMap<String, String>,
}
impl AddNotesBuilder {
/// Creates a new notes builder for the given file id
pub fn new(client: Client, file: FileIdentifier) -> Self {
Self {
client,
file,
notes: HashMap::new(),
}
}
/// Adds a single note
pub fn add_note<S1: ToString, S2: ToString>(mut self, name: S1, note: S2) -> Self {
self.notes.insert(name.to_string(), note.to_string());
self
}
/// Adds multiple notes to the builder
pub fn add_notes<I: IntoIterator<Item = (S1, S2)>, S1: ToString, S2: ToString>(
mut self,
notes: I,
) -> Self {
let notes_iter = notes
.into_iter()
.map(|(k, v): (S1, S2)| (k.to_string(), v.to_string()));
self.notes.extend(notes_iter);
self
}
/// Adds all notes mentioned in the builder to the given file
pub async fn run(self) -> Result<()> {
self.client.set_notes(self.file, self.notes).await
}
}

@ -2,6 +2,7 @@ use crate::api_core::adding_tags::{AddTagsRequestBuilder, TagAction};
use crate::api_core::common::{FileIdentifier, FileMetadataInfo, FileRecord, ServiceIdentifier};
use crate::error::{Error, Result};
use crate::utils::tag_list_to_string_list;
use crate::wrapper::builders::notes_builder::AddNotesBuilder;
use crate::wrapper::service::ServiceName;
use crate::wrapper::tag::Tag;
use crate::Client;
@ -318,6 +319,27 @@ impl HydrusFile {
self.client.add_tags(reqwest.build()).await
}
/// Creates a builder to add notes to the file
pub fn add_notes(&self) -> AddNotesBuilder {
AddNotesBuilder::new(self.client.clone(), self.id.clone())
}
/// Deletes a single note from the file
pub async fn delete_note<S1: ToString>(&self, name: S1) -> Result<()> {
self.client
.delete_notes(self.id.clone(), vec![name.to_string()])
.await
}
/// Deletes multiple notes from the file
pub async fn delete_notes<I: IntoIterator<Item = S>, S: ToString>(
&self,
names: I,
) -> Result<()> {
let names = names.into_iter().map(|n: S| n.to_string()).collect();
self.client.delete_notes(self.id.clone(), names).await
}
/// Retrieves the file record bytes
pub async fn retrieve(&self) -> Result<FileRecord> {
self.client.get_file(self.id.clone()).await

@ -5,3 +5,4 @@ mod test_adding_urls;
mod test_managing_cookies_and_http_headers;
mod test_managing_pages;
mod test_searching_and_fetching_files;
mod test_deleting_notes;

@ -1,4 +1,6 @@
use super::super::common;
use crate::common;
use crate::common::create_testdata;
use crate::common::test_data::get_test_hashes;
#[tokio::test]
async fn it_adds_files() {
@ -20,23 +22,26 @@ async fn it_adds_binary_files() {
#[tokio::test]
async fn it_deletes_files() {
let client = common::get_client();
client.delete_files(vec![]).await.unwrap();
client.delete_files(get_test_hashes()).await.unwrap();
}
#[tokio::test]
async fn it_undeletes_files() {
let client = common::get_client();
client.undelete_files(vec![]).await.unwrap();
create_testdata(&client).await;
client.undelete_files(get_test_hashes()).await.unwrap();
}
#[tokio::test]
async fn it_archives_files() {
let client = common::get_client();
client.archive_files(vec![]).await.unwrap();
create_testdata(&client).await;
client.archive_files(get_test_hashes()).await.unwrap();
}
#[tokio::test]
async fn it_unarchives_files() {
let client = common::get_client();
client.unarchive_files(vec![]).await.unwrap();
create_testdata(&client).await;
client.unarchive_files(get_test_hashes()).await.unwrap();
}

@ -1,4 +1,5 @@
use super::super::common;
use crate::common::test_data::EMPTY_HASH;
use hydrus_api::api_core::adding_tags::{AddTagsRequestBuilder, TagAction};
use hydrus_api::api_core::common::ServiceIdentifier;
@ -22,7 +23,7 @@ async fn it_adds_tags() {
#![allow(deprecated)]
let client = common::get_client();
let request = AddTagsRequestBuilder::default()
.add_hash("0000000000000000000000000000000000000000000000000000000000000000") // valid hash, I hope no files are affected
.add_hash(EMPTY_HASH) // valid hash, I hope no files are affected
.add_tags(
ServiceIdentifier::name("my tags"),
vec!["beach".into(), "summer".into()],

@ -1,14 +1,12 @@
use super::super::common;
use crate::common::test_data::{get_test_hashes, get_test_urls, TEST_URL_1};
use hydrus_api::api_core::adding_urls::{AddUrlRequestBuilder, URL_TYPE_POST};
use hydrus_api::api_core::common::ServiceIdentifier;
#[tokio::test]
async fn it_returns_files_for_an_url() {
let client = common::get_client();
let response = client
.get_url_files("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium")
.await
.unwrap();
let response = client.get_url_files(TEST_URL_1).await.unwrap();
assert!(response.normalised_url.len() > 0);
}
@ -16,10 +14,7 @@ async fn it_returns_files_for_an_url() {
#[tokio::test]
async fn it_returns_url_information() {
let client = common::get_client();
let info = client
.get_url_info("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium")
.await
.unwrap();
let info = client.get_url_info(TEST_URL_1).await.unwrap();
assert!(info.normalised_url.len() > 0);
assert_eq!(info.url_type, URL_TYPE_POST);
}
@ -29,7 +24,7 @@ async fn it_adds_urls() {
#![allow(deprecated)]
let client = common::get_client();
let request = AddUrlRequestBuilder::default()
.url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium")
.url(TEST_URL_1)
.add_tags(
ServiceIdentifier::name("my tags"),
vec!["ark mage".to_string(), "grinning".to_string()],
@ -44,14 +39,9 @@ async fn it_adds_urls() {
#[tokio::test]
async fn it_associates_urls() {
let client = common::get_client();
common::create_testdata(&client).await;
client
.associate_urls(
vec![
"https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium"
.to_string(),
],
vec!["0000000000000000000000000000000000000000000000000000000000000000".to_string()],
)
.associate_urls(get_test_urls(), get_test_hashes())
.await
.unwrap();
}
@ -59,14 +49,9 @@ async fn it_associates_urls() {
#[tokio::test]
async fn it_disassociates_urls() {
let client = common::get_client();
common::create_testdata(&client).await;
client
.disassociate_urls(
vec![
"https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium"
.to_string(),
],
vec!["0000000000000000000000000000000000000000000000000000000000000000".to_string()],
)
.disassociate_urls(get_test_urls(), get_test_hashes())
.await
.unwrap();
}

@ -0,0 +1,27 @@
use super::super::common;
use crate::common::test_data::TEST_HASH_1;
use hydrus_api::api_core::common::FileIdentifier;
use std::collections::HashMap;
#[tokio::test]
async fn it_sets_notes() {
let client = common::get_client();
common::create_testdata(&client).await;
let mut test_notes = HashMap::new();
test_notes.insert("test".to_string(), "value".to_string());
test_notes.insert("test2".to_string(), "value".to_string());
client
.set_notes(FileIdentifier::hash(TEST_HASH_1), test_notes)
.await
.unwrap();
}
#[tokio::test]
async fn it_deletes_notes() {
let client = common::get_client();
client
.delete_notes(FileIdentifier::hash(TEST_HASH_1), vec!["test".to_string()])
.await
.unwrap();
}

@ -1,8 +1,11 @@
use hydrus_api::api_core::adding_urls::AddUrlRequestBuilder;
use hydrus_api::api_core::client::Client;
use hydrus_api::Hydrus;
use std::env;
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::Duration;
use test_data::TEST_URLS;
pub mod test_data;
pub fn setup() {
lazy_static::lazy_static! { static ref SETUP_DONE: Arc<Mutex<bool>> = Arc::new(Mutex::new(false)); }
@ -30,3 +33,12 @@ pub fn get_hydrus() -> Hydrus {
Hydrus::new(client)
}
pub async fn create_testdata(client: &Client) {
for url in TEST_URLS {
client
.add_url(AddUrlRequestBuilder::default().url(url).build())
.await
.unwrap();
}
}

@ -0,0 +1,17 @@
pub const TEST_HASH_1: &str = "277a138cd1ee79fc1fdb2869c321b848d4861e45b82184487139ef66dd40b62d";
pub const TEST_HASH_2: &str = "9641a590e66d9f2e5137b6bcba07fdf6cec3ffaa54de2565c3afcc2125ad1160";
pub const EMPTY_HASH: &str = "0000000000000000000000000000000000000000000000000000000000000000";
pub const TEST_HASHES: &[&str] = &[TEST_HASH_1, TEST_HASH_2];
pub const TEST_URL_1: &str =
"https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium";
pub const TEST_URL_2: &str = "https://yande.re/post/show/923576";
pub const TEST_URLS: &[&str] = &[TEST_URL_1, TEST_URL_2];
pub fn get_test_hashes() -> Vec<String> {
TEST_HASHES.iter().map(|h| String::from(*h)).collect()
}
pub fn get_test_urls() -> Vec<String> {
TEST_URLS.iter().map(|u| String::from(*u)).collect()
}

@ -73,6 +73,23 @@ async fn it_modifies_tags() {
.unwrap();
}
#[tokio::test]
async fn it_adds_notes() {
let file = get_file().await;
file.add_notes()
.add_note("My Note", "My notes content")
.add_notes(vec![("My note 2", "More content")])
.run()
.await
.unwrap();
}
#[tokio::test]
async fn it_deletes_notes() {
let file = get_file().await;
file.delete_note("My Note").await.unwrap();
}
#[tokio::test]
async fn it_retrieves_content() {
let file = get_file().await;

Loading…
Cancel
Save