all asyncified except for streaming

master
Ondřej Hruška 3 years ago
parent b6b7372ebb
commit c967e59ffb
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 34
      Cargo.toml
  2. 14
      src/entities/itemsiter.rs
  3. 16
      src/errors.rs
  4. 4
      src/helpers/cli.rs
  5. 190
      src/lib.rs
  6. 46
      src/macros.rs
  7. 383
      src/mastodon_client.rs
  8. 9
      src/media_builder.rs
  9. 16
      src/page.rs
  10. 27
      src/registration.rs

@ -12,36 +12,20 @@ edition = "2018"
[dependencies]
doc-comment = "0.3"
envy = { version = "0.4.0", optional = true }
hyper-old-types = "0.11.0"
#envy = { version = "0.4.0", optional = true }
hyper-old-types = "0.11.0" # Used to parse the link header
isolang = { version = "1.0", features = ["serde_serialize"] }
log = "^0.4"
reqwest = { version = "0.11.4", default-features = false, features = ["json", "blocking", "multipart"] }
reqwest = { version = "0.11.4", default-features = false, features = ["json", "blocking", "multipart", "rustls-tls", "stream"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_urlencoded = "0.6.1"
serde_qs = "0.8.4"
url = "2.1.1"
tap-reader = "1"
#tap-reader = "1"
toml = { version = "^0.5.0", optional = true }
tungstenite = "0.15.0"
[dependencies.chrono]
version = "0.4"
features = ["serde"]
[features]
default = ["reqwest/default-tls","tungstenite/native-tls"]
json = []
env = ["envy"]
all = ["toml", "json", "env"]
rustls-tls = ["reqwest/rustls-tls","tungstenite/__rustls-tls"]
nightly = []
[dev-dependencies]
tempfile = "3.0.3"
indoc = "1.0.2"
pretty_env_logger = "0.4.0"
[package.metadata.docs.rs]
features = ["all"]
tokio-tungstenite = { version = "0.15.0", features = ["rustls-tls"] }
futures = { version = "0.3" }
tokio = {version = "1", features = ["full"] }
tokio-util = { version = "0.6", features = [ "compat", "io" ] }
chrono = { version = "0.4", features = ["serde"] }

@ -24,7 +24,7 @@ use serde::Deserialize;
/// # }
/// ```
#[derive(Debug, Clone)]
pub(crate) struct ItemsIter<'a, T: Clone + for<'de> Deserialize<'de>> {
pub struct ItemsIter<'a, T: Clone + for<'de> Deserialize<'de>> {
page: Page<'a, T>,
buffer: Vec<T>,
cur_idx: usize,
@ -45,8 +45,8 @@ impl<'a, T: Clone + for<'de> Deserialize<'de>> ItemsIter<'a, T> {
self.buffer.is_empty() || self.cur_idx == self.buffer.len()
}
fn fill_next_page(&mut self) -> Option<()> {
let items = if let Ok(items) = self.page.next_page() {
async fn fill_next_page(&mut self) -> Option<()> {
let items = if let Ok(items) = self.page.next_page().await {
items
} else {
return None;
@ -62,12 +62,8 @@ impl<'a, T: Clone + for<'de> Deserialize<'de>> ItemsIter<'a, T> {
None
}
}
}
impl<'a, T: Clone + for<'de> Deserialize<'de>> Iterator for ItemsIter<'a, T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
pub async fn next_item(&mut self) -> Option<T> {
if self.use_initial {
if self.page.initial_items.is_empty() || self.cur_idx == self.page.initial_items.len() {
return None;
@ -81,7 +77,7 @@ impl<'a, T: Clone + for<'de> Deserialize<'de>> Iterator for ItemsIter<'a, T> {
}
Some(self.page.initial_items[idx].clone())
} else {
if self.need_next_page() && self.fill_next_page().is_none() {
if self.need_next_page() && self.fill_next_page().await.is_none() {
return None;
}
let idx = self.cur_idx;

@ -12,7 +12,7 @@ use reqwest::{header::ToStrError as HeaderStrError, Error as HttpError, StatusCo
use serde_json::Error as SerdeError;
use serde_qs::Error as SerdeQsError;
use serde_urlencoded::ser::Error as UrlEncodedError;
use tungstenite::error::Error as WebSocketError;
// use tungstenite::error::Error as WebSocketError;
use url::ParseError as UrlError;
/// Convience type over `std::result::Result` with `Error` as the error type.
@ -57,14 +57,14 @@ pub enum Error {
HeaderStrError(HeaderStrError),
/// Error parsing the http Link header
HeaderParseError(HeaderParseError),
#[cfg(feature = "env")]
/// Error deserializing from the environment
Envy(EnvyError),
// #[cfg(feature = "env")]
// /// Error deserializing from the environment
// Envy(EnvyError),
/// Error serializing to a query string
SerdeQs(SerdeQsError),
/// WebSocket error
WebSocket(WebSocketError),
/// Other errors
// WebSocket(WebSocketError),
// /// Other errors
Other(String),
}
@ -92,7 +92,7 @@ impl error::Error for Error {
#[cfg(feature = "env")]
Error::Envy(ref e) => e,
Error::SerdeQs(ref e) => e,
Error::WebSocket(ref e) => e,
// Error::WebSocket(ref e) => e,
Error::Client(..) | Error::Server(..) => return None,
Error::ClientIdRequired => return None,
@ -148,7 +148,7 @@ from! {
HeaderParseError, HeaderParseError,
#[cfg(feature = "env")] EnvyError, Envy,
SerdeQsError, SerdeQs,
WebSocketError, WebSocket,
// WebSocketError, WebSocket,
String, Other,
}

@ -4,7 +4,7 @@ use crate::{errors::Result, registration::Registered, Mastodon};
/// Finishes the authentication process for the given `Registered` object,
/// using the command-line
pub fn authenticate(registration: Registered) -> Result<Mastodon> {
pub async fn authenticate(registration: Registered) -> Result<Mastodon> {
let url = registration.authorize_url()?;
let stdout = io::stdout();
@ -20,5 +20,5 @@ pub fn authenticate(registration: Registered) -> Result<Mastodon> {
let mut input = String::new();
stdin.read_line(&mut input)?;
let code = input.trim();
registration.complete(code)
registration.complete(code).await
}

@ -57,31 +57,30 @@
//! # }
//! ```
#![deny(
missing_docs,
warnings,
missing_debug_implementations,
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces,
unused_qualifications
)]
// #![deny(
// missing_docs,
// warnings,
// missing_debug_implementations,
// missing_copy_implementations,
// trivial_casts,
// trivial_numeric_casts,
// unsafe_code,
// unstable_features,
// unused_import_braces,
// unused_qualifications
// )]
// #![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 std::{borrow::Cow, ops};
pub use isolang::Language;
use reqwest::blocking::{Client, multipart, RequestBuilder, Response};
use tap_reader::Tap;
use tungstenite::stream::MaybeTlsStream;
use reqwest::{Client, multipart, RequestBuilder, Response, Body};
// use tap_reader::Tap;
// use tungstenite::stream::MaybeTlsStream;
use crate::{entities::prelude::*, page::Page};
pub use crate::{
@ -104,7 +103,6 @@ pub mod entities;
pub mod errors;
/// Collection of helpers for serializing/deserializing `Data` objects
pub mod helpers;
mod mastodon_client;
/// Constructing media attachments for a status.
pub mod media_builder;
/// Handling multiple pages of entities.
@ -132,37 +130,35 @@ pub mod prelude {
/// 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 {
impl From<Data> for Mastodon {
/// Creates a mastodon instance from the data struct.
fn from(data: Data) -> Mastodon {
let mut builder = MastodonBuilder::new();
builder.data(data);
builder.build().expect("We know `data` is present, so this should be fine")
}
}
// type MastodonStream = EventReader<WebSocket>;
impl Mastodon {
methods![get, post, delete,];
fn route(&self, url: &str) -> String {
format!("{}{}", self.base, url)
}
pub(crate) fn send(&self, req: RequestBuilder) -> Result<Response> {
pub(crate) async fn send(&self, req: RequestBuilder) -> Result<Response> {
let request = req.bearer_auth(&self.token).build()?;
Ok(self.client.execute(request)?)
Ok(self.client.execute(request).await?)
}
}
impl From<Data> for Mastodon {
/// Creates a mastodon instance from the data struct.
fn from(data: Data) -> Mastodon {
let mut builder = MastodonBuilder::new();
builder.data(data);
builder.build().expect("We know `data` is present, so this should be fine")
}
}
type MastodonStream = EventReader<WebSocket>;
impl Mastodon {
route_v1_paged!((get) favourites: "favourites" => Status);
route_v1_paged!((get) blocks: "blocks" => Account);
@ -224,9 +220,9 @@ impl Mastodon {
route_v1_id!((post) unendorse_user: "accounts/{}/unpin" => Relationship);
/// POST /api/v1/filters
pub fn add_filter(&self, request: &mut AddFilterRequest) -> Result<Filter> {
pub async 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 response = self.send(self.client.post(&url).json(&request)).await?;
let status = response.status();
@ -236,13 +232,13 @@ impl Mastodon {
return Err(Error::Server(status));
}
deserialise(response)
deserialise_response(response).await
}
/// PUT /api/v1/filters/:id
pub fn update_filter(&self, id: &str, request: &mut AddFilterRequest) -> Result<Filter> {
pub async 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))?;
let response = self.send(self.client.put(&url).json(&request)).await?;
let status = response.status();
@ -252,14 +248,14 @@ impl Mastodon {
return Err(Error::Server(status));
}
deserialise(response)
deserialise_response(response).await
}
/// PATCH /api/v1/accounts/update_credentials
pub fn update_credentials(&self, builder: &mut UpdateCredsRequest) -> Result<Account> {
pub async 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))?;
let response = self.send(self.client.patch(&url).json(&changes)).await?;
let status = response.status();
@ -269,19 +265,19 @@ impl Mastodon {
return Err(Error::Server(status));
}
deserialise(response)
deserialise_response(response).await
}
/// Post a new status to the account.
pub fn new_status(&self, status: NewStatus) -> Result<Status> {
let response = self.send(self.client.post(&self.route("/api/v1/statuses")).json(&status))?;
pub async fn new_status(&self, status: NewStatus) -> Result<Status> {
let response = self.send(self.client.post(&self.route("/api/v1/statuses")).json(&status)).await?;
deserialise(response)
deserialise_response(response).await
}
/// Get timeline filtered by a hashtag(eg. `#coffee`) either locally or
/// federated.
pub fn get_hashtag_timeline(&self, hashtag: &str, local: bool) -> Result<Page<Status>> {
pub async fn get_hashtag_timeline<'a>(&'a self, hashtag: &str, local: bool) -> Result<Page<'a, Status>> {
let base = "/api/v1/timelines/tag/";
let url = if local {
self.route(&format!("{}{}?local=1", base, hashtag))
@ -289,12 +285,12 @@ impl Mastodon {
self.route(&format!("{}{}", base, hashtag))
};
Page::new(self, self.send(self.client.get(&url))?)
Page::new(self, self.send(self.client.get(&url)).await?).await
}
/// Get statuses of a single account by id. Optionally only with pictures
/// and or excluding replies.
pub fn statuses<'a, 'b: 'a, S>(&'b self, id: &'b str, request: S) -> Result<Page<Status>>
pub async fn statuses<'a, 'b: 'a, S>(&'b self, id: &'b str, request: S) -> Result<Page<'b, Status>>
where
S: Into<Option<StatusesRequest<'a>>>,
{
@ -304,14 +300,14 @@ impl Mastodon {
url = format!("{}{}", url, request.to_querystring()?);
}
let response = self.send(self.client.get(&url))?;
let response = self.send(self.client.get(&url)).await?;
Page::new(self, response)
Page::new(self, response).await
}
/// Returns the client account's relationship to a list of other accounts.
/// Such as whether they follow them or vice versa.
pub fn relationships(&self, ids: &[&str]) -> Result<Page<Relationship>> {
pub async fn relationships<'a>(&'a self, ids: &[&str]) -> Result<Page<'a, Relationship>> {
let mut url = self.route("/api/v1/accounts/relationships?");
if ids.len() == 1 {
@ -326,40 +322,42 @@ impl Mastodon {
url.pop();
}
let response = self.send(self.client.get(&url))?;
let response = self.send(self.client.get(&url)).await?;
Page::new(self, response)
Page::new(self, response).await
}
/// Add a push notifications subscription
pub fn add_push_subscription(&self, request: &AddPushRequest) -> Result<Subscription> {
pub async 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))?;
let response = self.send(self.client.post(&self.route("/api/v1/push/subscription")).json(&request)).await?;
deserialise(response)
deserialise_response(response).await
}
/// Update the `data` portion of the push subscription associated with this
/// access token
pub fn update_push_data(&self, request: &UpdatePushRequest) -> Result<Subscription> {
pub async 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))?;
let response = self.send(self.client.put(&self.route("/api/v1/push/subscription")).json(&request)).await?;
deserialise(response)
deserialise_response(response).await
}
/// Get all accounts that follow the authenticated user
pub fn follows_me(&self) -> Result<Page<Account>> {
let me = self.verify_credentials()?;
self.followers(&me.id)
pub async fn follows_me<'a>(&'a self) -> Result<Page<'a, Account>> {
let me = self.verify_credentials().await?;
self.followers(&me.id).await
}
/// Get all accounts that the authenticated user follows
pub fn followed_by_me(&self) -> Result<Page<Account>> {
let me = self.verify_credentials()?;
self.following(&me.id)
pub async fn followed_by_me<'a>(&'a self) -> Result<Page<'a, Account>> {
let me = self.verify_credentials().await?;
self.following(&me.id).await
}
/*
/// returns events that are relevant to the authorized user, i.e. home
/// timeline & notifications
pub fn streaming_user(&self) -> Result<MastodonStream> {
@ -367,7 +365,7 @@ impl Mastodon {
url.query_pairs_mut()
.append_pair("access_token", &self.token)
.append_pair("stream", "user");
let mut url: url::Url = reqwest::blocking::get(url.as_str())?.url().as_str().parse()?;
let mut url: url::Url = reqwest::get(url.as_str())?.url().as_str().parse()?;
let new_scheme = match url.scheme() {
"http" => "ws",
"https" => "wss",
@ -503,6 +501,7 @@ impl Mastodon {
Ok(EventReader(WebSocket(client)))
}
*/
/// Upload some media to the server for possible attaching to a new status
///
@ -544,14 +543,13 @@ impl 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.
pub fn media(&self, media: MediaBuilder) -> Result<Attachment> {
pub async fn media(&self, media: MediaBuilder) -> Result<Attachment> {
use media_builder::MediaBuilderData;
let mut form = multipart::Form::new();
form = match media.data {
MediaBuilderData::Reader(reader) => {
let mut part = multipart::Part::reader(reader);
let mut part = multipart::Part::stream(Body::wrap_stream(tokio_util::io::ReaderStream::new(reader)));
if let Some(filename) = media.filename {
part = part.file_name(filename);
}
@ -562,7 +560,12 @@ impl Mastodon {
form.part("file", part)
}
MediaBuilderData::File(file) => form.file("file", &file)?,
MediaBuilderData::File(file) => {
let f = tokio::fs::OpenOptions::new().read(true).open(file).await?;
let part = multipart::Part::stream(Body::wrap_stream(tokio_util::io::ReaderStream::new(f)));
form.part("file", part)
// form.file("file", &file)?
},
};
if let Some(description) = media.description {
@ -573,12 +576,14 @@ impl Mastodon {
form = form.text("focus", format!("{},{}", x, y));
}
let response = self.send(self.client.post(&self.route("/api/v1/media")).multipart(form))?;
let response = self.send(self.client.post(&self.route("/api/v1/media")).multipart(form)).await?;
deserialise(response)
deserialise_response(response).await
}
}
/*
#[derive(Debug)]
/// WebSocket newtype so that EventStream can be implemented without coherency
/// issues
@ -680,6 +685,8 @@ impl<R: EventStream> EventReader<R> {
}
}
*/
impl ops::Deref for Mastodon {
type Target = Data;
@ -750,11 +757,13 @@ impl MastodonUnauth {
Ok(self.base.join(url)?)
}
fn send(&self, req: RequestBuilder) -> Result<Response> {
async fn send(&self, req: RequestBuilder) -> Result<Response> {
let req = req.build()?;
Ok(self.client.execute(req)?)
Ok(self.client.execute(req).await?)
}
/*
/// Get a stream of the public timeline
pub fn streaming_public(&self) -> Result<EventReader<WebSocket>> {
let mut url: url::Url = self.route("/api/v1/streaming/public/local")?;
@ -773,48 +782,49 @@ impl MastodonUnauth {
Ok(EventReader(WebSocket(client)))
}
*/
/// GET /api/v1/statuses/:id
pub fn get_status(&self, id: &str) -> Result<Status> {
pub async 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))?;
deserialise(response)
let response = self.send(self.client.get(route)).await?;
deserialise_response(response).await
}
/// GET /api/v1/statuses/:id/context
pub fn get_context(&self, id: &str) -> Result<Context> {
pub async 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")?;
let response = self.send(self.client.get(route))?;
deserialise(response)
let response = self.send(self.client.get(route)).await?;
deserialise_response(response).await
}
/// GET /api/v1/statuses/:id/card
pub fn get_card(&self, id: &str) -> Result<Card> {
pub async 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")?;
let response = self.send(self.client.get(route))?;
deserialise(response)
let response = self.send(self.client.get(route)).await?;
deserialise_response(response).await
}
}
// Convert the HTTP response body from JSON. Pass up deserialization errors
// transparently.
fn deserialise<T: for<'de> serde::Deserialize<'de>>(response: Response) -> Result<T> {
let mut reader = Tap::new(response);
match serde_json::from_reader(&mut reader) {
async fn deserialise_response<T: for<'de> serde::Deserialize<'de>>(response: Response) -> Result<T> {
let bytes = response.bytes().await?;
match serde_json::from_slice(&bytes) {
Ok(t) => {
log::debug!("{}", String::from_utf8_lossy(&reader.bytes));
debug!("{}", String::from_utf8_lossy(&bytes));
Ok(t)
}
// If deserializing into the desired type fails try again to
// see if this is an error response.
Err(e) => {
log::error!("{}", String::from_utf8_lossy(&reader.bytes));
if let Ok(error) = serde_json::from_slice(&reader.bytes) {
error!("{}", String::from_utf8_lossy(&bytes));
if let Ok(error) = serde_json::from_slice(&bytes) {
return Err(Error::Api(error));
}
Err(e.into())

@ -1,14 +1,14 @@
macro_rules! methods {
($($method:ident,)+) => {
$(
fn $method<T: for<'de> serde::Deserialize<'de>>(&self, url: String)
async fn $method<T: for<'de> serde::Deserialize<'de>>(&self, url: String)
-> Result<T>
{
let response = self.send(
self.client.$method(&url)
)?;
).await?;
deserialise(response)
deserialise_response(response).await
}
)+
};
@ -22,13 +22,13 @@ macro_rules! route_v1_paged {
"# Errors\n",
"If `access_token` is not set.",
),
pub fn $name(&self) -> Result<Page<$ret>> {
pub async fn $name<'a>(&'a self) -> Result<Page<'a, $ret>> {
let url = self.route(concat!("/api/v1/", $url));
let response = self.send(
self.client.$method(&url)
)?;
).await?;
Page::new(self, response)
Page::new(self, response).await
}
}
};
@ -40,7 +40,7 @@ macro_rules! route_v1_paged {
"# Errors\n",
"If `access_token` is not set.",
),
pub fn $name<'a>(&self, $($param: $typ,)*) -> Result<Page<$ret>> {
pub async fn $name<'a, 'b>(&'b self, $($param: $typ,)*) -> Result<Page<'b, $ret>> {
use serde_urlencoded;
use serde::Serialize;
@ -69,9 +69,9 @@ macro_rules! route_v1_paged {
let response = self.send(
self.client.get(&url)
)?;
).await?;
Page::new(self, response)
Page::new(self, response).await
}
}
};
@ -85,7 +85,7 @@ macro_rules! route_v2 {
"# Errors\n",
"If `access_token` is not set."
),
pub fn $name<'a>(&self, $($param: $typ,)*) -> Result<$ret> {
pub async fn $name<'a>(&self, $($param: $typ,)*) -> Result<$ret> {
use serde_urlencoded;
use serde::Serialize;
@ -109,7 +109,7 @@ macro_rules! route_v2 {
let url = format!(concat!("/api/v2/", $url, "?{}"), &qs);
self.get(self.route(&url))
self.get(self.route(&url)).await
}
}
};
@ -123,7 +123,7 @@ macro_rules! route_v1 {
"# Errors\n",
"If `access_token` is not set."
),
pub fn $name<'a>(&self, $($param: $typ,)*) -> Result<$ret> {
pub async fn $name<'a>(&self, $($param: $typ,)*) -> Result<$ret> {
use serde_urlencoded;
use serde::Serialize;
@ -147,7 +147,7 @@ macro_rules! route_v1 {
let url = format!(concat!("/api/v1/", $url, "?{}"), &qs);
self.get(self.route(&url))
self.get(self.route(&url)).await
}
}
};
@ -159,7 +159,7 @@ macro_rules! route_v1 {
"# Errors\n",
"If `access_token` is not set."
),
pub fn $name(&self, $($param: $typ,)*) -> Result<$ret> {
pub async fn $name(&self, $($param: $typ,)*) -> Result<$ret> {
let form_data = serde_json::json!({
$(
@ -170,7 +170,7 @@ macro_rules! route_v1 {
let response = self.send(
self.client.$method(&self.route(concat!("/api/v1/", $url)))
.json(&form_data)
)?;
).await?;
let status = response.status().clone();
@ -180,7 +180,7 @@ macro_rules! route_v1 {
return Err(Error::Server(status));
}
deserialise(response)
deserialise_response(response).await
}
}
};
@ -192,8 +192,8 @@ macro_rules! route_v1 {
"# Errors\n",
"If `access_token` is not set."
),
pub fn $name(&self) -> Result<$ret> {
self.$method(self.route(concat!("/api/v1/", $url)))
pub async fn $name(&self) -> Result<$ret> {
self.$method(self.route(concat!("/api/v1/", $url))).await
}
}
};
@ -207,8 +207,8 @@ macro_rules! route_v1_id {
"# Errors\n",
"If `access_token` is not set."
),
pub fn $name(&self, id: &str) -> Result<$ret> {
self.$method(self.route(&format!(concat!("/api/v1/", $url), id)))
pub async fn $name(&self, id: &str) -> Result<$ret> {
self.$method(self.route(&format!(concat!("/api/v1/", $url), id))).await
}
}
}
@ -221,13 +221,13 @@ macro_rules! route_v1_paged_id {
"# Errors\n",
"If `access_token` is not set."
),
pub fn $name(&self, id: &str) -> Result<Page<$ret>> {
pub async fn $name<'a>(&'a self, id: &str) -> Result<Page<'a, $ret>> {
let url = self.route(&format!(concat!("/api/v1/", $url), id));
let response = self.send(
self.client.$method(&url)
)?;
).await?;
Page::new(self, response)
Page::new(self, response).await
}
}
};

