aboutsummaryrefslogtreecommitdiff
path: root/src/ext/framework
diff options
context:
space:
mode:
Diffstat (limited to 'src/ext/framework')
-rw-r--r--src/ext/framework/buckets.rs60
-rw-r--r--src/ext/framework/command.rs164
-rw-r--r--src/ext/framework/configuration.rs261
-rw-r--r--src/ext/framework/create_command.rs226
-rw-r--r--src/ext/framework/create_group.rs68
-rw-r--r--src/ext/framework/help_commands.rs285
-rw-r--r--src/ext/framework/mod.rs665
7 files changed, 0 insertions, 1729 deletions
diff --git a/src/ext/framework/buckets.rs b/src/ext/framework/buckets.rs
deleted file mode 100644
index 02cd658..0000000
--- a/src/ext/framework/buckets.rs
+++ /dev/null
@@ -1,60 +0,0 @@
-use std::collections::HashMap;
-use std::default::Default;
-use time;
-
-#[doc(hidden)]
-pub struct Ratelimit {
- pub delay: i64,
- pub limit: Option<(i64, i32)>,
-}
-
-#[doc(hidden)]
-pub struct MemberRatelimit {
- pub tickets: i32,
- pub last_time: i64,
- pub set_time: i64,
-}
-
-impl Default for MemberRatelimit {
- fn default() -> Self {
- MemberRatelimit {
- tickets: 0,
- last_time: 0,
- set_time: 0,
- }
- }
-}
-
-#[doc(hidden)]
-pub struct Bucket {
- pub ratelimit: Ratelimit,
- pub users: HashMap<u64, MemberRatelimit>,
-}
-
-impl Bucket {
- pub fn take(&mut self, user_id: u64) -> i64 {
- let time = time::get_time().sec;
- 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/ext/framework/command.rs b/src/ext/framework/command.rs
deleted file mode 100644
index 5f43284..0000000
--- a/src/ext/framework/command.rs
+++ /dev/null
@@ -1,164 +0,0 @@
-use std::sync::Arc;
-use super::Configuration;
-use ::client::Context;
-use ::model::Message;
-use ::model::Permissions;
-use std::collections::HashMap;
-
-pub type Check = Fn(&mut Context, &Message) -> bool + Send + Sync + 'static;
-pub type Exec = Fn(&mut Context, &Message, Vec<String>) -> Result<(), String> + Send + Sync + 'static;
-pub type Help = Fn(&mut Context, &Message, HashMap<String, Arc<CommandGroup>>, &[String]) -> Result<(), String> + Send + Sync + 'static;
-pub type BeforeHook = Fn(&mut Context, &Message, &String) -> bool + Send + Sync + 'static;
-pub type AfterHook = Fn(&mut Context, &Message, &String, Result<(), String>) + Send + Sync + 'static;
-#[doc(hidden)]
-pub type InternalCommand = Arc<Command>;
-pub type PrefixCheck = Fn(&mut Context) -> Option<String> + Send + Sync + 'static;
-
-#[doc(hidden)]
-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,
- #[doc(hidden)]
- pub aliases: Vec<String>,
-}
-
-impl Command {
- pub fn new<F>(f: F) -> Self
- where F: Fn(&mut Context, &Message, Vec<String>) -> Result<(), String> + Send + Sync + '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, content: &str, 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(content, conf) {
- positions.push(mention_end);
- } else if let Some(ref func) = conf.dynamic_prefix {
- if let Some(x) = func(ctx) {
- if content.starts_with(&x) {
- positions.push(x.len());
- }
- } else {
- for n in conf.prefixes.clone() {
- if content.starts_with(&n) {
- positions.push(n.len());
- }
- }
- }
- } else {
- for n in conf.prefixes.clone() {
- if 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() {
- match find_mention_end(content, conf) {
- Some(mention_end) => {
- let mut positions = vec![mention_end];
-
- if conf.allow_whitespace {
- positions.insert(0, mention_end + 1);
- }
-
- Some(positions)
- },
- None => None,
- }
- } else {
- None
- }
-}
-
-fn find_mention_end(content: &str, conf: &Configuration) -> Option<usize> {
- if let Some(ref mentions) = conf.on_mention {
- for mention in mentions {
- if !content.starts_with(&mention[..]) {
- continue;
- }
-
- return Some(mention.len());
- }
- }
-
- None
-}
diff --git a/src/ext/framework/configuration.rs b/src/ext/framework/configuration.rs
deleted file mode 100644
index 4f73359..0000000
--- a/src/ext/framework/configuration.rs
+++ /dev/null
@@ -1,261 +0,0 @@
-use std::collections::HashSet;
-use std::default::Default;
-use super::command::PrefixCheck;
-use ::client::{Context, rest};
-use ::model::{GuildId, 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::Client;
-/// use std::env;
-///
-/// let mut client = Client::login(&env::var("DISCORD_BOT_TOKEN").unwrap());
-///
-/// client.with_framework(|f| f
-/// .configure(|c| c.on_mention(true).prefix("~")));
-/// ```
-///
-/// [`Client`]: ../../client/struct.Client.html
-/// [`Framework`]: struct.Framework.html
-pub struct Configuration {
- #[doc(hidden)]
- pub depth: usize,
- #[doc(hidden)]
- pub on_mention: Option<Vec<String>>,
- #[doc(hidden)]
- pub allow_whitespace: bool,
- #[doc(hidden)]
- pub prefixes: Vec<String>,
- #[doc(hidden)]
- pub dynamic_prefix: Option<Box<PrefixCheck>>,
- #[doc(hidden)]
- pub ignore_bots: bool,
- #[doc(hidden)]
- pub blocked_users: HashSet<UserId>,
- #[doc(hidden)]
- pub blocked_guilds: HashSet<GuildId>,
- #[doc(hidden)]
- pub owners: HashSet<UserId>,
- #[doc(hidden)]
- pub disabled_commands: HashSet<String>,
- #[doc(hidden)]
- pub allow_dm: bool,
- #[doc(hidden)]
- pub ignore_webhooks: bool,
-}
-
-impl Configuration {
- /// 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
- }
-
- /// 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
- }
-
- /// 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
- }
-
- /// 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
- }
-
- /// HashSet of user Ids whose commands will be ignored.
- /// Guilds owned by user Ids will also be ignored.
- pub fn blocked_users(mut self, users: HashSet<UserId>) -> Self {
- self.blocked_users = users;
-
- self
- }
-
- /// HashSet of guild Ids where commands will be ignored.
- pub fn blocked_guilds(mut self, guilds: HashSet<GuildId>) -> Self {
- self.blocked_guilds = guilds;
-
- 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.
- 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::Client;
- /// #
- /// # let mut client = Client::login("token");
- /// client.with_framework(|f| f
- /// .command("ping", |c| c.exec_str("Pong!"))
- /// .configure(|c| c.dynamic_prefix(|ctx| {
- /// Some(if ctx.channel_id.unwrap().0 % 5 == 0 {
- /// "!"
- /// } else {
- /// "~"
- /// }.to_owned())
- /// })));
- /// ```
- pub fn dynamic_prefix<F>(mut self, dynamic_prefix: F) -> Self
- where F: Fn(&mut Context) -> Option<String> + Send + Sync + 'static {
- self.dynamic_prefix = Some(Box::new(dynamic_prefix));
-
- 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) = rest::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.
- pub fn owners(mut self, user_ids: HashSet<UserId>) -> Self {
- self.owners = user_ids;
-
- self
- }
-
- /// Sets the prefix to respond to. This can either be a single-char or
- /// multi-char string.
- pub fn prefix(mut self, prefix: &str) -> Self {
- self.prefixes = vec![prefix.to_owned()];
-
- self
- }
-
- /// Sets the prefixes to respond to. Those can either be single-chararacter or
- /// multi-chararacter strings.
- pub fn prefixes(mut self, prefixes: Vec<&str>) -> Self {
- self.prefixes = prefixes.iter().map(|x| x.to_string()).collect();
-
- 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`
- 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,
- }
- }
-}
diff --git a/src/ext/framework/create_command.rs b/src/ext/framework/create_command.rs
deleted file mode 100644
index 512e82c..0000000
--- a/src/ext/framework/create_command.rs
+++ /dev/null
@@ -1,226 +0,0 @@
-pub use super::{Command, CommandType, CommandGroup};
-
-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 a ratelimit bucket.
- pub fn bucket(mut self, bucket: &str) -> Self {
- self.0.bucket = Some(bucket.to_owned());
-
- 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
- }
-
- /// Adds multiple aliases.
- pub fn batch_known_as(mut self, names: Vec<&str>) -> Self {
- for n in names {
- self.0.aliases.push(n.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::client::{Client, Context};
- /// use serenity::model::Message;
- /// use std::env;
- ///
- /// let mut client = Client::login(&env::var("DISCORD_TOKEN").unwrap());
- ///
- /// client.with_framework(|f| f
- /// .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>) -> Result<(), String> {
- /// let _ = message.channel_id.say("Pong!");
- ///
- /// Ok(())
- /// }
- ///
- /// fn owner_check(_context: &mut Context, message: &Message) -> 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) -> 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>) -> 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> + Send + Sync + '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
- }
-
- /// 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/ext/framework/create_group.rs b/src/ext/framework/create_group.rs
deleted file mode 100644
index 03fc33e..0000000
--- a/src/ext/framework/create_group.rs
+++ /dev/null
@@ -1,68 +0,0 @@
-pub use ext::framework::command::{Command, CommandType, CommandGroup, CommandOrAlias};
-pub use ext::framework::create_command::CreateCommand;
-
-use std::default::Default;
-use std::sync::Arc;
-use ::client::Context;
-use ::model::Message;
-
-#[derive(Default)]
-pub struct CreateGroup(pub CommandGroup);
-
-/// 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")))
-/// ```
-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>) -> 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/ext/framework/help_commands.rs b/src/ext/framework/help_commands.rs
deleted file mode 100644
index 1f38bd5..0000000
--- a/src/ext/framework/help_commands.rs
+++ /dev/null
@@ -1,285 +0,0 @@
-//! 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::ext::framework::help_commands;
-//! use serenity::Client;
-//! use std::env;
-//!
-//! let mut client = Client::login(&env::var("DISCORD_TOKEN").unwrap());
-//! client.with_framework(|f| f
-//! .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::Message;
-use ::utils::Colour;
-
-fn error_embed(ctx: &mut Context, input: &str) {
- let _ = ctx.channel_id
- .unwrap()
- .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
-}
-
-pub fn with_embeds(ctx: &mut Context,
- _: &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(ctx, &format!("Did you mean \"{}\"?", name));
- return Ok(());
- }
- }
- }
- }
-
- if let Some((command_name, command)) = found {
- if !command.help_available {
- error_embed(ctx, "**Error**: No help available.");
-
- return Ok(());
- }
-
- let _ = ctx.channel_id.unwrap().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(ctx, &error_msg);
-
- return Ok(());
- }
-
- let _ = ctx.channel_id.unwrap().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.");
-
- for (group_name, group) in groups {
- let mut desc = String::new();
-
- if let Some(ref x) = group.prefix {
- let _ = write!(desc, "Prefix: {}\n", x);
- }
-
- let mut no_commands = true;
-
- for (n, cmd) in remove_aliases(&group.commands) {
- if cmd.help_available {
- let _ = write!(desc, "`{}`\n", n);
-
- no_commands = false;
- }
- }
-
- if no_commands {
- let _ = write!(desc, "*[No commands]*");
- }
-
- e = e.field(|f| f.name(&group_name).value(&desc));
- }
-
- e
- }));
-
- Ok(())
-}
-
-pub fn plain(ctx: &mut Context,
- _: &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 _ = ctx.channel_id.unwrap().say(&format!("Did you mean {:?}?", name));
- return Ok(());
- }
- }
- }
- }
-
- if let Some((command_name, command)) = found {
- if !command.help_available {
- let _ = ctx.channel_id.unwrap().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);
- }
-
- result.push_str("**Available:** ");
- result.push_str(if command.dm_only {
- "Only in DM"
- } else if command.guild_only {
- "Only in guilds"
- } else {
- "In DM and guilds"
- });
- result.push_str("\n");
-
- let _ = ctx.channel_id.unwrap().say(&result);
-
- return Ok(());
- }
- }
-
- let _ = ctx.channel_id.unwrap().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();
-
- for (group_name, group) in groups {
- let _ = write!(result, "**{}:** ", group_name);
-
- if let Some(ref x) = group.prefix {
- let _ = write!(result, "(prefix: `{}`): ", x);
- }
-
- let mut no_commands = true;
-
- for (n, cmd) in remove_aliases(&group.commands) {
- if cmd.help_available {
- let _ = write!(result, "`{}` ", n);
-
- no_commands = false;
- }
- }
-
- if no_commands {
- result.push_str("*[No Commands]*");
- }
-
- result.push('\n');
- }
-
- let _ = ctx.channel_id.unwrap().say(&result);
-
- Ok(())
-}
diff --git a/src/ext/framework/mod.rs b/src/ext/framework/mod.rs
deleted file mode 100644
index cde10b2..0000000
--- a/src/ext/framework/mod.rs
+++ /dev/null
@@ -1,665 +0,0 @@
-//! The framework is a customizable method of separating commands.
-//!
-//! This is used in combination with [`Client::with_framework`].
-//!
-//! The framework has a number of configurations, and can have any number of
-//! commands bound to it. The primary purpose of it is to offer the utility of
-//! not needing to manually match message content strings to determine if a
-//! message is a command.
-//!
-//! Additionally, "checks" can be added to commands, to ensure that a certain
-//! condition is met prior to calling a command; this could be a check that the
-//! user who posted a message owns the bot, for example.
-//!
-//! Each command has a given named, and an associated function/closure. For
-//! example, you might have two commands: `"ping"` and `"weather"`. These each
-//! have an associated function that are called if the framework determines
-//! that a message is of that command.
-//!
-//! Assuming a command prefix of `"~"`, then the following would occur with the
-//! two previous commands:
-//!
-//! ```ignore
-//! ~ping // calls the ping command's function
-//! ~pin // does not
-//! ~ ping // _does_ call it _if_ the `allow_whitespace` option is enabled
-//! ~~ping // does not
-//! ```
-//!
-//! # Examples
-//!
-//! Configuring a Client with a framework, which has a prefix of `"~"` and a
-//! ping and about command:
-//!
-//! ```rust,ignore
-//! use serenity::client::{Client, Context};
-//! use serenity::model::Message;
-//! use std::env;
-//!
-//! let mut client = Client::login(&env::var("DISCORD_BOT_TOKEN").unwrap());
-//!
-//! client.with_framework(|f| f
-//! .configure(|c| c.prefix("~"))
-//! .command("about", |c| c.exec_str("A simple test bot"))
-//! .command("ping", |c| c.exec(ping)));
-//!
-//! command!(about(_context, message) {
-//! let _ = message.channel_id.say("A simple test bot");
-//! });
-//!
-//! command!(ping(_context, message) {
-//! let _ = message.channel_id.say("Pong!");
-//! });
-//! ```
-//!
-//! [`Client::with_framework`]: ../../client/struct.Client.html#method.with_framework
-
-pub mod help_commands;
-
-mod command;
-mod configuration;
-mod create_command;
-mod create_group;
-mod buckets;
-
-pub use self::buckets::{Bucket, MemberRatelimit, Ratelimit};
-pub use self::command::{Command, CommandType, CommandGroup, 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 std::thread;
-use ::client::Context;
-use ::model::{Message, UserId};
-use ::model::permissions::Permissions;
-use ::utils;
-
-#[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`]: ext/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>) -> ::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>) -> ::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>) -> ::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>) -> ::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,
- /// 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) + Send + Sync + 'static;
-
-/// A utility for easily managing dispatches to commands.
-///
-/// Refer to the [module-level documentation] for more information.
-///
-/// [module-level documentation]: index.html
-#[allow(type_complexity)]
-#[derive(Default)]
-pub struct Framework {
- 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 [`Client::on_message`] handler - to have the
- /// framework check if a [`Event::MessageCreate`] should be processed by
- /// itself.
- ///
- /// [`Client::on_message`]: ../../client/struct.Client.html#method.on_message
- /// [`Event::MessageCreate`]: ../../model/event/enum.Event.html#variant.MessageCreate
- pub initialized: bool,
- user_info: (u64, bool),
-}
-
-impl Framework {
- /// 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::Client;
- /// use std::env;
- ///
- /// let mut client = Client::login(&env::var("DISCORD_TOKEN").unwrap());
- /// client.with_framework(|f| f
- /// .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`.
- 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(),
- });
-
- self
- }
-
- /// Defines a bucket with only a `delay` between each command.
- 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(),
- });
-
- 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
- }
-
- fn checks_passed(&self, command: &Arc<Command>, mut context: &mut Context, message: &Message) -> bool {
- for check in &command.checks {
- if !(check)(&mut context, message) {
- return false;
- }
- }
-
- true
- }
-
- #[allow(too_many_arguments)]
- fn should_fail(&mut self,
- mut context: &mut Context,
- message: &Message,
- command: &Arc<Command>,
- args: usize,
- 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(rate_limit) = command.bucket.clone().map(|x| self.ratelimit_time(x.as_str(), message.author.id.0)) {
- if rate_limit > 0i64 {
- return Some(DispatchError::RateLimited(rate_limit));
- }
- }
-
- if let Some(x) = command.min_args {
- if args < x as usize {
- return Some(DispatchError::NotEnoughArguments {
- min: x,
- given: args
- });
- }
- }
-
- if let Some(x) = command.max_args {
- if args > x as usize {
- return Some(DispatchError::TooManyArguments {
- max: x,
- given: args
- });
- }
- }
-
- #[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.checks_passed(command, &mut context, message) {
- Some(DispatchError::CheckFailed)
- } 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 {
- None
- }
- }
- }
-
- #[allow(cyclomatic_complexity)]
- #[doc(hidden)]
- pub fn dispatch(&mut self, mut context: Context, message: Message) {
- let res = command::positions(&mut context, &message.content, &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 = {
- let content = message.content[position..].trim();
-
- if command.use_quotes {
- utils::parse_quotes(&content[command_length..])
- } else {
- content[command_length..]
- .split_whitespace()
- .map(|arg| arg.to_owned())
- .collect::<Vec<String>>()
- }
- };
-
- if let Some(error) = self.should_fail(&mut context, &message, &command, args.len(), &to_check, &built) {
- if let Some(ref handler) = self.dispatch_error_handler {
- handler(context, message, error);
- }
- return;
- }
-
- thread::spawn(move || {
- if let Some(before) = before {
- if !(before)(&mut context, &message, &built) {
- return;
- }
- }
-
- let result = match command.exec {
- CommandType::StringResponse(ref x) => {
- let _ = &mut context.channel_id.unwrap().say(x);
-
- Ok(())
- },
- CommandType::Basic(ref x) => {
- (x)(&mut context, &message, args)
- },
- CommandType::WithCommands(ref x) => {
- (x)(&mut context, &message, groups, &args)
- }
- };
-
- if let Some(after) = after {
- (after)(&mut context, &message, &built, result);
- }
- });
-
- return;
- }
- }
- }
- }
- }
-
- /// 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.
- ///
- /// Note that once v0.2.0 lands, 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
- pub fn on<F, S>(mut self, command_name: S, f: F) -> Self
- where F: Fn(&mut Context, &Message, Vec<String>) -> Result<(), String> + Send + Sync + '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.to_owned()), 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
- }
-
- 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.
- pub fn on_dispatch_error<F>(mut self, f: F) -> Self
- where F: Fn(Context, Message, DispatchError) + Send + Sync + '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.
- pub fn before<F>(mut self, f: F) -> Self
- where F: Fn(&mut Context, &Message, &String) -> bool + Send + Sync + '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.
- pub fn after<F>(mut self, f: F) -> Self
- where F: Fn(&mut Context, &Message, &String, Result<(), String>) + Send + Sync + 'static {
- self.after = Some(Arc::new(f));
-
- self
- }
-
- #[doc(hidden)]
- pub fn update_current_user(&mut self, user_id: UserId, is_bot: bool) {
- self.user_info = (user_id.0, is_bot);
- }
-
- fn ratelimit_time(&mut self, bucket_name: &str, user_id: u64) -> i64 {
- self.buckets
- .get_mut(bucket_name)
- .map(|bucket| bucket.take(user_id))
- .unwrap_or(0)
- }
-}