summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Woolcock <paul@woolcock.us>2018-09-13 21:10:47 -0400
committerPaul Woolcock <paul@woolcock.us>2018-09-14 05:11:45 -0400
commit7d164cb8dbf32466956109dc9be23e911f946645 (patch)
tree5c2eef6f54ff15c2b0b764e3fa2e14070d264f0c
parentd6a9911a0b3916f2c9b5788eb09a8282c06bc267 (diff)
Keyword/filtering API
This adds the 5 methods for the mastodon API that deal with keyword filtering: GET /api/v1/filters POST /api/v1/filters GET /api/v1/filters/:id PUT /api/v1/filters/:id DELETE /api/v1/filters/:id Closes #71
-rw-r--r--src/entities/filter.rs27
-rw-r--r--src/entities/mod.rs3
-rw-r--r--src/lib.rs72
-rw-r--r--src/mastodon_client.rs28
-rw-r--r--src/requests/filter.rs154
-rw-r--r--src/requests/mod.rs3
6 files changed, 265 insertions, 22 deletions
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<FilterContext>,
+ expires_at: Option<String>, // 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<H: HttpSend> MastodonClient<H> for Mastodon<H> {
(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<Status>,
(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<Filter>,
}
route_v2! {
@@ -221,6 +229,39 @@ impl<H: HttpSend> MastodonClient<H> for Mastodon<H> {
(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<Filter> {
+ 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<Filter> {
+ 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<Account> {
@@ -228,12 +269,12 @@ impl<H: HttpSend> MastodonClient<H> for Mastodon<H> {
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<H: HttpSend> MastodonClient<H> for Mastodon<H> {
deserialise(response)
}
- /// Get the federated timeline for the instance.
- fn get_public_timeline(&self, local: bool) -> Result<Vec<Status>> {
- 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<Vec<Status>> {
- 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<H: HttpSend = HttpSender> {
fn delete_push_subscription(&self) -> Result<Empty> {
unimplemented!("This method was not implemented");
}
+ /// GET /api/v1/filters
+ fn get_filters(&self) -> Result<Vec<Filter>> {
+ unimplemented!("This method was not implemented");
+ }
+ /// POST /api/v1/filters
+ fn add_filter(&self, request: &mut AddFilterRequest) -> Result<Filter> {
+ unimplemented!("This method was not implemented");
+ }
+ /// GET /api/v1/filters/:id
+ fn get_filter(&self, id: u64) -> Result<Filter> {
+ unimplemented!("This method was not implemented");
+ }
+ /// PUT /api/v1/filters/:id
+ fn update_filter(&self, id: u64, request: &mut AddFilterRequest) -> Result<Filter> {
+ unimplemented!("This method was not implemented");
+ }
+ /// DELETE /api/v1/filters/:id
+ fn delete_filter(&self, id: u64) -> Result<Empty> {
+ 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<Error>> {
+/// let request = AddFilterRequest::new("foo", FilterContext::Home);
+/// # Ok(())
+/// # }
+/// ```
+#[derive(Debug, Clone, PartialEq, Serialize)]
+pub struct AddFilterRequest {
+ phrase: String,
+ context: FilterContext,
+ irreversible: Option<bool>,
+ whole_word: Option<bool>,
+ #[serde(serialize_with = "serialize_duration::ser")]
+ expires_in: Option<Duration>,
+}
+
+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<S>(duration: &Option<Duration>, s: S) -> Result<S::Ok, S::Error>
+ 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;