Add methods & data structures for all the "push" endpoints

Closes #53
master
Paul Woolcock 6 years ago
parent 28192e1188
commit 690b029d99
  1. 2
      Cargo.toml
  2. 3
      src/entities/mod.rs
  3. 67
      src/entities/push.rs
  4. 59
      src/lib.rs
  5. 20
      src/mastodon_client.rs
  6. 3
      src/requests/mod.rs
  7. 633
      src/requests/push.rs

@ -19,9 +19,11 @@ serde_derive = "1"
serde_json = "1"
serde_urlencoded = "0.5.3"
url = "1"
tap-reader = "1"
try_from = "0.2.2"
toml = { version = "0.4.6", optional = true }
[dependencies.chrono]
version = "0.4"
features = ["serde"]

@ -15,6 +15,8 @@ pub mod list;
pub mod mention;
/// Data structures for ser/de of notification-related resources
pub mod notification;
/// Data structures for ser/de of push-subscription-related resources
pub mod push;
/// Data structures for ser/de of relationship-related resources
pub mod relationship;
/// Data structures for ser/de of report-related resources
@ -41,6 +43,7 @@ pub mod prelude {
list::List,
mention::Mention,
notification::Notification,
push::Subscription,
relationship::Relationship,
report::Report,
search_result::{SearchResult, SearchResultV2},

@ -0,0 +1,67 @@
/// Represents the `alerts` key of the `Subscription` object
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
pub struct Alerts {
/// flag for follow alerts
pub follow: Option<bool>,
/// flag for favourite alerts
pub favourite: Option<bool>,
/// flag for reblog alerts
pub reblog: Option<bool>,
/// flag for mention alerts
pub mention: Option<bool>,
}
/// Represents a new Push subscription
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct Subscription {
/// The `id` of the subscription
pub id: String,
/// The endpoint of the subscription
pub endpoint: String,
/// The server key of the subscription
pub server_key: String,
/// The status of the alerts for this subscription
pub alerts: Option<Alerts>,
}
pub(crate) mod add_subscription {
use super::Alerts;
#[derive(Debug, Clone, PartialEq, Serialize, Default)]
pub(crate) struct Form {
pub(crate) subscription: Subscription,
pub(crate) data: Option<Data>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Default)]
pub(crate) struct Subscription {
pub(crate) endpoint: String,
pub(crate) keys: Keys,
}
#[derive(Debug, Clone, PartialEq, Serialize, Default)]
pub(crate) struct Keys {
pub(crate) p256dh: String,
pub(crate) auth: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Default)]
pub(crate) struct Data {
pub(crate) alerts: Option<Alerts>,
}
}
pub(crate) mod update_data {
use super::Alerts;
#[derive(Debug, Clone, PartialEq, Serialize, Default)]
pub(crate) struct Data {
pub(crate) alerts: Option<Alerts>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Default)]
pub(crate) struct Form {
pub(crate) id: String,
pub(crate) data: Data,
}
}

