pub mod arguments;
pub mod context;
pub(crate) mod routing;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::sync::Arc;
use thiserror::Error;
use twilight_gateway::Event;
use twilight_model::application::command::{Command, CommandType};
use twilight_model::application::interaction::application_command::CommandDataOption;
use twilight_model::id::Id;
use crate::commands::errors::{ArgumentError, CommandError};
use crate::commands::permissions::{PermissionChecker, PermissionContext};
use crate::commands::slash::arguments::{ArgumentMeta, ArgumentType, IntoArgument};
use crate::commands::slash::context::SlashContext;
use crate::commands::{CommandGroupIntoCommandNode, CommandNode, CommandResult};
use crate::errors::{ErrorHandler, ErrorHandlerWithoutType, ErrorHandlerWrapper};
use crate::state::StateBound;
use crate::utils::DynFuture;
#[derive(Clone)]
pub struct SlashCommand<State>
where
State: StateBound,
{
name: String,
name_i18n: HashMap<String, String>,
description: String,
description_i18n: HashMap<String, String>,
handler: Arc<dyn SlashCommandHandlerWithoutArgs<State>>,
arguments: Vec<ArgumentMeta>,
on_errors: Vec<Arc<dyn ErrorHandlerWithoutType<State>>>,
checks: Vec<Arc<dyn PermissionChecker<State>>>,
}
impl<State> SlashCommand<State>
where
State: StateBound,
{
pub(crate) async fn run(&self, ctx: SlashContext<State>) -> CommandResult {
let permission_ctx = PermissionContext {
event: Event::InteractionCreate(Box::new(ctx.event.clone())),
handle: ctx.handle.clone(),
state: ctx.state.clone(),
};
for checker in &self.checks {
checker
.check(permission_ctx.clone())
.await
.map_err(CommandError::Permissions)?;
}
self.handler.run(ctx).await
}
}
impl<State> From<SlashCommand<State>> for Command
where
State: StateBound,
{
fn from(value: SlashCommand<State>) -> Self {
#[allow(deprecated)]
Command {
application_id: None,
contexts: None,
default_member_permissions: None,
description: value.description,
description_localizations: Some(value.description_i18n),
guild_id: None,
id: None,
integration_types: None,
kind: CommandType::ChatInput,
name: value.name,
name_localizations: Some(value.name_i18n),
nsfw: None,
options: value.arguments.into_iter().map(|arg| arg.into()).collect(),
version: Id::new(1),
dm_permission: None,
}
}
}
pub struct SlashCommandBuilder<State>
where
State: StateBound,
{
name: String,
name_i18n: HashMap<String, String>,
description: String,
description_i18n: HashMap<String, String>,
handler: Arc<dyn SlashCommandHandlerWithoutArgs<State>>,
arguments: Vec<ArgumentMeta>,
on_errors: Vec<Arc<dyn ErrorHandlerWithoutType<State>>>,
checks: Vec<Arc<dyn PermissionChecker<State>>>,
}
impl<State> SlashCommandBuilder<State>
where
State: StateBound,
{
pub(crate) fn new<Args>(
name: String,
handler: impl SlashCommandHandler<State, Args> + 'static,
) -> Self
where
Args: Send + Sync + 'static,
{
SlashCommandBuilder {
name,
name_i18n: HashMap::new(),
description: String::from("A Dyncord command."),
description_i18n: HashMap::new(),
handler: Arc::new(SlashCommandHandlerWrapper::new(handler)),
arguments: vec![],
on_errors: vec![],
checks: vec![],
}
}
pub fn name_i18n(mut self, lang: impl Into<String>, name: impl Into<String>) -> Self {
self.name_i18n.insert(lang.into(), name.into());
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = description.into();
self
}
pub fn description_i18n(
mut self,
lang: impl Into<String>,
description: impl Into<String>,
) -> Self {
self.description_i18n
.insert(lang.into(), description.into());
self
}
pub fn argument(mut self, argument: impl Into<ArgumentMeta>) -> Self {
self.arguments.push(argument.into());
self
}
pub fn on_error<Error>(mut self, handler: impl ErrorHandler<State, Error> + 'static) -> Self
where
Error: Send + Sync + 'static,
{
self.on_errors
.push(Arc::new(ErrorHandlerWrapper::new(handler)));
self
}
pub fn check(mut self, checker: impl PermissionChecker<State> + 'static) -> Self {
self.checks.push(Arc::new(checker));
self
}
pub(crate) fn build(self) -> SlashCommand<State> {
SlashCommand {
name: self.name,
name_i18n: self.name_i18n,
description: self.description,
description_i18n: self.description_i18n,
handler: self.handler,
arguments: self.arguments,
on_errors: self.on_errors,
checks: self.checks,
}
}
}
pub trait SlashCommandHandler<State, Args>: Send + Sync
where
State: StateBound,
{
fn run(&self, ctx: SlashContext<State>) -> DynFuture<'_, CommandResult>;
fn argument_types(&self) -> Vec<(ArgumentType, bool)>;
}
impl<State, Func, Fut, Res> SlashCommandHandler<State, ()> for Func
where
State: StateBound,
Func: Fn(SlashContext<State>) -> Fut + Send + Sync,
Fut: Future<Output = Res> + Send,
{
fn run(&self, ctx: SlashContext<State>) -> DynFuture<'_, CommandResult> {
Box::pin(async move {
self(ctx).await;
Ok(())
})
}
fn argument_types(&self) -> Vec<(ArgumentType, bool)> {
vec![]
}
}
async fn parse_arg<T, State>(
ctx: SlashContext<State>,
options: &[CommandDataOption],
index: usize,
) -> Result<T, ArgumentError>
where
T: IntoArgument<State>,
State: StateBound,
{
let argument = ctx
.command
.arguments
.get(index)
.ok_or(ArgumentError::MissingMeta)?;
let option = options.iter().find(|i| i.name == *argument.name());
T::into_argument_primitive(ctx, option.cloned()).await
}
impl<State, Func, Fut, Res, A> SlashCommandHandler<State, (A,)> for Func
where
State: StateBound,
Func: Fn(SlashContext<State>, A) -> Fut + Send + Sync,
Fut: Future<Output = Res> + Send,
A: IntoArgument<State>,
{
fn run(&self, ctx: SlashContext<State>) -> DynFuture<'_, CommandResult> {
Box::pin(async move {
let options = ctx.event_data.options.clone();
let a = parse_arg(ctx.clone(), &options, 0).await?;
self(ctx, a).await;
Ok(())
})
}
fn argument_types(&self) -> Vec<(ArgumentType, bool)> {
vec![A::r#type()]
}
}
impl<State, Func, Fut, Res, A, B> SlashCommandHandler<State, (A, B)> for Func
where
State: StateBound,
Func: Fn(SlashContext<State>, A, B) -> Fut + Send + Sync,
Fut: Future<Output = Res> + Send,
A: IntoArgument<State>,
B: IntoArgument<State>,
{
fn run(&self, ctx: SlashContext<State>) -> DynFuture<'_, CommandResult> {
Box::pin(async move {
let options = ctx.event_data.options.clone();
let a = parse_arg(ctx.clone(), &options, 0).await?;
let b = parse_arg(ctx.clone(), &options, 1).await?;
self(ctx, a, b).await;
Ok(())
})
}
fn argument_types(&self) -> Vec<(ArgumentType, bool)> {
vec![A::r#type(), B::r#type()]
}
}
impl<State, Func, Fut, Res, A, B, C> SlashCommandHandler<State, (A, B, C)> for Func
where
State: StateBound,
Func: Fn(SlashContext<State>, A, B, C) -> Fut + Send + Sync,
Fut: Future<Output = Res> + Send,
A: IntoArgument<State>,
B: IntoArgument<State>,
C: IntoArgument<State>,
{
fn run(&self, ctx: SlashContext<State>) -> DynFuture<'_, CommandResult> {
Box::pin(async move {
let options = ctx.event_data.options.clone();
let a = parse_arg(ctx.clone(), &options, 0).await?;
let b = parse_arg(ctx.clone(), &options, 1).await?;
let c = parse_arg(ctx.clone(), &options, 2).await?;
self(ctx, a, b, c).await;
Ok(())
})
}
fn argument_types(&self) -> Vec<(ArgumentType, bool)> {
vec![A::r#type(), B::r#type(), C::r#type()]
}
}
impl<State, Func, Fut, Res, A, B, C, D> SlashCommandHandler<State, (A, B, C, D)> for Func
where
State: StateBound,
Func: Fn(SlashContext<State>, A, B, C, D) -> Fut + Send + Sync,
Fut: Future<Output = Res> + Send,
A: IntoArgument<State>,
B: IntoArgument<State>,
C: IntoArgument<State>,
D: IntoArgument<State>,
{
fn run(&self, ctx: SlashContext<State>) -> DynFuture<'_, CommandResult> {
Box::pin(async move {
let options = ctx.event_data.options.clone();
let a = parse_arg(ctx.clone(), &options, 0).await?;
let b = parse_arg(ctx.clone(), &options, 1).await?;
let c = parse_arg(ctx.clone(), &options, 2).await?;
let d = parse_arg(ctx.clone(), &options, 3).await?;
self(ctx, a, b, c, d).await;
Ok(())
})
}
fn argument_types(&self) -> Vec<(ArgumentType, bool)> {
vec![A::r#type(), B::r#type(), C::r#type(), D::r#type()]
}
}
impl<State, Func, Fut, Res, A, B, C, D, E> SlashCommandHandler<State, (A, B, C, D, E)> for Func
where
State: StateBound,
Func: Fn(SlashContext<State>, A, B, C, D, E) -> Fut + Send + Sync,
Fut: Future<Output = Res> + Send,
A: IntoArgument<State>,
B: IntoArgument<State>,
C: IntoArgument<State>,
D: IntoArgument<State>,
E: IntoArgument<State>,
{
fn run(&self, ctx: SlashContext<State>) -> DynFuture<'_, CommandResult> {
Box::pin(async move {
let options = ctx.event_data.options.clone();
let a = parse_arg(ctx.clone(), &options, 0).await?;
let b = parse_arg(ctx.clone(), &options, 1).await?;
let c = parse_arg(ctx.clone(), &options, 2).await?;
let d = parse_arg(ctx.clone(), &options, 3).await?;
let e = parse_arg(ctx.clone(), &options, 4).await?;
self(ctx, a, b, c, d, e).await;
Ok(())
})
}
fn argument_types(&self) -> Vec<(ArgumentType, bool)> {
vec![
A::r#type(),
B::r#type(),
C::r#type(),
D::r#type(),
E::r#type(),
]
}
}
impl<State, Func, Fut, Res, A, B, C, D, E, F> SlashCommandHandler<State, (A, B, C, D, E, F)>
for Func
where
State: StateBound,
Func: Fn(SlashContext<State>, A, B, C, D, E, F) -> Fut + Send + Sync,
Fut: Future<Output = Res> + Send,
A: IntoArgument<State>,
B: IntoArgument<State>,
C: IntoArgument<State>,
D: IntoArgument<State>,
E: IntoArgument<State>,
F: IntoArgument<State>,
{
fn run(&self, ctx: SlashContext<State>) -> DynFuture<'_, CommandResult> {
Box::pin(async move {
let options = ctx.event_data.options.clone();
let a = parse_arg(ctx.clone(), &options, 0).await?;
let b = parse_arg(ctx.clone(), &options, 1).await?;
let c = parse_arg(ctx.clone(), &options, 2).await?;
let d = parse_arg(ctx.clone(), &options, 3).await?;
let e = parse_arg(ctx.clone(), &options, 4).await?;
let f = parse_arg(ctx.clone(), &options, 5).await?;
self(ctx, a, b, c, d, e, f).await;
Ok(())
})
}
fn argument_types(&self) -> Vec<(ArgumentType, bool)> {
vec![
A::r#type(),
B::r#type(),
C::r#type(),
D::r#type(),
E::r#type(),
F::r#type(),
]
}
}
pub struct SlashCommandHandlerWrapper<F, Args>
where
Args: Send + Sync,
{
handler: F,
__args: PhantomData<Args>,
}
impl<F, Args> SlashCommandHandlerWrapper<F, Args>
where
Args: Send + Sync,
{
fn new(handler: F) -> Self {
SlashCommandHandlerWrapper {
handler,
__args: PhantomData,
}
}
}
pub trait SlashCommandHandlerWithoutArgs<State>: Send + Sync
where
State: StateBound,
{
fn run(&self, ctx: SlashContext<State>) -> DynFuture<'_, CommandResult>;
fn argument_types(&self) -> Vec<(ArgumentType, bool)>;
}
impl<State, F, Args> SlashCommandHandlerWithoutArgs<State> for SlashCommandHandlerWrapper<F, Args>
where
State: StateBound,
F: SlashCommandHandler<State, Args>,
Args: Send + Sync,
{
fn run(&self, ctx: SlashContext<State>) -> DynFuture<'_, CommandResult> {
SlashCommandHandler::run(&self.handler, ctx)
}
fn argument_types(&self) -> Vec<(ArgumentType, bool)> {
SlashCommandHandler::argument_types(&self.handler)
}
}
#[derive(Clone)]
pub struct SlashCommandGroup<State>
where
State: StateBound,
{
pub name: String,
pub children: Vec<CommandNode<State>>,
pub on_errors: Vec<Arc<dyn ErrorHandlerWithoutType<State>>>,
}
impl<State> SlashCommandGroup<State>
where
State: StateBound,
{
pub fn build(name: impl Into<String>) -> SlashCommandGroupBuilder<State> {
SlashCommandGroupBuilder::new(name)
}
}
#[derive(Clone)]
pub struct SlashCommandGroupBuilder<State>
where
State: StateBound,
{
name: String,
children: Vec<CommandNode<State>>,
on_errors: Vec<Arc<dyn ErrorHandlerWithoutType<State>>>,
}
impl<State> SlashCommandGroupBuilder<State>
where
State: StateBound,
{
pub(crate) fn new(name: impl Into<String>) -> Self {
SlashCommandGroupBuilder {
name: name.into(),
children: vec![],
on_errors: vec![],
}
}
pub fn command(mut self, command: impl Into<SlashCommand<State>>) -> Self {
self.children
.push(CommandNode::SlashCommand(command.into()));
self
}
pub fn nest(mut self, group: impl Into<SlashCommandGroup<State>>) -> Self {
self.children
.push(CommandNode::SlashCommandGroup(group.into()));
self
}
pub fn on_error<Error>(mut self, handler: impl ErrorHandler<State, Error> + 'static) -> Self
where
Error: Send + Sync + 'static,
{
self.on_errors
.push(Arc::new(ErrorHandlerWrapper::new(handler)));
self
}
pub(crate) fn build(self) -> SlashCommandGroup<State> {
SlashCommandGroup {
name: self.name,
children: self.children,
on_errors: self.on_errors,
}
}
}
impl<State> CommandGroupIntoCommandNode<State> for SlashCommandGroup<State>
where
State: StateBound,
{
fn into_command_node(self) -> CommandNode<State> {
CommandNode::SlashCommandGroup(self)
}
}
impl<State> CommandGroupIntoCommandNode<State> for SlashCommandGroupBuilder<State>
where
State: StateBound,
{
fn into_command_node(self) -> CommandNode<State> {
CommandNode::SlashCommandGroup(self.build())
}
}
#[derive(Debug, Error)]
pub enum InvalidCommandError {
#[error(
"The command /{0}'s handler has more arguments than you defined when building the command through the `Command` interface. Add the remaining arguments."
)]
TooFewArguments(String),
#[error(
"The command /{0}'s handler has less arguments than you defined when building the command through the `Command` interface. Remove the extra arguments."
)]
TooManyArguments(String),
#[error(
"The command /{0} has invalid arguments passed as metadata. The handler function defines an argument of type {1:?}, but the metadata you set defines an argument of type {2:?}. This will always fail to parse. Correct the command's metadata, or change your handler's signature."
)]
MismatchingArgumentTypes(String, (ArgumentType, bool), (ArgumentType, bool)),
#[error("You have a slash command with an empty name!")]
CommandNameTooShort,
#[error("Your command /{0}'s name is too long. It cannot exceed 32 characters.")]
CommandNameTooLong(String),
#[error(
"Your command /{0} has an argument whose name is empty. Set it to something less than 32 characters long."
)]
ArgumentNameTooShort(String),
#[error(
"Your command /{0} has an argument called {1} whose name is too long. Set it to something less than 32 characters long."
)]
ArgumentNameTooLong(String, String),
}
pub fn validate_commands<State>(
commands: &[&SlashCommand<State>],
) -> Result<(), Vec<InvalidCommandError>>
where
State: StateBound,
{
let mut errors = vec![];
for command in commands {
let handler_argument_types = command.handler.argument_types();
let command_argument_types = command
.arguments
.iter()
.map(|a| a.r#type())
.collect::<Vec<_>>();
if handler_argument_types.len() > command_argument_types.len() {
errors.push(InvalidCommandError::TooFewArguments(command.name.clone()));
} else if handler_argument_types.len() < command_argument_types.len() {
errors.push(InvalidCommandError::TooManyArguments(command.name.clone()));
}
for argument in &command.arguments {
if argument.name().len() > 32 {
errors.push(InvalidCommandError::ArgumentNameTooLong(
command.name.clone(),
argument.name().clone(),
));
} else if argument.name().is_empty() {
errors.push(InvalidCommandError::ArgumentNameTooShort(
command.name.clone(),
));
}
}
for (type_1, type_2) in handler_argument_types.iter().zip(command_argument_types) {
if *type_1 != type_2 {
errors.push(InvalidCommandError::MismatchingArgumentTypes(
command.name.clone(),
*type_1,
type_2,
));
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}