forked from MightyPork/elefren-fork
Version 0.10.0-rc1
- Added the ability to handle paged entities like favourites and such. (Only favourites in prerelease) - Added optional `source` and `moved` fields to `Account`. - Added `Source` struct to match with the `Account.source` field. - Added `CredientialsBuilder` struct for updating profile using `verify_credientials`. - Attachment now handles being sent an empty object, which is converted to `None`. - Added ombed data fields to `Card`. - Added `version` and `urls` fields to `Instance`. - Added `id`, `muting_notifications`, and `domain_blocking` to `Relationship`. - Added `emojis`, `language`, and `pinned` fields to `Status` - Added `Emoji` struct. - Added `List` and `Mention` structs(matching routes not added yet). - Added example that prints your profile.
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
target
|
target
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
.env
|
.env
|
||||||
|
mastodon-data.toml
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# 0.10 (prerelease)
|
||||||
|
|
||||||
|
- Added the ability to handle paged entities like favourites and such.(Only favourites in prerelease)
|
||||||
|
- Added optional `source` and `moved` fields to `Account`.
|
||||||
|
- Added `Source` struct to match with the `Account.source` field.
|
||||||
|
- Added `CredientialsBuilder` struct for updating profile using
|
||||||
|
`verify_credientials`.
|
||||||
|
- Attachment now handles being sent an empty object, which is converted
|
||||||
|
to `None`.
|
||||||
|
- Added ombed data fields to `Card`.
|
||||||
|
- Added `version` and `urls` fields to `Instance`.
|
||||||
|
- Added `id`, `muting_notifications`, and `domain_blocking` to `Relationship`.
|
||||||
|
- Added `emojis`, `language`, and `pinned` fields to `Status`
|
||||||
|
- Added `Emoji` struct.
|
||||||
|
- Added `List` and `Mention` structs(matching routes not added yet).
|
||||||
|
- Added example that prints your profile.
|
||||||
+3
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mammut"
|
name = "mammut"
|
||||||
version = "0.9.1"
|
version = "0.10.0-rc1"
|
||||||
|
|
||||||
description = "A wrapper around the Mastodon API."
|
description = "A wrapper around the Mastodon API."
|
||||||
authors = ["Aaron Power <theaaronepower@gmail.com>"]
|
authors = ["Aaron Power <theaaronepower@gmail.com>"]
|
||||||
@@ -15,6 +15,7 @@ reqwest = "0.8"
|
|||||||
serde = "1"
|
serde = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
serde_derive = "1"
|
serde_derive = "1"
|
||||||
|
url = "1"
|
||||||
|
|
||||||
[dependencies.chrono]
|
[dependencies.chrono]
|
||||||
version = "0.4"
|
version = "0.4"
|
||||||
@@ -22,3 +23,4 @@ features = ["serde"]
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
dotenv = "0.10"
|
dotenv = "0.10"
|
||||||
|
toml = "0.4"
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
extern crate mammut;
|
||||||
|
extern crate toml;
|
||||||
|
use mammut::{Data, Mastodon, Registration};
|
||||||
|
use mammut::apps::{AppBuilder, Scopes};
|
||||||
|
use std::io;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mastodon = match File::open("mastodon-data.toml") {
|
||||||
|
Ok(mut file) => {
|
||||||
|
let mut config = String::new();
|
||||||
|
file.read_to_string(&mut config).unwrap();
|
||||||
|
let data: Data = toml::from_str(&config).unwrap();
|
||||||
|
Mastodon::from_data(data)
|
||||||
|
},
|
||||||
|
Err(_) => register(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let you = mastodon.verify_credentials().unwrap();
|
||||||
|
|
||||||
|
println!("{:#?}", you);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register() -> Mastodon {
|
||||||
|
let app = AppBuilder {
|
||||||
|
client_name: "mammut-examples",
|
||||||
|
redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
|
||||||
|
scopes: Scopes::Read,
|
||||||
|
website: Some("https://github.com/Aaronepower/mammut"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut registration = Registration::new("https://mastodon.social");
|
||||||
|
registration.register(app).unwrap();;
|
||||||
|
let url = registration.authorise().unwrap();
|
||||||
|
|
||||||
|
println!("Click this link to authorize on Mastodon: {}", url);
|
||||||
|
println!("Paste the returned authorization code: ");
|
||||||
|
|
||||||
|
let mut input = String::new();
|
||||||
|
io::stdin().read_line(&mut input).unwrap();
|
||||||
|
|
||||||
|
let code = input.trim();
|
||||||
|
let mastodon = registration.create_access_token(code.to_string()).unwrap();
|
||||||
|
|
||||||
|
// Save app data for using on the next run.
|
||||||
|
let toml = toml::to_string(&*mastodon).unwrap();
|
||||||
|
let mut file = File::create("mastodon-data.toml").unwrap();
|
||||||
|
file.write_all(toml.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
mastodon
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
//! A module containing everything relating to a account returned from the api.
|
//! A module containing everything relating to a account returned from the api.
|
||||||
|
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
|
use reqwest::multipart::Form;
|
||||||
|
use ::Result;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
/// A struct representing an Account.
|
/// A struct representing an Account.
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
@@ -36,4 +39,59 @@ pub struct Account {
|
|||||||
pub url: String,
|
pub url: String,
|
||||||
/// The username of the account.
|
/// The username of the account.
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
/// An extra attribute given from `verify_credentials` giving defaults about
|
||||||
|
/// a user
|
||||||
|
pub source: Option<Source>,
|
||||||
|
/// If the owner decided to switch accounts, new account is in
|
||||||
|
/// this attribute
|
||||||
|
pub moved: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An extra object given from `verify_credentials` giving defaults about a user
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct Source {
|
||||||
|
privacy: ::status_builder::Visibility,
|
||||||
|
sensitive: bool,
|
||||||
|
note: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CredientialsBuilder<'a> {
|
||||||
|
display_name: Option<&'a str>,
|
||||||
|
note: Option<&'a str>,
|
||||||
|
avatar: Option<&'a Path>,
|
||||||
|
header: Option<&'a Path>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CredientialsBuilder<'a> {
|
||||||
|
pub fn into_form(self) -> Result<Form> {
|
||||||
|
let mut form = Form::new();
|
||||||
|
macro_rules! add_to_form {
|
||||||
|
($key:ident : Text; $($rest:tt)*) => {{
|
||||||
|
if let Some(val) = self.$key {
|
||||||
|
form = form.text(stringify!($key), val.to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
add_to_form!{$($rest)*}
|
||||||
|
}};
|
||||||
|
|
||||||
|
($key:ident : File; $($rest:tt)*) => {{
|
||||||
|
if let Some(val) = self.$key {
|
||||||
|
form = form.file(stringify!($key), val)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_to_form!{$($rest)*}
|
||||||
|
}};
|
||||||
|
|
||||||
|
() => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
add_to_form! {
|
||||||
|
display_name: Text;
|
||||||
|
note: Text;
|
||||||
|
avatar: File;
|
||||||
|
header: File;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(form)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
//! Module containing everything related to media attachements.
|
//! Module containing everything related to media attachements.
|
||||||
|
use serde::{Deserialize, Deserializer};
|
||||||
|
use super::Empty;
|
||||||
|
|
||||||
/// A struct representing a media attachment.
|
/// A struct representing a media attachment.
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
@@ -18,11 +20,28 @@ pub struct Attachment {
|
|||||||
/// (only present on local images)
|
/// (only present on local images)
|
||||||
pub text_url: Option<String>,
|
pub text_url: Option<String>,
|
||||||
/// Meta information about the attachment.
|
/// Meta information about the attachment.
|
||||||
|
#[serde(deserialize_with="empty_as_none")]
|
||||||
pub meta: Option<Meta>,
|
pub meta: Option<Meta>,
|
||||||
/// Noop will be removed.
|
/// Noop will be removed.
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn empty_as_none<'de, D: Deserializer<'de>>(val: D)
|
||||||
|
-> Result<Option<Meta>, D::Error>
|
||||||
|
{
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum EmptyOrMeta {
|
||||||
|
Empty(Empty),
|
||||||
|
Meta(Meta),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(match EmptyOrMeta::deserialize(val)? {
|
||||||
|
EmptyOrMeta::Empty(_) => None,
|
||||||
|
EmptyOrMeta::Meta(m) => Some(m),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Information about the attachment itself.
|
/// Information about the attachment itself.
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct Meta {
|
pub struct Meta {
|
||||||
|
|||||||
+15
-1
@@ -10,5 +10,19 @@ pub struct Card {
|
|||||||
/// The card description.
|
/// The card description.
|
||||||
pub description: String,
|
pub description: String,
|
||||||
/// The image associated with the card, if any.
|
/// The image associated with the card, if any.
|
||||||
pub image: String,
|
pub image: Option<String>,
|
||||||
|
/// OEmbed data
|
||||||
|
author_name: Option<String>,
|
||||||
|
/// OEmbed data
|
||||||
|
author_url: Option<String>,
|
||||||
|
/// OEmbed data
|
||||||
|
provider_name: Option<String>,
|
||||||
|
/// OEmbed data
|
||||||
|
provider_url: Option<String>,
|
||||||
|
/// OEmbed data
|
||||||
|
html: Option<String>,
|
||||||
|
/// OEmbed data
|
||||||
|
width: Option<String>,
|
||||||
|
/// OEmbed data
|
||||||
|
height: Option<String>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,4 +12,8 @@ pub struct Instance {
|
|||||||
/// An email address which can be used to contact the
|
/// An email address which can be used to contact the
|
||||||
/// instance administrator.
|
/// instance administrator.
|
||||||
pub email: String,
|
pub email: String,
|
||||||
|
/// The Mastodon version used by instance.
|
||||||
|
pub version: String,
|
||||||
|
/// `streaming_api`
|
||||||
|
pub urls: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct List {
|
||||||
|
id: String,
|
||||||
|
title: String,
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
pub struct Mention {
|
||||||
|
/// URL of user's profile (can be remote)
|
||||||
|
pub url: String,
|
||||||
|
/// The username of the account
|
||||||
|
pub username: String,
|
||||||
|
/// Equals username for local users, includes `@domain` for remote ones
|
||||||
|
pub acct: String,
|
||||||
|
/// Account ID
|
||||||
|
pub id: String,
|
||||||
|
}
|
||||||
+6
-2
@@ -3,6 +3,8 @@ pub mod attachment;
|
|||||||
pub mod card;
|
pub mod card;
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod instance;
|
pub mod instance;
|
||||||
|
pub mod list;
|
||||||
|
pub mod mention;
|
||||||
pub mod notification;
|
pub mod notification;
|
||||||
pub mod relationship;
|
pub mod relationship;
|
||||||
pub mod report;
|
pub mod report;
|
||||||
@@ -17,14 +19,16 @@ pub mod prelude {
|
|||||||
//! The purpose of this module is to alleviate imports of many common structs
|
//! The purpose of this module is to alleviate imports of many common structs
|
||||||
//! by adding a glob import to the top of mastodon heavy modules:
|
//! by adding a glob import to the top of mastodon heavy modules:
|
||||||
pub use super::Empty;
|
pub use super::Empty;
|
||||||
pub use super::account::Account;
|
pub use super::account::{Account, CredientialsBuilder, Source};
|
||||||
pub use super::attachment::{Attachment, MediaType};
|
pub use super::attachment::{Attachment, MediaType};
|
||||||
pub use super::card::Card;
|
pub use super::card::Card;
|
||||||
pub use super::context::Context;
|
pub use super::context::Context;
|
||||||
pub use super::instance::Instance;
|
pub use super::instance::Instance;
|
||||||
|
pub use super::list::List;
|
||||||
|
pub use super::mention::Mention;
|
||||||
pub use super::notification::Notification;
|
pub use super::notification::Notification;
|
||||||
pub use super::relationship::Relationship;
|
pub use super::relationship::Relationship;
|
||||||
pub use super::report::Report;
|
pub use super::report::Report;
|
||||||
pub use super::search_result::SearchResult;
|
pub use super::search_result::SearchResult;
|
||||||
pub use super::status::{Status, Application};
|
pub use super::status::{Application, Emoji, Status};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
/// A struct containing information about a relationship with another account.
|
/// A struct containing information about a relationship with another account.
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct Relationship {
|
pub struct Relationship {
|
||||||
|
/// Target account id
|
||||||
|
pub id: String,
|
||||||
/// Whether the application client follows the account.
|
/// Whether the application client follows the account.
|
||||||
pub following: bool,
|
pub following: bool,
|
||||||
/// Whether the account follows the application client.
|
/// Whether the account follows the application client.
|
||||||
@@ -14,4 +16,8 @@ pub struct Relationship {
|
|||||||
pub muting: bool,
|
pub muting: bool,
|
||||||
/// Whether the application client has requested to follow the account.
|
/// Whether the application client has requested to follow the account.
|
||||||
pub requested: bool,
|
pub requested: bool,
|
||||||
|
/// Whether the user is also muting notifications
|
||||||
|
pub muting_notifications: bool,
|
||||||
|
/// Whether the user is currently blocking the accounts's domain
|
||||||
|
pub domain_blocking: bool,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ pub struct Status {
|
|||||||
pub content: String,
|
pub content: String,
|
||||||
/// The time the status was created.
|
/// The time the status was created.
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
|
/// An array of Emoji
|
||||||
|
pub emojis: Vec<Emoji>,
|
||||||
/// The number of reblogs for the status.
|
/// The number of reblogs for the status.
|
||||||
pub reblogs_count: u64,
|
pub reblogs_count: u64,
|
||||||
/// The number of favourites for the status.
|
/// The number of favourites for the status.
|
||||||
@@ -51,6 +53,10 @@ pub struct Status {
|
|||||||
pub tags: Vec<Tag>,
|
pub tags: Vec<Tag>,
|
||||||
/// Name of application used to post status.
|
/// Name of application used to post status.
|
||||||
pub application: Option<Application>,
|
pub application: Option<Application>,
|
||||||
|
/// The detected language for the status, if detected.
|
||||||
|
pub language: Option<String>,
|
||||||
|
/// Whether this is the pinned status for the account that posted it.
|
||||||
|
pub pinned: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A mention of another user.
|
/// A mention of another user.
|
||||||
@@ -66,6 +72,17 @@ pub struct Mention {
|
|||||||
pub id: String,
|
pub id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Struct representing an emoji within text.
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct Emoji {
|
||||||
|
/// The shortcode of the emoji
|
||||||
|
pub shortcode: String,
|
||||||
|
/// URL to the emoji static image
|
||||||
|
pub static_url: String,
|
||||||
|
/// URL to the emoji image
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// Hashtags in the status.
|
/// Hashtags in the status.
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct Tag {
|
pub struct Tag {
|
||||||
|
|||||||
+79
-21
@@ -40,6 +40,7 @@
|
|||||||
extern crate chrono;
|
extern crate chrono;
|
||||||
extern crate reqwest;
|
extern crate reqwest;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
|
extern crate url;
|
||||||
|
|
||||||
/// Registering your App
|
/// Registering your App
|
||||||
pub mod apps;
|
pub mod apps;
|
||||||
@@ -49,6 +50,8 @@ pub mod status_builder;
|
|||||||
pub mod entities;
|
pub mod entities;
|
||||||
/// Registering your app.
|
/// Registering your app.
|
||||||
pub mod registration;
|
pub mod registration;
|
||||||
|
/// Handling multiple pages of entities.
|
||||||
|
pub mod page;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
@@ -60,9 +63,11 @@ use json::Error as SerdeError;
|
|||||||
use reqwest::Error as HttpError;
|
use reqwest::Error as HttpError;
|
||||||
use reqwest::{Client, Response, StatusCode};
|
use reqwest::{Client, Response, StatusCode};
|
||||||
use reqwest::header::{Authorization, Bearer, Headers};
|
use reqwest::header::{Authorization, Bearer, Headers};
|
||||||
|
use url::ParseError as UrlError;
|
||||||
|
|
||||||
use entities::prelude::*;
|
use entities::prelude::*;
|
||||||
pub use status_builder::StatusBuilder;
|
pub use status_builder::StatusBuilder;
|
||||||
|
use page::Page;
|
||||||
|
|
||||||
pub use registration::Registration;
|
pub use registration::Registration;
|
||||||
/// Convience type over `std::result::Result` with `Error` as the error type.
|
/// Convience type over `std::result::Result` with `Error` as the error type.
|
||||||
@@ -78,12 +83,34 @@ macro_rules! methods {
|
|||||||
.headers(self.headers.clone())
|
.headers(self.headers.clone())
|
||||||
.send()?;
|
.send()?;
|
||||||
|
|
||||||
json_convert_response(response)
|
deserialise(response)
|
||||||
}
|
}
|
||||||
)+
|
)+
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! paged_routes {
|
||||||
|
|
||||||
|
(($method:ident) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => {
|
||||||
|
/// Equivalent to `/api/v1/
|
||||||
|
#[doc = $url]
|
||||||
|
/// `
|
||||||
|
///
|
||||||
|
#[doc = "# Errors"]
|
||||||
|
/// If `access_token` is not set.
|
||||||
|
pub fn $name(&self) -> Result<Page<$ret>> {
|
||||||
|
let url = self.route(concat!("/api/v1/", $url));
|
||||||
|
let response = self.client.$method(&url)
|
||||||
|
.headers(self.headers.clone())
|
||||||
|
.send()?;
|
||||||
|
|
||||||
|
Page::new(self, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
route!{$($rest)*}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! route {
|
macro_rules! route {
|
||||||
|
|
||||||
((post multipart ($($param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => {
|
((post multipart ($($param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => {
|
||||||
@@ -114,13 +141,13 @@ macro_rules! route {
|
|||||||
return Err(Error::Server(status));
|
return Err(Error::Server(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
json_convert_response(response)
|
deserialise(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
route!{$($rest)*}
|
route!{$($rest)*}
|
||||||
};
|
};
|
||||||
|
|
||||||
((post ($($param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => {
|
(($method:ident ($($param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => {
|
||||||
/// Equivalent to `/api/v1/
|
/// Equivalent to `/api/v1/
|
||||||
#[doc = $url]
|
#[doc = $url]
|
||||||
/// `
|
/// `
|
||||||
@@ -135,7 +162,7 @@ macro_rules! route {
|
|||||||
)*
|
)*
|
||||||
});
|
});
|
||||||
|
|
||||||
let response = self.client.post(&self.route(concat!("/api/v1/", $url)))
|
let response = self.client.$method(&self.route(concat!("/api/v1/", $url)))
|
||||||
.headers(self.headers.clone())
|
.headers(self.headers.clone())
|
||||||
.json(&form_data)
|
.json(&form_data)
|
||||||
.send()?;
|
.send()?;
|
||||||
@@ -148,7 +175,7 @@ macro_rules! route {
|
|||||||
return Err(Error::Server(status));
|
return Err(Error::Server(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
json_convert_response(response)
|
deserialise(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
route!{$($rest)*}
|
route!{$($rest)*}
|
||||||
@@ -231,6 +258,9 @@ pub enum Error {
|
|||||||
/// Wrapper around the `std::io::Error` struct.
|
/// Wrapper around the `std::io::Error` struct.
|
||||||
#[serde(skip_deserializing)]
|
#[serde(skip_deserializing)]
|
||||||
Io(IoError),
|
Io(IoError),
|
||||||
|
/// Wrapper around the `url::ParseError` struct.
|
||||||
|
#[serde(skip_deserializing)]
|
||||||
|
Url(UrlError),
|
||||||
/// Missing Client Id.
|
/// Missing Client Id.
|
||||||
#[serde(skip_deserializing)]
|
#[serde(skip_deserializing)]
|
||||||
ClientIdRequired,
|
ClientIdRequired,
|
||||||
@@ -263,6 +293,7 @@ impl StdError for Error {
|
|||||||
Error::Serde(ref e) => e.description(),
|
Error::Serde(ref e) => e.description(),
|
||||||
Error::Http(ref e) => e.description(),
|
Error::Http(ref e) => e.description(),
|
||||||
Error::Io(ref e) => e.description(),
|
Error::Io(ref e) => e.description(),
|
||||||
|
Error::Url(ref e) => e.description(),
|
||||||
Error::Client(ref status) | Error::Server(ref status) => {
|
Error::Client(ref status) | Error::Server(ref status) => {
|
||||||
status.canonical_reason().unwrap_or("Unknown Status code")
|
status.canonical_reason().unwrap_or("Unknown Status code")
|
||||||
},
|
},
|
||||||
@@ -323,22 +354,30 @@ impl Mastodon {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
paged_routes! {
|
||||||
|
(get) favourites: "favourites" => Status,
|
||||||
|
}
|
||||||
|
|
||||||
route! {
|
route! {
|
||||||
(get) verify: "accounts/verify_credentials" => Account,
|
(delete (domain: String,)) unblock_domain: "domain_blocks" => Empty,
|
||||||
(get) blocks: "blocks" => Vec<Account>,
|
(get) blocks: "blocks" => Vec<Account>,
|
||||||
|
(get) domain_blocks: "domain_blocks" => Vec<String>,
|
||||||
(get) follow_requests: "follow_requests" => Vec<Account>,
|
(get) follow_requests: "follow_requests" => Vec<Account>,
|
||||||
|
(get) get_home_timeline: "timelines/home" => Vec<Status>,
|
||||||
|
(get) instance: "instance" => Instance,
|
||||||
|
(get) get_emojis: "custom_emojis" => Vec<Emoji>,
|
||||||
(get) mutes: "mutes" => Vec<Account>,
|
(get) mutes: "mutes" => Vec<Account>,
|
||||||
(get) notifications: "notifications" => Vec<Notification>,
|
(get) notifications: "notifications" => Vec<Notification>,
|
||||||
(get) reports: "reports" => Vec<Report>,
|
(get) reports: "reports" => Vec<Report>,
|
||||||
(get) get_home_timeline: "timelines/home" => Vec<Status>,
|
(get) verify_credentials: "accounts/verify_credentials" => Account,
|
||||||
(post (id: u64,)) allow_follow_request: "accounts/follow_requests/authorize" => Empty,
|
(post (account_id: u64, status_ids: Vec<u64>, comment: String,)) report: "reports" => Report,
|
||||||
|
(post (domain: String,)) block_domain: "domain_blocks" => Empty,
|
||||||
|
(post (id: u64,)) authorize_follow_request: "accounts/follow_requests/authorize" => Empty,
|
||||||
(post (id: u64,)) reject_follow_request: "accounts/follow_requests/reject" => Empty,
|
(post (id: u64,)) reject_follow_request: "accounts/follow_requests/reject" => Empty,
|
||||||
(post (uri: Cow<'static, str>,)) follows: "follows" => Account,
|
|
||||||
(post) clear_notifications: "notifications/clear" => Empty,
|
|
||||||
(post multipart (file: Cow<'static, str>,)) media: "media" => Attachment,
|
|
||||||
(post (account_id: u64, status_ids: Vec<u64>, comment: String,)) report:
|
|
||||||
"reports" => Report,
|
|
||||||
(post (q: String, resolve: bool,)) search: "search" => SearchResult,
|
(post (q: String, resolve: bool,)) search: "search" => SearchResult,
|
||||||
|
(post (uri: Cow<'static, str>,)) follows: "follows" => Account,
|
||||||
|
(post multipart (file: Cow<'static, str>,)) media: "media" => Attachment,
|
||||||
|
(post) clear_notifications: "notifications/clear" => Empty,
|
||||||
}
|
}
|
||||||
|
|
||||||
route_id! {
|
route_id! {
|
||||||
@@ -364,6 +403,27 @@ impl Mastodon {
|
|||||||
(delete) delete_status: "statuses/{}" => Empty,
|
(delete) delete_status: "statuses/{}" => Empty,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_credentials(&self, changes: CredientialsBuilder)
|
||||||
|
-> Result<Account>
|
||||||
|
{
|
||||||
|
|
||||||
|
let url = self.route("/api/v1/accounts/update_credentials");
|
||||||
|
let response = self.client.patch(&url)
|
||||||
|
.headers(self.headers.clone())
|
||||||
|
.multipart(changes.into_form()?)
|
||||||
|
.send()?;
|
||||||
|
|
||||||
|
let status = response.status().clone();
|
||||||
|
|
||||||
|
if status.is_client_error() {
|
||||||
|
return Err(Error::Client(status));
|
||||||
|
} else if status.is_server_error() {
|
||||||
|
return Err(Error::Server(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
deserialise(response)
|
||||||
|
}
|
||||||
|
|
||||||
/// Post a new status to the account.
|
/// Post a new status to the account.
|
||||||
pub fn new_status(&self, status: StatusBuilder) -> Result<Status> {
|
pub fn new_status(&self, status: StatusBuilder) -> Result<Status> {
|
||||||
|
|
||||||
@@ -372,7 +432,7 @@ impl Mastodon {
|
|||||||
.json(&status)
|
.json(&status)
|
||||||
.send()?;
|
.send()?;
|
||||||
|
|
||||||
json_convert_response(response)
|
deserialise(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the federated timeline for the instance.
|
/// Get the federated timeline for the instance.
|
||||||
@@ -452,11 +512,6 @@ impl Mastodon {
|
|||||||
self.get(format!("{}/api/v1/accounts/search?q={}", self.base, query))
|
self.get(format!("{}/api/v1/accounts/search?q={}", self.base, query))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the current Instance.
|
|
||||||
pub fn instance(&self) -> Result<Instance> {
|
|
||||||
self.get(self.route("/api/v1/instance"))
|
|
||||||
}
|
|
||||||
|
|
||||||
methods![get, post, delete,];
|
methods![get, post, delete,];
|
||||||
|
|
||||||
fn route(&self, url: &str) -> String {
|
fn route(&self, url: &str) -> String {
|
||||||
@@ -488,14 +543,17 @@ macro_rules! from {
|
|||||||
}
|
}
|
||||||
|
|
||||||
from! {
|
from! {
|
||||||
SerdeError, Serde,
|
|
||||||
HttpError, Http,
|
HttpError, Http,
|
||||||
IoError, Io,
|
IoError, Io,
|
||||||
|
SerdeError, Serde,
|
||||||
|
UrlError, Url,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the HTTP response body from JSON. Pass up deserialization errors
|
// Convert the HTTP response body from JSON. Pass up deserialization errors
|
||||||
// transparently.
|
// transparently.
|
||||||
fn json_convert_response<T: for<'de> serde::Deserialize<'de>>(mut response: Response) -> Result<T> {
|
fn deserialise<T: for<'de> serde::Deserialize<'de>>(mut response: Response)
|
||||||
|
-> Result<T>
|
||||||
|
{
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
let mut vec = Vec::new();
|
let mut vec = Vec::new();
|
||||||
|
|||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
use super::{Mastodon, Result, deserialise};
|
||||||
|
use reqwest::Response;
|
||||||
|
use reqwest::header::{Link, RelationType};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
pub struct Page<'a, T: for<'de> Deserialize<'de>> {
|
||||||
|
mastodon: &'a Mastodon,
|
||||||
|
next: Option<Url>,
|
||||||
|
prev: Option<Url>,
|
||||||
|
/// Initial set of items
|
||||||
|
pub initial_items: Vec<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! pages {
|
||||||
|
($($direction:ident: $fun:ident),*) => {
|
||||||
|
|
||||||
|
$(
|
||||||
|
pub fn $fun(&mut self) -> Result<Option<Vec<T>>> {
|
||||||
|
let url = match self.$direction.take() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = self.mastodon.client.get(url)
|
||||||
|
.headers(self.mastodon.headers.clone())
|
||||||
|
.send()?;
|
||||||
|
|
||||||
|
let (prev, next) = get_links(&response)?;
|
||||||
|
self.next = next;
|
||||||
|
self.prev = prev;
|
||||||
|
|
||||||
|
deserialise(response)
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: for<'de> Deserialize<'de>> Page<'a, T> {
|
||||||
|
pub fn new(mastodon: &'a Mastodon, response: Response) -> Result<Self> {
|
||||||
|
let (prev, next) = get_links(&response)?;
|
||||||
|
Ok(Page {
|
||||||
|
initial_items: deserialise(response)?,
|
||||||
|
next,
|
||||||
|
prev,
|
||||||
|
mastodon
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pages! {
|
||||||
|
next: next_page,
|
||||||
|
prev: prev_page
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn get_links(response: &Response) -> Result<(Option<Url>, Option<Url>)> {
|
||||||
|
let mut prev = None;
|
||||||
|
let mut next = None;
|
||||||
|
|
||||||
|
if let Some(link_header) = response.headers().get::<Link>() {
|
||||||
|
for value in link_header.values() {
|
||||||
|
if let Some(relations) = value.rel() {
|
||||||
|
if relations.contains(&RelationType::Next) {
|
||||||
|
next = Some(Url::parse(value.link())?);
|
||||||
|
}
|
||||||
|
|
||||||
|
if relations.contains(&RelationType::Prev) {
|
||||||
|
prev = Some(Url::parse(value.link())?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((prev, next))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user