@ -9,18 +9,21 @@
//! # try().unwrap();
//! # }
//! # fn try() -> elefren::Result<()> {
//! use elefren::prelude::*;
//! use elefren::{helpers::cli, prelude::*};
//!
//! let registration = Registration::new("https://mastodon.social")
//! .client_name("elefren_test")
//! .build()?;
//! let url = registration.authorize_url()?;
//! // Here you now need to open the url in the browser
//! // And handle a the redirect url coming back with the code.
//! let code = String::from("RETURNED_FROM_BROWSER");
//! let mastodon = registration.complete(&code)?;
//! let mastodon = cli::authenticate(registration)?;
//!
//! println!("{:?}", mastodon.get_home_timeline()?.initial_items);
//! println!(
//! "{:?}",
//! mastodon
//! .get_home_timeline()?
//! .items_iter()
//! .take(100)
//! .collect::<Vec<_>>()
//! );
//! # Ok(())
//! # }
//! ```
@ -50,6 +53,7 @@ extern crate chrono;
extern crate reqwest;
extern crate serde;
extern crate serde_urlencoded;
extern crate tap_reader;
extern crate try_from;
extern crate url;
@ -74,6 +78,7 @@ use reqwest::{
RequestBuilder,
Response,
};
use tap_reader::Tap;
use entities::prelude::*;
use http_send::{HttpSend, HttpSender};
@ -84,7 +89,7 @@ pub use errors::{ApiError, Error, Result};
pub use isolang::Language;
pub use mastodon_client::MastodonClient;
pub use registration::Registration;
pub use requests::{StatusesRequest, UpdateCredsRequest};
pub use requests::{AddPushRequest, StatusesRequest, UpdateCredsRequest, UpdatePushRequest};
pub use status_builder::StatusBuilder;
/// Registering your App
@ -191,6 +196,8 @@ impl<H: HttpSend> MastodonClient<H> for Mastodon<H> {
(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,
}
route_v2! {
@ -345,6 +352,31 @@ impl<H: HttpSend> MastodonClient<H> for Mastodon<H> {
Page::new(self, response)
}
/// Add a push notifications subscription
fn add_push_subscription(&self, request: &AddPushRequest) -> Result<Subscription> {
let request = request.build()?;
let response = self.send(
self.client
.post(&self.route("/api/v1/push/subscription"))
.json(&request),
)?;
deserialise(response)
}
/// Update the `data` portion of the push subscription associated with this
/// access token
fn update_push_data(&self, request: &UpdatePushRequest) -> Result<Subscription> {
let request = request.build();
let response = self.send(
self.client
.put(&self.route("/api/v1/push/subscription"))
.json(&request),
)?;
deserialise(response)
}
}
impl<H: HttpSend> ops::Deref for Mastodon<H> {
@ -401,18 +433,15 @@ impl<H: HttpSend> MastodonBuilder<H> {
// Convert the HTTP response body from JSON. Pass up deserialization errors
// transparently.
fn deserialise<T: for<'de> serde::Deserialize<'de>>(mut response: Response) -> Result<T> {
use std::io::Read;
let mut vec = Vec::new();
response.read_to_end(&mut vec)?;
fn deserialise<T: for<'de> serde::Deserialize<'de>>(response: Response) -> Result<T> {
let mut reader = Tap::new(response);
match serde_json::from_slice(&vec) {
match serde_json::from_reader(&mut reader) {
Ok(t) => Ok(t),
// If deserializing into the desired type fails try again to
// see if this is an error response.
Err(e) => {
if let Ok(error) = serde_json::from_slice(&vec) {
if let Ok(error) = serde_json::from_slice(&reader.bytes) {
return Err(Error::Api(error));
}
Err(e.into())

@ -4,7 +4,7 @@ use entities::prelude::*;
use errors::Result;
use http_send::{HttpSend, HttpSender};
use page::Page;
use requests::{StatusesRequest, UpdateCredsRequest};
use requests::{AddPushRequest, StatusesRequest, UpdateCredsRequest, UpdatePushRequest};
use status_builder::StatusBuilder;
/// Represents the set of methods that a Mastodon Client can do, so that
@ -198,7 +198,7 @@ pub trait MastodonClient<H: HttpSend = HttpSender> {
{
unimplemented!("This method was not implemented");
}
/// GET /api/v1/accounts/relationships?
/// GET /api/v1/accounts/relationships
fn relationships(&self, ids: &[&str]) -> Result<Page<Relationship, H>> {
unimplemented!("This method was not implemented");
}
@ -211,4 +211,20 @@ pub trait MastodonClient<H: HttpSend = HttpSender> {
) -> Result<Page<Account, H>> {
unimplemented!("This method was not implemented");
}
/// POST /api/v1/push/subscription
fn add_push_subscription(&self, request: &AddPushRequest) -> Result<Subscription> {
unimplemented!("This method was not implemented");
}
/// PUT /api/v1/push/subscription
fn update_push_data(&self, request: &UpdatePushRequest) -> Result<Subscription> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/push/subscription
fn get_push_subscription(&self) -> Result<Subscription> {
unimplemented!("This method was not implemented");
}
/// DELETE /api/v1/push/subscription
fn delete_push_subscription(&self) -> Result<Empty> {
unimplemented!("This method was not implemented");
}
}

@ -1,7 +1,10 @@
/// Data structure for the MastodonClient::add_push_subscription method
pub use self::push::{AddPushRequest, Keys, UpdatePushRequest};
/// Data structure for the MastodonClient::statuses method
pub use self::statuses::StatusesRequest;
/// Data structure for the MastodonClient::update_credentials method
pub use self::update_credentials::UpdateCredsRequest;
mod push;
mod statuses;
mod update_credentials;

@ -0,0 +1,633 @@
use entities::push::{add_subscription, update_data};
use errors::Result;
/// Container for the key & auth strings for an AddPushRequest
///
/// # Example
///
/// ```
/// # extern crate elefren;
/// use elefren::requests::Keys;
///
/// let keys = Keys::new("anetohias===", "oeatssah=");
/// ```
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Keys {
pub(crate) p256dh: String,
pub(crate) auth: String,
}
impl Keys {
/// Create the `Keys` container
///
/// # Example
///
/// ```
/// # extern crate elefren;
/// use elefren::requests::Keys;
///
/// let keys = Keys::new("anetohias===", "oeatssah=");
/// ```
pub fn new(p256dh: &str, auth: &str) -> Keys {
Keys {
p256dh: p256dh.to_string(),
auth: auth.to_string(),
}
}
}
/// Builder to pass to the Mastodon::add_push_subscription method
///
/// # Example
///
/// ```no_run
/// # extern crate elefren;
/// # use elefren::{MastodonClient, Mastodon, Data};
/// # fn main() -> Result<(), elefren::Error> {
/// # let data = Data {
/// # base: "".into(),
/// # client_id: "".into(),
/// # client_secret: "".into(),
/// # redirect: "".into(),
/// # token: "".into(),
/// # };
/// use elefren::requests::{AddPushRequest, Keys};
///
/// let client = Mastodon::from(data);
///
/// let keys = Keys::new("stahesuahoei293ise===", "tasecoa,nmeozka==");
/// let mut request = AddPushRequest::new("http://example.com/push/endpoint", &keys);
/// request.follow().reblog();
///
/// client.add_push_subscription(&request)?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Default, Clone, PartialEq)]
pub struct AddPushRequest {
endpoint: String,
p256dh: String,
auth: String,
follow: Option<bool>,
favourite: Option<bool>,
reblog: Option<bool>,
mention: Option<bool>,
}
impl AddPushRequest {
/// Construct a new AddPushRequest
///
/// # Example
///
/// ```
/// # extern crate elefren;
/// use elefren::requests::{AddPushRequest, Keys};
/// let keys = Keys::new("abcdef===", "foobar==");
/// let push_endpoint = "https://example.com/push/endpoint";
/// let request = AddPushRequest::new(push_endpoint, &keys);
/// ```
pub fn new(endpoint: &str, keys: &Keys) -> AddPushRequest {
AddPushRequest {
endpoint: endpoint.to_string(),
p256dh: keys.p256dh.clone(),
auth: keys.auth.clone(),
..Default::default()
}
}
/// A flag that indicates if you want follow notifications pushed
///
/// # Example
/// ```
/// # extern crate elefren;
/// use elefren::requests::{AddPushRequest, Keys};
/// let keys = Keys::new("abcdef===", "foobar==");
/// let push_endpoint = "https://example.com/push/endpoint";
/// let mut request = AddPushRequest::new(push_endpoint, &keys);
/// request.follow();
/// ```
pub fn follow(&mut self) -> &mut Self {
self.follow = Some(true);
self
}
/// A flag that indicates if you want favourite notifications pushed
///
/// # Example
/// ```
/// # extern crate elefren;
/// use elefren::requests::{AddPushRequest, Keys};
/// let keys = Keys::new("abcdef===", "foobar==");
/// let push_endpoint = "https://example.com/push/endpoint";
/// let mut request = AddPushRequest::new(push_endpoint, &keys);
/// request.favourite();
/// ```
pub fn favourite(&mut self) -> &mut Self {
self.favourite = Some(true);
self
}
/// A flag that indicates if you want reblog notifications pushed
///
/// # Example
/// ```
/// # extern crate elefren;
/// use elefren::requests::{AddPushRequest, Keys};
/// let keys = Keys::new("abcdef===", "foobar==");
/// let push_endpoint = "https://example.com/push/endpoint";
/// let mut request = AddPushRequest::new(push_endpoint, &keys);
/// request.reblog();
/// ```
pub fn reblog(&mut self) -> &mut Self {
self.reblog = Some(true);
self
}
/// A flag that indicates if you want mention notifications pushed
///
/// # Example
/// ```
/// # extern crate elefren;
/// use elefren::requests::{AddPushRequest, Keys};
/// let keys = Keys::new("abcdef===", "foobar==");
/// let push_endpoint = "https://example.com/push/endpoint";
/// let mut request = AddPushRequest::new(push_endpoint, &keys);
/// request.mention();
/// ```
pub fn mention(&mut self) -> &mut Self {
self.mention = Some(true);
self
}
fn flags_present(&self) -> bool {
self.follow.is_some()
|| self.favourite.is_some()
|| self.reblog.is_some()
|| self.mention.is_some()
}
pub(crate) fn build(&self) -> Result<add_subscription::Form> {
use entities::push::{
add_subscription::{Data, Form, Keys, Subscription},
Alerts,
};
let mut form = Form {
subscription: Subscription {
endpoint: self.endpoint.clone(),
keys: Keys {
p256dh: self.p256dh.clone(),
auth: self.auth.clone(),
},
},
data: None,
};
if self.flags_present() {
let mut alerts = Alerts::default();
if let Some(follow) = self.follow {
alerts.follow = Some(follow);
}
if let Some(favourite) = self.favourite {
alerts.favourite = Some(favourite);
}
if let Some(reblog) = self.reblog {
alerts.reblog = Some(reblog);
}
if let Some(mention) = self.mention {
alerts.mention = Some(mention);
}
form.data = Some(Data {
alerts: Some(alerts),
});
}
Ok(form)
}
}
/// Builder to pass to the Mastodon::update_push_data method
///
/// # Example
///
/// ```no_run
/// # extern crate elefren;
/// # use elefren::{MastodonClient, Mastodon, Data};
/// # fn main() -> Result<(), elefren::Error> {
/// # let data = Data {
/// # base: "".into(),
/// # client_id: "".into(),
/// # client_secret: "".into(),
/// # redirect: "".into(),
/// # token: "".into(),
/// # };
/// use elefren::requests::UpdatePushRequest;
///
/// let client = Mastodon::from(data);
///
/// let mut request = UpdatePushRequest::new("foobar");
/// request.follow(true).reblog(true);
///
/// client.update_push_data(&request)?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Default, Clone, PartialEq, Serialize)]
pub struct UpdatePushRequest {
id: String,
follow: Option<bool>,
favourite: Option<bool>,
reblog: Option<bool>,
mention: Option<bool>,
}
impl UpdatePushRequest {
/// Construct a new UpdatePushRequest
///
/// # Example
///
/// ```
/// # extern crate elefren;
/// use elefren::requests::UpdatePushRequest;
/// let request = UpdatePushRequest::new("some-id");
/// ```
pub fn new(id: &str) -> UpdatePushRequest {
UpdatePushRequest {
id: id.to_string(),
..Default::default()
}
}
/// A flag that indicates if you want follow notifications pushed
///
/// # Example
/// ```
/// # extern crate elefren;
/// use elefren::requests::UpdatePushRequest;
/// let mut request = UpdatePushRequest::new("foobar");
/// request.follow(true);
/// ```
pub fn follow(&mut self, follow: bool) -> &mut Self {
self.follow = Some(follow);
self
}
/// A flag that indicates if you want favourite notifications pushed
///
/// # Example
/// ```
/// # extern crate elefren;
/// use elefren::requests::UpdatePushRequest;
/// let mut request = UpdatePushRequest::new("foobar");
/// request.favourite(true);
/// ```
pub fn favourite(&mut self, favourite: bool) -> &mut Self {
self.favourite = Some(favourite);
self
}
/// A flag that indicates if you want reblog notifications pushed
///
/// # Example
/// ```
/// # extern crate elefren;
/// use elefren::requests::UpdatePushRequest;
/// let mut request = UpdatePushRequest::new("foobar");
/// request.reblog(true);
/// ```
pub fn reblog(&mut self, reblog: bool) -> &mut Self {
self.reblog = Some(reblog);
self
}
/// A flag that indicates if you want mention notifications pushed
///
/// # Example
/// ```
/// # extern crate elefren;
/// use elefren::requests::UpdatePushRequest;
/// let mut request = UpdatePushRequest::new("foobar");
/// request.mention(true);
/// ```
pub fn mention(&mut self, mention: bool) -> &mut Self {
self.mention = Some(mention);
self
}
fn flags_present(&self) -> bool {
self.follow.is_some()
|| self.favourite.is_some()
|| self.reblog.is_some()
|| self.mention.is_some()
}
pub(crate) fn build(&self) -> update_data::Form {
use entities::push::{
update_data::{Data, Form},
Alerts,
};
let mut form = Form {
id: self.id.clone(),
..Default::default()
};
if self.flags_present() {
let mut alerts = Alerts::default();
if let Some(follow) = self.follow {
alerts.follow = Some(follow);
}
if let Some(favourite) = self.favourite {
alerts.favourite = Some(favourite);
}
if let Some(reblog) = self.reblog {
alerts.reblog = Some(reblog);
}
if let Some(mention) = self.mention {
alerts.mention = Some(mention);
}
form.data = Data {
alerts: Some(alerts),
};
}
form
}
}
#[cfg(test)]
mod tests {
use super::*;
use entities::push::{add_subscription, update_data, Alerts};
#[test]
fn test_keys_new() {
let keys = Keys::new("anetohias===", "oeatssah=");
assert_eq!(
keys,
Keys {
p256dh: "anetohias===".to_string(),
auth: "oeatssah=".to_string()
}
);
}
#[test]
fn test_add_push_request_new() {
let endpoint = "https://example.com/push/endpoint";
let keys = Keys::new("anetohias===", "oeatssah=");
let req = AddPushRequest::new(endpoint, &keys);
assert_eq!(
req,
AddPushRequest {
endpoint: "https://example.com/push/endpoint".to_string(),
p256dh: "anetohias===".to_string(),
auth: "oeatssah=".to_string(),
follow: None,
favourite: None,
reblog: None,
mention: None,
}
);
}
#[test]
fn test_add_push_request_follow() {
let endpoint = "https://example.com/push/endpoint";
let keys = Keys::new("anetohias===", "oeatssah=");
let mut req = AddPushRequest::new(endpoint, &keys);
req.follow();
assert_eq!(
req,
AddPushRequest {
endpoint: "https://example.com/push/endpoint".to_string(),
p256dh: "anetohias===".to_string(),
auth: "oeatssah=".to_string(),
follow: Some(true),
favourite: None,
reblog: None,
mention: None,
}
);
}
#[test]
fn test_add_push_request_favourite() {
let endpoint = "https://example.com/push/endpoint";
let keys = Keys::new("anetohias===", "oeatssah=");
let mut req = AddPushRequest::new(endpoint, &keys);
req.favourite();
assert_eq!(
req,
AddPushRequest {
endpoint: "https://example.com/push/endpoint".to_string(),
p256dh: "anetohias===".to_string(),
auth: "oeatssah=".to_string(),
follow: None,
favourite: Some(true),
reblog: None,
mention: None,
}
);
}
#[test]
fn test_add_push_request_reblog() {
let endpoint = "https://example.com/push/endpoint";
let keys = Keys::new("anetohias===", "oeatssah=");
let mut req = AddPushRequest::new(endpoint, &keys);
req.reblog();
assert_eq!(
req,
AddPushRequest {
endpoint: "https://example.com/push/endpoint".to_string(),
p256dh: "anetohias===".to_string(),
auth: "oeatssah=".to_string(),
follow: None,
favourite: None,
reblog: Some(true),
mention: None,
}
);
}
#[test]
fn test_add_push_request_mention() {
let endpoint = "https://example.com/push/endpoint";
let keys = Keys::new("anetohias===", "oeatssah=");
let mut req = AddPushRequest::new(endpoint, &keys);
req.mention();
assert_eq!(
req,
AddPushRequest {
endpoint: "https://example.com/push/endpoint".to_string(),
p256dh: "anetohias===".to_string(),
auth: "oeatssah=".to_string(),
follow: None,
favourite: None,
reblog: None,
mention: Some(true),
}
);
}
#[test]
fn test_add_push_request_build_no_flags() {
let endpoint = "https://example.com/push/endpoint";
let keys = Keys::new("anetohias===", "oeatssah=");
let req = AddPushRequest::new(endpoint, &keys);
let form = req.build().expect("Couldn't build form");
assert_eq!(
form,
add_subscription::Form {
subscription: add_subscription::Subscription {
endpoint: "https://example.com/push/endpoint".to_string(),
keys: add_subscription::Keys {
p256dh: "anetohias===".to_string(),
auth: "oeatssah=".to_string(),
},
},
data: None,
}
);
}
#[test]
fn test_add_push_request_build() {
let endpoint = "https://example.com/push/endpoint";
let keys = Keys::new("anetohias===", "oeatssah=");
let mut req = AddPushRequest::new(endpoint, &keys);
req.follow().reblog();
let form = req.build().expect("Couldn't build form");
assert_eq!(
form,
add_subscription::Form {
subscription: add_subscription::Subscription {
endpoint: "https://example.com/push/endpoint".to_string(),
keys: add_subscription::Keys {
p256dh: "anetohias===".to_string(),
auth: "oeatssah=".to_string(),
},
},
data: Some(add_subscription::Data {
alerts: Some(Alerts {
follow: Some(true),
favourite: None,
reblog: Some(true),
mention: None,
}),
}),
}
);
}
#[test]
fn test_update_push_request_new() {
let req = UpdatePushRequest::new("some-id");
assert_eq!(
req,
UpdatePushRequest {
id: "some-id".to_string(),
follow: None,
favourite: None,
reblog: None,
mention: None,
}
);
}
#[test]
fn test_update_push_request_follow() {
let mut req = UpdatePushRequest::new("some-id");
req.follow(true);
assert_eq!(
req,
UpdatePushRequest {
id: "some-id".to_string(),
follow: Some(true),
favourite: None,
reblog: None,
mention: None,
}
);
}
#[test]
fn test_update_push_request_favourite() {
let mut req = UpdatePushRequest::new("some-id");
req.favourite(true);
assert_eq!(
req,
UpdatePushRequest {
id: "some-id".to_string(),
follow: None,
favourite: Some(true),
reblog: None,
mention: None,
}
);
}
#[test]
fn test_update_push_request_reblog() {
let mut req = UpdatePushRequest::new("some-id");
req.reblog(true);
assert_eq!(
req,
UpdatePushRequest {
id: "some-id".to_string(),
follow: None,
favourite: None,
reblog: Some(true),
mention: None,
}
);
}
#[test]
fn test_update_push_request_mention() {
let mut req = UpdatePushRequest::new("some-id");
req.mention(true);
assert_eq!(
req,
UpdatePushRequest {
id: "some-id".to_string(),
follow: None,
favourite: None,
reblog: None,
mention: Some(true),
}
);
}
#[test]
fn test_update_push_request_build_no_flags() {
let req = UpdatePushRequest::new("some-id");
let form = req.build();
assert_eq!(
form,
update_data::Form {
id: "some-id".to_string(),
data: update_data::Data {
alerts: None,
},
}
);
}
#[test]
fn test_update_push_request_build() {
let mut req = UpdatePushRequest::new("some-id");
req.favourite(false);
let form = req.build();
assert_eq!(
form,
update_data::Form {
id: "some-id".to_string(),
data: update_data::Data {
alerts: Some(Alerts {
follow: None,
favourite: Some(false),
reblog: None,
mention: None,
}),
},
}
);
}
}
Loading…
Cancel
Save