|
|
|
@ -47,10 +47,10 @@ |
|
|
|
|
//! 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 => { /* .. */ },
|
|
|
|
|
//! Event::Update(ref status) => { /* .. */ }
|
|
|
|
|
//! Event::Notification(ref notification) => { /* .. */ }
|
|
|
|
|
//! Event::Delete(ref id) => { /* .. */ }
|
|
|
|
|
//! Event::FiltersChanged => { /* .. */ }
|
|
|
|
|
//! }
|
|
|
|
|
//! }
|
|
|
|
|
//! # Ok(())
|
|
|
|
@ -69,28 +69,30 @@ |
|
|
|
|
unused_import_braces, |
|
|
|
|
unused_qualifications |
|
|
|
|
)] |
|
|
|
|
#![cfg_attr(feature = "nightly", allow(broken_intra_doc_links))] |
|
|
|
|
#![allow(broken_intra_doc_links)] |
|
|
|
|
// #![cfg_attr(feature = "nightly", allow(broken_intra_doc_links))]
|
|
|
|
|
// #![allow(broken_intra_doc_links)]
|
|
|
|
|
|
|
|
|
|
#[macro_use] |
|
|
|
|
extern crate log; |
|
|
|
|
|
|
|
|
|
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 tungstenite::stream::MaybeTlsStream; |
|
|
|
|
|
|
|
|
|
use crate::{entities::prelude::*, page::Page}; |
|
|
|
|
|
|
|
|
|
pub use crate::{ |
|
|
|
|
data::Data, |
|
|
|
|
errors::{ApiError, Error, Result}, |
|
|
|
|
mastodon_client::{MastodonClient, MastodonUnauthenticated}, |
|
|
|
|
// mastodon_client::{MastodonClient, MastodonUnauthenticated},
|
|
|
|
|
media_builder::MediaBuilder, |
|
|
|
|
registration::Registration, |
|
|
|
|
requests::{AddFilterRequest, AddPushRequest, StatusesRequest, UpdateCredsRequest, UpdatePushRequest}, |
|
|
|
|
status_builder::{NewStatus, StatusBuilder}, |
|
|
|
|
}; |
|
|
|
|
pub use isolang::Language; |
|
|
|
|
use tungstenite::stream::MaybeTlsStream; |
|
|
|
|
use std::net::TcpStream; |
|
|
|
|
|
|
|
|
|
/// Registering your App
|
|
|
|
|
pub mod apps; |
|
|
|
@ -117,22 +119,27 @@ pub mod scopes; |
|
|
|
|
pub mod status_builder; |
|
|
|
|
#[macro_use] |
|
|
|
|
mod macros; |
|
|
|
|
|
|
|
|
|
/// Automatically import the things you need
|
|
|
|
|
pub mod prelude { |
|
|
|
|
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.
|
|
|
|
|
#[derive(Clone, Debug)] |
|
|
|
|
pub struct Mastodon { |
|
|
|
|
|
|
|
|
|
client: Client, |
|
|
|
|
/// Raw data about your mastodon instance.
|
|
|
|
|
pub data: Data, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl Mastodon { |
|
|
|
|
|
|
|
|
|
methods![get, post, delete,]; |
|
|
|
|
|
|
|
|
|
fn route(&self, url: &str) -> String { |
|
|
|
@ -154,79 +161,70 @@ impl From<Data> for Mastodon { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl MastodonClient for Mastodon { |
|
|
|
|
type Stream = EventReader<WebSocket>; |
|
|
|
|
|
|
|
|
|
paged_routes! { |
|
|
|
|
(get) favourites: "favourites" => Status, |
|
|
|
|
(get) blocks: "blocks" => Account, |
|
|
|
|
(get) domain_blocks: "domain_blocks" => String, |
|
|
|
|
(get) follow_requests: "follow_requests" => Account, |
|
|
|
|
(get) get_home_timeline: "timelines/home" => Status, |
|
|
|
|
(get) get_local_timeline: "timelines/public?local=true" => Status, |
|
|
|
|
(get) get_federated_timeline: "timelines/public?local=false" => Status, |
|
|
|
|
(get) get_emojis: "custom_emojis" => Emoji, |
|
|
|
|
(get) mutes: "mutes" => Account, |
|
|
|
|
(get) notifications: "notifications" => Notification, |
|
|
|
|
(get) reports: "reports" => Report, |
|
|
|
|
(get (q: &'a str, #[serde(skip_serializing_if = "Option::is_none")] limit: Option<u64>, following: bool,)) search_accounts: "accounts/search" => Account, |
|
|
|
|
(get) get_endorsements: "endorsements" => Account, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
paged_routes_with_id! { |
|
|
|
|
(get) followers: "accounts/{}/followers" => Account, |
|
|
|
|
(get) following: "accounts/{}/following" => Account, |
|
|
|
|
(get) reblogged_by: "statuses/{}/reblogged_by" => Account, |
|
|
|
|
(get) favourited_by: "statuses/{}/favourited_by" => Account, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
route! { |
|
|
|
|
(delete (domain: String,)) unblock_domain: "domain_blocks" => Empty, |
|
|
|
|
(get) instance: "instance" => Instance, |
|
|
|
|
(get) verify_credentials: "accounts/verify_credentials" => Account, |
|
|
|
|
(post (account_id: &str, status_ids: Vec<&str>, comment: String,)) report: "reports" => Report, |
|
|
|
|
(post (domain: String,)) block_domain: "domain_blocks" => Empty, |
|
|
|
|
(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, |
|
|
|
|
(post (uri: Cow<'static, str>,)) follows: "follows" => Account, |
|
|
|
|
(post) clear_notifications: "notifications/clear" => Empty, |
|
|
|
|
(post (id: &str,)) dismiss_notification: "notifications/dismiss" => Empty, |
|
|
|
|
(get) get_push_subscription: "push/subscription" => Subscription, |
|
|
|
|
(delete) delete_push_subscription: "push/subscription" => Empty, |
|
|
|
|
(get) get_filters: "filters" => Vec<Filter>, |
|
|
|
|
(get) get_follow_suggestions: "suggestions" => Vec<Account>, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
route_v2! { |
|
|
|
|
(get (q: &'a str, resolve: bool,)) search_v2: "search" => SearchResultV2, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
route_id! { |
|
|
|
|
(get) get_account: "accounts/{}" => Account, |
|
|
|
|
(post) follow: "accounts/{}/follow" => Relationship, |
|
|
|
|
(post) unfollow: "accounts/{}/unfollow" => Relationship, |
|
|
|
|
(post) block: "accounts/{}/block" => Relationship, |
|
|
|
|
(post) unblock: "accounts/{}/unblock" => Relationship, |
|
|
|
|
(get) mute: "accounts/{}/mute" => Relationship, |
|
|
|
|
(get) unmute: "accounts/{}/unmute" => Relationship, |
|
|
|
|
(get) get_notification: "notifications/{}" => Notification, |
|
|
|
|
(get) get_status: "statuses/{}" => Status, |
|
|
|
|
(get) get_context: "statuses/{}/context" => Context, |
|
|
|
|
(get) get_card: "statuses/{}/card" => Card, |
|
|
|
|
(post) reblog: "statuses/{}/reblog" => Status, |
|
|
|
|
(post) unreblog: "statuses/{}/unreblog" => Status, |
|
|
|
|
(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, |
|
|
|
|
(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> { |
|
|
|
|
type MastodonStream = EventReader<WebSocket>; |
|
|
|
|
|
|
|
|
|
impl Mastodon { |
|
|
|
|
route_v1_paged!((get) favourites: "favourites" => Status); |
|
|
|
|
route_v1_paged!((get) blocks: "blocks" => Account); |
|
|
|
|
route_v1_paged!((get) domain_blocks: "domain_blocks" => String); |
|
|
|
|
route_v1_paged!((get) follow_requests: "follow_requests" => Account); |
|
|
|
|
route_v1_paged!((get) get_home_timeline: "timelines/home" => Status); |
|
|
|
|
route_v1_paged!((get) get_local_timeline: "timelines/public?local=true" => Status); |
|
|
|
|
route_v1_paged!((get) get_federated_timeline: "timelines/public?local=false" => Status); |
|
|
|
|
route_v1_paged!((get) get_emojis: "custom_emojis" => Emoji); |
|
|
|
|
route_v1_paged!((get) mutes: "mutes" => Account); |
|
|
|
|
route_v1_paged!((get) notifications: "notifications" => Notification); |
|
|
|
|
route_v1_paged!((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); |
|
|
|
|
route_v1_paged!((get) get_endorsements: "endorsements" => Account); |
|
|
|
|
|
|
|
|
|
route_v1_paged_id!((get) followers: "accounts/{}/followers" => Account); |
|
|
|
|
route_v1_paged_id!((get) following: "accounts/{}/following" => Account); |
|
|
|
|
route_v1_paged_id!((get) reblogged_by: "statuses/{}/reblogged_by" => Account); |
|
|
|
|
route_v1_paged_id!((get) favourited_by: "statuses/{}/favourited_by" => Account); |
|
|
|
|
|
|
|
|
|
route_v1!((delete (domain: String,)) unblock_domain: "domain_blocks" => Empty); |
|
|
|
|
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_v1!((post (domain: String,)) block_domain: "domain_blocks" => Empty); |
|
|
|
|
route_v1!((post (id: &str,)) authorize_follow_request: "accounts/follow_requests/authorize" => Empty); |
|
|
|
|
route_v1!((post (id: &str,)) reject_follow_request: "accounts/follow_requests/reject" => Empty); |
|
|
|
|
route_v1!((get (q: &'a str, resolve: bool,)) search: "search" => SearchResult); |
|
|
|
|
route_v1!((post (uri: Cow<'static, str>,)) follows: "follows" => Account); |
|
|
|
|
route_v1!((post) clear_notifications: "notifications/clear" => Empty); |
|
|
|
|
route_v1!((post (id: &str,)) dismiss_notification: "notifications/dismiss" => Empty); |
|
|
|
|
route_v1!((get) get_push_subscription: "push/subscription" => Subscription); |
|
|
|
|
route_v1!((delete) delete_push_subscription: "push/subscription" => Empty); |
|
|
|
|
route_v1!((get) get_filters: "filters" => Vec<Filter>); |
|
|
|
|
route_v1!((get) get_follow_suggestions: "suggestions" => Vec<Account>); |
|
|
|
|
|
|
|
|
|
route_v2!((get (q: &'a str, resolve: bool,)) search_v2: "search" => SearchResultV2); |
|
|
|
|
|
|
|
|
|
route_v1_id!((get) get_account: "accounts/{}" => 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_v1_id!((post) unblock: "accounts/{}/unblock" => Relationship); |
|
|
|
|
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_v1_id!((get) get_status: "statuses/{}" => Status); |
|
|
|
|
route_v1_id!((get) get_context: "statuses/{}/context" => Context); |
|
|
|
|
route_v1_id!((get) get_card: "statuses/{}/card" => Card); |
|
|
|
|
route_v1_id!((post) reblog: "statuses/{}/reblog" => Status); |
|
|
|
|
route_v1_id!((post) unreblog: "statuses/{}/unreblog" => Status); |
|
|
|
|
route_v1_id!((post) favourite: "statuses/{}/favourite" => Status); |
|
|
|
|
route_v1_id!((post) unfavourite: "statuses/{}/unfavourite" => Status); |
|
|
|
|
route_v1_id!((delete) delete_status: "statuses/{}" => Empty); |
|
|
|
|
route_v1_id!((get) get_filter: "filters/{}" => Filter); |
|
|
|
|
route_v1_id!((delete) delete_filter: "filters/{}" => Empty); |
|
|
|
|
route_v1_id!((delete) delete_from_suggestions: "suggestions/{}" => Empty); |
|
|
|
|
route_v1_id!((post) endorse_user: "accounts/{}/pin" => Relationship); |
|
|
|
|
route_v1_id!((post) unendorse_user: "accounts/{}/unpin" => Relationship); |
|
|
|
|
|
|
|
|
|
/// POST /api/v1/filters
|
|
|
|
|
pub 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))?; |
|
|
|
|
|
|
|
|
@ -242,7 +240,7 @@ impl MastodonClient for Mastodon { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 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 response = self.send(self.client.put(&url).json(&request))?; |
|
|
|
|
|
|
|
|
@ -257,7 +255,8 @@ impl MastodonClient for Mastodon { |
|
|
|
|
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 url = self.route("/api/v1/accounts/update_credentials"); |
|
|
|
|
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.
|
|
|
|
|
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))?; |
|
|
|
|
|
|
|
|
|
deserialise(response) |
|
|
|
@ -282,7 +281,7 @@ impl MastodonClient for Mastodon { |
|
|
|
|
|
|
|
|
|
/// Get timeline filtered by a hashtag(eg. `#coffee`) either locally or
|
|
|
|
|
/// 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 url = if local { |
|
|
|
|
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
|
|
|
|
|
/// and or excluding replies.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
///
|
|
|
|
|
/// ```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>>>, |
|
|
|
|
pub 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); |
|
|
|
|
|
|
|
|
@ -352,7 +311,7 @@ impl MastodonClient for Mastodon { |
|
|
|
|
|
|
|
|
|
/// Returns the client account's relationship to a list of other accounts.
|
|
|
|
|
/// 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?"); |
|
|
|
|
|
|
|
|
|
if ids.len() == 1 { |
|
|
|
@ -373,7 +332,7 @@ impl MastodonClient for Mastodon { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 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 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
|
|
|
|
|
/// 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 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
|
|
|
|
|
fn follows_me(&self) -> Result<Page<Account>> { |
|
|
|
|
pub fn follows_me(&self) -> Result<Page<Account>> { |
|
|
|
|
let me = self.verify_credentials()?; |
|
|
|
|
self.followers(&me.id) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 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()?; |
|
|
|
|
self.following(&me.id) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// returns events that are relevant to the authorized user, i.e. home
|
|
|
|
|
/// timeline & notifications
|
|
|
|
|
///
|
|
|
|
|
/// # 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> { |
|
|
|
|
pub fn streaming_user(&self) -> Result<MastodonStream> { |
|
|
|
|
let mut url: url::Url = self.route("/api/v1/streaming").parse()?; |
|
|
|
|
url.query_pairs_mut() |
|
|
|
|
.append_pair("access_token", &self.token) |
|
|
|
@ -451,7 +382,7 @@ impl MastodonClient for Mastodon { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 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()?; |
|
|
|
|
url.query_pairs_mut() |
|
|
|
|
.append_pair("access_token", &self.token) |
|
|
|
@ -471,7 +402,7 @@ impl MastodonClient for Mastodon { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 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()?; |
|
|
|
|
url.query_pairs_mut() |
|
|
|
|
.append_pair("access_token", &self.token) |
|
|
|
@ -491,7 +422,7 @@ impl MastodonClient for Mastodon { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 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()?; |
|
|
|
|
url.query_pairs_mut() |
|
|
|
|
.append_pair("access_token", &self.token) |
|
|
|
@ -512,7 +443,7 @@ impl MastodonClient for Mastodon { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 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()?; |
|
|
|
|
url.query_pairs_mut() |
|
|
|
|
.append_pair("access_token", &self.token) |
|
|
|
@ -533,7 +464,7 @@ impl MastodonClient for Mastodon { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 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()?; |
|
|
|
|
url.query_pairs_mut() |
|
|
|
|
.append_pair("access_token", &self.token) |
|
|
|
@ -554,7 +485,7 @@ impl MastodonClient for Mastodon { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 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()?; |
|
|
|
|
url.query_pairs_mut() |
|
|
|
|
.append_pair("access_token", &self.token) |
|
|
|
@ -613,7 +544,7 @@ impl MastodonClient for Mastodon { |
|
|
|
|
/// ## Errors
|
|
|
|
|
/// 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.
|
|
|
|
|
fn media(&self, media: MediaBuilder) -> Result<Attachment> { |
|
|
|
|
pub fn media(&self, media: MediaBuilder) -> Result<Attachment> { |
|
|
|
|
use media_builder::MediaBuilderData; |
|
|
|
|
|
|
|
|
|
let mut form = multipart::Form::new(); |
|
|
|
@ -676,6 +607,7 @@ impl EventStream for WebSocket { |
|
|
|
|
#[derive(Debug)] |
|
|
|
|
/// Iterator that produces events from a mastodon streaming API event stream
|
|
|
|
|
pub struct EventReader<R: EventStream>(R); |
|
|
|
|
|
|
|
|
|
impl<R: EventStream> Iterator for EventReader<R> { |
|
|
|
|
type Item = Event; |
|
|
|
|
|
|
|
|
@ -683,15 +615,19 @@ impl<R: EventStream> Iterator for EventReader<R> { |
|
|
|
|
let mut lines = Vec::new(); |
|
|
|
|
loop { |
|
|
|
|
if let Ok(line) = self.0.read_message() { |
|
|
|
|
debug!("WS rx: {}", line); |
|
|
|
|
let line = line.trim().to_string(); |
|
|
|
|
if line.starts_with(':') || line.is_empty() { |
|
|
|
|
debug!("discard as comment"); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
lines.push(line); |
|
|
|
|
if let Ok(event) = self.make_event(&lines) { |
|
|
|
|
debug!("Parsed event"); |
|
|
|
|
lines.clear(); |
|
|
|
|
return Some(event); |
|
|
|
|
} else { |
|
|
|
|
debug!("Failed to parse"); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -704,12 +640,14 @@ impl<R: EventStream> EventReader<R> { |
|
|
|
|
let event; |
|
|
|
|
let data; |
|
|
|
|
if let Some(event_line) = lines.iter().find(|line| line.starts_with("event:")) { |
|
|
|
|
debug!("plaintext formatted event"); |
|
|
|
|
event = event_line[6..].trim().to_string(); |
|
|
|
|
data = lines |
|
|
|
|
.iter() |
|
|
|
|
.find(|line| line.starts_with("data:")) |
|
|
|
|
.map(|x| x[5..].trim().to_string()); |
|
|
|
|
} else { |
|
|
|
|
debug!("JSON formatted event"); |
|
|
|
|
use serde::Deserialize; |
|
|
|
|
#[derive(Deserialize)] |
|
|
|
|
struct Message { |
|
|
|
@ -834,11 +772,9 @@ impl MastodonUnauth { |
|
|
|
|
|
|
|
|
|
Ok(EventReader(WebSocket(client))) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl MastodonUnauthenticated for MastodonUnauth { |
|
|
|
|
/// 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 = route.join(id)?; |
|
|
|
|
let response = self.send(self.client.get(route))?; |
|
|
|
@ -846,7 +782,7 @@ impl MastodonUnauthenticated for MastodonUnauth { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 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 = route.join(id)?; |
|
|
|
|
let route = route.join("context")?; |
|
|
|
@ -855,7 +791,7 @@ impl MastodonUnauthenticated for MastodonUnauth { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 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 = route.join(id)?; |
|
|
|
|
let route = route.join("card")?; |
|
|
|
|