diff options
| author | Mishio595 <[email protected]> | 2018-08-06 20:15:11 -0600 |
|---|---|---|
| committer | Mishio595 <[email protected]> | 2018-08-06 20:15:11 -0600 |
| commit | d910405d8f50fb381b7d9430b76f0b38ef4c9458 (patch) | |
| tree | 1d09b965cf0447cce84384196e19c1eba562ef7c /src | |
| parent | Merge branch 'upstream' (diff) | |
| parent | Dereference a destructure instead (diff) | |
| download | serenity-d910405d8f50fb381b7d9430b76f0b38ef4c9458.tar.xz serenity-d910405d8f50fb381b7d9430b76f0b38ef4c9458.zip | |
Merge branch 'upstream'
Diffstat (limited to 'src')
| -rw-r--r-- | src/client/dispatch.rs | 13 | ||||
| -rw-r--r-- | src/framework/standard/args.rs | 68 | ||||
| -rw-r--r-- | src/framework/standard/command.rs | 19 | ||||
| -rw-r--r-- | src/framework/standard/configuration.rs | 116 | ||||
| -rw-r--r-- | src/framework/standard/mod.rs | 3 | ||||
| -rw-r--r-- | src/http/mod.rs | 1501 | ||||
| -rw-r--r-- | src/http/ratelimiting.rs | 280 | ||||
| -rw-r--r-- | src/http/request.rs | 116 | ||||
| -rw-r--r-- | src/http/routing.rs | 1433 | ||||
| -rw-r--r-- | src/internal/macros.rs | 30 | ||||
| -rw-r--r-- | src/model/gateway.rs | 12 |
11 files changed, 2423 insertions, 1168 deletions
diff --git a/src/client/dispatch.rs b/src/client/dispatch.rs index b8c5610..2bbe344 100644 --- a/src/client/dispatch.rs +++ b/src/client/dispatch.rs @@ -19,6 +19,8 @@ use typemap::ShareMap; use framework::Framework; #[cfg(feature = "cache")] use model::id::GuildId; +#[cfg(feature = "cache")] +use std::time::Duration; #[cfg(feature = "cache")] use super::CACHE; @@ -28,7 +30,16 @@ macro_rules! update { { #[cfg(feature = "cache")] { - CACHE.write().update(&mut $event) + CACHE.try_write_for(Duration::from_millis(10)) + .and_then(|mut lock| lock.update(&mut $event)) + .or_else(|| { + warn!( + "[dispatch] Possible deadlock: couldn't unlock cache to update with event: {:?}", + $event, + ); + + None + }) } } }; diff --git a/src/framework/standard/args.rs b/src/framework/standard/args.rs index 8a622af..cec1f20 100644 --- a/src/framework/standard/args.rs +++ b/src/framework/standard/args.rs @@ -324,6 +324,29 @@ impl Args { } } + /// Retrieves the current argument. Does not parse. + /// + /// # Note + /// + /// This borrows `Args` for the entire lifetime of the returned argument. + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("42 69", &[" ".to_string()]); + /// + /// assert_eq!(args.current(), Some("42")); + /// args.next(); + /// assert_eq!(args.current(), Some("69")); + /// args.next(); + /// assert_eq!(args.current(), None); + /// ``` + pub fn current(&self) -> Option<&str> { + self.args.get(self.offset).map(|t| t.lit.as_str()) + } + /// Parses the current argument and advances. /// /// # Examples @@ -425,7 +448,6 @@ impl Args { Some(vec) } - /// Provides an iterator that will spew arguments until the end of the message. /// /// # Examples @@ -463,6 +485,30 @@ impl Args { self.iter::<T>().collect() } + /// Retrieves the current argument and also removes quotes around it if they're present. + /// Does not parse. + /// + /// # Note + /// + /// This borrows `Args` for the entire lifetime of the returned argument. + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("42 \"69\"", &[" ".to_string()]); + /// + /// assert_eq!(args.current_quoted(), Some("42")); + /// args.next(); + /// assert_eq!(args.current_quoted(), Some("69")); + /// args.next(); + /// assert_eq!(args.current_quoted(), None); + /// ``` + pub fn current_quoted(&self) -> Option<&str> { + self.args.get(self.offset).map(|t| quotes_extract(t)) + } + /// Like [`single`], but accounts quotes. /// /// # Examples @@ -790,6 +836,26 @@ impl Args { self.len() - self.offset } + /// Move to the next argument. + /// This increments the offset pointer. + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("42 69", &[" ".to_string()]); + /// + /// args.next(); + /// + /// assert_eq!(args.single::<u32>().unwrap(), 69); + /// assert!(args.is_empty()); + /// ``` + #[inline] + pub fn next(&mut self) { + self.offset += 1; + } + /// Go one step behind. /// /// # Examples diff --git a/src/framework/standard/command.rs b/src/framework/standard/command.rs index a6a6074..42264f2 100644 --- a/src/framework/standard/command.rs +++ b/src/framework/standard/command.rs @@ -338,15 +338,14 @@ impl Default for CommandOptions { } pub fn positions(ctx: &mut Context, msg: &Message, conf: &Configuration) -> Option<Vec<usize>> { - if !conf.prefixes.is_empty() || conf.dynamic_prefix.is_some() { - // Find out if they were mentioned. If not, determine if the prefix - // was used. If not, return None. - let mut positions: Vec<usize> = vec![]; + // Mentions have the highest precedence. + if let Some(mention_end) = find_mention_end(&msg.content, conf) { + return Some(vec![mention_end]); // This can simply be returned without trying to find the end whitespaces as trim will remove it later + } - if let Some(mention_end) = find_mention_end(&msg.content, conf) { - positions.push(mention_end); - return Some(positions); - } + if !conf.prefixes.is_empty() || conf.dynamic_prefix.is_some() { + // Determine if a prefix was used. Otherwise return None. + let mut positions = Vec::new(); // Dynamic prefixes, if present and suitable, always have a higher priority. if let Some(x) = conf.dynamic_prefix.as_ref().and_then(|f| f(ctx, msg)) { @@ -390,10 +389,6 @@ pub fn positions(ctx: &mut Context, msg: &Message, conf: &Configuration) -> Opti } Some(positions) - } else if conf.on_mention.is_some() { - find_mention_end(&msg.content, conf).map(|mention_end| { - vec![mention_end] // This can simply be returned without trying to find the end whitespaces as trim will remove it later - }) } else { None } diff --git a/src/framework/standard/configuration.rs b/src/framework/standard/configuration.rs index f3d66d6..c237286 100644 --- a/src/framework/standard/configuration.rs +++ b/src/framework/standard/configuration.rs @@ -16,6 +16,8 @@ use super::command::PrefixCheck; /// This allows setting configurations like the depth to search for commands, /// whether to treat mentions like a command prefix, etc. /// +/// To see the default values, refer to the [default implementation]. +/// /// # Examples /// /// Responding to mentions and setting a command prefix of `"~"`: @@ -39,6 +41,7 @@ use super::command::PrefixCheck; /// /// [`Client`]: ../../client/struct.Client.html /// [`Framework`]: struct.Framework.html +/// [default implementation]: #impl-Default pub struct Configuration { #[doc(hidden)] pub allow_dm: bool, #[doc(hidden)] pub allow_whitespace: bool, @@ -60,6 +63,8 @@ pub struct Configuration { impl Configuration { /// If set to false, bot will ignore any private messages. + /// + /// **Note**: Defaults to `true`. pub fn allow_dm(mut self, allow_dm: bool) -> Self { self.allow_dm = allow_dm; @@ -96,7 +101,9 @@ impl Configuration { self } - /// HashSet of guild Ids where commands will be ignored. + /// HashSet of channels Ids where commands will be working. + /// + /// **Note**: Defaults to an empty HashSet. /// /// # Examples /// @@ -108,19 +115,21 @@ impl Configuration { /// # /// # impl EventHandler for Handler {} /// # let mut client = Client::new("token", Handler).unwrap(); - /// use serenity::model::id::GuildId; + /// use serenity::model::id::ChannelId; /// use serenity::framework::StandardFramework; /// /// client.with_framework(StandardFramework::new().configure(|c| c - /// .blocked_guilds(vec![GuildId(7), GuildId(77)].into_iter().collect()))); + /// .allowed_channels(vec![ChannelId(7), ChannelId(77)].into_iter().collect()))); /// ``` - pub fn blocked_guilds(mut self, guilds: HashSet<GuildId>) -> Self { - self.blocked_guilds = guilds; + pub fn allowed_channels(mut self, channels: HashSet<ChannelId>) -> Self { + self.allowed_channels = channels; self } - /// HashSet of channels Ids where commands will be working. + /// HashSet of guild Ids where commands will be ignored. + /// + /// **Note**: Defaults to an empty HashSet. /// /// # Examples /// @@ -132,21 +141,24 @@ impl Configuration { /// # /// # impl EventHandler for Handler {} /// # let mut client = Client::new("token", Handler).unwrap(); - /// use serenity::model::id::ChannelId; + /// use serenity::model::id::GuildId; /// use serenity::framework::StandardFramework; /// /// client.with_framework(StandardFramework::new().configure(|c| c - /// .allowed_channels(vec![ChannelId(7), ChannelId(77)].into_iter().collect()))); + /// .blocked_guilds(vec![GuildId(7), GuildId(77)].into_iter().collect()))); /// ``` - pub fn allowed_channels(mut self, channels: HashSet<ChannelId>) -> Self { - self.allowed_channels = channels; + pub fn blocked_guilds(mut self, guilds: HashSet<GuildId>) -> Self { + self.blocked_guilds = guilds; self } /// HashSet of user Ids whose commands will be ignored. + /// /// Guilds owned by user Ids will also be ignored. /// + /// **Note**: Defaults to an empty HashSet. + /// /// # Examples /// /// Create a HashSet in-place: @@ -169,9 +181,12 @@ impl Configuration { self } - /// The default depth of the message to check for commands. Defaults to 5. + /// The default depth of the message to check for commands. + /// /// This determines how "far" into a message to check for a valid command. /// + /// **Note**: Defaults to 5. + /// /// # Examples /// /// If you set a depth of `1`, and make a command of `"music play"`, but @@ -185,6 +200,8 @@ impl Configuration { /// HashSet of command names that won't be run. /// + /// **Note**: Defaults to an empty HashSet. + /// /// # Examples /// /// Ignore a set of commands, assuming they exist: @@ -218,6 +235,8 @@ impl Configuration { /// Return `None` to not have a special prefix for the dispatch, and to /// instead use the inherited prefix. /// + /// **Note**: Defaults to no dynamic prefix check. + /// /// # Examples /// /// If the Id of the channel is divisible by 5, return a prefix of `"!"`, @@ -256,6 +275,8 @@ impl Configuration { /// /// For example, if this is set to false, then the bot will respond to any /// other bots including itself. + /// + /// **Note**: Defaults to `true`. pub fn ignore_bots(mut self, ignore_bots: bool) -> Self { self.ignore_bots = ignore_bots; @@ -263,7 +284,8 @@ impl Configuration { } /// If set to true, bot will ignore all commands called by webhooks. - /// True by default. + /// + /// **Note**: Defaults to `true`. pub fn ignore_webhooks(mut self, ignore_webhooks: bool) -> Self { self.ignore_webhooks = ignore_webhooks; @@ -273,7 +295,7 @@ impl Configuration { /// Whether or not to respond to commands initiated with a mention. Note /// that this can be used in conjunction with [`prefix`]. /// - /// By default this is set to `false`. + /// **Note**: Defaults to `false`. /// /// # Examples /// @@ -308,6 +330,8 @@ impl Configuration { /// A `HashSet` of user Ids checks won't apply to. /// + /// **Note**: Defaults to an empty HashSet. + /// /// # Examples /// /// Create a HashSet in-place: @@ -352,6 +376,8 @@ impl Configuration { /// Sets the prefix to respond to. A prefix can be a string slice of any /// non-zero length. /// + /// **Note**: Defaults to an empty vector. + /// /// # Examples /// /// Assign a basic prefix: @@ -377,6 +403,8 @@ impl Configuration { /// Sets the prefixes to respond to. Each can be a string slice of any /// non-zero length. /// + /// **Note**: Refer to [`prefix`] for the default value. + /// /// # Examples /// /// Assign a set of prefixes the bot can respond to: @@ -393,6 +421,8 @@ impl Configuration { /// client.with_framework(StandardFramework::new().configure(|c| c /// .prefixes(vec!["!", ">", "+"]))); /// ``` + /// + /// [`prefix`]: #method.prefix pub fn prefixes<T: ToString, It: IntoIterator<Item=T>>(mut self, prefixes: It) -> Self { self.prefixes = prefixes.into_iter().map(|x| x.to_string()).collect(); @@ -401,7 +431,10 @@ impl Configuration { /// Sets whether command execution can done without a prefix. Works only in private channels. /// + /// **Note**: Defaults to `false`. + /// /// # Note + /// /// Needs the `cache` feature to be enabled. Otherwise this does nothing. pub fn no_dm_prefix(mut self, b: bool) -> Self { self.no_dm_prefix = b; @@ -411,6 +444,8 @@ impl Configuration { /// Sets a delimiter to be used when splitting the content after a command. /// + /// **Note**: Defaults to a vector with a single element of `" "`. + /// /// # Examples /// /// Have the args be seperated by a comma and a space: @@ -436,6 +471,8 @@ impl Configuration { /// Sets multiple delimiters to be used when splitting the content after a command. /// Additionally cleans the default delimiter from the vector. /// + /// **Note**: Refer to [`delimiter`] for the default value. + /// /// # Examples /// /// Have the args be seperated by a comma and a space; and a regular space: @@ -452,6 +489,8 @@ impl Configuration { /// client.with_framework(StandardFramework::new().configure(|c| c /// .delimiters(vec![", ", " "]))); /// ``` + /// + /// [`delimiter`]: #method.delimiter pub fn delimiters<T: ToString, It: IntoIterator<Item=T>>(mut self, delimiters: It) -> Self { self.delimiters.clear(); self.delimiters @@ -460,9 +499,13 @@ impl Configuration { self } - /// Whether the framework shouldn't care about the user's input if it's: `~command`, - /// `~Command`, `~COMMAND`. - /// Setting this to `true` will result in *all* command names to be case insensitive. + /// Whether the framework shouldn't care about the user's input if it's: + /// `~command`, `~Command`, or `~COMMAND`. + /// + /// Setting this to `true` will result in *all* command names to be case + /// insensitive. + /// + /// **Note**: Defaults to `false`. pub fn case_insensitivity(mut self, cs: bool) -> Self { self.case_insensitive = cs; @@ -473,31 +516,40 @@ impl Configuration { impl Default for Configuration { /// Builds a default framework configuration, setting the following: /// + /// - **allow_dm** to `true` /// - **allow_whitespace** to `false` + /// - **allowed_channels** to an empty HashSet + /// - **blocked_guilds** to an empty HashSet + /// - **blocked_users** to an empty HashSet + /// - **case_insensitive** to `false` + /// - **delimiters** to `vec![" "]` /// - **depth** to `5` - /// - **on_mention** to `false` (basically) - /// - **prefix** to `None` + /// - **disabled_commands** to an empty HashSet + /// - **dynamic_prefix** to no dynamic prefix check + /// - **ignore_bots** to `true` + /// - **ignore_webhooks** to `true` /// - **no_dm_prefix** to `false` - /// - **delimiters** to vec![" "] - /// - **case_insensitive** to `false` + /// - **on_mention** to `false` (basically) + /// - **owners** to an empty HashSet + /// - **prefix** to an empty vector fn default() -> Configuration { Configuration { - depth: 5, - on_mention: None, - dynamic_prefix: None, + allow_dm: true, allow_whitespace: false, - prefixes: vec![], - no_dm_prefix: false, - ignore_bots: true, - owners: HashSet::default(), - blocked_users: HashSet::default(), - blocked_guilds: HashSet::default(), allowed_channels: HashSet::default(), - disabled_commands: HashSet::default(), - allow_dm: true, - ignore_webhooks: true, + blocked_guilds: HashSet::default(), + blocked_users: HashSet::default(), case_insensitive: false, delimiters: vec![" ".to_string()], + depth: 5, + disabled_commands: HashSet::default(), + dynamic_prefix: None, + ignore_bots: true, + ignore_webhooks: true, + no_dm_prefix: false, + on_mention: None, + owners: HashSet::default(), + prefixes: vec![], } } } diff --git a/src/framework/standard/mod.rs b/src/framework/standard/mod.rs index 0bb0309..4aa462e 100644 --- a/src/framework/standard/mod.rs +++ b/src/framework/standard/mod.rs @@ -1146,12 +1146,13 @@ impl Framework for StandardFramework { } if check_contains_group_prefix { + if let &Some(CommandOrAlias::Command(ref command)) = &group.default_command { let command = Arc::clone(command); threadpool.execute(move || { if let Some(before) = before { - if !(before)(&mut context, &message, &built) { + if !(before)(&mut context, &message, &to_check) { return; } } diff --git a/src/http/mod.rs b/src/http/mod.rs index c50679a..7d0ca17 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -24,21 +24,22 @@ //! [model]: ../model/index.html pub mod ratelimiting; +pub mod request; +pub mod routing; mod error; -pub use self::error::Error as HttpError; pub use hyper::status::{StatusClass, StatusCode}; +pub use self::error::Error as HttpError; use constants; use hyper::{ client::{ Client as HyperClient, - Request, - RequestBuilder, + Request as HyperRequest, Response as HyperResponse }, - header::ContentType, + header::{ContentType, Headers}, method::Method, mime::{Mime, SubLevel, TopLevel}, net::HttpsConnector, @@ -52,25 +53,35 @@ use internal::prelude::*; use model::prelude::*; use multipart::client::Multipart; use parking_lot::Mutex; -use self::ratelimiting::Route; +use self::{ + request::Request, + routing::RouteInfo, +}; +use serde::de::DeserializeOwned; use serde_json; use std::{ collections::BTreeMap, default::Default, - fmt::Write as FmtWrite, fs::File, io::ErrorKind as IoErrorKind, path::{Path, PathBuf}, sync::Arc }; +lazy_static! { + static ref CLIENT: HyperClient = { + let tc = NativeTlsClient::new().expect("Unable to make http client"); + let connector = HttpsConnector::new(tc); + + HyperClient::with_connector(connector) + }; +} + /// 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,6 +94,18 @@ pub enum LightMethod { Put, } +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, + } + } +} + lazy_static! { static ref TOKEN: Arc<Mutex<String>> = Arc::new(Mutex::new(String::default())); } @@ -121,16 +144,11 @@ pub fn set_token(token: &str) { TOKEN.lock().clone_from(&token.to_string()); } /// [`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 - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::AddGroupRecipient { group_id, user_id }, + }) } /// Adds a single [`Role`] to a [`Member`] in a [`Guild`]. @@ -143,17 +161,11 @@ pub fn add_group_recipient(group_id: u64, user_id: u64) -> Result<()> { /// [`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 - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::AddMemberRole { guild_id, role_id, user_id }, + }) } /// Bans a [`User`] from a [`Guild`], removing their messages sent in the last @@ -168,18 +180,16 @@ pub fn add_member_role(guild_id: u64, user_id: u64, role_id: u64) -> Result<()> /// [`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={}", + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::GuildBanUser { + delete_message_days: Some(delete_message_days), + reason: Some(reason), guild_id, user_id, - delete_message_days, - reason - ), - ) + }, + }) } /// Ban zeyla from a [`Guild`], removing her messages sent in the last X number @@ -235,15 +245,11 @@ pub fn ban_servermoms(guild_id: u64, delete_message_days: u8, reason: &str) -> R /// /// [`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 - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::BroadcastTyping { channel_id }, + }) } /// Creates a [`GuildChannel`] in the [`Guild`] given its Id. @@ -257,16 +263,11 @@ pub fn broadcast_typing(channel_id: u64) -> Result<()> { /// [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) + fire(Request { + body: Some(map.to_string().as_bytes()), + headers: None, + route: RouteInfo::CreateChannel { guild_id }, + }) } /// Creates an emoji in the given [`Guild`] with the given data. @@ -280,16 +281,11 @@ pub fn create_channel(guild_id: u64, map: &Value) -> Result<GuildChannel> { /// [`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) + fire(Request { + body: Some(map.to_string().as_bytes()), + headers: None, + route: RouteInfo::CreateEmoji { guild_id }, + }) } /// Creates a guild with the data provided. @@ -329,11 +325,11 @@ pub fn create_emoji(guild_id: u64, map: &Value) -> Result<Emoji> { /// 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) + fire(Request { + body: Some(map.to_string().as_bytes()), + headers: None, + route: RouteInfo::CreateGuild, + }) } /// Creates an [`Integration`] for a [`Guild`]. @@ -347,18 +343,11 @@ pub fn create_guild(map: &Value) -> Result<PartialGuild> { /// [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/{}", - guild_id, - integration_id - ), - ) + wind(204, Request { + body: Some(map.to_string().as_bytes()), + headers: None, + route: RouteInfo::CreateGuildIntegration { guild_id, integration_id }, + }) } /// Creates a [`RichInvite`] for the given [channel][`GuildChannel`]. @@ -374,41 +363,35 @@ pub fn create_guild_integration(guild_id: u64, integration_id: u64, map: &Value) /// [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) + let body = serde_json::to_vec(map)?; + + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::CreateInvite { channel_id }, + }) } /// 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(); - - verify( - 204, - request!( - Route::ChannelsIdPermissionsOverwriteId(channel_id), - put(body), - "/channels/{}/permissions/{}", - channel_id, - target_id - ), - ) + let body = serde_json::to_vec(map)?; + + wind(204, Request { + body: Some(&body), + headers: None, + route: RouteInfo::CreatePermission { channel_id, target_id }, + }) } /// 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"); + let body = serde_json::to_vec(map)?; - serde_json::from_reader::<HyperResponse, PrivateChannel>(response) - .map_err(From::from) + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::GetUserDmChannels, + }) } /// Reacts to a message. @@ -416,31 +399,26 @@ 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", + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::CreateReaction { + reaction: &reaction_type.as_data(), channel_id, message_id, - reaction_type.as_data() - ), - ) + }, + }) } /// 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) + let body = serde_json::to_vec(map)?; + + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::CreateRole {guild_id }, + }) } /// Creates a webhook for the given [channel][`GuildChannel`]'s Id, passing in @@ -474,103 +452,77 @@ pub fn create_role(guild_id: u64, map: &JsonMap) -> Result<Role> { /// /// [`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) + let body = serde_json::to_vec(map)?; + + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::CreateWebhook { channel_id }, + }) } /// 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) + fire(Request { + body: None, + headers: None, + route: RouteInfo::DeleteChannel { channel_id }, + }) } /// 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 - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::DeleteEmoji { guild_id, emoji_id }, + }) } /// 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); - - serde_json::from_reader::<HyperResponse, PartialGuild>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::DeleteGuild { guild_id }, + }) } /// 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/{}", - guild_id, - integration_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::DeleteGuildIntegration { guild_id, integration_id }, + }) } /// Deletes an invite by code. pub fn delete_invite(code: &str) -> Result<Invite> { - let response = request!(Route::InvitesCode, delete, "/invites/{}", code); - - serde_json::from_reader::<HyperResponse, Invite>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::DeleteInvite { code }, + }) } /// 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 - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::DeleteMessage { channel_id, message_id }, + }) } /// 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 - ), - ) + wind(204, Request { + body: Some(map.to_string().as_bytes()), + headers: None, + route: RouteInfo::DeleteMessages { channel_id }, + }) } /// Deletes all of the [`Reaction`]s associated with a [`Message`]. @@ -591,30 +543,20 @@ pub fn delete_messages(channel_id: u64, map: &Value) -> Result<()> { /// [`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 - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::DeleteMessageReactions { channel_id, message_id }, + }) } /// 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/{}", - channel_id, - target_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::DeletePermission { channel_id, target_id }, + }) } /// Deletes a reaction from a message if owned by us or @@ -628,32 +570,25 @@ pub fn delete_reaction(channel_id: u64, .map(|uid| uid.to_string()) .unwrap_or_else(|| "@me".to_string()); - verify( - 204, - request!( - Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), - delete, - "/channels/{}/messages/{}/reactions/{}/{}", + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::DeleteReaction { + reaction: &reaction_type.as_data(), + user: &user, channel_id, message_id, - reaction_type.as_data(), - user - ), - ) + }, + }) } /// 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 - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::DeleteRole { guild_id, role_id }, + }) } /// Deletes a [`Webhook`] given its Id. @@ -679,15 +614,11 @@ pub fn delete_role(guild_id: u64, role_id: u64) -> Result<()> { /// [`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, - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::DeleteWebhook { webhook_id }, + }) } /// Deletes a [`Webhook`] given its Id and unique token. @@ -709,123 +640,93 @@ pub fn delete_webhook(webhook_id: u64) -> Result<()> { /// /// [`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)?, - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::DeleteWebhookWithToken { token, webhook_id }, + }) } /// 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) + let body = serde_json::to_vec(map)?; + + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditChannel {channel_id }, + }) } /// 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) + let body = serde_json::to_vec(map)?; + + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditEmoji { guild_id, emoji_id }, + }) } /// 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) + let body = serde_json::to_vec(map)?; + + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditGuild { guild_id }, + }) } /// 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, - ), - ) + let body = serde_json::to_vec(value)?; + + wind(204, Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditGuildChannels { guild_id }, + }) } /// 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) + let body = serde_json::to_vec(map)?; + + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditGuildEmbed { guild_id }, + }) } /// 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)?; - - verify( - 204, - request!( - Route::GuildsIdMembersId(guild_id), - patch(body), - "/guilds/{}/members/{}", - guild_id, - user_id - ), - ) + let body = serde_json::to_vec(map)?; + + wind(204, Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditMember { guild_id, user_id }, + }) } /// 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 - ); + let body = serde_json::to_vec(map)?; - serde_json::from_reader::<HyperResponse, Message>(response) - .map_err(From::from) + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditMessage { channel_id, message_id }, + }) } /// Edits the current user's nickname for the provided [`Guild`] via its Id. @@ -835,15 +736,13 @@ pub fn edit_message(channel_id: u64, message_id: u64, map: &Value) -> Result<Mes /// [`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 - ); + let body = serde_json::to_vec(&map)?; - verify(200, response) + wind(200, Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditNickname { guild_id }, + }) } /// Edits the current user's profile settings. @@ -859,8 +758,13 @@ pub fn edit_nickname(guild_id: u64, new_nickname: Option<&str>) -> Result<()> { /// change and when the token is internally changed to be invalid requests, as /// the token may be outdated. pub fn edit_profile(map: &JsonMap) -> Result<CurrentUser> { - let body = serde_json::to_string(map)?; - let response = request!(Route::UsersMe, patch(body), "/users/@me"); + let body = serde_json::to_vec(map)?; + + let response = request(Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditProfile, + })?; let mut value = serde_json::from_reader::<HyperResponse, Value>(response)?; @@ -872,41 +776,32 @@ pub fn edit_profile(map: &JsonMap) -> Result<CurrentUser> { } } - serde_json::from_value::<CurrentUser>(value) - .map_err(From::from) + serde_json::from_value::<CurrentUser>(value).map_err(From::from) } /// 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) + let body = serde_json::to_vec(&map)?; + + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditRole { guild_id, role_id }, + }) } /// 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!({ + let body = serde_json::to_vec(&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) + + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditRole { guild_id, role_id }, + }) } /// Edits a the webhook with the given data. @@ -949,16 +844,11 @@ pub fn edit_role_position(guild_id: u64, role_id: u64, position: u64) -> Result< // 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) + fire(Request { + body: Some(map.to_string().as_bytes()), + headers: None, + route: RouteInfo::EditWebhook { webhook_id }, + }) } /// Edits the webhook with the given data. @@ -988,17 +878,13 @@ pub fn edit_webhook(webhook_id: u64, map: &Value) -> Result<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)?; + let body = serde_json::to_vec(map)?; - serde_json::from_reader::<HyperResponse, Webhook>(response) - .map_err(From::from) + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditWebhookWithToken { token, webhook_id }, + }) } /// Executes a webhook, posting a [`Message`] in the webhook's associated @@ -1066,23 +952,18 @@ pub fn execute_webhook(webhook_id: u64, 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)?; + let body = serde_json::to_vec(map)?; + + let mut headers = Headers::new(); + headers.set(ContentType( + Mime(TopLevel::Application, SubLevel::Json, vec![]), + )); + + let response = request(Request { + body: Some(&body), + headers: Some(headers), + route: RouteInfo::ExecuteWebhook { token, wait, webhook_id }, + })?; if response.status == StatusCode::NoContent { return Ok(None); @@ -1097,10 +978,10 @@ pub fn execute_webhook(webhook_id: u64, /// /// Does not require authentication. pub fn get_active_maintenances() -> Result<Vec<Maintenance>> { - let client = request_client!(); - - let response = retry(|| { - client.get(status!("/scheduled-maintenances/active.json")) + let response = request(Request { + body: None, + headers: None, + route: RouteInfo::GetActiveMaintenance, })?; let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?; @@ -1114,15 +995,11 @@ pub fn get_active_maintenances() -> Result<Vec<Maintenance>> { /// 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) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetBans { guild_id }, + }) } /// Gets all audit logs in a specific guild. @@ -1131,57 +1008,35 @@ pub fn get_audit_logs(guild_id: u64, user_id: Option<u64>, before: Option<u64>, limit: Option<u8>) -> Result<AuditLogs> { - let mut params = Vec::with_capacity(4); - - if let Some(action_type) = action_type { - params.push(format!("action_type={}", action_type)); - } - if let Some(user_id) = user_id { - params.push(format!("user_id={}", user_id)); - } - if let Some(before) = before { - params.push(format!("before={}", before)); - } - if let Some(limit) = limit { - params.push(format!("limit={}", limit)); - } - - let mut query_string = params.join("&"); - if !query_string.is_empty() { - query_string.insert(0, '?'); - } - - let response = request!( - Route::GuildsIdAuditLogs(guild_id), - get, - "/guilds/{}/audit-logs{}", - guild_id, - query_string - ); - - serde_json::from_reader::<HyperResponse, AuditLogs>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetAuditLogs { + action_type, + before, + guild_id, + limit, + user_id, + }, + }) } /// Gets current bot gateway. pub fn get_bot_gateway() -> Result<BotGateway> { - let response = request!(Route::GatewayBot, get, "/gateway/bot"); - - serde_json::from_reader::<HyperResponse, BotGateway>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetBotGateway, + }) } /// 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) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetChannelInvites { channel_id }, + }) } /// Retrieves the webhooks for the given [channel][`GuildChannel`]'s Id. @@ -1203,114 +1058,94 @@ pub fn get_channel_invites(channel_id: u64) -> Result<Vec<RichInvite>> { /// /// [`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) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetChannelWebhooks { channel_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) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetChannel { channel_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) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetChannels { 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"); - - serde_json::from_reader::<HyperResponse, CurrentApplicationInfo>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetCurrentApplicationInfo, + }) } /// Gets information about the user we're connected with. pub fn get_current_user() -> Result<CurrentUser> { - let response = request!(Route::UsersMe, get, "/users/@me"); - - serde_json::from_reader::<HyperResponse, CurrentUser>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetCurrentUser, + }) } /// Gets current gateway. pub fn get_gateway() -> Result<Gateway> { - let response = request!(Route::Gateway, get, "/gateway"); - - serde_json::from_reader::<HyperResponse, Gateway>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGateway, + }) } /// Gets guild information. pub fn get_guild(guild_id: u64) -> Result<PartialGuild> { - let response = request!(Route::GuildsId(guild_id), get, "/guilds/{}", guild_id); - - serde_json::from_reader::<HyperResponse, PartialGuild>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGuild { guild_id }, + }) } /// 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) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGuildEmbed { 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) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGuildIntegrations { guild_id }, + }) } /// 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) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGuildInvites { guild_id }, + }) } /// Gets a guild's vanity URL if it has one. @@ -1320,12 +1155,11 @@ pub fn get_guild_vanity_url(guild_id: u64) -> Result<String> { code: String, } - let response = request!( - Route::GuildsIdVanityUrl(guild_id), - get, - "/guilds/{}/vanity-url", - guild_id - ); + let response = request(Request { + body: None, + headers: None, + route: RouteInfo::GetGuildVanityUrl { guild_id }, + })?; serde_json::from_reader::<HyperResponse, GuildVanityUrl>(response) .map(|x| x.code) @@ -1338,14 +1172,11 @@ 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 response = request(Request { + body: None, + headers: None, + route: RouteInfo::GetGuildMembers { after, guild_id, limit }, + })?; let mut v = serde_json::from_reader::<HyperResponse, Value>(response)?; @@ -1364,45 +1195,43 @@ pub fn get_guild_members(guild_id: u64, /// 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) + // Note for 0.6.x: turn this into a function parameter. + #[derive(Deserialize)] + struct GetGuildPruneCountRequest { + days: u64, + } + + let req = serde_json::from_value::<GetGuildPruneCountRequest>(map.clone())?; + + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGuildPruneCount { + days: req.days, + guild_id, + }, + }) } /// 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) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGuildRegions { guild_id }, + }) } /// 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) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGuildRoles { guild_id }, + }) } /// Retrieves the webhooks for the given [guild][`Guild`]'s Id. @@ -1424,15 +1253,11 @@ pub fn get_guild_roles(guild_id: u64) -> Result<Vec<Role>> { /// /// [`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) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGuildWebhooks { guild_id }, + }) } /// Gets a paginated list of the current user's guilds. @@ -1456,54 +1281,40 @@ pub fn get_guild_webhooks(guild_id: u64) -> Result<Vec<Webhook>> { /// /// [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); - - match *target { - GuildPagination::After(id) => { - write!(uri, "&after={}", id)?; - }, - GuildPagination::Before(id) => { - write!(uri, "&before={}", id)?; - }, - } - - let response = request!(Route::UsersMeGuilds, get, "{}", uri); + let (after, before) = match *target { + GuildPagination::After(id) => (Some(id.0), None), + GuildPagination::Before(id) => (None, Some(id.0)), + }; - serde_json::from_reader::<HyperResponse, Vec<GuildInfo>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGuilds { after, before, limit }, + }) } /// Gets information about a specific invite. #[allow(unused_mut)] -pub fn get_invite(code: &str, stats: bool) -> Result<Invite> { - let mut invite = code; - +pub fn get_invite(mut code: &str, stats: bool) -> Result<Invite> { #[cfg(feature = "utils")] { - invite = ::utils::parse_invite(invite); - } - - let mut uri = format!("/invites/{}", invite); - - if stats { - uri.push_str("?with_counts=true"); + code = ::utils::parse_invite(code); } - let response = request!(Route::InvitesCode, get, "{}", uri); - - serde_json::from_reader::<HyperResponse, Invite>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetInvite { code, stats }, + }) } /// 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 - ); + let response = request(Request { + body: None, + headers: None, + route: RouteInfo::GetMember { guild_id, user_id }, + })?; let mut v = serde_json::from_reader::<HyperResponse, Value>(response)?; @@ -1516,40 +1327,32 @@ pub fn get_member(guild_id: u64, user_id: u64) -> Result<Member> { /// 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) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetMessage { channel_id, message_id }, + }) } /// 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!(); - - let response = request(Route::ChannelsIdMessages(channel_id), || client.get(&url))?; - - serde_json::from_reader::<HyperResponse, Vec<Message>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetMessages { + query: query.to_owned(), + channel_id, + }, + }) } /// 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) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetPins { channel_id }, + }) } /// Gets user Ids based on their reaction to a message. This endpoint is dumb. @@ -1559,36 +1362,30 @@ pub fn get_reaction_users(channel_id: u64, 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 - ); + let reaction = reaction_type.as_data(); - serde_json::from_reader::<HyperResponse, Vec<User>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetReactionUsers { + after, + channel_id, + limit, + message_id, + reaction, + }, + }) } /// 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!(); - - let response = retry(|| client.get(status!("/incidents/unresolved.json")))?; + let response = request(Request { + body: None, + headers: None, + route: RouteInfo::GetUnresolvedIncidents, + })?; let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?; @@ -1603,10 +1400,10 @@ pub fn get_unresolved_incidents() -> Result<Vec<Incident>> { /// /// Does not require authentication. pub fn get_upcoming_maintenances() -> Result<Vec<Maintenance>> { - let client = request_client!(); - - let response = retry(|| { - client.get(status!("/scheduled-maintenances/upcoming.json")) + let response = request(Request { + body: None, + headers: None, + route: RouteInfo::GetUpcomingMaintenances, })?; let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?; @@ -1620,26 +1417,29 @@ pub fn get_upcoming_maintenances() -> Result<Vec<Maintenance>> { /// Gets a user by Id. pub fn get_user(user_id: u64) -> Result<User> { - let response = request!(Route::UsersId, get, "/users/{}", user_id); - - serde_json::from_reader::<HyperResponse, User>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetUser { user_id }, + }) } /// Gets our DM channels. pub fn get_user_dm_channels() -> Result<Vec<PrivateChannel>> { - let response = request!(Route::UsersMeChannels, get, "/users/@me/channels"); - - serde_json::from_reader::<HyperResponse, Vec<PrivateChannel>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetUserDmChannels, + }) } /// Gets all voice regions. pub fn get_voice_regions() -> Result<Vec<VoiceRegion>> { - let response = request!(Route::VoiceRegions, get, "/voice/regions"); - - serde_json::from_reader::<HyperResponse, Vec<VoiceRegion>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetVoiceRegions, + }) } /// Retrieves a webhook given its Id. @@ -1660,15 +1460,11 @@ pub fn get_voice_regions() -> Result<Vec<VoiceRegion>> { /// /// [`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) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetWebhook { webhook_id }, + }) } /// Retrieves a webhook given its Id and unique token. @@ -1689,64 +1485,47 @@ pub fn get_webhook(webhook_id: u64) -> Result<Webhook> { /// .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) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetWebhookWithToken { token, webhook_id }, + }) } /// 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 - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::KickMember { guild_id, user_id }, + }) } /// Leaves a group DM. -pub fn leave_group(guild_id: u64) -> Result<Group> { - let response = request!(Route::None, delete, "/channels/{}", guild_id); - - serde_json::from_reader::<HyperResponse, Group>(response) - .map_err(From::from) +pub fn leave_group(group_id: u64) -> Result<Group> { + fire(Request { + body: None, + headers: None, + route: RouteInfo::LeaveGroup { group_id }, + }) } /// Leaves a guild. pub fn leave_guild(guild_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::UsersMeGuildsId, - delete, - "/users/@me/guilds/{}", - guild_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::LeaveGuild { guild_id }, + }) } /// 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 - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::RemoveGroupRecipient { group_id, user_id }, + }) } /// Sends file(s) to a channel. @@ -1760,7 +1539,7 @@ pub fn remove_group_recipient(group_id: u64, user_id: u64) -> Result<()> { /// [`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 uri = api!("/channels/{}/messages", channel_id); let url = match Url::parse(&uri) { Ok(url) => url, Err(_) => return Err(Error::Url(uri)), @@ -1768,7 +1547,7 @@ pub fn send_files<'a, T, It: IntoIterator<Item=T>>(channel_id: u64, files: It, m let tc = NativeTlsClient::new()?; let connector = HttpsConnector::new(tc); - let mut request = Request::with_connector(Method::Post, url, &connector)?; + let mut request = HyperRequest::with_connector(Method::Post, url, &connector)?; request .headers_mut() .set(header::Authorization(TOKEN.lock().clone())); @@ -1817,50 +1596,36 @@ pub fn send_files<'a, T, It: IntoIterator<Item=T>>(channel_id: u64, files: It, m return Err(Error::Http(HttpError::UnsuccessfulRequest(response))); } - serde_json::from_reader::<HyperResponse, Message>(response) - .map_err(From::from) + serde_json::from_reader(response).map_err(From::from) } /// 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 - ); + let body = serde_json::to_vec(map)?; - serde_json::from_reader::<HyperResponse, Message>(response) - .map_err(From::from) + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::CreateMessage { channel_id }, + }) } /// 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 - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::PinMessage { channel_id, message_id }, + }) } /// 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 - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::RemoveBan { guild_id, user_id }, + }) } /// Deletes a single [`Role`] from a [`Member`] in a [`Guild`]. @@ -1873,67 +1638,144 @@ pub fn remove_ban(guild_id: u64, user_id: u64) -> Result<()> { /// [`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 - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::RemoveMemberRole { 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) + // Note for 0.6.x: turn this into a function parameter. + #[derive(Deserialize)] + struct StartGuildPruneRequest { + days: u64, + } + + let req = serde_json::from_value::<StartGuildPruneRequest>(map.clone())?; + + fire(Request { + body: None, + headers: None, + route: RouteInfo::StartGuildPrune { + days: req.days, + guild_id, + }, + }) } /// 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 - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::StartIntegrationSync { 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 - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::UnpinMessage { 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()) - })?; +/// Fires off a request, deserializing the response reader via the given type +/// bound. +/// +/// If you don't need to deserialize the response and want the response instance +/// itself, use [`request`]. +/// +/// # Examples +/// +/// Create a new message via the [`RouteInfo::CreateMessage`] endpoint and +/// deserialize the response into a [`Message`]: +/// +/// ```rust,no_run +/// # extern crate serenity; +/// # +/// # use std::error::Error; +/// # +/// # fn main() -> Result<(), Box<Error>> { +/// # +/// use serenity::{ +/// http::{ +/// self, +/// request::RequestBuilder, +/// routing::RouteInfo, +/// }, +/// model::channel::Message, +/// }; +/// +/// let bytes = vec![ +/// // payload bytes here +/// ]; +/// let channel_id = 381880193700069377; +/// let route_info = RouteInfo::CreateMessage { channel_id }; +/// +/// let mut request = RequestBuilder::new(route_info); +/// request.body(Some(&bytes)); +/// +/// let message = http::fire::<Message>(request.build())?; +/// +/// println!("Message content: {}", message.content); +/// # +/// # Ok(()) +/// # } +/// ``` +/// +/// [`request`]: fn.request.html +pub fn fire<T: DeserializeOwned>(req: Request) -> Result<T> { + let response = request(req)?; + + serde_json::from_reader(response).map_err(From::from) +} + +/// Performs a request, ratelimiting it if necessary. +/// +/// Returns the raw hyper Response. Use [`fire`] to deserialize the response +/// into some type. +/// +/// # Examples +/// +/// Send a body of bytes over the [`RouteInfo::CreateMessage`] endpoint: +/// +/// ```rust,no_run +/// # extern crate serenity; +/// # +/// # use std::error::Error; +/// # +/// # fn main() -> Result<(), Box<Error>> { +/// # +/// use serenity::http::{ +/// self, +/// request::RequestBuilder, +/// routing::RouteInfo, +/// }; +/// +/// let bytes = vec![ +/// // payload bytes here +/// ]; +/// let channel_id = 381880193700069377; +/// let route_info = RouteInfo::CreateMessage { channel_id }; +/// +/// let mut request = RequestBuilder::new(route_info); +/// request.body(Some(&bytes)); +/// +/// let response = http::request(request.build())?; +/// +/// println!("Response successful?: {}", response.status.is_success()); +/// # +/// # Ok(()) +/// # } +/// ``` +/// +/// [`fire`]: fn.fire.html +pub fn request(req: Request) -> Result<HyperResponse> { + let response = ratelimiting::perform(req)?; if response.status.class() == StatusClass::Success { Ok(response) @@ -1942,28 +1784,37 @@ fn request<'a, F>(route: Route, f: F) -> Result<HyperResponse> } } -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, +fn retry(request: &Request) -> HyperResult<HyperResponse> { + // Retry the request twice in a loop until it succeeds. + // + // If it doesn't and the loop breaks, try one last time. + for _ in 0..3 { + match request.build().send() { + Err(HyperError::Io(ref io)) + if io.kind() == IoErrorKind::ConnectionAborted => continue, + other => return other, + } } + + request.build().send() } -fn verify(expected: u16, response: HyperResponse) -> Result<()> { - if response.status.to_u16() == expected { +/// Performs a request and then verifies that the response status code is equal +/// to the expected value. +/// +/// This is a function that performs a light amount of work and returns an +/// empty tuple, so it's called "wind" to denote that it's lightweight. +fn wind(expected: u16, req: Request) -> Result<()> { + let resp = request(req)?; + + if resp.status.to_u16() == expected { return Ok(()); } - debug!("Expected {}, got {}", expected, response.status); - trace!("Unsuccessful response: {:?}", response); + debug!("Expected {}, got {}", expected, resp.status); + trace!("Unsuccessful response: {:?}", resp); - Err(Error::Http(HttpError::UnsuccessfulRequest(response))) + Err(Error::Http(HttpError::UnsuccessfulRequest(resp))) } /// Enum that allows a user to pass a `Path` or a `File` type to `send_files` diff --git a/src/http/ratelimiting.rs b/src/http/ratelimiting.rs index b8152d1..309ba35 100644 --- a/src/http/ratelimiting.rs +++ b/src/http/ratelimiting.rs @@ -40,8 +40,10 @@ //! [Taken from]: https://discordapp.com/developers/docs/topics/rate-limits#rate-limits #![allow(zero_ptr)] +pub use super::routing::Route; + use chrono::{DateTime, Utc}; -use hyper::client::{RequestBuilder, Response}; +use hyper::client::Response; use hyper::header::Headers; use hyper::status::StatusCode; use internal::prelude::*; @@ -54,7 +56,7 @@ use std::{ thread, i64 }; -use super::{HttpError, LightMethod}; +use super::{HttpError, Request}; /// Refer to [`offset`]. /// @@ -102,272 +104,22 @@ lazy_static! { }; } -/// 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 (i.e. user account-only - /// routes). - /// - /// This is a special case, in that if the route is `None` then pre- and - /// post-hooks are not executed. - None, -} - -pub(crate) fn perform<'a, F>(route: Route, f: F) -> Result<Response> - where F: Fn() -> RequestBuilder<'a> { +pub(super) fn perform(req: Request) -> Result<Response> { loop { // This will block if another thread already has the global // unlocked already (due to receiving an x-ratelimit-global). let _ = GLOBAL.lock(); + // Destructure the tuple instead of retrieving the third value to + // take advantage of the type system. If `RouteInfo::deconstruct` + // returns a different number of tuple elements in the future, directly + // accessing a certain index (e.g. `req.route.deconstruct().1`) would + // mean this code would not indicate it might need to be updated for the + // new tuple element amount. + // + // This isn't normally important, but might be for ratelimiting. + let (_, route, _) = req.route.deconstruct(); + // Perform pre-checking here: // // - get the route's relevant rate @@ -390,7 +142,7 @@ pub(crate) fn perform<'a, F>(route: Route, f: F) -> Result<Response> let mut lock = bucket.lock(); lock.pre_hook(&route); - let response = super::retry(&f)?; + let response = super::retry(&req)?; // Check if an offset has been calculated yet to determine the time // difference from Discord can the client. diff --git a/src/http/request.rs b/src/http/request.rs new file mode 100644 index 0000000..92dd073 --- /dev/null +++ b/src/http/request.rs @@ -0,0 +1,116 @@ +use constants; +use hyper::{ + client::{Body, RequestBuilder as HyperRequestBuilder}, + header::{Authorization, ContentType, Headers, UserAgent}, +}; +use super::{ + CLIENT, + TOKEN, + routing::RouteInfo, +}; + +pub struct RequestBuilder<'a> { + body: Option<&'a [u8]>, + headers: Option<Headers>, + route: RouteInfo<'a>, +} + +impl<'a> RequestBuilder<'a> { + pub fn new(route_info: RouteInfo<'a>) -> Self { + Self { + body: None, + headers: None, + route: route_info, + } + } + + pub fn build(self) -> Request<'a> { + Request::new(self) + } + + pub fn body(&mut self, body: Option<&'a [u8]>) -> &mut Self { + self.body = body; + + self + } + + pub fn headers(&mut self, headers: Option<Headers>) -> &mut Self { + self.headers = headers; + + self + } + + pub fn route(&mut self, route_info: RouteInfo<'a>) -> &mut Self { + self.route = route_info; + + self + } +} + +#[derive(Clone, Debug)] +pub struct Request<'a> { + pub(super) body: Option<&'a [u8]>, + pub(super) headers: Option<Headers>, + pub(super) route: RouteInfo<'a>, +} + +impl<'a> Request<'a> { + pub fn new(builder: RequestBuilder<'a>) -> Self { + let RequestBuilder { body, headers, route } = builder; + + Self { body, headers, route } + } + + pub fn build(&'a self) -> HyperRequestBuilder<'a> { + let Request { + body, + headers: ref request_headers, + route: ref route_info, + } = *self; + let (method, _, path) = route_info.deconstruct(); + + let mut builder = CLIENT.request( + method.hyper_method(), + &path.into_owned(), + ); + + if let Some(ref bytes) = body { + builder = builder.body(Body::BufBody(bytes, bytes.len())); + } + + let mut headers = Headers::new(); + headers.set(UserAgent(constants::USER_AGENT.to_string())); + headers.set(Authorization(TOKEN.lock().clone())); + headers.set(ContentType::json()); + + if let Some(request_headers) = request_headers.clone() { + headers.extend(request_headers.iter()); + } + + builder.headers(headers) + } + + pub fn body_ref(&self) -> &Option<&'a [u8]> { + &self.body + } + + pub fn body_mut(&mut self) -> &mut Option<&'a [u8]> { + &mut self.body + } + + pub fn headers_ref(&self) -> &Option<Headers> { + &self.headers + } + + pub fn headers_mut(&mut self) -> &mut Option<Headers> { + &mut self.headers + } + + pub fn route_ref(&self) -> &RouteInfo { + &self.route + } + + pub fn route_mut(&mut self) -> &mut RouteInfo<'a> { + &mut self.route + } +} diff --git a/src/http/routing.rs b/src/http/routing.rs new file mode 100644 index 0000000..dfbbb7f --- /dev/null +++ b/src/http/routing.rs @@ -0,0 +1,1433 @@ +use std::{ + borrow::Cow, + fmt::{Display, Write}, +}; +use super::LightMethod; + +/// 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 (i.e. user account-only + /// routes). + /// + /// This is a special case, in that if the route is `None` then pre- and + /// post-hooks are not executed. + None, +} + +impl Route { + 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, + ) -> String { + api!("/channels/{}/messages/{}/reactions", channel_id, message_id) + } + + pub fn channel_message_reactions_list( + 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, query: Option<&str>) -> String { + format!( + api!("/channels/{}/messages{}"), + channel_id, + query.unwrap_or(""), + ) + } + + 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) + } +} + +#[derive(Clone, Debug)] +pub enum RouteInfo<'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, + }, + StatusIncidentsUnresolved, + StatusMaintenancesActive, + StatusMaintenancesUpcoming, + UnpinMessage { + channel_id: u64, + message_id: u64, + }, +} + +impl<'a> RouteInfo<'a> { + pub fn deconstruct(&self) -> (LightMethod, Route, Cow<str>) { + match *self { + RouteInfo::AddGroupRecipient { group_id, user_id } => ( + LightMethod::Post, + Route::None, + Cow::from(Route::group_recipient(group_id, user_id)), + ), + RouteInfo::AddMemberRole { guild_id, role_id, user_id } => ( + LightMethod::Delete, + Route::GuildsIdMembersIdRolesId(guild_id), + Cow::from(Route::guild_member_role(guild_id, user_id, role_id)), + ), + RouteInfo::GuildBanUser { + guild_id, + delete_message_days, + reason, + user_id, + } => ( + // TODO + LightMethod::Delete, + Route::GuildsIdBansUserId(guild_id), + Cow::from(Route::guild_ban_optioned( + guild_id, + user_id, + delete_message_days.unwrap_or(0), + reason.unwrap_or(""), + )), + ), + RouteInfo::BroadcastTyping { channel_id } => ( + LightMethod::Post, + Route::ChannelsIdTyping(channel_id), + Cow::from(Route::channel_typing(channel_id)), + ), + RouteInfo::CreateChannel { guild_id } => ( + LightMethod::Post, + Route::GuildsIdChannels(guild_id), + Cow::from(Route::guild_channels(guild_id)), + ), + RouteInfo::CreateEmoji { guild_id } => ( + LightMethod::Post, + Route::GuildsIdEmojis(guild_id), + Cow::from(Route::guild_emojis(guild_id)), + ), + RouteInfo::CreateGuild => ( + LightMethod::Post, + Route::Guilds, + Cow::from(Route::guilds()), + ), + RouteInfo::CreateGuildIntegration { guild_id, integration_id } => ( + LightMethod::Post, + Route::GuildsIdIntegrationsId(guild_id), + Cow::from(Route::guild_integration(guild_id, integration_id)), + ), + RouteInfo::CreateInvite { channel_id } => ( + LightMethod::Post, + Route::ChannelsIdInvites(channel_id), + Cow::from(Route::channel_invites(channel_id)), + ), + RouteInfo::CreateMessage { channel_id } => ( + LightMethod::Post, + Route::ChannelsIdMessages(channel_id), + Cow::from(Route::channel_messages(channel_id, None)), + ), + RouteInfo::CreatePermission { channel_id, target_id } => ( + LightMethod::Post, + Route::ChannelsIdPermissionsOverwriteId(channel_id), + Cow::from(Route::channel_permission(channel_id, target_id)), + ), + RouteInfo::CreatePrivateChannel => ( + LightMethod::Post, + Route::UsersMeChannels, + Cow::from(Route::user_dm_channels("@me")), + ), + RouteInfo::CreateReaction { channel_id, message_id, reaction } => ( + LightMethod::Put, + Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), + Cow::from(Route::channel_message_reaction( + channel_id, + message_id, + "@me", + reaction, + )), + ), + RouteInfo::CreateRole { guild_id } => ( + LightMethod::Delete, + Route::GuildsIdRoles(guild_id), + Cow::from(Route::guild_roles(guild_id)), + ), + RouteInfo::CreateWebhook { channel_id } => ( + LightMethod::Delete, + Route::ChannelsIdWebhooks(channel_id), + Cow::from(Route::channel_webhooks(channel_id)), + ), + RouteInfo::DeleteChannel { channel_id } => ( + LightMethod::Delete, + Route::ChannelsId(channel_id), + Cow::from(Route::channel(channel_id)), + ), + RouteInfo::DeleteEmoji { emoji_id, guild_id } => ( + LightMethod::Delete, + Route::GuildsIdEmojisId(guild_id), + Cow::from(Route::guild_emoji(guild_id, emoji_id)), + ), + RouteInfo::DeleteGuild { guild_id } => ( + LightMethod::Delete, + Route::GuildsId(guild_id), + Cow::from(Route::guild(guild_id)), + ), + RouteInfo::DeleteGuildIntegration { guild_id, integration_id } => ( + LightMethod::Delete, + Route::GuildsIdIntegrationsId(guild_id), + Cow::from(Route::guild_integration(guild_id, integration_id)), + ), + RouteInfo::DeleteInvite { code } => ( + LightMethod::Delete, + Route::InvitesCode, + Cow::from(Route::invite(code)), + ), + RouteInfo::DeleteMessageReactions { channel_id, message_id } => ( + LightMethod::Delete, + Route::ChannelsIdMessagesIdReactions(channel_id), + Cow::from(Route::channel_message_reactions( + channel_id, + message_id, + )), + ), + RouteInfo::DeleteMessage { channel_id, message_id } => ( + LightMethod::Delete, + Route::ChannelsIdMessagesId(LightMethod::Delete, message_id), + Cow::from(Route::channel_message(channel_id, message_id)), + ), + RouteInfo::DeleteMessages { channel_id } => ( + LightMethod::Delete, + Route::ChannelsIdMessagesBulkDelete(channel_id), + Cow::from(Route::channel_messages_bulk_delete(channel_id)), + ), + RouteInfo::DeletePermission { channel_id, target_id } => ( + LightMethod::Delete, + Route::ChannelsIdPermissionsOverwriteId(channel_id), + Cow::from(Route::channel_permission(channel_id, target_id)), + ), + RouteInfo::DeleteReaction { + channel_id, + message_id, + reaction, + user, + } => ( + LightMethod::Delete, + Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), + Cow::from(Route::channel_message_reaction( + channel_id, + message_id, + user, + reaction, + )) + ), + RouteInfo::DeleteRole { guild_id, role_id } => ( + LightMethod::Delete, + Route::GuildsIdRolesId(guild_id), + Cow::from(Route::guild_role(guild_id, role_id)), + ), + RouteInfo::DeleteWebhook { webhook_id } => ( + LightMethod::Delete, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook(webhook_id)), + ), + RouteInfo::DeleteWebhookWithToken { token, webhook_id } => ( + LightMethod::Delete, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook_with_token(webhook_id, token)), + ), + RouteInfo::EditChannel { channel_id } => ( + LightMethod::Patch, + Route::ChannelsId(channel_id), + Cow::from(Route::channel(channel_id)), + ), + RouteInfo::EditEmoji { emoji_id, guild_id } => ( + LightMethod::Patch, + Route::GuildsIdEmojisId(guild_id), + Cow::from(Route::guild_emoji(guild_id, emoji_id)), + ), + RouteInfo::EditGuild { guild_id } => ( + LightMethod::Patch, + Route::GuildsId(guild_id), + Cow::from(Route::guild(guild_id)), + ), + RouteInfo::EditGuildChannels { guild_id } => ( + LightMethod::Patch, + Route::GuildsIdChannels(guild_id), + Cow::from(Route::guild_channels(guild_id)), + ), + RouteInfo::EditGuildEmbed { guild_id } => ( + LightMethod::Patch, + Route::GuildsIdEmbed(guild_id), + Cow::from(Route::guild_embed(guild_id)), + ), + RouteInfo::EditMember { guild_id, user_id } => ( + LightMethod::Patch, + Route::GuildsIdMembersId(guild_id), + Cow::from(Route::guild_member(guild_id, user_id)), + ), + RouteInfo::EditMessage { channel_id, message_id } => ( + LightMethod::Patch, + Route::ChannelsIdMessagesId(LightMethod::Patch, channel_id), + Cow::from(Route::channel_message(channel_id, message_id)), + ), + RouteInfo::EditNickname { guild_id } => ( + LightMethod::Patch, + Route::GuildsIdMembersMeNick(guild_id), + Cow::from(Route::guild_nickname(guild_id)), + ), + RouteInfo::EditProfile => ( + LightMethod::Patch, + Route::UsersMe, + Cow::from(Route::user("@me")), + ), + RouteInfo::EditRole { guild_id, role_id } => ( + LightMethod::Patch, + Route::GuildsIdRolesId(guild_id), + Cow::from(Route::guild_role(guild_id, role_id)), + ), + RouteInfo::EditWebhook { webhook_id } => ( + LightMethod::Patch, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook(webhook_id)), + ), + RouteInfo::EditWebhookWithToken { token, webhook_id } => ( + LightMethod::Patch, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook_with_token(webhook_id, token)), + ), + RouteInfo::ExecuteWebhook { token, wait, webhook_id } => ( + LightMethod::Post, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook_with_token_optioned( + webhook_id, + token, + wait, + )), + ), + RouteInfo::GetActiveMaintenance => ( + LightMethod::Get, + Route::None, + Cow::from(Route::status_maintenances_active()), + ), + RouteInfo::GetAuditLogs { + action_type, + before, + guild_id, + limit, + user_id, + } => ( + LightMethod::Get, + Route::GuildsIdAuditLogs(guild_id), + Cow::from(Route::guild_audit_logs( + guild_id, + action_type, + user_id, + before, + limit, + )), + ), + RouteInfo::GetBans { guild_id } => ( + LightMethod::Get, + Route::GuildsIdBans(guild_id), + Cow::from(Route::guild_bans(guild_id)), + ), + RouteInfo::GetBotGateway => ( + LightMethod::Get, + Route::GatewayBot, + Cow::from(Route::gateway_bot()), + ), + RouteInfo::GetChannel { channel_id } => ( + LightMethod::Get, + Route::ChannelsId(channel_id), + Cow::from(Route::channel(channel_id)), + ), + RouteInfo::GetChannelInvites { channel_id } => ( + LightMethod::Get, + Route::ChannelsIdInvites(channel_id), + Cow::from(Route::channel_invites(channel_id)), + ), + RouteInfo::GetChannelWebhooks { channel_id } => ( + LightMethod::Get, + Route::ChannelsIdWebhooks(channel_id), + Cow::from(Route::channel_webhooks(channel_id)), + ), + RouteInfo::GetChannels { guild_id } => ( + LightMethod::Get, + Route::GuildsIdChannels(guild_id), + Cow::from(Route::guild_channels(guild_id)), + ), + RouteInfo::GetCurrentApplicationInfo => ( + LightMethod::Get, + Route::None, + Cow::from(Route::oauth2_application_current()), + ), + RouteInfo::GetCurrentUser => ( + LightMethod::Get, + Route::UsersMe, + Cow::from(Route::user("@me")), + ), + RouteInfo::GetGateway => ( + LightMethod::Get, + Route::Gateway, + Cow::from(Route::gateway()), + ), + RouteInfo::GetGuild { guild_id } => ( + LightMethod::Get, + Route::GuildsId(guild_id), + Cow::from(Route::guild(guild_id)), + ), + RouteInfo::GetGuildEmbed { guild_id } => ( + LightMethod::Get, + Route::GuildsIdEmbed(guild_id), + Cow::from(Route::guild_embed(guild_id)), + ), + RouteInfo::GetGuildIntegrations { guild_id } => ( + LightMethod::Get, + Route::GuildsIdIntegrations(guild_id), + Cow::from(Route::guild_integrations(guild_id)), + ), + RouteInfo::GetGuildInvites { guild_id } => ( + LightMethod::Get, + Route::GuildsIdInvites(guild_id), + Cow::from(Route::guild_invites(guild_id)), + ), + RouteInfo::GetGuildMembers { after, guild_id, limit } => ( + LightMethod::Get, + Route::GuildsIdMembers(guild_id), + Cow::from(Route::guild_members_optioned(guild_id, after, limit)), + ), + RouteInfo::GetGuildPruneCount { days, guild_id } => ( + LightMethod::Get, + Route::GuildsIdPrune(guild_id), + Cow::from(Route::guild_prune(guild_id, days)), + ), + RouteInfo::GetGuildRegions { guild_id } => ( + LightMethod::Get, + Route::GuildsIdRegions(guild_id), + Cow::from(Route::guild_regions(guild_id)), + ), + RouteInfo::GetGuildRoles { guild_id } => ( + LightMethod::Get, + Route::GuildsIdRoles(guild_id), + Cow::from(Route::guild_roles(guild_id)), + ), + RouteInfo::GetGuildVanityUrl { guild_id } => ( + LightMethod::Get, + Route::GuildsIdVanityUrl(guild_id), + Cow::from(Route::guild_vanity_url(guild_id)), + ), + RouteInfo::GetGuildWebhooks { guild_id } => ( + LightMethod::Get, + Route::GuildsIdWebhooks(guild_id), + Cow::from(Route::guild_webhooks(guild_id)), + ), + RouteInfo::GetGuilds { after, before, limit } => ( + LightMethod::Get, + Route::UsersMeGuilds, + Cow::from(Route::user_guilds_optioned( + "@me", + after, + before, + limit, + )), + ), + RouteInfo::GetInvite { code, stats } => ( + LightMethod::Get, + Route::InvitesCode, + Cow::from(Route::invite_optioned(code, stats)), + ), + RouteInfo::GetMember { guild_id, user_id } => ( + LightMethod::Get, + Route::GuildsIdMembersId(guild_id), + Cow::from(Route::guild_member(guild_id, user_id)), + ), + RouteInfo::GetMessage { channel_id, message_id } => ( + LightMethod::Get, + Route::ChannelsIdMessagesId(LightMethod::Get, channel_id), + Cow::from(Route::channel_message(channel_id, message_id)), + ), + RouteInfo::GetMessages { channel_id, ref query } => ( + LightMethod::Get, + Route::ChannelsIdMessages(channel_id), + Cow::from(Route::channel_messages( + channel_id, + Some(query.as_ref()), + )), + ), + RouteInfo::GetPins { channel_id } => ( + LightMethod::Get, + Route::ChannelsIdPins(channel_id), + Cow::from(Route::channel_pins(channel_id)), + ), + RouteInfo::GetReactionUsers { + after, + channel_id, + limit, + message_id, + ref reaction, + } => ( + LightMethod::Get, + Route::ChannelsIdMessagesIdReactions(channel_id), + Cow::from(Route::channel_message_reactions_list( + channel_id, + message_id, + reaction, + limit, + after, + )), + ), + RouteInfo::GetUnresolvedIncidents => ( + LightMethod::Get, + Route::None, + Cow::from(Route::status_incidents_unresolved()), + ), + RouteInfo::GetUpcomingMaintenances => ( + LightMethod::Get, + Route::None, + Cow::from(Route::status_maintenances_upcoming()), + ), + RouteInfo::GetUser { user_id } => ( + LightMethod::Get, + Route::UsersId, + Cow::from(Route::user(user_id)), + ), + RouteInfo::GetUserDmChannels => ( + LightMethod::Get, + Route::UsersMeChannels, + Cow::from(Route::user_dm_channels("@me")), + ), + RouteInfo::GetVoiceRegions => ( + LightMethod::Get, + Route::VoiceRegions, + Cow::from(Route::voice_regions()), + ), + RouteInfo::GetWebhook { webhook_id } => ( + LightMethod::Get, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook(webhook_id)), + ), + RouteInfo::GetWebhookWithToken { token, webhook_id } => ( + LightMethod::Get, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook_with_token(webhook_id, token)), + ), + RouteInfo::KickMember { guild_id, user_id } => ( + LightMethod::Delete, + Route::GuildsIdMembersId(guild_id), + Cow::from(Route::guild_member(guild_id, user_id)), + ), + RouteInfo::LeaveGroup { group_id } => ( + LightMethod::Delete, + Route::ChannelsId(group_id), + Cow::from(Route::channel(group_id)), + ), + RouteInfo::LeaveGuild { guild_id } => ( + LightMethod::Delete, + Route::UsersMeGuildsId, + Cow::from(Route::user_guild("@me", guild_id)), + ), + RouteInfo::RemoveGroupRecipient { group_id, user_id } => ( + LightMethod::Delete, + Route::None, + Cow::from(Route::group_recipient(group_id, user_id)), + ), + RouteInfo::PinMessage { channel_id, message_id } => ( + LightMethod::Put, + Route::ChannelsIdPins(channel_id), + Cow::from(Route::channel_pin(channel_id, message_id)), + ), + RouteInfo::RemoveBan { guild_id, user_id } => ( + LightMethod::Delete, + Route::GuildsIdBansUserId(guild_id), + Cow::from(Route::guild_ban(guild_id, user_id)), + ), + RouteInfo::RemoveMemberRole { guild_id, role_id, user_id } => ( + LightMethod::Delete, + Route::GuildsIdMembersIdRolesId(guild_id), + Cow::from(Route::guild_member_role(guild_id, user_id, role_id)), + ), + RouteInfo::StartGuildPrune { days, guild_id } => ( + LightMethod::Post, + Route::GuildsIdPrune(guild_id), + Cow::from(Route::guild_prune(guild_id, days)), + ), + RouteInfo::StartIntegrationSync { guild_id, integration_id } => ( + LightMethod::Post, + Route::GuildsIdIntegrationsId(guild_id), + Cow::from(Route::guild_integration_sync( + guild_id, + integration_id, + )), + ), + RouteInfo::StatusIncidentsUnresolved => ( + LightMethod::Get, + Route::None, + Cow::from(Route::status_incidents_unresolved()), + ), + RouteInfo::StatusMaintenancesActive => ( + LightMethod::Get, + Route::None, + Cow::from(Route::status_maintenances_active()), + ), + RouteInfo::StatusMaintenancesUpcoming => ( + LightMethod::Get, + Route::None, + Cow::from(Route::status_maintenances_upcoming()), + ), + RouteInfo::UnpinMessage { channel_id, message_id } => ( + LightMethod::Delete, + Route::ChannelsIdPinsMessageId(channel_id), + Cow::from(Route::channel_pin(channel_id, message_id)), + ), + } + } +} diff --git a/src/internal/macros.rs b/src/internal/macros.rs index c4d2b6f..31b6b7d 100644 --- a/src/internal/macros.rs +++ b/src/internal/macros.rs @@ -1,36 +1,6 @@ //! A set of macros for easily working with internals. #[cfg(feature = "http")] -macro_rules! request { - ($route:expr, $method:ident($body:expr), $url:expr, $($rest:tt)*) => {{ - let client = request_client!(); - - request($route, || client - .$method(&format!(api!($url), $($rest)*)) - .body(&$body))? - }}; - ($route:expr, $method:ident($body:expr), $url:expr) => {{ - let client = request_client!(); - - request($route, || client - .$method(api!($url)) - .body(&$body))? - }}; - ($route:expr, $method:ident, $url:expr, $($rest:tt)*) => {{ - let client = request_client!(); - - request($route, || client - .$method(&format!(api!($url), $($rest)*)))? - }}; - ($route:expr, $method:ident, $url:expr) => {{ - let client = request_client!(); - - request($route, || client - .$method(api!($url)))? - }}; -} - -#[cfg(feature = "http")] macro_rules! request_client { () => {{ use hyper::net::HttpsConnector; diff --git a/src/model/gateway.rs b/src/model/gateway.rs index 9af4a7e..989cff8 100644 --- a/src/model/gateway.rs +++ b/src/model/gateway.rs @@ -133,13 +133,21 @@ impl Game { impl<'a> From<&'a str> for Game { fn from(name: &'a str) -> Self { - Game::playing(name) + Game { + kind: GameType::Playing, + name: name.to_owned(), + url: None, + } } } impl From<String> for Game { fn from(name: String) -> Self { - Game::playing(&name) + Game { + kind: GameType::Playing, + url: None, + name, + } } } |