@ -1,383 +0,0 @@
use std::borrow::Cow;
use crate::{
entities::prelude::*,
errors::Result,
media_builder::MediaBuilder,
page::Page,
requests::{AddFilterRequest, AddPushRequest, StatusesRequest, UpdateCredsRequest, UpdatePushRequest},
status_builder::NewStatus,
};
/// Represents the set of methods that a Mastodon Client can do, so that
/// implementations might be swapped out for testing
#[allow(unused)]
pub trait MastodonClient {
/// Type that wraps streaming API streams
type Stream: Iterator<Item = Event>;
/// GET /api/v1/favourites
fn favourites(&self) -> Result<Page<Status>> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/blocks
fn blocks(&self) -> Result<Page<Account>> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/domain_blocks
fn domain_blocks(&self) -> Result<Page<String>> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/follow_requests
fn follow_requests(&self) -> Result<Page<Account>> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/timelines/home
fn get_home_timeline(&self) -> Result<Page<Status>> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/custom_emojis
fn get_emojis(&self) -> Result<Page<Emoji>> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/mutes
fn mutes(&self) -> Result<Page<Account>> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/notifications
fn notifications(&self) -> Result<Page<Notification>> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/reports
fn reports(&self) -> Result<Page<Report>> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/accounts/:id/followers
fn followers(&self, id: &str) -> Result<Page<Account>> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/accounts/:id/following
fn following(&self, id: &str) -> Result<Page<Account>> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/statuses/:id/reblogged_by
fn reblogged_by(&self, id: &str) -> Result<Page<Account>> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/statuses/:id/favourited_by
fn favourited_by(&self, id: &str) -> Result<Page<Account>> {
unimplemented!("This method was not implemented");
}
/// DELETE /api/v1/domain_blocks
fn unblock_domain(&self, domain: String) -> Result<Empty> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/instance
fn instance(&self) -> Result<Instance> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/accounts/verify_credentials
fn verify_credentials(&self) -> Result<Account> {
unimplemented!("This method was not implemented");
}
/// POST /api/v1/reports
fn report(&self, account_id: &str, status_ids: Vec<&str>, comment: String) -> Result<Report> {
unimplemented!("This method was not implemented");
}
/// POST /api/v1/domain_blocks
fn block_domain(&self, domain: String) -> Result<Empty> {
unimplemented!("This method was not implemented");
}
/// POST /api/v1/accounts/follow_requests/authorize
fn authorize_follow_request(&self, id: &str) -> Result<Empty> {
unimplemented!("This method was not implemented");
}
/// POST /api/v1/accounts/follow_requests/reject
fn reject_follow_request(&self, id: &str) -> Result<Empty> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/search
fn search(&self, q: &'_ str, resolve: bool) -> Result<SearchResult> {
unimplemented!("This method was not implemented");
}
/// GET /api/v2/search
fn search_v2(&self, q: &'_ str, resolve: bool) -> Result<SearchResultV2> {
unimplemented!("This method was not implemented");
}
/// POST /api/v1/follows
fn follows(&self, uri: Cow<'static, str>) -> Result<Account> {
unimplemented!("This method was not implemented");
}
/// POST /api/v1/media
fn media(&self, media_builder: MediaBuilder) -> Result<Attachment> {
unimplemented!("This method was not implemented");
}
/// POST /api/v1/notifications/clear
fn clear_notifications(&self) -> Result<Empty> {
unimplemented!("This method was not implemented");
}
/// POST /api/v1/notifications/dismiss
fn dismiss_notification(&self, id: &str) -> Result<Empty> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/accounts/:id
fn get_account(&self, id: &str) -> Result<Account> {
unimplemented!("This method was not implemented");
}
/// POST /api/v1/accounts/:id/follow
fn follow(&self, id: &str) -> Result<Relationship> {
unimplemented!("This method was not implemented");
}
/// POST /api/v1/accounts/:id/unfollow
fn unfollow(&self, id: &str) -> Result<Relationship> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/accounts/:id/block
fn block(&self, id: &str) -> Result<Relationship> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/accounts/:id/unblock
fn unblock(&self, id: &str) -> Result<Relationship> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/accounts/:id/mute
fn mute(&self, id: &str) -> Result<Relationship> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/accounts/:id/unmute
fn unmute(&self, id: &str) -> Result<Relationship> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/notifications/:id
fn get_notification(&self, id: &str) -> Result<Notification> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/statuses/:id
fn get_status(&self, id: &str) -> Result<Status> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/statuses/:id/context
fn get_context(&self, id: &str) -> Result<Context> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/statuses/:id/card
fn get_card(&self, id: &str) -> Result<Card> {
unimplemented!("This method was not implemented");
}
/// POST /api/v1/statuses/:id/reblog
fn reblog(&self, id: &str) -> Result<Status> {
unimplemented!("This method was not implemented");
}
/// POST /api/v1/statuses/:id/unreblog
fn unreblog(&self, id: &str) -> Result<Status> {
unimplemented!("This method was not implemented");
}
/// POST /api/v1/statuses/:id/favourite
fn favourite(&self, id: &str) -> Result<Status> {
unimplemented!("This method was not implemented");
}
/// POST /api/v1/statuses/:id/unfavourite
fn unfavourite(&self, id: &str) -> Result<Status> {
unimplemented!("This method was not implemented");
}
/// DELETE /api/v1/statuses/:id
fn delete_status(&self, id: &str) -> Result<Empty> {
unimplemented!("This method was not implemented");
}
/// PATCH /api/v1/accounts/update_credentials
fn update_credentials(&self, builder: &mut UpdateCredsRequest) -> Result<Account> {
unimplemented!("This method was not implemented");
}
/// POST /api/v1/statuses
fn new_status(&self, status: NewStatus) -> Result<Status> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/timelines/public?local=true
fn get_local_timeline(&self) -> Result<Page<Status>> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/timelines/public?local=false
fn get_federated_timeline(&self) -> Result<Page<Status>> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/timelines/tag/:hashtag
fn get_hashtag_timeline(&self, hashtag: &str, local: bool) -> Result<Page<Status>> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/accounts/:id/statuses
fn statuses<'a, 'b: 'a, S>(&'b self, id: &'b str, request: S) -> Result<Page<Status>>
where
S: Into<Option<StatusesRequest<'a>>>,
{
unimplemented!("This method was not implemented");
}
/// GET /api/v1/accounts/relationships
fn relationships(&self, ids: &[&str]) -> Result<Page<Relationship>> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/accounts/search?q=:query&limit=:limit&following=:following
fn search_accounts(&self, query: &str, limit: Option<u64>, following: bool) -> Result<Page<Account>> {
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");
}
/// 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: &str) -> Result<Filter> {
unimplemented!("This method was not implemented");
}
/// PUT /api/v1/filters/:id
fn update_filter(&self, id: &str, request: &mut AddFilterRequest) -> Result<Filter> {
unimplemented!("This method was not implemented");
}
/// DELETE /api/v1/filters/:id
fn delete_filter(&self, id: &str) -> Result<Empty> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/suggestions
fn get_follow_suggestions(&self) -> Result<Vec<Account>> {
unimplemented!("This method was not implemented");
}
/// DELETE /api/v1/suggestions/:account_id
fn delete_from_suggestions(&self, id: &str) -> Result<Empty> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/endorsements
fn get_endorsements(&self) -> Result<Page<Account>> {
unimplemented!("This method was not implemented");
}
/// POST /api/v1/accounts/:id/pin
fn endorse_user(&self, id: &str) -> Result<Relationship> {
unimplemented!("This method was not implemented");
}
/// POST /api/v1/accounts/:id/unpin
fn unendorse_user(&self, id: &str) -> Result<Relationship> {
unimplemented!("This method was not implemented");
}
/// Shortcut for: `let me = client.verify_credentials(); client.followers()`
///
/// ```no_run
/// # extern crate elefren;
/// # use std::error::Error;
/// # use elefren::prelude::*;
/// # 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 follows_me = client.follows_me()?;
/// # Ok(())
/// # }
fn follows_me(&self) -> Result<Page<Account>> {
unimplemented!("This method was not implemented");
}
/// Shortcut for
/// `let me = client.verify_credentials(); client.following(&me.id)`
///
/// ```no_run
/// # extern crate elefren;
/// # use std::error::Error;
/// # use elefren::prelude::*;
/// # 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 follows_me = client.followed_by_me()?;
/// # Ok(())
/// # }
fn followed_by_me(&self) -> Result<Page<Account>> {
unimplemented!("This method was not implemented");
}
/// Returns events that are relevant to the authorized user, i.e. home
/// timeline and notifications
fn streaming_user(&self) -> Result<Self::Stream> {
unimplemented!("This method was not implemented");
}
/// Returns all public statuses
fn streaming_public(&self) -> Result<Self::Stream> {
unimplemented!("This method was not implemented");
}
/// Returns all local statuses
fn streaming_local(&self) -> Result<Self::Stream> {
unimplemented!("This method was not implemented");
}
/// Returns all public statuses for a particular hashtag
fn streaming_public_hashtag(&self, hashtag: &str) -> Result<Self::Stream> {
unimplemented!("This method was not implemented");
}
/// Returns all local statuses for a particular hashtag
fn streaming_local_hashtag(&self, hashtag: &str) -> Result<Self::Stream> {
unimplemented!("This method was not implemented");
}
/// Returns statuses for a list
fn streaming_list(&self, list_id: &str) -> Result<Self::Stream> {
unimplemented!("This method was not implemented");
}
/// Returns all direct messages
fn streaming_direct(&self) -> Result<Self::Stream> {
unimplemented!("This method was not implemented");
}
}
/// Trait that represents clients that can make unauthenticated calls to a
/// mastodon instance
#[allow(unused)]
pub trait MastodonUnauthenticated {
/// GET /api/v1/statuses/:id
fn get_status(&self, id: &str) -> Result<Status> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/statuses/:id/context
fn get_context(&self, id: &str) -> Result<Context> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/statuses/:id/card
fn get_card(&self, id: &str) -> Result<Card> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/statuses/:id/reblogged_by
fn reblogged_by(&self, id: &str) -> Result<Page<Account>> {
unimplemented!("This method was not implemented");
}
/// GET /api/v1/statuses/:id/favourited_by
fn favourited_by(&self, id: &str) -> Result<Page<Account>> {
unimplemented!("This method was not implemented");
}
}

