aboutsummaryrefslogtreecommitdiff
path: root/src/http
diff options
context:
space:
mode:
authorZeyla Hellyer <[email protected]>2018-05-28 16:34:38 -0700
committerZeyla Hellyer <[email protected]>2018-05-28 16:34:38 -0700
commit6b5f3b98084b86b00e3f7e78b5eb9512e75e78a0 (patch)
tree4011d56b63d88999eb8169e332c54f3eafe972ae /src/http
parentMake Message Builder use &mut self instead of self (diff)
parentFutures shard manager #298 (WIP) (#300) (diff)
downloadserenity-6b5f3b98084b86b00e3f7e78b5eb9512e75e78a0.tar.xz
serenity-6b5f3b98084b86b00e3f7e78b5eb9512e75e78a0.zip
Merge branch 'futures' into v0.6.x
Diffstat (limited to 'src/http')
-rw-r--r--src/http/constants.rs13
-rw-r--r--src/http/error.rs127
-rw-r--r--src/http/macros.rs23
-rw-r--r--src/http/mod.rs3154
-rw-r--r--src/http/ratelimiting.rs591
-rw-r--r--src/http/routing.rs1430
-rw-r--r--src/http/utils.rs12
7 files changed, 3295 insertions, 2055 deletions
diff --git a/src/http/constants.rs b/src/http/constants.rs
new file mode 100644
index 0000000..ad08754
--- /dev/null
+++ b/src/http/constants.rs
@@ -0,0 +1,13 @@
+//! A set of constants denoting the URIs that the lib uses and a constant
+//! representing the version in use.
+
+/// The base URI to the REST API.
+pub const API_URI_BASE: &str = "https://discordapp.com/api";
+/// The versioned URI to the REST API.
+pub const API_URI_VERSIONED: &str = "https://discordapp.com/api/v6";
+/// The status page base URI.
+pub const STATUS_URI_BASE: &str = "https://status.discordapp.com/api";
+/// The versioned URI to the status page.
+pub const STATUS_URI_VERSIONED: &str = "https://status.discordapp.com/api/v2";
+/// The API version that the library supports and uses.
+pub const VERSION: u8 = 6;
diff --git a/src/http/error.rs b/src/http/error.rs
index 2a5adeb..f185675 100644
--- a/src/http/error.rs
+++ b/src/http/error.rs
@@ -1,23 +1,51 @@
-use hyper::client::Response;
+use futures::Canceled;
+use hyper::{
+ error::{Error as HyperError, UriError},
+ Response,
+};
+use native_tls::Error as TlsError;
+use serde_json::Error as JsonError;
use std::{
+ cell::BorrowMutError,
error::Error as StdError,
- fmt::{
- Display,
- Formatter,
- Result as FmtResult
- }
+ fmt::{Display, Error as FmtError, Formatter, Result as FmtResult},
+ io::Error as IoError,
+ result::Result as StdResult,
};
+use super::ratelimiting::RateLimitError;
+use tokio_timer::TimerError;
+
+pub type Result<T> = StdResult<T, Error>;
#[derive(Debug)]
pub enum Error {
- /// When a non-successful status code was received for a request.
- UnsuccessfulRequest(Response),
- /// When the decoding of a ratelimit header could not be properly decoded
- /// into an `i64`.
- RateLimitI64,
- /// When the decoding of a ratelimit header could not be properly decoded
- /// from UTF-8.
- RateLimitUtf8,
+ /// There was an error mutably borrowing an `std::cell::RefCell`.
+ BorrowMut(BorrowMutError),
+ /// A future was canceled.
+ ///
+ /// This most likely occurred during a pre-emptive ratelimit.
+ Canceled(Canceled),
+ /// An error from the `std::fmt` module.
+ Format(FmtError),
+ /// An error from the `hyper` crate.
+ Hyper(HyperError),
+ /// When a status code was unexpectedly received for a request's status.
+ InvalidRequest(Response),
+ /// An error from the `std::io` module.
+ Io(IoError),
+ /// An error from the `serde_json` crate.
+ Json(JsonError),
+ /// An error from the `ratelimiting` module.
+ RateLimit(RateLimitError),
+ /// An error occurred while creating a timer.
+ Timer(TimerError),
+ /// An error from the `native_tls` crate.
+ Tls(TlsError),
+ /// When a status is received, but the verification to ensure the response
+ /// is valid does not recognize the status.
+ UnknownStatus(u16),
+ /// A `hyper` error while parsing a Uri.
+ Uri(UriError),
}
impl Display for Error {
@@ -27,11 +55,72 @@ impl Display for Error {
impl StdError for Error {
fn description(&self) -> &str {
match *self {
- Error::UnsuccessfulRequest(_) => {
- "A non-successful response status code was received"
- },
- Error::RateLimitI64 => "Error decoding a header into an i64",
- Error::RateLimitUtf8 => "Error decoding a header from UTF-8",
+ Error::BorrowMut(ref inner) => inner.description(),
+ Error::Canceled(ref inner) => inner.description(),
+ Error::Format(ref inner) => inner.description(),
+ Error::Hyper(ref inner) => inner.description(),
+ Error::InvalidRequest(_) => "Received an unexpected status code",
+ Error::Io(ref inner) => inner.description(),
+ Error::Json(ref inner) => inner.description(),
+ Error::RateLimit(ref inner) => inner.description(),
+ Error::Timer(ref inner) => inner.description(),
+ Error::Tls(ref inner) => inner.description(),
+ Error::UnknownStatus(_) => "Verification does not understand status",
+ Error::Uri(ref inner) => inner.description(),
}
}
}
+
+impl From<BorrowMutError> for Error {
+ fn from(err: BorrowMutError) -> Self {
+ Error::BorrowMut(err)
+ }
+}
+
+impl From<Canceled> for Error {
+ fn from(err: Canceled) -> Self {
+ Error::Canceled(err)
+ }
+}
+
+impl From<FmtError> for Error {
+ fn from(err: FmtError) -> Self {
+ Error::Format(err)
+ }
+}
+
+impl From<HyperError> for Error {
+ fn from(err: HyperError) -> Self {
+ Error::Hyper(err)
+ }
+}
+
+impl From<IoError> for Error {
+ fn from(err: IoError) -> Self {
+ Error::Io(err)
+ }
+}
+
+impl From<JsonError> for Error {
+ fn from(err: JsonError) -> Self {
+ Error::Json(err)
+ }
+}
+
+impl From<RateLimitError> for Error {
+ fn from(err: RateLimitError) -> Self {
+ Error::RateLimit(err)
+ }
+}
+
+impl From<TimerError> for Error {
+ fn from(err: TimerError) -> Self {
+ Error::Timer(err)
+ }
+}
+
+impl From<TlsError> for Error {
+ fn from(err: TlsError) -> Self {
+ Error::Tls(err)
+ }
+}
diff --git a/src/http/macros.rs b/src/http/macros.rs
new file mode 100644
index 0000000..87596f6
--- /dev/null
+++ b/src/http/macros.rs
@@ -0,0 +1,23 @@
+macro_rules! try_uri {
+ ($url:expr) => {{
+ match ::hyper::Uri::from_str($url) {
+ Ok(v) => v,
+ Err(why) => return Box::new(::futures::future::err(::Error::Uri(why))),
+ }
+ }};
+}
+
+macro_rules! api {
+ ($e:expr) => {
+ concat!("https://discordapp.com/api/v6", $e)
+ };
+ ($e:expr, $($rest:tt)*) => {
+ format!(api!($e), $($rest)*)
+ };
+}
+
+macro_rules! status {
+ ($e:expr) => {
+ concat!("https://status.discordapp.com/api/v2", $e)
+ }
+}
diff --git a/src/http/mod.rs b/src/http/mod.rs
index 40b35bb..880809d 100644
--- a/src/http/mod.rs
+++ b/src/http/mod.rs
@@ -23,54 +23,43 @@
//! [`Client`]: ../struct.Client.html
//! [model]: ../model/index.html
+#[macro_use] mod macros;
+
pub mod ratelimiting;
mod error;
-
-pub use self::error::Error as HttpError;
-pub use hyper::status::{StatusClass, StatusCode};
-
-use constants;
-use hyper::{
- client::{
- Client as HyperClient,
- Request,
- RequestBuilder,
- Response as HyperResponse
- },
- header::ContentType,
- method::Method,
- mime::{Mime, SubLevel, TopLevel},
- net::HttpsConnector,
- header,
- Error as HyperError,
- Result as HyperResult,
- Url
-};
-use hyper_native_tls::NativeTlsClient;
-use internal::prelude::*;
+mod routing;
+mod utils;
+
+pub use hyper::StatusCode;
+pub use self::error::{Error as HttpError, Result};
+pub use self::routing::{Path, Route};
+
+use futures::{Future, Stream, future};
+use hyper::client::{Client as HyperClient, Config as HyperConfig, HttpConnector};
+use hyper::header::{Authorization, ContentType};
+use hyper::{Body, Method, Request, Response};
+use hyper_multipart_rfc7578::client::multipart::{Body as MultipartBody, Form};
+use hyper_tls::HttpsConnector;
use model::prelude::*;
-use multipart::client::Multipart;
-use parking_lot::Mutex;
-use self::ratelimiting::Route;
-use serde_json;
-use std::{
- collections::BTreeMap,
- default::Default,
- fmt::Write as FmtWrite,
- fs::File,
- io::ErrorKind as IoErrorKind,
- path::{Path, PathBuf},
- sync::Arc
-};
+use self::ratelimiting::RateLimiter;
+use serde::de::DeserializeOwned;
+use serde_json::{self, Number, Value};
+use std::cell::RefCell;
+use std::fmt::Write;
+use std::fs::File;
+use std::io::Cursor;
+use std::rc::Rc;
+use std::str::FromStr;
+use tokio_core::reactor::Handle;
+use ::builder::*;
+use ::{Error, utils as serenity_utils};
/// An method used for ratelimiting special routes.
///
/// This is needed because `hyper`'s `Method` enum does not derive Copy.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum LightMethod {
- /// Indicates that a route is for "any" method.
- Any,
/// Indicates that a route is for the `DELETE` method only.
Delete,
/// Indicates that a route is for the `GET` method only.
@@ -83,1884 +72,1571 @@ pub enum LightMethod {
Put,
}
-lazy_static! {
- static ref TOKEN: Arc<Mutex<String>> = Arc::new(Mutex::new(String::default()));
+impl LightMethod {
+ pub fn hyper_method(&self) -> Method {
+ match *self {
+ LightMethod::Delete => Method::Delete,
+ LightMethod::Get => Method::Get,
+ LightMethod::Patch => Method::Patch,
+ LightMethod::Post => Method::Post,
+ LightMethod::Put => Method::Put,
+ }
+ }
}
-/// Sets the token to be used across all requests which require authentication.
-///
-/// If you are using the client module, you don't need to use this. If you're
-/// using serenity solely for HTTP, you need to use this.
-///
-/// # Examples
-///
-/// Setting the token from an environment variable:
-///
-/// ```rust,no_run
-/// # use std::error::Error;
-/// #
-/// # fn try_main() -> Result<(), Box<Error>> {
-/// #
-/// use serenity::http;
-/// use std::env;
-///
-/// http::set_token(&env::var("DISCORD_TOKEN")?);
-/// # Ok(())
-/// # }
-/// #
-/// # fn main() {
-/// # try_main().unwrap();
-/// # }
-pub fn set_token(token: &str) { TOKEN.lock().clone_from(&token.to_string()); }
-
-/// Adds a [`User`] as a recipient to a [`Group`].
-///
-/// **Note**: Groups have a limit of 10 recipients, including the current user.
-///
-/// [`Group`]: ../model/channel/struct.Group.html
-/// [`Group::add_recipient`]: ../model/channel/struct.Group.html#method.add_recipient
-/// [`User`]: ../model/user/struct.User.html
-pub fn add_group_recipient(group_id: u64, user_id: u64) -> Result<()> {
- verify(
- 204,
- request!(
- Route::None,
- put,
- "/channels/{}/recipients/{}",
- group_id,
- user_id
- ),
- )
-}
+#[derive(Clone, Debug)]
+pub struct Client {
+ pub client: Rc<HyperClient<HttpsConnector<HttpConnector>, Body>>,
+ pub handle: Handle,
+ pub multiparter: Rc<HyperClient<HttpsConnector<HttpConnector>, MultipartBody>>,
+ pub ratelimiter: Rc<RefCell<RateLimiter>>,
+ pub token: Rc<String>,
+}
+
+impl Client {
+ pub fn new(
+ client: Rc<HyperClient<HttpsConnector<HttpConnector>, Body>>,
+ handle: Handle,
+ token: Rc<String>,
+ ) -> Result<Self> {
+ let connector = HttpsConnector::new(4, &handle)?;
+
+ let multiparter = Rc::new(HyperConfig::default()
+ .body::<MultipartBody>()
+ .connector(connector)
+ .keep_alive(true)
+ .build(&handle));
+
+ Ok(Self {
+ ratelimiter: Rc::new(RefCell::new(RateLimiter::new(handle.clone()))),
+ client,
+ handle,
+ multiparter,
+ token,
+ })
+ }
-/// Adds a single [`Role`] to a [`Member`] in a [`Guild`].
-///
-/// **Note**: Requires the [Manage Roles] permission and respect of role
-/// hierarchy.
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-/// [`Member`]: ../model/guild/struct.Member.html
-/// [`Role`]: ../model/guild/struct.Role.html
-/// [Manage Roles]: ../model/permissions/constant.MANAGE_ROLES.html
-pub fn add_member_role(guild_id: u64, user_id: u64, role_id: u64) -> Result<()> {
- verify(
- 204,
- request!(
- Route::GuildsIdMembersIdRolesId(guild_id),
- put,
- "/guilds/{}/members/{}/roles/{}",
- guild_id,
- user_id,
- role_id
- ),
- )
-}
+ pub fn set_token(&mut self, token: Rc<String>) {
+ self.token = token;
+ }
-/// Bans a [`User`] from a [`Guild`], removing their messages sent in the last
-/// X number of days.
-///
-/// Passing a `delete_message_days` of `0` is equivalent to not removing any
-/// messages. Up to `7` days' worth of messages may be deleted.
-///
-/// **Note**: Requires that you have the [Ban Members] permission.
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-/// [`User`]: ../model/user/struct.User.html
-/// [Ban Members]: ../model/permissions/constant.BAN_MEMBERS.html
-pub fn ban_user(guild_id: u64, user_id: u64, delete_message_days: u8, reason: &str) -> Result<()> {
- verify(
- 204,
- request!(
- Route::GuildsIdBansUserId(guild_id),
- put,
- "/guilds/{}/bans/{}?delete_message_days={}&reason={}",
+ /// Adds a [`User`] as a recipient to a [`Group`].
+ ///
+ /// **Note**: Groups have a limit of 10 recipients, including the current
+ /// user.
+ ///
+ /// [`Group`]: ../model/channel/struct.Group.html
+ /// [`Group::add_recipient`]: ../model/channel/struct.Group.html#method.add_recipient
+ /// [`User`]: ../model/user/struct.User.html
+ pub fn add_group_recipient(&self, group_id: u64, user_id: u64)
+ -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::AddGroupRecipient { group_id, user_id }, None)
+ }
+
+ /// Adds a single [`Role`] to a [`Member`] in a [`Guild`].
+ ///
+ /// **Note**: Requires the [Manage Roles] permission and respect of role
+ /// hierarchy.
+ ///
+ /// [`Guild`]: ../model/guild/struct.Guild.html
+ /// [`Member`]: ../model/guild/struct.Member.html
+ /// [`Role`]: ../model/guild/struct.Role.html
+ /// [Manage Roles]: ../model/permissions/constant.MANAGE_ROLES.html
+ pub fn add_member_role(
+ &self,
+ guild_id: u64,
+ user_id: u64,
+ role_id: u64
+ ) -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::AddMemberRole { guild_id, user_id, role_id }, None)
+ }
+
+ /// Bans a [`User`] from a [`Guild`], removing their messages sent in the
+ /// last X number of days.
+ ///
+ /// Passing a `delete_message_days` of `0` is equivalent to not removing any
+ /// messages. Up to `7` days' worth of messages may be deleted.
+ ///
+ /// **Note**: Requires that you have the [Ban Members] permission.
+ ///
+ /// [`Guild`]: ../model/guild/struct.Guild.html
+ /// [`User`]: ../model/user/struct.User.html
+ /// [Ban Members]: ../model/permissions/constant.BAN_MEMBERS.html
+ pub fn ban_user(
+ &self,
+ guild_id: u64,
+ user_id: u64,
+ delete_message_days: u8,
+ reason: &str,
+ ) -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::GuildBanUser {
+ delete_message_days: Some(delete_message_days),
+ reason: Some(reason),
guild_id,
user_id,
- delete_message_days,
- reason
- ),
- )
-}
+ }, None)
+ }
-/// Ban zeyla from a [`Guild`], removing her messages sent in the last X number
-/// of days.
-///
-/// Passing a `delete_message_days` of `0` is equivalent to not removing any
-/// messages. Up to `7` days' worth of messages may be deleted.
-///
-/// **Note**: Requires that you have the [Ban Members] permission.
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-/// [Ban Members]: ../model/permissions/constant.BAN_MEMBERS.html
-pub fn ban_zeyla(guild_id: u64, delete_message_days: u8, reason: &str) -> Result<()> {
- ban_user(guild_id, 114941315417899012, delete_message_days, reason)
-}
+ /// Ban zeyla from a [`Guild`], removing her messages sent in the last X number
+ /// of days.
+ ///
+ /// Passing a `delete_message_days` of `0` is equivalent to not removing any
+ /// messages. Up to `7` days' worth of messages may be deleted.
+ ///
+ /// **Note**: Requires that you have the [Ban Members] permission.
+ ///
+ /// [`Guild`]: ../model/guild/struct.Guild.html
+ /// [Ban Members]: ../model/permissions/constant.BAN_MEMBERS.html
+ pub fn ban_zeyla(
+ &self,
+ guild_id: u64,
+ delete_message_days: u8,
+ reason: &str,
+ ) -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::GuildBanUser {
+ delete_message_days: Some(delete_message_days),
+ reason: Some(reason),
+ guild_id,
+ user_id: 114941315417899012,
+ }, None)
+ }
-/// Broadcasts that the current user is typing in the given [`Channel`].
-///
-/// This lasts for about 10 seconds, and will then need to be renewed to
-/// indicate that the current user is still typing.
-///
-/// This should rarely be used for bots, although it is a good indicator that a
-/// long-running command is still being processed.
-///
-/// [`Channel`]: ../model/channel/enum.Channel.html
-pub fn broadcast_typing(channel_id: u64) -> Result<()> {
- verify(
- 204,
- request!(
- Route::ChannelsIdTyping(channel_id),
- post,
- "/channels/{}/typing",
- channel_id
- ),
- )
-}
+ /// Broadcasts that the current user is typing in the given [`Channel`].
+ ///
+ /// This lasts for about 10 seconds, and will then need to be renewed to
+ /// indicate that the current user is still typing.
+ ///
+ /// This should rarely be used for bots, although it is a good indicator
+ /// that a long-running command is still being processed.
+ ///
+ /// [`Channel`]: ../model/channel/enum.Channel.html
+ pub fn broadcast_typing(&self, channel_id: u64)
+ -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::BroadcastTyping { channel_id }, None)
+ }
-/// Creates a [`GuildChannel`] in the [`Guild`] given its Id.
-///
-/// Refer to the Discord's [docs] for information on what fields this requires.
-///
-/// **Note**: Requires the [Manage Channels] permission.
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-/// [`GuildChannel`]: ../model/channel/struct.GuildChannel.html
-/// [docs]: https://discordapp.com/developers/docs/resources/guild#create-guild-channel
-/// [Manage Channels]: ../model/permissions/constant.MANAGE_CHANNELS.html
-pub fn create_channel(guild_id: u64, map: &Value) -> Result<GuildChannel> {
- let body = map.to_string();
- let response = request!(
- Route::GuildsIdChannels(guild_id),
- post(body),
- "/guilds/{}/channels",
- guild_id
- );
-
- serde_json::from_reader::<HyperResponse, GuildChannel>(response)
- .map_err(From::from)
-}
+ /// Creates a [`GuildChannel`] in the [`Guild`] given its Id.
+ ///
+ /// Refer to the Discord's [docs] for information on what fields this
+ /// requires.
+ ///
+ /// **Note**: Requires the [Manage Channels] permission.
+ ///
+ /// [`Guild`]: ../model/guild/struct.Guild.html
+ /// [`GuildChannel`]: ../model/channel/struct.GuildChannel.html
+ /// [docs]: https://discordapp.com/developers/docs/resources/guild#create-guild-channel
+ /// [Manage Channels]: ../model/permissions/constant.MANAGE_CHANNELS.html
+ pub fn create_channel(
+ &self,
+ guild_id: u64,
+ name: &str,
+ kind: ChannelType,
+ category_id: Option<u64>,
+ ) -> impl Future<Item = GuildChannel, Error = Error> {
+ self.post(Route::CreateChannel { guild_id }, Some(&json!({
+ "name": name,
+ "parent_id": category_id,
+ "type": kind as u8,
+ })))
+ }
-/// Creates an emoji in the given [`Guild`] with the given data.
-///
-/// View the source code for [`Context::create_emoji`] to see what fields this
-/// requires.
-///
-/// **Note**: Requires the [Manage Emojis] permission.
-///
-/// [`Context::create_emoji`]: ../struct.Context.html#method.create_emoji
-/// [`Guild`]: ../model/guild/struct.Guild.html
-/// [Manage Emojis]: ../model/permissions/constant.MANAGE_EMOJIS.html
-pub fn create_emoji(guild_id: u64, map: &Value) -> Result<Emoji> {
- let body = map.to_string();
- let response = request!(
- Route::GuildsIdEmojis(guild_id),
- post(body),
- "/guilds/{}/emojis",
- guild_id
- );
-
- serde_json::from_reader::<HyperResponse, Emoji>(response)
- .map_err(From::from)
-}
+ /// Creates an emoji in the given [`Guild`] with the given data.
+ ///
+ /// View the source code for [`Context::create_emoji`] to see what fields
+ /// this requires.
+ ///
+ /// **Note**: Requires the [Manage Emojis] permission.
+ ///
+ /// [`Context::create_emoji`]: ../struct.Context.html#method.create_emoji
+ /// [`Guild`]: ../model/guild/struct.Guild.html
+ /// [Manage Emojis]: ../model/permissions/constant.MANAGE_EMOJIS.html
+ pub fn create_emoji(&self, guild_id: u64, name: &str, image: &str)
+ -> impl Future<Item = Emoji, Error = Error> {
+ self.post(Route::CreateEmoji { guild_id }, Some(&json!({
+ "image": image,
+ "name": name,
+ })))
+ }
-/// Creates a guild with the data provided.
-///
-/// Only a [`PartialGuild`] will be immediately returned, and a full [`Guild`]
-/// will be received over a [`Shard`], if at least one is running.
-///
-/// **Note**: This endpoint is currently limited to 10 active guilds. The
-/// limits are raised for whitelisted [GameBridge] applications. See the
-/// [documentation on this endpoint] for more info.
-///
-/// # Examples
-///
-/// Create a guild called `"test"` in the [US West region]:
-///
-/// ```rust,ignore
-/// extern crate serde_json;
-///
-/// use serde_json::builder::ObjectBuilder;
-/// use serde_json::Value;
-/// use serenity::http;
-///
-/// let map = ObjectBuilder::new()
-/// .insert("name", "test")
-/// .insert("region", "us-west")
-/// .build();
-///
-/// let _result = http::create_guild(map);
-/// ```
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-/// [`PartialGuild`]: ../model/guild/struct.PartialGuild.html
-/// [`Shard`]: ../gateway/struct.Shard.html
-/// [GameBridge]: https://discordapp.com/developers/docs/topics/gamebridge
-/// [US West Region]: ../model/guild/enum.Region.html#variant.UsWest
-/// [documentation on this endpoint]:
-/// https://discordapp.com/developers/docs/resources/guild#create-guild
-/// [whitelist]: https://discordapp.com/developers/docs/resources/guild#create-guild
-pub fn create_guild(map: &Value) -> Result<PartialGuild> {
- let body = map.to_string();
- let response = request!(Route::Guilds, post(body), "/guilds");
-
- serde_json::from_reader::<HyperResponse, PartialGuild>(response)
- .map_err(From::from)
-}
+ /// Creates a guild with the data provided.
+ ///
+ /// Only a [`PartialGuild`] will be immediately returned, and a full
+ /// [`Guild`] will be received over a [`Shard`], if at least one is running.
+ ///
+ /// **Note**: This endpoint is currently limited to 10 active guilds. The
+ /// limits are raised for whitelisted [GameBridge] applications. See the
+ /// [documentation on this endpoint] for more info.
+ ///
+ /// # Examples
+ ///
+ /// Create a guild called `"test"` in the [US West region]:
+ ///
+ /// ```rust,ignore
+ /// extern crate serde_json;
+ ///
+ /// use serde_json::builder::ObjectBuilder;
+ /// use serde_json::Value;
+ /// use serenity::http;
+ ///
+ /// let map = ObjectBuilder::new()
+ /// .insert("name", "test")
+ /// .insert("region", "us-west")
+ /// .build();
+ ///
+ /// let _result = http::create_guild(map);
+ /// ```
+ ///
+ /// [`Guild`]: ../model/guild/struct.Guild.html
+ /// [`PartialGuild`]: ../model/guild/struct.PartialGuild.html
+ /// [`Shard`]: ../gateway/struct.Shard.html
+ /// [GameBridge]: https://discordapp.com/developers/docs/topics/gamebridge
+ /// [US West Region]: ../model/enum.Region.html#variant.UsWest
+ /// [documentation on this endpoint]:
+ /// https://discordapp.com/developers/docs/resources/guild#create-guild
+ /// [whitelist]: https://discordapp.com/developers/docs/resources/guild#create-guild
+ pub fn create_guild(&self, map: &Value)
+ -> impl Future<Item = PartialGuild, Error = Error> {
+ self.post(Route::CreateGuild, Some(map))
+ }
-/// Creates an [`Integration`] for a [`Guild`].
-///
-/// Refer to Discord's [docs] for field information.
-///
-/// **Note**: Requires the [Manage Guild] permission.
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-/// [`Integration`]: ../model/guild/struct.Integration.html
-/// [Manage Guild]: ../model/permissions/constant.MANAGE_GUILD.html
-/// [docs]: https://discordapp.com/developers/docs/resources/guild#create-guild-integration
-pub fn create_guild_integration(guild_id: u64, integration_id: u64, map: &Value) -> Result<()> {
- let body = map.to_string();
-
- verify(
- 204,
- request!(
- Route::GuildsIdIntegrations(guild_id),
- post(body),
- "/guilds/{}/integrations/{}",
+ /// Creates an [`Integration`] for a [`Guild`].
+ ///
+ /// Refer to Discord's [docs] for field information.
+ ///
+ /// **Note**: Requires the [Manage Guild] permission.
+ ///
+ /// [`Guild`]: ../model/guild/struct.Guild.html
+ /// [`Integration`]: ../model/guild/struct.Integration.html
+ /// [Manage Guild]: ../model/permissions/constant.MANAGE_GUILD.html
+ /// [docs]: https://discordapp.com/developers/docs/resources/guild#create-guild-integration
+ pub fn create_guild_integration(
+ &self,
+ guild_id: u64,
+ integration_id: u64,
+ kind: &str,
+ ) -> impl Future<Item = (), Error = Error> {
+ let json = json!({
+ "id": integration_id,
+ "type": kind,
+ });
+ self.verify(Route::CreateGuildIntegration {
guild_id,
- integration_id
- ),
- )
-}
-
-/// Creates a [`RichInvite`] for the given [channel][`GuildChannel`].
-///
-/// Refer to Discord's [docs] for field information.
-///
-/// All fields are optional.
-///
-/// **Note**: Requires the [Create Invite] permission.
-///
-/// [`GuildChannel`]: ../model/channel/struct.GuildChannel.html
-/// [`RichInvite`]: ../model/guild/struct.RichInvite.html
-/// [Create Invite]: ../model/permissions/constant.CREATE_INVITE.html
-/// [docs]: https://discordapp.com/developers/docs/resources/channel#create-channel-invite
-pub fn create_invite(channel_id: u64, map: &JsonMap) -> Result<RichInvite> {
- let body = serde_json::to_string(map)?;
- let response = request!(
- Route::ChannelsIdInvites(channel_id),
- post(body),
- "/channels/{}/invites",
- channel_id
- );
-
- serde_json::from_reader::<HyperResponse, RichInvite>(response)
- .map_err(From::from)
-}
+ integration_id,
+ }, Some(&json))
+ }
-/// Creates a permission override for a member or a role in a channel.
-pub fn create_permission(channel_id: u64, target_id: u64, map: &Value) -> Result<()> {
- let body = map.to_string();
+ /// Creates a [`RichInvite`] for the given [channel][`GuildChannel`].
+ ///
+ /// Refer to Discord's [docs] for field information.
+ ///
+ /// All fields are optional.
+ ///
+ /// **Note**: Requires the [Create Invite] permission.
+ ///
+ /// [`GuildChannel`]: ../model/channel/struct.GuildChannel.html
+ /// [`RichInvite`]: ../model/guild/struct.RichInvite.html
+ /// [Create Invite]: ../model/permissions/constant.CREATE_INVITE.html
+ /// [docs]: https://discordapp.com/developers/docs/resources/channel#create-channel-invite
+ pub fn create_invite<F>(&self, channel_id: u64, f: F)
+ -> impl Future<Item = RichInvite, Error = Error>
+ where F: FnOnce(CreateInvite) -> CreateInvite {
+ let map = serenity_utils::vecmap_to_json_map(f(CreateInvite::default()).0);
+
+ self.post(Route::CreateInvite { channel_id }, Some(&Value::Object(map)))
+ }
- verify(
- 204,
- request!(
- Route::ChannelsIdPermissionsOverwriteId(channel_id),
- put(body),
- "/channels/{}/permissions/{}",
+ /// Creates a permission override for a member or a role in a channel.
+ pub fn create_permission(
+ &self,
+ channel_id: u64,
+ target: &PermissionOverwrite,
+ ) -> impl Future<Item = (), Error = Error> {
+ let (id, kind) = match target.kind {
+ PermissionOverwriteType::Member(id) => (id.0, "member"),
+ PermissionOverwriteType::Role(id) => (id.0, "role"),
+ };
+ let map = json!({
+ "allow": target.allow.bits(),
+ "deny": target.deny.bits(),
+ "id": id,
+ "type": kind,
+ });
+
+ self.verify(Route::CreatePermission {
+ target_id: id,
channel_id,
- target_id
- ),
- )
-}
+ }, Some(&map))
+ }
-/// Creates a private channel with a user.
-pub fn create_private_channel(map: &Value) -> Result<PrivateChannel> {
- let body = map.to_string();
- let response = request!(Route::UsersMeChannels, post(body), "/users/@me/channels");
+ /// Creates a private channel with a user.
+ pub fn create_private_channel(&self, user_id: u64)
+ -> impl Future<Item = PrivateChannel, Error = Error> {
+ let map = json!({
+ "recipient_id": user_id,
+ });
- serde_json::from_reader::<HyperResponse, PrivateChannel>(response)
- .map_err(From::from)
-}
+ self.post(Route::CreatePrivateChannel, Some(&map))
+ }
-/// Reacts to a message.
-pub fn create_reaction(channel_id: u64,
- message_id: u64,
- reaction_type: &ReactionType)
- -> Result<()> {
- verify(
- 204,
- request!(
- Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id),
- put,
- "/channels/{}/messages/{}/reactions/{}/@me",
+ /// Reacts to a message.
+ pub fn create_reaction(
+ &self,
+ channel_id: u64,
+ message_id: u64,
+ reaction_type: &ReactionType
+ ) -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::CreateReaction {
+ reaction: &utils::reaction_type_data(reaction_type),
channel_id,
message_id,
- reaction_type.as_data()
- ),
- )
-}
+ }, None)
+ }
-/// Creates a role.
-pub fn create_role(guild_id: u64, map: &JsonMap) -> Result<Role> {
- let body = serde_json::to_string(map)?;
- let response = request!(
- Route::GuildsIdRoles(guild_id),
- post(body),
- "/guilds/{}/roles",
- guild_id
- );
-
- serde_json::from_reader::<HyperResponse, Role>(response)
- .map_err(From::from)
-}
+ /// Creates a role.
+ pub fn create_role<F>(&self, guild_id: u64, f: F) -> impl Future<Item = Role, Error = Error>
+ where F: FnOnce(EditRole) -> EditRole {
+ let map = serenity_utils::vecmap_to_json_map(f(EditRole::default()).0);
-/// Creates a webhook for the given [channel][`GuildChannel`]'s Id, passing in
-/// the given data.
-///
-/// This method requires authentication.
-///
-/// The Value is a map with the values of:
-///
-/// - **avatar**: base64-encoded 128x128 image for the webhook's default avatar
-/// (_optional_);
-/// - **name**: the name of the webhook, limited to between 2 and 100 characters
-/// long.
-///
-/// # Examples
-///
-/// Creating a webhook named `test`:
-///
-/// ```rust,ignore
-/// extern crate serde_json;
-/// extern crate serenity;
-///
-/// use serde_json::builder::ObjectBuilder;
-/// use serenity::http;
-///
-/// let channel_id = 81384788765712384;
-/// let map = ObjectBuilder::new().insert("name", "test").build();
-///
-/// let webhook = http::create_webhook(channel_id, map).expect("Error creating");
-/// ```
-///
-/// [`GuildChannel`]: ../model/channel/struct.GuildChannel.html
-pub fn create_webhook(channel_id: u64, map: &Value) -> Result<Webhook> {
- let body = map.to_string();
- let response = request!(
- Route::ChannelsIdWebhooks(channel_id),
- post(body),
- "/channels/{}/webhooks",
- channel_id
- );
-
- serde_json::from_reader::<HyperResponse, Webhook>(response)
- .map_err(From::from)
-}
+ self.post(Route::CreateRole { guild_id }, Some(&Value::Object(map)))
+ }
-/// Deletes a private channel or a channel in a guild.
-pub fn delete_channel(channel_id: u64) -> Result<Channel> {
- let response = request!(
- Route::ChannelsId(channel_id),
- delete,
- "/channels/{}",
- channel_id
- );
-
- serde_json::from_reader::<HyperResponse, Channel>(response)
- .map_err(From::from)
-}
+ /// Creates a webhook for the given [channel][`GuildChannel`]'s Id, passing
+ /// in the given data.
+ ///
+ /// This method requires authentication.
+ ///
+ /// The Value is a map with the values of:
+ ///
+ /// - **avatar**: base64-encoded 128x128 image for the webhook's default
+ /// avatar (_optional_);
+ /// - **name**: the name of the webhook, limited to between 2 and 100
+ /// characters long.
+ ///
+ /// # Examples
+ ///
+ /// Creating a webhook named `test`:
+ ///
+ /// ```rust,ignore
+ /// extern crate serde_json;
+ /// extern crate serenity;
+ ///
+ /// use serde_json::builder::ObjectBuilder;
+ /// use serenity::http;
+ ///
+ /// let channel_id = 81384788765712384;
+ /// let map = ObjectBuilder::new().insert("name", "test").build();
+ ///
+ /// let webhook = http::create_webhook(channel_id, map)
+ /// .expect("Error creating");
+ /// ```
+ ///
+ /// [`GuildChannel`]: ../model/channel/struct.GuildChannel.html
+ pub fn create_webhook(&self, channel_id: u64, map: &Value)
+ -> impl Future<Item = Webhook, Error = Error> {
+ self.post(Route::CreateWebhook { channel_id }, Some(map))
+ }
-/// Deletes an emoji from a server.
-pub fn delete_emoji(guild_id: u64, emoji_id: u64) -> Result<()> {
- verify(
- 204,
- request!(
- Route::GuildsIdEmojisId(guild_id),
- delete,
- "/guilds/{}/emojis/{}",
- guild_id,
- emoji_id
- ),
- )
-}
+ /// Deletes a private channel or a channel in a guild.
+ pub fn delete_channel(&self, channel_id: u64) -> impl Future<Item = Channel, Error = Error> {
+ self.delete(Route::DeleteChannel { channel_id }, None)
+ }
-/// Deletes a guild, only if connected account owns it.
-pub fn delete_guild(guild_id: u64) -> Result<PartialGuild> {
- let response = request!(Route::GuildsId(guild_id), delete, "/guilds/{}", guild_id);
+ /// Deletes an emoji from a server.
+ pub fn delete_emoji(&self, guild_id: u64, emoji_id: u64)
+ -> impl Future<Item = (), Error = Error> {
+ self.delete(Route::DeleteEmoji { guild_id, emoji_id }, None)
+ }
- serde_json::from_reader::<HyperResponse, PartialGuild>(response)
- .map_err(From::from)
-}
+ /// Deletes a guild, only if connected account owns it.
+ pub fn delete_guild(&self, guild_id: u64)
+ -> impl Future<Item = PartialGuild, Error = Error> {
+ self.delete(Route::DeleteGuild { guild_id }, None)
+ }
-/// Remvoes an integration from a guild.
-pub fn delete_guild_integration(guild_id: u64, integration_id: u64) -> Result<()> {
- verify(
- 204,
- request!(
- Route::GuildsIdIntegrationsId(guild_id),
- delete,
- "/guilds/{}/integrations/{}",
+ /// Remvoes an integration from a guild.
+ pub fn delete_guild_integration(
+ &self,
+ guild_id: u64,
+ integration_id: u64,
+ ) -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::DeleteGuildIntegration {
guild_id,
- integration_id
- ),
- )
-}
+ integration_id,
+ }, None)
+ }
-/// Deletes an invite by code.
-pub fn delete_invite(code: &str) -> Result<Invite> {
- let response = request!(Route::InvitesCode, delete, "/invites/{}", code);
+ /// Deletes an invite by code.
+ pub fn delete_invite(&self, code: &str) -> impl Future<Item = Invite, Error = Error> {
+ self.delete(Route::DeleteInvite { code }, None)
+ }
- serde_json::from_reader::<HyperResponse, Invite>(response)
- .map_err(From::from)
-}
+ /// Deletes a message if created by us or we have specific permissions.
+ pub fn delete_message(&self, channel_id: u64, message_id: u64)
+ -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::DeleteMessage { channel_id, message_id }, None)
+ }
-/// Deletes a message if created by us or we have
-/// specific permissions.
-pub fn delete_message(channel_id: u64, message_id: u64) -> Result<()> {
- verify(
- 204,
- request!(
- Route::ChannelsIdMessagesId(LightMethod::Delete, channel_id),
- delete,
- "/channels/{}/messages/{}",
- channel_id,
- message_id
- ),
- )
-}
+ /// Deletes a bunch of messages, only works for bots.
+ pub fn delete_messages<T, It>(&self, channel_id: u64, message_ids: It)
+ -> impl Future<Item = (), Error = Error>
+ where T: AsRef<MessageId>, It: IntoIterator<Item=T> {
+ let ids = message_ids
+ .into_iter()
+ .map(|id| id.as_ref().0)
+ .collect::<Vec<u64>>();
-/// Deletes a bunch of messages, only works for bots.
-pub fn delete_messages(channel_id: u64, map: &Value) -> Result<()> {
- let body = map.to_string();
-
- verify(
- 204,
- request!(
- Route::ChannelsIdMessagesBulkDelete(channel_id),
- post(body),
- "/channels/{}/messages/bulk-delete",
- channel_id
- ),
- )
-}
+ let map = json!({
+ "messages": ids,
+ });
-/// Deletes all of the [`Reaction`]s associated with a [`Message`].
-///
-/// # Examples
-///
-/// ```rust,no_run
-/// use serenity::http;
-/// use serenity::model::id::{ChannelId, MessageId};
-///
-/// let channel_id = ChannelId(7);
-/// let message_id = MessageId(8);
-///
-/// let _ = http::delete_message_reactions(channel_id.0, message_id.0)
-/// .expect("Error deleting reactions");
-/// ```
-///
-/// [`Message`]: ../model/channel/struct.Message.html
-/// [`Reaction`]: ../model/channel/struct.Reaction.html
-pub fn delete_message_reactions(channel_id: u64, message_id: u64) -> Result<()> {
- verify(
- 204,
- request!(
- Route::ChannelsIdMessagesIdReactions(channel_id),
- delete,
- "/channels/{}/messages/{}/reactions",
- channel_id,
- message_id
- ),
- )
-}
+ self.verify(Route::DeleteMessages { channel_id }, Some(&map))
+ }
-/// Deletes a permission override from a role or a member in a channel.
-pub fn delete_permission(channel_id: u64, target_id: u64) -> Result<()> {
- verify(
- 204,
- request!(
- Route::ChannelsIdPermissionsOverwriteId(channel_id),
- delete,
- "/channels/{}/permissions/{}",
+ /// Deletes all of the [`Reaction`]s associated with a [`Message`].
+ ///
+ /// # Examples
+ ///
+ /// ```rust,no_run
+ /// use serenity::http;
+ /// use serenity::model::{ChannelId, MessageId};
+ ///
+ /// let channel_id = ChannelId(7);
+ /// let message_id = MessageId(8);
+ ///
+ /// let _ = http::delete_message_reactions(channel_id.0, message_id.0)
+ /// .expect("Error deleting reactions");
+ /// ```
+ ///
+ /// [`Message`]: ../model/channel/struct.Message.html
+ /// [`Reaction`]: ../model/channel/struct.Reaction.html
+ pub fn delete_message_reactions(&self, channel_id: u64, message_id: u64)
+ -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::DeleteMessageReactions {
channel_id,
- target_id
- ),
- )
-}
+ message_id,
+ }, None)
+ }
+
+ /// Deletes a permission override from a role or a member in a channel.
+ pub fn delete_permission(&self, channel_id: u64, target_id: u64)
+ -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::DeletePermission { channel_id, target_id }, None)
+ }
-/// Deletes a reaction from a message if owned by us or
-/// we have specific permissions.
-pub fn delete_reaction(channel_id: u64,
- message_id: u64,
- user_id: Option<u64>,
- reaction_type: &ReactionType)
- -> Result<()> {
- let user = user_id
- .map(|uid| uid.to_string())
- .unwrap_or_else(|| "@me".to_string());
-
- verify(
- 204,
- request!(
- Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id),
- delete,
- "/channels/{}/messages/{}/reactions/{}/{}",
+ /// Deletes a reaction from a message if owned by us or
+ /// we have specific permissions.
+ pub fn delete_reaction(
+ &self,
+ channel_id: u64,
+ message_id: u64,
+ user_id: Option<u64>,
+ reaction_type: &ReactionType,
+ ) -> impl Future<Item = (), Error = Error> {
+ let reaction_type = utils::reaction_type_data(reaction_type);
+ let user = user_id
+ .map(|uid| uid.to_string())
+ .unwrap_or_else(|| "@me".to_string());
+
+ self.verify(Route::DeleteReaction {
+ reaction: &reaction_type,
+ user: &user,
channel_id,
message_id,
- reaction_type.as_data(),
- user
- ),
- )
-}
+ }, None)
+ }
-/// Deletes a role from a server. Can't remove the default everyone role.
-pub fn delete_role(guild_id: u64, role_id: u64) -> Result<()> {
- verify(
- 204,
- request!(
- Route::GuildsIdRolesId(guild_id),
- delete,
- "/guilds/{}/roles/{}",
- guild_id,
- role_id
- ),
- )
-}
+ /// Deletes a role from a server. Can't remove the default everyone role.
+ pub fn delete_role(&self, guild_id: u64, role_id: u64)
+ -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::DeleteRole { guild_id, role_id }, None)
+ }
-/// Deletes a [`Webhook`] given its Id.
-///
-/// This method requires authentication, whereas [`delete_webhook_with_token`]
-/// does not.
-///
-/// # Examples
-///
-/// Deletes a webhook given its Id:
-///
-/// ```rust,no_run
-/// use serenity::{Client, http};
-/// use std::env;
-///
-/// // Due to the `delete_webhook` function requiring you to authenticate, you
-/// // must have set the token first.
-/// http::set_token(&env::var("DISCORD_TOKEN").unwrap());
-///
-/// http::delete_webhook(245037420704169985).expect("Error deleting webhook");
-/// ```
-///
-/// [`Webhook`]: ../model/webhook/struct.Webhook.html
-/// [`delete_webhook_with_token`]: fn.delete_webhook_with_token.html
-pub fn delete_webhook(webhook_id: u64) -> Result<()> {
- verify(
- 204,
- request!(
- Route::WebhooksId(webhook_id),
- delete,
- "/webhooks/{}",
- webhook_id,
- ),
- )
-}
+ /// Deletes a [`Webhook`] given its Id.
+ ///
+ /// This method requires authentication, whereas
+ /// [`delete_webhook_with_token`] does not.
+ ///
+ /// # Examples
+ ///
+ /// Deletes a webhook given its Id:
+ ///
+ /// ```rust,no_run
+ /// use serenity::{Client, http};
+ /// use std::env;
+ ///
+ /// // Due to the `delete_webhook` function requiring you to authenticate,
+ /// // you must have set the token first.
+ /// http::set_token(&env::var("DISCORD_TOKEN").unwrap());
+ ///
+ /// http::delete_webhook(245037420704169985)
+ /// .expect("Error deleting webhook");
+ /// ```
+ ///
+ /// [`Webhook`]: ../model/webhook/struct.Webhook.html
+ /// [`delete_webhook_with_token`]: fn.delete_webhook_with_token.html
+ pub fn delete_webhook(&self, webhook_id: u64) -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::DeleteWebhook { webhook_id }, None)
+ }
-/// Deletes a [`Webhook`] given its Id and unique token.
-///
-/// This method does _not_ require authentication.
-///
-/// # Examples
-///
-/// Deletes a webhook given its Id and unique token:
-///
-/// ```rust,no_run
-/// use serenity::http;
-///
-/// let id = 245037420704169985;
-/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
-///
-/// http::delete_webhook_with_token(id, token).expect("Error deleting webhook");
-/// ```
-///
-/// [`Webhook`]: ../model/webhook/struct.Webhook.html
-pub fn delete_webhook_with_token(webhook_id: u64, token: &str) -> Result<()> {
- let client = request_client!();
-
- verify(
- 204,
- retry(|| {
- client
- .delete(&format!(api!("/webhooks/{}/{}"), webhook_id, token))
- }).map_err(Error::Hyper)?,
- )
-}
+ /// Deletes a [`Webhook`] given its Id and unique token.
+ ///
+ /// This method does _not_ require authentication.
+ ///
+ /// # Examples
+ ///
+ /// Deletes a webhook given its Id and unique token:
+ ///
+ /// ```rust,no_run
+ /// use serenity::http;
+ ///
+ /// let id = 245037420704169985;
+ /// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
+ ///
+ /// http::delete_webhook_with_token(id, token)
+ /// .expect("Error deleting webhook");
+ /// ```
+ ///
+ /// [`Webhook`]: ../model/webhook/struct.Webhook.html
+ pub fn delete_webhook_with_token(&self, webhook_id: u64, token: &str)
+ -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::DeleteWebhookWithToken { webhook_id, token }, None)
+ }
-/// Changes channel information.
-pub fn edit_channel(channel_id: u64, map: &JsonMap) -> Result<GuildChannel> {
- let body = serde_json::to_string(map)?;
- let response = request!(
- Route::ChannelsId(channel_id),
- patch(body),
- "/channels/{}",
- channel_id
- );
-
- serde_json::from_reader::<HyperResponse, GuildChannel>(response)
- .map_err(From::from)
-}
+ /// Changes channel information.
+ pub fn edit_channel<F>(&self, channel_id: u64, f: F)
+ -> impl Future<Item = GuildChannel, Error = Error>
+ where F: FnOnce(EditChannel) -> EditChannel {
+ let channel = f(EditChannel::default()).0;
+ let map = Value::Object(serenity_utils::vecmap_to_json_map(channel));
-/// Changes emoji information.
-pub fn edit_emoji(guild_id: u64, emoji_id: u64, map: &Value) -> Result<Emoji> {
- let body = map.to_string();
- let response = request!(
- Route::GuildsIdEmojisId(guild_id),
- patch(body),
- "/guilds/{}/emojis/{}",
- guild_id,
- emoji_id
- );
-
- serde_json::from_reader::<HyperResponse, Emoji>(response)
- .map_err(From::from)
-}
+ self.patch(Route::EditChannel { channel_id }, Some(&map))
+ }
-/// Changes guild information.
-pub fn edit_guild(guild_id: u64, map: &JsonMap) -> Result<PartialGuild> {
- let body = serde_json::to_string(map)?;
- let response = request!(
- Route::GuildsId(guild_id),
- patch(body),
- "/guilds/{}",
- guild_id
- );
-
- serde_json::from_reader::<HyperResponse, PartialGuild>(response)
- .map_err(From::from)
-}
+ /// Changes emoji information.
+ pub fn edit_emoji(&self, guild_id: u64, emoji_id: u64, name: &str)
+ -> impl Future<Item = Emoji, Error = Error> {
+ let map = json!({
+ "name": name,
+ });
-/// Edits the positions of a guild's channels.
-pub fn edit_guild_channel_positions(guild_id: u64, value: &Value)
- -> Result<()> {
- let body = serde_json::to_string(value)?;
-
- verify(
- 204,
- request!(
- Route::GuildsIdChannels(guild_id),
- patch(body),
- "/guilds/{}/channels",
- guild_id,
- ),
- )
-}
+ self.patch(Route::EditEmoji { guild_id, emoji_id }, Some(&map))
+ }
-/// Edits a [`Guild`]'s embed setting.
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-pub fn edit_guild_embed(guild_id: u64, map: &Value) -> Result<GuildEmbed> {
- let body = map.to_string();
- let response = request!(
- Route::GuildsIdEmbed(guild_id),
- patch(body),
- "/guilds/{}/embed",
- guild_id
- );
-
- serde_json::from_reader::<HyperResponse, GuildEmbed>(response)
- .map_err(From::from)
-}
+ /// Changes guild information.
+ pub fn edit_guild<F>(&self, guild_id: u64, f: F)
+ -> impl Future<Item = PartialGuild, Error = Error>
+ where F: FnOnce(EditGuild) -> EditGuild {
+ let guild = f(EditGuild::default()).0;
+ let map = Value::Object(serenity_utils::vecmap_to_json_map(guild));
-/// Does specific actions to a member.
-pub fn edit_member(guild_id: u64, user_id: u64, map: &JsonMap) -> Result<()> {
- let body = serde_json::to_string(map)?;
+ self.patch(Route::EditGuild { guild_id }, Some(&map))
+ }
- verify(
- 204,
- request!(
- Route::GuildsIdMembersId(guild_id),
- patch(body),
- "/guilds/{}/members/{}",
- guild_id,
- user_id
- ),
- )
-}
+ /// Edits the positions of a guild's channels.
+ pub fn edit_guild_channel_positions<It>(&self, guild_id: u64, channels: It)
+ -> impl Future<Item = (), Error = Error> where It: IntoIterator<Item = (ChannelId, u64)> {
+ let items = channels.into_iter().map(|(id, pos)| json!({
+ "id": id,
+ "position": pos,
+ })).collect();
-/// Edits a message by Id.
-///
-/// **Note**: Only the author of a message can modify it.
-pub fn edit_message(channel_id: u64, message_id: u64, map: &Value) -> Result<Message> {
- let body = map.to_string();
- let response = request!(
- Route::ChannelsIdMessagesId(LightMethod::Any, channel_id),
- patch(body),
- "/channels/{}/messages/{}",
- channel_id,
- message_id
- );
-
- serde_json::from_reader::<HyperResponse, Message>(response)
- .map_err(From::from)
-}
+ let map = Value::Array(items);
-/// Edits the current user's nickname for the provided [`Guild`] via its Id.
-///
-/// Pass `None` to reset the nickname.
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-pub fn edit_nickname(guild_id: u64, new_nickname: Option<&str>) -> Result<()> {
- let map = json!({ "nick": new_nickname });
- let body = map.to_string();
- let response = request!(
- Route::GuildsIdMembersMeNick(guild_id),
- patch(body),
- "/guilds/{}/members/@me/nick",
- guild_id
- );
-
- verify(200, response)
-}
+ self.patch(Route::EditGuildChannels { guild_id }, Some(&map))
+ }
-/// Edits the current user's profile settings.
-///
-/// Refer to Discord's [docs] for field information.
-///
-/// [docs]: https://discordapp.com/developers/docs/resources/user#modify-current-user
-pub fn edit_profile(map: &JsonMap) -> Result<CurrentUser> {
- let body = serde_json::to_string(map)?;
- let response = request!(Route::UsersMe, patch(body), "/users/@me");
+ /// Edits a [`Guild`]'s embed setting.
+ ///
+ /// [`Guild`]: ../model/guild/struct.Guild.html
+ // todo
+ pub fn edit_guild_embed(&self, guild_id: u64, map: &Value)
+ -> impl Future<Item = GuildEmbed, Error = Error> {
+ self.patch(Route::EditGuildEmbed { guild_id }, Some(map))
+ }
- let mut value = serde_json::from_reader::<HyperResponse, Value>(response)?;
+ /// Does specific actions to a member.
+ pub fn edit_member<F>(&self, guild_id: u64, user_id: u64, f: F)
+ -> impl Future<Item = (), Error = Error> where F: FnOnce(EditMember) -> EditMember {
+ let member = f(EditMember::default()).0;
+ let map = Value::Object(serenity_utils::vecmap_to_json_map(member));
- if let Some(map) = value.as_object_mut() {
- if !TOKEN.lock().starts_with("Bot ") {
- if let Some(Value::String(token)) = map.remove("token") {
- set_token(&token);
- }
- }
+ self.verify(Route::EditMember { guild_id, user_id }, Some(&map))
}
- serde_json::from_value::<CurrentUser>(value)
- .map_err(From::from)
-}
+ /// Edits a message by Id.
+ ///
+ /// **Note**: Only the author of a message can modify it.
+ pub fn edit_message<F: FnOnce(EditMessage) -> EditMessage>(
+ &self,
+ channel_id: u64,
+ message_id: u64,
+ f: F,
+ ) -> impl Future<Item = Message, Error = Error> {
+ let msg = f(EditMessage::default());
+ let map = Value::Object(serenity_utils::vecmap_to_json_map(msg.0));
+
+ self.patch(Route::EditMessage { channel_id, message_id }, Some(&map))
+ }
-/// Changes a role in a guild.
-pub fn edit_role(guild_id: u64, role_id: u64, map: &JsonMap) -> Result<Role> {
- let body = serde_json::to_string(map)?;
- let response = request!(
- Route::GuildsIdRolesId(guild_id),
- patch(body),
- "/guilds/{}/roles/{}",
- guild_id,
- role_id
- );
-
- serde_json::from_reader::<HyperResponse, Role>(response)
- .map_err(From::from)
-}
+ /// Edits the current user's nickname for the provided [`Guild`] via its Id.
+ ///
+ /// Pass `None` to reset the nickname.
+ ///
+ /// [`Guild`]: ../model/guild/struct.Guild.html
+ pub fn edit_nickname(&self, guild_id: u64, new_nickname: Option<&str>)
+ -> impl Future<Item = (), Error = Error> {
+ self.patch(Route::EditNickname { guild_id }, Some(&json!({
+ "nick": new_nickname,
+ })))
+ }
-/// Changes the position of a role in a guild.
-pub fn edit_role_position(guild_id: u64, role_id: u64, position: u64) -> Result<Vec<Role>> {
- let body = serde_json::to_string(&json!({
- "id": role_id,
- "position": position,
- }))?;
- let response = request!(
- Route::GuildsIdRolesId(guild_id),
- patch(body),
- "/guilds/{}/roles/{}",
- guild_id,
- role_id
- );
-
- serde_json::from_reader::<HyperResponse, Vec<Role>>(response)
- .map_err(From::from)
-}
+ /// Edits the current user's profile settings.
+ pub fn edit_profile(&self, map: &Value) -> impl Future<Item = CurrentUser, Error = Error> {
+ self.patch(Route::EditProfile, Some(map))
+ }
-/// Edits a the webhook with the given data.
-///
-/// The Value is a map with optional values of:
-///
-/// - **avatar**: base64-encoded 128x128 image for the webhook's default avatar
-/// (_optional_);
-/// - **name**: the name of the webhook, limited to between 2 and 100 characters
-/// long.
-///
-/// Note that, unlike with [`create_webhook`], _all_ values are optional.
-///
-/// This method requires authentication, whereas [`edit_webhook_with_token`]
-/// does not.
-///
-/// # Examples
-///
-/// Edit the image of a webhook given its Id and unique token:
-///
-/// ```rust,ignore
-/// extern crate serde_json;
-/// extern crate serenity;
-///
-/// use serde_json::builder::ObjectBuilder;
-/// use serenity::http;
-///
-/// let id = 245037420704169985;
-/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
-/// let image = serenity::utils::read_image("./webhook_img.png")
-/// .expect("Error reading image");
-/// let map = ObjectBuilder::new().insert("avatar", image).build();
-///
-/// let edited = http::edit_webhook_with_token(id, token, map)
-/// .expect("Error editing webhook");
-/// ```
-///
-/// [`create_webhook`]: fn.create_webhook.html
-/// [`edit_webhook_with_token`]: fn.edit_webhook_with_token.html
-// The tests are ignored, rather than no_run'd, due to rustdoc tests with
-// external crates being incredibly messy and misleading in the end user's view.
-pub fn edit_webhook(webhook_id: u64, map: &Value) -> Result<Webhook> {
- let body = map.to_string();
- let response = request!(
- Route::WebhooksId(webhook_id),
- patch(body),
- "/webhooks/{}",
- webhook_id,
- );
-
- serde_json::from_reader::<HyperResponse, Webhook>(response)
- .map_err(From::from)
-}
+ /// Changes a role in a guild.
+ pub fn edit_role<F>(&self, guild_id: u64, role_id: u64, f: F)
+ -> impl Future<Item = Role, Error = Error> where F: FnOnce(EditRole) -> EditRole {
+ let role = f(EditRole::default()).0;
+ let map = Value::Object(serenity_utils::vecmap_to_json_map(role));
-/// Edits the webhook with the given data.
-///
-/// Refer to the documentation for [`edit_webhook`] for more information.
-///
-/// This method does _not_ require authentication.
-///
-/// # Examples
-///
-/// Edit the name of a webhook given its Id and unique token:
-///
-/// ```rust,ignore
-/// extern crate serde_json;
-/// extern crate serenity;
-///
-/// use serde_json::builder::ObjectBuilder;
-/// use serenity::http;
-///
-/// let id = 245037420704169985;
-/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
-/// let map = ObjectBuilder::new().insert("name", "new name").build();
-///
-/// let edited = http::edit_webhook_with_token(id, token, map)
-/// .expect("Error editing webhook");
-/// ```
-///
-/// [`edit_webhook`]: fn.edit_webhook.html
-pub fn edit_webhook_with_token(webhook_id: u64, token: &str, map: &JsonMap) -> Result<Webhook> {
- let body = serde_json::to_string(map)?;
- let client = request_client!();
-
- let response = retry(|| {
- client
- .patch(&format!(api!("/webhooks/{}/{}"), webhook_id, token))
- .body(&body)
- }).map_err(Error::Hyper)?;
-
- serde_json::from_reader::<HyperResponse, Webhook>(response)
- .map_err(From::from)
-}
+ self.patch(Route::EditRole { guild_id, role_id }, Some(&map))
+ }
-/// Executes a webhook, posting a [`Message`] in the webhook's associated
-/// [`Channel`].
-///
-/// This method does _not_ require authentication.
-///
-/// Pass `true` to `wait` to wait for server confirmation of the message sending
-/// before receiving a response. From the [Discord docs]:
-///
-/// > waits for server confirmation of message send before response, and returns
-/// > the created message body (defaults to false; when false a message that is
-/// > not saved does not return an error)
-///
-/// The map can _optionally_ contain the following data:
-///
-/// - `avatar_url`: Override the default avatar of the webhook with a URL.
-/// - `tts`: Whether this is a text-to-speech message (defaults to `false`).
-/// - `username`: Override the default username of the webhook.
-///
-/// Additionally, _at least one_ of the following must be given:
-///
-/// - `content`: The content of the message.
-/// - `embeds`: An array of rich embeds.
-///
-/// **Note**: For embed objects, all fields are registered by Discord except for
-/// `height`, `provider`, `proxy_url`, `type` (it will always be `rich`),
-/// `video`, and `width`. The rest will be determined by Discord.
-///
-/// # Examples
-///
-/// Sending a webhook with message content of `test`:
-///
-/// ```rust,ignore
-/// extern crate serde_json;
-/// extern crate serenity;
-///
-/// use serde_json::builder::ObjectBuilder;
-/// use serenity::http;
-///
-/// let id = 245037420704169985;
-/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
-/// let map = ObjectBuilder::new().insert("content", "test").build();
-///
-/// let message = match http::execute_webhook(id, token, true, map) {
-/// Ok(Some(message)) => message,
-/// Ok(None) => {
-/// println!("Expected a webhook message");
-///
-/// return;
-/// },
-/// Err(why) => {
-/// println!("Error executing webhook: {:?}", why);
-///
-/// return;
-/// },
-/// };
-/// ```
-///
-/// [`Channel`]: ../model/channel/enum.Channel.html
-/// [`Message`]: ../model/channel/struct.Message.html
-/// [Discord docs]: https://discordapp.com/developers/docs/resources/webhook#querystring-params
-pub fn execute_webhook(webhook_id: u64,
- token: &str,
- wait: bool,
- map: &JsonMap)
- -> Result<Option<Message>> {
- let body = serde_json::to_string(map)?;
-
- let client = request_client!();
-
- let response = retry(|| {
- client
- .post(&format!(
- api!("/webhooks/{}/{}?wait={}"),
- webhook_id,
- token,
- wait
- ))
- .body(&body)
- .header(ContentType(
- Mime(TopLevel::Application, SubLevel::Json, vec![]),
- ))
- }).map_err(Error::Hyper)?;
-
- if response.status == StatusCode::NoContent {
- return Ok(None);
- }
-
- serde_json::from_reader::<HyperResponse, Message>(response)
- .map(Some)
- .map_err(From::from)
-}
+ /// Changes the position of a role in a guild.
+ pub fn edit_role_position(
+ &self,
+ guild_id: u64,
+ role_id: u64,
+ position: u64,
+ ) -> impl Future<Item = Vec<Role>, Error = Error> {
+ self.patch(Route::EditRole { guild_id, role_id }, Some(&json!({
+ "id": role_id,
+ "position": position,
+ })))
+ }
-/// Gets the active maintenances from Discord's Status API.
-///
-/// Does not require authentication.
-pub fn get_active_maintenances() -> Result<Vec<Maintenance>> {
- let client = request_client!();
+ /// Edits a the webhook with the given data.
+ ///
+ /// The Value is a map with optional values of:
+ ///
+ /// - **avatar**: base64-encoded 128x128 image for the webhook's default
+ /// avatar (_optional_);
+ /// - **name**: the name of the webhook, limited to between 2 and 100
+ /// characters long.
+ ///
+ /// Note that, unlike with [`create_webhook`], _all_ values are optional.
+ ///
+ /// This method requires authentication, whereas [`edit_webhook_with_token`]
+ /// does not.
+ ///
+ /// # Examples
+ ///
+ /// Edit the image of a webhook given its Id and unique token:
+ ///
+ /// ```rust,ignore
+ /// extern crate serde_json;
+ /// extern crate serenity;
+ ///
+ /// use serde_json::builder::ObjectBuilder;
+ /// use serenity::http;
+ ///
+ /// let id = 245037420704169985;
+ /// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
+ /// let image = serenity::utils::read_image("./webhook_img.png")
+ /// .expect("Error reading image");
+ /// let map = ObjectBuilder::new().insert("avatar", image).build();
+ ///
+ /// let edited = http::edit_webhook_with_token(id, token, map)
+ /// .expect("Error editing webhook");
+ /// ```
+ ///
+ /// [`create_webhook`]: fn.create_webhook.html
+ /// [`edit_webhook_with_token`]: fn.edit_webhook_with_token.html
+ // The tests are ignored, rather than no_run'd, due to rustdoc tests with
+ // external crates being incredibly messy and misleading in the end user's
+ // view.
+ pub fn edit_webhook(
+ &self,
+ webhook_id: u64,
+ name: Option<&str>,
+ avatar: Option<&str>,
+ ) -> impl Future<Item = Webhook, Error = Error> {
+ let map = json!({
+ "avatar": avatar,
+ "name": name,
+ });
+
+ self.patch(Route::EditWebhook { webhook_id }, Some(&map))
+ }
- let response = retry(|| {
- client.get(status!("/scheduled-maintenances/active.json"))
- })?;
+ /// Edits the webhook with the given data.
+ ///
+ /// Refer to the documentation for [`edit_webhook`] for more information.
+ ///
+ /// This method does _not_ require authentication.
+ ///
+ /// # Examples
+ ///
+ /// Edit the name of a webhook given its Id and unique token:
+ ///
+ /// ```rust,ignore
+ /// extern crate serde_json;
+ /// extern crate serenity;
+ ///
+ /// use serde_json::builder::ObjectBuilder;
+ /// use serenity::http;
+ ///
+ /// let id = 245037420704169985;
+ /// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
+ /// let map = ObjectBuilder::new().insert("name", "new name").build();
+ ///
+ /// let edited = http::edit_webhook_with_token(id, token, map)
+ /// .expect("Error editing webhook");
+ /// ```
+ ///
+ /// [`edit_webhook`]: fn.edit_webhook.html
+ pub fn edit_webhook_with_token(
+ &self,
+ webhook_id: u64,
+ token: &str,
+ name: Option<&str>,
+ avatar: Option<&str>,
+ ) -> impl Future<Item = Webhook, Error = Error> {
+ let map = json!({
+ "avatar": avatar,
+ "name": name,
+ });
+ let route = Route::EditWebhookWithToken {
+ token,
+ webhook_id,
+ };
- let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?;
+ self.patch(route, Some(&map))
+ }
- match map.remove("scheduled_maintenances") {
- Some(v) => serde_json::from_value::<Vec<Maintenance>>(v)
- .map_err(From::from),
- None => Ok(vec![]),
+ /// Executes a webhook, posting a [`Message`] in the webhook's associated
+ /// [`Channel`].
+ ///
+ /// This method does _not_ require authentication.
+ ///
+ /// Pass `true` to `wait` to wait for server confirmation of the message
+ /// sending before receiving a response. From the [Discord docs]:
+ ///
+ /// > waits for server confirmation of message send before response, and
+ /// > returns the created message body (defaults to false; when false a
+ /// > message that is not saved does not return an error)
+ ///
+ /// The map can _optionally_ contain the following data:
+ ///
+ /// - `avatar_url`: Override the default avatar of the webhook with a URL.
+ /// - `tts`: Whether this is a text-to-speech message (defaults to `false`).
+ /// - `username`: Override the default username of the webhook.
+ ///
+ /// Additionally, _at least one_ of the following must be given:
+ ///
+ /// - `content`: The content of the message.
+ /// - `embeds`: An array of rich embeds.
+ ///
+ /// **Note**: For embed objects, all fields are registered by Discord except
+ /// for `height`, `provider`, `proxy_url`, `type` (it will always be
+ /// `rich`), `video`, and `width`. The rest will be determined by Discord.
+ ///
+ /// # Examples
+ ///
+ /// Sending a webhook with message content of `test`:
+ ///
+ /// ```rust,ignore
+ /// extern crate serde_json;
+ /// extern crate serenity;
+ ///
+ /// use serde_json::builder::ObjectBuilder;
+ /// use serenity::http;
+ ///
+ /// let id = 245037420704169985;
+ /// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
+ /// let map = ObjectBuilder::new().insert("content", "test").build();
+ ///
+ /// let message = match http::execute_webhook(id, token, true, map) {
+ /// Ok(Some(message)) => message,
+ /// Ok(None) => {
+ /// println!("Expected a webhook message");
+ ///
+ /// return;
+ /// },
+ /// Err(why) => {
+ /// println!("Error executing webhook: {:?}", why);
+ ///
+ /// return;
+ /// },
+ /// };
+ /// ```
+ ///
+ /// [`Channel`]: ../model/channel/enum.Channel.html
+ /// [`Message`]: ../model/channel/struct.Message.html
+ /// [Discord docs]: https://discordapp.com/developers/docs/resources/webhook#querystring-params
+ pub fn execute_webhook<F: FnOnce(ExecuteWebhook) -> ExecuteWebhook>(
+ &self,
+ webhook_id: u64,
+ token: &str,
+ wait: bool,
+ f: F,
+ ) -> impl Future<Item = Option<Message>, Error = Error> {
+ let execution = f(ExecuteWebhook::default()).0;
+ let map = Value::Object(serenity_utils::vecmap_to_json_map(execution));
+
+ let route = Route::ExecuteWebhook {
+ token,
+ wait,
+ webhook_id,
+ };
+
+ if wait {
+ self.post(route, Some(&map))
+ } else {
+ Box::new(self.verify(route, Some(&map)).map(|_| None))
+ }
}
-}
-/// Gets all the users that are banned in specific guild.
-pub fn get_bans(guild_id: u64) -> Result<Vec<Ban>> {
- let response = request!(
- Route::GuildsIdBans(guild_id),
- get,
- "/guilds/{}/bans",
- guild_id
- );
-
- serde_json::from_reader::<HyperResponse, Vec<Ban>>(response)
- .map_err(From::from)
-}
+ /// Gets the active maintenances from Discord's Status API.
+ ///
+ /// Does not require authentication.
+ // pub fn get_active_maintenances() -> impl Future<Item = Vec<Maintenance>, Error = Error> {
+ // let client = request_client!();
+
+ // let response = retry(|| {
+ // client.get(status!("/scheduled-maintenances/active.json"))
+ // })?;
+
+ // let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?;
-/// Gets all audit logs in a specific guild.
-pub fn get_audit_logs(guild_id: u64,
- action_type: Option<u8>,
- user_id: Option<u64>,
- before: Option<u64>,
- limit: Option<u8>) -> Result<AuditLogs> {
- let mut params = Vec::with_capacity(4);
+ // match map.remove("scheduled_maintenances") {
+ // Some(v) => serde_json::from_value::<Vec<Maintenance>>(v)
+ // .map_err(From::from),
+ // None => Ok(vec![]),
+ // }
+ // }
- if let Some(action_type) = action_type {
- params.push(format!("action_type={}", action_type));
+ /// Gets all the users that are banned in specific guild.
+ pub fn get_bans(&self, guild_id: u64) -> impl Future<Item = Vec<Ban>, Error = Error> {
+ self.get(Route::GetBans { guild_id })
}
- if let Some(user_id) = user_id {
- params.push(format!("user_id={}", user_id));
+
+ /// Gets all audit logs in a specific guild.
+ pub fn get_audit_logs(
+ &self,
+ guild_id: u64,
+ action_type: Option<u8>,
+ user_id: Option<u64>,
+ before: Option<u64>,
+ limit: Option<u8>,
+ ) -> impl Future<Item = AuditLogs, Error = Error> {
+ self.get(Route::GetAuditLogs {
+ action_type,
+ before,
+ guild_id,
+ limit,
+ user_id,
+ })
}
- if let Some(before) = before {
- params.push(format!("before={}", before));
+
+ /// Gets current bot gateway.
+ pub fn get_bot_gateway(&self) -> impl Future<Item = BotGateway, Error = Error> {
+ self.get(Route::GetBotGateway)
}
- if let Some(limit) = limit {
- params.push(format!("limit={}", limit));
+
+ /// Gets all invites for a channel.
+ pub fn get_channel_invites(&self, channel_id: u64)
+ -> impl Future<Item = Vec<RichInvite>, Error = Error> {
+ self.get(Route::GetChannelInvites { channel_id })
}
- let mut query_string = params.join("&");
- if !query_string.is_empty() {
- query_string.insert(0, '?');
+ /// Retrieves the webhooks for the given [channel][`GuildChannel`]'s Id.
+ ///
+ /// This method requires authentication.
+ ///
+ /// # Examples
+ ///
+ /// Retrieve all of the webhooks owned by a channel:
+ ///
+ /// ```rust,no_run
+ /// use serenity::http;
+ ///
+ /// let channel_id = 81384788765712384;
+ ///
+ /// let webhooks = http::get_channel_webhooks(channel_id)
+ /// .expect("Error getting channel webhooks");
+ /// ```
+ ///
+ /// [`GuildChannel`]: ../model/channel/struct.GuildChannel.html
+ pub fn get_channel_webhooks(&self, channel_id: u64)
+ -> impl Future<Item = Vec<Webhook>, Error = Error> {
+ self.get(Route::GetChannelWebhooks { channel_id })
}
- let response = request!(
- Route::GuildsIdAuditLogs(guild_id),
- get,
- "/guilds/{}/audit-logs{}",
- guild_id,
- query_string
- );
+ /// Gets channel information.
+ pub fn get_channel(&self, channel_id: u64) -> impl Future<Item = Channel, Error = Error> {
+ self.get(Route::GetChannel { channel_id })
+ }
- serde_json::from_reader::<HyperResponse, AuditLogs>(response)
- .map_err(From::from)
-}
+ /// Gets all channels in a guild.
+ pub fn get_channels(&self, guild_id: u64)
+ -> impl Future<Item = Vec<GuildChannel>, Error = Error> {
+ self.get(Route::GetChannels { guild_id })
+ }
-/// Gets current bot gateway.
-pub fn get_bot_gateway() -> Result<BotGateway> {
- let response = request!(Route::GatewayBot, get, "/gateway/bot");
+ /// Gets information about the current application.
+ ///
+ /// **Note**: Only applications may use this endpoint.
+ pub fn get_current_application_info(&self)
+ -> impl Future<Item = CurrentApplicationInfo, Error = Error> {
+ self.get(Route::GetCurrentApplicationInfo)
+ }
- serde_json::from_reader::<HyperResponse, BotGateway>(response)
- .map_err(From::from)
-}
+ /// Gets information about the user we're connected with.
+ pub fn get_current_user(&self, ) -> impl Future<Item = CurrentUser, Error = Error> {
+ self.get(Route::GetCurrentUser)
+ }
-/// Gets all invites for a channel.
-pub fn get_channel_invites(channel_id: u64) -> Result<Vec<RichInvite>> {
- let response = request!(
- Route::ChannelsIdInvites(channel_id),
- get,
- "/channels/{}/invites",
- channel_id
- );
-
- serde_json::from_reader::<HyperResponse, Vec<RichInvite>>(response)
- .map_err(From::from)
-}
+ /// Gets current gateway.
+ pub fn get_gateway(&self) -> impl Future<Item = Gateway, Error = Error> {
+ self.get(Route::GetGateway)
+ }
-/// Retrieves the webhooks for the given [channel][`GuildChannel`]'s Id.
-///
-/// This method requires authentication.
-///
-/// # Examples
-///
-/// Retrieve all of the webhooks owned by a channel:
-///
-/// ```rust,no_run
-/// use serenity::http;
-///
-/// let channel_id = 81384788765712384;
-///
-/// let webhooks = http::get_channel_webhooks(channel_id)
-/// .expect("Error getting channel webhooks");
-/// ```
-///
-/// [`GuildChannel`]: ../model/channel/struct.GuildChannel.html
-pub fn get_channel_webhooks(channel_id: u64) -> Result<Vec<Webhook>> {
- let response = request!(
- Route::ChannelsIdWebhooks(channel_id),
- get,
- "/channels/{}/webhooks",
- channel_id
- );
-
- serde_json::from_reader::<HyperResponse, Vec<Webhook>>(response)
- .map_err(From::from)
-}
+ /// Gets guild information.
+ pub fn get_guild(&self, guild_id: u64) -> impl Future<Item = PartialGuild, Error = Error> {
+ self.get(Route::GetGuild { guild_id })
+ }
-/// Gets channel information.
-pub fn get_channel(channel_id: u64) -> Result<Channel> {
- let response = request!(
- Route::ChannelsId(channel_id),
- get,
- "/channels/{}",
- channel_id
- );
-
- serde_json::from_reader::<HyperResponse, Channel>(response)
- .map_err(From::from)
-}
+ /// Gets a guild embed information.
+ pub fn get_guild_embed(&self, guild_id: u64)
+ -> impl Future<Item = GuildEmbed, Error = Error> {
+ self.get(Route::GetGuildEmbed { guild_id })
+ }
-/// Gets all channels in a guild.
-pub fn get_channels(guild_id: u64) -> Result<Vec<GuildChannel>> {
- let response = request!(
- Route::ChannelsId(guild_id),
- get,
- "/guilds/{}/channels",
- guild_id
- );
-
- serde_json::from_reader::<HyperResponse, Vec<GuildChannel>>(response)
- .map_err(From::from)
-}
+ /// Gets integrations that a guild has.
+ pub fn get_guild_integrations(&self, guild_id: u64)
+ -> impl Future<Item = Vec<Integration>, Error = Error> {
+ self.get(Route::GetGuildIntegrations { guild_id })
+ }
-/// Gets information about the current application.
-///
-/// **Note**: Only applications may use this endpoint.
-pub fn get_current_application_info() -> Result<CurrentApplicationInfo> {
- let response = request!(Route::None, get, "/oauth2/applications/@me");
+ /// Gets all invites to a guild.
+ pub fn get_guild_invites(&self, guild_id: u64)
+ -> impl Future<Item = Vec<RichInvite>, Error = Error> {
+ self.get(Route::GetGuildInvites { guild_id })
+ }
- serde_json::from_reader::<HyperResponse, CurrentApplicationInfo>(response)
- .map_err(From::from)
-}
+ /// Gets the members of a guild. Optionally pass a `limit` and the Id of the
+ /// user to offset the result by.
+ pub fn get_guild_members(
+ &self,
+ guild_id: u64,
+ limit: Option<u64>,
+ after: Option<u64>
+ ) -> impl Future<Item = Vec<Member>, Error = Error> {
+ let done = self.get(Route::GetGuildMembers { after, guild_id, limit })
+ .and_then(move |mut v: Value| {
+ if let Some(values) = v.as_array_mut() {
+ let num = Value::Number(Number::from(guild_id));
+
+ for value in values {
+ if let Some(element) = value.as_object_mut() {
+ element.insert("guild_id".to_string(), num.clone());
+ }
+ }
+ }
+
+ serde_json::from_value::<Vec<Member>>(v).map_err(From::from)
+ });
+
+ Box::new(done)
+ }
-/// Gets information about the user we're connected with.
-pub fn get_current_user() -> Result<CurrentUser> {
- let response = request!(Route::UsersMe, get, "/users/@me");
+ /// Gets the amount of users that can be pruned.
+ pub fn get_guild_prune_count(&self, guild_id: u64, days: u16)
+ -> impl Future<Item = GuildPrune, Error = Error> {
+ self.get(Route::GetGuildPruneCount {
+ days: days as u64,
+ guild_id,
+ })
+ }
- serde_json::from_reader::<HyperResponse, CurrentUser>(response)
- .map_err(From::from)
-}
+ /// Gets regions that a guild can use. If a guild has
+ /// [`Feature::VipRegions`] enabled, then additional VIP-only regions are
+ /// returned.
+ ///
+ /// [`Feature::VipRegions`]: ../model/enum.Feature.html#variant.VipRegions
+ pub fn get_guild_regions(&self, guild_id: u64)
+ -> impl Future<Item = Vec<VoiceRegion>, Error = Error> {
+ self.get(Route::GetGuildRegions { guild_id })
+ }
-/// Gets current gateway.
-pub fn get_gateway() -> Result<Gateway> {
- let response = request!(Route::Gateway, get, "/gateway");
+ /// Retrieves a list of roles in a [`Guild`].
+ ///
+ /// [`Guild`]: ../model/guild/struct.Guild.html
+ pub fn get_guild_roles(&self, guild_id: u64)
+ -> impl Future<Item = Vec<Role>, Error = Error> {
+ self.get(Route::GetGuildRoles { guild_id })
+ }
- serde_json::from_reader::<HyperResponse, Gateway>(response)
- .map_err(From::from)
-}
+ /// Gets a guild's vanity URL if it has one.
+ pub fn get_guild_vanity_url(&self, guild_id: u64)
+ -> impl Future<Item = String, Error = Error> {
+ #[derive(Deserialize)]
+ struct GuildVanityUrl {
+ code: String,
+ }
-/// Gets guild information.
-pub fn get_guild(guild_id: u64) -> Result<PartialGuild> {
- let response = request!(Route::GuildsId(guild_id), get, "/guilds/{}", guild_id);
+ let done = self.get::<GuildVanityUrl>(
+ Route::GetGuildVanityUrl { guild_id },
+ ).map(|resp| {
+ resp.code
+ });
- serde_json::from_reader::<HyperResponse, PartialGuild>(response)
- .map_err(From::from)
-}
+ Box::new(done)
+ }
-/// Gets a guild embed information.
-pub fn get_guild_embed(guild_id: u64) -> Result<GuildEmbed> {
- let response = request!(
- Route::GuildsIdEmbed(guild_id),
- get,
- "/guilds/{}/embeds",
- guild_id
- );
-
- serde_json::from_reader::<HyperResponse, GuildEmbed>(response)
- .map_err(From::from)
-}
+ /// Retrieves the webhooks for the given [guild][`Guild`]'s Id.
+ ///
+ /// This method requires authentication.
+ ///
+ /// # Examples
+ ///
+ /// Retrieve all of the webhooks owned by a guild:
+ ///
+ /// ```rust,no_run
+ /// use serenity::http;
+ ///
+ /// let guild_id = 81384788765712384;
+ ///
+ /// let webhooks = http::get_guild_webhooks(guild_id)
+ /// .expect("Error getting guild webhooks");
+ /// ```
+ ///
+ /// [`Guild`]: ../model/guild/struct.Guild.html
+ pub fn get_guild_webhooks(&self, guild_id: u64)
+ -> impl Future<Item = Vec<Webhook>, Error = Error> {
+ self.get(Route::GetGuildWebhooks { guild_id })
+ }
-/// Gets integrations that a guild has.
-pub fn get_guild_integrations(guild_id: u64) -> Result<Vec<Integration>> {
- let response = request!(
- Route::GuildsIdIntegrations(guild_id),
- get,
- "/guilds/{}/integrations",
- guild_id
- );
-
- serde_json::from_reader::<HyperResponse, Vec<Integration>>(response)
- .map_err(From::from)
-}
+ /// Gets a paginated list of the current user's guilds.
+ ///
+ /// The `limit` has a maximum value of 100.
+ ///
+ /// [Discord's documentation][docs]
+ ///
+ /// # Examples
+ ///
+ /// Get the first 10 guilds after a certain guild's Id:
+ ///
+ /// ```rust,no_run
+ /// use serenity::http::{GuildPagination, get_guilds};
+ /// use serenity::model::GuildId;
+ ///
+ /// let guild_id = GuildId(81384788765712384);
+ ///
+ /// let guilds = get_guilds(&GuildPagination::After(guild_id), 10).unwrap();
+ /// ```
+ ///
+ /// [docs]: https://discordapp.com/developers/docs/resources/user#get-current-user-guilds
+ pub fn get_guilds(&self, target: &GuildPagination, limit: u64)
+ -> impl Future<Item = Vec<GuildInfo>, Error = Error> {
+ let (after, before) = match *target {
+ GuildPagination::After(v) => (Some(v.0), None),
+ GuildPagination::Before(v) => (None, Some(v.0)),
+ };
-/// Gets all invites to a guild.
-pub fn get_guild_invites(guild_id: u64) -> Result<Vec<RichInvite>> {
- let response = request!(
- Route::GuildsIdInvites(guild_id),
- get,
- "/guilds/{}/invites",
- guild_id
- );
-
- serde_json::from_reader::<HyperResponse, Vec<RichInvite>>(response)
- .map_err(From::from)
-}
+ self.get(Route::GetGuilds { after, before, limit })
+ }
-/// Gets a guild's vanity URL if it has one.
-pub fn get_guild_vanity_url(guild_id: u64) -> Result<String> {
- #[derive(Deserialize)]
- struct GuildVanityUrl {
- code: String,
+ /// Gets information about a specific invite.
+ pub fn get_invite<'a>(&self, code: &'a str, stats: bool)
+ -> Box<Future<Item = Invite, Error = Error> + 'a> {
+ self.get(Route::GetInvite { code, stats })
}
- let response = request!(
- Route::GuildsIdVanityUrl(guild_id),
- get,
- "/guilds/{}/vanity-url",
- guild_id
- );
+ /// Gets member of a guild.
+ pub fn get_member(&self, guild_id: u64, user_id: u64)
+ -> impl Future<Item = Member, Error = Error> {
+ let done = self.get::<Value>(Route::GetMember { guild_id, user_id })
+ .and_then(move |mut v| {
+ if let Some(map) = v.as_object_mut() {
+ map.insert("guild_id".to_string(), Value::Number(Number::from(guild_id)));
+ }
- serde_json::from_reader::<HyperResponse, GuildVanityUrl>(response)
- .map(|x| x.code)
- .map_err(From::from)
-}
+ serde_json::from_value(v).map_err(From::from)
+ });
-/// Gets the members of a guild. Optionally pass a `limit` and the Id of the
-/// user to offset the result by.
-pub fn get_guild_members(guild_id: u64,
- limit: Option<u64>,
- after: Option<u64>)
- -> Result<Vec<Member>> {
- let response = request!(
- Route::GuildsIdMembers(guild_id),
- get,
- "/guilds/{}/members?limit={}&after={}",
- guild_id,
- limit.unwrap_or(500),
- after.unwrap_or(0)
- );
-
- let mut v = serde_json::from_reader::<HyperResponse, Value>(response)?;
-
- if let Some(values) = v.as_array_mut() {
- let num = Value::Number(Number::from(guild_id));
-
- for value in values {
- if let Some(element) = value.as_object_mut() {
- element.insert("guild_id".to_string(), num.clone());
- }
- }
+ Box::new(done)
}
- serde_json::from_value::<Vec<Member>>(v).map_err(From::from)
-}
+ /// Gets a message by an Id, bots only.
+ pub fn get_message(&self, channel_id: u64, message_id: u64)
+ -> impl Future<Item = Message, Error = Error> {
+ self.get(Route::GetMessage { channel_id, message_id })
+ }
-/// Gets the amount of users that can be pruned.
-pub fn get_guild_prune_count(guild_id: u64, map: &Value) -> Result<GuildPrune> {
- let body = map.to_string();
- let response = request!(
- Route::GuildsIdPrune(guild_id),
- get(body),
- "/guilds/{}/prune",
- guild_id
- );
-
- serde_json::from_reader::<HyperResponse, GuildPrune>(response)
- .map_err(From::from)
-}
+ /// Gets X messages from a channel.
+ pub fn get_messages<'a, F: FnOnce(GetMessages) -> GetMessages>(
+ &'a self,
+ channel_id: u64,
+ f: F,
+ ) -> Box<Future<Item = Vec<Message>, Error = Error> + 'a> {
+ let mut map = f(GetMessages::default()).0;
-/// Gets regions that a guild can use. If a guild has the `VIP_REGIONS` feature
-/// enabled, then additional VIP-only regions are returned.
-pub fn get_guild_regions(guild_id: u64) -> Result<Vec<VoiceRegion>> {
- let response = request!(
- Route::GuildsIdRegions(guild_id),
- get,
- "/guilds/{}/regions",
- guild_id
- );
-
- serde_json::from_reader::<HyperResponse, Vec<VoiceRegion>>(response)
- .map_err(From::from)
-}
+ let limit = map.remove(&"limit").unwrap_or(50);
+ let mut query = format!("?limit={}", limit);
-/// Retrieves a list of roles in a [`Guild`].
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-pub fn get_guild_roles(guild_id: u64) -> Result<Vec<Role>> {
- let response = request!(
- Route::GuildsIdRoles(guild_id),
- get,
- "/guilds/{}/roles",
- guild_id
- );
-
- serde_json::from_reader::<HyperResponse, Vec<Role>>(response)
- .map_err(From::from)
-}
+ if let Some(after) = map.remove(&"after") {
+ ftry!(write!(query, "&after={}", after));
+ }
-/// Retrieves the webhooks for the given [guild][`Guild`]'s Id.
-///
-/// This method requires authentication.
-///
-/// # Examples
-///
-/// Retrieve all of the webhooks owned by a guild:
-///
-/// ```rust,no_run
-/// use serenity::http;
-///
-/// let guild_id = 81384788765712384;
-///
-/// let webhooks = http::get_guild_webhooks(guild_id)
-/// .expect("Error getting guild webhooks");
-/// ```
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-pub fn get_guild_webhooks(guild_id: u64) -> Result<Vec<Webhook>> {
- let response = request!(
- Route::GuildsIdWebhooks(guild_id),
- get,
- "/guilds/{}/webhooks",
- guild_id
- );
-
- serde_json::from_reader::<HyperResponse, Vec<Webhook>>(response)
- .map_err(From::from)
-}
+ if let Some(around) = map.remove(&"around") {
+ ftry!(write!(query, "&around={}", around));
+ }
-/// Gets a paginated list of the current user's guilds.
-///
-/// The `limit` has a maximum value of 100.
-///
-/// [Discord's documentation][docs]
-///
-/// # Examples
-///
-/// Get the first 10 guilds after a certain guild's Id:
-///
-/// ```rust,no_run
-/// use serenity::http::{GuildPagination, get_guilds};
-/// use serenity::model::id::GuildId;
-///
-/// let guild_id = GuildId(81384788765712384);
-///
-/// let guilds = get_guilds(&GuildPagination::After(guild_id), 10).unwrap();
-/// ```
-///
-/// [docs]: https://discordapp.com/developers/docs/resources/user#get-current-user-guilds
-pub fn get_guilds(target: &GuildPagination, limit: u64) -> Result<Vec<GuildInfo>> {
- let mut uri = format!("/users/@me/guilds?limit={}", limit);
+ if let Some(before) = map.remove(&"before") {
+ ftry!(write!(query, "&before={}", before));
+ }
- match *target {
- GuildPagination::After(id) => {
- write!(uri, "&after={}", id)?;
- },
- GuildPagination::Before(id) => {
- write!(uri, "&before={}", id)?;
- },
+ self.get(Route::GetMessages {
+ channel_id,
+ query,
+ })
}
- let response = request!(Route::UsersMeGuilds, get, "{}", uri);
-
- serde_json::from_reader::<HyperResponse, Vec<GuildInfo>>(response)
- .map_err(From::from)
-}
+ /// Gets all pins of a channel.
+ pub fn get_pins(&self, channel_id: u64) -> impl Future<Item = Vec<Message>, Error = Error> {
+ self.get(Route::GetPins { channel_id })
+ }
-/// Gets information about a specific invite.
-#[allow(unused_mut)]
-pub fn get_invite(code: &str, stats: bool) -> Result<Invite> {
- let mut invite = code;
+ /// Gets user Ids based on their reaction to a message.
+ pub fn get_reaction_users(
+ &self,
+ channel_id: u64,
+ message_id: u64,
+ reaction_type: &ReactionType,
+ limit: Option<u8>,
+ after: Option<u64>
+ ) -> impl Future<Item = Vec<User>, Error = Error> {
+ let reaction = utils::reaction_type_data(reaction_type);
+ self.get(Route::GetReactionUsers {
+ limit: limit.unwrap_or(50),
+ after,
+ channel_id,
+ message_id,
+ reaction,
+ })
+ }
- #[cfg(feature = "utils")]
- {
- invite = ::utils::parse_invite(invite);
+ // /// Gets the current unresolved incidents from Discord's Status API.
+ // ///
+ // /// Does not require authentication.
+ // pub fn get_unresolved_incidents(&self) -> impl Future<Item = Vec<Incident>, Error = Error> {
+ // let client = request_client!();
+
+ // let response = retry(|| client.get(status!("/incidents/unresolved.json")))?;
+
+ // let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?;
+
+ // match map.remove("incidents") {
+ // Some(v) => serde_json::from_value::<Vec<Incident>>(v)
+ // .map_err(From::from),
+ // None => Ok(vec![]),
+ // }
+ // }
+
+ // /// Gets the upcoming (planned) maintenances from Discord's Status API.
+ // ///
+ // /// Does not require authentication.
+ // pub fn get_upcoming_maintenances(&self) -> impl Future<Item = Vec<Maintenance>, Error = Error> {
+ // let client = request_client!();
+
+ // let response = retry(|| {
+ // client.get(status!("/scheduled-maintenances/upcoming.json"))
+ // })?;
+
+ // let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?;
+
+ // match map.remove("scheduled_maintenances") {
+ // Some(v) => serde_json::from_value::<Vec<Maintenance>>(v)
+ // .map_err(From::from),
+ // None => Ok(vec![]),
+ // }
+ // }
+
+ /// Gets a user by Id.
+ pub fn get_user(&self, user_id: u64) -> impl Future<Item = User, Error = Error> {
+ self.get(Route::GetUser { user_id })
}
- let mut uri = format!("/invites/{}", invite);
+ /// Gets our DM channels.
+ pub fn get_user_dm_channels(&self)
+ -> impl Future<Item = Vec<PrivateChannel>, Error = Error> {
+ self.get(Route::GetUserDmChannels)
+ }
- if stats {
- uri.push_str("?with_counts=true");
+ /// Gets all voice regions.
+ pub fn get_voice_regions(&self) -> impl Future<Item = Vec<VoiceRegion>, Error = Error> {
+ self.get(Route::GetVoiceRegions)
}
- let response = request!(Route::InvitesCode, get, "{}", uri);
+ /// Retrieves a webhook given its Id.
+ ///
+ /// This method requires authentication, whereas [`get_webhook_with_token`] does
+ /// not.
+ ///
+ /// # Examples
+ ///
+ /// Retrieve a webhook by Id:
+ ///
+ /// ```rust,no_run
+ /// use serenity::http;
+ ///
+ /// let id = 245037420704169985;
+ /// let webhook = http::get_webhook(id).expect("Error getting webhook");
+ /// ```
+ ///
+ /// [`get_webhook_with_token`]: fn.get_webhook_with_token.html
+ pub fn get_webhook(&self, webhook_id: u64) -> impl Future<Item = Webhook, Error = Error> {
+ self.get(Route::GetWebhook { webhook_id })
+ }
- serde_json::from_reader::<HyperResponse, Invite>(response)
- .map_err(From::from)
-}
+ /// Retrieves a webhook given its Id and unique token.
+ ///
+ /// This method does _not_ require authentication.
+ ///
+ /// # Examples
+ ///
+ /// Retrieve a webhook by Id and its unique token:
+ ///
+ /// ```rust,no_run
+ /// use serenity::http;
+ ///
+ /// let id = 245037420704169985;
+ /// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
+ ///
+ /// let webhook = http::get_webhook_with_token(id, token)
+ /// .expect("Error getting webhook");
+ /// ```
+ pub fn get_webhook_with_token<'a>(
+ &self,
+ webhook_id: u64,
+ token: &'a str,
+ ) -> Box<Future<Item = Webhook, Error = Error> + 'a> {
+ self.get(Route::GetWebhookWithToken { token, webhook_id })
+ }
-/// Gets member of a guild.
-pub fn get_member(guild_id: u64, user_id: u64) -> Result<Member> {
- let response = request!(
- Route::GuildsIdMembersId(guild_id),
- get,
- "/guilds/{}/members/{}",
- guild_id,
- user_id
- );
+ /// Kicks a member from a guild.
+ pub fn kick_member(&self, guild_id: u64, user_id: u64)
+ -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::KickMember { guild_id, user_id }, None)
+ }
- let mut v = serde_json::from_reader::<HyperResponse, Value>(response)?;
+ /// Leaves a group DM.
+ pub fn leave_group(&self, group_id: u64) -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::DeleteChannel {
+ channel_id: group_id,
+ }, None)
+ }
- if let Some(map) = v.as_object_mut() {
- map.insert("guild_id".to_string(), Value::Number(Number::from(guild_id)));
+ /// Leaves a guild.
+ pub fn leave_guild(&self, guild_id: u64) -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::LeaveGuild { guild_id }, None)
}
- serde_json::from_value::<Member>(v).map_err(From::from)
-}
+ /// Deletes a user from group DM.
+ pub fn remove_group_recipient(&self, group_id: u64, user_id: u64)
+ -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::RemoveGroupRecipient { group_id, user_id }, None)
+ }
-/// Gets a message by an Id, bots only.
-pub fn get_message(channel_id: u64, message_id: u64) -> Result<Message> {
- let response = request!(
- Route::ChannelsIdMessagesId(LightMethod::Any, channel_id),
- get,
- "/channels/{}/messages/{}",
- channel_id,
- message_id
- );
-
- serde_json::from_reader::<HyperResponse, Message>(response)
- .map_err(From::from)
-}
+ // /// Sends file(s) to a channel.
+ // ///
+ // /// # Errors
+ // ///
+ // /// Returns an
+ // /// [`HttpError::InvalidRequest(PayloadTooLarge)`][`HttpError::InvalidRequest`]
+ // /// if the file is too large to send.
+ // ///
+ // /// [`HttpError::InvalidRequest`]: enum.HttpError.html#variant.InvalidRequest
+ pub fn send_files<F, T, It>(
+ &self,
+ channel_id: u64,
+ files: It,
+ f: F,
+ ) -> Box<Future<Item = Message, Error = Error>>
+ where F: FnOnce(CreateMessage) -> CreateMessage,
+ T: Into<AttachmentType>,
+ It: IntoIterator<Item = T> {
+ let msg = f(CreateMessage::default());
+ let map = serenity_utils::vecmap_to_json_map(msg.0);
+
+ let uri = try_uri!(Path::channel_messages(channel_id).as_ref());
+ let mut form = Form::default();
+ let mut file_num = "0".to_string();
+
+ for file in files {
+ match file.into() {
+ AttachmentType::Bytes((mut bytes, filename)) => {
+ form.add_reader_file(
+ file_num.to_owned(),
+ Cursor::new(bytes),
+ filename,
+ );
+ },
+ AttachmentType::File((mut f, filename)) => {
+ form.add_reader_file(
+ file_num.to_owned(),
+ f,
+ filename,
+ );
+ },
+ }
-/// Gets X messages from a channel.
-pub fn get_messages(channel_id: u64, query: &str) -> Result<Vec<Message>> {
- let url = format!(api!("/channels/{}/messages{}"), channel_id, query);
- let client = request_client!();
+ unsafe {
+ let vec = file_num.as_mut_vec();
+ vec[0] += 1;
+ }
+ }
- let response = request(Route::ChannelsIdMessages(channel_id), || client.get(&url))?;
+ for (k, v) in map.into_iter() {
+ match v {
+ Value::Bool(false) => form.add_text(k, "false"),
+ Value::Bool(true) => form.add_text(k, "true"),
+ Value::Number(inner) => form.add_text(k, inner.to_string()),
+ Value::String(inner) => form.add_text(k, inner),
+ Value::Object(inner) => form.add_text(k, ftry!(serde_json::to_string(&inner))),
+ _ => continue,
+ }
+ }
- serde_json::from_reader::<HyperResponse, Vec<Message>>(response)
- .map_err(From::from)
-}
+ let mut request = Request::new(Method::Get, uri);
+ form.set_body(&mut request);
-/// Gets all pins of a channel.
-pub fn get_pins(channel_id: u64) -> Result<Vec<Message>> {
- let response = request!(
- Route::ChannelsIdPins(channel_id),
- get,
- "/channels/{}/pins",
- channel_id
- );
-
- serde_json::from_reader::<HyperResponse, Vec<Message>>(response)
- .map_err(From::from)
-}
+ let client = Rc::clone(&self.multiparter);
-/// Gets user Ids based on their reaction to a message. This endpoint is dumb.
-pub fn get_reaction_users(channel_id: u64,
- message_id: u64,
- reaction_type: &ReactionType,
- limit: u8,
- after: Option<u64>)
- -> Result<Vec<User>> {
- let mut uri = format!(
- "/channels/{}/messages/{}/reactions/{}?limit={}",
- channel_id,
- message_id,
- reaction_type.as_data(),
- limit
- );
-
- if let Some(user_id) = after {
- write!(uri, "&after={}", user_id)?;
- }
-
- let response = request!(
- Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id),
- get,
- "{}",
- uri
- );
-
- serde_json::from_reader::<HyperResponse, Vec<User>>(response)
- .map_err(From::from)
-}
+ let done = client.request(request)
+ .from_err()
+ .and_then(verify_status)
+ .and_then(|res| res.body().concat2().map_err(From::from))
+ .and_then(|body| serde_json::from_slice(&body).map_err(From::from));
-/// Gets the current unresolved incidents from Discord's Status API.
-///
-/// Does not require authentication.
-pub fn get_unresolved_incidents() -> Result<Vec<Incident>> {
- let client = request_client!();
+ Box::new(done)
+ }
- let response = retry(|| client.get(status!("/incidents/unresolved.json")))?;
+ /// Sends a message to a channel.
+ pub fn send_message<F>(&self, channel_id: u64, f: F) -> impl Future<Item = Message, Error = Error>
+ where F: FnOnce(CreateMessage) -> CreateMessage {
+ let msg = f(CreateMessage::default());
+ let map = Value::Object(serenity_utils::vecmap_to_json_map(msg.0));
- let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?;
+ self.post(Route::CreateMessage { channel_id }, Some(&map))
+ }
- match map.remove("incidents") {
- Some(v) => serde_json::from_value::<Vec<Incident>>(v)
- .map_err(From::from),
- None => Ok(vec![]),
+ /// Pins a message in a channel.
+ pub fn pin_message(&self, channel_id: u64, message_id: u64)
+ -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::PinMessage { channel_id, message_id }, None)
}
-}
-/// Gets the upcoming (planned) maintenances from Discord's Status API.
-///
-/// Does not require authentication.
-pub fn get_upcoming_maintenances() -> Result<Vec<Maintenance>> {
- let client = request_client!();
+ /// Unbans a user from a guild.
+ pub fn remove_ban(&self, guild_id: u64, user_id: u64)
+ -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::RemoveBan { guild_id, user_id }, None)
+ }
- let response = retry(|| {
- client.get(status!("/scheduled-maintenances/upcoming.json"))
- })?;
+ /// Deletes a single [`Role`] from a [`Member`] in a [`Guild`].
+ ///
+ /// **Note**: Requires the [Manage Roles] permission and respect of role
+ /// hierarchy.
+ ///
+ /// [`Guild`]: ../model/guild/struct.Guild.html
+ /// [`Member`]: ../model/guild/struct.Member.html
+ /// [`Role`]: ../model/guild/struct.Role.html
+ /// [Manage Roles]: ../model/permissions/constant.MANAGE_ROLES.html
+ pub fn remove_member_role(
+ &self,
+ guild_id: u64,
+ user_id: u64,
+ role_id: u64,
+ ) -> impl Future<Item = (), Error = Error> {
+ self.verify(
+ Route::RemoveMemberRole { guild_id, role_id, user_id },
+ None,
+ )
+ }
- let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?;
+ /// Starts removing some members from a guild based on the last time they've been online.
+ pub fn start_guild_prune(&self, guild_id: u64, days: u16)
+ -> impl Future<Item = GuildPrune, Error = Error> {
+ self.post(Route::StartGuildPrune {
+ days: days as u64,
+ guild_id,
+ }, None)
+ }
- match map.remove("scheduled_maintenances") {
- Some(v) => serde_json::from_value::<Vec<Maintenance>>(v)
- .map_err(From::from),
- None => Ok(vec![]),
+ /// Starts syncing an integration with a guild.
+ pub fn start_integration_sync(&self, guild_id: u64, integration_id: u64)
+ -> impl Future<Item = (), Error = Error> {
+ self.verify(
+ Route::StartIntegrationSync { guild_id, integration_id },
+ None,
+ )
}
-}
-/// Gets a user by Id.
-pub fn get_user(user_id: u64) -> Result<User> {
- let response = request!(Route::UsersId, get, "/users/{}", user_id);
+ /// Unpins a message from a channel.
+ pub fn unpin_message(&self, channel_id: u64, message_id: u64)
+ -> impl Future<Item = (), Error = Error> {
+ self.verify(Route::UnpinMessage { channel_id, message_id }, None)
+ }
- serde_json::from_reader::<HyperResponse, User>(response)
- .map_err(From::from)
-}
+ fn delete<'a, T: DeserializeOwned + 'static>(
+ &self,
+ route: Route<'a>,
+ map: Option<&Value>,
+ ) -> Box<Future<Item = T, Error = Error>> {
+ self.request(route, map)
+ }
-/// Gets our DM channels.
-pub fn get_user_dm_channels() -> Result<Vec<PrivateChannel>> {
- let response = request!(Route::UsersMeChannels, get, "/users/@me/channels");
+ fn get<'a, T: DeserializeOwned + 'static>(&self, route: Route<'a>)
+ -> Box<Future<Item = T, Error = Error> + 'a> {
+ self.request(route, None)
+ }
- serde_json::from_reader::<HyperResponse, Vec<PrivateChannel>>(response)
- .map_err(From::from)
-}
+ fn patch<'a, T: DeserializeOwned + 'static>(
+ &self,
+ route: Route<'a>,
+ map: Option<&Value>,
+ ) -> Box<Future<Item = T, Error = Error>> {
+ self.request(route, map)
+ }
-/// Gets all voice regions.
-pub fn get_voice_regions() -> Result<Vec<VoiceRegion>> {
- let response = request!(Route::VoiceRegions, get, "/voice/regions");
+ fn post<'a, T: DeserializeOwned + 'static>(
+ &self,
+ route: Route<'a>,
+ map: Option<&Value>,
+ ) -> Box<Future<Item = T, Error = Error>> {
+ self.request(route, map)
+ }
- serde_json::from_reader::<HyperResponse, Vec<VoiceRegion>>(response)
- .map_err(From::from)
-}
+ fn request<'a, T: DeserializeOwned + 'static>(
+ &self,
+ route: Route<'a>,
+ map: Option<&Value>,
+ ) -> Box<Future<Item = T, Error = Error>> {
+ let (method, path, url) = route.deconstruct();
-/// Retrieves a webhook given its Id.
-///
-/// This method requires authentication, whereas [`get_webhook_with_token`] does
-/// not.
-///
-/// # Examples
-///
-/// Retrieve a webhook by Id:
-///
-/// ```rust,no_run
-/// use serenity::http;
-///
-/// let id = 245037420704169985;
-/// let webhook = http::get_webhook(id).expect("Error getting webhook");
-/// ```
-///
-/// [`get_webhook_with_token`]: fn.get_webhook_with_token.html
-pub fn get_webhook(webhook_id: u64) -> Result<Webhook> {
- let response = request!(
- Route::WebhooksId(webhook_id),
- get,
- "/webhooks/{}",
- webhook_id,
- );
-
- serde_json::from_reader::<HyperResponse, Webhook>(response)
- .map_err(From::from)
-}
+ let built_uri = try_uri!(url.as_ref());
+ let mut request = Request::new(method.hyper_method(), built_uri);
-/// Retrieves a webhook given its Id and unique token.
-///
-/// This method does _not_ require authentication.
-///
-/// # Examples
-///
-/// Retrieve a webhook by Id and its unique token:
-///
-/// ```rust,no_run
-/// use serenity::http;
-///
-/// let id = 245037420704169985;
-/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
-///
-/// let webhook = http::get_webhook_with_token(id, token)
-/// .expect("Error getting webhook");
-/// ```
-pub fn get_webhook_with_token(webhook_id: u64, token: &str) -> Result<Webhook> {
- let client = request_client!();
-
- let response = retry(|| {
- client
- .get(&format!(api!("/webhooks/{}/{}"), webhook_id, token))
- }).map_err(Error::Hyper)?;
-
- serde_json::from_reader::<HyperResponse, Webhook>(response)
- .map_err(From::from)
-}
+ if let Some(value) = map {
+ request.set_body(ftry!(serde_json::to_string(value)));
+ }
-/// Kicks a member from a guild.
-pub fn kick_member(guild_id: u64, user_id: u64) -> Result<()> {
- verify(
- 204,
- request!(
- Route::GuildsIdMembersId(guild_id),
- delete,
- "/guilds/{}/members/{}",
- guild_id,
- user_id
- ),
- )
-}
+ {
+ let headers = request.headers_mut();
+ headers.set(Authorization(self.token()));
+ headers.set(ContentType::json());
+ }
-/// Leaves a group DM.
-pub fn leave_group(guild_id: u64) -> Result<Group> {
- let response = request!(Route::None, delete, "/channels/{}", guild_id);
+ let client = Rc::clone(&self.client);
- serde_json::from_reader::<HyperResponse, Group>(response)
- .map_err(From::from)
-}
+ Box::new(ftry!(self.ratelimiter.try_borrow_mut()).take(&path)
+ .and_then(move |_| client.request(request).map_err(From::from))
+ .from_err()
+ .and_then(verify_status)
+ .and_then(|res| res.body().concat2().map_err(From::from))
+ .and_then(|body| serde_json::from_slice(&body).map_err(From::from)))
+ }
-/// Leaves a guild.
-pub fn leave_guild(guild_id: u64) -> Result<()> {
- verify(
- 204,
- request!(
- Route::UsersMeGuildsId,
- delete,
- "/users/@me/guilds/{}",
- guild_id
- ),
- )
-}
+ fn verify<'a>(
+ &self,
+ route: Route<'a>,
+ map: Option<&Value>,
+ ) -> Box<Future<Item = (), Error = Error>> {
+ let (method, path, url) = route.deconstruct();
-/// Deletes a user from group DM.
-pub fn remove_group_recipient(group_id: u64, user_id: u64) -> Result<()> {
- verify(
- 204,
- request!(
- Route::None,
- delete,
- "/channels/{}/recipients/{}",
- group_id,
- user_id
- ),
- )
-}
+ let mut request = Request::new(
+ method.hyper_method(),
+ try_uri!(url.as_ref()),
+ );
-/// Sends file(s) to a channel.
-///
-/// # Errors
-///
-/// Returns an
-/// [`HttpError::InvalidRequest(PayloadTooLarge)`][`HttpError::InvalidRequest`]
-/// if the file is too large to send.
-///
-/// [`HttpError::InvalidRequest`]: enum.HttpError.html#variant.InvalidRequest
-pub fn send_files<'a, T, It: IntoIterator<Item=T>>(channel_id: u64, files: It, map: JsonMap) -> Result<Message>
- where T: Into<AttachmentType<'a>> {
- let uri = format!(api!("/channels/{}/messages"), channel_id);
- let url = match Url::parse(&uri) {
- Ok(url) => url,
- Err(_) => return Err(Error::Url(uri)),
- };
-
- let tc = NativeTlsClient::new()?;
- let connector = HttpsConnector::new(tc);
- let mut request = Request::with_connector(Method::Post, url, &connector)?;
- request
- .headers_mut()
- .set(header::Authorization(TOKEN.lock().clone()));
- request
- .headers_mut()
- .set(header::UserAgent(constants::USER_AGENT.to_string()));
-
- let mut request = Multipart::from_request(request)?;
- let mut file_num = "0".to_string();
-
- for file in files {
- match file.into() {
- AttachmentType::Bytes((mut bytes, filename)) => {
- request
- .write_stream(&file_num, &mut bytes, Some(filename), None)?;
- },
- AttachmentType::File((mut f, filename)) => {
- request
- .write_stream(&file_num, &mut f, Some(filename), None)?;
- },
- AttachmentType::Path(p) => {
- request.write_file(&file_num, &p)?;
- },
+ if let Some(value) = map {
+ request.set_body(ftry!(serde_json::to_string(value)));
}
- unsafe {
- let vec = file_num.as_mut_vec();
- vec[0] += 1;
+ {
+ let headers = request.headers_mut();
+ headers.set(Authorization(self.token()));
+ headers.set(ContentType::json());
}
- }
- for (k, v) in map {
- match v {
- Value::Bool(false) => request.write_text(&k, "false")?,
- Value::Bool(true) => request.write_text(&k, "true")?,
- Value::Number(inner) => request.write_text(&k, inner.to_string())?,
- Value::String(inner) => request.write_text(&k, inner)?,
- Value::Object(inner) => request.write_text(&k, serde_json::to_string(&inner)?)?,
- _ => continue,
- };
- }
-
- let response = request.send()?;
+ let client = Rc::clone(&self.client);
- if response.status.class() != StatusClass::Success {
- return Err(Error::Http(HttpError::UnsuccessfulRequest(response)));
+ Box::new(ftry!(self.ratelimiter.try_borrow_mut()).take(&path)
+ .and_then(move |_| client.request(request).map_err(From::from))
+ .map_err(From::from)
+ .and_then(verify_status)
+ .map(|_| ()))
}
- serde_json::from_reader::<HyperResponse, Message>(response)
- .map_err(From::from)
-}
+ fn token(&self) -> String {
+ let pointer = Rc::into_raw(Rc::clone(&self.token));
+ let token = unsafe {
+ (*pointer).clone()
+ };
-/// Sends a message to a channel.
-pub fn send_message(channel_id: u64, map: &Value) -> Result<Message> {
- let body = map.to_string();
- let response = request!(
- Route::ChannelsIdMessages(channel_id),
- post(body),
- "/channels/{}/messages",
- channel_id
- );
-
- serde_json::from_reader::<HyperResponse, Message>(response)
- .map_err(From::from)
-}
+ unsafe {
+ drop(Rc::from_raw(pointer));
+ }
-/// Pins a message in a channel.
-pub fn pin_message(channel_id: u64, message_id: u64) -> Result<()> {
- verify(
- 204,
- request!(
- Route::ChannelsIdPinsMessageId(channel_id),
- put,
- "/channels/{}/pins/{}",
- channel_id,
- message_id
- ),
- )
+ token
+ }
}
-/// Unbans a user from a guild.
-pub fn remove_ban(guild_id: u64, user_id: u64) -> Result<()> {
- verify(
- 204,
- request!(
- Route::GuildsIdBansUserId(guild_id),
- delete,
- "/guilds/{}/bans/{}",
- guild_id,
- user_id
- ),
- )
-}
-/// Deletes a single [`Role`] from a [`Member`] in a [`Guild`].
+/// Verifies the status of the response according to the method used to create
+/// the accompanying request.
///
-/// **Note**: Requires the [Manage Roles] permission and respect of role
-/// hierarchy.
+/// If the status code is correct (a 200 is expected and the response status is
+/// also 200), then the future resolves. Otherwise, a leaf future is returned
+/// with an error as the `Error` type.
///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-/// [`Member`]: ../model/guild/struct.Member.html
-/// [`Role`]: ../model/guild/struct.Role.html
-/// [Manage Roles]: ../model/permissions/constant.MANAGE_ROLES.html
-pub fn remove_member_role(guild_id: u64, user_id: u64, role_id: u64) -> Result<()> {
- verify(
- 204,
- request!(
- Route::GuildsIdMembersIdRolesId(guild_id),
- delete,
- "/guilds/{}/members/{}/roles/{}",
- guild_id,
- user_id,
- role_id
- ),
- )
-}
-
-/// Starts removing some members from a guild based on the last time they've been online.
-pub fn start_guild_prune(guild_id: u64, map: &Value) -> Result<GuildPrune> {
- let body = map.to_string();
- let response = request!(
- Route::GuildsIdPrune(guild_id),
- post(body),
- "/guilds/{}/prune",
- guild_id
- );
-
- serde_json::from_reader::<HyperResponse, GuildPrune>(response)
- .map_err(From::from)
-}
-
-/// Starts syncing an integration with a guild.
-pub fn start_integration_sync(guild_id: u64, integration_id: u64) -> Result<()> {
- verify(
- 204,
- request!(
- Route::GuildsIdIntegrationsIdSync(guild_id),
- post,
- "/guilds/{}/integrations/{}/sync",
- guild_id,
- integration_id
- ),
- )
-}
-
-/// Unpins a message from a channel.
-pub fn unpin_message(channel_id: u64, message_id: u64) -> Result<()> {
- verify(
- 204,
- request!(
- Route::ChannelsIdPinsMessageId(channel_id),
- delete,
- "/channels/{}/pins/{}",
- channel_id,
- message_id
- ),
- )
-}
-
-fn request<'a, F>(route: Route, f: F) -> Result<HyperResponse>
- where F: Fn() -> RequestBuilder<'a> {
- let response = ratelimiting::perform(route, || {
- f().header(header::Authorization(TOKEN.lock().clone()))
- .header(header::ContentType::json())
- })?;
-
- if response.status.class() == StatusClass::Success {
- Ok(response)
+/// # Errors
+///
+/// Returns [`Error::InvalidRequest`] if the response status code is unexpected.
+///
+/// [`Error::InvalidRequest`]: enum.Error.html#variant.InvalidRequest
+fn verify_status(response: Response) ->
+ Box<Future<Item = Response, Error = Error>> {
+ if response.status().is_success() {
+ Box::new(future::ok(response))
} else {
- Err(Error::Http(HttpError::UnsuccessfulRequest(response)))
- }
-}
-
-pub(crate) fn retry<'a, F>(f: F) -> HyperResult<HyperResponse>
- where F: Fn() -> RequestBuilder<'a> {
- let req = || {
- f().header(header::UserAgent(constants::USER_AGENT.to_string()))
- .send()
- };
-
- match req() {
- Err(HyperError::Io(ref io)) if io.kind() == IoErrorKind::ConnectionAborted => req(),
- other => other,
+ Box::new(future::err(Error::Http(HttpError::InvalidRequest(response))))
}
}
-fn verify(expected: u16, response: HyperResponse) -> Result<()> {
- if response.status.to_u16() == expected {
- return Ok(());
- }
-
- debug!("Expected {}, got {}", expected, response.status);
- trace!("Unsuccessful response: {:?}", response);
-
- Err(Error::Http(HttpError::UnsuccessfulRequest(response)))
-}
-
-/// Enum that allows a user to pass a `Path` or a `File` type to `send_files`
-#[derive(Clone, Debug)]
-pub enum AttachmentType<'a> {
+/// Enum that allows a user to pass a `Path` or a `File` type to `send_files`.
+#[derive(Debug)]
+pub enum AttachmentType {
/// Indicates that the `AttachmentType` is a byte slice with a filename.
- Bytes((&'a [u8], &'a str)),
+ Bytes((Vec<u8>, String)),
/// Indicates that the `AttachmentType` is a `File`
- File((&'a File, &'a str)),
- /// Indicates that the `AttachmentType` is a `Path`
- Path(&'a Path),
+ File((File, String)),
}
-impl<'a> From<(&'a [u8], &'a str)> for AttachmentType<'a> {
- fn from(params: (&'a [u8], &'a str)) -> AttachmentType { AttachmentType::Bytes(params) }
-}
-
-impl<'a> From<&'a str> for AttachmentType<'a> {
- fn from(s: &'a str) -> AttachmentType { AttachmentType::Path(Path::new(s)) }
-}
-
-impl<'a> From<&'a Path> for AttachmentType<'a> {
- fn from(path: &'a Path) -> AttachmentType {
- AttachmentType::Path(path)
+impl From<(Vec<u8>, String)> for AttachmentType {
+ fn from(params: (Vec<u8>, String)) -> AttachmentType {
+ AttachmentType::Bytes(params)
}
}
-impl<'a> From<&'a PathBuf> for AttachmentType<'a> {
- fn from(pathbuf: &'a PathBuf) -> AttachmentType { AttachmentType::Path(pathbuf.as_path()) }
-}
-
-impl<'a> From<(&'a File, &'a str)> for AttachmentType<'a> {
- fn from(f: (&'a File, &'a str)) -> AttachmentType<'a> { AttachmentType::File((f.0, f.1)) }
+impl From<(File, String)> for AttachmentType {
+ fn from(f: (File, String)) -> AttachmentType {
+ AttachmentType::File((f.0, f.1))
+ }
}
/// Representation of the method of a query to send for the [`get_guilds`]
diff --git a/src/http/ratelimiting.rs b/src/http/ratelimiting.rs
index 64bdf68..9f41f24 100644
--- a/src/http/ratelimiting.rs
+++ b/src/http/ratelimiting.rs
@@ -38,41 +38,156 @@
//! differentiating between different ratelimits.
//!
//! [Taken from]: https://discordapp.com/developers/docs/topics/rate-limits#rate-limits
-#![allow(zero_ptr)]
+#![cfg_attr(feature = "cargo-clippy", allow(zero_ptr))]
use chrono::{DateTime, Utc};
-use hyper::client::{RequestBuilder, Response};
+use futures::sync::oneshot::{self, Receiver, Sender};
+use futures::{Future, future};
+use hyper::client::{Response};
use hyper::header::Headers;
-use hyper::status::StatusCode;
-use internal::prelude::*;
-use parking_lot::Mutex;
-use std::{
- collections::HashMap,
- sync::Arc,
- time::Duration,
- str,
- thread,
- i64
-};
-use super::{HttpError, LightMethod};
-
-/// The calculated offset of the time difference between Discord and the client
-/// in seconds.
+use hyper::StatusCode;
+use std::cell::RefCell;
+use std::collections::{HashMap, VecDeque};
+use std::error::Error as StdError;
+use std::fmt::{Display, Formatter, Result as FmtResult};
+use std::rc::Rc;
+use std::time::Duration;
+use std::{i64, str, u8};
+use super::{Error, Path, Result};
+use tokio_core::reactor::Handle;
+use tokio_timer::Timer;
+
+#[derive(Debug)]
+pub enum RateLimitError {
+ /// When the decoding of a header could not be properly decoded as UTF-8.
+ DecodingUtf8,
+ /// When the decoding of a header could not be properly decoded as an `i64`.
+ DecodingInteger,
+}
+
+impl Display for RateLimitError {
+ fn fmt(&self, f: &mut Formatter) -> FmtResult {
+ f.write_str(self.description())
+ }
+}
+
+impl StdError for RateLimitError {
+ fn description(&self) -> &str {
+ use self::RateLimitError::*;
+
+ match *self {
+ DecodingInteger => "Error decoding a header into an i64",
+ DecodingUtf8 => "Error decoding a header from UTF-8",
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub enum RateLimit {
+ Global(i64),
+ NotReached(RateLimitHeaders),
+ Reached(i64),
+}
+
+impl RateLimit {
+ fn from_headers(headers: &Headers) -> Result<Self> {
+ if headers.get_raw("x-ratelimit-global").is_some() {
+ if let Some(retry_after) = parse_header(headers, "retry-after")? {
+ debug!("Global ratelimited for {}ms", retry_after);
+
+ return Ok(RateLimit::Global(retry_after));
+ }
+
+ warn!("Globally ratelimited with no retry-after? Skipping...");
+ }
+
+ if let Some(retry_after) = parse_header(headers, "retry-after")? {
+ return Ok(RateLimit::Reached(retry_after));
+ }
+
+ let limit = parse_header(headers, "x-ratelimit-limit")?.map(|x| x as u8);
+ let remaining = parse_header(headers, "x-ratelimit-remaining")?.map(|x| x as u8);
+ let reset = parse_header(headers, "x-ratelimit-remaining")?;
+
+ Ok(RateLimit::NotReached(RateLimitHeaders {
+ limit,
+ remaining,
+ reset,
+ }))
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct RateLimitHeaders {
+ pub limit: Option<u8>,
+ pub remaining: Option<u8>,
+ pub reset: Option<i64>,
+}
+
+/// A set of data containing information about the ratelimits for a particular
+/// [`Route`], which is stored in the [`ROUTES`] mutex.
///
-/// This does not have millisecond precision as calculating that isn't
-/// realistic.
+/// See the [Discord docs] on ratelimits for more information.
///
-/// This is used in ratelimiting to help determine how long to wait for
-/// pre-emptive ratelimits. For example, if the client is 2 seconds ahead, then
-/// the client would think the ratelimit is over 2 seconds before it actually is
-/// and would then send off queued requests. Using an offset, we can know that
-/// there's actually still 2 seconds left (+/- some milliseconds).
+/// **Note**: You should _not_ mutate any of the fields, as this can cause 429s.
///
-/// This isn't a definitive solution to fix all problems, but it can help with
-/// some precision gains.
-static mut OFFSET: Option<i64> = None;
+/// [`ROUTES`]: struct.ROUTES.html
+/// [`Route`]: enum.Route.html
+/// [Discord docs]: https://discordapp.com/developers/docs/topics/rate-limits
+// todo: impl Debug
+#[derive(Debug)]
+pub struct Bucket {
+ /// The total number of requests that can be made in a period of time.
+ pub limit: i64,
+ /// A queue of requests that were held back due to a pre-emptive ratelimit.
+ pub queue: VecDeque<Sender<()>>,
+ /// The number of requests remaining in the period of time.
+ pub remaining: i64,
+ /// When the interval resets and the [`limit`] resets to the value of
+ /// [`remaining`].
+ ///
+ /// [`limit`]: #structfield.limit
+ /// [`remaining`]: #structfield.remaining
+ pub reset: i64,
+ /// Whether the bucket has a timeout in the background to release (part of)
+ /// the queue.
+ pub timeout: bool,
+}
+
+impl Default for Bucket {
+ fn default() -> Self {
+ Self {
+ limit: i64::MAX,
+ queue: VecDeque::new(),
+ remaining: i64::MAX,
+ reset: i64::MAX,
+ timeout: false,
+ }
+ }
+}
+
+impl Bucket {
+ fn take(&mut self) -> Option<Receiver<()>> {
+ if self.reset == 0 {
+ let (tx, rx) = oneshot::channel();
+
+ self.queue.push_back(tx);
+
+ Some(rx)
+ } else {
+ None
+ }
+ }
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct Global {
+ pub blocked: bool,
+ pub queue: Rc<RefCell<VecDeque<Receiver<()>>>>,
+}
-lazy_static! {
+#[derive(Clone, Debug)]
+pub struct RateLimiter {
/// The global mutex is a mutex unlocked and then immediately re-locked
/// prior to every request, to abide by Discord's global ratelimit.
///
@@ -87,7 +202,24 @@ lazy_static! {
/// The only reason that you would need to use the global mutex is to
/// block requests yourself. This has the side-effect of potentially
/// blocking many of your event handlers or framework commands.
- pub static ref GLOBAL: Arc<Mutex<()>> = Arc::new(Mutex::new(()));
+ pub global: Rc<RefCell<Global>>,
+ /// A handle to the core that this is running on.
+ pub handle: Handle,
+ /// The calculated offset of the time difference between Discord and the client
+ /// in seconds.
+ ///
+ /// This does not have millisecond precision as calculating that isn't
+ /// realistic.
+ ///
+ /// This is used in ratelimiting to help determine how long to wait for
+ /// pre-emptive ratelimits. For example, if the client is 2 seconds ahead, then
+ /// the client would think the ratelimit is over 2 seconds before it actually is
+ /// and would then send off queued requests. Using an offset, we can know that
+ /// there's actually still 2 seconds left (+/- some milliseconds).
+ ///
+ /// This isn't a definitive solution to fix all problems, but it can help with
+ /// some precision gains.
+ offset: Option<i64>,
/// The routes mutex is a HashMap of each [`Route`] and their respective
/// ratelimit information.
///
@@ -108,269 +240,122 @@ lazy_static! {
///
/// [`RateLimit`]: struct.RateLimit.html
/// [`Route`]: enum.Route.html
- pub static ref ROUTES: Arc<Mutex<HashMap<Route, Arc<Mutex<RateLimit>>>>> = {
- Arc::new(Mutex::new(HashMap::default()))
- };
+ pub routes: Rc<RefCell<HashMap<Path, Bucket>>>,
}
-/// A representation of all routes registered within the library. These are safe
-/// and memory-efficient representations of each path that functions exist for
-/// in the [`http`] module.
-///
-/// [`http`]: ../index.html
-#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
-pub enum Route {
- /// Route for the `/channels/:channel_id` path.
- ///
- /// The data is the relevant [`ChannelId`].
- ///
- /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
- ChannelsId(u64),
- /// Route for the `/channels/:channel_id/invites` path.
- ///
- /// The data is the relevant [`ChannelId`].
- ///
- /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
- ChannelsIdInvites(u64),
- /// Route for the `/channels/:channel_id/messages` path.
- ///
- /// The data is the relevant [`ChannelId`].
- ///
- /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
- ChannelsIdMessages(u64),
- /// Route for the `/channels/:channel_id/messages/bulk-delete` path.
- ///
- /// The data is the relevant [`ChannelId`].
- ///
- /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
- ChannelsIdMessagesBulkDelete(u64),
- /// Route for the `/channels/:channel_id/messages/:message_id` path.
- ///
- /// The data is the relevant [`ChannelId`].
- ///
- /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
- // This route is a unique case. The ratelimit for message _deletions_ is
- // different than the overall route ratelimit.
- //
- // Refer to the docs on [Rate Limits] in the yellow warning section.
- //
- // Additionally, this needs to be a `LightMethod` from the parent module
- // and _not_ a `hyper` `Method` due to `hyper`'s not deriving `Copy`.
- //
- // [Rate Limits]: https://discordapp.com/developers/docs/topics/rate-limits
- ChannelsIdMessagesId(LightMethod, u64),
- /// Route for the `/channels/:channel_id/messages/:message_id/ack` path.
- ///
- /// The data is the relevant [`ChannelId`].
- ///
- /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
- ChannelsIdMessagesIdAck(u64),
- /// Route for the `/channels/:channel_id/messages/:message_id/reactions`
- /// path.
- ///
- /// The data is the relevant [`ChannelId`].
- ///
- /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
- ChannelsIdMessagesIdReactions(u64),
- /// Route for the
- /// `/channels/:channel_id/messages/:message_id/reactions/:reaction/@me`
- /// path.
- ///
- /// The data is the relevant [`ChannelId`].
- ///
- /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
- ChannelsIdMessagesIdReactionsUserIdType(u64),
- /// Route for the `/channels/:channel_id/permissions/:target_id` path.
- ///
- /// The data is the relevant [`ChannelId`].
- ///
- /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
- ChannelsIdPermissionsOverwriteId(u64),
- /// Route for the `/channels/:channel_id/pins` path.
- ///
- /// The data is the relevant [`ChannelId`].
- ///
- /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
- ChannelsIdPins(u64),
- /// Route for the `/channels/:channel_id/pins/:message_id` path.
- ///
- /// The data is the relevant [`ChannelId`].
- ///
- /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
- ChannelsIdPinsMessageId(u64),
- /// Route for the `/channels/:channel_id/typing` path.
- ///
- /// The data is the relevant [`ChannelId`].
- ///
- /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
- ChannelsIdTyping(u64),
- /// Route for the `/channels/:channel_id/webhooks` path.
- ///
- /// The data is the relevant [`ChannelId`].
- ///
- /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
- ChannelsIdWebhooks(u64),
- /// Route for the `/gateway` path.
- Gateway,
- /// Route for the `/gateway/bot` path.
- GatewayBot,
- /// Route for the `/guilds` path.
- Guilds,
- /// Route for the `/guilds/:guild_id` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsId(u64),
- /// Route for the `/guilds/:guild_id/bans` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdBans(u64),
- /// Route for the `/guilds/:guild_id/audit-logs` path.
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdAuditLogs(u64),
- /// Route for the `/guilds/:guild_id/bans/:user_id` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdBansUserId(u64),
- /// Route for the `/guilds/:guild_id/channels/:channel_id` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdChannels(u64),
- /// Route for the `/guilds/:guild_id/embed` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdEmbed(u64),
- /// Route for the `/guilds/:guild_id/emojis` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdEmojis(u64),
- /// Route for the `/guilds/:guild_id/emojis/:emoji_id` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdEmojisId(u64),
- /// Route for the `/guilds/:guild_id/integrations` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdIntegrations(u64),
- /// Route for the `/guilds/:guild_id/integrations/:integration_id` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdIntegrationsId(u64),
- /// Route for the `/guilds/:guild_id/integrations/:integration_id/sync`
- /// path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdIntegrationsIdSync(u64),
- /// Route for the `/guilds/:guild_id/invites` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdInvites(u64),
- /// Route for the `/guilds/:guild_id/members` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdMembers(u64),
- /// Route for the `/guilds/:guild_id/members/:user_id` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdMembersId(u64),
- /// Route for the `/guilds/:guild_id/members/:user_id/roles/:role_id` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdMembersIdRolesId(u64),
- /// Route for the `/guilds/:guild_id/members/@me/nick` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdMembersMeNick(u64),
- /// Route for the `/guilds/:guild_id/prune` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdPrune(u64),
- /// Route for the `/guilds/:guild_id/regions` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdRegions(u64),
- /// Route for the `/guilds/:guild_id/roles` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdRoles(u64),
- /// Route for the `/guilds/:guild_id/roles/:role_id` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdRolesId(u64),
- /// Route for the `/guilds/:guild_id/vanity-url` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdVanityUrl(u64),
- /// Route for the `/guilds/:guild_id/webhooks` path.
- ///
- /// The data is the relevant [`GuildId`].
- ///
- /// [`GuildId`]: struct.GuildId.html
- GuildsIdWebhooks(u64),
- /// Route for the `/invites/:code` path.
- InvitesCode,
- /// Route for the `/users/:user_id` path.
- UsersId,
- /// Route for the `/users/@me` path.
- UsersMe,
- /// Route for the `/users/@me/channels` path.
- UsersMeChannels,
- /// Route for the `/users/@me/guilds` path.
- UsersMeGuilds,
- /// Route for the `/users/@me/guilds/:guild_id` path.
- UsersMeGuildsId,
- /// Route for the `/voice/regions` path.
- VoiceRegions,
- /// Route for the `/webhooks/:webhook_id` path.
- WebhooksId(u64),
- /// Route where no ratelimit headers are in place.
- ///
- /// This is a special case, in that if the route is `None` then pre- and
- /// post-hooks are not executed.
- None,
+impl RateLimiter {
+ pub fn new(handle: Handle) -> Self {
+ Self {
+ global: Rc::new(RefCell::new(Global::default())),
+ offset: None,
+ routes: Rc::new(RefCell::new(HashMap::new())),
+ handle,
+ }
+ }
+
+ pub fn take(&mut self, route: &Path)
+ -> Box<Future<Item = (), Error = Error>> {
+ // TODO: handle global
+ let mut routes = self.routes.borrow_mut();
+ let bucket = routes.entry(*route).or_insert_with(Default::default);
+ let take = bucket.take();
+
+ match take {
+ Some(rx) => {
+ if !bucket.timeout {
+ let reset_ms = (bucket.reset * 1000) as u64;
+ let now = Utc::now();
+ let now_millis = now.timestamp_subsec_millis() as i64;
+ let now_ms = (now.timestamp() * 1000) + now_millis;
+ let wait_ms = reset_ms.saturating_sub(now_ms as u64);
+ let duration = Duration::from_millis(wait_ms as u64);
+
+ let done = Timer::default()
+ .sleep(duration)
+ .map(|_| {
+ ()
+ }).map_err(|why| {
+ warn!("Err with pre-ratelimit sleep: {:?}", why);
+
+ ()
+ });
+
+ self.handle.spawn(done);
+ }
+
+ Box::new(rx.from_err())
+ },
+ None => Box::new(future::ok(())),
+ }
+ }
+
+ pub fn handle<'a>(&'a mut self, route: &'a Path, response: &'a Response)
+ -> Result<Option<Box<Future<Item = (), Error = ()>>>> {
+ let mut routes = self.routes.borrow_mut();
+ let bucket = routes.entry(*route).or_insert_with(Default::default);
+
+ if response.status() != StatusCode::TooManyRequests {
+ return Ok(None);
+ }
+
+ Ok(match RateLimit::from_headers(&response.headers())? {
+ RateLimit::Global(millis) => {
+ debug!("Globally ratelimited for {:?}ms", millis);
+
+ self.global.borrow_mut().blocked = true;
+ let global = Rc::clone(&self.global);
+
+ let done = Timer::default()
+ .sleep(Duration::from_millis(millis as u64))
+ .map(move |_| {
+ let mut global = global.borrow_mut();
+ global.blocked = false;
+ })
+ .map_err(|why| {
+ warn!("Err with global ratelimit timer: {:?}", why);
+
+ ()
+ });
+
+ Some(Box::new(done))
+ },
+ RateLimit::NotReached(headers) => {
+ let RateLimitHeaders { limit, remaining, reset } = headers;
+
+ if let Some(reset) = reset {
+ if reset != bucket.reset {
+ bucket.reset = reset;
+
+ if let Some(limit) = limit {
+ bucket.limit = limit as i64;
+ }
+
+ if let Some(remaining) = remaining {
+ bucket.remaining = remaining as i64;
+ }
+ }
+ }
+
+ None
+ },
+ RateLimit::Reached(millis) => {
+ debug!("Ratelimited on route {:?} for {:?}ms", route, millis);
+
+ let done = Timer::default()
+ .sleep(Duration::from_millis(millis as u64))
+ .map_err(|why| {
+ warn!("Err with ratelimited timer: {:?}", why);
+
+ ()
+ });
+
+ Some(Box::new(done))
+ },
+ })
+ }
}
+/*
pub(crate) fn perform<'a, F>(route: Route, f: F) -> Result<Response>
where F: Fn() -> RequestBuilder<'a> {
loop {
@@ -538,8 +523,11 @@ impl RateLimit {
})
}
}
+*/
-fn calculate_offset(header: Option<&[Vec<u8>]>) {
+#[allow(dead_code)]
+// todo
+fn calculate_offset(header: Option<&[Vec<u8>]>) -> Option<i64> {
// Get the current time as soon as possible.
let now = Utc::now().timestamp();
@@ -560,23 +548,32 @@ fn calculate_offset(header: Option<&[Vec<u8>]>) {
let diff = offset - now;
- unsafe {
- OFFSET = Some(diff);
+ debug!("[ratelimiting] Set the ratelimit offset to {}", diff);
- debug!("[ratelimiting] Set the ratelimit offset to {}", diff);
- }
+ return Some(diff);
}
}
+
+ None
}
-fn parse_header(headers: &Headers, header: &str) -> Result<Option<i64>> {
- headers.get_raw(header).map_or(Ok(None), |header| {
+fn parse_header(headers: &Headers, header_raw: &str) -> Result<Option<i64>> {
+ headers.get_raw(header_raw).map_or(Ok(None), |header| {
str::from_utf8(&header[0])
- .map_err(|_| Error::Http(HttpError::RateLimitUtf8))
+ .map_err(|why| {
+ warn!("Error parsing {} as utf8: {:?}", header_raw, why);
+
+ RateLimitError::DecodingUtf8
+ })
.and_then(|v| {
v.parse::<i64>()
.map(Some)
- .map_err(|_| Error::Http(HttpError::RateLimitI64))
+ .map_err(|why| {
+ warn!("Error parsing {}: {:?} to i64", header_raw, why);
+
+ RateLimitError::DecodingInteger
+ })
})
+ .map_err(From::from)
})
}
diff --git a/src/http/routing.rs b/src/http/routing.rs
new file mode 100644
index 0000000..53b7875
--- /dev/null
+++ b/src/http/routing.rs
@@ -0,0 +1,1430 @@
+use std::borrow::Cow;
+use std::fmt::{Display, Formatter, Result as FmtResult, Write};
+use super::LightMethod;
+
+/// A representation of all path registered within the library. These are safe
+/// and memory-efficient representations of each path that request functions
+/// exist for.
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+pub enum Path {
+ /// Route for the `/channels/:channel_id` path.
+ ///
+ /// The data is the relevant [`ChannelId`].
+ ///
+ /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
+ ChannelsId(u64),
+ /// Route for the `/channels/:channel_id/invites` path.
+ ///
+ /// The data is the relevant [`ChannelId`].
+ ///
+ /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
+ ChannelsIdInvites(u64),
+ /// Route for the `/channels/:channel_id/messages` path.
+ ///
+ /// The data is the relevant [`ChannelId`].
+ ///
+ /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
+ ChannelsIdMessages(u64),
+ /// Route for the `/channels/:channel_id/messages/bulk-delete` path.
+ ///
+ /// The data is the relevant [`ChannelId`].
+ ///
+ /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
+ ChannelsIdMessagesBulkDelete(u64),
+ /// Route for the `/channels/:channel_id/messages/:message_id` path.
+ ///
+ /// The data is the relevant [`ChannelId`].
+ ///
+ /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
+ // This route is a unique case. The ratelimit for message _deletions_ is
+ // different than the overall route ratelimit.
+ //
+ // Refer to the docs on [Rate Limits] in the yellow warning section.
+ //
+ // Additionally, this needs to be a `LightMethod` from the parent module
+ // and _not_ a `hyper` `Method` due to `hyper`'s not deriving `Copy`.
+ //
+ // [Rate Limits]: https://discordapp.com/developers/docs/topics/rate-limits
+ ChannelsIdMessagesId(LightMethod, u64),
+ /// Route for the `/channels/:channel_id/messages/:message_id/ack` path.
+ ///
+ /// The data is the relevant [`ChannelId`].
+ ///
+ /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
+ ChannelsIdMessagesIdAck(u64),
+ /// Route for the `/channels/:channel_id/messages/:message_id/reactions`
+ /// path.
+ ///
+ /// The data is the relevant [`ChannelId`].
+ ///
+ /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
+ ChannelsIdMessagesIdReactions(u64),
+ /// Route for the
+ /// `/channels/:channel_id/messages/:message_id/reactions/:reaction/@me`
+ /// path.
+ ///
+ /// The data is the relevant [`ChannelId`].
+ ///
+ /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
+ ChannelsIdMessagesIdReactionsUserIdType(u64),
+ /// Route for the `/channels/:channel_id/permissions/:target_id` path.
+ ///
+ /// The data is the relevant [`ChannelId`].
+ ///
+ /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
+ ChannelsIdPermissionsOverwriteId(u64),
+ /// Route for the `/channels/:channel_id/pins` path.
+ ///
+ /// The data is the relevant [`ChannelId`].
+ ///
+ /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
+ ChannelsIdPins(u64),
+ /// Route for the `/channels/:channel_id/pins/:message_id` path.
+ ///
+ /// The data is the relevant [`ChannelId`].
+ ///
+ /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
+ ChannelsIdPinsMessageId(u64),
+ /// Route for the `/channels/:channel_id/recipients/:user_id` path.
+ ///
+ /// The data is the relevant `ChannelId`.
+ ChannelsIdRecipientsId(u64),
+ /// Route for the `/channels/:channel_id/typing` path.
+ ///
+ /// The data is the relevant [`ChannelId`].
+ ///
+ /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
+ ChannelsIdTyping(u64),
+ /// Route for the `/channels/:channel_id/webhooks` path.
+ ///
+ /// The data is the relevant [`ChannelId`].
+ ///
+ /// [`ChannelId`]: ../../model/id/struct.ChannelId.html
+ ChannelsIdWebhooks(u64),
+ /// Route for the `/gateway` path.
+ Gateway,
+ /// Route for the `/gateway/bot` path.
+ GatewayBot,
+ /// Route for the `/guilds` path.
+ Guilds,
+ /// Route for the `/guilds/:guild_id` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsId(u64),
+ /// Route for the `/guilds/:guild_id/bans` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdBans(u64),
+ /// Route for the `/guilds/:guild_id/audit-logs` path.
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdAuditLogs(u64),
+ /// Route for the `/guilds/:guild_id/bans/:user_id` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdBansUserId(u64),
+ /// Route for the `/guilds/:guild_id/channels/:channel_id` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdChannels(u64),
+ /// Route for the `/guilds/:guild_id/embed` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdEmbed(u64),
+ /// Route for the `/guilds/:guild_id/emojis` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdEmojis(u64),
+ /// Route for the `/guilds/:guild_id/emojis/:emoji_id` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdEmojisId(u64),
+ /// Route for the `/guilds/:guild_id/integrations` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdIntegrations(u64),
+ /// Route for the `/guilds/:guild_id/integrations/:integration_id` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdIntegrationsId(u64),
+ /// Route for the `/guilds/:guild_id/integrations/:integration_id/sync`
+ /// path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdIntegrationsIdSync(u64),
+ /// Route for the `/guilds/:guild_id/invites` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdInvites(u64),
+ /// Route for the `/guilds/:guild_id/members` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdMembers(u64),
+ /// Route for the `/guilds/:guild_id/members/:user_id` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdMembersId(u64),
+ /// Route for the `/guilds/:guild_id/members/:user_id/roles/:role_id` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdMembersIdRolesId(u64),
+ /// Route for the `/guilds/:guild_id/members/@me/nick` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdMembersMeNick(u64),
+ /// Route for the `/guilds/:guild_id/prune` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdPrune(u64),
+ /// Route for the `/guilds/:guild_id/regions` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdRegions(u64),
+ /// Route for the `/guilds/:guild_id/roles` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdRoles(u64),
+ /// Route for the `/guilds/:guild_id/roles/:role_id` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdRolesId(u64),
+ /// Route for the `/guilds/:guild_id/vanity-url` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdVanityUrl(u64),
+ /// Route for the `/guilds/:guild_id/webhooks` path.
+ ///
+ /// The data is the relevant [`GuildId`].
+ ///
+ /// [`GuildId`]: struct.GuildId.html
+ GuildsIdWebhooks(u64),
+ /// Route for the `/invites/:code` path.
+ InvitesCode,
+ StatusMaintenancesActive,
+ /// Route for the `/users/:user_id` path.
+ UsersId,
+ /// Route for the `/users/@me` path.
+ UsersMe,
+ /// Route for the `/users/@me/channels` path.
+ UsersMeChannels,
+ /// Route for the `/users/@me/guilds` path.
+ UsersMeGuilds,
+ /// Route for the `/users/@me/guilds/:guild_id` path.
+ UsersMeGuildsId,
+ /// Route for the `/voice/regions` path.
+ VoiceRegions,
+ /// Route for the `/webhooks/:webhook_id` path.
+ WebhooksId(u64),
+}
+
+impl Path {
+ pub fn channel(channel_id: u64) -> String {
+ format!(api!("/channels/{}"), channel_id)
+ }
+
+ pub fn channel_invites(channel_id: u64) -> String {
+ format!(api!("/channels/{}/invites"), channel_id)
+ }
+
+ pub fn channel_message(channel_id: u64, message_id: u64) -> String {
+ format!(api!("/channels/{}/messages/{}"), channel_id, message_id)
+ }
+
+ pub fn channel_message_reaction<D, T>(
+ channel_id: u64,
+ message_id: u64,
+ user_id: D,
+ reaction_type: T
+ ) -> String where D: Display, T: Display {
+ format!(
+ api!("/channels/{}/messages/{}/reactions/{}/{}"),
+ channel_id,
+ message_id,
+ reaction_type,
+ user_id,
+ )
+ }
+
+ pub fn channel_message_reactions(
+ channel_id: u64,
+ message_id: u64,
+ reaction: &str,
+ limit: u8,
+ after: Option<u64>,
+ ) -> String {
+ let mut uri = format!(
+ api!("/channels/{}/messages/{}/reactions/{}?limit={}"),
+ channel_id,
+ message_id,
+ reaction,
+ limit,
+ );
+
+ if let Some(after) = after {
+ let _ = write!(uri, "&after={}", after);
+ }
+
+ uri
+ }
+
+ pub fn channel_messages(channel_id: u64) -> String {
+ format!(api!("/channels/{}/messages"), channel_id)
+ }
+
+ pub fn channel_messages_bulk_delete(channel_id: u64) -> String {
+ format!(api!("/channels/{}/messages/bulk-delete"), channel_id)
+ }
+
+ pub fn channel_permission(channel_id: u64, target_id: u64) -> String {
+ format!(api!("/channels/{}/permissions/{}"), channel_id, target_id)
+ }
+
+ pub fn channel_pin(channel_id: u64, message_id: u64) -> String {
+ format!(api!("/channels/{}/pins/{}"), channel_id, message_id)
+ }
+
+ pub fn channel_pins(channel_id: u64) -> String {
+ format!(api!("/channels/{}/pins"), channel_id)
+ }
+
+ pub fn channel_typing(channel_id: u64) -> String {
+ format!(api!("/channels/{}/typing"), channel_id)
+ }
+
+ pub fn channel_webhooks(channel_id: u64) -> String {
+ format!(api!("/channels/{}/webhooks"), channel_id)
+ }
+
+ pub fn gateway() -> &'static str {
+ api!("/gateway")
+ }
+
+ pub fn gateway_bot() -> &'static str {
+ api!("/gateway/bot")
+ }
+
+ pub fn group_recipient(group_id: u64, user_id: u64) -> String {
+ format!(api!("/channels/{}/recipients/{}"), group_id, user_id)
+ }
+
+ pub fn guild(guild_id: u64) -> String {
+ format!(api!("/guilds/{}"), guild_id)
+ }
+
+ pub fn guild_audit_logs(
+ guild_id: u64,
+ action_type: Option<u8>,
+ user_id: Option<u64>,
+ before: Option<u64>,
+ limit: Option<u8>,
+ ) -> String {
+ let mut s = format!(
+ api!("/guilds/{}/audit-logs?"),
+ guild_id,
+ );
+
+ if let Some(action_type) = action_type {
+ let _ = write!(s, "&action_type={}", action_type);
+ }
+
+ if let Some(before) = before {
+ let _ = write!(s, "&before={}", before);
+ }
+
+ if let Some(limit) = limit {
+ let _ = write!(s, "&limit={}", limit);
+ }
+
+ if let Some(user_id) = user_id {
+ let _ = write!(s, "&user_id={}", user_id);
+ }
+
+ s
+ }
+
+ pub fn guild_ban(guild_id: u64, user_id: u64) -> String {
+ format!(api!("/guilds/{}/bans/{}"), guild_id, user_id)
+ }
+
+ pub fn guild_ban_optioned(
+ guild_id: u64,
+ user_id: u64,
+ delete_message_days: u8,
+ reason: &str,
+ ) -> String {
+ format!(
+ api!("/guilds/{}/bans/{}?delete_message_days={}&reason={}"),
+ guild_id,
+ user_id,
+ delete_message_days,
+ reason,
+ )
+ }
+
+ pub fn guild_bans(guild_id: u64) -> String {
+ format!(api!("/guilds/{}/bans"), guild_id)
+ }
+
+ pub fn guild_channels(guild_id: u64) -> String {
+ format!(api!("/guilds/{}/channels"), guild_id)
+ }
+
+ pub fn guild_embed(guild_id: u64) -> String {
+ format!(api!("/guilds/{}/embed"), guild_id)
+ }
+
+ pub fn guild_emojis(guild_id: u64) -> String {
+ format!(api!("/guilds/{}/emojis"), guild_id)
+ }
+
+ pub fn guild_emoji(guild_id: u64, emoji_id: u64) -> String {
+ format!(api!("/guilds/{}/emojis/{}"), guild_id, emoji_id)
+ }
+
+ pub fn guild_integration(
+ guild_id: u64,
+ integration_id: u64,
+ ) -> String {
+ format!(api!("/guilds/{}/integrations/{}"), guild_id, integration_id)
+ }
+
+ pub fn guild_integration_sync(
+ guild_id: u64,
+ integration_id: u64,
+ ) -> String {
+ format!(
+ api!("/guilds/{}/integrations/{}/sync"),
+ guild_id,
+ integration_id,
+ )
+ }
+
+ pub fn guild_integrations(guild_id: u64) -> String {
+ format!(api!("/guilds/{}/integrations"), guild_id)
+ }
+
+ pub fn guild_invites(guild_id: u64) -> String {
+ format!(api!("/guilds/{}/invites"), guild_id)
+ }
+
+ pub fn guild_member(guild_id: u64, user_id: u64) -> String {
+ format!(api!("/guilds/{}/members/{}"), guild_id, user_id)
+ }
+
+ pub fn guild_member_role(
+ guild_id: u64,
+ user_id: u64,
+ role_id: u64,
+ ) -> String {
+ format!(
+ api!("/guilds/{}/members/{}/roles/{}"),
+ guild_id,
+ user_id,
+ role_id,
+ )
+ }
+
+ pub fn guild_members(guild_id: u64) -> String {
+ format!(api!("/guilds/{}/members"), guild_id)
+ }
+
+ pub fn guild_members_optioned(
+ guild_id: u64,
+ after: Option<u64>,
+ limit: Option<u64>,
+ ) -> String {
+ let mut s = format!(api!("/guilds/{}/members?"), guild_id);
+
+ if let Some(after) = after {
+ let _ = write!(s, "&after={}", after);
+ }
+
+ if let Some(limit) = limit {
+ let _ = write!(s, "&limit={}", limit);
+ }
+
+ s
+ }
+
+ pub fn guild_nickname(guild_id: u64) -> String {
+ format!(api!("/guilds/{}/members/@me/nick"), guild_id)
+ }
+
+ pub fn guild_prune(guild_id: u64, days: u64) -> String {
+ format!(api!("/guilds/{}/prune?days={}"), guild_id, days)
+ }
+
+ pub fn guild_regions(guild_id: u64) -> String {
+ format!(api!("/guilds/{}/regions"), guild_id)
+ }
+
+ pub fn guild_role(guild_id: u64, role_id: u64) -> String {
+ format!(api!("/guilds/{}/roles/{}"), guild_id, role_id)
+ }
+
+ pub fn guild_roles(guild_id: u64) -> String {
+ format!(api!("/guilds/{}/roles"), guild_id)
+ }
+
+ pub fn guild_vanity_url(guild_id: u64) -> String {
+ format!(api!("/guilds/{}/vanity-url"), guild_id)
+ }
+
+ pub fn guild_webhooks(guild_id: u64) -> String {
+ format!(api!("/guilds/{}/webhooks"), guild_id)
+ }
+
+ pub fn guilds() -> &'static str {
+ api!("/guilds")
+ }
+
+ pub fn invite(code: &str) -> String {
+ format!(api!("/invites/{}"), code)
+ }
+
+ pub fn invite_optioned(code: &str, stats: bool) -> String {
+ format!(api!("/invites/{}?with_counts={}"), code, stats)
+ }
+
+ pub fn oauth2_application_current() -> &'static str {
+ api!("/oauth2/applications/@me")
+ }
+
+ pub fn private_channel() -> &'static str {
+ api!("/users/@me/channels")
+ }
+
+ pub fn status_incidents_unresolved() -> &'static str {
+ status!("/incidents/unresolved.json")
+ }
+
+ pub fn status_maintenances_active() -> &'static str {
+ status!("/scheduled-maintenances/active.json")
+ }
+
+ pub fn status_maintenances_upcoming() -> &'static str {
+ status!("/scheduled-maintenances/upcoming.json")
+ }
+
+ pub fn user<D: Display>(target: D) -> String {
+ format!(api!("/users/{}"), target)
+ }
+
+ pub fn user_dm_channels<D: Display>(target: D) -> String {
+ format!(api!("/users/{}/channels"), target)
+ }
+
+ pub fn user_guild<D: Display>(target: D, guild_id: u64) -> String {
+ format!(api!("/users/{}/guilds/{}"), target, guild_id)
+ }
+
+ pub fn user_guilds<D: Display>(target: D) -> String {
+ format!(api!("/users/{}/guilds"), target)
+ }
+
+ pub fn user_guilds_optioned<D: Display>(
+ target: D,
+ after: Option<u64>,
+ before: Option<u64>,
+ limit: u64,
+ ) -> String {
+ let mut s = format!(api!("/users/{}/guilds?limit={}&"), target, limit);
+
+ if let Some(after) = after {
+ let _ = write!(s, "&after={}", after);
+ }
+
+ if let Some(before) = before {
+ let _ = write!(s, "&before={}", before);
+ }
+
+ s
+ }
+
+ pub fn voice_regions() -> &'static str {
+ api!("/voice/regions")
+ }
+
+ pub fn webhook(webhook_id: u64) -> String {
+ format!(api!("/webhooks/{}"), webhook_id)
+ }
+
+ pub fn webhook_with_token<D>(webhook_id: u64, token: D) -> String
+ where D: Display {
+ format!(api!("/webhooks/{}/{}"), webhook_id, token)
+ }
+
+ pub fn webhook_with_token_optioned<D>(webhook_id: u64, token: D, wait: bool)
+ -> String where D: Display {
+ format!(api!("/webhooks/{}/{}?wait={}"), webhook_id, token, wait)
+ }
+
+ /// Returns the path's base string without replacements.
+ pub fn base(&self) -> &str {
+ use self::Path::*;
+
+ match *self {
+ ChannelsId(_) => "/channels/{}",
+ ChannelsIdInvites(_) => "/channels/{}/invites",
+ ChannelsIdMessages(_) => "/channels/{}/messages",
+ ChannelsIdMessagesBulkDelete(_) => "/channels/{}/messages/bulk-delete",
+ ChannelsIdMessagesId(_, _) => "/channels/{}/messages/{}",
+ ChannelsIdMessagesIdAck(_) => "/channels/{}/messages/ack",
+ ChannelsIdMessagesIdReactions(_) => "/channels/{}/messages/{}/reactions",
+ ChannelsIdMessagesIdReactionsUserIdType(_) => "/channels/{}/messages/{}/reactions/{}/@me",
+ ChannelsIdPermissionsOverwriteId(_) => "/channels/{}/permissions/{}",
+ ChannelsIdPins(_) => "/channels/{}/pins",
+ ChannelsIdPinsMessageId(_) => "/channels/{}/pins/{}",
+ ChannelsIdRecipientsId(_) => "/channels/{}/recipients/{}",
+ ChannelsIdTyping(_) => "/channels/{}/typing",
+ ChannelsIdWebhooks(_) => "/channels/{}/webhooks",
+ Gateway => "/gateway",
+ GatewayBot => "/gateway/bot",
+ Guilds => "/guilds",
+ GuildsId(_) => "/guilds/{}",
+ GuildsIdBans(_) => "/guilds/{}/bans",
+ GuildsIdAuditLogs(_) => "/guilds/{}/audit-logs",
+ GuildsIdBansUserId(_) => "/guilds/{}/bans/{}",
+ GuildsIdChannels(_) => "/guilds/{}/channels",
+ GuildsIdEmbed(_) => "/guilds/{}/embed",
+ GuildsIdEmojis(_) => "/guilds/{}/emojis",
+ GuildsIdEmojisId(_) => "/guilds/{}/emojis/{}",
+ GuildsIdIntegrations(_) => "/guilds/{}/integrations",
+ GuildsIdIntegrationsId(_) => "/guilds/{}/integrations/{}",
+ GuildsIdIntegrationsIdSync(_) => "/guilds/{}/integrations/{}/sync",
+ GuildsIdInvites(_) => "/guilds/{}/invites",
+ GuildsIdMembers(_) => "/guilds/{}/membes",
+ GuildsIdMembersId(_) => "/guilds/{}/members/{}",
+ GuildsIdMembersIdRolesId(_) => "/guilds/{}/members/{}/roles/{}",
+ GuildsIdMembersMeNick(_) => "/guilds/{}/members/@me/nick",
+ GuildsIdPrune(_) => "/guilds/{}/prune",
+ GuildsIdRegions(_) => "/guilds/{}/regions",
+ GuildsIdRoles(_) => "/guilds/{}/roles",
+ GuildsIdRolesId(_) => "/guilds/{}/roles/{}",
+ GuildsIdVanityUrl(_) => "/guilds/{}/vanity-url",
+ GuildsIdWebhooks(_) => "/guilds/{}/webhooks",
+ InvitesCode => "/invites/{}",
+ StatusMaintenancesActive => "/scheduled-maintenances/active.json",
+ UsersId => "/users/{}",
+ UsersMe => "/users/@me",
+ UsersMeChannels => "/users/@me/channels",
+ UsersMeGuilds => "/users/@me/guilds",
+ UsersMeGuildsId => "/users/@me/guilds/{}",
+ VoiceRegions => "/voice/regions",
+ WebhooksId(_) => "/webhooks/{}",
+ }
+ }
+}
+
+impl Display for Path {
+ fn fmt(&self, f: &mut Formatter) -> FmtResult {
+ f.write_str(self.base())
+ }
+}
+
+#[derive(Clone)]
+pub enum Route<'a> {
+ AddGroupRecipient {
+ group_id: u64,
+ user_id: u64,
+ },
+ AddMemberRole {
+ guild_id: u64,
+ role_id: u64,
+ user_id: u64,
+ },
+ GuildBanUser {
+ guild_id: u64,
+ user_id: u64,
+ delete_message_days: Option<u8>,
+ reason: Option<&'a str>,
+ },
+ BroadcastTyping {
+ channel_id: u64,
+ },
+ CreateChannel {
+ guild_id: u64,
+ },
+ CreateEmoji {
+ guild_id: u64,
+ },
+ CreateGuild,
+ CreateGuildIntegration {
+ guild_id: u64,
+ integration_id: u64,
+ },
+ CreateInvite {
+ channel_id: u64,
+ },
+ CreateMessage {
+ channel_id: u64,
+ },
+ CreatePermission {
+ channel_id: u64,
+ target_id: u64,
+ },
+ CreatePrivateChannel,
+ CreateReaction {
+ channel_id: u64,
+ message_id: u64,
+ reaction: &'a str,
+ },
+ CreateRole {
+ guild_id: u64,
+ },
+ CreateWebhook {
+ channel_id: u64,
+ },
+ DeleteChannel {
+ channel_id: u64,
+ },
+ DeleteEmoji {
+ guild_id: u64,
+ emoji_id: u64,
+ },
+ DeleteGuild {
+ guild_id: u64,
+ },
+ DeleteGuildIntegration {
+ guild_id: u64,
+ integration_id: u64,
+ },
+ DeleteInvite {
+ code: &'a str,
+ },
+ DeleteMessage {
+ channel_id: u64,
+ message_id: u64,
+ },
+ DeleteMessages {
+ channel_id: u64,
+ },
+ DeleteMessageReactions {
+ channel_id: u64,
+ message_id: u64,
+ },
+ DeletePermission {
+ channel_id: u64,
+ target_id: u64,
+ },
+ DeleteReaction {
+ channel_id: u64,
+ message_id: u64,
+ user: &'a str,
+ reaction: &'a str,
+ },
+ DeleteRole {
+ guild_id: u64,
+ role_id: u64,
+ },
+ DeleteWebhook {
+ webhook_id: u64,
+ },
+ DeleteWebhookWithToken {
+ token: &'a str,
+ webhook_id: u64,
+ },
+ EditChannel {
+ channel_id: u64,
+ },
+ EditEmoji {
+ guild_id: u64,
+ emoji_id: u64,
+ },
+ EditGuild {
+ guild_id: u64,
+ },
+ EditGuildChannels {
+ guild_id: u64,
+ },
+ EditGuildEmbed {
+ guild_id: u64,
+ },
+ EditMember {
+ guild_id: u64,
+ user_id: u64,
+ },
+ EditMessage {
+ channel_id: u64,
+ message_id: u64,
+ },
+ EditNickname {
+ guild_id: u64,
+ },
+ EditProfile,
+ EditRole {
+ guild_id: u64,
+ role_id: u64,
+ },
+ EditWebhook {
+ webhook_id: u64,
+ },
+ EditWebhookWithToken {
+ token: &'a str,
+ webhook_id: u64,
+ },
+ ExecuteWebhook {
+ token: &'a str,
+ wait: bool,
+ webhook_id: u64,
+ },
+ GetActiveMaintenance,
+ GetAuditLogs {
+ action_type: Option<u8>,
+ before: Option<u64>,
+ guild_id: u64,
+ limit: Option<u8>,
+ user_id: Option<u64>,
+ },
+ GetBans {
+ guild_id: u64,
+ },
+ GetBotGateway,
+ GetChannel {
+ channel_id: u64,
+ },
+ GetChannelInvites {
+ channel_id: u64,
+ },
+ GetChannelWebhooks {
+ channel_id: u64,
+ },
+ GetChannels {
+ guild_id: u64,
+ },
+ GetCurrentApplicationInfo,
+ GetCurrentUser,
+ GetGateway,
+ GetGuild {
+ guild_id: u64,
+ },
+ GetGuildEmbed {
+ guild_id: u64,
+ },
+ GetGuildIntegrations {
+ guild_id: u64,
+ },
+ GetGuildInvites {
+ guild_id: u64,
+ },
+ GetGuildMembers {
+ after: Option<u64>,
+ limit: Option<u64>,
+ guild_id: u64,
+ },
+ GetGuildPruneCount {
+ days: u64,
+ guild_id: u64,
+ },
+ GetGuildRegions {
+ guild_id: u64,
+ },
+ GetGuildRoles {
+ guild_id: u64,
+ },
+ GetGuildVanityUrl {
+ guild_id: u64,
+ },
+ GetGuildWebhooks {
+ guild_id: u64,
+ },
+ GetGuilds {
+ after: Option<u64>,
+ before: Option<u64>,
+ limit: u64,
+ },
+ GetInvite {
+ code: &'a str,
+ stats: bool,
+ },
+ GetMember {
+ guild_id: u64,
+ user_id: u64,
+ },
+ GetMessage {
+ channel_id: u64,
+ message_id: u64,
+ },
+ GetMessages {
+ channel_id: u64,
+ query: String,
+ },
+ GetPins {
+ channel_id: u64,
+ },
+ GetReactionUsers {
+ after: Option<u64>,
+ channel_id: u64,
+ limit: u8,
+ message_id: u64,
+ reaction: String,
+ },
+ GetUnresolvedIncidents,
+ GetUpcomingMaintenances,
+ GetUser {
+ user_id: u64,
+ },
+ GetUserDmChannels,
+ GetVoiceRegions,
+ GetWebhook {
+ webhook_id: u64,
+ },
+ GetWebhookWithToken {
+ token: &'a str,
+ webhook_id: u64,
+ },
+ KickMember {
+ guild_id: u64,
+ user_id: u64,
+ },
+ LeaveGroup {
+ group_id: u64,
+ },
+ LeaveGuild {
+ guild_id: u64,
+ },
+ RemoveGroupRecipient {
+ group_id: u64,
+ user_id: u64,
+ },
+ PinMessage {
+ channel_id: u64,
+ message_id: u64,
+ },
+ RemoveBan {
+ guild_id: u64,
+ user_id: u64,
+ },
+ RemoveMemberRole {
+ guild_id: u64,
+ role_id: u64,
+ user_id: u64,
+ },
+ StartGuildPrune {
+ days: u64,
+ guild_id: u64,
+ },
+ StartIntegrationSync {
+ guild_id: u64,
+ integration_id: u64,
+ },
+ UnpinMessage {
+ channel_id: u64,
+ message_id: u64,
+ },
+}
+
+impl<'a> Route<'a> {
+ pub fn deconstruct(&self) -> (LightMethod, Path, Cow<str>) {
+ match *self {
+ Route::AddGroupRecipient { group_id, user_id } => (
+ LightMethod::Post,
+ Path::ChannelsIdRecipientsId(group_id),
+ Cow::from(Path::group_recipient(group_id, user_id)),
+ ),
+ Route::AddMemberRole { guild_id, role_id, user_id } => (
+ LightMethod::Delete,
+ Path::GuildsIdMembersIdRolesId(guild_id),
+ Cow::from(Path::guild_member_role(guild_id, user_id, role_id)),
+ ),
+ Route::GuildBanUser {
+ guild_id,
+ delete_message_days,
+ reason,
+ user_id,
+ } => (
+ // TODO
+ LightMethod::Delete,
+ Path::GuildsIdBansUserId(guild_id),
+ Cow::from(Path::guild_ban_optioned(
+ guild_id,
+ user_id,
+ delete_message_days.unwrap_or(0),
+ reason.unwrap_or(""),
+ )),
+ ),
+ Route::BroadcastTyping { channel_id } => (
+ LightMethod::Post,
+ Path::ChannelsIdTyping(channel_id),
+ Cow::from(Path::channel_typing(channel_id)),
+ ),
+ Route::CreateChannel { guild_id } => (
+ LightMethod::Post,
+ Path::GuildsIdChannels(guild_id),
+ Cow::from(Path::guild_channels(guild_id)),
+ ),
+ Route::CreateEmoji { guild_id } => (
+ LightMethod::Post,
+ Path::GuildsIdEmojis(guild_id),
+ Cow::from(Path::guild_emojis(guild_id)),
+ ),
+ Route::CreateGuild => (
+ LightMethod::Post,
+ Path::Guilds,
+ Cow::from(Path::guilds()),
+ ),
+ Route::CreateGuildIntegration { guild_id, integration_id } => (
+ LightMethod::Post,
+ Path::GuildsIdIntegrationsId(guild_id),
+ Cow::from(Path::guild_integration(guild_id, integration_id)),
+ ),
+ Route::CreateInvite { channel_id } => (
+ LightMethod::Post,
+ Path::ChannelsIdInvites(channel_id),
+ Cow::from(Path::channel_invites(channel_id)),
+ ),
+ Route::CreateMessage { channel_id } => (
+ LightMethod::Post,
+ Path::ChannelsIdMessages(channel_id),
+ Cow::from(Path::channel_messages(channel_id)),
+ ),
+ Route::CreatePermission { channel_id, target_id } => (
+ LightMethod::Post,
+ Path::ChannelsIdPermissionsOverwriteId(channel_id),
+ Cow::from(Path::channel_permission(channel_id, target_id)),
+ ),
+ Route::CreatePrivateChannel => (
+ LightMethod::Post,
+ Path::UsersMeChannels,
+ Cow::from(Path::user_dm_channels("@me")),
+ ),
+ Route::CreateReaction { channel_id, message_id, reaction } => (
+ LightMethod::Put,
+ Path::ChannelsIdMessagesIdReactionsUserIdType(channel_id),
+ Cow::from(Path::channel_message_reaction(
+ channel_id,
+ message_id,
+ "@me",
+ reaction,
+ )),
+ ),
+ Route::CreateRole { guild_id } => (
+ LightMethod::Delete,
+ Path::GuildsIdRoles(guild_id),
+ Cow::from(Path::guild_roles(guild_id)),
+ ),
+ Route::CreateWebhook { channel_id } => (
+ LightMethod::Delete,
+ Path::ChannelsIdWebhooks(channel_id),
+ Cow::from(Path::channel_webhooks(channel_id)),
+ ),
+ Route::DeleteChannel { channel_id } => (
+ LightMethod::Delete,
+ Path::ChannelsId(channel_id),
+ Cow::from(Path::channel(channel_id)),
+ ),
+ Route::DeleteEmoji { emoji_id, guild_id } => (
+ LightMethod::Delete,
+ Path::GuildsIdEmojisId(guild_id),
+ Cow::from(Path::guild_emoji(guild_id, emoji_id)),
+ ),
+ Route::DeleteGuild { guild_id } => (
+ LightMethod::Delete,
+ Path::GuildsId(guild_id),
+ Cow::from(Path::guild(guild_id)),
+ ),
+ Route::DeleteGuildIntegration { guild_id, integration_id } => (
+ LightMethod::Delete,
+ Path::GuildsIdIntegrationsId(guild_id),
+ Cow::from(Path::guild_integration(guild_id, integration_id)),
+ ),
+ Route::DeleteInvite { code } => (
+ LightMethod::Delete,
+ Path::InvitesCode,
+ Cow::from(Path::invite(code)),
+ ),
+ Route::DeleteMessage { channel_id, message_id } => (
+ LightMethod::Delete,
+ Path::ChannelsIdMessagesId(LightMethod::Delete, message_id),
+ Cow::from(Path::channel_message(channel_id, message_id)),
+ ),
+ Route::DeleteMessages { channel_id } => (
+ LightMethod::Delete,
+ Path::ChannelsIdMessagesBulkDelete(channel_id),
+ Cow::from(Path::channel_messages_bulk_delete(channel_id)),
+ ),
+ Route::DeletePermission { channel_id, target_id } => (
+ LightMethod::Delete,
+ Path::ChannelsIdPermissionsOverwriteId(channel_id),
+ Cow::from(Path::channel_permission(channel_id, target_id)),
+ ),
+ Route::DeleteReaction {
+ channel_id,
+ message_id,
+ reaction,
+ user,
+ } => (
+ LightMethod::Delete,
+ Path::ChannelsIdMessagesIdReactionsUserIdType(channel_id),
+ Cow::from(Path::channel_message_reaction(
+ channel_id,
+ message_id,
+ user,
+ reaction,
+ ))
+ ),
+ Route::DeleteRole { guild_id, role_id } => (
+ LightMethod::Delete,
+ Path::GuildsIdRolesId(guild_id),
+ Cow::from(Path::guild_role(guild_id, role_id)),
+ ),
+ Route::DeleteWebhook { webhook_id } => (
+ LightMethod::Delete,
+ Path::WebhooksId(webhook_id),
+ Cow::from(Path::webhook(webhook_id)),
+ ),
+ Route::DeleteWebhookWithToken { token, webhook_id } => (
+ LightMethod::Delete,
+ Path::WebhooksId(webhook_id),
+ Cow::from(Path::webhook_with_token(webhook_id, token)),
+ ),
+ Route::EditChannel { channel_id } => (
+ LightMethod::Patch,
+ Path::ChannelsId(channel_id),
+ Cow::from(Path::channel(channel_id)),
+ ),
+ Route::EditEmoji { emoji_id, guild_id } => (
+ LightMethod::Patch,
+ Path::GuildsIdEmojisId(guild_id),
+ Cow::from(Path::guild_emoji(guild_id, emoji_id)),
+ ),
+ Route::EditGuild { guild_id } => (
+ LightMethod::Patch,
+ Path::GuildsId(guild_id),
+ Cow::from(Path::guild(guild_id)),
+ ),
+ Route::EditGuildChannels { guild_id } => (
+ LightMethod::Patch,
+ Path::GuildsIdChannels(guild_id),
+ Cow::from(Path::guild_channels(guild_id)),
+ ),
+ Route::EditGuildEmbed { guild_id } => (
+ LightMethod::Patch,
+ Path::GuildsIdEmbed(guild_id),
+ Cow::from(Path::guild_embed(guild_id)),
+ ),
+ Route::EditMember { guild_id, user_id } => (
+ LightMethod::Patch,
+ Path::GuildsIdMembersId(guild_id),
+ Cow::from(Path::guild_member(guild_id, user_id)),
+ ),
+ Route::EditMessage { channel_id, message_id } => (
+ LightMethod::Patch,
+ Path::ChannelsIdMessagesId(LightMethod::Patch, channel_id),
+ Cow::from(Path::channel_message(channel_id, message_id)),
+ ),
+ Route::EditNickname { guild_id } => (
+ LightMethod::Patch,
+ Path::GuildsIdMembersMeNick(guild_id),
+ Cow::from(Path::guild_nickname(guild_id)),
+ ),
+ Route::EditProfile => (
+ LightMethod::Patch,
+ Path::UsersMe,
+ Cow::from(Path::user("@me")),
+ ),
+ Route::EditRole { guild_id, role_id } => (
+ LightMethod::Patch,
+ Path::GuildsIdRolesId(guild_id),
+ Cow::from(Path::guild_role(guild_id, role_id)),
+ ),
+ Route::EditWebhook { webhook_id } => (
+ LightMethod::Patch,
+ Path::WebhooksId(webhook_id),
+ Cow::from(Path::webhook(webhook_id)),
+ ),
+ Route::EditWebhookWithToken { token, webhook_id } => (
+ LightMethod::Patch,
+ Path::WebhooksId(webhook_id),
+ Cow::from(Path::webhook_with_token(webhook_id, token)),
+ ),
+ Route::ExecuteWebhook { token, wait, webhook_id } => (
+ LightMethod::Post,
+ Path::WebhooksId(webhook_id),
+ Cow::from(Path::webhook_with_token_optioned(
+ webhook_id,
+ token,
+ wait,
+ )),
+ ),
+ Route::GetActiveMaintenance => (
+ LightMethod::Get,
+ Path::StatusMaintenancesActive,
+ Cow::from(Path::status_maintenances_active()),
+ ),
+ Route::GetAuditLogs {
+ action_type,
+ before,
+ guild_id,
+ limit,
+ user_id,
+ } => (
+ LightMethod::Get,
+ Path::GuildsIdAuditLogs(guild_id),
+ Cow::from(Path::guild_audit_logs(
+ guild_id,
+ action_type,
+ user_id,
+ before,
+ limit,
+ )),
+ ),
+ Route::GetBans { guild_id } => (
+ LightMethod::Get,
+ Path::GuildsIdBans(guild_id),
+ Cow::from(Path::guild_bans(guild_id)),
+ ),
+ Route::GetBotGateway => (
+ LightMethod::Get,
+ Path::GatewayBot,
+ Cow::from(Path::gateway_bot()),
+ ),
+ Route::GetChannel { channel_id } => (
+ LightMethod::Get,
+ Path::ChannelsId(channel_id),
+ Cow::from(Path::channel(channel_id)),
+ ),
+ Route::GetChannelInvites { channel_id } => (
+ LightMethod::Get,
+ Path::ChannelsIdInvites(channel_id),
+ Cow::from(Path::channel_invites(channel_id)),
+ ),
+ Route::GetChannelWebhooks { channel_id } => (
+ LightMethod::Get,
+ Path::ChannelsIdWebhooks(channel_id),
+ Cow::from(Path::channel_webhooks(channel_id)),
+ ),
+ Route::GetChannels { guild_id } => (
+ LightMethod::Get,
+ Path::GuildsIdChannels(guild_id),
+ Cow::from(Path::guild_channels(guild_id)),
+ ),
+ Route::GetCurrentUser => (
+ LightMethod::Get,
+ Path::UsersMe,
+ Cow::from(Path::user("@me")),
+ ),
+ Route::GetGateway => (
+ LightMethod::Get,
+ Path::Gateway,
+ Cow::from(Path::gateway()),
+ ),
+ Route::GetGuild { guild_id } => (
+ LightMethod::Get,
+ Path::GuildsId(guild_id),
+ Cow::from(Path::guild(guild_id)),
+ ),
+ Route::GetGuildEmbed { guild_id } => (
+ LightMethod::Get,
+ Path::GuildsIdEmbed(guild_id),
+ Cow::from(Path::guild_embed(guild_id)),
+ ),
+ Route::GetGuildIntegrations { guild_id } => (
+ LightMethod::Get,
+ Path::GuildsIdIntegrations(guild_id),
+ Cow::from(Path::guild_integrations(guild_id)),
+ ),
+ Route::GetGuildInvites { guild_id } => (
+ LightMethod::Get,
+ Path::GuildsIdInvites(guild_id),
+ Cow::from(Path::guild_invites(guild_id)),
+ ),
+ Route::GetGuildMembers { after, guild_id, limit } => (
+ LightMethod::Get,
+ Path::GuildsIdMembers(guild_id),
+ Cow::from(Path::guild_members_optioned(guild_id, after, limit)),
+ ),
+ Route::GetGuildPruneCount { days, guild_id } => (
+ LightMethod::Get,
+ Path::GuildsIdPrune(guild_id),
+ Cow::from(Path::guild_prune(guild_id, days)),
+ ),
+ Route::GetGuildRegions { guild_id } => (
+ LightMethod::Get,
+ Path::GuildsIdRegions(guild_id),
+ Cow::from(Path::guild_regions(guild_id)),
+ ),
+ Route::GetGuildRoles { guild_id } => (
+ LightMethod::Get,
+ Path::GuildsIdRoles(guild_id),
+ Cow::from(Path::guild_roles(guild_id)),
+ ),
+ Route::GetGuildVanityUrl { guild_id } => (
+ LightMethod::Get,
+ Path::GuildsIdVanityUrl(guild_id),
+ Cow::from(Path::guild_vanity_url(guild_id)),
+ ),
+ Route::GetGuildWebhooks { guild_id } => (
+ LightMethod::Get,
+ Path::GuildsIdWebhooks(guild_id),
+ Cow::from(Path::guild_webhooks(guild_id)),
+ ),
+ Route::GetGuilds { after, before, limit } => (
+ LightMethod::Get,
+ Path::UsersMeGuilds,
+ Cow::from(Path::user_guilds_optioned(
+ "@me",
+ after,
+ before,
+ limit,
+ )),
+ ),
+ Route::GetInvite { code, stats } => (
+ LightMethod::Get,
+ Path::InvitesCode,
+ Cow::from(Path::invite_optioned(code, stats)),
+ ),
+ Route::GetMember { guild_id, user_id } => (
+ LightMethod::Get,
+ Path::GuildsIdMembersId(guild_id),
+ Cow::from(Path::guild_member(guild_id, user_id)),
+ ),
+ Route::GetMessage { channel_id, message_id } => (
+ LightMethod::Get,
+ Path::ChannelsIdMessagesId(LightMethod::Get, channel_id),
+ Cow::from(Path::channel_message(channel_id, message_id)),
+ ),
+ Route::GetPins { channel_id } => (
+ LightMethod::Get,
+ Path::ChannelsIdPins(channel_id),
+ Cow::from(Path::channel_pins(channel_id)),
+ ),
+ Route::GetReactionUsers {
+ after,
+ channel_id,
+ limit,
+ message_id,
+ ref reaction,
+ } => (
+ LightMethod::Get,
+ Path::ChannelsIdMessagesIdReactions(channel_id),
+ Cow::from(Path::channel_message_reactions(
+ channel_id,
+ message_id,
+ reaction,
+ limit,
+ after,)),
+ ),
+ Route::GetUser { user_id } => (
+ LightMethod::Get,
+ Path::UsersId,
+ Cow::from(Path::user(user_id)),
+ ),
+ Route::GetUserDmChannels => (
+ LightMethod::Get,
+ Path::UsersMeChannels,
+ Cow::from(Path::user_dm_channels("@me")),
+ ),
+ Route::GetVoiceRegions => (
+ LightMethod::Get,
+ Path::VoiceRegions,
+ Cow::from(Path::voice_regions()),
+ ),
+ Route::GetWebhook { webhook_id } => (
+ LightMethod::Get,
+ Path::WebhooksId(webhook_id),
+ Cow::from(Path::webhook(webhook_id)),
+ ),
+ Route::GetWebhookWithToken { token, webhook_id } => (
+ LightMethod::Get,
+ Path::WebhooksId(webhook_id),
+ Cow::from(Path::webhook_with_token(webhook_id, token)),
+ ),
+ Route::KickMember { guild_id, user_id } => (
+ LightMethod::Delete,
+ Path::GuildsIdMembersId(guild_id),
+ Cow::from(Path::guild_member(guild_id, user_id)),
+ ),
+ Route::LeaveGroup { group_id } => (
+ LightMethod::Delete,
+ Path::ChannelsId(group_id),
+ Cow::from(Path::channel(group_id)),
+ ),
+ Route::LeaveGuild { guild_id } => (
+ LightMethod::Delete,
+ Path::UsersMeGuildsId,
+ Cow::from(Path::user_guild("@me", guild_id)),
+ ),
+ Route::RemoveGroupRecipient { group_id, user_id } => (
+ LightMethod::Delete,
+ Path::ChannelsIdRecipientsId(group_id),
+ Cow::from(Path::group_recipient(group_id, user_id)),
+ ),
+ Route::PinMessage { channel_id, message_id } => (
+ LightMethod::Put,
+ Path::ChannelsIdPins(channel_id),
+ Cow::from(Path::channel_pin(channel_id, message_id)),
+ ),
+ Route::RemoveBan { guild_id, user_id } => (
+ LightMethod::Delete,
+ Path::GuildsIdBansUserId(guild_id),
+ Cow::from(Path::guild_ban(guild_id, user_id)),
+ ),
+ Route::RemoveMemberRole { guild_id, role_id, user_id } => (
+ LightMethod::Delete,
+ Path::GuildsIdMembersIdRolesId(guild_id),
+ Cow::from(Path::guild_member_role(guild_id, user_id, role_id)),
+ ),
+ Route::StartGuildPrune { days, guild_id } => (
+ LightMethod::Post,
+ Path::GuildsIdPrune(guild_id),
+ Cow::from(Path::guild_prune(guild_id, days)),
+ ),
+ Route::StartIntegrationSync { guild_id, integration_id } => (
+ LightMethod::Post,
+ Path::GuildsIdIntegrationsId(guild_id),
+ Cow::from(Path::guild_integration_sync(
+ guild_id,
+ integration_id,
+ )),
+ ),
+ Route::UnpinMessage { channel_id, message_id } => (
+ LightMethod::Delete,
+ Path::ChannelsIdPinsMessageId(channel_id),
+ Cow::from(Path::channel_pin(channel_id, message_id)),
+ ),
+ _ => unreachable!(), // TODO: finish 5 unconvered variants
+ }
+ }
+}
diff --git a/src/http/utils.rs b/src/http/utils.rs
new file mode 100644
index 0000000..017fe0e
--- /dev/null
+++ b/src/http/utils.rs
@@ -0,0 +1,12 @@
+use model::channel::ReactionType;
+
+pub fn reaction_type_data(reaction_type: &ReactionType) -> String {
+ match *reaction_type {
+ ReactionType::Custom {
+ id,
+ ref name,
+ ..
+ } => format!("{}:{}", name.as_ref().map_or("", |s| s.as_str()), id),
+ ReactionType::Unicode(ref unicode) => unicode.clone(),
+ }
+}