aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMishio595 <[email protected]>2018-11-13 22:23:39 -0700
committerMishio595 <[email protected]>2018-11-13 22:23:39 -0700
commit1dad6996d8e9c983dc9e1689a93dbef5b0696c22 (patch)
tree1ee7226178cae76b20e6adcdc7e7cfcc3c477c32 /src
parentFinish rebase (diff)
parentMake `Region`s `Japan`-variant lowercase. (diff)
downloadarchived-serenity-1dad6996d8e9c983dc9e1689a93dbef5b0696c22.tar.xz
archived-serenity-1dad6996d8e9c983dc9e1689a93dbef5b0696c22.zip
Merge branch 'upstream'
Diffstat (limited to 'src')
-rw-r--r--src/builder/edit_guild.rs2
-rw-r--r--src/builder/edit_role.rs2
-rw-r--r--src/builder/execute_webhook.rs4
-rw-r--r--src/cache/mod.rs12
-rw-r--r--src/cache/settings.rs163
-rw-r--r--src/client/bridge/gateway/shard_runner_message.rs2
-rw-r--r--src/client/dispatch.rs31
-rw-r--r--src/client/event_handler.rs4
-rw-r--r--src/framework/standard/args.rs45
-rw-r--r--src/framework/standard/command.rs15
-rw-r--r--src/framework/standard/configuration.rs20
-rw-r--r--src/framework/standard/create_command.rs16
-rw-r--r--src/framework/standard/create_group.rs19
-rw-r--r--src/framework/standard/create_help_command.rs6
-rw-r--r--src/framework/standard/help_commands.rs13
-rw-r--r--src/framework/standard/mod.rs130
-rw-r--r--src/http/error.rs4
-rw-r--r--src/http/ratelimiting.rs4
-rw-r--r--src/http/raw.rs3502
-rw-r--r--src/model/channel/channel_category.rs3
-rw-r--r--src/model/channel/channel_id.rs14
-rw-r--r--src/model/channel/group.rs4
-rw-r--r--src/model/channel/guild_channel.rs6
-rw-r--r--src/model/channel/message.rs9
-rw-r--r--src/model/channel/mod.rs10
-rw-r--r--src/model/channel/private_channel.rs4
-rw-r--r--src/model/guild/mod.rs2
-rw-r--r--src/model/guild/role.rs9
-rw-r--r--src/model/invite.rs4
-rw-r--r--src/model/misc.rs2
-rw-r--r--src/model/permissions.rs2
-rw-r--r--src/model/user.rs25
-rw-r--r--src/utils/mod.rs559
-rw-r--r--src/voice/connection.rs2
-rw-r--r--src/voice/threading.rs2
35 files changed, 2754 insertions, 1897 deletions
diff --git a/src/builder/edit_guild.rs b/src/builder/edit_guild.rs
index ba87f0c..c9083a2 100644
--- a/src/builder/edit_guild.rs
+++ b/src/builder/edit_guild.rs
@@ -93,7 +93,7 @@ impl EditGuild {
/// Set the name of the guild.
///
- /// **Note**: Must be between (and including) 2-100 chracters.
+ /// **Note**: Must be between (and including) 2-100 characters.
pub fn name(mut self, name: &str) -> Self {
self.0.insert("name", Value::String(name.to_string()));
diff --git a/src/builder/edit_role.rs b/src/builder/edit_role.rs
index 1deb57d..bf0b489 100644
--- a/src/builder/edit_role.rs
+++ b/src/builder/edit_role.rs
@@ -5,7 +5,7 @@ use model::{
};
use utils::VecMap;
-/// A builer to create or edit a [`Role`] for use via a number of model methods.
+/// A builder to create or edit a [`Role`] for use via a number of model methods.
///
/// These are:
///
diff --git a/src/builder/execute_webhook.rs b/src/builder/execute_webhook.rs
index 65823bb..780d102 100644
--- a/src/builder/execute_webhook.rs
+++ b/src/builder/execute_webhook.rs
@@ -143,7 +143,7 @@ impl ExecuteWebhook {
///
/// # Examples
///
- /// Overriuding the username to `"hakase"`:
+ /// Overriding the username to `"hakase"`:
///
/// ```rust,no_run
/// # use serenity::client::rest;
@@ -173,7 +173,7 @@ impl Default for ExecuteWebhook {
/// ```rust
/// use serenity::builder::ExecuteWebhook;
///
- /// let executer = ExecuteWebhook::default();
+ /// let executor = ExecuteWebhook::default();
/// ```
///
/// [`Webhook`]: ../model/webhook/struct.Webhook.html
diff --git a/src/cache/mod.rs b/src/cache/mod.rs
index 41d66d4..ece7a57 100644
--- a/src/cache/mod.rs
+++ b/src/cache/mod.rs
@@ -52,7 +52,8 @@ use std::collections::{
};
use std::{
default::Default,
- sync::Arc
+ sync::Arc,
+ time::Duration,
};
mod cache_update;
@@ -798,6 +799,15 @@ impl Cache {
e.update(self)
}
+ /// Gets the duration it will try for when acquiring a write lock.
+ ///
+ /// Refer to the documentation for [`cache_lock_time`] for more information.
+ ///
+ /// [`cache_lock_time`]: struct.Settings.html#method.cache_lock_time
+ pub fn get_try_write_duration(&self) -> Option<Duration> {
+ self.settings.cache_lock_time
+ }
+
pub(crate) fn update_user_entry(&mut self, user: &User) {
match self.users.entry(user.id) {
Entry::Vacant(e) => {
diff --git a/src/cache/settings.rs b/src/cache/settings.rs
index e8e456b..975ddc6 100644
--- a/src/cache/settings.rs
+++ b/src/cache/settings.rs
@@ -1,50 +1,113 @@
-/// Settings for the cache.
-///
-/// # Examples
-///
-/// Create new settings, specifying the maximum number of messages:
-///
-/// ```rust
-/// use serenity::cache::Settings as CacheSettings;
-///
-/// let mut settings = CacheSettings::new();
-/// settings.max_messages(10);
-/// ```
-#[derive(Clone, Debug, Default)]
-pub struct Settings {
- /// The maximum number of messages to store in a channel's message cache.
- ///
- /// Defaults to 0.
- pub max_messages: usize,
- __nonexhaustive: (),
-}
-
-impl Settings {
- /// Creates new settings to be used with a cache.
- #[inline]
- pub fn new() -> Self {
- Self::default()
- }
-
- /// Sets the maximum number of messages to cache in a channel.
- ///
- /// Refer to [`max_messages`] for more information.
- ///
- /// # Examples
- ///
- /// Set the maximum number of messages to cache:
- ///
- /// ```rust
- /// use serenity::cache::Settings;
- ///
- /// let mut settings = Settings::new();
- /// settings.max_messages(10);
- /// ```
- ///
- /// [`max_messages`]: #structfield.max_messages
- pub fn max_messages(&mut self, max: usize) -> &mut Self {
- self.max_messages = max;
-
- self
- }
-}
+use std::time::Duration;
+
+/// Settings for the cache.
+///
+/// # Examples
+///
+/// Create new settings, specifying the maximum number of messages:
+///
+/// ```rust
+/// use serenity::cache::Settings as CacheSettings;
+///
+/// let mut settings = CacheSettings::new();
+/// settings.max_messages(10);
+/// ```
+#[derive(Clone, Debug)]
+pub struct Settings {
+ /// The maximum number of messages to store in a channel's message cache.
+ ///
+ /// Defaults to 0.
+ pub max_messages: usize,
+
+ /// The Duration cache updates will try to acquire write-locks for.
+ ///
+ /// Defaults to 10 milliseconds.
+ ///
+ /// **Note**:
+ /// If set to `None`, cache updates will acquire write-lock until available,
+ /// potentially deadlocking.
+ pub cache_lock_time: Option<Duration>,
+ __nonexhaustive: (),
+}
+
+impl Default for Settings {
+ fn default() -> Self {
+ Settings {
+ max_messages: usize::default(),
+ cache_lock_time: Some(Duration::from_millis(10)),
+ __nonexhaustive: (),
+ }
+ }
+}
+
+impl Settings {
+ /// Creates new settings to be used with a cache.
+ #[inline]
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Sets the maximum number of messages to cache in a channel.
+ ///
+ /// Refer to [`max_messages`] for more information.
+ ///
+ /// # Examples
+ ///
+ /// Set the maximum number of messages to cache:
+ ///
+ /// ```rust
+ /// use serenity::cache::Settings;
+ ///
+ /// let mut settings = Settings::new();
+ /// settings.max_messages(10);
+ /// ```
+ ///
+ /// [`max_messages`]: #structfield.max_messages
+ pub fn max_messages(&mut self, max: usize) -> &mut Self {
+ self.max_messages = max;
+
+ self
+ }
+
+ /// Sets the duration that the cache will try to aquire a write lock.
+ ///
+ /// Refer to [`cache_lock_time`] for more information.
+ ///
+ /// **Note**:
+ /// Should be set before the client gets started, as it can not be
+ /// changed after the first read of the duration.
+ ///
+ /// # Examples
+ ///
+ /// Set the time that it will try to aquire a lock.
+ ///
+ /// ```rust,no_run
+ /// use std::time::Duration;
+ /// use std::env;
+ /// use serenity::prelude::*;
+ ///
+ /// struct Handler;
+ ///
+ /// impl EventHandler for Handler {}
+ ///
+ /// fn main() {
+ /// let token = env::var("DISCORD_TOKEN")
+ /// .expect("Expected a token in the environment");
+ /// serenity::CACHE
+ /// .write().settings_mut()
+ /// .cache_lock_time(Some(Duration::from_secs(1)));
+ /// let mut client = Client::new(&token, Handler).unwrap();
+ ///
+ /// if let Err(why) = client.start() {
+ /// println!("Client error: {:?}", why);
+ /// }
+ /// }
+ /// ```
+ ///
+ /// [`cache_lock_time`]: #structfield.cache_lock_time
+ pub fn cache_lock_time(&mut self, duration: Option<Duration>) -> &mut Self {
+ self.cache_lock_time = duration;
+
+ self
+ }
+}
diff --git a/src/client/bridge/gateway/shard_runner_message.rs b/src/client/bridge/gateway/shard_runner_message.rs
index f281d21..edb9b8a 100644
--- a/src/client/bridge/gateway/shard_runner_message.rs
+++ b/src/client/bridge/gateway/shard_runner_message.rs
@@ -41,7 +41,7 @@ pub enum ShardRunnerMessage {
/// Indicates that the client is to update the shard's presence's game.
SetGame(Option<Game>),
/// Indicates that the client is to update the shard's presence in its
- /// entirity.
+ /// entirety.
SetPresence(OnlineStatus, Option<Game>),
/// Indicates that the client is to update the shard's presence's status.
SetStatus(OnlineStatus),
diff --git a/src/client/dispatch.rs b/src/client/dispatch.rs
index e201852..2c8dd61 100644
--- a/src/client/dispatch.rs
+++ b/src/client/dispatch.rs
@@ -25,20 +25,31 @@ use std::time::Duration;
#[cfg(feature = "cache")]
use super::CACHE;
+#[cfg(feature = "cache")]
+lazy_static! {
+ pub static ref CACHE_TRY_WRITE_DURATION: Option<Duration> =
+ CACHE.read().get_try_write_duration();
+}
+
macro_rules! update {
($event:expr) => {
{
#[cfg(feature = "cache")]
{
- if let Some(mut lock) = CACHE.try_write_for(Duration::from_millis(10)) {
- lock.update(&mut $event)
- } else {
- warn!(
- "[dispatch] Possible deadlock: couldn't unlock cache to update with event: {:?}",
- $event,
- );
-
- None
+ match *CACHE_TRY_WRITE_DURATION {
+ Some(duration) => {
+ if let Some(mut lock) = CACHE.try_write_for(duration) {
+ lock.update(&mut $event)
+ } else {
+ warn!(
+ "[dispatch] Possible deadlock: couldn't unlock cache to update with event: {:?}",
+ $event,
+ );
+ None
+ }},
+ None => {
+ CACHE.write().update(&mut $event)
+ },
}
}
}
@@ -167,7 +178,7 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>(
let context = context(data, runner_tx, shard_id);
// Discord sends both a MessageCreate and a ChannelCreate upon a new message in a private channel.
- // This could potentionally be annoying to handle when otherwise wanting to normally take care of a new channel.
+ // This could potentially be annoying to handle when otherwise wanting to normally take care of a new channel.
// So therefore, private channels are dispatched to their own handler code.
match event.channel {
Channel::Private(channel) => {
diff --git a/src/client/event_handler.rs b/src/client/event_handler.rs
index 5af7314..44cc1e3 100644
--- a/src/client/event_handler.rs
+++ b/src/client/event_handler.rs
@@ -229,12 +229,12 @@ pub trait EventHandler {
/// Provides the reaction's data.
fn reaction_add(&self, _ctx: Context, _add_reaction: Reaction) {}
- /// Dispatched when a reaction is dettached from a message.
+ /// Dispatched when a reaction is detached from a message.
///
/// Provides the reaction's data.
fn reaction_remove(&self, _ctx: Context, _removed_reaction: Reaction) {}
- /// Dispatched when all reactions of a message are dettached from a message.
+ /// Dispatched when all reactions of a message are detached from a message.
///
/// Provides the channel's id and the message's id.
fn reaction_remove_all(&self, _ctx: Context, _channel_id: ChannelId, _removed_from_message_id: MessageId) {}
diff --git a/src/framework/standard/args.rs b/src/framework/standard/args.rs
index 2700bfb..6841bd7 100644
--- a/src/framework/standard/args.rs
+++ b/src/framework/standard/args.rs
@@ -348,6 +348,51 @@ impl Args {
self.args.get(self.offset).map(|t| t.lit.as_str())
}
+ /// Trims the current argument off leading and trailing whitespace.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use serenity::framework::standard::Args;
+ ///
+ /// let mut args = Args::new(" 42 ", &[]);
+ ///
+ /// assert_eq!(args.trim().current(), Some("42"));
+ /// ```
+ pub fn trim(&mut self) -> &mut Self {
+ if self.is_empty() {
+ return self;
+ }
+
+ self.args[self.offset].lit = self.args[self.offset].lit.trim().to_string();
+
+ self
+ }
+
+ /// Trims all of the arguments after the offset off leading and trailing whitespace.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use serenity::framework::standard::Args;
+ ///
+ /// let mut args = Args::new(" 42 , 84 ,\t168\t", &[",".to_string()]);
+ ///
+ /// args.trim_all();
+ /// assert_eq!(args.single::<String>().unwrap(), "42");
+ /// assert_eq!(args.single::<String>().unwrap(), "84");
+ /// assert_eq!(args.single::<String>().unwrap(), "168");
+ /// ```
+ pub fn trim_all(&mut self) {
+ if self.is_empty() {
+ return;
+ }
+
+ for token in &mut self.args[self.offset..] {
+ token.lit = token.lit.trim().to_string();
+ }
+ }
+
/// Parses the current argument and advances.
///
/// # Examples
diff --git a/src/framework/standard/command.rs b/src/framework/standard/command.rs
index 2b1149e..5345f4b 100644
--- a/src/framework/standard/command.rs
+++ b/src/framework/standard/command.rs
@@ -60,6 +60,7 @@ impl HelpCommand for Help {
pub type BeforeHook = Fn(&mut Context, &Message, &str) -> bool + Send + Sync + 'static;
pub type AfterHook = Fn(&mut Context, &Message, &str, Result<(), Error>) + Send + Sync + 'static;
pub type UnrecognisedCommandHook = Fn(&mut Context, &Message, &str) + Send + Sync + 'static;
+pub type MessageWithoutCommandHook = Fn(&mut Context, &Message) + Send + Sync + 'static;
pub(crate) type InternalCommand = Arc<Command>;
pub type PrefixCheck = Fn(&mut Context, &Message) -> Option<String> + Send + Sync + 'static;
@@ -81,7 +82,7 @@ impl fmt::Debug for CommandOrAlias {
#[derive(Clone, Debug)]
pub struct Error(pub String);
-// TODO: Have seperate `From<(&)String>` and `From<&str>` impls via specialization
+// TODO: Have separate `From<(&)String>` and `From<&str>` impls via specialization
impl<D: fmt::Display> From<D> for Error {
fn from(d: D) -> Self {
Error(d.to_string())
@@ -99,6 +100,7 @@ pub struct CommandGroup {
pub help_available: bool,
pub dm_only: bool,
pub guild_only: bool,
+ pub owner_privileges: bool,
pub owners_only: bool,
pub help: Option<Arc<Help>>,
/// A set of checks to be called prior to executing the command-group. The checks
@@ -117,6 +119,7 @@ impl Default for CommandGroup {
required_permissions: Permissions::empty(),
dm_only: false,
guild_only: false,
+ owner_privileges: true,
help_available: true,
owners_only: false,
allowed_roles: Vec::new(),
@@ -141,7 +144,7 @@ pub struct CommandOptions {
pub example: Option<String>,
/// Command usage schema, used by other commands.
pub usage: Option<String>,
- /// Minumum amount of arguments that should be passed.
+ /// Minimum amount of arguments that should be passed.
pub min_args: Option<i32>,
/// Maximum amount of arguments that can be passed.
pub max_args: Option<i32>,
@@ -155,6 +158,8 @@ pub struct CommandOptions {
pub dm_only: bool,
/// Whether command can be used only in guilds or not.
pub guild_only: bool,
+ /// Whether the command treats owners as normal users.
+ pub owner_privileges: bool,
/// Whether command can only be used by owners or not.
pub owners_only: bool,
/// Other names that can be used to call this command instead.
@@ -216,7 +221,7 @@ pub struct HelpOptions {
pub wrong_channel: HelpBehaviour,
/// Colour help-embed will use upon encountering an error.
pub embed_error_colour: Colour,
- /// Colour help-embed will use if no error occured.
+ /// Colour help-embed will use if no error occurred.
pub embed_success_colour: Colour,
/// If not 0, help will check whether a command is similar to searched named.
pub max_levenshtein_distance: usize,
@@ -335,6 +340,7 @@ impl Default for CommandOptions {
required_permissions: Permissions::empty(),
dm_only: false,
guild_only: false,
+ owner_privileges: true,
help_available: true,
owners_only: false,
allowed_roles: Vec::new(),
@@ -374,7 +380,8 @@ pub fn positions(ctx: &mut Context, msg: &Message, conf: &Configuration) -> Opti
// If the above do not fill `positions`, then that means no kind of prefix was present.
// Check if a no-prefix-execution is applicable.
- if conf.no_dm_prefix && private && positions.is_empty() {
+ if conf.no_dm_prefix && private && positions.is_empty() &&
+ !(conf.ignore_bots && msg.author.bot) {
positions.push(0);
}
}
diff --git a/src/framework/standard/configuration.rs b/src/framework/standard/configuration.rs
index 795493d..a8ef075 100644
--- a/src/framework/standard/configuration.rs
+++ b/src/framework/standard/configuration.rs
@@ -6,9 +6,10 @@ use model::{
};
use std::{
collections::HashSet,
- default::Default
+ default::Default,
+ sync::Arc,
};
-use super::command::PrefixCheck;
+use super::command::{Command, InternalCommand, PrefixCheck};
/// The configuration to use for a [`StandardFramework`] associated with a [`Client`]
/// instance.
@@ -59,6 +60,7 @@ pub struct Configuration {
#[doc(hidden)] pub no_dm_prefix: bool,
#[doc(hidden)] pub delimiters: Vec<String>,
#[doc(hidden)] pub case_insensitive: bool,
+ #[doc(hidden)] pub prefix_only_cmd: Option<InternalCommand>,
}
impl Configuration {
@@ -448,7 +450,7 @@ impl Configuration {
///
/// # Examples
///
- /// Have the args be seperated by a comma and a space:
+ /// Have the args be separated by a comma and a space:
///
/// ```rust,no_run
/// # use serenity::prelude::*;
@@ -475,7 +477,7 @@ impl Configuration {
///
/// # Examples
///
- /// Have the args be seperated by a comma and a space; and a regular space:
+ /// Have the args be separated by a comma and a space; and a regular space:
///
/// ```rust,no_run
/// # use serenity::prelude::*;
@@ -511,6 +513,15 @@ impl Configuration {
self
}
+
+ /// Sets a command to dispatch if user's input is a prefix only.
+ ///
+ /// **Note**: Defaults to no command and ignores prefix only.
+ pub fn prefix_only_cmd<C: Command + 'static>(mut self, c: C) -> Self {
+ self.prefix_only_cmd = Some(Arc::new(c));
+
+ self
+ }
}
impl Default for Configuration {
@@ -550,6 +561,7 @@ impl Default for Configuration {
on_mention: None,
owners: HashSet::default(),
prefixes: vec![],
+ prefix_only_cmd: None,
}
}
}
diff --git a/src/framework/standard/create_command.rs b/src/framework/standard/create_command.rs
index a769d4c..3976f6d 100644
--- a/src/framework/standard/create_command.rs
+++ b/src/framework/standard/create_command.rs
@@ -168,6 +168,14 @@ impl CreateCommand {
self
}
+ /// Whether owners shall bypass buckets, missing permissions,
+ /// wrong channels, missing roles, and checks.
+ pub fn owner_privileges(mut self, owner_privileges: bool) -> Self {
+ self.0.owner_privileges = owner_privileges;
+
+ self
+ }
+
/// Whether command should be displayed in help list or not, used by other commands.
pub fn help_available(mut self, help_available: bool) -> Self {
self.0.help_available = help_available;
@@ -189,7 +197,7 @@ impl CreateCommand {
self
}
- /// Minumum amount of arguments that should be passed.
+ /// Minimum amount of arguments that should be passed.
pub fn min_args(mut self, min_args: i32) -> Self {
self.0.min_args = Some(min_args);
@@ -236,7 +244,7 @@ impl CreateCommand {
/// Sets an initialise middleware to be called upon the command's actual registration.
///
- /// This is similiar to implementing the `init` function on `Command`.
+ /// This is similar to implementing the `init` function on `Command`.
pub fn init<F: Fn() + Send + Sync + 'static>(mut self, f: F) -> Self {
self.2.init = Some(Arc::new(f));
@@ -245,7 +253,7 @@ impl CreateCommand {
/// Sets a before middleware to be called before the command's execution.
///
- /// This is similiar to implementing the `before` function on `Command`.
+ /// This is similar to implementing the `before` function on `Command`.
pub fn before<F: Send + Sync + 'static>(mut self, f: F) -> Self
where F: Fn(&mut Context, &Message) -> bool {
self.2.before = Some(Arc::new(f));
@@ -255,7 +263,7 @@ impl CreateCommand {
/// Sets an after middleware to be called after the command's execution.
///
- /// This is similiar to implementing the `after` function on `Command`.
+ /// This is similar to implementing the `after` function on `Command`.
pub fn after<F: Send + Sync + 'static>(mut self, f: F) -> Self
where F: Fn(&mut Context, &Message, &Result<(), CommandError>) {
self.2.after = Some(Arc::new(f));
diff --git a/src/framework/standard/create_group.rs b/src/framework/standard/create_group.rs
index 3cd4047..0049cf3 100644
--- a/src/framework/standard/create_group.rs
+++ b/src/framework/standard/create_group.rs
@@ -43,7 +43,8 @@ impl CreateGroup {
.dm_only(self.0.dm_only)
.guild_only(self.0.guild_only)
.help_available(self.0.help_available)
- .owners_only(self.0.owners_only);
+ .owners_only(self.0.owners_only)
+ .owner_privileges(self.0.owner_privileges);
if let Some(ref bucket) = self.0.bucket {
cmd = cmd.bucket(bucket);
@@ -151,6 +152,14 @@ impl CreateGroup {
self
}
+ /// Whether owners shall bypass buckets, missing permissions,
+ /// wrong channels, missing roles, and checks.
+ pub fn owner_privileges(mut self, owner_privileges: bool) -> Self {
+ self.0.owner_privileges = owner_privileges;
+
+ self
+ }
+
/// Whether command should be displayed in help list or not, used by other commands.
pub fn help_available(mut self, help_available: bool) -> Self {
self.0.help_available = help_available;
@@ -198,11 +207,11 @@ impl CreateGroup {
/// Adds a command for a group that will be executed if no command-name
/// has been passed.
pub fn default_cmd<C: Command + 'static>(mut self, c: C) -> Self {
- let cmd: Arc<Command> = Arc::new(c);
-
- self.0.default_command = Some(CommandOrAlias::Command(Arc::clone(&cmd)));
+ c.init();
- cmd.init();
+ let cmd_with_group_options = self.build_command().cmd(c).finish();
+ let cmd_finished = CommandOrAlias::Command(cmd_with_group_options);
+ self.0.default_command = Some(cmd_finished);
self
}
diff --git a/src/framework/standard/create_help_command.rs b/src/framework/standard/create_help_command.rs
index b8eb57a..c05cb32 100644
--- a/src/framework/standard/create_help_command.rs
+++ b/src/framework/standard/create_help_command.rs
@@ -126,7 +126,7 @@ impl CreateHelpCommand {
self
}
- /// Sets how the group-prexix shall be labeled.
+ /// Sets how the group-prefix shall be labeled.
pub fn group_prefix(mut self, text: &str) -> Self {
self.0.group_prefix = text.to_string();
@@ -204,14 +204,14 @@ impl CreateHelpCommand {
self
}
- /// Sets the colour for the embed if no error occured.
+ /// Sets the colour for the embed if no error occurred.
pub fn embed_success_colour(mut self, colour: Colour) -> Self {
self.0.embed_success_colour = colour;
self
}
- /// Sets the colour for the embed if an error occured.
+ /// Sets the colour for the embed if an error occurred.
pub fn embed_error_colour(mut self, colour: Colour) -> Self {
self.0.embed_error_colour = colour;
diff --git a/src/framework/standard/help_commands.rs b/src/framework/standard/help_commands.rs
index ca8fdc6..94c9127 100644
--- a/src/framework/standard/help_commands.rs
+++ b/src/framework/standard/help_commands.rs
@@ -112,8 +112,8 @@ impl Suggestions {
&self.0
}
- /// Concats names of suggestions with a given `seperator`.
- fn join(&self, seperator: &str) -> String {
+ /// Concats names of suggestions with a given `separator`.
+ fn join(&self, separator: &str) -> String {
let mut iter = self.as_vec().iter();
let first_iter_element = match iter.next() {
@@ -122,12 +122,12 @@ impl Suggestions {
};
let size = self.as_vec().iter().fold(0, |total_size, size| total_size + size.name.len());
- let byte_len_of_sep = self.as_vec().len().checked_sub(1).unwrap_or(0) * seperator.len();
+ let byte_len_of_sep = self.as_vec().len().checked_sub(1).unwrap_or(0) * separator.len();
let mut result = String::with_capacity(size + byte_len_of_sep);
result.push_str(first_iter_element.name.borrow());
for element in iter {
- result.push_str(&*seperator);
+ result.push_str(&*separator);
result.push_str(element.name.borrow());
}
@@ -303,6 +303,7 @@ pub fn is_command_visible(command_options: &Arc<CommandOptions>, msg: &Message,
/// Tries to extract a single command matching searched command name otherwise
/// returns similar commands.
+#[cfg(feature = "cache")]
fn fetch_single_command<'a, H: BuildHasher>(
groups: &'a HashMap<String, Arc<CommandGroup>, H>,
name: &str,
@@ -423,6 +424,7 @@ fn fetch_single_command<'a, H: BuildHasher>(
}
/// Tries to extract a single command matching searched command name.
+#[cfg(feature = "cache")]
fn fetch_all_eligible_commands_in_group<'a>(
commands: &HashMap<&String, &InternalCommand>,
command_names: &[&&String],
@@ -471,6 +473,7 @@ fn fetch_all_eligible_commands_in_group<'a>(
}
/// Fetch groups with their commands.
+#[cfg(feature = "cache")]
fn create_command_group_commands_pair_from_groups<'a, H: BuildHasher>(
groups: &'a HashMap<String, Arc<CommandGroup>, H>,
group_names: &[&'a String],
@@ -498,6 +501,7 @@ fn create_command_group_commands_pair_from_groups<'a, H: BuildHasher>(
}
/// Fetches a single group with its commands.
+#[cfg(feature = "cache")]
fn create_single_group<'a>(
group: &CommandGroup,
group_name: &'a str,
@@ -527,6 +531,7 @@ let commands = remove_aliases(&group.commands);
/// Iterates over all commands and forges them into a `CustomisedHelpData`
/// taking `HelpOptions` into consideration when deciding on whether a command
/// shall be picked and in what textual format.
+#[cfg(feature = "cache")]
pub fn create_customised_help_data<'a, H: BuildHasher>(
groups: &'a HashMap<String, Arc<CommandGroup>, H>,
args: &'a Args,
diff --git a/src/framework/standard/mod.rs b/src/framework/standard/mod.rs
index aa32d11..162871d 100644
--- a/src/framework/standard/mod.rs
+++ b/src/framework/standard/mod.rs
@@ -39,7 +39,7 @@ use model::{
id::{ChannelId, GuildId, UserId},
Permissions
};
-use self::command::{AfterHook, BeforeHook, UnrecognisedCommandHook};
+use self::command::{AfterHook, BeforeHook, MessageWithoutCommandHook, UnrecognisedCommandHook};
use std::{
collections::HashMap,
default::Default,
@@ -217,6 +217,7 @@ pub struct StandardFramework {
buckets: HashMap<String, Bucket>,
after: Option<Arc<AfterHook>>,
unrecognised_command: Option<Arc<UnrecognisedCommandHook>>,
+ message_without_command: Option<Arc<MessageWithoutCommandHook>>,
/// Whether the framework has been "initialized".
///
/// The framework is initialized once one of the following occurs:
@@ -532,7 +533,7 @@ impl StandardFramework {
}
}
- if self.configuration.owners.contains(&message.author.id) {
+ if command.owner_privileges && self.configuration.owners.contains(&message.author.id) {
return None;
}
@@ -949,9 +950,34 @@ impl StandardFramework {
self
}
+ /// Specify the function to be called if a message contains no command.
+ ///
+ /// # Examples
+ ///
+ /// Using `message_without_command`:
+ ///
+ /// ```rust,no_run
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler).unwrap();
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .message_without_command(|ctx, msg| { }));
+ /// ```
+ pub fn message_without_command<F>(mut self, f: F) -> Self
+ where F: Fn(&mut Context, &Message) + Send + Sync + 'static {
+ self.message_without_command = Some(Arc::new(f));
+
+ self
+ }
+
/// Sets what code should be executed when a user sends `(prefix)help`.
///
- /// If a command named `help` was set with [`command`], then this takes precendence first.
+ /// If a command named `help` was set with [`command`], then this takes precedence first.
///
/// [`command`]: #method.command
pub fn help(mut self, f: HelpFunction) -> Self {
@@ -1004,12 +1030,54 @@ impl Framework for StandardFramework {
// Ensure that there is _at least one_ position remaining. There
// is no point in continuing if there is not.
if positions.is_empty() {
+
+ if let Some(ref prefix_only_cmd) =
+ self.configuration.prefix_only_cmd {
+ let prefix_only_cmd = Arc::clone(prefix_only_cmd);
+ let before = self.before.clone();
+ let after = self.after.clone();
+
+ threadpool.execute(move || {
+ if let Some(before) = before {
+ if !(before)(&mut context, &message, "") {
+ return;
+ }
+ }
+
+ if !prefix_only_cmd.before(&mut context, &message) {
+ return;
+ }
+
+ let result = prefix_only_cmd.execute(&mut context,
+ &message, Args::new("", &Vec::new()));
+
+ prefix_only_cmd.after(&mut context, &message,
+ &result);
+
+ if let Some(after) = after {
+ (after)(&mut context, &message, "", result);
+ }
+ });
+ }
+
return;
}
positions
},
- None => return,
+ None => {
+ if let &Some(ref message_without_command) = &self.message_without_command {
+
+ if !(self.configuration.ignore_bots && message.author.bot) {
+ let message_without_command = message_without_command.clone();
+ threadpool.execute(move || {
+ (message_without_command)(&mut context, &message);
+ });
+ }
+ }
+
+ return;
+ },
};
'outer: for position in positions {
@@ -1160,6 +1228,21 @@ impl Framework for StandardFramework {
Args::new(&orginal_round[longest_matching_prefix_len..], &self.configuration.delimiters)
};
+ if let Some(error) = self.should_fail(
+ &mut context,
+ &message,
+ &command.options(),
+ &group,
+ &mut args,
+ &to_check,
+ &built,
+ ) {
+ if let Some(ref handler) = self.dispatch_error_handler {
+ handler(context, message, error);
+ }
+ return;
+ }
+
threadpool.execute(move || {
if let Some(before) = before {
if !(before)(&mut context, &message, &args.full()) {
@@ -1190,9 +1273,33 @@ impl Framework for StandardFramework {
if !(self.configuration.ignore_bots && message.author.bot) {
if let &Some(ref unrecognised_command) = &self.unrecognised_command {
- let unrecognised_command = unrecognised_command.clone();
- threadpool.execute(move || {
- (unrecognised_command)(&mut context, &message, &unrecognised_command_name);
+
+ // If both functions are set, we need to clone `Context` and
+ // `Message`, else we can avoid it.
+ if let &Some(ref message_without_command) = &self.message_without_command {
+ let mut context_unrecognised = context.clone();
+ let message_unrecognised = message.clone();
+
+ let unrecognised_command = unrecognised_command.clone();
+ threadpool.execute(move || {
+ (unrecognised_command)(&mut context_unrecognised, &message_unrecognised,
+ &unrecognised_command_name);
+ });
+
+ let message_without_command = message_without_command.clone();
+ threadpool.execute(move || {
+ (message_without_command)(&mut context, &message);
+ });
+ } else {
+ let unrecognised_command = unrecognised_command.clone();
+ threadpool.execute(move || {
+ (unrecognised_command)(&mut context, &message, &unrecognised_command_name);
+ });
+ }
+ } else if let &Some(ref message_without_command) = &self.message_without_command {
+ let message_without_command = message_without_command.clone();
+ threadpool.execute(move || {
+ (message_without_command)(&mut context, &message);
});
}
}
@@ -1205,8 +1312,9 @@ impl Framework for StandardFramework {
#[cfg(feature = "cache")]
pub fn has_correct_permissions(command: &Arc<CommandOptions>, message: &Message) -> bool {
- if !command.required_permissions.is_empty() {
-
+ if command.required_permissions.is_empty() {
+ true
+ } else {
if let Some(guild) = message.guild() {
let perms = guild
.with(|g| g.permissions_in(message.channel_id, message.author.id));
@@ -1215,8 +1323,6 @@ pub fn has_correct_permissions(command: &Arc<CommandOptions>, message: &Message)
} else {
false
}
- } else {
- true
}
}
@@ -1238,7 +1344,7 @@ pub fn has_correct_roles(cmd: &Arc<CommandOptions>, guild: &Guild, member: &Memb
/// The command can't be used in the current channel (as in `DM only` or `guild only`).
#[derive(PartialEq, Debug)]
pub enum HelpBehaviour {
- /// Strikes a command by applying `~~{comand_name}~~`.
+ /// Strikes a command by applying `~~{command_name}~~`.
Strike,
/// Does not list a command in the help-menu.
Hide,
diff --git a/src/http/error.rs b/src/http/error.rs
index b433cb5..76a1d4f 100644
--- a/src/http/error.rs
+++ b/src/http/error.rs
@@ -27,9 +27,7 @@ impl Display for Error {
impl StdError for Error {
fn description(&self) -> &str {
match *self {
- Error::UnsuccessfulRequest(_) => {
- "A non-successful response status code was received"
- },
+ Error::UnsuccessfulRequest(_) => "A non-successful response status code was received",
Error::RateLimitI64 => "Error decoding a header into an i64",
Error::RateLimitUtf8 => "Error decoding a header from UTF-8",
}
diff --git a/src/http/ratelimiting.rs b/src/http/ratelimiting.rs
index 5edb6ee..7713b27 100644
--- a/src/http/ratelimiting.rs
+++ b/src/http/ratelimiting.rs
@@ -68,7 +68,7 @@ lazy_static! {
/// prior to every request, to abide by Discord's global ratelimit.
///
/// The global ratelimit is the total number of requests that may be made
- /// across the entirity of the API within an amount of time. If this is
+ /// across the entirety of the API within an amount of time. If this is
/// reached, then the global mutex is unlocked for the amount of time
/// present in the "Retry-After" header.
///
@@ -82,7 +82,7 @@ lazy_static! {
/// The routes mutex is a HashMap of each [`Route`] and their respective
/// ratelimit information.
///
- /// See the documentation for [`RateLimit`] for more infomation on how the
+ /// See the documentation for [`RateLimit`] for more information on how the
/// library handles ratelimiting.
///
/// # Examples
diff --git a/src/http/raw.rs b/src/http/raw.rs
index acce267..b4a9cc9 100644
--- a/src/http/raw.rs
+++ b/src/http/raw.rs
@@ -1,1751 +1,1751 @@
-use constants;
-use hyper::{
- client::{
- Request as HyperRequest,
- Response as HyperResponse
- },
- header::{ContentType, Headers},
- method::Method,
- mime::{Mime, SubLevel, TopLevel},
- net::HttpsConnector,
- header,
- Error as HyperError,
- Result as HyperResult,
- Url
-};
-use hyper_native_tls::NativeTlsClient;
-use internal::prelude::*;
-use model::prelude::*;
-use multipart::client::Multipart;
-use super::{
- TOKEN,
- ratelimiting,
- request::Request,
- routing::RouteInfo,
- AttachmentType,
- GuildPagination,
- HttpError,
- StatusClass,
- StatusCode,
-};
-use serde::de::DeserializeOwned;
-use serde_json;
-use std::{
- collections::BTreeMap,
- io::ErrorKind as IoErrorKind,
-};
-
-/// Sets the token to be used across all requests which require authentication.
-///
-/// If you are using the client module, you don't need to use this. If you're
-/// using serenity solely for HTTP, you need to use this.
-///
-/// # Examples
-///
-/// Setting the token from an environment variable:
-///
-/// ```rust,no_run
-/// # use std::error::Error;
-/// #
-/// # fn try_main() -> Result<(), Box<Error>> {
-/// #
-/// use serenity::http;
-/// use std::env;
-///
-/// http::set_token(&env::var("DISCORD_TOKEN")?);
-/// # Ok(())
-/// # }
-/// #
-/// # fn main() {
-/// # try_main().unwrap();
-/// # }
-pub fn set_token(token: &str) { TOKEN.lock().clone_from(&token.to_string()); }
-
-/// Adds a [`User`] as a recipient to a [`Group`].
-///
-/// **Note**: Groups have a limit of 10 recipients, including the current user.
-///
-/// [`Group`]: ../model/channel/struct.Group.html
-/// [`Group::add_recipient`]: ../model/channel/struct.Group.html#method.add_recipient
-/// [`User`]: ../model/user/struct.User.html
-pub fn add_group_recipient(group_id: u64, user_id: u64) -> Result<()> {
- wind(204, Request {
- body: None,
- headers: None,
- route: RouteInfo::AddGroupRecipient { group_id, user_id },
- })
-}
-
-/// Adds a single [`Role`] to a [`Member`] in a [`Guild`].
-///
-/// **Note**: Requires the [Manage Roles] permission and respect of role
-/// hierarchy.
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-/// [`Member`]: ../model/guild/struct.Member.html
-/// [`Role`]: ../model/guild/struct.Role.html
-/// [Manage Roles]: ../model/permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES
-pub fn add_member_role(guild_id: u64, user_id: u64, role_id: u64) -> Result<()> {
- 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
-/// X number of days.
-///
-/// Passing a `delete_message_days` of `0` is equivalent to not removing any
-/// messages. Up to `7` days' worth of messages may be deleted.
-///
-/// **Note**: Requires that you have the [Ban Members] permission.
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-/// [`User`]: ../model/user/struct.User.html
-/// [Ban Members]: ../model/permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS
-pub fn ban_user(guild_id: u64, user_id: u64, delete_message_days: u8, reason: &str) -> Result<()> {
- wind(204, Request {
- body: None,
- headers: None,
- route: RouteInfo::GuildBanUser {
- delete_message_days: Some(delete_message_days),
- reason: Some(reason),
- guild_id,
- user_id,
- },
- })
-}
-
-/// Ban zeyla from a [`Guild`], removing her messages sent in the last X number
-/// of days.
-///
-/// Passing a `delete_message_days` of `0` is equivalent to not removing any
-/// messages. Up to `7` days' worth of messages may be deleted.
-///
-/// **Note**: Requires that you have the [Ban Members] permission.
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-/// [Ban Members]: ../model/permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS
-pub fn ban_zeyla(guild_id: u64, delete_message_days: u8, reason: &str) -> Result<()> {
- ban_user(guild_id, 114_941_315_417_899_012, delete_message_days, reason)
-}
-
-/// Ban luna from a [`Guild`], removing her messages sent in the last X number
-/// of days.
-///
-/// Passing a `delete_message_days` of `0` is equivalent to not removing any
-/// messages. Up to `7` days' worth of messages may be deleted.
-///
-/// **Note**: Requires that you have the [Ban Members] permission.
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-/// [Ban Members]: ../model/permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS
-pub fn ban_luna(guild_id: u64, delete_message_days: u8, reason: &str) -> Result<()> {
- ban_user(guild_id, 180_731_582_049_550_336, delete_message_days, reason)
-}
-
-/// Ban the serenity servermoms from a [`Guild`], removing their messages
-/// sent in the last X number of days.
-///
-/// Passing a `delete_message_days` of `0` is equivalent to not removing any
-/// messages. Up to `7` days' worth of messages may be deleted.
-///
-/// **Note**: Requires that you have the [Ban Members] permission.
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-/// [Ban Members]: ../model/permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS
-pub fn ban_servermoms(guild_id: u64, delete_message_days: u8, reason: &str) -> Result<()> {
- ban_zeyla(guild_id, delete_message_days, reason)?;
- ban_luna(guild_id, delete_message_days, reason)
-}
-
-/// Broadcasts that the current user is typing in the given [`Channel`].
-///
-/// This lasts for about 10 seconds, and will then need to be renewed to
-/// indicate that the current user is still typing.
-///
-/// This should rarely be used for bots, although it is a good indicator that a
-/// long-running command is still being processed.
-///
-/// [`Channel`]: ../model/channel/enum.Channel.html
-pub fn broadcast_typing(channel_id: u64) -> Result<()> {
- wind(204, Request {
- body: None,
- headers: None,
- route: RouteInfo::BroadcastTyping { channel_id },
- })
-}
-
-/// Creates a [`GuildChannel`] in the [`Guild`] given its Id.
-///
-/// Refer to the Discord's [docs] for information on what fields this requires.
-///
-/// **Note**: Requires the [Manage Channels] permission.
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-/// [`GuildChannel`]: ../model/channel/struct.GuildChannel.html
-/// [docs]: https://discordapp.com/developers/docs/resources/guild#create-guild-channel
-/// [Manage Channels]: ../model/permissions/struct.Permissions.html#associatedconstant.MANAGE_CHANNELS
-pub fn create_channel(guild_id: u64, map: &Value) -> Result<GuildChannel> {
- 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.
-///
-/// View the source code for [`Guild`]'s [`create_emoji`] method to see what
-/// fields this requires.
-///
-/// **Note**: Requires the [Manage Emojis] permission.
-///
-/// [`create_emoji`]: ../model/guild/struct.Guild.html#method.create_emoji
-/// [`Guild`]: ../model/guild/struct.Guild.html
-/// [Manage Emojis]: ../model/permissions/struct.Permissions.html#associatedconstant.MANAGE_EMOJIS
-pub fn create_emoji(guild_id: u64, map: &Value) -> Result<Emoji> {
- fire(Request {
- body: Some(map.to_string().as_bytes()),
- headers: None,
- route: RouteInfo::CreateEmoji { guild_id },
- })
-}
-
-/// Creates a guild with the data provided.
-///
-/// Only a [`PartialGuild`] will be immediately returned, and a full [`Guild`]
-/// will be received over a [`Shard`], if at least one is running.
-///
-/// **Note**: This endpoint is currently limited to 10 active guilds. The
-/// limits are raised for whitelisted [GameBridge] applications. See the
-/// [documentation on this endpoint] for more info.
-///
-/// # Examples
-///
-/// Create a guild called `"test"` in the [US West region]:
-///
-/// ```rust,ignore
-/// extern crate serde_json;
-///
-/// use serde_json::builder::ObjectBuilder;
-/// use serde_json::Value;
-/// use serenity::http;
-///
-/// let map = ObjectBuilder::new()
-/// .insert("name", "test")
-/// .insert("region", "us-west")
-/// .build();
-///
-/// let _result = http::create_guild(map);
-/// ```
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-/// [`PartialGuild`]: ../model/guild/struct.PartialGuild.html
-/// [`Shard`]: ../gateway/struct.Shard.html
-/// [GameBridge]: https://discordapp.com/developers/docs/topics/gamebridge
-/// [US West Region]: ../model/guild/enum.Region.html#variant.UsWest
-/// [documentation on this endpoint]:
-/// https://discordapp.com/developers/docs/resources/guild#create-guild
-/// [whitelist]: https://discordapp.com/developers/docs/resources/guild#create-guild
-pub fn create_guild(map: &Value) -> Result<PartialGuild> {
- fire(Request {
- body: Some(map.to_string().as_bytes()),
- headers: None,
- route: RouteInfo::CreateGuild,
- })
-}
-
-/// Creates an [`Integration`] for a [`Guild`].
-///
-/// Refer to Discord's [docs] for field information.
-///
-/// **Note**: Requires the [Manage Guild] permission.
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-/// [`Integration`]: ../model/guild/struct.Integration.html
-/// [Manage Guild]: ../model/permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD
-/// [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<()> {
- 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`].
-///
-/// Refer to Discord's [docs] for field information.
-///
-/// All fields are optional.
-///
-/// **Note**: Requires the [Create Invite] permission.
-///
-/// [`GuildChannel`]: ../model/channel/struct.GuildChannel.html
-/// [`RichInvite`]: ../model/invite/struct.RichInvite.html
-/// [Create Invite]: ../model/permissions/struct.Permissions.html#associatedconstant.CREATE_INVITE
-/// [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_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 = 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 = serde_json::to_vec(map)?;
-
- fire(Request {
- body: Some(&body),
- headers: None,
- route: RouteInfo::CreatePrivateChannel,
- })
-}
-
-/// Reacts to a message.
-pub fn create_reaction(channel_id: u64,
- message_id: u64,
- reaction_type: &ReactionType)
- -> Result<()> {
- wind(204, Request {
- body: None,
- headers: None,
- route: RouteInfo::CreateReaction {
- reaction: &reaction_type.as_data(),
- channel_id,
- message_id,
- },
- })
-}
-
-/// Creates a role.
-pub fn create_role(guild_id: u64, map: &JsonMap) -> Result<Role> {
- 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
-/// the given data.
-///
-/// This method requires authentication.
-///
-/// The Value is a map with the values of:
-///
-/// - **avatar**: base64-encoded 128x128 image for the webhook's default avatar
-/// (_optional_);
-/// - **name**: the name of the webhook, limited to between 2 and 100 characters
-/// long.
-///
-/// # Examples
-///
-/// Creating a webhook named `test`:
-///
-/// ```rust,ignore
-/// extern crate serde_json;
-/// extern crate serenity;
-///
-/// use serde_json::builder::ObjectBuilder;
-/// use serenity::http;
-///
-/// let channel_id = 81384788765712384;
-/// let map = ObjectBuilder::new().insert("name", "test").build();
-///
-/// let webhook = http::create_webhook(channel_id, map).expect("Error creating");
-/// ```
-///
-/// [`GuildChannel`]: ../model/channel/struct.GuildChannel.html
-pub fn create_webhook(channel_id: u64, map: &Value) -> Result<Webhook> {
- let body = 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> {
- 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<()> {
- 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> {
- 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<()> {
- 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> {
- 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<()> {
- 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<()> {
- 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`].
-///
-/// # Examples
-///
-/// ```rust,no_run
-/// use serenity::http;
-/// use serenity::model::id::{ChannelId, MessageId};
-///
-/// let channel_id = ChannelId(7);
-/// let message_id = MessageId(8);
-///
-/// let _ = http::delete_message_reactions(channel_id.0, message_id.0)
-/// .expect("Error deleting reactions");
-/// ```
-///
-/// [`Message`]: ../model/channel/struct.Message.html
-/// [`Reaction`]: ../model/channel/struct.Reaction.html
-pub fn delete_message_reactions(channel_id: u64, message_id: u64) -> Result<()> {
- 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<()> {
- 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
-/// we have specific permissions.
-pub fn delete_reaction(channel_id: u64,
- message_id: u64,
- user_id: Option<u64>,
- reaction_type: &ReactionType)
- -> Result<()> {
- let user = user_id
- .map(|uid| uid.to_string())
- .unwrap_or_else(|| "@me".to_string());
-
- wind(204, Request {
- body: None,
- headers: None,
- route: RouteInfo::DeleteReaction {
- reaction: &reaction_type.as_data(),
- user: &user,
- channel_id,
- message_id,
- },
- })
-}
-
-/// Deletes a role from a server. Can't remove the default everyone role.
-pub fn delete_role(guild_id: u64, role_id: u64) -> Result<()> {
- wind(204, Request {
- body: None,
- headers: None,
- route: RouteInfo::DeleteRole { guild_id, role_id },
- })
-}
-
-/// Deletes a [`Webhook`] given its Id.
-///
-/// This method requires authentication, whereas [`delete_webhook_with_token`]
-/// does not.
-///
-/// # Examples
-///
-/// Deletes a webhook given its Id:
-///
-/// ```rust,no_run
-/// use serenity::http;
-/// use std::env;
-///
-/// // Due to the `delete_webhook` function requiring you to authenticate, you
-/// // must have set the token first.
-/// http::set_token(&env::var("DISCORD_TOKEN").unwrap());
-///
-/// http::delete_webhook(245037420704169985).expect("Error deleting webhook");
-/// ```
-///
-/// [`Webhook`]: ../model/webhook/struct.Webhook.html
-/// [`delete_webhook_with_token`]: fn.delete_webhook_with_token.html
-pub fn delete_webhook(webhook_id: u64) -> Result<()> {
- wind(204, Request {
- body: None,
- headers: None,
- route: RouteInfo::DeleteWebhook { webhook_id },
- })
-}
-
-/// Deletes a [`Webhook`] given its Id and unique token.
-///
-/// This method does _not_ require authentication.
-///
-/// # Examples
-///
-/// Deletes a webhook given its Id and unique token:
-///
-/// ```rust,no_run
-/// use serenity::http;
-///
-/// let id = 245037420704169985;
-/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
-///
-/// http::delete_webhook_with_token(id, token).expect("Error deleting webhook");
-/// ```
-///
-/// [`Webhook`]: ../model/webhook/struct.Webhook.html
-pub fn delete_webhook_with_token(webhook_id: u64, token: &str) -> Result<()> {
- 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_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 = 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_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_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 = 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_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 = serde_json::to_vec(map)?;
-
- 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.
-///
-/// Pass `None` to reset the nickname.
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-pub fn edit_nickname(guild_id: u64, new_nickname: Option<&str>) -> Result<()> {
- let map = json!({ "nick": new_nickname });
- let body = serde_json::to_vec(&map)?;
-
- wind(200, Request {
- body: Some(&body),
- headers: None,
- route: RouteInfo::EditNickname { guild_id },
- })
-}
-
-/// Edits the current user's profile settings.
-///
-/// For bot users, the password is optional.
-///
-/// # User Accounts
-///
-/// If a new token is received due to a password change, then the stored token
-/// internally will be updated.
-///
-/// **Note**: this token change may cause requests made between the actual token
-/// 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_vec(map)?;
-
- let response = request(Request {
- body: Some(&body),
- headers: None,
- route: RouteInfo::EditProfile,
- })?;
-
- let mut value = serde_json::from_reader::<HyperResponse, Value>(response)?;
-
- if let Some(map) = value.as_object_mut() {
- if !TOKEN.lock().starts_with("Bot ") {
- if let Some(Value::String(token)) = map.remove("token") {
- set_token(&token);
- }
- }
- }
-
- 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_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_vec(&json!({
- "id": role_id,
- "position": position,
- }))?;
-
- fire(Request {
- body: Some(&body),
- headers: None,
- route: RouteInfo::EditRole { guild_id, role_id },
- })
-}
-
-/// Edits a the webhook with the given data.
-///
-/// The Value is a map with optional values of:
-///
-/// - **avatar**: base64-encoded 128x128 image for the webhook's default avatar
-/// (_optional_);
-/// - **name**: the name of the webhook, limited to between 2 and 100 characters
-/// long.
-///
-/// Note that, unlike with [`create_webhook`], _all_ values are optional.
-///
-/// This method requires authentication, whereas [`edit_webhook_with_token`]
-/// does not.
-///
-/// # Examples
-///
-/// Edit the image of a webhook given its Id and unique token:
-///
-/// ```rust,ignore
-/// extern crate serde_json;
-/// extern crate serenity;
-///
-/// use serde_json::builder::ObjectBuilder;
-/// use serenity::http;
-///
-/// let id = 245037420704169985;
-/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
-/// let image = serenity::utils::read_image("./webhook_img.png")
-/// .expect("Error reading image");
-/// let map = ObjectBuilder::new().insert("avatar", image).build();
-///
-/// let edited = http::edit_webhook_with_token(id, token, map)
-/// .expect("Error editing webhook");
-/// ```
-///
-/// [`create_webhook`]: fn.create_webhook.html
-/// [`edit_webhook_with_token`]: fn.edit_webhook_with_token.html
-// The tests are ignored, rather than no_run'd, due to rustdoc tests with
-// external crates being incredibly messy and misleading in the end user's view.
-pub fn edit_webhook(webhook_id: u64, map: &Value) -> Result<Webhook> {
- fire(Request {
- body: Some(map.to_string().as_bytes()),
- headers: None,
- route: RouteInfo::EditWebhook { webhook_id },
- })
-}
-
-/// Edits the webhook with the given data.
-///
-/// Refer to the documentation for [`edit_webhook`] for more information.
-///
-/// This method does _not_ require authentication.
-///
-/// # Examples
-///
-/// Edit the name of a webhook given its Id and unique token:
-///
-/// ```rust,ignore
-/// extern crate serde_json;
-/// extern crate serenity;
-///
-/// use serde_json::builder::ObjectBuilder;
-/// use serenity::http;
-///
-/// let id = 245037420704169985;
-/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
-/// let map = ObjectBuilder::new().insert("name", "new name").build();
-///
-/// let edited = http::edit_webhook_with_token(id, token, map)
-/// .expect("Error editing webhook");
-/// ```
-///
-/// [`edit_webhook`]: fn.edit_webhook.html
-pub fn edit_webhook_with_token(webhook_id: u64, token: &str, map: &JsonMap) -> Result<Webhook> {
- let body = serde_json::to_vec(map)?;
-
- fire(Request {
- body: Some(&body),
- headers: None,
- route: RouteInfo::EditWebhookWithToken { token, webhook_id },
- })
-}
-
-/// Executes a webhook, posting a [`Message`] in the webhook's associated
-/// [`Channel`].
-///
-/// This method does _not_ require authentication.
-///
-/// Pass `true` to `wait` to wait for server confirmation of the message sending
-/// before receiving a response. From the [Discord docs]:
-///
-/// > waits for server confirmation of message send before response, and returns
-/// > the created message body (defaults to false; when false a message that is
-/// > not saved does not return an error)
-///
-/// The map can _optionally_ contain the following data:
-///
-/// - `avatar_url`: Override the default avatar of the webhook with a URL.
-/// - `tts`: Whether this is a text-to-speech message (defaults to `false`).
-/// - `username`: Override the default username of the webhook.
-///
-/// Additionally, _at least one_ of the following must be given:
-///
-/// - `content`: The content of the message.
-/// - `embeds`: An array of rich embeds.
-///
-/// **Note**: For embed objects, all fields are registered by Discord except for
-/// `height`, `provider`, `proxy_url`, `type` (it will always be `rich`),
-/// `video`, and `width`. The rest will be determined by Discord.
-///
-/// # Examples
-///
-/// Sending a webhook with message content of `test`:
-///
-/// ```rust,ignore
-/// extern crate serde_json;
-/// extern crate serenity;
-///
-/// use serde_json::builder::ObjectBuilder;
-/// use serenity::http;
-///
-/// let id = 245037420704169985;
-/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
-/// let map = ObjectBuilder::new().insert("content", "test").build();
-///
-/// let message = match http::execute_webhook(id, token, true, map) {
-/// Ok(Some(message)) => message,
-/// Ok(None) => {
-/// println!("Expected a webhook message");
-///
-/// return;
-/// },
-/// Err(why) => {
-/// println!("Error executing webhook: {:?}", why);
-///
-/// return;
-/// },
-/// };
-/// ```
-///
-/// [`Channel`]: ../model/channel/enum.Channel.html
-/// [`Message`]: ../model/channel/struct.Message.html
-/// [Discord docs]: https://discordapp.com/developers/docs/resources/webhook#querystring-params
-pub fn execute_webhook(webhook_id: u64,
- token: &str,
- wait: bool,
- map: &JsonMap)
- -> Result<Option<Message>> {
- let body = serde_json::to_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);
- }
-
- serde_json::from_reader::<HyperResponse, Message>(response)
- .map(Some)
- .map_err(From::from)
-}
-
-/// Gets the active maintenances from Discord's Status API.
-///
-/// Does not require authentication.
-pub fn get_active_maintenances() -> Result<Vec<Maintenance>> {
- let response = request(Request {
- body: None,
- headers: None,
- route: RouteInfo::GetActiveMaintenance,
- })?;
-
- let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?;
-
- match map.remove("scheduled_maintenances") {
- Some(v) => serde_json::from_value::<Vec<Maintenance>>(v)
- .map_err(From::from),
- None => Ok(vec![]),
- }
-}
-
-/// Gets all the users that are banned in specific guild.
-pub fn get_bans(guild_id: u64) -> Result<Vec<Ban>> {
- fire(Request {
- body: None,
- headers: None,
- route: RouteInfo::GetBans { guild_id },
- })
-}
-
-/// Gets all audit logs in a specific guild.
-pub fn get_audit_logs(guild_id: u64,
- action_type: Option<u8>,
- user_id: Option<u64>,
- before: Option<u64>,
- limit: Option<u8>) -> Result<AuditLogs> {
- 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> {
- 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>> {
- fire(Request {
- body: None,
- headers: None,
- route: RouteInfo::GetChannelInvites { channel_id },
- })
-}
-
-/// Retrieves the webhooks for the given [channel][`GuildChannel`]'s Id.
-///
-/// This method requires authentication.
-///
-/// # Examples
-///
-/// Retrieve all of the webhooks owned by a channel:
-///
-/// ```rust,no_run
-/// use serenity::http;
-///
-/// let channel_id = 81384788765712384;
-///
-/// let webhooks = http::get_channel_webhooks(channel_id)
-/// .expect("Error getting channel webhooks");
-/// ```
-///
-/// [`GuildChannel`]: ../model/channel/struct.GuildChannel.html
-pub fn get_channel_webhooks(channel_id: u64) -> Result<Vec<Webhook>> {
- fire(Request {
- body: None,
- headers: None,
- route: RouteInfo::GetChannelWebhooks { channel_id },
- })
-}
-
-/// Gets channel information.
-pub fn get_channel(channel_id: u64) -> Result<Channel> {
- 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>> {
- 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> {
- 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> {
- fire(Request {
- body: None,
- headers: None,
- route: RouteInfo::GetCurrentUser,
- })
-}
-
-/// Gets current gateway.
-pub fn get_gateway() -> Result<Gateway> {
- fire(Request {
- body: None,
- headers: None,
- route: RouteInfo::GetGateway,
- })
-}
-
-/// Gets guild information.
-pub fn get_guild(guild_id: u64) -> Result<PartialGuild> {
- 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> {
- 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>> {
- 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>> {
- fire(Request {
- body: None,
- headers: None,
- route: RouteInfo::GetGuildInvites { guild_id },
- })
-}
-
-/// Gets a guild's vanity URL if it has one.
-pub fn get_guild_vanity_url(guild_id: u64) -> Result<String> {
- #[derive(Deserialize)]
- struct GuildVanityUrl {
- code: String,
- }
-
- let response = request(Request {
- body: None,
- headers: None,
- route: RouteInfo::GetGuildVanityUrl { guild_id },
- })?;
-
- serde_json::from_reader::<HyperResponse, GuildVanityUrl>(response)
- .map(|x| x.code)
- .map_err(From::from)
-}
-
-/// Gets the members of a guild. Optionally pass a `limit` and the Id of the
-/// user to offset the result by.
-pub fn get_guild_members(guild_id: u64,
- limit: Option<u64>,
- after: Option<u64>)
- -> Result<Vec<Member>> {
- let response = request(Request {
- body: None,
- headers: None,
- route: RouteInfo::GetGuildMembers { after, guild_id, limit },
- })?;
-
- let mut v = serde_json::from_reader::<HyperResponse, Value>(response)?;
-
- if let Some(values) = v.as_array_mut() {
- let num = Value::Number(Number::from(guild_id));
-
- for value in values {
- if let Some(element) = value.as_object_mut() {
- element.insert("guild_id".to_string(), num.clone());
- }
- }
- }
-
- serde_json::from_value::<Vec<Member>>(v).map_err(From::from)
-}
-
-/// Gets the amount of users that can be pruned.
-pub fn get_guild_prune_count(guild_id: u64, map: &Value) -> Result<GuildPrune> {
- // 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>> {
- 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>> {
- fire(Request {
- body: None,
- headers: None,
- route: RouteInfo::GetGuildRoles { guild_id },
- })
-}
-
-/// Retrieves the webhooks for the given [guild][`Guild`]'s Id.
-///
-/// This method requires authentication.
-///
-/// # Examples
-///
-/// Retrieve all of the webhooks owned by a guild:
-///
-/// ```rust,no_run
-/// use serenity::http;
-///
-/// let guild_id = 81384788765712384;
-///
-/// let webhooks = http::get_guild_webhooks(guild_id)
-/// .expect("Error getting guild webhooks");
-/// ```
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-pub fn get_guild_webhooks(guild_id: u64) -> Result<Vec<Webhook>> {
- fire(Request {
- body: None,
- headers: None,
- route: RouteInfo::GetGuildWebhooks { guild_id },
- })
-}
-
-/// Gets a paginated list of the current user's guilds.
-///
-/// The `limit` has a maximum value of 100.
-///
-/// [Discord's documentation][docs]
-///
-/// # Examples
-///
-/// Get the first 10 guilds after a certain guild's Id:
-///
-/// ```rust,no_run
-/// use serenity::http::{GuildPagination, get_guilds};
-/// use serenity::model::id::GuildId;
-///
-/// let guild_id = GuildId(81384788765712384);
-///
-/// let guilds = get_guilds(&GuildPagination::After(guild_id), 10).unwrap();
-/// ```
-///
-/// [docs]: https://discordapp.com/developers/docs/resources/user#get-current-user-guilds
-pub fn get_guilds(target: &GuildPagination, limit: u64) -> Result<Vec<GuildInfo>> {
- let (after, before) = match *target {
- GuildPagination::After(id) => (Some(id.0), None),
- GuildPagination::Before(id) => (None, Some(id.0)),
- };
-
- 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(mut code: &str, stats: bool) -> Result<Invite> {
- #[cfg(feature = "utils")]
- {
- code = ::utils::parse_invite(code);
- }
-
- 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(Request {
- body: None,
- headers: None,
- route: RouteInfo::GetMember { guild_id, user_id },
- })?;
-
- let mut v = serde_json::from_reader::<HyperResponse, Value>(response)?;
-
- if let Some(map) = v.as_object_mut() {
- map.insert("guild_id".to_string(), Value::Number(Number::from(guild_id)));
- }
-
- serde_json::from_value::<Member>(v).map_err(From::from)
-}
-
-/// Gets a message by an Id, bots only.
-pub fn get_message(channel_id: u64, message_id: u64) -> Result<Message> {
- 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>> {
- 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>> {
- 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.
-pub fn get_reaction_users(channel_id: u64,
- message_id: u64,
- reaction_type: &ReactionType,
- limit: u8,
- after: Option<u64>)
- -> Result<Vec<User>> {
- let reaction = reaction_type.as_data();
-
- 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 response = request(Request {
- body: None,
- headers: None,
- route: RouteInfo::GetUnresolvedIncidents,
- })?;
-
- let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?;
-
- match map.remove("incidents") {
- Some(v) => serde_json::from_value::<Vec<Incident>>(v)
- .map_err(From::from),
- None => Ok(vec![]),
- }
-}
-
-/// Gets the upcoming (planned) maintenances from Discord's Status API.
-///
-/// Does not require authentication.
-pub fn get_upcoming_maintenances() -> Result<Vec<Maintenance>> {
- let response = request(Request {
- body: None,
- headers: None,
- route: RouteInfo::GetUpcomingMaintenances,
- })?;
-
- let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?;
-
- match map.remove("scheduled_maintenances") {
- Some(v) => serde_json::from_value::<Vec<Maintenance>>(v)
- .map_err(From::from),
- None => Ok(vec![]),
- }
-}
-
-/// Gets a user by Id.
-pub fn get_user(user_id: u64) -> Result<User> {
- fire(Request {
- body: None,
- headers: None,
- route: RouteInfo::GetUser { user_id },
- })
-}
-
-/// Gets our DM channels.
-pub fn get_user_dm_channels() -> Result<Vec<PrivateChannel>> {
- fire(Request {
- body: None,
- headers: None,
- route: RouteInfo::GetUserDmChannels,
- })
-}
-
-/// Gets all voice regions.
-pub fn get_voice_regions() -> Result<Vec<VoiceRegion>> {
- fire(Request {
- body: None,
- headers: None,
- route: RouteInfo::GetVoiceRegions,
- })
-}
-
-/// Retrieves a webhook given its Id.
-///
-/// This method requires authentication, whereas [`get_webhook_with_token`] does
-/// not.
-///
-/// # Examples
-///
-/// Retrieve a webhook by Id:
-///
-/// ```rust,no_run
-/// use serenity::http;
-///
-/// let id = 245037420704169985;
-/// let webhook = http::get_webhook(id).expect("Error getting webhook");
-/// ```
-///
-/// [`get_webhook_with_token`]: fn.get_webhook_with_token.html
-pub fn get_webhook(webhook_id: u64) -> Result<Webhook> {
- fire(Request {
- body: None,
- headers: None,
- route: RouteInfo::GetWebhook { webhook_id },
- })
-}
-
-/// Retrieves a webhook given its Id and unique token.
-///
-/// This method does _not_ require authentication.
-///
-/// # Examples
-///
-/// Retrieve a webhook by Id and its unique token:
-///
-/// ```rust,no_run
-/// use serenity::http;
-///
-/// let id = 245037420704169985;
-/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
-///
-/// let webhook = http::get_webhook_with_token(id, token)
-/// .expect("Error getting webhook");
-/// ```
-pub fn get_webhook_with_token(webhook_id: u64, token: &str) -> Result<Webhook> {
- 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<()> {
- wind(204, Request {
- body: None,
- headers: None,
- route: RouteInfo::KickMember { guild_id, user_id },
- })
-}
-
-/// Leaves a group DM.
-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<()> {
- 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<()> {
- wind(204, Request {
- body: None,
- headers: None,
- route: RouteInfo::RemoveGroupRecipient { group_id, user_id },
- })
-}
-
-/// Sends file(s) to a channel.
-///
-/// # Errors
-///
-/// Returns an
-/// [`HttpError::InvalidRequest(PayloadTooLarge)`][`HttpError::InvalidRequest`]
-/// if the file is too large to send.
-///
-/// [`HttpError::InvalidRequest`]: enum.HttpError.html#variant.InvalidRequest
-pub fn send_files<'a, T, It: IntoIterator<Item=T>>(channel_id: u64, files: It, map: JsonMap) -> Result<Message>
- where T: Into<AttachmentType<'a>> {
- let uri = api!("/channels/{}/messages", channel_id);
- let url = match Url::parse(&uri) {
- Ok(url) => url,
- Err(_) => return Err(Error::Url(uri)),
- };
-
- let tc = NativeTlsClient::new()?;
- let connector = HttpsConnector::new(tc);
- let mut request = HyperRequest::with_connector(Method::Post, url, &connector)?;
- request
- .headers_mut()
- .set(header::Authorization(TOKEN.lock().clone()));
- request
- .headers_mut()
- .set(header::UserAgent(constants::USER_AGENT.to_string()));
-
- let mut request = Multipart::from_request(request)?;
- let mut file_num = "0".to_string();
-
- for file in files {
- match file.into() {
- AttachmentType::Bytes((mut bytes, filename)) => {
- request
- .write_stream(&file_num, &mut bytes, Some(filename), None)?;
- },
- AttachmentType::File((mut f, filename)) => {
- request
- .write_stream(&file_num, &mut f, Some(filename), None)?;
- },
- AttachmentType::Path(p) => {
- request.write_file(&file_num, &p)?;
- },
- }
-
- unsafe {
- let vec = file_num.as_mut_vec();
- vec[0] += 1;
- }
- }
-
- for (k, v) in map {
- match v {
- Value::Bool(false) => request.write_text(&k, "false")?,
- Value::Bool(true) => request.write_text(&k, "true")?,
- Value::Number(inner) => request.write_text(&k, inner.to_string())?,
- Value::String(inner) => request.write_text(&k, inner)?,
- Value::Object(inner) => request.write_text(&k, serde_json::to_string(&inner)?)?,
- _ => continue,
- };
- }
-
- let response = request.send()?;
-
- if response.status.class() != StatusClass::Success {
- return Err(Error::Http(HttpError::UnsuccessfulRequest(response)));
- }
-
- 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 = serde_json::to_vec(map)?;
-
- 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<()> {
- 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<()> {
- wind(204, Request {
- body: None,
- headers: None,
- route: RouteInfo::RemoveBan { guild_id, user_id },
- })
-}
-
-/// Deletes a single [`Role`] from a [`Member`] in a [`Guild`].
-///
-/// **Note**: Requires the [Manage Roles] permission and respect of role
-/// hierarchy.
-///
-/// [`Guild`]: ../model/guild/struct.Guild.html
-/// [`Member`]: ../model/guild/struct.Member.html
-/// [`Role`]: ../model/guild/struct.Role.html
-/// [Manage Roles]: ../model/permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES
-pub fn remove_member_role(guild_id: u64, user_id: u64, role_id: u64) -> Result<()> {
- 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> {
- // 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<()> {
- 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<()> {
- wind(204, Request {
- body: None,
- headers: None,
- route: RouteInfo::UnpinMessage { channel_id, message_id },
- })
-}
-
-/// 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 try_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(())
-/// # }
-/// #
-/// # fn main() {
-/// # try_main().unwrap();
-/// # }
-/// ```
-///
-/// [`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 try_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(())
-/// # }
-/// #
-/// # fn main() {
-/// # try_main().unwrap();
-/// # }
-/// ```
-///
-/// [`fire`]: fn.fire.html
-pub fn request(req: Request) -> Result<HyperResponse> {
- let response = ratelimiting::perform(req)?;
-
- if response.status.class() == StatusClass::Success {
- Ok(response)
- } else {
- Err(Error::Http(HttpError::UnsuccessfulRequest(response)))
- }
-}
-
-pub(super) 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()
-}
-
-/// 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.
-pub(super) fn wind(expected: u16, req: Request) -> Result<()> {
- let resp = request(req)?;
-
- if resp.status.to_u16() == expected {
- return Ok(());
- }
-
- debug!("Expected {}, got {}", expected, resp.status);
- trace!("Unsuccessful response: {:?}", resp);
-
- Err(Error::Http(HttpError::UnsuccessfulRequest(resp)))
-}
+use constants;
+use hyper::{
+ client::{
+ Request as HyperRequest,
+ Response as HyperResponse
+ },
+ header::{ContentType, Headers},
+ method::Method,
+ mime::{Mime, SubLevel, TopLevel},
+ net::HttpsConnector,
+ header,
+ Error as HyperError,
+ Result as HyperResult,
+ Url
+};
+use hyper_native_tls::NativeTlsClient;
+use internal::prelude::*;
+use model::prelude::*;
+use multipart::client::Multipart;
+use super::{
+ TOKEN,
+ ratelimiting,
+ request::Request,
+ routing::RouteInfo,
+ AttachmentType,
+ GuildPagination,
+ HttpError,
+ StatusClass,
+ StatusCode,
+};
+use serde::de::DeserializeOwned;
+use serde_json;
+use std::{
+ collections::BTreeMap,
+ io::ErrorKind as IoErrorKind,
+};
+
+/// Sets the token to be used across all requests which require authentication.
+///
+/// If you are using the client module, you don't need to use this. If you're
+/// using serenity solely for HTTP, you need to use this.
+///
+/// # Examples
+///
+/// Setting the token from an environment variable:
+///
+/// ```rust,no_run
+/// # use std::error::Error;
+/// #
+/// # fn try_main() -> Result<(), Box<Error>> {
+/// #
+/// use serenity::http;
+/// use std::env;
+///
+/// http::set_token(&env::var("DISCORD_TOKEN")?);
+/// # Ok(())
+/// # }
+/// #
+/// # fn main() {
+/// # try_main().unwrap();
+/// # }
+pub fn set_token(token: &str) { TOKEN.lock().clone_from(&token.to_string()); }
+
+/// Adds a [`User`] as a recipient to a [`Group`].
+///
+/// **Note**: Groups have a limit of 10 recipients, including the current user.
+///
+/// [`Group`]: ../../model/channel/struct.Group.html
+/// [`Group::add_recipient`]: ../../model/channel/struct.Group.html#method.add_recipient
+/// [`User`]: ../../model/user/struct.User.html
+pub fn add_group_recipient(group_id: u64, user_id: u64) -> Result<()> {
+ wind(204, Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::AddGroupRecipient { group_id, user_id },
+ })
+}
+
+/// Adds a single [`Role`] to a [`Member`] in a [`Guild`].
+///
+/// **Note**: Requires the [Manage Roles] permission and respect of role
+/// hierarchy.
+///
+/// [`Guild`]: ../../model/guild/struct.Guild.html
+/// [`Member`]: ../../model/guild/struct.Member.html
+/// [`Role`]: ../../model/guild/struct.Role.html
+/// [Manage Roles]: ../../model/permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES
+pub fn add_member_role(guild_id: u64, user_id: u64, role_id: u64) -> Result<()> {
+ 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
+/// X number of days.
+///
+/// Passing a `delete_message_days` of `0` is equivalent to not removing any
+/// messages. Up to `7` days' worth of messages may be deleted.
+///
+/// **Note**: Requires that you have the [Ban Members] permission.
+///
+/// [`Guild`]: ../../model/guild/struct.Guild.html
+/// [`User`]: ../../model/user/struct.User.html
+/// [Ban Members]: ../../model/permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS
+pub fn ban_user(guild_id: u64, user_id: u64, delete_message_days: u8, reason: &str) -> Result<()> {
+ wind(204, Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::GuildBanUser {
+ delete_message_days: Some(delete_message_days),
+ reason: Some(reason),
+ guild_id,
+ user_id,
+ },
+ })
+}
+
+/// Ban zeyla from a [`Guild`], removing her messages sent in the last X number
+/// of days.
+///
+/// Passing a `delete_message_days` of `0` is equivalent to not removing any
+/// messages. Up to `7` days' worth of messages may be deleted.
+///
+/// **Note**: Requires that you have the [Ban Members] permission.
+///
+/// [`Guild`]: ../model/guild/struct.Guild.html
+/// [Ban Members]: ../model/permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS
+pub fn ban_zeyla(guild_id: u64, delete_message_days: u8, reason: &str) -> Result<()> {
+ ban_user(guild_id, 114_941_315_417_899_012, delete_message_days, reason)
+}
+
+/// Ban luna from a [`Guild`], removing her messages sent in the last X number
+/// of days.
+///
+/// Passing a `delete_message_days` of `0` is equivalent to not removing any
+/// messages. Up to `7` days' worth of messages may be deleted.
+///
+/// **Note**: Requires that you have the [Ban Members] permission.
+///
+/// [`Guild`]: ../model/guild/struct.Guild.html
+/// [Ban Members]: ../model/permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS
+pub fn ban_luna(guild_id: u64, delete_message_days: u8, reason: &str) -> Result<()> {
+ ban_user(guild_id, 180_731_582_049_550_336, delete_message_days, reason)
+}
+
+/// Ban the serenity servermoms from a [`Guild`], removing their messages
+/// sent in the last X number of days.
+///
+/// Passing a `delete_message_days` of `0` is equivalent to not removing any
+/// messages. Up to `7` days' worth of messages may be deleted.
+///
+/// **Note**: Requires that you have the [Ban Members] permission.
+///
+/// [`Guild`]: ../model/guild/struct.Guild.html
+/// [Ban Members]: ../model/permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS
+pub fn ban_servermoms(guild_id: u64, delete_message_days: u8, reason: &str) -> Result<()> {
+ ban_zeyla(guild_id, delete_message_days, reason)?;
+ ban_luna(guild_id, delete_message_days, reason)
+}
+
+/// Broadcasts that the current user is typing in the given [`Channel`].
+///
+/// This lasts for about 10 seconds, and will then need to be renewed to
+/// indicate that the current user is still typing.
+///
+/// This should rarely be used for bots, although it is a good indicator that a
+/// long-running command is still being processed.
+///
+/// [`Channel`]: ../../model/channel/enum.Channel.html
+pub fn broadcast_typing(channel_id: u64) -> Result<()> {
+ wind(204, Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::BroadcastTyping { channel_id },
+ })
+}
+
+/// Creates a [`GuildChannel`] in the [`Guild`] given its Id.
+///
+/// Refer to the Discord's [docs] for information on what fields this requires.
+///
+/// **Note**: Requires the [Manage Channels] permission.
+///
+/// [`Guild`]: ../../model/guild/struct.Guild.html
+/// [`GuildChannel`]: ../../model/channel/struct.GuildChannel.html
+/// [docs]: https://discordapp.com/developers/docs/resources/guild#create-guild-channel
+/// [Manage Channels]: ../../model/permissions/struct.Permissions.html#associatedconstant.MANAGE_CHANNELS
+pub fn create_channel(guild_id: u64, map: &Value) -> Result<GuildChannel> {
+ 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.
+///
+/// View the source code for [`Guild`]'s [`create_emoji`] method to see what
+/// fields this requires.
+///
+/// **Note**: Requires the [Manage Emojis] permission.
+///
+/// [`create_emoji`]: ../../model/guild/struct.Guild.html#method.create_emoji
+/// [`Guild`]: ../../model/guild/struct.Guild.html
+/// [Manage Emojis]: ../../model/permissions/struct.Permissions.html#associatedconstant.MANAGE_EMOJIS
+pub fn create_emoji(guild_id: u64, map: &Value) -> Result<Emoji> {
+ fire(Request {
+ body: Some(map.to_string().as_bytes()),
+ headers: None,
+ route: RouteInfo::CreateEmoji { guild_id },
+ })
+}
+
+/// Creates a guild with the data provided.
+///
+/// Only a [`PartialGuild`] will be immediately returned, and a full [`Guild`]
+/// will be received over a [`Shard`], if at least one is running.
+///
+/// **Note**: This endpoint is currently limited to 10 active guilds. The
+/// limits are raised for whitelisted [GameBridge] applications. See the
+/// [documentation on this endpoint] for more info.
+///
+/// # Examples
+///
+/// Create a guild called `"test"` in the [US West region]:
+///
+/// ```rust,ignore
+/// extern crate serde_json;
+///
+/// use serde_json::builder::ObjectBuilder;
+/// use serde_json::Value;
+/// use serenity::http;
+///
+/// let map = ObjectBuilder::new()
+/// .insert("name", "test")
+/// .insert("region", "us-west")
+/// .build();
+///
+/// let _result = http::create_guild(map);
+/// ```
+///
+/// [`Guild`]: ../../model/guild/struct.Guild.html
+/// [`PartialGuild`]: ../../model/guild/struct.PartialGuild.html
+/// [`Shard`]: ../../gateway/struct.Shard.html
+/// [GameBridge]: https://discordapp.com/developers/docs/topics/gamebridge
+/// [US West Region]: ../../model/guild/enum.Region.html#variant.UsWest
+/// [documentation on this endpoint]:
+/// https://discordapp.com/developers/docs/resources/guild#create-guild
+/// [whitelist]: https://discordapp.com/developers/docs/resources/guild#create-guild
+pub fn create_guild(map: &Value) -> Result<PartialGuild> {
+ fire(Request {
+ body: Some(map.to_string().as_bytes()),
+ headers: None,
+ route: RouteInfo::CreateGuild,
+ })
+}
+
+/// Creates an [`Integration`] for a [`Guild`].
+///
+/// Refer to Discord's [docs] for field information.
+///
+/// **Note**: Requires the [Manage Guild] permission.
+///
+/// [`Guild`]: ../../model/guild/struct.Guild.html
+/// [`Integration`]: ../../model/guild/struct.Integration.html
+/// [Manage Guild]: ../../model/permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD
+/// [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<()> {
+ 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`].
+///
+/// Refer to Discord's [docs] for field information.
+///
+/// All fields are optional.
+///
+/// **Note**: Requires the [Create Invite] permission.
+///
+/// [`GuildChannel`]: ../../model/channel/struct.GuildChannel.html
+/// [`RichInvite`]: ../../model/invite/struct.RichInvite.html
+/// [Create Invite]: ../../model/permissions/struct.Permissions.html#associatedconstant.CREATE_INVITE
+/// [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_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 = 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 = serde_json::to_vec(map)?;
+
+ fire(Request {
+ body: Some(&body),
+ headers: None,
+ route: RouteInfo::CreatePrivateChannel,
+ })
+}
+
+/// Reacts to a message.
+pub fn create_reaction(channel_id: u64,
+ message_id: u64,
+ reaction_type: &ReactionType)
+ -> Result<()> {
+ wind(204, Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::CreateReaction {
+ reaction: &reaction_type.as_data(),
+ channel_id,
+ message_id,
+ },
+ })
+}
+
+/// Creates a role.
+pub fn create_role(guild_id: u64, map: &JsonMap) -> Result<Role> {
+ 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
+/// the given data.
+///
+/// This method requires authentication.
+///
+/// The Value is a map with the values of:
+///
+/// - **avatar**: base64-encoded 128x128 image for the webhook's default avatar
+/// (_optional_);
+/// - **name**: the name of the webhook, limited to between 2 and 100 characters
+/// long.
+///
+/// # Examples
+///
+/// Creating a webhook named `test`:
+///
+/// ```rust,ignore
+/// extern crate serde_json;
+/// extern crate serenity;
+///
+/// use serde_json::builder::ObjectBuilder;
+/// use serenity::http;
+///
+/// let channel_id = 81384788765712384;
+/// let map = ObjectBuilder::new().insert("name", "test").build();
+///
+/// let webhook = http::create_webhook(channel_id, map).expect("Error creating");
+/// ```
+///
+/// [`GuildChannel`]: ../../model/channel/struct.GuildChannel.html
+pub fn create_webhook(channel_id: u64, map: &Value) -> Result<Webhook> {
+ let body = 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> {
+ 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<()> {
+ 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> {
+ fire(Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::DeleteGuild { guild_id },
+ })
+}
+
+/// Removes an integration from a guild.
+pub fn delete_guild_integration(guild_id: u64, integration_id: u64) -> Result<()> {
+ 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> {
+ 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<()> {
+ 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<()> {
+ 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`].
+///
+/// # Examples
+///
+/// ```rust,no_run
+/// use serenity::http;
+/// use serenity::model::id::{ChannelId, MessageId};
+///
+/// let channel_id = ChannelId(7);
+/// let message_id = MessageId(8);
+///
+/// let _ = http::delete_message_reactions(channel_id.0, message_id.0)
+/// .expect("Error deleting reactions");
+/// ```
+///
+/// [`Message`]: ../../model/channel/struct.Message.html
+/// [`Reaction`]: ../../model/channel/struct.Reaction.html
+pub fn delete_message_reactions(channel_id: u64, message_id: u64) -> Result<()> {
+ 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<()> {
+ 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
+/// we have specific permissions.
+pub fn delete_reaction(channel_id: u64,
+ message_id: u64,
+ user_id: Option<u64>,
+ reaction_type: &ReactionType)
+ -> Result<()> {
+ let user = user_id
+ .map(|uid| uid.to_string())
+ .unwrap_or_else(|| "@me".to_string());
+
+ wind(204, Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::DeleteReaction {
+ reaction: &reaction_type.as_data(),
+ user: &user,
+ channel_id,
+ message_id,
+ },
+ })
+}
+
+/// Deletes a role from a server. Can't remove the default everyone role.
+pub fn delete_role(guild_id: u64, role_id: u64) -> Result<()> {
+ wind(204, Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::DeleteRole { guild_id, role_id },
+ })
+}
+
+/// Deletes a [`Webhook`] given its Id.
+///
+/// This method requires authentication, whereas [`delete_webhook_with_token`]
+/// does not.
+///
+/// # Examples
+///
+/// Deletes a webhook given its Id:
+///
+/// ```rust,no_run
+/// use serenity::http;
+/// use std::env;
+///
+/// // Due to the `delete_webhook` function requiring you to authenticate, you
+/// // must have set the token first.
+/// http::set_token(&env::var("DISCORD_TOKEN").unwrap());
+///
+/// http::delete_webhook(245037420704169985).expect("Error deleting webhook");
+/// ```
+///
+/// [`Webhook`]: ../../model/webhook/struct.Webhook.html
+/// [`delete_webhook_with_token`]: fn.delete_webhook_with_token.html
+pub fn delete_webhook(webhook_id: u64) -> Result<()> {
+ wind(204, Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::DeleteWebhook { webhook_id },
+ })
+}
+
+/// Deletes a [`Webhook`] given its Id and unique token.
+///
+/// This method does _not_ require authentication.
+///
+/// # Examples
+///
+/// Deletes a webhook given its Id and unique token:
+///
+/// ```rust,no_run
+/// use serenity::http;
+///
+/// let id = 245037420704169985;
+/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
+///
+/// http::delete_webhook_with_token(id, token).expect("Error deleting webhook");
+/// ```
+///
+/// [`Webhook`]: ../../model/webhook/struct.Webhook.html
+pub fn delete_webhook_with_token(webhook_id: u64, token: &str) -> Result<()> {
+ 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_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 = 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_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_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 = 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_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 = serde_json::to_vec(map)?;
+
+ 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.
+///
+/// Pass `None` to reset the nickname.
+///
+/// [`Guild`]: ../../model/guild/struct.Guild.html
+pub fn edit_nickname(guild_id: u64, new_nickname: Option<&str>) -> Result<()> {
+ let map = json!({ "nick": new_nickname });
+ let body = serde_json::to_vec(&map)?;
+
+ wind(200, Request {
+ body: Some(&body),
+ headers: None,
+ route: RouteInfo::EditNickname { guild_id },
+ })
+}
+
+/// Edits the current user's profile settings.
+///
+/// For bot users, the password is optional.
+///
+/// # User Accounts
+///
+/// If a new token is received due to a password change, then the stored token
+/// internally will be updated.
+///
+/// **Note**: this token change may cause requests made between the actual token
+/// 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_vec(map)?;
+
+ let response = request(Request {
+ body: Some(&body),
+ headers: None,
+ route: RouteInfo::EditProfile,
+ })?;
+
+ let mut value = serde_json::from_reader::<HyperResponse, Value>(response)?;
+
+ if let Some(map) = value.as_object_mut() {
+ if !TOKEN.lock().starts_with("Bot ") {
+ if let Some(Value::String(token)) = map.remove("token") {
+ set_token(&token);
+ }
+ }
+ }
+
+ 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_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_vec(&json!({
+ "id": role_id,
+ "position": position,
+ }))?;
+
+ fire(Request {
+ body: Some(&body),
+ headers: None,
+ route: RouteInfo::EditRole { guild_id, role_id },
+ })
+}
+
+/// Edits a the webhook with the given data.
+///
+/// The Value is a map with optional values of:
+///
+/// - **avatar**: base64-encoded 128x128 image for the webhook's default avatar
+/// (_optional_);
+/// - **name**: the name of the webhook, limited to between 2 and 100 characters
+/// long.
+///
+/// Note that, unlike with [`create_webhook`], _all_ values are optional.
+///
+/// This method requires authentication, whereas [`edit_webhook_with_token`]
+/// does not.
+///
+/// # Examples
+///
+/// Edit the image of a webhook given its Id and unique token:
+///
+/// ```rust,ignore
+/// extern crate serde_json;
+/// extern crate serenity;
+///
+/// use serde_json::builder::ObjectBuilder;
+/// use serenity::http;
+///
+/// let id = 245037420704169985;
+/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
+/// let image = serenity::utils::read_image("./webhook_img.png")
+/// .expect("Error reading image");
+/// let map = ObjectBuilder::new().insert("avatar", image).build();
+///
+/// let edited = http::edit_webhook_with_token(id, token, map)
+/// .expect("Error editing webhook");
+/// ```
+///
+/// [`create_webhook`]: fn.create_webhook.html
+/// [`edit_webhook_with_token`]: fn.edit_webhook_with_token.html
+// The tests are ignored, rather than no_run'd, due to rustdoc tests with
+// external crates being incredibly messy and misleading in the end user's view.
+pub fn edit_webhook(webhook_id: u64, map: &Value) -> Result<Webhook> {
+ fire(Request {
+ body: Some(map.to_string().as_bytes()),
+ headers: None,
+ route: RouteInfo::EditWebhook { webhook_id },
+ })
+}
+
+/// Edits the webhook with the given data.
+///
+/// Refer to the documentation for [`edit_webhook`] for more information.
+///
+/// This method does _not_ require authentication.
+///
+/// # Examples
+///
+/// Edit the name of a webhook given its Id and unique token:
+///
+/// ```rust,ignore
+/// extern crate serde_json;
+/// extern crate serenity;
+///
+/// use serde_json::builder::ObjectBuilder;
+/// use serenity::http;
+///
+/// let id = 245037420704169985;
+/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
+/// let map = ObjectBuilder::new().insert("name", "new name").build();
+///
+/// let edited = http::edit_webhook_with_token(id, token, map)
+/// .expect("Error editing webhook");
+/// ```
+///
+/// [`edit_webhook`]: fn.edit_webhook.html
+pub fn edit_webhook_with_token(webhook_id: u64, token: &str, map: &JsonMap) -> Result<Webhook> {
+ let body = serde_json::to_vec(map)?;
+
+ fire(Request {
+ body: Some(&body),
+ headers: None,
+ route: RouteInfo::EditWebhookWithToken { token, webhook_id },
+ })
+}
+
+/// Executes a webhook, posting a [`Message`] in the webhook's associated
+/// [`Channel`].
+///
+/// This method does _not_ require authentication.
+///
+/// Pass `true` to `wait` to wait for server confirmation of the message sending
+/// before receiving a response. From the [Discord docs]:
+///
+/// > waits for server confirmation of message send before response, and returns
+/// > the created message body (defaults to false; when false a message that is
+/// > not saved does not return an error)
+///
+/// The map can _optionally_ contain the following data:
+///
+/// - `avatar_url`: Override the default avatar of the webhook with a URL.
+/// - `tts`: Whether this is a text-to-speech message (defaults to `false`).
+/// - `username`: Override the default username of the webhook.
+///
+/// Additionally, _at least one_ of the following must be given:
+///
+/// - `content`: The content of the message.
+/// - `embeds`: An array of rich embeds.
+///
+/// **Note**: For embed objects, all fields are registered by Discord except for
+/// `height`, `provider`, `proxy_url`, `type` (it will always be `rich`),
+/// `video`, and `width`. The rest will be determined by Discord.
+///
+/// # Examples
+///
+/// Sending a webhook with message content of `test`:
+///
+/// ```rust,ignore
+/// extern crate serde_json;
+/// extern crate serenity;
+///
+/// use serde_json::builder::ObjectBuilder;
+/// use serenity::http;
+///
+/// let id = 245037420704169985;
+/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
+/// let map = ObjectBuilder::new().insert("content", "test").build();
+///
+/// let message = match http::execute_webhook(id, token, true, map) {
+/// Ok(Some(message)) => message,
+/// Ok(None) => {
+/// println!("Expected a webhook message");
+///
+/// return;
+/// },
+/// Err(why) => {
+/// println!("Error executing webhook: {:?}", why);
+///
+/// return;
+/// },
+/// };
+/// ```
+///
+/// [`Channel`]: ../../model/channel/enum.Channel.html
+/// [`Message`]: ../../model/channel/struct.Message.html
+/// [Discord docs]: https://discordapp.com/developers/docs/resources/webhook#querystring-params
+pub fn execute_webhook(webhook_id: u64,
+ token: &str,
+ wait: bool,
+ map: &JsonMap)
+ -> Result<Option<Message>> {
+ let body = serde_json::to_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);
+ }
+
+ serde_json::from_reader::<HyperResponse, Message>(response)
+ .map(Some)
+ .map_err(From::from)
+}
+
+/// Gets the active maintenances from Discord's Status API.
+///
+/// Does not require authentication.
+pub fn get_active_maintenances() -> Result<Vec<Maintenance>> {
+ let response = request(Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::GetActiveMaintenance,
+ })?;
+
+ let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?;
+
+ match map.remove("scheduled_maintenances") {
+ Some(v) => serde_json::from_value::<Vec<Maintenance>>(v)
+ .map_err(From::from),
+ None => Ok(vec![]),
+ }
+}
+
+/// Gets all the users that are banned in specific guild.
+pub fn get_bans(guild_id: u64) -> Result<Vec<Ban>> {
+ fire(Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::GetBans { guild_id },
+ })
+}
+
+/// Gets all audit logs in a specific guild.
+pub fn get_audit_logs(guild_id: u64,
+ action_type: Option<u8>,
+ user_id: Option<u64>,
+ before: Option<u64>,
+ limit: Option<u8>) -> Result<AuditLogs> {
+ 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> {
+ 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>> {
+ fire(Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::GetChannelInvites { channel_id },
+ })
+}
+
+/// Retrieves the webhooks for the given [channel][`GuildChannel`]'s Id.
+///
+/// This method requires authentication.
+///
+/// # Examples
+///
+/// Retrieve all of the webhooks owned by a channel:
+///
+/// ```rust,no_run
+/// use serenity::http;
+///
+/// let channel_id = 81384788765712384;
+///
+/// let webhooks = http::get_channel_webhooks(channel_id)
+/// .expect("Error getting channel webhooks");
+/// ```
+///
+/// [`GuildChannel`]: ../../model/channel/struct.GuildChannel.html
+pub fn get_channel_webhooks(channel_id: u64) -> Result<Vec<Webhook>> {
+ fire(Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::GetChannelWebhooks { channel_id },
+ })
+}
+
+/// Gets channel information.
+pub fn get_channel(channel_id: u64) -> Result<Channel> {
+ 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>> {
+ 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> {
+ 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> {
+ fire(Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::GetCurrentUser,
+ })
+}
+
+/// Gets current gateway.
+pub fn get_gateway() -> Result<Gateway> {
+ fire(Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::GetGateway,
+ })
+}
+
+/// Gets guild information.
+pub fn get_guild(guild_id: u64) -> Result<PartialGuild> {
+ 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> {
+ 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>> {
+ 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>> {
+ fire(Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::GetGuildInvites { guild_id },
+ })
+}
+
+/// Gets a guild's vanity URL if it has one.
+pub fn get_guild_vanity_url(guild_id: u64) -> Result<String> {
+ #[derive(Deserialize)]
+ struct GuildVanityUrl {
+ code: String,
+ }
+
+ let response = request(Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::GetGuildVanityUrl { guild_id },
+ })?;
+
+ serde_json::from_reader::<HyperResponse, GuildVanityUrl>(response)
+ .map(|x| x.code)
+ .map_err(From::from)
+}
+
+/// Gets the members of a guild. Optionally pass a `limit` and the Id of the
+/// user to offset the result by.
+pub fn get_guild_members(guild_id: u64,
+ limit: Option<u64>,
+ after: Option<u64>)
+ -> Result<Vec<Member>> {
+ let response = request(Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::GetGuildMembers { after, guild_id, limit },
+ })?;
+
+ let mut v = serde_json::from_reader::<HyperResponse, Value>(response)?;
+
+ if let Some(values) = v.as_array_mut() {
+ let num = Value::Number(Number::from(guild_id));
+
+ for value in values {
+ if let Some(element) = value.as_object_mut() {
+ element.insert("guild_id".to_string(), num.clone());
+ }
+ }
+ }
+
+ serde_json::from_value::<Vec<Member>>(v).map_err(From::from)
+}
+
+/// Gets the amount of users that can be pruned.
+pub fn get_guild_prune_count(guild_id: u64, map: &Value) -> Result<GuildPrune> {
+ // 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>> {
+ 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>> {
+ fire(Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::GetGuildRoles { guild_id },
+ })
+}
+
+/// Retrieves the webhooks for the given [guild][`Guild`]'s Id.
+///
+/// This method requires authentication.
+///
+/// # Examples
+///
+/// Retrieve all of the webhooks owned by a guild:
+///
+/// ```rust,no_run
+/// use serenity::http;
+///
+/// let guild_id = 81384788765712384;
+///
+/// let webhooks = http::get_guild_webhooks(guild_id)
+/// .expect("Error getting guild webhooks");
+/// ```
+///
+/// [`Guild`]: ../../model/guild/struct.Guild.html
+pub fn get_guild_webhooks(guild_id: u64) -> Result<Vec<Webhook>> {
+ fire(Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::GetGuildWebhooks { guild_id },
+ })
+}
+
+/// Gets a paginated list of the current user's guilds.
+///
+/// The `limit` has a maximum value of 100.
+///
+/// [Discord's documentation][docs]
+///
+/// # Examples
+///
+/// Get the first 10 guilds after a certain guild's Id:
+///
+/// ```rust,no_run
+/// use serenity::http::{GuildPagination, get_guilds};
+/// use serenity::model::id::GuildId;
+///
+/// let guild_id = GuildId(81384788765712384);
+///
+/// let guilds = get_guilds(&GuildPagination::After(guild_id), 10).unwrap();
+/// ```
+///
+/// [docs]: https://discordapp.com/developers/docs/resources/user#get-current-user-guilds
+pub fn get_guilds(target: &GuildPagination, limit: u64) -> Result<Vec<GuildInfo>> {
+ let (after, before) = match *target {
+ GuildPagination::After(id) => (Some(id.0), None),
+ GuildPagination::Before(id) => (None, Some(id.0)),
+ };
+
+ 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(mut code: &str, stats: bool) -> Result<Invite> {
+ #[cfg(feature = "utils")]
+ {
+ code = ::utils::parse_invite(code);
+ }
+
+ 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(Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::GetMember { guild_id, user_id },
+ })?;
+
+ let mut v = serde_json::from_reader::<HyperResponse, Value>(response)?;
+
+ if let Some(map) = v.as_object_mut() {
+ map.insert("guild_id".to_string(), Value::Number(Number::from(guild_id)));
+ }
+
+ serde_json::from_value::<Member>(v).map_err(From::from)
+}
+
+/// Gets a message by an Id, bots only.
+pub fn get_message(channel_id: u64, message_id: u64) -> Result<Message> {
+ 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>> {
+ 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>> {
+ 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.
+pub fn get_reaction_users(channel_id: u64,
+ message_id: u64,
+ reaction_type: &ReactionType,
+ limit: u8,
+ after: Option<u64>)
+ -> Result<Vec<User>> {
+ let reaction = reaction_type.as_data();
+
+ 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 response = request(Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::GetUnresolvedIncidents,
+ })?;
+
+ let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?;
+
+ match map.remove("incidents") {
+ Some(v) => serde_json::from_value::<Vec<Incident>>(v)
+ .map_err(From::from),
+ None => Ok(vec![]),
+ }
+}
+
+/// Gets the upcoming (planned) maintenances from Discord's Status API.
+///
+/// Does not require authentication.
+pub fn get_upcoming_maintenances() -> Result<Vec<Maintenance>> {
+ let response = request(Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::GetUpcomingMaintenances,
+ })?;
+
+ let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?;
+
+ match map.remove("scheduled_maintenances") {
+ Some(v) => serde_json::from_value::<Vec<Maintenance>>(v)
+ .map_err(From::from),
+ None => Ok(vec![]),
+ }
+}
+
+/// Gets a user by Id.
+pub fn get_user(user_id: u64) -> Result<User> {
+ fire(Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::GetUser { user_id },
+ })
+}
+
+/// Gets our DM channels.
+pub fn get_user_dm_channels() -> Result<Vec<PrivateChannel>> {
+ fire(Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::GetUserDmChannels,
+ })
+}
+
+/// Gets all voice regions.
+pub fn get_voice_regions() -> Result<Vec<VoiceRegion>> {
+ fire(Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::GetVoiceRegions,
+ })
+}
+
+/// Retrieves a webhook given its Id.
+///
+/// This method requires authentication, whereas [`get_webhook_with_token`] does
+/// not.
+///
+/// # Examples
+///
+/// Retrieve a webhook by Id:
+///
+/// ```rust,no_run
+/// use serenity::http;
+///
+/// let id = 245037420704169985;
+/// let webhook = http::get_webhook(id).expect("Error getting webhook");
+/// ```
+///
+/// [`get_webhook_with_token`]: fn.get_webhook_with_token.html
+pub fn get_webhook(webhook_id: u64) -> Result<Webhook> {
+ fire(Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::GetWebhook { webhook_id },
+ })
+}
+
+/// Retrieves a webhook given its Id and unique token.
+///
+/// This method does _not_ require authentication.
+///
+/// # Examples
+///
+/// Retrieve a webhook by Id and its unique token:
+///
+/// ```rust,no_run
+/// use serenity::http;
+///
+/// let id = 245037420704169985;
+/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV";
+///
+/// let webhook = http::get_webhook_with_token(id, token)
+/// .expect("Error getting webhook");
+/// ```
+pub fn get_webhook_with_token(webhook_id: u64, token: &str) -> Result<Webhook> {
+ 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<()> {
+ wind(204, Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::KickMember { guild_id, user_id },
+ })
+}
+
+/// Leaves a group DM.
+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<()> {
+ 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<()> {
+ wind(204, Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::RemoveGroupRecipient { group_id, user_id },
+ })
+}
+
+/// Sends file(s) to a channel.
+///
+/// # Errors
+///
+/// Returns an
+/// [`HttpError::InvalidRequest(PayloadTooLarge)`][`HttpError::InvalidRequest`]
+/// if the file is too large to send.
+///
+/// [`HttpError::InvalidRequest`]: enum.HttpError.html#variant.InvalidRequest
+pub fn send_files<'a, T, It: IntoIterator<Item=T>>(channel_id: u64, files: It, map: JsonMap) -> Result<Message>
+ where T: Into<AttachmentType<'a>> {
+ let uri = api!("/channels/{}/messages", channel_id);
+ let url = match Url::parse(&uri) {
+ Ok(url) => url,
+ Err(_) => return Err(Error::Url(uri)),
+ };
+
+ let tc = NativeTlsClient::new()?;
+ let connector = HttpsConnector::new(tc);
+ let mut request = HyperRequest::with_connector(Method::Post, url, &connector)?;
+ request
+ .headers_mut()
+ .set(header::Authorization(TOKEN.lock().clone()));
+ request
+ .headers_mut()
+ .set(header::UserAgent(constants::USER_AGENT.to_string()));
+
+ let mut request = Multipart::from_request(request)?;
+ let mut file_num = "0".to_string();
+
+ for file in files {
+ match file.into() {
+ AttachmentType::Bytes((mut bytes, filename)) => {
+ request
+ .write_stream(&file_num, &mut bytes, Some(filename), None)?;
+ },
+ AttachmentType::File((mut f, filename)) => {
+ request
+ .write_stream(&file_num, &mut f, Some(filename), None)?;
+ },
+ AttachmentType::Path(p) => {
+ request.write_file(&file_num, &p)?;
+ },
+ }
+
+ unsafe {
+ let vec = file_num.as_mut_vec();
+ vec[0] += 1;
+ }
+ }
+
+ for (k, v) in map {
+ match v {
+ Value::Bool(false) => request.write_text(&k, "false")?,
+ Value::Bool(true) => request.write_text(&k, "true")?,
+ Value::Number(inner) => request.write_text(&k, inner.to_string())?,
+ Value::String(inner) => request.write_text(&k, inner)?,
+ Value::Object(inner) => request.write_text(&k, serde_json::to_string(&inner)?)?,
+ _ => continue,
+ };
+ }
+
+ let response = request.send()?;
+
+ if response.status.class() != StatusClass::Success {
+ return Err(Error::Http(HttpError::UnsuccessfulRequest(response)));
+ }
+
+ 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 = serde_json::to_vec(map)?;
+
+ 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<()> {
+ 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<()> {
+ wind(204, Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::RemoveBan { guild_id, user_id },
+ })
+}
+
+/// Deletes a single [`Role`] from a [`Member`] in a [`Guild`].
+///
+/// **Note**: Requires the [Manage Roles] permission and respect of role
+/// hierarchy.
+///
+/// [`Guild`]: ../../model/guild/struct.Guild.html
+/// [`Member`]: ../../model/guild/struct.Member.html
+/// [`Role`]: ../../model/guild/struct.Role.html
+/// [Manage Roles]: ../../model/permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES
+pub fn remove_member_role(guild_id: u64, user_id: u64, role_id: u64) -> Result<()> {
+ 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> {
+ // 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<()> {
+ 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<()> {
+ wind(204, Request {
+ body: None,
+ headers: None,
+ route: RouteInfo::UnpinMessage { channel_id, message_id },
+ })
+}
+
+/// 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 try_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(())
+/// # }
+/// #
+/// # fn main() {
+/// # try_main().unwrap();
+/// # }
+/// ```
+///
+/// [`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 try_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(())
+/// # }
+/// #
+/// # fn main() {
+/// # try_main().unwrap();
+/// # }
+/// ```
+///
+/// [`fire`]: fn.fire.html
+pub fn request(req: Request) -> Result<HyperResponse> {
+ let response = ratelimiting::perform(req)?;
+
+ if response.status.class() == StatusClass::Success {
+ Ok(response)
+ } else {
+ Err(Error::Http(HttpError::UnsuccessfulRequest(response)))
+ }
+}
+
+pub(super) 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()
+}
+
+/// 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.
+pub(super) fn wind(expected: u16, req: Request) -> Result<()> {
+ let resp = request(req)?;
+
+ if resp.status.to_u16() == expected {
+ return Ok(());
+ }
+
+ debug!("Expected {}, got {}", expected, resp.status);
+ trace!("Unsuccessful response: {:?}", resp);
+
+ Err(Error::Http(HttpError::UnsuccessfulRequest(resp)))
+}
diff --git a/src/model/channel/channel_category.rs b/src/model/channel/channel_category.rs
index 47b2281..f57910e 100644
--- a/src/model/channel/channel_category.rs
+++ b/src/model/channel/channel_category.rs
@@ -125,10 +125,9 @@ impl ChannelCategory {
})
}
- #[cfg(feature = "utils")]
#[inline]
pub fn is_nsfw(&self) -> bool {
- self.kind == ChannelType::Text && (self.nsfw || serenity_utils::is_nsfw(&self.name))
+ self.kind == ChannelType::Text && self.nsfw
}
/// Returns the name of the category.
diff --git a/src/model/channel/channel_id.rs b/src/model/channel/channel_id.rs
index a897002..3f52055 100644
--- a/src/model/channel/channel_id.rs
+++ b/src/model/channel/channel_id.rs
@@ -14,6 +14,8 @@ use builder::{
};
#[cfg(all(feature = "cache", feature = "model"))]
use CACHE;
+#[cfg(all(feature = "cache", feature = "model"))]
+use Cache;
#[cfg(feature = "model")]
use http::{self, AttachmentType};
#[cfg(feature = "model")]
@@ -293,7 +295,17 @@ impl ChannelId {
/// [`Channel`]: ../channel/enum.Channel.html
#[cfg(feature = "cache")]
#[inline]
- pub fn to_channel_cached(self) -> Option<Channel> { CACHE.read().channel(self) }
+ pub fn to_channel_cached(self) -> Option<Channel> {
+ self._to_channel_cached(&CACHE)
+ }
+
+ /// To allow testing pass their own cache instead of using the globale one.
+ #[cfg(feature = "cache")]
+ #[inline]
+ pub(crate) fn _to_channel_cached(self, cache: &RwLock<Cache>) -> Option<Channel> {
+ cache.read().channel(self)
+ }
+
/// Search the cache for the channel. If it can't be found, the channel is
/// requested over REST.
diff --git a/src/model/channel/group.rs b/src/model/channel/group.rs
index ad16fa0..e6946df 100644
--- a/src/model/channel/group.rs
+++ b/src/model/channel/group.rs
@@ -174,12 +174,8 @@ impl Group {
/// Determines if the channel is NSFW.
///
- /// Refer to [`utils::is_nsfw`] for more details.
- ///
/// **Note**: This method is for consistency. This will always return
/// `false`, due to groups not being considered NSFW.
- ///
- /// [`utils::is_nsfw`]: ../../utils/fn.is_nsfw.html
#[inline]
pub fn is_nsfw(&self) -> bool { false }
diff --git a/src/model/channel/guild_channel.rs b/src/model/channel/guild_channel.rs
index 3713004..c403b4b 100644
--- a/src/model/channel/guild_channel.rs
+++ b/src/model/channel/guild_channel.rs
@@ -386,18 +386,14 @@ impl GuildChannel {
/// Determines if the channel is NSFW.
///
- /// Refer to [`utils::is_nsfw`] for more details.
- ///
/// Only [text channels][`ChannelType::Text`] are taken into consideration
/// as being NSFW. [voice channels][`ChannelType::Voice`] are never NSFW.
///
/// [`ChannelType::Text`]: enum.ChannelType.html#variant.Text
/// [`ChannelType::Voice`]: enum.ChannelType.html#variant.Voice
- /// [`utils::is_nsfw`]: ../../utils/fn.is_nsfw.html
- #[cfg(feature = "utils")]
#[inline]
pub fn is_nsfw(&self) -> bool {
- self.kind == ChannelType::Text && (self.nsfw || serenity_utils::is_nsfw(&self.name))
+ self.kind == ChannelType::Text && self.nsfw
}
/// Gets a message from the channel.
diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs
index 5589ad0..e568a28 100644
--- a/src/model/channel/message.rs
+++ b/src/model/channel/message.rs
@@ -546,6 +546,15 @@ impl Message {
http::unpin_message(self.channel_id.0, self.id.0)
}
+ /// Tries to return author's nickname in the current channel's guild.
+ ///
+ /// **Note**:
+ /// If message was sent in a private channel, then the function will return
+ /// `None`.
+ pub fn author_nick(&self) -> Option<String> {
+ self.guild_id.as_ref().and_then(|guild_id| self.author.nick_in(*guild_id))
+ }
+
pub(crate) fn check_content_length(map: &JsonMap) -> Result<()> {
if let Some(content) = map.get("content") {
if let Value::String(ref content) = *content {
diff --git a/src/model/channel/mod.rs b/src/model/channel/mod.rs
index 73e2ff3..bc4ea75 100644
--- a/src/model/channel/mod.rs
+++ b/src/model/channel/mod.rs
@@ -334,11 +334,7 @@ impl Channel {
}
/// Determines if the channel is NSFW.
- ///
- /// Refer to [`utils::is_nsfw`] for more details.
- ///
- /// [`utils::is_nsfw`]: ../../utils/fn.is_nsfw.html
- #[cfg(all(feature = "model", feature = "utils"))]
+ #[cfg(feature = "model")]
#[inline]
pub fn is_nsfw(&self) -> bool {
match *self {
@@ -769,7 +765,7 @@ mod test {
#[test]
fn nsfw_checks() {
let mut channel = guild_channel();
- assert!(channel.is_nsfw());
+ assert!(!channel.is_nsfw());
channel.kind = ChannelType::Voice;
assert!(!channel.is_nsfw());
@@ -778,7 +774,7 @@ mod test {
assert!(!channel.is_nsfw());
channel.name = "nsfw".to_string();
- assert!(channel.is_nsfw());
+ assert!(!channel.is_nsfw());
channel.kind = ChannelType::Voice;
assert!(!channel.is_nsfw());
channel.kind = ChannelType::Text;
diff --git a/src/model/channel/private_channel.rs b/src/model/channel/private_channel.rs
index 2657d3b..4d602c9 100644
--- a/src/model/channel/private_channel.rs
+++ b/src/model/channel/private_channel.rs
@@ -150,12 +150,8 @@ impl PrivateChannel {
/// Determines if the channel is NSFW.
///
- /// Refer to [`utils::is_nsfw`] for more details.
- ///
/// **Note**: This method is for consistency. This will always return
/// `false`, due to DMs not being considered NSFW.
- ///
- /// [`utils::is_nsfw`]: ../../utils/fn.is_nsfw.html
#[inline]
pub fn is_nsfw(&self) -> bool { false }
diff --git a/src/model/guild/mod.rs b/src/model/guild/mod.rs
index 43cde4b..b91e829 100644
--- a/src/model/guild/mod.rs
+++ b/src/model/guild/mod.rs
@@ -1957,7 +1957,7 @@ impl Region {
Region::EuWest => "eu-west",
Region::Frankfurt => "frankfurt",
Region::HongKong => "hongkong",
- Region::Japan => "Japan",
+ Region::Japan => "japan",
Region::London => "london",
Region::Russia => "russia",
Region::Singapore => "singapore",
diff --git a/src/model/guild/role.rs b/src/model/guild/role.rs
index ac969c0..53239a8 100644
--- a/src/model/guild/role.rs
+++ b/src/model/guild/role.rs
@@ -6,7 +6,7 @@ use builder::EditRole;
#[cfg(all(feature = "cache", feature = "model"))]
use internal::prelude::*;
#[cfg(all(feature = "cache", feature = "model"))]
-use {CACHE, http};
+use {CACHE, Cache, http};
#[cfg(all(feature = "cache", feature = "model", feature = "utils"))]
use std::str::FromStr;
@@ -182,9 +182,12 @@ impl RoleId {
/// [`Role`]: ../guild/struct.Role.html
#[cfg(feature = "cache")]
pub fn to_role_cached(self) -> Option<Role> {
- let cache = CACHE.read();
+ self._to_role_cached(&CACHE)
+ }
- for guild in cache.guilds.values() {
+ #[cfg(feature = "cache")]
+ pub(crate) fn _to_role_cached(self, cache: &RwLock<Cache>) -> Option<Role> {
+ for guild in cache.read().guilds.values() {
let guild = guild.read();
if !guild.roles.contains_key(&self) {
diff --git a/src/model/invite.rs b/src/model/invite.rs
index a6aa756..b391f9b 100644
--- a/src/model/invite.rs
+++ b/src/model/invite.rs
@@ -162,7 +162,7 @@ impl Invite {
pub fn url(&self) -> String { format!("https://discord.gg/{}", self.code) }
}
-/// A inimal information about the channel an invite points to.
+/// A minimal information about the channel an invite points to.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct InviteChannel {
pub id: ChannelId,
@@ -253,7 +253,7 @@ pub struct RichInvite {
pub max_age: u64,
/// The maximum number of times that an invite may be used before it expires.
- /// Note that this does not supercede the [`max_age`] value, if the value of
+ /// Note that this does not supersede the [`max_age`] value, if the value of
/// [`temporary`] is `true`. If the value of `temporary` is `false`, then the
/// invite _will_ self-expire after the given number of max uses.
diff --git a/src/model/misc.rs b/src/model/misc.rs
index ff0f3de..9c09a9b 100644
--- a/src/model/misc.rs
+++ b/src/model/misc.rs
@@ -156,7 +156,7 @@ macro_rules! impl_from_str {
type Err = $err;
fn from_str(s: &str) -> StdResult<Self, Self::Err> {
- Ok(match utils::parse_username(s) {
+ Ok(match utils::parse_mention(s) {
Some(id) => $id(id),
None => s.parse::<u64>().map($id).map_err(|_| $err::InvalidFormat)?,
})
diff --git a/src/model/permissions.rs b/src/model/permissions.rs
index 784071e..b318416 100644
--- a/src/model/permissions.rs
+++ b/src/model/permissions.rs
@@ -9,7 +9,7 @@
//! presets are available. These are [`PRESET_GENERAL`], [`PRESET_TEXT`], and
//! [`PRESET_VOICE`].
//!
-//! Permissions follow a heirarchy:
+//! Permissions follow a hierarchy:
//!
//! - An account can grant roles to users that are of a lower position than
//! its highest role;
diff --git a/src/model/user.rs b/src/model/user.rs
index 694f098..2c4ad00 100644
--- a/src/model/user.rs
+++ b/src/model/user.rs
@@ -612,7 +612,7 @@ impl User {
///
/// # Examples
///
- /// If maintaing a very long-running bot, you may want to periodically
+ /// If maintaining a very long-running bot, you may want to periodically
/// refresh information about certain users if the state becomes
/// out-of-sync:
///
@@ -712,6 +712,29 @@ impl User {
/// ```
#[inline]
pub fn tag(&self) -> String { tag(&self.name, self.discriminator) }
+
+ /// Returns the user's nickname in the given `guild_id`.
+ ///
+ /// If none is used, it returns `None`.
+ #[inline]
+ pub fn nick_in<G>(&self, guild_id: G) -> Option<String>
+ where G: Into<GuildId> {
+ self._nick_in(guild_id.into())
+ }
+
+ fn _nick_in(&self, guild_id: GuildId) -> Option<String> {
+ #[cfg(feature = "cache")]
+ {
+ guild_id.to_guild_cached().and_then(|guild| {
+ guild.read().members.get(&self.id).and_then(|member| member.nick.clone())
+ })
+ }
+
+ #[cfg(not(feature = "cache"))]
+ {
+ guild_id.member(&self.id).and_then(|member| member.nick.clone())
+ }
+ }
}
impl fmt::Display for User {
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index 347e19a..0696aad 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -13,15 +13,26 @@ pub use self::{
use base64;
use internal::prelude::*;
-use model::id::EmojiId;
-use model::misc::EmojiIdentifier;
+use prelude::RwLock;
+use model::{
+ channel::Channel,
+ misc::EmojiIdentifier,
+ id::{
+ ChannelId,
+ GuildId,
+ EmojiId,
+ RoleId,
+ UserId,
+ },
+};
use std::{
collections::HashMap,
ffi::OsStr,
fs::File,
hash::{BuildHasher, Hash},
io::Read,
- path::Path
+ path::Path,
+ str::FromStr,
};
#[cfg(feature = "cache")]
@@ -91,6 +102,7 @@ pub fn vecmap_to_json_map<K: PartialEq + ToString>(map: VecMap<K, Value>) -> Map
///
/// assert!(!utils::is_nsfw("nsfwstuff"));
/// ```
+#[deprecated(since = "0.5.10", note = "Discord no longer turns a channel NSFW based on its name.")]
pub fn is_nsfw(name: &str) -> bool {
name == "nsfw" || name.chars().count() > 5 && name.starts_with("nsfw-")
}
@@ -261,6 +273,31 @@ pub fn parse_channel(mention: &str) -> Option<u64> {
}
}
+/// Retrieve the ID number out of a channel, role, or user mention.
+///
+/// If the mention is invalid, `None` is returned.
+///
+/// # Examples
+///
+/// ```rust
+/// use serenity::utils::parse_mention;
+///
+/// assert_eq!(parse_mention("<@136510335967297536>"), Some(136510335967297536));
+/// assert_eq!(parse_mention("<@&137235212097683456>"), Some(137235212097683456));
+/// assert_eq!(parse_mention("<#137234234728251392>"), Some(137234234728251392));
+/// ```
+pub fn parse_mention(mention: &str) -> Option<u64> {
+ if mention.starts_with("<@&") {
+ parse_role(mention)
+ } else if mention.starts_with("<@") || mention.starts_with("<@!") {
+ parse_username(mention)
+ } else if mention.starts_with("<#") {
+ parse_channel(mention)
+ } else {
+ None
+ }
+}
+
/// Retrieves the name and Id from an emoji mention, in the form of an
/// `EmojiIdentifier`.
///
@@ -501,6 +538,328 @@ pub fn with_cache_mut<T, F>(mut f: F) -> T
f(&mut cache)
}
+/// Struct that allows to alter [`content_safe`]'s behaviour.
+///
+/// [`content_safe`]: fn.content_safe.html
+#[cfg(feature = "cache")]
+#[derive(Clone, Debug)]
+pub struct ContentSafeOptions {
+ clean_role: bool,
+ clean_user: bool,
+ clean_channel: bool,
+ clean_here: bool,
+ clean_everyone: bool,
+ show_discriminator: bool,
+ guild_reference: Option<GuildId>,
+}
+
+#[cfg(feature = "cache")]
+impl ContentSafeOptions {
+ pub fn new() -> Self {
+ ContentSafeOptions::default()
+ }
+
+ /// [`content_safe`] will replace role mentions (`<@&{id}>`) with its name
+ /// prefixed with `@` (`@rolename`) or with `@deleted-role` if the
+ /// identifier is invalid.
+ ///
+ /// [`content_safe`]: fn.content_safe.html
+ pub fn clean_role(mut self, b: bool) -> Self {
+ self.clean_role = b;
+
+ self
+ }
+
+ /// If set to true, [`content_safe`] will replace user mentions
+ /// (`<@!{id}>` or `<@{id}>`) with the user's name prefixed with `@`
+ /// (`@username`) or with `@invalid-user` if the identifier is invalid.
+ ///
+ /// [`content_safe`]: fn.content_safe.html
+ pub fn clean_user(mut self, b: bool) -> Self {
+ self.clean_user = b;
+
+ self
+ }
+
+ /// If set to true, [`content_safe`] will replace channel mentions
+ /// (`<#{id}>`) with the channel's name prefixed with `#`
+ /// (`#channelname`) or with `#deleted-channel` if the identifier is
+ /// invalid.
+ ///
+ /// [`content_safe`]: fn.content_safe.html
+ pub fn clean_channel(mut self, b: bool) -> Self {
+ self.clean_channel = b;
+
+ self
+ }
+
+ /// If set to true, if [`content_safe`] replaces a user mention it will
+ /// add their four digit discriminator with a preceeding `#`,
+ /// turning `@username` to `@username#discriminator`.
+ ///
+ /// [`content_safe`]: fn.content_safe.html
+ pub fn show_discriminator(mut self, b: bool) -> Self {
+ self.show_discriminator = b;
+
+ self
+ }
+
+ /// If set, [`content_safe`] will replace a user mention with the user's
+ /// display name in passed `guild`.
+ ///
+ /// [`content_safe`]: fn.content_safe.html
+ pub fn display_as_member_from<G: Into<GuildId>>(mut self, guild: G) -> Self {
+ self.guild_reference = Some(guild.into());
+
+ self
+ }
+
+ /// If set, [`content_safe`] will replace `@here` with a non-pinging
+ /// alternative.
+ ///
+ /// [`content_safe`]: fn.content_safe.html
+ pub fn clean_here(mut self, b: bool) -> Self {
+ self.clean_here = b;
+
+ self
+ }
+
+ /// If set, [`content_safe`] will replace `@everyone` with a non-pinging
+ /// alternative.
+ ///
+ /// [`content_safe`]: fn.content_safe.html
+ pub fn clean_everyone(mut self, b: bool) -> Self {
+ self.clean_everyone = b;
+
+ self
+ }
+}
+
+#[cfg(feature = "cache")]
+impl Default for ContentSafeOptions {
+ /// Instantiates with all options set to `true`.
+ fn default() -> Self {
+ ContentSafeOptions {
+ clean_role: true,
+ clean_user: true,
+ clean_channel: true,
+ clean_here: true,
+ clean_everyone: true,
+ show_discriminator: true,
+ guild_reference: None,
+ }
+ }
+}
+
+#[cfg(feature = "cache")]
+#[inline]
+fn clean_roles(cache: &RwLock<Cache>, s: &mut String) {
+ let mut progress = 0;
+
+ while let Some(mut mention_start) = s[progress..].find("<@&") {
+ mention_start += progress;
+
+ if let Some(mut mention_end) = s[mention_start..].find(">") {
+ mention_end += mention_start;
+ mention_start += "<@&".len();
+
+ if let Ok(id) = RoleId::from_str(&s[mention_start..mention_end]) {
+ let to_replace = format!("<@&{}>", &s[mention_start..mention_end]);
+
+ *s = if let Some(role) = id._to_role_cached(&cache) {
+ s.replace(&to_replace, &format!("@{}", &role.name))
+ } else {
+ s.replace(&to_replace, &"@deleted-role")
+ };
+ } else {
+ let id = &s[mention_start..mention_end].to_string();
+
+ if !id.is_empty() && id.as_bytes().iter()
+ .all(u8::is_ascii_digit){
+ let to_replace = format!("<@&{}>", id);
+
+ *s = s.replace(&to_replace, &"@deleted-role");
+ } else {
+ progress = mention_end;
+ }
+ }
+ } else {
+ break;
+ }
+ }
+}
+
+#[cfg(feature = "cache")]
+#[inline]
+fn clean_channels(cache: &RwLock<Cache>, s: &mut String) {
+ let mut progress = 0;
+
+ while let Some(mut mention_start) = s[progress..].find("<#") {
+ mention_start += progress;
+
+ if let Some(mut mention_end) = s[mention_start..].find(">") {
+ mention_end += mention_start;
+ mention_start += "<#".len();
+
+ if let Ok(id) = ChannelId::from_str(&s[mention_start..mention_end]) {
+ let to_replace = format!("<#{}>", &s[mention_start..mention_end]);
+
+ *s = if let Some(Channel::Guild(channel)) = id._to_channel_cached(&cache) {
+ let replacement = format!("#{}", &channel.read().name);
+ s.replace(&to_replace, &replacement)
+ } else {
+ s.replace(&to_replace, &"#deleted-channel")
+ };
+ } else {
+ let id = &s[mention_start..mention_end].to_string();
+
+ if !id.is_empty() && id.as_bytes().iter()
+ .all(u8::is_ascii_digit) {
+ let to_replace = format!("<#{}>", id);
+
+ *s = s.replace(&to_replace, &"#deleted-channel");
+ } else {
+ progress = mention_end;
+ }
+ }
+ } else {
+ break;
+ }
+ }
+}
+
+#[cfg(feature = "cache")]
+#[inline]
+fn clean_users(cache: &RwLock<Cache>, s: &mut String, show_discriminator: bool, guild: Option<GuildId>) {
+ let mut progress = 0;
+
+ while let Some(mut mention_start) = s[progress..].find("<@") {
+ mention_start += progress;
+
+ if let Some(mut mention_end) = s[mention_start..].find(">") {
+ mention_end += mention_start;
+ mention_start += "<@".len();
+ let mut has_exclamation = false;
+
+ if s[mention_start..].as_bytes().get(0).map_or(false, |c| *c == b'!') {
+ mention_start += "!".len();
+ has_exclamation = true;
+ }
+
+ if let Ok(id) = UserId::from_str(&s[mention_start..mention_end]) {
+ let mut replacement = if let Some(guild) = guild {
+
+ if let Some(guild) = cache.read().guild(&guild) {
+
+ if let Some(member) = guild.read().members.get(&id) {
+
+ if show_discriminator {
+ format!("@{}", member.distinct())
+ } else {
+ format!("@{}", member.display_name())
+ }
+ } else {
+ "@invalid-user".to_string()
+ }
+ } else {
+ "@invalid-user".to_string()
+ }
+ } else {
+ let user = cache.read().users.get(&id).map(|user| user.clone());
+
+ if let Some(user) = user {
+ let user = user.read();
+
+ if show_discriminator {
+ format!("@{}#{:04}", user.name, user.discriminator)
+ } else {
+ format!("@{}", user.name)
+ }
+ } else {
+ "@invalid-user".to_string()
+ }
+ };
+
+ let code_start = if has_exclamation { "<@!" } else { "<@" };
+ let to_replace = format!("{}{}>", code_start, &s[mention_start..mention_end]);
+
+ *s = s.replace(&to_replace, &replacement)
+ } else {
+ let id = &s[mention_start..mention_end].to_string();
+
+ if !id.is_empty() && id.as_bytes().iter().all(u8::is_ascii_digit) {
+ let code_start = if has_exclamation { "<@!" } else { "<@" };
+ let to_replace = format!("{}{}>", code_start, id);
+
+ *s = s.replace(&to_replace, &"@invalid-user");
+ } else {
+ progress = mention_end;
+ }
+ }
+ } else {
+ break;
+ }
+ }
+}
+
+/// Transforms role, channel, user, `@everyone` and `@here` mentions
+/// into raw text by using the [`Cache`] only.
+///
+/// [`ContentSafeOptions`] decides what kind of mentions should be filtered
+/// and how the raw-text will be displayed.
+///
+/// # Examples
+///
+/// Sanitise an `@everyone` mention.
+///
+/// ```rust
+/// use serenity::utils::{
+/// content_safe,
+/// ContentSafeOptions,
+/// };
+///
+/// let with_mention = "@everyone";
+/// let without_mention = content_safe(&with_mention, &ContentSafeOptions::default());
+///
+/// assert_eq!("@\u{200B}everyone".to_string(), without_mention);
+/// ```
+/// [`ContentSafeOptions`]: struct.ContentSafeOptions.html
+/// [`Cache`]: ../cache/struct.Cache.html
+#[cfg(feature = "cache")]
+pub fn content_safe(s: &str, options: &ContentSafeOptions) -> String {
+ let cache = &CACHE;
+
+ _content_safe(&cache, s, options)
+}
+
+
+#[cfg(feature = "cache")]
+fn _content_safe(cache: &RwLock<Cache>, s: &str, options: &ContentSafeOptions) -> String {
+ let mut s = s.to_string();
+
+ if options.clean_role {
+ clean_roles(&cache, &mut s);
+ }
+
+ if options.clean_channel {
+ clean_channels(&cache, &mut s);
+ }
+
+ if options.clean_user {
+ clean_users(&cache, &mut s, options.show_discriminator, options.guild_reference);
+ }
+
+ if options.clean_here {
+ s = s.replace("@here", "@\u{200B}here");
+ }
+
+ if options.clean_everyone {
+ s = s.replace("@everyone", "@\u{200B}everyone");
+ }
+
+ s
+}
+
#[cfg(test)]
mod test {
use super::*;
@@ -541,6 +900,7 @@ mod test {
assert_eq!(parsed, ["a", "b c", "d", "e f", "g"]);
}
+ #[allow(deprecated)]
#[test]
fn test_is_nsfw() {
assert!(!is_nsfw("general"));
@@ -550,4 +910,197 @@ mod test {
assert!(!is_nsfw("général"));
assert!(is_nsfw("nsfw-général"));
}
+
+ #[test]
+ fn test_content_safe() {
+ use model::{
+ user::User,
+ Permissions,
+ prelude::*,
+ };
+ use chrono::DateTime;
+ use std::{
+ collections::HashMap,
+ sync::Arc,
+ };
+
+ let user = User {
+ id: UserId(100000000000000000),
+ avatar: None,
+ bot: false,
+ discriminator: 0000,
+ name: "Crab".to_string(),
+ };
+
+ let mut guild = Guild {
+ afk_channel_id: None,
+ afk_timeout: 0,
+ application_id: None,
+ channels: HashMap::new(),
+ default_message_notifications: DefaultMessageNotificationLevel::All,
+ emojis: HashMap::new(),
+ explicit_content_filter: ExplicitContentFilter::None,
+ features: Vec::new(),
+ icon: None,
+ id: GuildId(381880193251409931),
+ joined_at: DateTime::parse_from_str(
+ "1983 Apr 13 12:09:14.274 +0000",
+ "%Y %b %d %H:%M:%S%.3f %z").unwrap(),
+ large: false,
+ member_count: 1,
+ members: HashMap::new(),
+ mfa_level: MfaLevel::None,
+ name: "serenity".to_string(),
+ owner_id: UserId(114941315417899012),
+ presences: HashMap::new(),
+ region: "Ferris Island".to_string(),
+ roles: HashMap::new(),
+ splash: None,
+ system_channel_id: None,
+ verification_level: VerificationLevel::None,
+ voice_states: HashMap::new(),
+ };
+
+ let member = Member {
+ deaf: false,
+ guild_id: guild.id,
+ joined_at: None,
+ mute: false,
+ nick: Some("Ferris".to_string()),
+ roles: Vec::new(),
+ user: Arc::new(RwLock::new(user.clone())),
+ };
+
+ let role = Role {
+ id: RoleId(333333333333333333),
+ colour: Colour::ORANGE,
+ hoist: true,
+ managed: false,
+ mentionable: true,
+ name: "ferris-club-member".to_string(),
+ permissions: Permissions::all(),
+ position: 0,
+ };
+
+ let channel = GuildChannel {
+ id: ChannelId(111880193700067777),
+ bitrate: None,
+ category_id: None,
+ guild_id: guild.id,
+ kind: ChannelType::Text,
+ last_message_id: None,
+ last_pin_timestamp: None,
+ name: "general".to_string(),
+ permission_overwrites: Vec::new(),
+ position: 0,
+ topic: None,
+ user_limit: None,
+ nsfw: false,
+ };
+
+ let cache = RwLock::new(Cache::default());
+
+ {
+ let mut cache = cache.try_write().unwrap();
+ guild.members.insert(user.id, member.clone());
+ guild.roles.insert(role.id, role.clone());
+ cache.users.insert(user.id, Arc::new(RwLock::new(user.clone())));
+ cache.guilds.insert(guild.id, Arc::new(RwLock::new(guild.clone())));
+ cache.channels.insert(channel.id, Arc::new(RwLock::new(channel.clone())));
+ }
+
+ let with_user_metions = "<@!100000000000000000> <@!000000000000000000> <@123> <@!123> \
+ <@!123123123123123123123> <@123> <@123123123123123123> <@!invalid> \
+ <@invalid> <@日本語 한국어$§)[/__#\\(/&2032$§#> \
+ <@!i)/==(<<>z/9080)> <@!1231invalid> <@invalid123> \
+ <@123invalid> <@> <@ ";
+
+ let without_user_mentions = "@Crab#0000 @invalid-user @invalid-user @invalid-user \
+ @invalid-user @invalid-user @invalid-user <@!invalid> \
+ <@invalid> <@日本語 한국어$§)[/__#\\(/&2032$§#> \
+ <@!i)/==(<<>z/9080)> <@!1231invalid> <@invalid123> \
+ <@123invalid> <@> <@ ";
+
+ // User mentions
+ let options = ContentSafeOptions::default();
+ assert_eq!(without_user_mentions, _content_safe(&cache, with_user_metions, &options));
+
+ let options = ContentSafeOptions::default();
+ assert_eq!(format!("@{}#{:04}", user.name, user.discriminator),
+ _content_safe(&cache, "<@!100000000000000000>", &options));
+
+ let options = ContentSafeOptions::default();
+ assert_eq!(format!("@{}#{:04}", user.name, user.discriminator),
+ _content_safe(&cache, "<@100000000000000000>", &options));
+
+ let options = options.show_discriminator(false);
+ assert_eq!(format!("@{}", user.name),
+ _content_safe(&cache, "<@!100000000000000000>", &options));
+
+ let options = options.show_discriminator(false);
+ assert_eq!(format!("@{}", user.name),
+ _content_safe(&cache, "<@100000000000000000>", &options));
+
+ let options = options.display_as_member_from(guild.id);
+ assert_eq!(format!("@{}", member.nick.unwrap()),
+ _content_safe(&cache, "<@!100000000000000000>", &options));
+
+ let options = options.clean_user(false);
+ assert_eq!(with_user_metions,
+ _content_safe(&cache, with_user_metions, &options));
+
+ // Channel mentions
+ let with_channel_mentions = "<#> <#deleted-channel> #deleted-channel <#0> \
+ #unsafe-club <#111880193700067777> <#ferrisferrisferris> \
+ <#000000000000000000>";
+
+ let without_channel_mentions = "<#> <#deleted-channel> #deleted-channel \
+ #deleted-channel #unsafe-club #general <#ferrisferrisferris> \
+ #deleted-channel";
+
+ assert_eq!(without_channel_mentions,
+ _content_safe(&cache, with_channel_mentions, &options));
+
+ let options = options.clean_channel(false);
+ assert_eq!(with_channel_mentions,
+ _content_safe(&cache, with_channel_mentions, &options));
+
+ // Role mentions
+ let with_role_mentions = "<@&> @deleted-role <@&9829> \
+ <@&333333333333333333> <@&000000000000000000>";
+
+ let without_role_mentions = "<@&> @deleted-role @deleted-role \
+ @ferris-club-member @deleted-role";
+
+ assert_eq!(without_role_mentions,
+ _content_safe(&cache, with_role_mentions, &options));
+
+ let options = options.clean_role(false);
+ assert_eq!(with_role_mentions,
+ _content_safe(&cache, with_role_mentions, &options));
+
+ // Everyone mentions
+ let with_everyone_mention = "@everyone";
+
+ let without_everyone_mention = "@\u{200B}everyone";
+
+ assert_eq!(without_everyone_mention,
+ _content_safe(&cache, with_everyone_mention, &options));
+
+ let options = options.clean_everyone(false);
+ assert_eq!(with_everyone_mention,
+ _content_safe(&cache, with_everyone_mention, &options));
+
+ // Here mentions
+ let with_here_mention = "@here";
+
+ let without_here_mention = "@\u{200B}here";
+
+ assert_eq!(without_here_mention,
+ _content_safe(&cache, with_here_mention, &options));
+
+ let options = options.clean_here(false);
+ assert_eq!(with_here_mention,
+ _content_safe(&cache, with_here_mention, &options));
+ }
}
diff --git a/src/voice/connection.rs b/src/voice/connection.rs
index 278e6b4..5f806a0 100644
--- a/src/voice/connection.rs
+++ b/src/voice/connection.rs
@@ -585,7 +585,7 @@ fn start_threads(client: Arc<Mutex<Client>>, udp: &UdpSocket) -> Result<ThreadIt
while let Ok(Some(value)) = client.lock().recv_json() {
let msg = match VoiceEvent::deserialize(value) {
Ok(msg) => msg,
- Err(why) => break,
+ Err(_) => break,
};
if tx_clone.send(ReceiverStatus::Websocket(msg)).is_err() {
diff --git a/src/voice/threading.rs b/src/voice/threading.rs
index 1e826a4..55e3651 100644
--- a/src/voice/threading.rs
+++ b/src/voice/threading.rs
@@ -54,7 +54,7 @@ fn runner(rx: &MpscReceiver<Status>) {
senders.push(s);
},
Err(TryRecvError::Empty) => {
- // If we receieved nothing, then we can perform an update.
+ // If we received nothing, then we can perform an update.
break;
},
Err(TryRecvError::Disconnected) => {