diff --git a/src/client.rs b/src/client.rs index ded1b05..1cfdc07 100644 --- a/src/client.rs +++ b/src/client.rs @@ -6,7 +6,8 @@ mod rate_limit; mod rate_limit; use futures::Future; -use reqwest::Url; +use reqwest::{Response, Url}; +use serde::Serialize; use { super::error::{Error, Result}, @@ -138,7 +139,7 @@ impl Client { Ok(url) } - pub(crate) async fn post_form(&self, endpoint: &str, body: &T) -> Result + async fn post_response(&self, endpoint: &str, body: &T) -> Result where T: serde::Serialize, { @@ -162,9 +163,7 @@ impl Client { .map_err(|e| Error::CannotSendRequest(format!("{}", e)))?; if res.status().is_success() { - res.json() - .await - .map_err(|e| Error::Serial(format!("{}", e))) + Ok(res) } else { Err(Error::Http { url, @@ -179,6 +178,30 @@ impl Client { .await } + pub(crate) async fn post_form(&self, endpoint: &str, body: &T) -> Result + where + T: serde::Serialize, + { + self.post_response(endpoint, body) + .await? + .json() + .await + .map_err(|e| Error::Serial(format!("{e}"))) + } + + pub(crate) async fn delete(&self, endpoint: &str) -> Result<()> { + #[derive(Serialize)] + struct Form { + _method: &'static str, + } + + // Can't use HTTP DELETE because e621's CORS headers aren't permissive enough. Thankfully + // ruby on rails has a workaround for exactly this purpose. + self.post_response(endpoint, &Form { _method: "delete" }) + .await?; + Ok(()) + } + pub fn get_json_endpoint( &self, endpoint: &str, diff --git a/src/post.rs b/src/post.rs index e01eeb5..c2464c5 100644 --- a/src/post.rs +++ b/src/post.rs @@ -637,6 +637,25 @@ impl Client { serde_json::from_value(value).map_err(|e| Error::Serial(format!("{}", e))) } + /// Mark a [`Post`] (identified by `id`) as no longer particularly liked. + /// + /// ```no_run + /// # use { + /// # rs621::client::Client, + /// # futures::prelude::*, + /// # }; + /// # #[tokio::main] + /// # async fn main() -> rs621::error::Result<()> { + /// let client = Client::new("https://e926.net", "MyProject/1.0 (by username on e621)")?; + /// + /// client.post_unfavorite(1234).await?; + /// # Ok(()) } + /// ``` + pub async fn post_unfavorite(&self, id: u64) -> Result<(), Error> { + self.delete(&format!("/favorites/{id}.json")).await?; + Ok(()) + } + /// Vote a [`Post`] (identified by `id`) up or down. /// /// Use [`VoteDir::Toggle`] to clear an existing vote. @@ -746,6 +765,21 @@ mod tests { ); } + #[tokio::test] + async fn post_unfavorite() { + let mut client = Client::new(&mockito::server_url(), b"rs621/unit_test").unwrap(); + client.login("foo".into(), "bar".into()); + + let _m = mock( + "POST", + Matcher::Exact("/favorites/3758515.json?login=foo&api_key=bar".into()), + ) + .match_body("_method=delete") + .create(); + + client.post_unfavorite(3758515).await.unwrap(); + } + #[tokio::test] async fn search_ordered() { let client = Client::new(&mockito::server_url(), b"rs621/unit_test").unwrap();