commit
						36ae371beb
					
				| @ -0,0 +1,2 @@ | |||||||
|  | target | ||||||
|  | Cargo.lock | ||||||
| @ -0,0 +1,20 @@ | |||||||
|  | [package] | ||||||
|  | name = "mammut" | ||||||
|  | version = "0.1.0" | ||||||
|  | description = "A wrapper around the Mastodon API." | ||||||
|  | authors = ["Aaron Power <theaaronepower@gmail.com>"] | ||||||
|  | license = "MIT/Apache-2.0" | ||||||
|  | readme = "README.md" | ||||||
|  | repository = "https://github.com/Aaronepower/mastodon.rs.git" | ||||||
|  | keywords = ["api", "web", "social", "mastodon", "wrapper"] | ||||||
|  | categories = ["web-programming", "http-client"] | ||||||
|  | 
 | ||||||
|  | [dependencies] | ||||||
|  | reqwest = "0.5" | ||||||
|  | serde = "0.9" | ||||||
|  | serde_json = "0.9" | ||||||
|  | serde_derive = "0.9" | ||||||
|  | 
 | ||||||
|  | [dependencies.chrono] | ||||||
|  | version = "0.3" | ||||||
|  | features = ["serde"] | ||||||
| @ -0,0 +1,13 @@ | |||||||
|  | Copyright 2016 Aaron Power | ||||||
|  | 
 | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | 
 | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
| @ -0,0 +1,21 @@ | |||||||
|  | MIT License (MIT) | ||||||
|  | 
 | ||||||
|  | Copyright (c) 2016 Aaron Power | ||||||
|  | 
 | ||||||
|  | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  | of this software and associated documentation files (the "Software"), to deal | ||||||
|  | in the Software without restriction, including without limitation the rights | ||||||
|  | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  | copies of the Software, and to permit persons to whom the Software is | ||||||
|  | furnished to do so, subject to the following conditions: | ||||||
|  | 
 | ||||||
|  | The above copyright notice and this permission notice shall be included in | ||||||
|  | all copies or substantial portions of the Software. | ||||||
|  | 
 | ||||||
|  | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||||
|  | THE SOFTWARE. | ||||||
| @ -0,0 +1,5 @@ | |||||||
|  | # Mammut. A API Wrapper for the Mastodon API. | ||||||
|  | 
 | ||||||
|  | ### [Documentation](https://docs.rs/mastodon) | ||||||
|  | 
 | ||||||
