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,6 +1,9 @@
|
||||
//! A module containing everything relating to a account returned from the api.
|
||||
|
||||
use chrono::prelude::*;
|
||||
use reqwest::multipart::Form;
|
||||
use ::Result;
|
||||
use std::path::Path;
|
||||
|
||||
/// A struct representing an Account.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
@@ -36,4 +39,59 @@ pub struct Account {
|
||||
pub url: String,
|
||||
/// The username of the account.
|
||||
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.
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use super::Empty;
|
||||
|
||||
/// A struct representing a media attachment.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
@@ -18,11 +20,28 @@ pub struct Attachment {
|
||||
/// (only present on local images)
|
||||
pub text_url: Option<String>,
|
||||
/// Meta information about the attachment.
|
||||
#[serde(deserialize_with="empty_as_none")]
|
||||
pub meta: Option<Meta>,
|
||||
/// Noop will be removed.
|
||||
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.
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Meta {
|
||||
|
||||
+15
-1
@@ -10,5 +10,19 @@ pub struct Card {
|
||||
/// The card description.
|
||||
pub description: String,
|
||||
/// 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
|
||||
/// instance administrator.
|
||||
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 context;
|
||||
pub mod instance;
|
||||
pub mod list;
|
||||
pub mod mention;
|
||||
pub mod notification;
|
||||
pub mod relationship;
|
||||
pub mod report;
|
||||
@@ -17,14 +19,16 @@ pub mod prelude {
|
||||
//! 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:
|
||||
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::card::Card;
|
||||
pub use super::context::Context;
|
||||
pub use super::instance::Instance;
|
||||
pub use super::list::List;
|
||||
pub use super::mention::Mention;
|
||||
pub use super::notification::Notification;
|
||||
pub use super::relationship::Relationship;
|
||||
pub use super::report::Report;
|
||||
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.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Relationship {
|
||||
/// Target account id
|
||||
pub id: String,
|
||||
/// Whether the application client follows the account.
|
||||
pub following: bool,
|
||||
/// Whether the account follows the application client.
|
||||
@@ -14,4 +16,8 @@ pub struct Relationship {
|
||||
pub muting: bool,
|
||||
/// Whether the application client has requested to follow the account.
|
||||
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,
|
||||
/// The time the status was created.
|
||||
pub created_at: DateTime<Utc>,
|
||||
/// An array of Emoji
|
||||
pub emojis: Vec<Emoji>,
|
||||
/// The number of reblogs for the status.
|
||||
pub reblogs_count: u64,
|
||||
/// The number of favourites for the status.
|
||||
@@ -51,6 +53,10 @@ pub struct Status {
|
||||
pub tags: Vec<Tag>,
|
||||
/// Name of application used to post status.
|
||||
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.
|
||||
@@ -66,6 +72,17 @@ pub struct Mention {
|
||||
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.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Tag {
|
||||
|
||||
+79
-21
@@ -40,6 +40,7 @@
|
||||
extern crate chrono;
|
||||
extern crate reqwest;
|
||||
extern crate serde;
|
||||
extern crate url;
|
||||
|
||||
/// Registering your App
|
||||
pub mod apps;
|
||||
@@ -49,6 +50,8 @@ pub mod status_builder;
|
||||
pub mod entities;
|
||||
/// Registering your app.
|
||||
pub mod registration;
|
||||
/// Handling multiple pages of entities.
|
||||
pub mod page;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::error::Error as StdError;
|
||||
@@ -60,9 +63,11 @@ use json::Error as SerdeError;
|
||||
use reqwest::Error as HttpError;
|
||||
use reqwest::{Client, Response, StatusCode};
|
||||
use reqwest::header::{Authorization, Bearer, Headers};
|
||||
use url::ParseError as UrlError;
|
||||
|
||||
use entities::prelude::*;
|
||||
pub use status_builder::StatusBuilder;
|
||||
use page::Page;
|
||||
|
||||
pub use registration::Registration;
|
||||
/// Convience type over `std::result::Result` with `Error` as the error type.
|
||||
@@ -78,12 +83,34 @@ macro_rules! methods {
|
||||
.headers(self.headers.clone())
|
||||
.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 {
|
||||
|
||||
((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));
|
||||
}
|
||||
|
||||
json_convert_response(response)
|
||||
deserialise(response)
|
||||
}
|
||||
|
||||
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/
|
||||
#[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())
|
||||
.json(&form_data)
|
||||
.send()?;
|
||||
@@ -148,7 +175,7 @@ macro_rules! route {
|
||||
return Err(Error::Server(status));
|
||||
}
|
||||
|
||||
json_convert_response(response)
|
||||
deserialise(response)
|
||||
}
|
||||
|
||||
route!{$($rest)*}
|
||||
@@ -231,6 +258,9 @@ pub enum Error {
|
||||
/// Wrapper around the `std::io::Error` struct.
|
||||
#[serde(skip_deserializing)]
|
||||
Io(IoError),
|
||||
/// Wrapper around the `url::ParseError` struct.
|
||||
#[serde(skip_deserializing)]
|
||||
Url(UrlError),
|
||||
/// Missing Client Id.
|
||||
#[serde(skip_deserializing)]
|
||||
ClientIdRequired,
|
||||
@@ -263,6 +293,7 @@ impl StdError for Error {
|
||||
Error::Serde(ref e) => e.description(),
|
||||
Error::Http(ref e) => e.description(),
|
||||
Error::Io(ref e) => e.description(),
|
||||
Error::Url(ref e) => e.description(),
|
||||
Error::Client(ref status) | Error::Server(ref status) => {
|
||||
status.canonical_reason().unwrap_or("Unknown Status code")
|
||||
},
|
||||
@@ -323,22 +354,30 @@ impl Mastodon {
|
||||
}
|
||||
}
|
||||
|
||||
paged_routes! {
|
||||
(get) favourites: "favourites" => Status,
|
||||
}
|
||||
|
||||
route! {
|
||||
(get) verify: "accounts/verify_credentials" => Account,
|
||||
(delete (domain: String,)) unblock_domain: "domain_blocks" => Empty,
|
||||
(get) blocks: "blocks" => Vec<Account>,
|
||||
(get) domain_blocks: "domain_blocks" => Vec<String>,
|
||||
(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) notifications: "notifications" => Vec<Notification>,
|
||||
(get) reports: "reports" => Vec<Report>,
|
||||
(get) get_home_timeline: "timelines/home" => Vec<Status>,
|
||||
(post (id: u64,)) allow_follow_request: "accounts/follow_requests/authorize" => Empty,
|
||||
(get) verify_credentials: "accounts/verify_credentials" => Account,
|
||||
(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 (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 (uri: Cow<'static, str>,)) follows: "follows" => Account,
|
||||
(post multipart (file: Cow<'static, str>,)) media: "media" => Attachment,
|
||||
(post) clear_notifications: "notifications/clear" => Empty,
|
||||
}
|
||||
|
||||
route_id! {
|
||||
@@ -364,6 +403,27 @@ impl Mastodon {
|
||||
(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.
|
||||
pub fn new_status(&self, status: StatusBuilder) -> Result<Status> {
|
||||
|
||||
@@ -372,7 +432,7 @@ impl Mastodon {
|
||||
.json(&status)
|
||||
.send()?;
|
||||
|
||||
json_convert_response(response)
|
||||
deserialise(response)
|
||||
}
|
||||
|
||||
/// Get the federated timeline for the instance.
|
||||
@@ -452,11 +512,6 @@ impl Mastodon {
|
||||
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,];
|
||||
|
||||
fn route(&self, url: &str) -> String {
|
||||
@@ -488,14 +543,17 @@ macro_rules! from {
|
||||
}
|
||||
|
||||
from! {
|
||||
SerdeError, Serde,
|
||||
HttpError, Http,
|
||||
IoError, Io,
|
||||
SerdeError, Serde,
|
||||
UrlError, Url,
|
||||
}
|
||||
|
||||
// Convert the HTTP response body from JSON. Pass up deserialization errors
|
||||
// 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;
|
||||
|
||||
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