removed the trait so it can be easier made async

master
Ondřej Hruška 3 years ago
parent 3c7a84a124
commit b6b7372ebb
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 294
      src/lib.rs
  2. 196
      src/macros.rs

@ -47,10 +47,10 @@
//! let client = Mastodon::from(data); //! let client = Mastodon::from(data);
//! for event in client.streaming_user()? { //! for event in client.streaming_user()? {
//! match event { //! match event {
//! Event::Update(ref status) => { /* .. */ }, //! Event::Update(ref status) => { /* .. */ }
//! Event::Notification(ref notification) => { /* .. */ }, //! Event::Notification(ref notification) => { /* .. */ }
//! Event::Delete(ref id) => { /* .. */ }, //! Event::Delete(ref id) => { /* .. */ }
//! Event::FiltersChanged => { /* .. */ }, //! Event::FiltersChanged => { /* .. */ }
//! } //! }
//! } //! }
//! # Ok(()) //! # Ok(())
@ -69,28 +69,30 @@
unused_import_braces, unused_import_braces,
unused_qualifications unused_qualifications
)] )]
#![cfg_attr(feature = "nightly", allow(broken_intra_doc_links))] // #![cfg_attr(feature = "nightly", allow(broken_intra_doc_links))]
#![allow(broken_intra_doc_links)] // #![allow(broken_intra_doc_links)]
#[macro_use]
extern crate log;
use std::{borrow::Cow, io::BufRead, ops}; use std::{borrow::Cow, io::BufRead, ops};
use std::net::TcpStream;
use reqwest::blocking::{multipart, Client, RequestBuilder, Response}; pub use isolang::Language;
use reqwest::blocking::{Client, multipart, RequestBuilder, Response};
use tap_reader::Tap; use tap_reader::Tap;
use tungstenite::stream::MaybeTlsStream;
use crate::{entities::prelude::*, page::Page}; use crate::{entities::prelude::*, page::Page};
pub use crate::{ pub use crate::{
data::Data, data::Data,
errors::{ApiError, Error, Result}, errors::{ApiError, Error, Result},
mastodon_client::{MastodonClient, MastodonUnauthenticated}, // mastodon_client::{MastodonClient, MastodonUnauthenticated},
media_builder::MediaBuilder, media_builder::MediaBuilder,
registration::Registration, registration::Registration,
requests::{AddFilterRequest, AddPushRequest, StatusesRequest, UpdateCredsRequest, UpdatePushRequest}, requests::{AddFilterRequest, AddPushRequest, StatusesRequest, UpdateCredsRequest, UpdatePushRequest},
status_builder::{NewStatus, StatusBuilder}, status_builder::{NewStatus, StatusBuilder},
}; };
pub use isolang::Language;
use tungstenite::stream::MaybeTlsStream;
use std::net::TcpStream;
/// Registering your App /// Registering your App
pub mod apps; pub mod apps;
@ -117,22 +119,27 @@ pub mod scopes;
pub mod status_builder; pub mod status_builder;
#[macro_use] #[macro_use]
mod macros; mod macros;
/// Automatically import the things you need /// Automatically import the things you need
pub mod prelude { pub mod prelude {
pub use crate::{ pub use crate::{
scopes::Scopes, Data, Mastodon, MastodonClient, NewStatus, Registration, StatusBuilder, StatusesRequest, Data, Mastodon, NewStatus, Registration, scopes::Scopes, StatusBuilder, StatusesRequest,
}; };
} }
// type Stream = EventReader<WebSocket>;
/// Your mastodon application client, handles all requests to and from Mastodon. /// Your mastodon application client, handles all requests to and from Mastodon.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Mastodon { pub struct Mastodon {
client: Client, client: Client,
/// Raw data about your mastodon instance. /// Raw data about your mastodon instance.
pub data: Data, pub data: Data,
} }
impl Mastodon { impl Mastodon {
methods![get, post, delete,]; methods![get, post, delete,];
fn route(&self, url: &str) -> String { fn route(&self, url: &str) -> String {
@ -154,79 +161,70 @@ impl From<Data> for Mastodon {
} }
} }
impl MastodonClient for Mastodon { type MastodonStream = EventReader<WebSocket>;
type Stream = EventReader<WebSocket>;
impl Mastodon {
paged_routes! { route_v1_paged!((get) favourites: "favourites" => Status);
(get) favourites: "favourites" => Status, route_v1_paged!((get) blocks: "blocks" => Account);
(get) blocks: "blocks" => Account, route_v1_paged!((get) domain_blocks: "domain_blocks" => String);
(get) domain_blocks: "domain_blocks" => String, route_v1_paged!((get) follow_requests: "follow_requests" => Account);
(get) follow_requests: "follow_requests" => Account, route_v1_paged!((get) get_home_timeline: "timelines/home" => Status);
(get) get_home_timeline: "timelines/home" => Status, route_v1_paged!((get) get_local_timeline: "timelines/public?local=true" => Status);
(get) get_local_timeline: "timelines/public?local=true" => Status, route_v1_paged!((get) get_federated_timeline: "timelines/public?local=false" => Status);
(get) get_federated_timeline: "timelines/public?local=false" => Status, route_v1_paged!((get) get_emojis: "custom_emojis" => Emoji);
(get) get_emojis: "custom_emojis" => Emoji, route_v1_paged!((get) mutes: "mutes" => Account);
(get) mutes: "mutes" => Account, route_v1_paged!((get) notifications: "notifications" => Notification);
(get) notifications: "notifications" => Notification, route_v1_paged!((get) reports: "reports" => Report);
(get) reports: "reports" => Report, route_v1_paged!((get (q: &'a str, #[serde(skip_serializing_if = "Option::is_none")] limit: Option<u64>, following: bool,)) search_accounts: "accounts/search" => Account);
(get (q: &'a str, #[serde(skip_serializing_if = "Option::is_none")] limit: Option<u64>, following: bool,)) search_accounts: "accounts/search" => Account, route_v1_paged!((get) get_endorsements: "endorsements" => Account);
(get) get_endorsements: "endorsements" => Account,
} route_v1_paged_id!((get) followers: "accounts/{}/followers" => Account);
route_v1_paged_id!((get) following: "accounts/{}/following" => Account);
paged_routes_with_id! { route_v1_paged_id!((get) reblogged_by: "statuses/{}/reblogged_by" => Account);
(get) followers: "accounts/{}/followers" => Account, route_v1_paged_id!((get) favourited_by: "statuses/{}/favourited_by" => Account);
(get) following: "accounts/{}/following" => Account,
(get) reblogged_by: "statuses/{}/reblogged_by" => Account, route_v1!((delete (domain: String,)) unblock_domain: "domain_blocks" => Empty);
(get) favourited_by: "statuses/{}/favourited_by" => Account, route_v1!((get) instance: "instance" => Instance);
} route_v1!((get) verify_credentials: "accounts/verify_credentials" => Account);
route_v1!((post (account_id: &str, status_ids: Vec<&str>, comment: String,)) report: "reports" => Report);
route! { route_v1!((post (domain: String,)) block_domain: "domain_blocks" => Empty);
(delete (domain: String,)) unblock_domain: "domain_blocks" => Empty, route_v1!((post (id: &str,)) authorize_follow_request: "accounts/follow_requests/authorize" => Empty);
(get) instance: "instance" => Instance, route_v1!((post (id: &str,)) reject_follow_request: "accounts/follow_requests/reject" => Empty);
(get) verify_credentials: "accounts/verify_credentials" => Account, route_v1!((get (q: &'a str, resolve: bool,)) search: "search" => SearchResult);
(post (account_id: &str, status_ids: Vec<&str>, comment: String,)) report: "reports" => Report, route_v1!((post (uri: Cow<'static, str>,)) follows: "follows" => Account);
(post (domain: String,)) block_domain: "domain_blocks" => Empty, route_v1!((post) clear_notifications: "notifications/clear" => Empty);
(post (id: &str,)) authorize_follow_request: "accounts/follow_requests/authorize" => Empty, route_v1!((post (id: &str,)) dismiss_notification: "notifications/dismiss" => Empty);
(post (id: &str,)) reject_follow_request: "accounts/follow_requests/reject" => Empty, route_v1!((get) get_push_subscription: "push/subscription" => Subscription);
(get (q: &'a str, resolve: bool,)) search: "search" => SearchResult, route_v1!((delete) delete_push_subscription: "push/subscription" => Empty);
(post (uri: Cow<'static, str>,)) follows: "follows" => Account, route_v1!((get) get_filters: "filters" => Vec<Filter>);
(post) clear_notifications: "notifications/clear" => Empty, route_v1!((get) get_follow_suggestions: "suggestions" => Vec<Account>);
(post (id: &str,)) dismiss_notification: "notifications/dismiss" => Empty,
(get) get_push_subscription: "push/subscription" => Subscription, route_v2!((get (q: &'a str, resolve: bool,)) search_v2: "search" => SearchResultV2);
(delete) delete_push_subscription: "push/subscription" => Empty,
(get) get_filters: "filters" => Vec<Filter>, route_v1_id!((get) get_account: "accounts/{}" => Account);
(get) get_follow_suggestions: "suggestions" => Vec<Account>, route_v1_id!((post) follow: "accounts/{}/follow" => Relationship);
} route_v1_id!((post) unfollow: "accounts/{}/unfollow" => Relationship);
route_v1_id!((post) block: "accounts/{}/block" => Relationship);
route_v2! { route_v1_id!((post) unblock: "accounts/{}/unblock" => Relationship);
(get (q: &'a str, resolve: bool,)) search_v2: "search" => SearchResultV2, route_v1_id!((get) mute: "accounts/{}/mute" => Relationship);
} route_v1_id!((get) unmute: "accounts/{}/unmute" => Relationship);
route_v1_id!((get) get_notification: "notifications/{}" => Notification);
route_id! { route_v1_id!((get) get_status: "statuses/{}" => Status);
(get) get_account: "accounts/{}" => Account, route_v1_id!((get) get_context: "statuses/{}/context" => Context);
(post) follow: "accounts/{}/follow" => Relationship, route_v1_id!((get) get_card: "statuses/{}/card" => Card);
(post) unfollow: "accounts/{}/unfollow" => Relationship, route_v1_id!((post) reblog: "statuses/{}/reblog" => Status);
(post) block: "accounts/{}/block" => Relationship, route_v1_id!((post) unreblog: "statuses/{}/unreblog" => Status);
(post) unblock: "accounts/{}/unblock" => Relationship, route_v1_id!((post) favourite: "statuses/{}/favourite" => Status);
(get) mute: "accounts/{}/mute" => Relationship, route_v1_id!((post) unfavourite: "statuses/{}/unfavourite" => Status);
(get) unmute: "accounts/{}/unmute" => Relationship, route_v1_id!((delete) delete_status: "statuses/{}" => Empty);
(get) get_notification: "notifications/{}" => Notification, route_v1_id!((get) get_filter: "filters/{}" => Filter);
(get) get_status: "statuses/{}" => Status, route_v1_id!((delete) delete_filter: "filters/{}" => Empty);
(get) get_context: "statuses/{}/context" => Context, route_v1_id!((delete) delete_from_suggestions: "suggestions/{}" => Empty);
(get) get_card: "statuses/{}/card" => Card, route_v1_id!((post) endorse_user: "accounts/{}/pin" => Relationship);
(post) reblog: "statuses/{}/reblog" => Status, route_v1_id!((post) unendorse_user: "accounts/{}/unpin" => Relationship);
(post) unreblog: "statuses/{}/unreblog" => Status,
(post) favourite: "statuses/{}/favourite" => Status, /// POST /api/v1/filters
(post) unfavourite: "statuses/{}/unfavourite" => Status, pub fn add_filter(&self, request: &mut AddFilterRequest) -> Result<Filter> {
(delete) delete_status: "statuses/{}" => Empty,
(get) get_filter: "filters/{}" => Filter,
(delete) delete_filter: "filters/{}" => Empty,
(delete) delete_from_suggestions: "suggestions/{}" => Empty,
(post) endorse_user: "accounts/{}/pin" => Relationship,
(post) unendorse_user: "accounts/{}/unpin" => Relationship,
}
fn add_filter(&self, request: &mut AddFilterRequest) -> Result<Filter> {
let url = self.route("/api/v1/filters"); let url = self.route("/api/v1/filters");
let response = self.send(self.client.post(&url).json(&request))?; let response = self.send(self.client.post(&url).json(&request))?;
@ -242,7 +240,7 @@ impl MastodonClient for Mastodon {
} }
/// PUT /api/v1/filters/:id /// PUT /api/v1/filters/:id
fn update_filter(&self, id: &str, request: &mut AddFilterRequest) -> Result<Filter> { pub fn update_filter(&self, id: &str, request: &mut AddFilterRequest) -> Result<Filter> {
let url = self.route(&format!("/api/v1/filters/{}", id)); let url = self.route(&format!("/api/v1/filters/{}", id));
let response = self.send(self.client.put(&url).json(&request))?; let response = self.send(self.client.put(&url).json(&request))?;
@ -257,7 +255,8 @@ impl MastodonClient for Mastodon {
deserialise(response) deserialise(response)
} }
fn update_credentials(&self, builder: &mut UpdateCredsRequest) -> Result<Account> { /// PATCH /api/v1/accounts/update_credentials
pub fn update_credentials(&self, builder: &mut UpdateCredsRequest) -> Result<Account> {
let changes = builder.build()?; let changes = builder.build()?;
let url = self.route("/api/v1/accounts/update_credentials"); let url = self.route("/api/v1/accounts/update_credentials");
let response = self.send(self.client.patch(&url).json(&changes))?; let response = self.send(self.client.patch(&url).json(&changes))?;
@ -274,7 +273,7 @@ impl MastodonClient for Mastodon {
} }
/// Post a new status to the account. /// Post a new status to the account.
fn new_status(&self, status: NewStatus) -> Result<Status> { pub fn new_status(&self, status: NewStatus) -> Result<Status> {
let response = self.send(self.client.post(&self.route("/api/v1/statuses")).json(&status))?; let response = self.send(self.client.post(&self.route("/api/v1/statuses")).json(&status))?;
deserialise(response) deserialise(response)
@ -282,7 +281,7 @@ impl MastodonClient for Mastodon {
/// Get timeline filtered by a hashtag(eg. `#coffee`) either locally or /// Get timeline filtered by a hashtag(eg. `#coffee`) either locally or
/// federated. /// federated.
fn get_hashtag_timeline(&self, hashtag: &str, local: bool) -> Result<Page<Status>> { pub fn get_hashtag_timeline(&self, hashtag: &str, local: bool) -> Result<Page<Status>> {
let base = "/api/v1/timelines/tag/"; let base = "/api/v1/timelines/tag/";
let url = if local { let url = if local {
self.route(&format!("{}{}?local=1", base, hashtag)) self.route(&format!("{}{}?local=1", base, hashtag))
@ -295,49 +294,9 @@ impl MastodonClient for Mastodon {
/// Get statuses of a single account by id. Optionally only with pictures /// Get statuses of a single account by id. Optionally only with pictures
/// and or excluding replies. /// and or excluding replies.
/// pub fn statuses<'a, 'b: 'a, S>(&'b self, id: &'b str, request: S) -> Result<Page<Status>>
/// # Example where
/// S: Into<Option<StatusesRequest<'a>>>,
/// ```no_run
/// # extern crate elefren;
/// # use elefren::prelude::*;
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// # let data = Data {
/// # base: "".into(),
/// # client_id: "".into(),
/// # client_secret: "".into(),
/// # redirect: "".into(),
/// # token: "".into(),
/// # };
/// let client = Mastodon::from(data);
/// let statuses = client.statuses("user-id", None)?;
/// # Ok(())
/// # }
/// ```
///
/// ```no_run
/// # extern crate elefren;
/// # use elefren::prelude::*;
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// # let data = Data {
/// # base: "".into(),
/// # client_id: "".into(),
/// # client_secret: "".into(),
/// # redirect: "".into(),
/// # token: "".into(),
/// # };
/// let client = Mastodon::from(data);
/// let mut request = StatusesRequest::new();
/// request.only_media();
/// let statuses = client.statuses("user-id", request)?;
/// # Ok(())
/// # }
/// ```
fn statuses<'a, 'b: 'a, S>(&'b self, id: &'b str, request: S) -> Result<Page<Status>>
where
S: Into<Option<StatusesRequest<'a>>>,
{ {
let mut url = format!("{}/api/v1/accounts/{}/statuses", self.base, id); let mut url = format!("{}/api/v1/accounts/{}/statuses", self.base, id);
@ -352,7 +311,7 @@ impl MastodonClient for Mastodon {
/// Returns the client account's relationship to a list of other accounts. /// Returns the client account's relationship to a list of other accounts.
/// Such as whether they follow them or vice versa. /// Such as whether they follow them or vice versa.
fn relationships(&self, ids: &[&str]) -> Result<Page<Relationship>> { pub fn relationships(&self, ids: &[&str]) -> Result<Page<Relationship>> {
let mut url = self.route("/api/v1/accounts/relationships?"); let mut url = self.route("/api/v1/accounts/relationships?");
if ids.len() == 1 { if ids.len() == 1 {
@ -373,7 +332,7 @@ impl MastodonClient for Mastodon {
} }
/// Add a push notifications subscription /// Add a push notifications subscription
fn add_push_subscription(&self, request: &AddPushRequest) -> Result<Subscription> { pub fn add_push_subscription(&self, request: &AddPushRequest) -> Result<Subscription> {
let request = request.build()?; let request = request.build()?;
let response = self.send(self.client.post(&self.route("/api/v1/push/subscription")).json(&request))?; let response = self.send(self.client.post(&self.route("/api/v1/push/subscription")).json(&request))?;
@ -382,7 +341,7 @@ impl MastodonClient for Mastodon {
/// Update the `data` portion of the push subscription associated with this /// Update the `data` portion of the push subscription associated with this
/// access token /// access token
fn update_push_data(&self, request: &UpdatePushRequest) -> Result<Subscription> { pub fn update_push_data(&self, request: &UpdatePushRequest) -> Result<Subscription> {
let request = request.build(); let request = request.build();
let response = self.send(self.client.put(&self.route("/api/v1/push/subscription")).json(&request))?; let response = self.send(self.client.put(&self.route("/api/v1/push/subscription")).json(&request))?;
@ -390,48 +349,20 @@ impl MastodonClient for Mastodon {
} }
/// Get all accounts that follow the authenticated user /// Get all accounts that follow the authenticated user
fn follows_me(&self) -> Result<Page<Account>> { pub fn follows_me(&self) -> Result<Page<Account>> {
let me = self.verify_credentials()?; let me = self.verify_credentials()?;
self.followers(&me.id) self.followers(&me.id)
} }
/// Get all accounts that the authenticated user follows /// Get all accounts that the authenticated user follows
fn followed_by_me(&self) -> Result<Page<Account>> { pub fn followed_by_me(&self) -> Result<Page<Account>> {
let me = self.verify_credentials()?; let me = self.verify_credentials()?;
self.following(&me.id) self.following(&me.id)
} }
/// returns events that are relevant to the authorized user, i.e. home /// returns events that are relevant to the authorized user, i.e. home
/// timeline & notifications /// timeline & notifications
/// pub fn streaming_user(&self) -> Result<MastodonStream> {
/// # Example
///
/// ```no_run
/// # extern crate elefren;
/// # use elefren::prelude::*;
/// # use std::error::Error;
/// use elefren::entities::event::Event;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// # let data = Data {
/// # base: "".into(),
/// # client_id: "".into(),
/// # client_secret: "".into(),
/// # redirect: "".into(),
/// # token: "".into(),
/// # };
/// let client = Mastodon::from(data);
/// for event in client.streaming_user()? {
/// match event {
/// Event::Update(ref status) => { /* .. */ },
/// Event::Notification(ref notification) => { /* .. */ },
/// Event::Delete(ref id) => { /* .. */ },
/// Event::FiltersChanged => { /* .. */ },
/// }
/// }
/// # Ok(())
/// # }
/// ```
fn streaming_user(&self) -> Result<Self::Stream> {
let mut url: url::Url = self.route("/api/v1/streaming").parse()?; let mut url: url::Url = self.route("/api/v1/streaming").parse()?;
url.query_pairs_mut() url.query_pairs_mut()
.append_pair("access_token", &self.token) .append_pair("access_token", &self.token)
@ -451,7 +382,7 @@ impl MastodonClient for Mastodon {
} }
/// returns all public statuses /// returns all public statuses
fn streaming_public(&self) -> Result<Self::Stream> { pub fn streaming_public(&self) -> Result<MastodonStream> {
let mut url: url::Url = self.route("/api/v1/streaming").parse()?; let mut url: url::Url = self.route("/api/v1/streaming").parse()?;
url.query_pairs_mut() url.query_pairs_mut()
.append_pair("access_token", &self.token) .append_pair("access_token", &self.token)
@ -471,7 +402,7 @@ impl MastodonClient for Mastodon {
} }
/// Returns all local statuses /// Returns all local statuses
fn streaming_local(&self) -> Result<Self::Stream> { pub fn streaming_local(&self) -> Result<MastodonStream> {
let mut url: url::Url = self.route("/api/v1/streaming").parse()?; let mut url: url::Url = self.route("/api/v1/streaming").parse()?;
url.query_pairs_mut() url.query_pairs_mut()
.append_pair("access_token", &self.token) .append_pair("access_token", &self.token)
@ -491,7 +422,7 @@ impl MastodonClient for Mastodon {
} }
/// Returns all public statuses for a particular hashtag /// Returns all public statuses for a particular hashtag
fn streaming_public_hashtag(&self, hashtag: &str) -> Result<Self::Stream> { pub fn streaming_public_hashtag(&self, hashtag: &str) -> Result<MastodonStream> {
let mut url: url::Url = self.route("/api/v1/streaming").parse()?; let mut url: url::Url = self.route("/api/v1/streaming").parse()?;
url.query_pairs_mut() url.query_pairs_mut()
.append_pair("access_token", &self.token) .append_pair("access_token", &self.token)
@ -512,7 +443,7 @@ impl MastodonClient for Mastodon {
} }
/// Returns all local statuses for a particular hashtag /// Returns all local statuses for a particular hashtag
fn streaming_local_hashtag(&self, hashtag: &str) -> Result<Self::Stream> { pub fn streaming_local_hashtag(&self, hashtag: &str) -> Result<MastodonStream> {
let mut url: url::Url = self.route("/api/v1/streaming").parse()?; let mut url: url::Url = self.route("/api/v1/streaming").parse()?;
url.query_pairs_mut() url.query_pairs_mut()
.append_pair("access_token", &self.token) .append_pair("access_token", &self.token)
@ -533,7 +464,7 @@ impl MastodonClient for Mastodon {
} }
/// Returns statuses for a list /// Returns statuses for a list
fn streaming_list(&self, list_id: &str) -> Result<Self::Stream> { pub fn streaming_list(&self, list_id: &str) -> Result<MastodonStream> {
let mut url: url::Url = self.route("/api/v1/streaming").parse()?; let mut url: url::Url = self.route("/api/v1/streaming").parse()?;
url.query_pairs_mut() url.query_pairs_mut()
.append_pair("access_token", &self.token) .append_pair("access_token", &self.token)
@ -554,7 +485,7 @@ impl MastodonClient for Mastodon {
} }
/// Returns all direct messages /// Returns all direct messages
fn streaming_direct(&self) -> Result<Self::Stream> { pub fn streaming_direct(&self) -> Result<MastodonStream> {
let mut url: url::Url = self.route("/api/v1/streaming").parse()?; let mut url: url::Url = self.route("/api/v1/streaming").parse()?;
url.query_pairs_mut() url.query_pairs_mut()
.append_pair("access_token", &self.token) .append_pair("access_token", &self.token)
@ -613,7 +544,7 @@ impl MastodonClient for Mastodon {
/// ## Errors /// ## Errors
/// This function may return an `Error::Http` before sending anything over the network if the /// This function may return an `Error::Http` before sending anything over the network if the
/// `MediaBuilder` was supplied with a reader and a `mimetype` string which cannot be pasrsed. /// `MediaBuilder` was supplied with a reader and a `mimetype` string which cannot be pasrsed.
fn media(&self, media: MediaBuilder) -> Result<Attachment> { pub fn media(&self, media: MediaBuilder) -> Result<Attachment> {
use media_builder::MediaBuilderData; use media_builder::MediaBuilderData;
let mut form = multipart::Form::new(); let mut form = multipart::Form::new();
@ -676,6 +607,7 @@ impl EventStream for WebSocket {
#[derive(Debug)] #[derive(Debug)]
/// Iterator that produces events from a mastodon streaming API event stream /// Iterator that produces events from a mastodon streaming API event stream
pub struct EventReader<R: EventStream>(R); pub struct EventReader<R: EventStream>(R);
impl<R: EventStream> Iterator for EventReader<R> { impl<R: EventStream> Iterator for EventReader<R> {
type Item = Event; type Item = Event;
@ -683,15 +615,19 @@ impl<R: EventStream> Iterator for EventReader<R> {
let mut lines = Vec::new(); let mut lines = Vec::new();
loop { loop {
if let Ok(line) = self.0.read_message() { if let Ok(line) = self.0.read_message() {
debug!("WS rx: {}", line);
let line = line.trim().to_string(); let line = line.trim().to_string();
if line.starts_with(':') || line.is_empty() { if line.starts_with(':') || line.is_empty() {
debug!("discard as comment");
continue; continue;
} }
lines.push(line); lines.push(line);
if let Ok(event) = self.make_event(&lines) { if let Ok(event) = self.make_event(&lines) {
debug!("Parsed event");
lines.clear(); lines.clear();
return Some(event); return Some(event);
} else { } else {
debug!("Failed to parse");
continue; continue;
} }
} }
@ -704,12 +640,14 @@ impl<R: EventStream> EventReader<R> {
let event; let event;
let data; let data;
if let Some(event_line) = lines.iter().find(|line| line.starts_with("event:")) { if let Some(event_line) = lines.iter().find(|line| line.starts_with("event:")) {
debug!("plaintext formatted event");
event = event_line[6..].trim().to_string(); event = event_line[6..].trim().to_string();
data = lines data = lines
.iter() .iter()
.find(|line| line.starts_with("data:")) .find(|line| line.starts_with("data:"))
.map(|x| x[5..].trim().to_string()); .map(|x| x[5..].trim().to_string());
} else { } else {
debug!("JSON formatted event");
use serde::Deserialize; use serde::Deserialize;
#[derive(Deserialize)] #[derive(Deserialize)]
struct Message { struct Message {
@ -834,11 +772,9 @@ impl MastodonUnauth {
Ok(EventReader(WebSocket(client))) Ok(EventReader(WebSocket(client)))
} }
}
impl MastodonUnauthenticated for MastodonUnauth {
/// GET /api/v1/statuses/:id /// GET /api/v1/statuses/:id
fn get_status(&self, id: &str) -> Result<Status> { pub fn get_status(&self, id: &str) -> Result<Status> {
let route = self.route("/api/v1/statuses")?; let route = self.route("/api/v1/statuses")?;
let route = route.join(id)?; let route = route.join(id)?;
let response = self.send(self.client.get(route))?; let response = self.send(self.client.get(route))?;
@ -846,7 +782,7 @@ impl MastodonUnauthenticated for MastodonUnauth {
} }
/// GET /api/v1/statuses/:id/context /// GET /api/v1/statuses/:id/context
fn get_context(&self, id: &str) -> Result<Context> { pub fn get_context(&self, id: &str) -> Result<Context> {
let route = self.route("/api/v1/statuses")?; let route = self.route("/api/v1/statuses")?;
let route = route.join(id)?; let route = route.join(id)?;
let route = route.join("context")?; let route = route.join("context")?;
@ -855,7 +791,7 @@ impl MastodonUnauthenticated for MastodonUnauth {
} }
/// GET /api/v1/statuses/:id/card /// GET /api/v1/statuses/:id/card
fn get_card(&self, id: &str) -> Result<Card> { pub fn get_card(&self, id: &str) -> Result<Card> {
let route = self.route("/api/v1/statuses")?; let route = self.route("/api/v1/statuses")?;
let route = route.join(id)?; let route = route.join(id)?;
let route = route.join("card")?; let route = route.join("card")?;

@ -14,33 +14,15 @@ macro_rules! methods {
}; };
} }
macro_rules! paged_routes { macro_rules! route_v1_paged {
(($method:ident) $name:ident: $url:expr => $ret:ty) => {
(($method:ident) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => {
doc_comment::doc_comment! { doc_comment::doc_comment! {
concat!( concat!(
"Equivalent to `", stringify!($method), " /api/v1/", "Equivalent to `", stringify!($method), " /api/v1/", $url, "`\n",
$url, "# Errors\n",
"`\n# Errors\nIf `access_token` is not set.", "If `access_token` is not set.",
"\n",
"```no_run",
"# extern crate elefren;\n",
"# use elefren::prelude::*;\n",
"# fn main() -> Result<(), Box<::std::error::Error>> {\n",
"# let data = Data {\n",
"# base: \"https://example.com\".into(),\n",
"# client_id: \"taosuah\".into(),\n",
"# client_secret: \"htnjdiuae\".into(),\n",
"# redirect: \"https://example.com\".into(),\n",
"# token: \"tsaohueaheis\".into(),\n",
"# };\n",
"let client = Mastodon::from(data);\n",
"client.", stringify!($name), "();\n",
"# Ok(())\n",
"# }\n",
"```"
), ),
fn $name(&self) -> Result<Page<$ret>> { pub fn $name(&self) -> Result<Page<$ret>> {
let url = self.route(concat!("/api/v1/", $url)); let url = self.route(concat!("/api/v1/", $url));
let response = self.send( let response = self.send(
self.client.$method(&url) self.client.$method(&url)
@ -48,20 +30,17 @@ macro_rules! paged_routes {
Page::new(self, response) Page::new(self, response)
} }
} }
paged_routes!{$($rest)*}
}; };
((get ($($(#[$m:meta])* $param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => { ((get ($($(#[$m:meta])* $param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty) => {
doc_comment::doc_comment! { doc_comment::doc_comment! {
concat!( concat!(
"Equivalent to `get /api/v1/", "Equivalent to `get /api/v1/", $url, "` with query params\n",
$url, "# Errors\n",
"`\n# Errors\nIf `access_token` is not set." "If `access_token` is not set.",
), ),
fn $name<'a>(&self, $($param: $typ,)*) -> Result<Page<$ret>> { pub fn $name<'a>(&self, $($param: $typ,)*) -> Result<Page<$ret>> {
use serde_urlencoded; use serde_urlencoded;
use serde::Serialize; use serde::Serialize;
@ -95,22 +74,18 @@ macro_rules! paged_routes {
Page::new(self, response) Page::new(self, response)
} }
} }
paged_routes!{$($rest)*}
}; };
() => {}
} }
macro_rules! route_v2 { macro_rules! route_v2 {
((get ($($param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => { ((get ($($param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty) => {
doc_comment::doc_comment! { doc_comment::doc_comment! {
concat!( concat!(
"Equivalent to `get /api/v2/", "Equivalent to `get /api/v2/",$url,"`\n",
$url, "# Errors\n",
"`\n# Errors\nIf `access_token` is not set." "If `access_token` is not set."
), ),
fn $name<'a>(&self, $($param: $typ,)*) -> Result<$ret> { pub fn $name<'a>(&self, $($param: $typ,)*) -> Result<$ret> {
use serde_urlencoded; use serde_urlencoded;
use serde::Serialize; use serde::Serialize;
@ -137,23 +112,18 @@ macro_rules! route_v2 {
self.get(self.route(&url)) self.get(self.route(&url))
} }
} }
route_v2!{$($rest)*}
}; };
() => {}
} }
macro_rules! route { macro_rules! route_v1 {
((get ($($param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty) => {
((get ($($param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => {
doc_comment::doc_comment! { doc_comment::doc_comment! {
concat!( concat!(
"Equivalent to `get /api/v1/", "Equivalent to `get /api/v1/", $url, "`\n",
$url, "# Errors\n",
"`\n# Errors\nIf `access_token` is not set." "If `access_token` is not set."
), ),
fn $name<'a>(&self, $($param: $typ,)*) -> Result<$ret> { pub fn $name<'a>(&self, $($param: $typ,)*) -> Result<$ret> {
use serde_urlencoded; use serde_urlencoded;
use serde::Serialize; use serde::Serialize;
@ -180,18 +150,16 @@ macro_rules! route {
self.get(self.route(&url)) self.get(self.route(&url))
} }
} }
route!{$($rest)*}
}; };
(($method:ident ($($param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => { (($method:ident ($($param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty) => {
doc_comment::doc_comment! { doc_comment::doc_comment! {
concat!( concat!(
"Equivalent to `", stringify!($method), " /api/v1/", "Equivalent to `", stringify!($method), " /api/v1/", $url, "`\n",
$url, "# Errors\n",
"`\n# Errors\nIf `access_token` is not set.", "If `access_token` is not set."
), ),
fn $name(&self, $($param: $typ,)*) -> Result<$ret> { pub fn $name(&self, $($param: $typ,)*) -> Result<$ret> {
let form_data = serde_json::json!({ let form_data = serde_json::json!({
$( $(
@ -215,107 +183,45 @@ macro_rules! route {
deserialise(response) deserialise(response)
} }
} }
route!{$($rest)*}
}; };
(($method:ident) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => { (($method:ident) $name:ident: $url:expr => $ret:ty) => {
doc_comment::doc_comment! { doc_comment::doc_comment! {
concat!( concat!(
"Equivalent to `", stringify!($method), " /api/v1/", "Equivalent to `", stringify!($method), " /api/v1/", $url, "`\n",
$url, "# Errors\n",
"`\n# Errors\nIf `access_token` is not set.", "If `access_token` is not set."
"\n",
"```no_run",
"# extern crate elefren;\n",
"# use elefren::prelude::*;\n",
"# fn main() -> Result<(), Box<::std::error::Error>> {\n",
"# let data = Data {\n",
"# base: \"https://example.com\".into(),\n",
"# client_id: \"taosuah\".into(),\n",
"# client_secret: \"htnjdiuae\".into(),\n",
"# redirect: \"https://example.com\".into(),\n",
"# token: \"tsaohueaheis\".into(),\n",
"# };\n",
"let client = Mastodon::from(data);\n",
"client.", stringify!($name), "();\n",
"# Ok(())\n",
"# }\n",
"```"
), ),
fn $name(&self) -> Result<$ret> { pub fn $name(&self) -> Result<$ret> {
self.$method(self.route(concat!("/api/v1/", $url))) self.$method(self.route(concat!("/api/v1/", $url)))
} }
} }
route!{$($rest)*}
}; };
() => {}
} }
macro_rules! route_id { macro_rules! route_v1_id {
(($method:ident) $name:ident: $url:expr => $ret:ty) => {
($(($method:ident) $name:ident: $url:expr => $ret:ty,)*) => { doc_comment::doc_comment! {
$( concat!(
doc_comment::doc_comment! { "Equivalent to `", stringify!($method), " /api/v1/", $url, "`\n",
concat!( "# Errors\n",
"Equivalent to `", stringify!($method), " /api/v1/", "If `access_token` is not set."
$url, ),
"`\n# Errors\nIf `access_token` is not set.", pub fn $name(&self, id: &str) -> Result<$ret> {
"\n", self.$method(self.route(&format!(concat!("/api/v1/", $url), id)))
"```no_run",
"# extern crate elefren;\n",
"# use elefren::prelude::*;\n",
"# fn main() -> Result<(), Box<::std::error::Error>> {\n",
"# let data = Data {\n",
"# base: \"https://example.com\".into(),\n",
"# client_id: \"taosuah\".into(),\n",
"# client_secret: \"htnjdiuae\".into(),\n",
"# redirect: \"https://example.com\".into(),\n",
"# token: \"tsaohueaheis\".into(),\n",
"# };\n",
"let client = Mastodon::from(data);\n",
"client.", stringify!($name), "(\"42\");\n",
"# Ok(())\n",
"# }\n",
"```"
),
fn $name(&self, id: &str) -> Result<$ret> {
self.$method(self.route(&format!(concat!("/api/v1/", $url), id)))
}
} }
)* }
} }
} }
macro_rules! paged_routes_with_id { macro_rules! route_v1_paged_id {
(($method:ident) $name:ident: $url:expr => $ret:ty) => {
(($method:ident) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => {
doc_comment::doc_comment! { doc_comment::doc_comment! {
concat!( concat!(
"Equivalent to `", stringify!($method), " /api/v1/", "Equivalent to `", stringify!($method), " /api/v1/", $url, "` with ID\n",
$url, "# Errors\n",
"`\n# Errors\nIf `access_token` is not set.", "If `access_token` is not set."
"\n",
"```no_run",
"# extern crate elefren;\n",
"# use elefren::prelude::*;\n",
"# fn main() -> Result<(), Box<::std::error::Error>> {\n",
"# let data = Data {\n",
"# base: \"https://example.com\".into(),\n",
"# client_id: \"taosuah\".into(),\n",
"# client_secret: \"htnjdiuae\".into(),\n",
"# redirect: \"https://example.com\".into(),\n",
"# token: \"tsaohueaheis\".into(),\n",
"# };\n",
"let client = Mastodon::from(data);\n",
"client.", stringify!($name), "(\"some-id\");\n",
"# Ok(())\n",
"# }\n",
"```"
), ),
fn $name(&self, id: &str) -> Result<Page<$ret>> { pub fn $name(&self, id: &str) -> Result<Page<$ret>> {
let url = self.route(&format!(concat!("/api/v1/", $url), id)); let url = self.route(&format!(concat!("/api/v1/", $url), id));
let response = self.send( let response = self.send(
self.client.$method(&url) self.client.$method(&url)
@ -324,9 +230,5 @@ macro_rules! paged_routes_with_id {
Page::new(self, response) Page::new(self, response)
} }
} }
paged_routes_with_id!{$($rest)*}
}; };
() => {}
} }

Loading…
Cancel
Save