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, CommandType, CommandGroup};
pub(crate) 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 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;
#[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(())
}
};
}
pub enum DispatchError {
CheckFailed,
CommandDisabled(String),
BlockedUser,
BlockedGuild,
LackOfPermissions(Permissions),
RateLimited(i64),
OnlyForDM,
OnlyForGuilds,
OnlyForOwners,
NotEnoughArguments { min: i32, given: usize },
TooManyArguments { max: i32, given: usize },
IgnoredBot,
WebhookAuthor,
}
type DispatchErrorHook = Fn(Context, Message, DispatchError) + Send + Sync + 'static;
#[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>>,
pub initialized: bool,
user_info: (u64, bool),
}
impl Framework {
pub fn configure<F>(mut self, f: F) -> Self
where F: FnOnce(Configuration) -> Configuration {
self.configuration = f(self.configuration);
self
}
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
}
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)]
pub(crate) fn dispatch(&mut self, mut context: Context, message: Message) {
let res = command::positions(&mut context, &message, &self.configuration);
let positions = match res {
Some(mut positions) => {
positions.retain(|p| *p < message.content.len());
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 _ = message.channel_id.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;
}
}
}
}
}
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
}
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
}
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
}
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
}
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
}
pub(crate) 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)
}
}