use crate::serenity_prelude as serenity;
fn find_matching_command<'a, 'b, U, E>(
interaction_name: &str,
interaction_options: &'b [serenity::ResolvedOption<'b>],
commands: &'a [crate::Command<U, E>],
parent_commands: &mut Vec<&'a crate::Command<U, E>>,
) -> Option<(&'a crate::Command<U, E>, &'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;
}
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,
})
{
parent_commands.push(cmd);
find_matching_command(sub_name, sub_interaction, &cmd.subcommands, parent_commands)
} else {
Some((cmd, interaction_options))
}
})
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::result_large_err)] fn extract_command<'a, U, E>(
framework: crate::FrameworkContext<'a, U, E>,
ctx: &'a serenity::Context,
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>],
parent_commands: &'a mut Vec<&'a crate::Command<U, E>>,
) -> Result<crate::ApplicationContext<'a, U, E>, crate::FrameworkError<'a, U, E>> {
let search_result = find_matching_command(
&interaction.data.name,
options,
&framework.options.commands,
parent_commands,
);
let (command, leaf_interaction_options) =
search_result.ok_or(crate::FrameworkError::UnknownInteraction {
ctx,
framework,
interaction,
})?;
Ok(crate::ApplicationContext {
data: framework.user_data,
serenity_context: ctx,
framework,
interaction,
interaction_type,
args: leaf_interaction_options,
command,
parent_commands,
has_sent_initial_response,
invocation_data,
__non_exhaustive: (),
})
}
#[allow(clippy::too_many_arguments)] pub async fn extract_command_and_run_checks<'a, U, E>(
framework: crate::FrameworkContext<'a, U, E>,
ctx: &'a serenity::Context,
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>],
parent_commands: &'a mut Vec<&'a crate::Command<U, E>>,
) -> Result<crate::ApplicationContext<'a, U, E>, crate::FrameworkError<'a, U, E>> {
let ctx = extract_command(
framework,
ctx,
interaction,
interaction_type,
has_sent_initial_response,
invocation_data,
options,
parent_commands,
)?;
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 action_result = match ctx.interaction.data.kind {
serenity::CommandType::ChatInput => {
let action = ctx
.command
.slash_action
.ok_or(command_structure_mismatch_error)?;
action(ctx).await
}
serenity::CommandType::User => {
match (
ctx.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 (
ctx.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>,
ctx: &'a serenity::Context,
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>],
parent_commands: &'a mut Vec<&'a crate::Command<U, E>>,
) -> Result<(), crate::FrameworkError<'a, U, E>> {
let ctx = extract_command(
framework,
ctx,
interaction,
crate::CommandInteractionType::Command,
has_sent_initial_response,
invocation_data,
options,
parent_commands,
)?;
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 = match autocomplete_callback(ctx, partial_input).await {
Ok(x) => x,
Err(e) => {
tracing::warn!("couldn't generate autocomplete response: {e}");
return Ok(());
}
};
if let Err(e) = ctx
.interaction
.create_response(
&ctx.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>,
ctx: &'a serenity::Context,
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>],
parent_commands: &'a mut Vec<&'a crate::Command<U, E>>,
) -> Result<(), crate::FrameworkError<'a, U, E>> {
let ctx = extract_command(
framework,
ctx,
interaction,
crate::CommandInteractionType::Autocomplete,
has_sent_initial_response,
invocation_data,
options,
parent_commands,
)?;
crate::catch_unwind_maybe(run_autocomplete(ctx))
.await
.map_err(|payload| crate::FrameworkError::CommandPanic {
payload,
ctx: ctx.into(),
})??;
Ok(())
}