|  | A wrapper around the [API](https://github.com/tootsuite/mastodon/blob/master/docs/Using-the-API/API.md#tag) for [Mastodon](https://mastodon.social/) | ||||||
| @ -0,0 +1,32 @@ | |||||||
|  | #[derive(Debug, Default, Serialize)] | ||||||
|  | pub struct AppBuilder<'a> { | ||||||
|  |     pub client_name: &'a str, | ||||||
|  |     pub redirect_uris: &'a str, | ||||||
|  |     pub scopes: Scope, | ||||||
|  |     #[serde(skip_serializing_if="Option::is_none")] | ||||||
|  |     pub website: Option<&'a str>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Clone, Copy, Serialize)] | ||||||
|  | pub enum Scope { | ||||||
|  |     #[serde(rename = "read write follow")] | ||||||
|  |     All, | ||||||
|  |     #[serde(rename = "follow")] | ||||||
|  |     Follow, | ||||||
|  |     #[serde(rename = "read")] | ||||||
|  |     Read, | ||||||
|  |     #[serde(rename = "read follow")] | ||||||
|  |     ReadFollow, | ||||||
|  |     #[serde(rename = "read write")] | ||||||
|  |     ReadWrite, | ||||||
|  |     #[serde(rename = "write")] | ||||||
|  |     Write, | ||||||
|  |     #[serde(rename = "write follow")] | ||||||
|  |     WriteFollow, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Default for Scope { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Scope::Read | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,17 @@ | |||||||
|  | use chrono::prelude::*; | ||||||
|  | #[derive(Deserialize)] | ||||||
|  | pub struct Account { | ||||||
|  |     pub id: u64, | ||||||
|  |     pub username: String, | ||||||
|  |     pub acct: String, | ||||||
|  |     pub display_name: String, | ||||||
|  |     pub note: String, | ||||||
|  |     pub url: String, | ||||||
|  |     pub avatar: String, | ||||||
|  |     pub header: String, | ||||||
|  |     pub locked: bool, | ||||||
|  |     pub created_at: DateTime<UTC>, | ||||||
|  |     pub followers_count: u64, | ||||||
|  |     pub following_count: u64, | ||||||
|  |     pub statuses_count: u64, | ||||||
|  | } | ||||||
| @ -0,0 +1,20 @@ | |||||||
|  | #[derive(Debug, Clone, Deserialize)] | ||||||
|  | pub struct Attachment { | ||||||
|  |     pub id: u64, | ||||||
|  |     #[serde(rename="type")] | ||||||
|  |     pub media_type: MediaType, | ||||||
|  |     pub url: String, | ||||||
|  |     pub remote_url: String, | ||||||
|  |     pub preview_url: String, | ||||||
|  |     pub text_url: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Deserialize, Clone, Copy)] | ||||||
|  | pub enum MediaType { | ||||||
|  |     #[serde(rename = "image")] | ||||||
|  |     Image, | ||||||
|  |     #[serde(rename = "video")] | ||||||
|  |     Video, | ||||||
|  |     #[serde(rename = "gifv")] | ||||||
|  |     Gifv, | ||||||
|  | } | ||||||
| @ -0,0 +1,7 @@ | |||||||
|  | #[derive(Deserialize)] | ||||||
|  | pub struct Card { | ||||||
|  |     pub url: String, | ||||||
|  |     pub title: String, | ||||||
|  |     pub description: String, | ||||||
|  |     pub image: String, | ||||||
|  | } | ||||||
| @ -0,0 +1,7 @@ | |||||||
|  | use super::status::Status; | ||||||
|  | 
 | ||||||
|  | #[derive(Deserialize)] | ||||||
|  | pub struct Context { | ||||||
|  |     pub ancestors: Vec<Status>, | ||||||
|  |     pub descendants: Vec<Status>, | ||||||
|  | } | ||||||
| @ -0,0 +1,7 @@ | |||||||
|  | #[derive(Deserialize)] | ||||||
|  | pub struct Instance { | ||||||
|  |     pub uri: String, | ||||||
|  |     pub title: String, | ||||||
|  |     pub description: String, | ||||||
|  |     pub email: String, | ||||||
|  | } | ||||||
| @ -0,0 +1,28 @@ | |||||||
|  | pub mod account; | ||||||
|  | pub mod attachment; | ||||||
|  | pub mod card; | ||||||
|  | pub mod context; | ||||||
|  | pub mod instance; | ||||||
|  | pub mod notification; | ||||||
|  | pub mod relationship; | ||||||
|  | pub mod report; | ||||||
|  | pub mod search_result; | ||||||
|  | pub mod status; | ||||||
|  | 
 | ||||||
|  | /// An empty JSON object.
 | ||||||
|  | #[derive(Deserialize)] | ||||||
|  | pub struct Empty {} | ||||||
|  | 
 | ||||||
|  | pub mod prelude { | ||||||
|  |     pub use super::Empty; | ||||||
|  |     pub use super::account::Account; | ||||||
|  |     pub use super::attachment::{Attachment, MediaType}; | ||||||
|  |     pub use super::card::Card; | ||||||
|  |     pub use super::context::Context; | ||||||
|  |     pub use super::instance::Instance; | ||||||
|  |     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}; | ||||||
|  | } | ||||||
| @ -0,0 +1,20 @@ | |||||||
|  | use chrono::prelude::*; | ||||||
|  | use super::account::Account; | ||||||
|  | use super::status::Status; | ||||||
|  | 
 | ||||||
|  | #[derive(Deserialize)] | ||||||
|  | pub struct Notification { | ||||||
|  |     pub id: u64, | ||||||
|  |     pub notification_type: NotificationType, | ||||||
|  |     pub created_at: DateTime<UTC>, | ||||||
|  |     pub account: Account, | ||||||
|  |     pub status: Option<Status>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Deserialize)] | ||||||
|  | pub enum NotificationType { | ||||||
|  |     Mention, | ||||||
|  |     Reblog, | ||||||
|  |     Favourite, | ||||||
|  |     Follow, | ||||||
|  | } | ||||||
| @ -0,0 +1,8 @@ | |||||||
|  | #[derive(Deserialize)] | ||||||
|  | pub struct Relationship { | ||||||
|  |     pub following: bool, | ||||||
|  |     pub followed_by: bool, | ||||||
|  |     pub blocking: bool, | ||||||
|  |     pub muting: bool, | ||||||
|  |     pub requested: bool, | ||||||
|  | } | ||||||
| @ -0,0 +1,5 @@ | |||||||
|  | #[derive(Deserialize)] | ||||||
|  | pub struct Report { | ||||||
|  |     pub id: u64, | ||||||
|  |     pub action_taken: String, | ||||||
|  | } | ||||||
| @ -0,0 +1,8 @@ | |||||||
|  | use super::prelude::{Account, Status}; | ||||||
|  | 
 | ||||||