@ -1,6 +1,7 @@
use std::fmt;
use std::io::Read;
use std::path::{Path, PathBuf};
use tokio::io::AsyncRead;
use std::pin::Pin;
#[derive(Debug)]
/// A builder pattern struct for preparing a single attachment for upload.
@ -28,7 +29,7 @@ pub struct MediaBuilder {
/// Enum representing possible sources of attachments to upload
pub enum MediaBuilderData {
/// An arbitrary reader. It is useful for reading from media already in memory.
Reader(Box<dyn Read + Send>),
Reader(Pin<Box<dyn AsyncRead + Send + Sync>>),
/// Variant represening a file path of the file to attach.
File(PathBuf),
@ -45,9 +46,9 @@ impl fmt::Debug for MediaBuilderData {
impl MediaBuilder {
/// Create a new MediaBuilder from a reader `data`
pub fn from_reader<R: Read + Send + 'static>(data: R) -> MediaBuilder {
pub fn from_reader<R: AsyncRead + Send + Sync + 'static>(data: R) -> MediaBuilder {
MediaBuilder {
data: MediaBuilderData::Reader(Box::from(data)),
data: MediaBuilderData::Reader(Box::pin(data)),
filename: None,
mimetype: None,
description: None,

@ -1,7 +1,7 @@
use super::{deserialise, Mastodon, Result};
use super::{deserialise_response, Mastodon, Result};
use crate::entities::itemsiter::ItemsIter;
use hyper_old_types::header::{parsing, Link, RelationType};
use reqwest::{blocking::Response, header::LINK};
use reqwest::{Response, header::LINK};
use serde::Deserialize;
use url::Url;
@ -11,7 +11,7 @@ macro_rules! pages {
$(
doc_comment::doc_comment!(concat!(
"Method to retrieve the ", stringify!($direction), " page of results"),
pub fn $fun(&mut self) -> Result<Option<Vec<T>>> {
pub async fn $fun(&mut self) -> Result<Option<Vec<T>>> {
let url = match self.$direction.take() {
Some(s) => s,
None => return Ok(None),
@ -19,13 +19,13 @@ macro_rules! pages {
let response = self.mastodon.send(
self.mastodon.client.get(url)
)?;
).await?;
let (prev, next) = get_links(&response)?;
self.next = next;
self.prev = prev;
deserialise(response)
deserialise_response(response).await
});
)*
}
@ -107,10 +107,10 @@ impl<'a, T: for<'de> Deserialize<'de>> Page<'a, T> {
prev: prev_page
}
pub(crate) fn new(mastodon: &'a Mastodon, response: Response) -> Result<Self> {
pub(crate) async fn new<'m>(mastodon: &'m Mastodon, response: Response) -> Result<Page<'m, T>> {
let (prev, next) = get_links(&response)?;
Ok(Page {
initial_items: deserialise(response)?,
initial_items: deserialise_response(response).await?,
next,
prev,
mastodon,
@ -189,7 +189,7 @@ impl<'a, T: Clone + for<'de> Deserialize<'de>> Page<'a, T> {
/// # Ok(())
/// # }
/// ```
pub fn items_iter(self) -> impl Iterator<Item = T> + 'a
pub fn items_iter(self) -> ItemsIter<'a, T>
where
T: 'a,
{

@ -1,6 +1,6 @@
use std::borrow::Cow;
use reqwest::blocking::{Client, RequestBuilder, Response};
use reqwest::{Client, RequestBuilder, Response};
use serde::Deserialize;
use std::convert::TryInto;
@ -93,9 +93,9 @@ impl<'a> Registration<'a> {
self
}
fn send(&self, req: RequestBuilder) -> Result<Response> {
async fn send(&self, req: RequestBuilder) -> Result<Response> {
let req = req.build()?;
Ok(self.client.execute(req)?)
Ok(self.client.execute(req).await?)
}
/// Register the given application
@ -119,12 +119,12 @@ impl<'a> Registration<'a> {
/// # Ok(())
/// # }
/// ```
pub fn register<I: TryInto<App>>(&mut self, app: I) -> Result<Registered>
pub async fn register<I: TryInto<App>>(&mut self, app: I) -> Result<Registered>
where
Error: From<<I as TryInto<App>>::Error>,
{
let app = app.try_into()?;
let oauth = self.send_app(&app)?;
let oauth = self.send_app(&app).await?;
Ok(Registered {
base: self.base.clone(),
@ -157,9 +157,9 @@ impl<'a> Registration<'a> {
/// # Ok(())
/// # }
/// ```
pub fn build(&mut self) -> Result<Registered> {
pub async fn build(&mut self) -> Result<Registered> {
let app: App = self.app_builder.clone().build()?;
let oauth = self.send_app(&app)?;
let oauth = self.send_app(&app).await?;
Ok(Registered {
base: self.base.clone(),
@ -172,9 +172,10 @@ impl<'a> Registration<'a> {
})
}
fn send_app(&self, app: &App) -> Result<OAuth> {
async fn send_app(&self, app: &App) -> Result<OAuth> {
let url = format!("{}/api/v1/apps", self.base);
Ok(self.send(self.client.post(&url).json(&app))?.json()?)
Ok(self.send(self.client.post(&url).json(&app)).await?
.json().await?)
}
}
@ -228,9 +229,9 @@ impl Registered {
}
impl Registered {
fn send(&self, req: RequestBuilder) -> Result<Response> {
async fn send(&self, req: RequestBuilder) -> Result<Response> {
let req = req.build()?;
Ok(self.client.execute(req)?)
Ok(self.client.execute(req).await?)
}
/// Returns the parts of the `Registered` struct that can be used to
@ -298,14 +299,14 @@ impl Registered {
/// Create an access token from the client id, client secret, and code
/// provided by the authorisation url.
pub fn complete(&self, code: &str) -> Result<Mastodon> {
pub async fn complete(&self, code: &str) -> Result<Mastodon> {
let url = format!(
"{}/oauth/token?client_id={}&client_secret={}&code={}&grant_type=authorization_code&\
redirect_uri={}",
self.base, self.client_id, self.client_secret, code, self.redirect
);
let token: AccessToken = self.send(self.client.post(&url))?.json()?;
let token: AccessToken = self.send(self.client.post(&url)).await?.json().await?;
let data = Data {
base: self.base.clone().into(),

Loading…
Cancel
Save