diff --git a/src/entities/filter.rs b/src/entities/filter.rs new file mode 100644 index 0000000..29dcb27 --- /dev/null +++ b/src/entities/filter.rs @@ -0,0 +1,27 @@ +/// Represents a single Filter +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Filter { + id: String, + phrase: String, + context: Vec, + expires_at: Option, // TODO: timestamp + irreversible: bool, + whole_word: bool, +} + +/// Represents the various types of Filter contexts +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub enum FilterContext { + /// Represents the "home" context + #[serde(rename = "home")] + Home, + /// Represents the "notifications" context + #[serde(rename = "notifications")] + Notifications, + /// Represents the "public" context + #[serde(rename = "public")] + Public, + /// Represents the "thread" context + #[serde(rename = "thread")] + Thread, +} diff --git a/src/entities/mod.rs b/src/entities/mod.rs index 7c79bbb..69e13bf 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -6,6 +6,8 @@ pub mod attachment; pub mod card; /// Data structures for ser/de of contetx-related resources pub mod context; +/// Data structures for ser/de of filter-related resources +pub mod filter; /// Data structures for ser/de of instance-related resources pub mod instance; pub(crate) mod itemsiter; @@ -39,6 +41,7 @@ pub mod prelude { attachment::{Attachment, MediaType}, card::Card, context::Context, + filter::{Filter, FilterContext}, instance::*, list::List, mention::Mention, diff --git a/src/lib.rs b/src/lib.rs index c45e78f..b4c0a0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,7 +89,13 @@ pub use errors::{ApiError, Error, Result}; pub use isolang::Language; pub use mastodon_client::MastodonClient; pub use registration::Registration; -pub use requests::{AddPushRequest, StatusesRequest, UpdateCredsRequest, UpdatePushRequest}; +pub use requests::{ + AddFilterRequest, + AddPushRequest, + StatusesRequest, + UpdateCredsRequest, + UpdatePushRequest, +}; pub use status_builder::StatusBuilder; /// Registering your App @@ -193,11 +199,13 @@ impl MastodonClient for Mastodon { (post (id: &str,)) authorize_follow_request: "accounts/follow_requests/authorize" => Empty, (post (id: &str,)) reject_follow_request: "accounts/follow_requests/reject" => Empty, (get (q: &'a str, resolve: bool,)) search: "search" => SearchResult, + (get (local: bool,)) get_public_timeline: "timelines/public" => Vec, (post (uri: Cow<'static, str>,)) follows: "follows" => Account, (post multipart (file: Cow<'static, str>,)) media: "media" => Attachment, (post) clear_notifications: "notifications/clear" => Empty, (get) get_push_subscription: "push/subscription" => Subscription, (delete) delete_push_subscription: "push/subscription" => Empty, + (get) get_filters: "filters" => Vec, } route_v2! { @@ -221,6 +229,39 @@ impl MastodonClient for Mastodon { (post) favourite: "statuses/{}/favourite" => Status, (post) unfavourite: "statuses/{}/unfavourite" => Status, (delete) delete_status: "statuses/{}" => Empty, + (get) get_filter: "filters/{}" => Filter, + (delete) delete_filter: "filters/{}" => Empty, + } + + fn add_filter(&self, request: &mut AddFilterRequest) -> Result { + let url = self.route("/api/v1/filters"); + let response = self.send(self.client.post(&url).json(&request))?; + + let status = response.status(); + + if status.is_client_error() { + return Err(Error::Client(status.clone())); + } else if status.is_server_error() { + return Err(Error::Server(status.clone())); + } + + deserialise(response) + } + + /// PUT /api/v1/filters/:id + fn update_filter(&self, id: u64, request: &mut AddFilterRequest) -> Result { + let url = self.route(&format!("/api/v1/filters/{}", id)); + let response = self.send(self.client.put(&url).json(&request))?; + + let status = response.status(); + + if status.is_client_error() { + return Err(Error::Client(status.clone())); + } else if status.is_server_error() { + return Err(Error::Server(status.clone())); + } + + deserialise(response) } fn update_credentials(&self, builder: &mut UpdateCredsRequest) -> Result { @@ -228,12 +269,12 @@ impl MastodonClient for Mastodon { let url = self.route("/api/v1/accounts/update_credentials"); let response = self.send(self.client.patch(&url).json(&changes))?; - let status = response.status().clone(); + let status = response.status(); if status.is_client_error() { - return Err(Error::Client(status)); + return Err(Error::Client(status.clone())); } else if status.is_server_error() { - return Err(Error::Server(status)); + return Err(Error::Server(status.clone())); } deserialise(response) @@ -250,26 +291,15 @@ impl MastodonClient for Mastodon { deserialise(response) } - /// Get the federated timeline for the instance. - fn get_public_timeline(&self, local: bool) -> Result> { - let mut url = self.route("/api/v1/timelines/public"); - - if local { - url += "?local=1"; - } - - self.get(url) - } - /// Get timeline filtered by a hashtag(eg. `#coffee`) either locally or /// federated. fn get_tagged_timeline(&self, hashtag: String, local: bool) -> Result> { - let mut url = self.route("/api/v1/timelines/tag/"); - url += &hashtag; - - if local { - url += "?local=1"; - } + let base = "/api/v1/timelines/tag/"; + let url = if local { + self.route(&format!("{}{}?local=1", base, hashtag)) + } else { + self.route(&format!("{}{}", base, hashtag)) + }; self.get(url) } diff --git a/src/mastodon_client.rs b/src/mastodon_client.rs index 827abee..539886f 100644 --- a/src/mastodon_client.rs +++ b/src/mastodon_client.rs @@ -4,7 +4,13 @@ use entities::prelude::*; use errors::Result; use http_send::{HttpSend, HttpSender}; use page::Page; -use requests::{AddPushRequest, StatusesRequest, UpdateCredsRequest, UpdatePushRequest}; +use requests::{ + AddFilterRequest, + AddPushRequest, + StatusesRequest, + UpdateCredsRequest, + UpdatePushRequest, +}; use status_builder::StatusBuilder; /// Represents the set of methods that a Mastodon Client can do, so that @@ -227,4 +233,24 @@ pub trait MastodonClient { fn delete_push_subscription(&self) -> Result { unimplemented!("This method was not implemented"); } + /// GET /api/v1/filters + fn get_filters(&self) -> Result> { + unimplemented!("This method was not implemented"); + } + /// POST /api/v1/filters + fn add_filter(&self, request: &mut AddFilterRequest) -> Result { + unimplemented!("This method was not implemented"); + } + /// GET /api/v1/filters/:id + fn get_filter(&self, id: u64) -> Result { + unimplemented!("This method was not implemented"); + } + /// PUT /api/v1/filters/:id + fn update_filter(&self, id: u64, request: &mut AddFilterRequest) -> Result { + unimplemented!("This method was not implemented"); + } + /// DELETE /api/v1/filters/:id + fn delete_filter(&self, id: u64) -> Result { + unimplemented!("This method was not implemented"); + } } diff --git a/src/requests/filter.rs b/src/requests/filter.rs new file mode 100644 index 0000000..6f3c700 --- /dev/null +++ b/src/requests/filter.rs @@ -0,0 +1,154 @@ +use entities::filter::FilterContext; +use std::time::Duration; + +/// Form used to create a filter +/// +/// # Example +/// +/// ``` +/// # extern crate elefren; +/// # use std::error::Error; +/// use elefren::{entities::filter::FilterContext, requests::AddFilterRequest}; +/// # fn main() -> Result<(), Box> { +/// let request = AddFilterRequest::new("foo", FilterContext::Home); +/// # Ok(()) +/// # } +/// ``` +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct AddFilterRequest { + phrase: String, + context: FilterContext, + irreversible: Option, + whole_word: Option, + #[serde(serialize_with = "serialize_duration::ser")] + expires_in: Option, +} + +impl AddFilterRequest { + /// Create a new AddFilterRequest + pub fn new(phrase: &str, context: FilterContext) -> AddFilterRequest { + AddFilterRequest { + phrase: phrase.to_string(), + context, + irreversible: None, + whole_word: None, + expires_in: None, + } + } + + /// Set `irreversible` to `true` + pub fn irreversible(&mut self) -> &mut Self { + self.irreversible = Some(true); + self + } + + /// Set `whole_word` to `true` + pub fn whole_word(&mut self) -> &mut Self { + self.whole_word = Some(true); + self + } + + /// Set `expires_in` to a duration + pub fn expires_in(&mut self, d: Duration) -> &mut Self { + self.expires_in = Some(d); + self + } +} + +mod serialize_duration { + use serde::ser::Serializer; + use std::time::Duration; + + pub(crate) fn ser(duration: &Option, s: S) -> Result + where + S: Serializer, + { + if let Some(d) = duration { + let sec = d.as_secs(); + s.serialize_u64(sec) + } else { + s.serialize_none() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json; + use std::time::Duration; + + #[test] + fn test_new() { + let request = AddFilterRequest::new("foo", FilterContext::Home); + assert_eq!( + request, + AddFilterRequest { + phrase: "foo".to_string(), + context: FilterContext::Home, + irreversible: None, + whole_word: None, + expires_in: None, + } + ) + } + + #[test] + fn test_irreversible() { + let mut request = AddFilterRequest::new("foo", FilterContext::Home); + request.irreversible(); + assert_eq!( + request, + AddFilterRequest { + phrase: "foo".to_string(), + context: FilterContext::Home, + irreversible: Some(true), + whole_word: None, + expires_in: None, + } + ) + } + + #[test] + fn test_whole_word() { + let mut request = AddFilterRequest::new("foo", FilterContext::Home); + request.whole_word(); + assert_eq!( + request, + AddFilterRequest { + phrase: "foo".to_string(), + context: FilterContext::Home, + irreversible: None, + whole_word: Some(true), + expires_in: None, + } + ) + } + + #[test] + fn test_expires_in() { + let mut request = AddFilterRequest::new("foo", FilterContext::Home); + request.expires_in(Duration::from_secs(300)); + assert_eq!( + request, + AddFilterRequest { + phrase: "foo".to_string(), + context: FilterContext::Home, + irreversible: None, + whole_word: None, + expires_in: Some(Duration::from_secs(300)), + } + ) + } + + #[test] + fn test_serialize_request() { + let mut request = AddFilterRequest::new("foo", FilterContext::Home); + request.expires_in(Duration::from_secs(300)); + let ser = serde_json::to_string(&request).expect("Couldn't serialize"); + assert_eq!( + ser, + r#"{"phrase":"foo","context":"home","irreversible":null,"whole_word":null,"expires_in":300}"# + ) + } +} diff --git a/src/requests/mod.rs b/src/requests/mod.rs index 31785aa..375ebd1 100644 --- a/src/requests/mod.rs +++ b/src/requests/mod.rs @@ -1,3 +1,5 @@ +/// Data structure for the MastodonClient::add_filter method +pub use self::filter::AddFilterRequest; /// Data structure for the MastodonClient::add_push_subscription method pub use self::push::{AddPushRequest, Keys, UpdatePushRequest}; /// Data structure for the MastodonClient::statuses method @@ -5,6 +7,7 @@ pub use self::statuses::StatusesRequest; /// Data structure for the MastodonClient::update_credentials method pub use self::update_credentials::UpdateCredsRequest; +mod filter; mod push; mod statuses; mod update_credentials;