|  | #[derive(Deserialize)] | ||||||
|  | pub struct SearchResult { | ||||||
|  |     pub accounts: Vec<Account>, | ||||||
|  |     pub statuses: Vec<Status>, | ||||||
|  |     pub hashtags: Vec<String>, | ||||||
|  | } | ||||||
| @ -0,0 +1,49 @@ | |||||||
|  | use chrono::prelude::*; | ||||||
|  | use super::prelude::*; | ||||||
|  | use status_builder::Visibility; | ||||||
|  | 
 | ||||||
|  | #[derive(Deserialize)] | ||||||
|  | pub struct Status { | ||||||
|  |     pub id: i64, | ||||||
|  |     pub uri: String, | ||||||
|  |     pub url: String, | ||||||
|  |     pub account: Account, | ||||||
|  |     pub in_reply_to_id: Option<u64>, | ||||||
|  |     pub in_reply_to_account_id: Option<u64>, | ||||||
|  |     pub reblog: Option<Box<Status>>, | ||||||
|  |     pub content: String, | ||||||
|  |     pub created_at: DateTime<UTC>, | ||||||
|  |     pub reblogs_count: u64, | ||||||
|  |     pub favourites_count: u64, | ||||||
|  |     pub reblogged: bool, | ||||||
|  |     pub favourited: bool, | ||||||
|  |     pub sensitive: bool, | ||||||
|  |     pub spoiler_text: String, | ||||||
|  |     pub visibility: Visibility, | ||||||
|  |     pub media_attachments: Vec<Attachment>, | ||||||
|  |     pub mentions: Vec<Mention>, | ||||||
|  |     pub tags: Vec<Tag>, | ||||||
|  |     pub application: Application, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Deserialize)] | ||||||
|  | pub struct Mention { | ||||||
|  |     pub url: String, | ||||||
|  |     pub username: String, | ||||||
|  |     pub acct: String, | ||||||
|  |     pub id: u64, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Deserialize)] | ||||||
|  | pub struct Tag { | ||||||
|  |     pub name: String, | ||||||
|  |     pub url: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Deserialize)] | ||||||
|  | pub struct Application { | ||||||
|  |     pub name: String, | ||||||
|  |     pub website: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @ -0,0 +1,374 @@ | |||||||
|  | //! # Mammut: API Wrapper around the Mastodon API.
 | ||||||
|  | //!
 | ||||||
|  | //! Most of the api is documented on [Mastodon's
 | ||||||
|  | //! github](https://github.com/tootsuite/mastodon/blob/master/docs/Using-the-API/API.md#tag)
 | ||||||
|  | #![deny(unused_must_use)] | ||||||
|  | 
 | ||||||
|  | #[cfg_attr(test, deny(warnings))] | ||||||
|  | 
 | ||||||
|  | #[macro_use] extern crate serde_derive; | ||||||
|  | #[macro_use] extern crate serde_json as json; | ||||||
|  | extern crate chrono; | ||||||
|  | extern crate reqwest; | ||||||
|  | extern crate serde; | ||||||
|  | 
 | ||||||
|  | pub mod apps; | ||||||
|  | pub mod status_builder; | ||||||
|  | pub mod entities; | ||||||
|  | 
 | ||||||
|  | use json::Error as SerdeError; | ||||||
|  | use reqwest::Error as HttpError; | ||||||
|  | use reqwest::Client; | ||||||
|  | use reqwest::header::{Authorization, Bearer, Headers}; | ||||||
|  | 
 | ||||||
|  | use entities::prelude::*; | ||||||
|  | use status_builder::StatusBuilder; | ||||||
|  | 
 | ||||||
|  | pub type Result<T> = std::result::Result<T, Error>; | ||||||
|  | 
 | ||||||
|  | macro_rules! methods { | ||||||
|  |     ($($method:ident,)+) => { | ||||||
|  |         $( | ||||||
|  |             fn $method<T: serde::Deserialize>(&self, url: String) | ||||||
|  |             -> Result<T> | ||||||
|  |             { | ||||||
|  |                 Ok(self.client.$method(&url) | ||||||
|  |                    .headers(self.access_token.clone().unwrap()) | ||||||
|  |                    .send()? | ||||||
|  |                    .json()?) | ||||||
|  |             } | ||||||
|  |          )+ | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | macro_rules! route { | ||||||
|  | 
 | ||||||
|  |     ((post ($($param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => { | ||||||
|  |         /// Requires `access_token` or will return error.
 | ||||||
|  |         pub fn $name(&self, $($param: $typ,)*) -> Result<$ret> { | ||||||
|  |             self.has_access_token()?; | ||||||
|  | 
 | ||||||
|  |             let form_data = json!({ | ||||||
|  |                 $( | ||||||
|  |                     stringify!($param): $param, | ||||||
|  |                     )* | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             Ok(self.client.post(&self.route(concat!("/api/v1/", $url))) | ||||||
|  |                           .headers(self.access_token.clone().unwrap()) | ||||||
|  |                           .form(&form_data) | ||||||
|  |                           .send()? | ||||||
|  |                           .json()?) | ||||||
|  |         } | ||||||
|  |         route!{$($rest)*} | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     (($method:ident) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => { | ||||||
|  |         /// Requires `access_token` or will return error.
 | ||||||
|  |         pub fn $name(&self) -> Result<$ret> { | ||||||
|  |             self.has_access_token()?; | ||||||
|  | 
 | ||||||
|  |             self.$method(self.route(concat!("/api/v1/", $url))) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         route!{$($rest)*} | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     () => {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | macro_rules! route_id { | ||||||
|  | 
 | ||||||
|  |     ($(($method:ident) $name:ident: $url:expr => $ret:ty,)*) => { | ||||||
|  |         $( | ||||||
|  |             /// Requires `access_token` or will return error.
 | ||||||
|  |             pub fn $name(&self, id: u64) -> Result<$ret> { | ||||||
|  |                 self.has_access_token()?; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |                 self.$method(self.route(&format!(concat!("/api/v1/", $url), id))) | ||||||
|  |             } | ||||||
|  |          )* | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Clone, Debug)] | ||||||
|  | pub struct Mastodon { | ||||||
|  |     base_url: String, | ||||||
|  |     client: Client, | ||||||
|  |     client_id: Option<String>, | ||||||
|  |     client_secret: Option<String>, | ||||||
|  |     redirect_uri: Option<String>, | ||||||
|  |     access_token: Option<Headers>, | ||||||
|  |     id: Option<u64>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Deserialize)] | ||||||
|  | struct OAuth { | ||||||
|  |     client_id: String, | ||||||
|  |     client_secret: String, | ||||||
|  |     id: u64, | ||||||
|  |     redirect_uri: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub enum Error { | ||||||
|  |     Serde(SerdeError), | ||||||
|  |     Http(HttpError), | ||||||
|  |     ClientIdRequired, | ||||||
|  |     ClientSecretRequired, | ||||||
|  |     AccessTokenRequired, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Mastodon { | ||||||
|  |     /// Inits new Mastodon object. `base_url` is expected in the following
 | ||||||
|  |     /// format `https://mastodon.social` with no leading forward slash.
 | ||||||
|  |     ///
 | ||||||
|  |     /// ```
 | ||||||
|  |     /// use mammut::Mastodon;
 | ||||||
|  |     ///
 | ||||||
|  |     /// let mastodon = Mastodon::new("https://mastodon.social").unwrap();
 | ||||||
|  |     /// ```
 | ||||||
|  |     pub fn new<I: Into<String>>(base_url: I) -> Result<Self> { | ||||||
|  |         Ok(Mastodon { | ||||||
|  |             base_url: base_url.into(), | ||||||
|  |             client: Client::new()?, | ||||||
|  |             client_id: None, | ||||||
|  |             client_secret: None, | ||||||
|  |             redirect_uri: None, | ||||||
|  |             access_token: None, | ||||||
|  |             id: None, | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Register the application with the server from `base_url`.
 | ||||||
|  |     ///
 | ||||||
|  |     /// ```
 | ||||||
|  |     /// # extern crate mammut;
 | ||||||
|  |     /// # fn main() {
 | ||||||
|  |     /// #    try().unwrap();
 | ||||||
|  |     /// # }
 | ||||||
|  |     ///
 | ||||||
|  |     /// # fn try() -> mammut::Result<()> {
 | ||||||
|  |     /// use mammut::Mastodon;
 | ||||||
|  |     /// use mammut::apps::{AppBuilder, Scope};
 | ||||||
|  |     ///
 | ||||||
|  |     /// let app = AppBuilder {
 | ||||||
|  |     ///     client_name: "mammut_test",
 | ||||||
|  |     ///     redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
 | ||||||
|  |     ///     scopes: Scope::Read,
 | ||||||
|  |     ///     website: None,
 | ||||||
|  |     /// };
 | ||||||
|  |     ///
 | ||||||
|  |     /// let mut mastodon = Mastodon::new("https://mastodon.social")?;
 | ||||||
|  |     /// mastodon.register(app)?;
 | ||||||
|  |     /// # Ok(())
 | ||||||
|  |     /// # }
 | ||||||
|  |     /// ```
 | ||||||
|  |     pub fn register(&mut self, app_builder: apps::AppBuilder) -> Result<()> { | ||||||
|  |         let url = self.route("/api/v1/apps"); | ||||||
|  | 
 | ||||||
|  |         let app: OAuth = self.client.post(&url).form(&app_builder).send()?.json()?; | ||||||
|  | 
 | ||||||
|  |         self.id = Some(app.id); | ||||||
|  |         self.client_id = Some(app.client_id); | ||||||
|  |         self.client_secret = Some(app.client_secret); | ||||||
|  |         self.redirect_uri = Some(app.redirect_uri); | ||||||
|  | 
 | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Returns the full url needed for authorisation. This needs to be opened
 | ||||||
|  |     /// in a browser.
 | ||||||
|  |     pub fn authorise(&mut self) -> Result<String> { | ||||||
|  |         self.is_registered()?; | ||||||
|  | 
 | ||||||
|  |         let url = format!( | ||||||
|  |             "{}/oauth/authorize?client_id={}&redirect_uri={}&response_type=code", | ||||||
|  |             self.base_url, | ||||||
|  |             self.client_id.clone().unwrap(), | ||||||
|  |             self.redirect_uri.clone().unwrap(), | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         Ok(url) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Set `access_token` required to use any method about the user.
 | ||||||
|  |     pub fn set_access_token(&mut self, access_token: String) { | ||||||
|  |         let mut headers = Headers::new(); | ||||||
|  | 
 | ||||||
|  |         headers.set(Authorization(Bearer { token: access_token })); | ||||||
|  | 
 | ||||||
|  |         self.access_token = Some(headers); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     route! { | ||||||
|  |         (get) verify: "accounts/verify_credentials" => Account, | ||||||
|  |         (get) blocks: "blocks" => Vec<Account>, | ||||||
|  |         (get) follow_requests: "follow_requests" => Vec<Account>, | ||||||
|  |         (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, | ||||||
|  |         (post (id: u64,)) reject_follow_request: "accounts/follow_requests/reject" => Empty, | ||||||
|  |         (post (uri: String,)) follows: "follows" => Account, | ||||||
|  |         (post) clear_notifications: "notifications/clear" => Empty, | ||||||
|  |         (post (file: Vec<u8>,)) 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 (status: StatusBuilder,)) new_status: "statuses" => Status, | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     route_id! { | ||||||
|  |         (get) get_account: "accounts/{}" => Account, | ||||||
|  |         (get) followers: "accounts/{}/followers" => Vec<Account>, | ||||||
|  |         (get) following: "accounts/{}/following" => Vec<Account>, | ||||||
|  |         (get) follow: "accounts/{}/follow" => Account, | ||||||
|  |         (get) unfollow: "accounts/{}/unfollow" => Account, | ||||||
|  |         (get) block: "accounts/{}/block" => Account, | ||||||
|  |         (get) unblock: "accounts/{}/unblock" => Account, | ||||||
|  |         (get) mute: "accounts/{}/mute" => Account, | ||||||
|  |         (get) unmute: "accounts/{}/unmute" => Account, | ||||||
|  |         (get) get_notification: "notifications/{}" => Notification, | ||||||
|  |         (get) get_status: "statuses/{}" => Status, | ||||||
|  |         (get) get_context: "statuses/{}/context" => Context, | ||||||
|  |         (get) get_card: "statuses/{}/card" => Card, | ||||||
|  |         (get) reblogged_by: "statuses/{}/reblogged_by" => Vec<Account>, | ||||||
|  |         (get) favourited_by: "statuses/{}/favourited_by" => Vec<Account>, | ||||||
|  |         (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, | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn get_public_timeline(&self, local: bool) -> Result<Vec<Status>> { | ||||||
|  |         self.has_access_token()?; | ||||||
|  | 
 | ||||||
|  |         let mut url = self.route("/api/v1/timelines/public"); | ||||||
|  | 
 | ||||||
|  |         if local { | ||||||
|  |             url += "?local=1"; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         self.get(url) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn get_tagged_timeline(&self, hashtag: String, local: bool) -> Result<Vec<Status>> { | ||||||
|  |         self.has_access_token()?; | ||||||
|  | 
 | ||||||
|  |         let mut url = self.route("/api/v1/timelines/tag/"); | ||||||
|  |         url += &hashtag; | ||||||
|  | 
 | ||||||
|  |         if local { | ||||||
|  |             url += "?local=1"; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         self.get(url) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn statuses(&self, id: u64, only_media: bool, exclude_replies: bool) | ||||||
|  |         -> Result<Vec<Status>> | ||||||
|  |     { | ||||||
|  |         self.has_access_token()?; | ||||||
|  |         let mut url = format!("{}/api/v1/accounts/{}/statuses", self.base_url, id); | ||||||
|  | 
 | ||||||
|  |         if only_media { | ||||||
|  |             url += "?only_media=1"; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if exclude_replies { | ||||||
|  |             url += if only_media { | ||||||
|  |                 "&" | ||||||
|  |             } else { | ||||||
|  |                 "?" | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             url += "exclude_replies=1"; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         self.get(url) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     pub fn relationships(&self, ids: &[u64]) -> Result<Vec<Relationship>> { | ||||||
|  |         self.has_access_token()?; | ||||||
|  | 
 | ||||||
|  |         let mut url = self.route("/api/v1/accounts/relationships?"); | ||||||
|  | 
 | ||||||
|  |         if ids.len() == 1 { | ||||||
|  |             url += "id="; | ||||||
|  |             url += &ids[0].to_string(); | ||||||
|  |         } else { | ||||||
|  |             for id in ids { | ||||||
|  |                 url += "id[]="; | ||||||
|  |                 url += &id.to_string(); | ||||||
|  |                 url += "&"; | ||||||
|  |             } | ||||||
|  |             url.pop(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         self.get(url) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // TODO: Add a limit fn
 | ||||||
|  |     pub fn search_accounts(&self, query: &str) -> Result<Vec<Account>> { | ||||||
|  |         self.has_access_token()?; | ||||||
|  |         self.get(format!("{}/api/v1/accounts/search?q={}", self.base_url, query)) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn instance(&self) -> Result<Instance> { | ||||||
|  |         self.is_registered()?; | ||||||
|  | 
 | ||||||
|  |         self.get(self.route("/api/v1/instance")) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     fn has_access_token(&self) -> Result<()> { | ||||||
|  |         if self.access_token.is_none() { | ||||||
|  |             Err(Error::AccessTokenRequired) | ||||||
|  |         } else { | ||||||
|  |             Ok(()) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn is_registered(&self) -> Result<()> { | ||||||
|  |         if self.client_id.is_none() { | ||||||
|  |             Err(Error::ClientIdRequired) | ||||||
|  |         } else if self.client_secret.is_none() { | ||||||
|  |             Err(Error::ClientSecretRequired) | ||||||
|  |         } else { | ||||||
|  |             Ok(()) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     methods![get, post, delete,]; | ||||||
|  | 
 | ||||||
|  |     fn route(&self, url: &str) -> String { | ||||||
|  |         let mut s = self.base_url.clone(); | ||||||
|  |         s += url; | ||||||
|  |         s | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | macro_rules! from { | ||||||
|  |     ($($typ:ident, $variant:ident,)*) => { | ||||||
|  |         $( | ||||||
|  |             impl From<$typ> for Error { | ||||||
|  |                 fn from(from: $typ) -> Self { | ||||||
|  |                     use Error::*; | ||||||
|  |                     $variant(from) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         )* | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | from! { | ||||||
|  |     SerdeError, Serde, | ||||||
|  |     HttpError, Http, | ||||||
|  | } | ||||||
| @ -0,0 +1,42 @@ | |||||||
|  | #[derive(Debug, Default, Clone, Serialize)] | ||||||
|  | pub struct StatusBuilder { | ||||||
|  |     status: String, | ||||||
|  |     #[serde(skip_serializing_if="Option::is_none")] | ||||||
|  |     in_reply_to_id: Option<u64>, | ||||||
|  |     #[serde(skip_serializing_if="Option::is_none")] | ||||||
|  |     media_ids: Option<Vec<u64>>, | ||||||
|  |     #[serde(skip_serializing_if="Option::is_none")] | ||||||
|  |     sensitive: Option<bool>, | ||||||
|  |     #[serde(skip_serializing_if="Option::is_none")] | ||||||
|  |     spoiler_text: Option<String>, | ||||||
|  |     #[serde(skip_serializing_if="Option::is_none")] | ||||||
|  |     visibility: Option<Visibility>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Clone, Copy, Debug, Deserialize, Serialize)] | ||||||
|  | pub enum Visibility { | ||||||
|  |     #[serde(rename = "direct")] | ||||||
|  |     Direct, | ||||||
|  |     #[serde(rename = "private")] | ||||||
|  |     Private, | ||||||
|  |     #[serde(rename = "unlisted")] | ||||||
|  |     Unlisted, | ||||||
|  |     #[serde(rename = "public")] | ||||||
|  |     Public, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl StatusBuilder { | ||||||
|  | 
 | ||||||
|  |     pub fn new(status: String) -> Self { | ||||||
|  |         StatusBuilder { | ||||||
|  |             status: status, | ||||||
|  |             ..Self::default() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Default for Visibility { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Visibility::Public | ||||||
|  |     } | ||||||
|  | } | ||||||
					Loading…
					
					
				
		Reference in new issue