use crate::serenity_prelude as serenity;
fn find_matching_command<'a, 'b, U, E>(
interaction_name: &str,
interaction_kind: serenity::CommandType,
interaction_options: &'b [serenity::ResolvedOption<'b>],
commands: &'a [crate::Command<U, E>],
command_tree: &mut Vec<&'a crate::Command<U, E>>,
) -> Option<&'b [serenity::ResolvedOption<'b>]> {
commands.iter().find_map(|cmd| {
if interaction_name != cmd.name
&& Some(interaction_name) != cmd.context_menu_name.as_deref()
{
return None;
}
match interaction_kind {
serenity::CommandType::ChatInput => {
cmd.slash_action?;
}
serenity::CommandType::User | serenity::CommandType::Message => {
cmd.context_menu_action
.map(serenity::CommandType::from)
.filter(|kind| kind == &interaction_kind)?;
}
_ => unimplemented!(),
}
command_tree.push(cmd);
if let Some((sub_name, sub_interaction)) =
interaction_options
.iter()
.find_map(|option| match &option.value {
serenity::ResolvedValue::SubCommand(o)
| serenity::ResolvedValue::SubCommandGroup(o) => Some((&option.name, o)),
_ => None,
})
{
find_matching_command(
sub_name,
interaction_kind,
sub_interaction,
&cmd.subcommands,
command_tree,
)
} else {
Some(interaction_options)
}
})
}
fn extract_command<'a, U, E>(
framework: crate::FrameworkContext<'a, U, E>,
interaction: &'a serenity::CommandInteraction,
interaction_type: crate::CommandInteractionType,
has_sent_initial_response: &'a std::sync::atomic::AtomicBool,
invocation_data: &'a tokio::sync::Mutex<Box<dyn std::any::Any + Send + Sync>>,
options: &'a [serenity::ResolvedOption<'a>],
command_tree: &'a mut Vec<&'a crate::Command<U, E>>,
) -> Result<crate::ApplicationContext<'a, U, E>, crate::FrameworkError<'a, U, E>> {
let Some(leaf_interaction_options) = find_matching_command(
&interaction.data.name,
interaction.data.kind,
options,
&framework.options.commands,
command_tree,
) else {
return Err(crate::FrameworkError::UnknownInteraction {
framework,
interaction,
});
};
Ok(crate::ApplicationContext {
framework,
interaction,
interaction_type,
args: leaf_interaction_options,
command_tree,
has_sent_initial_response,
invocation_data,
__non_exhaustive: (),
})
}
pub async fn extract_command_and_run_checks<'a, U, E>(
framework: crate::FrameworkContext<'a, U, E>,
interaction: &'a serenity::CommandInteraction,
interaction_type: crate::CommandInteractionType,
has_sent_initial_response: &'a std::sync::atomic::AtomicBool,
invocation_data: &'a tokio::sync::Mutex<Box<dyn std::any::Any + Send + Sync>>,
options: &'a [serenity::ResolvedOption<'a>],
command_tree: &'a mut Vec<&'a crate::Command<U, E>>,
) -> Result<crate::ApplicationContext<'a, U, E>, crate::FrameworkError<'a, U, E>> {
let ctx = extract_command(
framework,
interaction,
interaction_type,
has_sent_initial_response,
invocation_data,
options,
command_tree,
)?;
super::common::check_permissions_and_cooldown(ctx.into()).await?;
Ok(ctx)
}
async fn run_command<U, E>(
ctx: crate::ApplicationContext<'_, U, E>,
) -> Result<(), crate::FrameworkError<'_, U, E>> {
super::common::check_permissions_and_cooldown(ctx.into()).await?;
(ctx.framework.options.pre_command)(crate::Context::Application(ctx)).await;
let command_structure_mismatch_error = crate::FrameworkError::CommandStructureMismatch {
ctx,
description: "received interaction type but command contained no \
matching action or interaction contained no matching context menu object",
};
let command = ctx.command();
let action_result = match ctx.interaction.data.kind {
serenity::CommandType::ChatInput => {
let action = command
.slash_action
.ok_or(command_structure_mismatch_error)?;
action(ctx).await
}
serenity::CommandType::User => {
match (command.context_menu_action, &ctx.interaction.data.target()) {
(
Some(crate::ContextMenuCommandAction::User(action)),
Some(serenity::ResolvedTarget::User(user, member)),
) => {
let mut user = (*user).clone();
user.member = member.map(|v| Box::new(v.clone()));
action(ctx, user).await
}
_ => return Err(command_structure_mismatch_error),
}
}
serenity::CommandType::Message => {
match (command.context_menu_action, &ctx.interaction.data.target()) {
(
Some(crate::ContextMenuCommandAction::Message(action)),
Some(serenity::ResolvedTarget::Message(message)),
) => action(ctx, (*message).clone()).await,
_ => return Err(command_structure_mismatch_error),
}
}
other => {
tracing::warn!("unknown interaction command type: {:?}", other);
return Ok(());
}
};
action_result?;
(ctx.framework.options.post_command)(crate::Context::Application(ctx)).await;
Ok(())
}
pub async fn dispatch_interaction<'a, U, E>(
framework: crate::FrameworkContext<'a, U, E>,
interaction: &'a serenity::CommandInteraction,
has_sent_initial_response: &'a std::sync::atomic::AtomicBool,
invocation_data: &'a tokio::sync::Mutex<Box<dyn std::any::Any + Send + Sync>>,
options: &'a [serenity::ResolvedOption<'a>],
command_tree: &'a mut Vec<&'a crate::Command<U, E>>,
) -> Result<(), crate::FrameworkError<'a, U, E>> {
let ctx = extract_command(
framework,
interaction,
crate::CommandInteractionType::Command,
has_sent_initial_response,
invocation_data,
options,
command_tree,
)?;
crate::catch_unwind_maybe(run_command(ctx))
.await
.map_err(|payload| crate::FrameworkError::CommandPanic {
payload,
ctx: ctx.into(),
})??;
Ok(())
}
async fn run_autocomplete<U, E>(
ctx: crate::ApplicationContext<'_, U, E>,
) -> Result<(), crate::FrameworkError<'_, U, E>> {
super::common::check_permissions_and_cooldown(ctx.into()).await?;
let (focused_option_name, partial_input) = match ctx.args.iter().find_map(|o| match &o.value {
serenity::ResolvedValue::Autocomplete { value, .. } => Some((&o.name, value)),
_ => None,
}) {
Some(x) => x,
None => {
tracing::warn!("no option is focused in autocomplete interaction");
return Ok(());
}
};
let parameters = &ctx.command().parameters;
let focused_parameter = parameters
.iter()
.find(|p| &p.name == focused_option_name)
.ok_or(crate::FrameworkError::CommandStructureMismatch {
ctx,
description: "focused autocomplete parameter name not recognized",
})?;
let autocomplete_callback = match focused_parameter.autocomplete_callback {
Some(a) => a,
_ => return Ok(()),
};
#[allow(unused_imports)]
use ::serenity::json::*;
let autocomplete_response = autocomplete_callback(ctx, partial_input).await;
if let Err(e) = ctx
.interaction
.create_response(
&ctx.framework.serenity_context,
serenity::CreateInteractionResponse::Autocomplete(autocomplete_response),
)
.await
{
tracing::warn!("couldn't send autocomplete response: {e}");
}
Ok(())
}
pub async fn dispatch_autocomplete<'a, U, E>(
framework: crate::FrameworkContext<'a, U, E>,
interaction: &'a serenity::CommandInteraction,
has_sent_initial_response: &'a std::sync::atomic::AtomicBool,
invocation_data: &'a tokio::sync::Mutex<Box<dyn std::any::Any + Send + Sync>>,
options: &'a [serenity::ResolvedOption<'a>],
command_tree: &'a mut Vec<&'a crate::Command<U, E>>,
) -> Result<(), crate::FrameworkError<'a, U, E>> {
let ctx = extract_command(
framework,
interaction,
crate::CommandInteractionType::Autocomplete,
has_sent_initial_response,
invocation_data,
options,
command_tree,
)?;
crate::catch_unwind_maybe(run_autocomplete(ctx))
.await
.map_err(|payload| crate::FrameworkError::CommandPanic {
payload,
ctx: ctx.into(),
})??;
Ok(())
}