aboutsummaryrefslogtreecommitdiff
path: root/src/framework/standard
diff options
context:
space:
mode:
authorZeyla Hellyer <[email protected]>2017-08-19 09:36:15 -0700
committerZeyla Hellyer <[email protected]>2017-08-19 09:39:44 -0700
commit948b27ce74e8dce458d427d8159f2a821d4d7cec (patch)
treebf82bedd1821ca210e4a9f08644581486738aed6 /src/framework/standard
parentAdd html_root_url (diff)
downloadserenity-948b27ce74e8dce458d427d8159f2a821d4d7cec.tar.xz
serenity-948b27ce74e8dce458d427d8159f2a821d4d7cec.zip
Move builtin framework impl to its own module
The framework is now moved in its entirity to the `framework` module, with the `Framework` trait currently on its own and the builtin implementation provided. The builtin implementation has been renamed to "Standard". Upgrade path: Rename the `BuiltinFramework` import to `StandardFramework`. Instead of importing builtin framework items from `serenity::framework`, import them from `serenity::framework::standard`. This is the beginning to #60. The root `framework` module (non-standard implementation) will be built more by the time it's closed.
Diffstat (limited to 'src/framework/standard')
-rw-r--r--src/framework/standard/buckets.rs58
-rw-r--r--src/framework/standard/command.rs157
-rw-r--r--src/framework/standard/configuration.rs451
-rw-r--r--src/framework/standard/create_command.rs245
-rw-r--r--src/framework/standard/create_group.rs86
-rw-r--r--src/framework/standard/help_commands.rs346
-rw-r--r--src/framework/standard/mod.rs1026
7 files changed, 2369 insertions, 0 deletions
diff --git a/src/framework/standard/buckets.rs b/src/framework/standard/buckets.rs
new file mode 100644
index 0000000..f2c4486
--- /dev/null
+++ b/src/framework/standard/buckets.rs
@@ -0,0 +1,58 @@
+use chrono::Utc;
+use std::collections::HashMap;
+use std::default::Default;
+use client::Context;
+use model::{ChannelId, GuildId, UserId};
+
+#[cfg(feature = "cache")]
+type Check = Fn(&mut Context, Option<GuildId>, ChannelId, UserId) -> bool + 'static;
+
+#[cfg(not(feature = "cache"))]
+type Check = Fn(&mut Context, ChannelId, UserId) -> bool + 'static;
+
+pub(crate) struct Ratelimit {
+ pub delay: i64,
+ pub limit: Option<(i64, i32)>,
+}
+
+#[derive(Default)]
+pub(crate) struct MemberRatelimit {
+ pub last_time: i64,
+ pub set_time: i64,
+ pub tickets: i32,
+}
+
+pub(crate) struct Bucket {
+ pub ratelimit: Ratelimit,
+ pub users: HashMap<u64, MemberRatelimit>,
+ pub check: Option<Box<Check>>,
+}
+
+impl Bucket {
+ pub fn take(&mut self, user_id: u64) -> i64 {
+ let time = Utc::now().timestamp();
+ let user = self.users.entry(user_id).or_insert_with(
+ MemberRatelimit::default,
+ );
+
+ if let Some((timespan, limit)) = self.ratelimit.limit {
+ if (user.tickets + 1) > limit {
+ if time < (user.set_time + timespan) {
+ return (user.set_time + timespan) - time;
+ } else {
+ user.tickets = 0;
+ user.set_time = time;
+ }
+ }
+ }
+
+ if time < user.last_time + self.ratelimit.delay {
+ (user.last_time + self.ratelimit.delay) - time
+ } else {
+ user.tickets += 1;
+ user.last_time = time;
+
+ 0
+ }
+ }
+}
diff --git a/src/framework/standard/command.rs b/src/framework/standard/command.rs
new file mode 100644
index 0000000..01c4c5e
--- /dev/null
+++ b/src/framework/standard/command.rs
@@ -0,0 +1,157 @@
+use std::sync::Arc;
+use super::Configuration;
+use client::Context;
+use model::{Message, Permissions};
+use std::collections::HashMap;
+
+pub type Check = Fn(&mut Context, &Message, &[String], &Arc<Command>) -> bool + 'static;
+pub type Exec = Fn(&mut Context, &Message, Vec<String>, String) -> Result<(), String> + 'static;
+pub type Help = Fn(&mut Context,
+ &Message,
+ HashMap<String, Arc<CommandGroup>>,
+ &[String])
+ -> Result<(), String>
+ + 'static;
+pub type BeforeHook = Fn(&mut Context, &Message, &str) -> bool + 'static;
+pub type AfterHook = Fn(&mut Context, &Message, &str, Result<(), String>) + 'static;
+pub(crate) type InternalCommand = Arc<Command>;
+pub type PrefixCheck = Fn(&mut Context, &Message) -> Option<String> + 'static;
+
+pub enum CommandOrAlias {
+ Alias(String),
+ Command(InternalCommand),
+}
+
+/// Command function type. Allows to access internal framework things inside
+/// your commands.
+pub enum CommandType {
+ StringResponse(String),
+ Basic(Box<Exec>),
+ WithCommands(Box<Help>),
+}
+
+#[derive(Default)]
+pub struct CommandGroup {
+ pub prefix: Option<String>,
+ pub commands: HashMap<String, CommandOrAlias>,
+}
+
+/// Command struct used to store commands internally.
+pub struct Command {
+ /// A set of checks to be called prior to executing the command. The checks
+ /// will short-circuit on the first check that returns `false`.
+ pub checks: Vec<Box<Check>>,
+ /// Function called when the command is called.
+ pub exec: CommandType,
+ /// Ratelimit bucket.
+ pub bucket: Option<String>,
+ /// Command description, used by other commands.
+ pub desc: Option<String>,
+ /// Example arguments, used by other commands.
+ pub example: Option<String>,
+ /// Command usage schema, used by other commands.
+ pub usage: Option<String>,
+ /// Whether arguments should be parsed using quote parser or not.
+ pub use_quotes: bool,
+ /// Minumum 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>,
+ /// Permissions required to use this command.
+ pub required_permissions: Permissions,
+ /// Whether command should be displayed in help list or not, used by other commands.
+ pub help_available: bool,
+ /// Whether command can be used only privately or not.
+ pub dm_only: bool,
+ /// Whether command can be used only in guilds or not.
+ pub guild_only: bool,
+ /// Whether command can only be used by owners or not.
+ pub owners_only: bool,
+ pub(crate) aliases: Vec<String>,
+}
+
+impl Command {
+ pub fn new<F>(f: F) -> Self
+ where F: Fn(&mut Context, &Message, Vec<String>, String) -> Result<(), String> + 'static {
+ Command {
+ aliases: Vec::new(),
+ checks: Vec::default(),
+ exec: CommandType::Basic(Box::new(f)),
+ desc: None,
+ usage: None,
+ example: None,
+ use_quotes: false,
+ dm_only: false,
+ bucket: None,
+ guild_only: false,
+ help_available: true,
+ min_args: None,
+ max_args: None,
+ owners_only: false,
+ required_permissions: Permissions::empty(),
+ }
+ }
+}
+
+pub fn positions(ctx: &mut Context, msg: &Message, conf: &Configuration) -> Option<Vec<usize>> {
+ if !conf.prefixes.is_empty() || conf.dynamic_prefix.is_some() {
+ // Find out if they were mentioned. If not, determine if the prefix
+ // was used. If not, return None.
+ let mut positions: Vec<usize> = vec![];
+
+ if let Some(mention_end) = find_mention_end(&msg.content, conf) {
+ positions.push(mention_end);
+ } else if let Some(ref func) = conf.dynamic_prefix {
+ if let Some(x) = func(ctx, msg) {
+ if msg.content.starts_with(&x) {
+ positions.push(x.len());
+ }
+ } else {
+ for n in &conf.prefixes {
+ if msg.content.starts_with(n) {
+ positions.push(n.len());
+ }
+ }
+ }
+ } else {
+ for n in &conf.prefixes {
+ if msg.content.starts_with(n) {
+ positions.push(n.len());
+ }
+ }
+ };
+
+ if positions.is_empty() {
+ return None;
+ }
+
+ if conf.allow_whitespace {
+ let pos = *unsafe { positions.get_unchecked(0) };
+
+ positions.insert(0, pos + 1);
+ }
+
+ Some(positions)
+ } else if conf.on_mention.is_some() {
+ find_mention_end(&msg.content, conf).map(|mention_end| {
+ let mut positions = vec![mention_end];
+
+ if conf.allow_whitespace {
+ positions.insert(0, mention_end + 1);
+ }
+
+ positions
+ })
+ } else {
+ None
+ }
+}
+
+fn find_mention_end(content: &str, conf: &Configuration) -> Option<usize> {
+ conf.on_mention.as_ref().and_then(|mentions| {
+ mentions
+ .iter()
+ .find(|mention| content.starts_with(&mention[..]))
+ .map(|m| m.len())
+ })
+}
diff --git a/src/framework/standard/configuration.rs b/src/framework/standard/configuration.rs
new file mode 100644
index 0000000..491bbc4
--- /dev/null
+++ b/src/framework/standard/configuration.rs
@@ -0,0 +1,451 @@
+use std::collections::HashSet;
+use std::default::Default;
+use super::command::PrefixCheck;
+use client::Context;
+use http;
+use model::{GuildId, Message, UserId};
+
+/// The configuration to use for a [`Framework`] associated with a [`Client`]
+/// instance.
+///
+/// This allows setting configurations like the depth to search for commands,
+/// whether to treat mentions like a command prefix, etc.
+///
+/// # Examples
+///
+/// Responding to mentions and setting a command prefix of `"~"`:
+///
+/// ```rust,no_run
+/// # use serenity::prelude::EventHandler;
+/// struct Handler;
+///
+/// impl EventHandler for Handler {}
+/// use serenity::Client;
+/// use std::env;
+/// use serenity::framework::StandardFramework;
+///
+/// let mut client = Client::new(&env::var("DISCORD_BOT_TOKEN").unwrap(), Handler);
+///
+/// client.with_framework(StandardFramework::new()
+/// .configure(|c| c.on_mention(true).prefix("~")));
+/// ```
+///
+/// [`Client`]: ../../client/struct.Client.html
+/// [`Framework`]: struct.Framework.html
+pub struct Configuration {
+ #[doc(hidden)]
+ pub allow_dm: bool,
+ #[doc(hidden)]
+ pub allow_whitespace: bool,
+ #[doc(hidden)]
+ pub blocked_guilds: HashSet<GuildId>,
+ #[doc(hidden)]
+ pub blocked_users: HashSet<UserId>,
+ #[doc(hidden)]
+ pub depth: usize,
+ #[doc(hidden)]
+ pub disabled_commands: HashSet<String>,
+ #[doc(hidden)]
+ pub dynamic_prefix: Option<Box<PrefixCheck>>,
+ #[doc(hidden)]
+ pub ignore_bots: bool,
+ #[doc(hidden)]
+ pub ignore_webhooks: bool,
+ #[doc(hidden)]
+ pub on_mention: Option<Vec<String>>,
+ #[doc(hidden)]
+ pub owners: HashSet<UserId>,
+ #[doc(hidden)]
+ pub prefixes: Vec<String>,
+ #[doc(hidden)]
+ pub delimiters: Vec<String>,
+}
+
+impl Configuration {
+ /// If set to false, bot will ignore any private messages.
+ pub fn allow_dm(mut self, allow_dm: bool) -> Self {
+ self.allow_dm = allow_dm;
+
+ self
+ }
+
+ /// Whether to allow whitespace being optional between a mention/prefix and
+ /// a command.
+ ///
+ /// **Note**: Defaults to `false`.
+ ///
+ /// # Examples
+ ///
+ /// Setting this to `false` will _only_ allow this scenario to occur:
+ ///
+ /// ```ignore
+ /// <@245571012924538880> about
+ /// !about
+ ///
+ /// // bot processes and executes the "about" command if it exists
+ /// ```
+ ///
+ /// while setting this to `true` will _also_ allow this scenario to occur:
+ ///
+ /// ```ignore
+ /// <@245571012924538880>about
+ /// ! about
+ ///
+ /// // bot processes and executes the "about" command if it exists
+ /// ```
+ pub fn allow_whitespace(mut self, allow_whitespace: bool) -> Self {
+ self.allow_whitespace = allow_whitespace;
+
+ self
+ }
+
+ /// HashSet of guild Ids where commands will be ignored.
+ ///
+ /// # Examples
+ ///
+ /// Create a HashSet in-place:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// use serenity::model::GuildId;
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new().configure(|c| c
+ /// .blocked_guilds(vec![GuildId(7), GuildId(77)].into_iter().collect())));
+ /// ```
+ pub fn blocked_guilds(mut self, guilds: HashSet<GuildId>) -> Self {
+ self.blocked_guilds = guilds;
+
+ self
+ }
+
+ /// HashSet of user Ids whose commands will be ignored.
+ /// Guilds owned by user Ids will also be ignored.
+ ///
+ /// # Examples
+ ///
+ /// Create a HashSet in-place:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// use serenity::model::UserId;
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new().configure(|c| c
+ /// .blocked_users(vec![UserId(7), UserId(77)].into_iter().collect())));
+ /// ```
+ pub fn blocked_users(mut self, users: HashSet<UserId>) -> Self {
+ self.blocked_users = users;
+
+ self
+ }
+
+ /// The default depth of the message to check for commands. Defaults to 5.
+ /// This determines how "far" into a message to check for a valid command.
+ ///
+ /// # Examples
+ ///
+ /// If you set a depth of `1`, and make a command of `"music play"`, but
+ /// not a `"music"` command, then the former command will never be
+ /// triggered, as its "depth" is `2`.
+ pub fn depth(mut self, depth: u8) -> Self {
+ self.depth = depth as usize;
+
+ self
+ }
+
+ /// HashSet of command names that won't be run.
+ ///
+ /// # Examples
+ ///
+ /// Ignore a set of commands, assuming they exist:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// let disabled = vec!["ping"].into_iter().map(|x| x.to_owned()).collect();
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .command("ping", |c| c.exec_str("pong!"))
+ /// .configure(|c| c.disabled_commands(disabled)));
+ /// ```
+ pub fn disabled_commands(mut self, commands: HashSet<String>) -> Self {
+ self.disabled_commands = commands;
+
+ self
+ }
+
+ /// Sets the prefix to respond to dynamically based on conditions.
+ ///
+ /// Return `None` to not have a special prefix for the dispatch, and to
+ /// instead use the inherited prefix.
+ ///
+ /// # Examples
+ ///
+ /// If the Id of the channel is divisible by 5, return a prefix of `"!"`,
+ /// otherwise return a prefix of `"~"`.
+ ///
+ /// ```rust,no_run
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .command("ping", |c| c.exec_str("Pong!"))
+ /// .configure(|c| c.dynamic_prefix(|_, msg| {
+ /// Some(if msg.channel_id.0 % 5 == 0 {
+ /// "!"
+ /// } else {
+ /// "~"
+ /// }.to_owned())
+ /// })));
+ /// ```
+ pub fn dynamic_prefix<F>(mut self, dynamic_prefix: F) -> Self
+ where F: Fn(&mut Context, &Message) -> Option<String> + Send + Sync + 'static {
+ self.dynamic_prefix = Some(Box::new(dynamic_prefix));
+
+ self
+ }
+
+ /// Whether the bot should respond to other bots.
+ ///
+ /// For example, if this is set to false, then the bot will respond to any
+ /// other bots including itself.
+ pub fn ignore_bots(mut self, ignore_bots: bool) -> Self {
+ self.ignore_bots = ignore_bots;
+
+ self
+ }
+
+ /// If set to true, bot will ignore all commands called by webhooks.
+ /// True by default.
+ pub fn ignore_webhooks(mut self, ignore_webhooks: bool) -> Self {
+ self.ignore_webhooks = ignore_webhooks;
+
+ self
+ }
+
+ /// Whether or not to respond to commands initiated with a mention. Note
+ /// that this can be used in conjunction with [`prefix`].
+ ///
+ /// By default this is set to `false`.
+ ///
+ /// # Examples
+ ///
+ /// Setting this to `true` will allow the following types of mentions to be
+ /// responded to:
+ ///
+ /// ```ignore
+ /// <@245571012924538880> about
+ /// <@!245571012924538880> about
+ /// ```
+ ///
+ /// The former is a direct mention, while the latter is a nickname mention,
+ /// which aids mobile devices in determining whether to display a user's
+ /// nickname. It has no real meaning for your bot, and the library
+ /// encourages you to ignore differentiating between the two.
+ ///
+ /// [`prefix`]: #method.prefix
+ pub fn on_mention(mut self, on_mention: bool) -> Self {
+ if !on_mention {
+ return self;
+ }
+
+ if let Ok(current_user) = http::get_current_user() {
+ self.on_mention = Some(vec![
+ format!("<@{}>", current_user.id), // Regular mention
+ format!("<@!{}>", current_user.id) /* Nickname mention */,
+ ]);
+ }
+
+ self
+ }
+
+ /// A `HashSet` of user Ids checks won't apply to.
+ ///
+ /// # Examples
+ ///
+ /// Create a HashSet in-place:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// use serenity::model::UserId;
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new().configure(|c| c
+ /// .owners(vec![UserId(7), UserId(77)].into_iter().collect())));
+ /// ```
+ ///
+ /// Create a HashSet beforehand:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// use serenity::model::UserId;
+ /// use std::collections::HashSet;
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// let mut set = HashSet::new();
+ /// set.insert(UserId(7));
+ /// set.insert(UserId(77));
+ ///
+ /// client.with_framework(StandardFramework::new().configure(|c| c.owners(set)));
+ /// ```
+ pub fn owners(mut self, user_ids: HashSet<UserId>) -> Self {
+ self.owners = user_ids;
+
+ self
+ }
+
+ /// Sets the prefix to respond to. A prefix can be a string slice of any
+ /// non-zero length.
+ ///
+ /// # Examples
+ ///
+ /// Assign a basic prefix:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new().configure(|c| c
+ /// .prefix("!")));
+ /// ```
+ pub fn prefix(mut self, prefix: &str) -> Self {
+ self.prefixes = vec![prefix.to_owned()];
+
+ self
+ }
+
+ /// Sets the prefixes to respond to. Each can be a string slice of any
+ /// non-zero length.
+ ///
+ /// # Examples
+ ///
+ /// Assign a set of prefixes the bot can respond to:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new().configure(|c| c
+ /// .prefixes(vec!["!", ">", "+"])));
+ /// ```
+ pub fn prefixes(mut self, prefixes: Vec<&str>) -> Self {
+ self.prefixes = prefixes.iter().map(|x| x.to_string()).collect();
+
+ self
+ }
+
+ /// Sets a delimiter to be used when splitting the content after a command.
+ ///
+ /// # Examples
+ ///
+ /// Have the args be seperated by a comma and a space:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new().configure(|c| c
+ /// .delimiter(", ")));
+ /// ```
+ pub fn delimiter(mut self, delimiter: &str) -> Self {
+ self.delimiters.push(delimiter.to_string());
+
+ self
+ }
+
+ /// Sets multiple delimiters to be used when splitting the content after a command.
+ /// Additionally cleans the default delimiter from the vector.
+ ///
+ /// # Examples
+ ///
+ /// Have the args be seperated by a comma and a space; and a regular space:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new().configure(|c| c
+ /// .delimiters(vec![", ", " "])));
+ /// ```
+ pub fn delimiters(mut self, delimiters: Vec<&str>) -> Self {
+ self.delimiters.clear();
+ self.delimiters.extend(
+ delimiters.into_iter().map(|s| s.to_string()),
+ );
+
+ self
+ }
+}
+
+impl Default for Configuration {
+ /// Builds a default framework configuration, setting the following:
+ ///
+ /// - **allow_whitespace** to `false`
+ /// - **depth** to `5`
+ /// - **on_mention** to `false` (basically)
+ /// - **prefix** to `None`
+ /// - **delimiters** to vec![" "]
+ fn default() -> Configuration {
+ Configuration {
+ depth: 5,
+ on_mention: None,
+ dynamic_prefix: None,
+ allow_whitespace: false,
+ prefixes: vec![],
+ ignore_bots: true,
+ owners: HashSet::default(),
+ blocked_users: HashSet::default(),
+ blocked_guilds: HashSet::default(),
+ disabled_commands: HashSet::default(),
+ allow_dm: true,
+ ignore_webhooks: true,
+ delimiters: vec![" ".to_string()],
+ }
+ }
+}
diff --git a/src/framework/standard/create_command.rs b/src/framework/standard/create_command.rs
new file mode 100644
index 0000000..160a508
--- /dev/null
+++ b/src/framework/standard/create_command.rs
@@ -0,0 +1,245 @@
+pub use super::{Command, CommandGroup, CommandType};
+
+use std::collections::HashMap;
+use std::default::Default;
+use std::sync::Arc;
+use client::Context;
+use model::{Message, Permissions};
+
+pub struct CreateCommand(pub Command);
+
+impl CreateCommand {
+ /// Adds multiple aliases.
+ pub fn batch_known_as(mut self, names: Vec<&str>) -> Self {
+ self.0.aliases.extend(
+ names.into_iter().map(|n| n.to_owned()),
+ );
+
+ self
+ }
+
+ /// Adds a ratelimit bucket.
+ pub fn bucket(mut self, bucket: &str) -> Self {
+ self.0.bucket = Some(bucket.to_owned());
+
+ self
+ }
+
+ /// Adds a "check" to a command, which checks whether or not the command's
+ /// function should be called.
+ ///
+ /// # Examples
+ ///
+ /// Ensure that the user who created a message, calling a "ping" command,
+ /// is the owner.
+ ///
+ /// ```rust,no_run
+ /// # use serenity::prelude::EventHandler;
+ /// # struct Handler;
+ /// # impl EventHandler for Handler {}
+ /// use serenity::client::{Client, Context};
+ /// use serenity::framework::standard::{Command, StandardFramework};
+ /// use serenity::model::Message;
+ /// use std::env;
+ /// use std::sync::Arc;
+ ///
+ /// let mut client = Client::new(&env::var("DISCORD_TOKEN").unwrap(), Handler);
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .configure(|c| c.prefix("~"))
+ /// .command("ping", |c| c
+ /// .check(owner_check)
+ /// .desc("Replies to a ping with a pong")
+ /// .exec(ping)));
+ ///
+ /// fn ping(_context: &mut Context, message: &Message, _args: Vec<String>, _original_msg:
+ /// String) -> Result<(),
+ /// String> {
+ /// let _ = message.channel_id.say("Pong!");
+ ///
+ /// Ok(())
+ /// }
+ ///
+ /// fn owner_check(_context: &mut Context, message: &Message, _: &[String], _:
+ /// &Arc<Command>) -> bool {
+ /// // replace with your user ID
+ /// message.author.id == 7
+ /// }
+ /// ```
+ pub fn check<F>(mut self, check: F) -> Self
+ where F: Fn(&mut Context, &Message, &[String], &Arc<Command>) -> bool
+ + Send
+ + Sync
+ + 'static {
+ self.0.checks.push(Box::new(check));
+
+ self
+ }
+
+ /// Description, used by other commands.
+ pub fn desc(mut self, desc: &str) -> Self {
+ self.0.desc = Some(desc.to_owned());
+
+ self
+ }
+
+ /// Whether command can be used only privately or not.
+ pub fn dm_only(mut self, dm_only: bool) -> Self {
+ self.0.dm_only = dm_only;
+
+ self
+ }
+
+ /// Example arguments, used by other commands.
+ pub fn example(mut self, example: &str) -> Self {
+ self.0.example = Some(example.to_owned());
+
+ self
+ }
+
+ /// A function that can be called when a command is received.
+ /// You can return `Err(string)` if there's an error.
+ ///
+ /// See [`exec_str`] if you _only_ need to return a string on command use.
+ ///
+ /// [`exec_str`]: #method.exec_str
+ pub fn exec<F>(mut self, func: F) -> Self
+ where F: Fn(&mut Context, &Message, Vec<String>, String) -> Result<(), String>
+ + Send
+ + Sync
+ + 'static {
+ self.0.exec = CommandType::Basic(Box::new(func));
+
+ self
+ }
+
+ /// Sets a function that's called when a command is called that can access
+ /// the internal HashMap of commands, used specifically for creating a help
+ /// command.
+ ///
+ /// You can return `Err(string)` if there's an error.
+ pub fn exec_help<F>(mut self, f: F) -> Self
+ where F: Fn(&mut Context,
+ &Message,
+ HashMap<String, Arc<CommandGroup>>,
+ &[String])
+ -> Result<(), String>
+ + 'static {
+ self.0.exec = CommandType::WithCommands(Box::new(f));
+
+ self
+ }
+
+ /// Sets a string to be sent in the channel of context on command. This can
+ /// be useful for an `about`, `invite`, `ping`, etc. command.
+ ///
+ /// # Examples
+ ///
+ /// Create a command named "ping" that returns "Pong!":
+ ///
+ /// ```rust,ignore
+ /// client.with_framework(|f| f
+ /// .command("ping", |c| c.exec_str("Pong!")));
+ /// ```
+ pub fn exec_str(mut self, content: &str) -> Self {
+ self.0.exec = CommandType::StringResponse(content.to_owned());
+
+ self
+ }
+
+ /// Whether command can be used only in guilds or not.
+ pub fn guild_only(mut self, guild_only: bool) -> Self {
+ self.0.guild_only = guild_only;
+
+ 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;
+
+ self
+ }
+
+ /// Adds an alias, allowing users to use the command under a different name.
+ pub fn known_as(mut self, name: &str) -> Self {
+ self.0.aliases.push(name.to_owned());
+
+ self
+ }
+
+ /// Maximum amount of arguments that can be passed.
+ pub fn max_args(mut self, max_args: i32) -> Self {
+ self.0.max_args = Some(max_args);
+
+ self
+ }
+
+ /// Minumum amount of arguments that should be passed.
+ pub fn min_args(mut self, min_args: i32) -> Self {
+ self.0.min_args = Some(min_args);
+
+ self
+ }
+
+ /// Whether command can be used only privately or not.
+ pub fn owners_only(mut self, owners_only: bool) -> Self {
+ self.0.owners_only = owners_only;
+
+ self
+ }
+
+ /// The permissions that a user must have in the contextual channel in order
+ /// for the command to be processed.
+ pub fn required_permissions(mut self, permissions: Permissions) -> Self {
+ self.0.required_permissions = permissions;
+
+ self
+ }
+
+ /// Command usage schema, used by other commands.
+ pub fn usage(mut self, usage: &str) -> Self {
+ self.0.usage = Some(usage.to_owned());
+
+ self
+ }
+
+ /// Whether or not arguments should be parsed using the quotation parser.
+ ///
+ /// Enabling this will parse `~command "this is arg 1" "this is arg 2"` as
+ /// two arguments: `this is arg 1` and `this is arg 2`.
+ ///
+ /// Disabling this will parse `~command "this is arg 1" "this is arg 2"` as
+ /// eight arguments: `"this`, `is`, `arg`, `1"`, `"this`, `is`, `arg`, `2"`.
+ ///
+ /// Refer to [`utils::parse_quotes`] for information on the parser.
+ ///
+ /// [`utils::parse_quotes`]: ../../utils/fn.parse_quotes.html
+ pub fn use_quotes(mut self, use_quotes: bool) -> Self {
+ self.0.use_quotes = use_quotes;
+
+ self
+ }
+}
+
+impl Default for Command {
+ fn default() -> Command {
+ Command {
+ aliases: Vec::new(),
+ checks: Vec::default(),
+ exec: CommandType::Basic(Box::new(|_, _, _, _| Ok(()))),
+ desc: None,
+ usage: None,
+ example: None,
+ use_quotes: false,
+ min_args: None,
+ bucket: None,
+ max_args: None,
+ required_permissions: Permissions::empty(),
+ dm_only: false,
+ guild_only: false,
+ help_available: true,
+ owners_only: false,
+ }
+ }
+}
diff --git a/src/framework/standard/create_group.rs b/src/framework/standard/create_group.rs
new file mode 100644
index 0000000..b45c41e
--- /dev/null
+++ b/src/framework/standard/create_group.rs
@@ -0,0 +1,86 @@
+pub use super::command::{Command, CommandGroup, CommandType};
+pub(crate) use super::command::CommandOrAlias;
+pub use super::create_command::CreateCommand;
+
+use std::default::Default;
+use std::sync::Arc;
+use client::Context;
+use model::Message;
+
+/// Used to create command groups
+///
+/// # Examples
+///
+/// Create group named Information where all commands are prefixed with info,
+/// and add one command named "name". For example, if prefix is "~", we say "~info name"
+/// to call the "name" command.
+///
+/// ```rust,ignore
+/// framework.group("Information", |g| g
+/// .prefix("info")
+/// .command("name", |c| c
+/// .exec_str("Hakase")))
+/// ```
+#[derive(Default)]
+pub struct CreateGroup(pub CommandGroup);
+
+impl CreateGroup {
+ /// Adds a command to group.
+ pub fn command<F>(mut self, command_name: &str, f: F) -> Self
+ where F: FnOnce(CreateCommand) -> CreateCommand {
+ let cmd = f(CreateCommand(Command::default())).0;
+
+ for n in &cmd.aliases {
+ if let Some(ref prefix) = self.0.prefix {
+ self.0.commands.insert(
+ format!("{} {}", prefix, n.to_owned()),
+ CommandOrAlias::Alias(
+ format!("{} {}", prefix, command_name.to_string()),
+ ),
+ );
+ } else {
+ self.0.commands.insert(
+ n.to_owned(),
+ CommandOrAlias::Alias(command_name.to_string()),
+ );
+ }
+ }
+
+ self.0.commands.insert(
+ command_name.to_owned(),
+ CommandOrAlias::Command(Arc::new(cmd)),
+ );
+
+ self
+ }
+
+ /// Adds a command to group with simplified API.
+ /// You can return Err(string) if there's an error.
+ pub fn on<F>(mut self, command_name: &str, f: F) -> Self
+ where F: Fn(&mut Context, &Message, Vec<String>, String) -> Result<(), String>
+ + Send
+ + Sync
+ + 'static {
+ let cmd = Arc::new(Command::new(f));
+
+ self.0.commands.insert(
+ command_name.to_owned(),
+ CommandOrAlias::Command(cmd),
+ );
+
+ self
+ }
+
+ /// If prefix is set, it will be required before all command names.
+ /// For example, if bot prefix is "~" and group prefix is "image"
+ /// we'd call a subcommand named "hibiki" by sending "~image hibiki".
+ ///
+ /// **Note**: serenity automatically puts a space after group prefix.
+ ///
+ /// **Note**: It's suggested to call this first when making a group.
+ pub fn prefix(mut self, desc: &str) -> Self {
+ self.0.prefix = Some(desc.to_owned());
+
+ self
+ }
+}
diff --git a/src/framework/standard/help_commands.rs b/src/framework/standard/help_commands.rs
new file mode 100644
index 0000000..a7c20fd
--- /dev/null
+++ b/src/framework/standard/help_commands.rs
@@ -0,0 +1,346 @@
+//! A collection of default help commands for the framework.
+//!
+//! # Example
+//!
+//! Using the [`with_embeds`] function to have the framework's help message use
+//! embeds:
+//!
+//! ```rs,no_run
+//! use serenity::framework::standard::help_commands;
+//! use serenity::Client;
+//! use std::env;
+//!
+//! let mut client = Client::new(&env::var("DISCORD_TOKEN").unwrap());
+//! use serenity::framework::StandardFramework;
+//!
+//! client.with_framework(StandardFramework::new()
+//! .command("help", |c| c.exec_help(help_commands::with_embeds)));
+//! ```
+//!
+//! The same can be accomplished with no embeds by substituting `with_embeds`
+//! with the [`plain`] function.
+//!
+//! [`plain`]: fn.plain.html
+//! [`with_embeds`]: fn.with_embeds.html
+
+use std::collections::HashMap;
+use std::sync::Arc;
+use std::fmt::Write;
+use super::command::InternalCommand;
+use super::{Command, CommandGroup, CommandOrAlias};
+use client::Context;
+use model::{ChannelId, Message};
+use utils::Colour;
+
+fn error_embed(channel_id: &ChannelId, input: &str) {
+ let _ = channel_id.send_message(|m| {
+ m.embed(|e| e.colour(Colour::dark_red()).description(input))
+ });
+}
+
+fn remove_aliases(cmds: &HashMap<String, CommandOrAlias>) -> HashMap<&String, &InternalCommand> {
+ let mut result = HashMap::new();
+
+ for (n, v) in cmds {
+ if let CommandOrAlias::Command(ref cmd) = *v {
+ result.insert(n, cmd);
+ }
+ }
+
+ result
+}
+
+/// Posts an embed showing each individual command group and its commands.
+///
+/// # Examples
+///
+/// Use the command with `exec_help`:
+///
+/// ```rust
+/// # use serenity::prelude::*;
+/// # struct Handler;
+/// #
+/// # impl EventHandler for Handler {}
+/// # let mut client = Client::new("token", Handler);
+/// #
+/// use serenity::framework::standard::{StandardFramework, help_commands};
+///
+/// client.with_framework(StandardFramework::new()
+/// .command("help", |c| c.exec_help(help_commands::with_embeds)));
+/// ```
+pub fn with_embeds(_: &mut Context,
+ msg: &Message,
+ groups: HashMap<String, Arc<CommandGroup>>,
+ args: &[String])
+ -> Result<(), String> {
+ if !args.is_empty() {
+ let name = args.join(" ");
+
+ for (group_name, group) in groups {
+ let mut found: Option<(&String, &InternalCommand)> = None;
+
+ for (command_name, command) in &group.commands {
+ let with_prefix = if let Some(ref prefix) = group.prefix {
+ format!("{} {}", prefix, command_name)
+ } else {
+ command_name.to_owned()
+ };
+
+ if name == with_prefix || name == *command_name {
+ match *command {
+ CommandOrAlias::Command(ref cmd) => {
+ found = Some((command_name, cmd));
+ },
+ CommandOrAlias::Alias(ref name) => {
+ error_embed(&msg.channel_id, &format!("Did you mean \"{}\"?", name));
+
+ return Ok(());
+ },
+ }
+ }
+ }
+
+ if let Some((command_name, command)) = found {
+ if !command.help_available {
+ error_embed(&msg.channel_id, "**Error**: No help available.");
+
+ return Ok(());
+ }
+
+ let _ = msg.channel_id.send_message(|m| {
+ m.embed(|e| {
+ let mut embed = e.colour(Colour::rosewater()).title(command_name);
+ if let Some(ref desc) = command.desc {
+ embed = embed.description(desc);
+ }
+
+ if let Some(ref usage) = command.usage {
+ embed = embed.field(|f| {
+ f.name("Usage").value(
+ &format!("`{} {}`", command_name, usage),
+ )
+ });
+ }
+
+ if let Some(ref example) = command.example {
+ embed = embed.field(|f| {
+ f.name("Sample usage").value(&format!(
+ "`{} {}`",
+ command_name,
+ example
+ ))
+ });
+ }
+
+ if group_name != "Ungrouped" {
+ embed = embed.field(|f| f.name("Group").value(&group_name));
+ }
+
+ let available = if command.dm_only {
+ "Only in DM"
+ } else if command.guild_only {
+ "Only in guilds"
+ } else {
+ "In DM and guilds"
+ };
+
+ embed = embed.field(|f| f.name("Available").value(available));
+
+ embed
+ })
+ });
+
+ return Ok(());
+ }
+ }
+
+ let error_msg = format!("**Error**: Command `{}` not found.", name);
+ error_embed(&msg.channel_id, &error_msg);
+
+ return Ok(());
+ }
+
+ let _ = msg.channel_id.send_message(|m| {
+ m.embed(|mut e| {
+ e = e.colour(Colour::rosewater()).description(
+ "To get help with an individual command, pass its \
+ name as an argument to this command.",
+ );
+
+ let mut group_names = groups.keys().collect::<Vec<_>>();
+ group_names.sort();
+
+ for group_name in group_names {
+ let group = &groups[group_name];
+ let mut desc = String::new();
+
+ if let Some(ref x) = group.prefix {
+ let _ = write!(desc, "Prefix: {}\n", x);
+ }
+
+ let mut has_commands = false;
+
+ let commands = remove_aliases(&group.commands);
+ let mut command_names = commands.keys().collect::<Vec<_>>();
+ command_names.sort();
+
+ for name in command_names {
+ let cmd = &commands[name];
+
+ if cmd.help_available {
+ let _ = write!(desc, "`{}`\n", name);
+
+ has_commands = true;
+ }
+ }
+
+ if has_commands {
+ e = e.field(|f| f.name(group_name).value(&desc));
+ }
+ }
+
+ e
+ })
+ });
+
+ Ok(())
+}
+
+/// Posts formatted text displaying each individual command group and its commands.
+///
+/// # Examples
+///
+/// Use the command with `exec_help`:
+///
+/// ```rust
+/// # use serenity::prelude::*;
+/// # struct Handler;
+/// #
+/// # impl EventHandler for Handler {}
+/// # let mut client = Client::new("token", Handler);
+/// #
+/// use serenity::framework::standard::{StandardFramework, help_commands};
+///
+/// client.with_framework(StandardFramework::new()
+/// .command("help", |c| c.exec_help(help_commands::plain)));
+/// ```
+pub fn plain(_: &mut Context,
+ msg: &Message,
+ groups: HashMap<String, Arc<CommandGroup>>,
+ args: &[String])
+ -> Result<(), String> {
+ if !args.is_empty() {
+ let name = args.join(" ");
+
+ for (group_name, group) in groups {
+ let mut found: Option<(&String, &Command)> = None;
+
+ for (command_name, command) in &group.commands {
+ let with_prefix = if let Some(ref prefix) = group.prefix {
+ format!("{} {}", prefix, command_name)
+ } else {
+ command_name.to_owned()
+ };
+
+ if name == with_prefix || name == *command_name {
+ match *command {
+ CommandOrAlias::Command(ref cmd) => {
+ found = Some((command_name, cmd));
+ },
+ CommandOrAlias::Alias(ref name) => {
+ let _ = msg.channel_id.say(&format!("Did you mean {:?}?", name));
+ return Ok(());
+ },
+ }
+ }
+ }
+
+ if let Some((command_name, command)) = found {
+ if !command.help_available {
+ let _ = msg.channel_id.say("**Error**: No help available.");
+ return Ok(());
+ }
+
+ let mut result = format!("**{}**\n", command_name);
+
+ if let Some(ref desc) = command.desc {
+ let _ = write!(result, "**Description:** {}\n", desc);
+ }
+
+ if let Some(ref usage) = command.usage {
+ let _ = write!(result, "**Usage:** `{} {}`\n", command_name, usage);
+ }
+
+ if let Some(ref example) = command.example {
+ let _ = write!(result, "**Sample usage:** `{} {}`\n", command_name, example);
+ }
+
+ if group_name != "Ungrouped" {
+ let _ = write!(result, "**Group:** {}\n", group_name);
+ }
+
+ let only = if command.dm_only {
+ "Only in DM"
+ } else if command.guild_only {
+ "Only in guilds"
+ } else {
+ "In DM and guilds"
+ };
+
+ result.push_str("**Available:** ");
+ result.push_str(only);
+ result.push_str("\n");
+
+ let _ = msg.channel_id.say(&result);
+
+ return Ok(());
+ }
+ }
+
+ let _ = msg.channel_id.say(&format!(
+ "**Error**: Command `{}` not found.",
+ name
+ ));
+
+ return Ok(());
+ }
+
+ let mut result = "**Commands**\nTo get help with an individual command, pass its \
+ name as an argument to this command.\n\n"
+ .to_string();
+
+ let mut group_names = groups.keys().collect::<Vec<_>>();
+ group_names.sort();
+
+ for group_name in group_names {
+ let group = &groups[group_name];
+ let mut group_help = String::new();
+
+ let commands = remove_aliases(&group.commands);
+ let mut command_names = commands.keys().collect::<Vec<_>>();
+ command_names.sort();
+
+ for name in command_names {
+ let cmd = &commands[name];
+
+ if cmd.help_available {
+ let _ = write!(group_help, "`{}` ", name);
+ }
+ }
+
+ if !group_help.is_empty() {
+ let _ = write!(result, "**{}:** ", group_name);
+
+ if let Some(ref x) = group.prefix {
+ let _ = write!(result, "(prefix: `{}`): ", x);
+ }
+
+ result.push_str(&group_help);
+ result.push('\n');
+ }
+ }
+
+ let _ = msg.channel_id.say(&result);
+
+ Ok(())
+}
diff --git a/src/framework/standard/mod.rs b/src/framework/standard/mod.rs
new file mode 100644
index 0000000..f9c0e11
--- /dev/null
+++ b/src/framework/standard/mod.rs
@@ -0,0 +1,1026 @@
+
+pub mod help_commands;
+
+mod command;
+mod configuration;
+mod create_command;
+mod create_group;
+mod buckets;
+
+pub(crate) use self::buckets::{Bucket, Ratelimit};
+pub use self::command::{Command, CommandGroup, CommandType};
+pub use self::command::CommandOrAlias;
+pub use self::configuration::Configuration;
+pub use self::create_command::CreateCommand;
+pub use self::create_group::CreateGroup;
+
+use self::command::{AfterHook, BeforeHook};
+use std::collections::HashMap;
+use std::default::Default;
+use std::sync::Arc;
+use client::Context;
+use super::Framework;
+use model::{ChannelId, GuildId, Message, UserId};
+use model::permissions::Permissions;
+use utils;
+use tokio_core::reactor::Handle;
+use itertools::Itertools;
+use regex::Regex;
+use regex::escape;
+
+#[cfg(feature = "cache")]
+use client::CACHE;
+#[cfg(feature = "cache")]
+use model::Channel;
+
+/// A macro to generate "named parameters". This is useful to avoid manually
+/// using the "arguments" parameter and manually parsing types.
+///
+/// This is meant for use with the command [`Framework`].
+///
+/// # Examples
+///
+/// Create a regular `ping` command which takes no arguments:
+///
+/// ```rust,ignore
+/// command!(ping(_context, message, _args) {
+/// if let Err(why) = message.reply("Pong!") {
+/// println!("Error sending pong: {:?}", why);
+/// }
+/// });
+/// ```
+///
+/// Create a command named `multiply` which accepts 2 floats and multiplies
+/// them, sending the product as a reply:
+///
+/// ```rust,ignore
+/// command!(multiply(_context, message, _args, first: f64, second: f64) {
+/// let product = first * second;
+///
+/// if let Err(why) = message.reply(&product.to_string()) {
+/// println!("Error sending product: {:?}", why);
+/// }
+/// });
+/// ```
+///
+/// [`Framework`]: framework/index.html
+#[macro_export]
+macro_rules! command {
+ ($fname:ident($c:ident) $b:block) => {
+ #[allow(unreachable_code, unused_mut)]
+ pub fn $fname(mut $c: &mut $crate::client::Context,
+ _: &$crate::model::Message,
+ _: Vec<String>,
+ _: String)
+ -> ::std::result::Result<(), String> {
+ $b
+
+ Ok(())
+ }
+ };
+ ($fname:ident($c:ident, $m:ident) $b:block) => {
+ #[allow(unreachable_code, unused_mut)]
+ pub fn $fname(mut $c: &mut $crate::client::Context,
+ $m: &$crate::model::Message,
+ _: Vec<String>,
+ _: String)
+ -> ::std::result::Result<(), String> {
+ $b
+
+ Ok(())
+ }
+ };
+ ($fname:ident($c:ident, $m:ident, $a:ident) $b:block) => {
+ #[allow(unreachable_code, unused_mut)]
+ pub fn $fname(mut $c: &mut $crate::client::Context,
+ $m: &$crate::model::Message,
+ $a: Vec<String>,
+ _: String)
+ -> ::std::result::Result<(), String> {
+ $b
+
+ Ok(())
+ }
+ };
+ ($fname:ident($c:ident, $m:ident, @$a:ident) $b:block) => {
+ #[allow(unreachable_code, unused_mut)]
+ pub fn $fname(mut $c: &mut $crate::client::Context,
+ $m: &$crate::model::Message,
+ _: Vec<String>,
+ $a: String)
+ -> ::std::result::Result<(), String> {
+ $b
+
+ Ok(())
+ }
+ };
+ ($fname:ident($c:ident, $m:ident, $a:ident, @$f:ident) $b:block) => {
+ #[allow(unreachable_code, unused_mut)]
+ pub fn $fname(mut $c: &mut $crate::client::Context,
+ $m: &$crate::model::Message,
+ $a: Vec<String>,
+ $f: String)
+ -> ::std::result::Result<(), String> {
+ $b
+
+ Ok(())
+ }
+ };
+ ($fname:ident($c:ident, $m:ident, $a:ident, $($name:ident: $t:ty),*) $b:block) => {
+ #[allow(unreachable_code, unreachable_patterns, unused_mut)]
+ pub fn $fname(mut $c: &mut $crate::client::Context,
+ $m: &$crate::model::Message,
+ $a: Vec<String>,
+ _: String)
+ -> ::std::result::Result<(), String> {
+ let mut i = $a.iter();
+ let mut arg_counter = 0;
+
+ $(
+ arg_counter += 1;
+
+ let $name = match i.next() {
+ Some(v) => match v.parse::<$t>() {
+ Ok(v) => v,
+ Err(_) => return Err(format!("Failed to parse argument #{} of type {:?}",
+ arg_counter,
+ stringify!($t))),
+ },
+ None => return Err(format!("Missing argument #{} of type {:?}",
+ arg_counter,
+ stringify!($t))),
+ };
+ )*
+
+ drop(i);
+
+ $b
+
+ Ok(())
+ }
+ };
+}
+
+/// A enum representing all possible fail conditions under which a command won't
+/// be executed.
+pub enum DispatchError {
+ /// When a custom function check has failed.
+ CheckFailed(Arc<Command>),
+ /// When the requested command is disabled in bot configuration.
+ CommandDisabled(String),
+ /// When the user is blocked in bot configuration.
+ BlockedUser,
+ /// When the guild or its owner is blocked in bot configuration.
+ BlockedGuild,
+ /// When the command requester lacks specific required permissions.
+ LackOfPermissions(Permissions),
+ /// When the command requester has exceeded a ratelimit bucket. The attached
+ /// value is the time a requester has to wait to run the command again.
+ RateLimited(i64),
+ /// When the requested command can only be used in a direct message or group
+ /// channel.
+ OnlyForDM,
+ /// When the requested command can only be ran in guilds, or the bot doesn't
+ /// support DMs.
+ OnlyForGuilds,
+ /// When the requested command can only be used by bot owners.
+ OnlyForOwners,
+ /// When there are too few arguments.
+ NotEnoughArguments { min: i32, given: usize },
+ /// When there are too many arguments.
+ TooManyArguments { max: i32, given: usize },
+ /// When the command was requested by a bot user when they are set to be
+ /// ignored.
+ IgnoredBot,
+ /// When the bot ignores webhooks and a command was issued by one.
+ WebhookAuthor,
+}
+
+type DispatchErrorHook = Fn(Context, Message, DispatchError) + 'static;
+
+/// A utility for easily managing dispatches to commands.
+///
+/// Refer to the [module-level documentation] for more information.
+///
+/// [module-level documentation]: index.html
+#[derive(Default)]
+pub struct StandardFramework {
+ configuration: Configuration,
+ groups: HashMap<String, Arc<CommandGroup>>,
+ before: Option<Arc<BeforeHook>>,
+ dispatch_error_handler: Option<Arc<DispatchErrorHook>>,
+ buckets: HashMap<String, Bucket>,
+ after: Option<Arc<AfterHook>>,
+ /// Whether the framework has been "initialized".
+ ///
+ /// The framework is initialized once one of the following occurs:
+ ///
+ /// - configuration has been set;
+ /// - a command handler has been set;
+ /// - a command check has been set.
+ ///
+ /// This is used internally to determine whether or not - in addition to
+ /// dispatching to the [`EventHandler::on_message`] handler - to have the
+ /// framework check if a [`Event::MessageCreate`] should be processed by
+ /// itself.
+ ///
+ /// [`EventHandler::on_message`]:
+ /// ../client/event_handler/trait.EventHandler.html#method.on_message
+ /// [`Event::MessageCreate`]: ../model/event/enum.Event.html#variant.MessageCreate
+ pub initialized: bool,
+ user_info: (u64, bool),
+}
+
+impl StandardFramework {
+ pub fn new() -> Self { StandardFramework::default() }
+
+ /// Configures the framework, setting non-default values. All fields are
+ /// optional. Refer to [`Configuration::default`] for more information on
+ /// the default values.
+ ///
+ /// # Examples
+ ///
+ /// Configuring the framework for a [`Client`], setting the [`depth`] to 3,
+ /// [allowing whitespace], and setting the [`prefix`] to `"~"`:
+ ///
+ /// ```rust,no_run
+ /// # use serenity::prelude::EventHandler;
+ /// # struct Handler;
+ /// # impl EventHandler for Handler {}
+ /// use serenity::Client;
+ /// use serenity::framework::StandardFramework;
+ /// use std::env;
+ ///
+ /// let mut client = Client::new(&env::var("DISCORD_TOKEN").unwrap(), Handler);
+ /// client.with_framework(StandardFramework::new()
+ /// .configure(|c| c
+ /// .depth(3)
+ /// .allow_whitespace(true)
+ /// .prefix("~")));
+ /// ```
+ ///
+ /// [`Client`]: ../client/struct.Client.html
+ /// [`Configuration::default`]: struct.Configuration.html#method.default
+ /// [`depth`]: struct.Configuration.html#method.depth
+ /// [`prefix`]: struct.Configuration.html#method.prefix
+ /// [allowing whitespace]: struct.Configuration.html#method.allow_whitespace
+ pub fn configure<F>(mut self, f: F) -> Self
+ where F: FnOnce(Configuration) -> Configuration {
+ self.configuration = f(self.configuration);
+
+ self
+ }
+
+ /// Defines a bucket with `delay` between each command, and the `limit` of uses
+ /// per `time_span`.
+ ///
+ /// # Examples
+ ///
+ /// Create and use a bucket that limits a command to 3 uses per 10 seconds with
+ /// a 2 second delay inbetween invocations:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .bucket("basic", 2, 10, 3)
+ /// .command("ping", |c| c
+ /// .bucket("basic")
+ /// .exec_str("pong!")));
+ /// ```
+ pub fn bucket<S>(mut self, s: S, delay: i64, time_span: i64, limit: i32) -> Self
+ where S: Into<String> {
+ self.buckets.insert(
+ s.into(),
+ Bucket {
+ ratelimit: Ratelimit {
+ delay: delay,
+ limit: Some((time_span, limit)),
+ },
+ users: HashMap::new(),
+ check: None,
+ },
+ );
+
+ self
+ }
+
+ /// Same as [`bucket`] but with a check added.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .complex_bucket("basic", 2, 10, 3, |_, guild_id, channel_id, user_id| {
+ /// // check if the guild is `123` and the channel where the command(s) was called:
+ /// // `456`
+ /// // and if the user who called the command(s) is `789`
+ /// // otherwise don't apply the bucket at all.
+ /// guild_id.is_some() && guild_id.unwrap() == 123 && channel_id == 456
+ /// && user_id == 789
+ /// })
+ /// .command("ping", |c| c
+ /// .bucket("basic")
+ /// .exec_str("pong!")));
+ /// ```
+ ///
+ /// [`bucket`]: #method.bucket
+ #[cfg(feature = "cache")]
+ pub fn complex_bucket<S, Check>(mut self,
+ s: S,
+ delay: i64,
+ time_span: i64,
+ limit: i32,
+ check: Check)
+ -> Self
+ where Check: Fn(&mut Context, Option<GuildId>, ChannelId, UserId) -> bool + 'static,
+ S: Into<String> {
+ self.buckets.insert(
+ s.into(),
+ Bucket {
+ ratelimit: Ratelimit {
+ delay,
+ limit: Some((time_span, limit)),
+ },
+ users: HashMap::new(),
+ check: Some(Box::new(check)),
+ },
+ );
+
+ self
+ }
+
+ /// Same as [`bucket`] but with a check added.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .complex_bucket("basic", 2, 10, 3, |_, channel_id, user_id| {
+ /// // check if the channel's id where the command(s) was called is `456`
+ /// // and if the user who called the command(s) is `789`
+ /// // otherwise don't apply the bucket at all.
+ /// channel_id == 456 && user_id == 789
+ /// })
+ /// .command("ping", |c| c
+ /// .bucket("basic")
+ /// .exec_str("pong!")));
+ /// ```
+ ///
+ /// [`bucket`]: #method.bucket
+ #[cfg(not(feature = "cache"))]
+ pub fn complex_bucket<S, Check>(mut self,
+ s: S,
+ delay: i64,
+ time_span: i64,
+ limit: i32,
+ check: Check)
+ -> Self
+ where Check: Fn(&mut Context, ChannelId, UserId) -> bool + 'static, S: Into<String> {
+ self.buckets.insert(
+ s.into(),
+ Bucket {
+ ratelimit: Ratelimit {
+ delay,
+ limit: Some((time_span, limit)),
+ },
+ users: HashMap::new(),
+ check: Some(Box::new(check)),
+ },
+ );
+
+ self
+ }
+
+ /// Defines a bucket with only a `delay` between each command.
+ ///
+ /// # Examples
+ ///
+ /// Create and use a simple bucket that has a 2 second delay between invocations:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .simple_bucket("simple", 2)
+ /// .command("ping", |c| c
+ /// .bucket("simple")
+ /// .exec_str("pong!")));
+ /// ```
+ pub fn simple_bucket<S>(mut self, s: S, delay: i64) -> Self
+ where S: Into<String> {
+ self.buckets.insert(
+ s.into(),
+ Bucket {
+ ratelimit: Ratelimit {
+ delay: delay,
+ limit: None,
+ },
+ users: HashMap::new(),
+ check: None,
+ },
+ );
+
+ self
+ }
+
+ #[cfg(feature = "cache")]
+ fn is_blocked_guild(&self, message: &Message) -> bool {
+ if let Some(Channel::Guild(channel)) = CACHE.read().unwrap().channel(message.channel_id) {
+ let guild_id = channel.read().unwrap().guild_id;
+ if self.configuration.blocked_guilds.contains(&guild_id) {
+ return true;
+ }
+
+ if let Some(guild) = guild_id.find() {
+ return self.configuration.blocked_users.contains(
+ &guild
+ .read()
+ .unwrap()
+ .owner_id,
+ );
+ }
+ }
+
+ false
+ }
+
+ #[cfg(feature = "cache")]
+ fn has_correct_permissions(&self, command: &Arc<Command>, message: &Message) -> bool {
+ if !command.required_permissions.is_empty() {
+ if let Some(guild) = message.guild() {
+ let perms = guild.read().unwrap().permissions_for(
+ message.channel_id,
+ message.author.id,
+ );
+
+ return perms.contains(command.required_permissions);
+ }
+ }
+
+ true
+ }
+
+ #[allow(too_many_arguments)]
+ fn should_fail(&mut self,
+ mut context: &mut Context,
+ message: &Message,
+ command: &Arc<Command>,
+ args: &[String],
+ to_check: &str,
+ built: &str)
+ -> Option<DispatchError> {
+ if self.configuration.ignore_bots && message.author.bot {
+ Some(DispatchError::IgnoredBot)
+ } else if self.configuration.ignore_webhooks && message.webhook_id.is_some() {
+ Some(DispatchError::WebhookAuthor)
+ } else if self.configuration.owners.contains(&message.author.id) {
+ None
+ } else {
+ if let Some(ref bucket) = command.bucket {
+ if let Some(ref mut bucket) = self.buckets.get_mut(bucket) {
+ let rate_limit = bucket.take(message.author.id.0);
+ match bucket.check {
+ Some(ref check) => {
+ let apply =
+ feature_cache! {{
+ let guild_id = message.guild_id();
+ (check)(context, guild_id, message.channel_id, message.author.id)
+ } else {
+ (check)(context, message.channel_id, message.author.id)
+ }};
+
+ if apply && rate_limit > 0i64 {
+ return Some(DispatchError::RateLimited(rate_limit));
+ }
+ },
+ None => {
+ if rate_limit > 0i64 {
+ return Some(DispatchError::RateLimited(rate_limit));
+ }
+ },
+ }
+ }
+ }
+
+ let arg_len = args.len();
+
+ if let Some(x) = command.min_args {
+ if arg_len < x as usize {
+ return Some(DispatchError::NotEnoughArguments {
+ min: x,
+ given: arg_len,
+ });
+ }
+ }
+
+ if let Some(x) = command.max_args {
+ if arg_len > x as usize {
+ return Some(DispatchError::TooManyArguments {
+ max: x,
+ given: arg_len,
+ });
+ }
+ }
+
+ #[cfg(feature = "cache")]
+ {
+ if self.is_blocked_guild(message) {
+ return Some(DispatchError::BlockedGuild);
+ }
+
+ if !self.has_correct_permissions(command, message) {
+ return Some(DispatchError::LackOfPermissions(
+ command.required_permissions,
+ ));
+ }
+
+ if (!self.configuration.allow_dm && message.is_private()) ||
+ (command.guild_only && message.is_private()) {
+ return Some(DispatchError::OnlyForGuilds);
+ }
+
+ if command.dm_only && !message.is_private() {
+ return Some(DispatchError::OnlyForDM);
+ }
+ }
+
+ if command.owners_only {
+ Some(DispatchError::OnlyForOwners)
+ } else if self.configuration.blocked_users.contains(
+ &message.author.id,
+ ) {
+ Some(DispatchError::BlockedUser)
+ } else if self.configuration.disabled_commands.contains(to_check) {
+ Some(DispatchError::CommandDisabled(to_check.to_owned()))
+ } else if self.configuration.disabled_commands.contains(built) {
+ Some(DispatchError::CommandDisabled(built.to_owned()))
+ } else {
+ let all_passed = command.checks.iter().all(|check| {
+ check(&mut context, message, args, command)
+ });
+
+ if all_passed {
+ None
+ } else {
+ Some(DispatchError::CheckFailed(command.to_owned()))
+ }
+ }
+ }
+ }
+
+ /// Adds a function to be associated with a command, which will be called
+ /// when a command is used in a message.
+ ///
+ /// This requires that a check - if one exists - passes, prior to being
+ /// called.
+ ///
+ /// Prior to v0.2.0, you will need to use the command builder
+ /// via the [`command`] method to set checks. This command will otherwise
+ /// only be for simple commands.
+ ///
+ /// Refer to the [module-level documentation] for more information and
+ /// usage.
+ ///
+ /// [`command`]: #method.command
+ /// [module-level documentation]: index.html
+ ///
+ /// # Examples
+ ///
+ /// Create and use a simple command:
+ ///
+ /// ```rust
+ /// # #[macro_use] extern crate serenity;
+ /// #
+ /// # fn main() {
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new().on("ping", ping));
+ ///
+ /// command!(ping(_ctx, msg) {
+ /// let _ = msg.channel_id.say("pong!");
+ /// });
+ /// # }
+ /// ```
+ pub fn on<F, S>(mut self, command_name: S, f: F) -> Self
+ where F: Fn(&mut Context, &Message, Vec<String>, String) -> Result<(), String> + 'static,
+ S: Into<String> {
+ {
+ let ungrouped = self.groups.entry("Ungrouped".to_owned()).or_insert_with(
+ || {
+ Arc::new(CommandGroup::default())
+ },
+ );
+
+ if let Some(ref mut group) = Arc::get_mut(ungrouped) {
+ let name = command_name.into();
+
+ group.commands.insert(
+ name,
+ CommandOrAlias::Command(
+ Arc::new(Command::new(f)),
+ ),
+ );
+ }
+ }
+
+ self.initialized = true;
+
+ self
+ }
+
+ /// Adds a command using command builder.
+ ///
+ /// # Examples
+ ///
+ /// ```rust,ignore
+ /// framework.command("ping", |c| c
+ /// .description("Responds with 'pong'.")
+ /// .exec(|ctx, _, _| {
+ /// let _ = ctx.say("pong");
+ /// }));
+ /// ```
+ pub fn command<F, S>(mut self, command_name: S, f: F) -> Self
+ where F: FnOnce(CreateCommand) -> CreateCommand, S: Into<String> {
+ {
+ let ungrouped = self.groups.entry("Ungrouped".to_owned()).or_insert_with(
+ || {
+ Arc::new(CommandGroup::default())
+ },
+ );
+
+ if let Some(ref mut group) = Arc::get_mut(ungrouped) {
+ let cmd = f(CreateCommand(Command::default())).0;
+ let name = command_name.into();
+
+ if let Some(ref prefix) = group.prefix {
+ for v in &cmd.aliases {
+ group.commands.insert(
+ format!("{} {}", prefix, v),
+ CommandOrAlias::Alias(format!("{} {}", prefix, name)),
+ );
+ }
+ } else {
+ for v in &cmd.aliases {
+ group.commands.insert(
+ v.to_owned(),
+ CommandOrAlias::Alias(name.clone()),
+ );
+ }
+ }
+
+ group.commands.insert(
+ name,
+ CommandOrAlias::Command(Arc::new(cmd)),
+ );
+ }
+ }
+
+ self.initialized = true;
+
+ self
+ }
+
+ /// Adds a group which can organize several related commands.
+ /// Groups are taken into account when using
+ /// `serenity::framework::standard::help_commands`.
+ ///
+ /// # Examples
+ ///
+ /// Creating a simple group:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .group("ping-pong", |g| g
+ /// .command("ping", |c| c.exec_str("pong!"))
+ /// .command("pong", |c| c.exec_str("ping!"))));
+ /// ```
+ pub fn group<F, S>(mut self, group_name: S, f: F) -> Self
+ where F: FnOnce(CreateGroup) -> CreateGroup, S: Into<String> {
+ let group = f(CreateGroup(CommandGroup::default())).0;
+
+ self.groups.insert(group_name.into(), Arc::new(group));
+ self.initialized = true;
+
+ self
+ }
+
+ /// Specify the function that's called in case a command wasn't executed for one reason or
+ /// another.
+ ///
+ /// DispatchError represents all possible fail conditions.
+ ///
+ /// # Examples
+ ///
+ /// Making a simple argument error responder:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// use serenity::framework::standard::DispatchError::{NotEnoughArguments, TooManyArguments};
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .on_dispatch_error(|_, msg, error| {
+ /// match error {
+ /// NotEnoughArguments { min, given } => {
+ /// let s = format!("Need {} arguments, but only got {}.", min, given);
+ ///
+ /// let _ = msg.channel_id.say(&s);
+ /// },
+ /// TooManyArguments { max, given } => {
+ /// let s = format!("Max arguments allowed is {}, but got {}.", max, given);
+ ///
+ /// let _ = msg.channel_id.say(&s);
+ /// },
+ /// _ => println!("Unhandled dispatch error."),
+ /// }
+ /// }));
+ /// ```
+ pub fn on_dispatch_error<F>(mut self, f: F) -> Self
+ where F: Fn(Context, Message, DispatchError) + 'static {
+ self.dispatch_error_handler = Some(Arc::new(f));
+
+ self
+ }
+
+ /// Specify the function to be called prior to every command's execution.
+ /// If that function returns true, the command will be executed.
+ ///
+ /// # Examples
+ ///
+ /// Using `before` to log command usage:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .before(|ctx, msg, cmd_name| {
+ /// println!("Running command {}", cmd_name);
+ /// true
+ /// }));
+ /// ```
+ ///
+ /// Using before to prevent command usage:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .before(|_, msg, cmd_name| {
+ /// if let Ok(channel) = msg.channel_id.get() {
+ /// // Don't run unless in nsfw channel
+ /// if !channel.is_nsfw() {
+ /// return false;
+ /// }
+ /// }
+ ///
+ /// println!("Running command {}", cmd_name);
+ ///
+ /// true
+ /// }));
+ /// ```
+ ///
+ pub fn before<F>(mut self, f: F) -> Self
+ where F: Fn(&mut Context, &Message, &str) -> bool + 'static {
+ self.before = Some(Arc::new(f));
+
+ self
+ }
+
+ /// Specify the function to be called after every command's execution.
+ /// Fourth argument exists if command returned an error which you can handle.
+ ///
+ /// # Examples
+ ///
+ /// Using `after` to log command usage:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .after(|ctx, msg, cmd_name, error| {
+ /// // Print out an error if it happened
+ /// if let Err(why) = error {
+ /// println!("Error in {}: {:?}", cmd_name, why);
+ /// }
+ /// }));
+ /// ```
+ pub fn after<F>(mut self, f: F) -> Self
+ where F: Fn(&mut Context, &Message, &str, Result<(), String>) + 'static {
+ self.after = Some(Arc::new(f));
+
+ self
+ }
+}
+
+impl Framework for StandardFramework {
+ fn dispatch(&mut self, mut context: Context, message: Message, tokio_handle: &Handle) {
+ let res = command::positions(&mut context, &message, &self.configuration);
+
+ let positions = match res {
+ Some(mut positions) => {
+ // First, take out the prefixes that are as long as _or_ longer
+ // than the message, to avoid character boundary violations.
+ positions.retain(|p| *p < message.content.len());
+
+ // Ensure that there is _at least one_ position remaining. There
+ // is no point in continuing if there is not.
+ if positions.is_empty() {
+ return;
+ }
+
+ positions
+ },
+ None => return,
+ };
+
+ 'outer: for position in positions {
+ let mut built = String::new();
+ let round = message.content.chars().skip(position).collect::<String>();
+ let round = round.trim().split_whitespace().collect::<Vec<&str>>();
+
+ for i in 0..self.configuration.depth {
+ if i != 0 {
+ built.push(' ');
+ }
+
+ built.push_str(match round.get(i) {
+ Some(piece) => piece,
+ None => continue 'outer,
+ });
+
+ let groups = self.groups.clone();
+
+ for group in groups.values() {
+ let command_length = built.len();
+
+ if let Some(&CommandOrAlias::Alias(ref points_to)) =
+ group.commands.get(&built) {
+ built = points_to.to_owned();
+ }
+
+ let to_check = if let Some(ref prefix) = group.prefix {
+ if built.starts_with(prefix) && command_length > prefix.len() + 1 {
+ built[(prefix.len() + 1)..].to_owned()
+ } else {
+ continue;
+ }
+ } else {
+ built.clone()
+ };
+
+ if let Some(&CommandOrAlias::Command(ref command)) =
+ group.commands.get(&to_check) {
+ let before = self.before.clone();
+ let command = command.clone();
+ let after = self.after.clone();
+ let groups = self.groups.clone();
+
+ let (args, content) = {
+ let mut content = message.content[position..].trim();
+ content = content[command_length..].trim();
+
+ if command.use_quotes {
+ (utils::parse_quotes(content), content.to_string())
+ } else {
+ let delimiters = &self.configuration.delimiters;
+ let regular_expression = delimiters
+ .iter()
+ .map(|delimiter| escape(delimiter))
+ .join("|");
+
+ let regex = Regex::new(&regular_expression).unwrap();
+
+ (
+ regex
+ .split(content)
+ .filter_map(|p| if p.is_empty() {
+ None
+ } else {
+ Some(p.to_string())
+ })
+ .collect::<Vec<_>>(),
+ content.to_string(),
+ )
+ }
+ };
+
+ if let Some(error) = self.should_fail(
+ &mut context,
+ &message,
+ &command,
+ &args,
+ &to_check,
+ &built,
+ ) {
+ if let Some(ref handler) = self.dispatch_error_handler {
+ handler(context, message, error);
+ }
+ return;
+ }
+
+ tokio_handle.spawn_fn(move || {
+ if let Some(before) = before {
+ if !(before)(&mut context, &message, &built) {
+ return Ok(());
+ }
+ }
+
+ let result = match command.exec {
+ CommandType::StringResponse(ref x) => {
+ let _ = message.channel_id.say(x);
+
+ Ok(())
+ },
+ CommandType::Basic(ref x) => {
+ (x)(&mut context, &message, args, content)
+ },
+ CommandType::WithCommands(ref x) => {
+ (x)(&mut context, &message, groups, &args)
+ },
+ };
+
+ if let Some(after) = after {
+ (after)(&mut context, &message, &built, result);
+ }
+
+ Ok(())
+ });
+
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ fn update_current_user(&mut self, user_id: UserId, is_bot: bool) {
+ self.user_info = (user_id.0, is_bot);
+ }
+
+ fn initialized(&self) -> bool { self.initialized }
+}