use crate::serenity_prelude as serenity;
async fn strip_prefix<'a, U, E>(
framework: crate::FrameworkContext<'a, U, E>,
ctx: &'a serenity::Context,
msg: &'a serenity::Message,
) -> Option<(&'a str, &'a str)> {
let partial_ctx = crate::PartialContext {
guild_id: msg.guild_id,
channel_id: msg.channel_id,
author: &msg.author,
serenity_context: ctx,
framework,
data: framework.user_data,
__non_exhaustive: (),
};
if let Some(dynamic_prefix) = framework.options.prefix_options.dynamic_prefix {
match dynamic_prefix(partial_ctx).await {
Ok(prefix) => {
if let Some(prefix) = prefix {
if msg.content.starts_with(&prefix) {
return Some(msg.content.split_at(prefix.len()));
}
}
}
Err(error) => {
(framework.options.on_error)(crate::FrameworkError::DynamicPrefix {
error,
ctx: partial_ctx,
msg,
})
.await;
}
}
}
if let Some(prefix) = &framework.options.prefix_options.prefix {
if let Some(content) = msg.content.strip_prefix(prefix) {
return Some((prefix, content));
}
}
if let Some((prefix, content)) = framework
.options
.prefix_options
.additional_prefixes
.iter()
.find_map(|prefix| match prefix {
&crate::Prefix::Literal(prefix) => Some((prefix, msg.content.strip_prefix(prefix)?)),
crate::Prefix::Regex(prefix) => {
let regex_match = prefix.find(&msg.content)?;
if regex_match.start() == 0 {
Some(msg.content.split_at(regex_match.end()))
} else {
None
}
}
crate::Prefix::__NonExhaustive => unreachable!(),
})
{
return Some((prefix, content));
}
if let Some(dynamic_prefix) = framework.options.prefix_options.stripped_dynamic_prefix {
match dynamic_prefix(ctx, msg, framework.user_data).await {
Ok(result) => {
if let Some((prefix, content)) = result {
return Some((prefix, content));
}
}
Err(error) => {
(framework.options.on_error)(crate::FrameworkError::DynamicPrefix {
error,
ctx: partial_ctx,
msg,
})
.await;
}
}
}
if framework.options.prefix_options.mention_as_prefix {
if let Some(stripped_content) = (|| {
msg.content
.strip_prefix("<@")?
.trim_start_matches('!')
.strip_prefix(&framework.bot_id.to_string())?
.strip_prefix('>')
})() {
let mention_prefix = &msg.content[..(msg.content.len() - stripped_content.len())];
return Some((mention_prefix, stripped_content));
}
}
None
}
pub fn find_command<'a, U, E>(
commands: &'a [crate::Command<U, E>],
remaining_message: &'a str,
case_insensitive: bool,
parent_commands: &mut Vec<&'a crate::Command<U, E>>,
) -> Option<(&'a crate::Command<U, E>, &'a str, &'a str)> {
let string_equal = if case_insensitive {
|a: &str, b: &str| a.eq_ignore_ascii_case(b)
} else {
|a: &str, b: &str| a == b
};
let (command_name, remaining_message) = {
let mut iter = remaining_message.splitn(2, char::is_whitespace);
(iter.next().unwrap(), iter.next().unwrap_or("").trim_start())
};
for command in commands {
let primary_name_matches = string_equal(&command.name, command_name);
let alias_matches = command
.aliases
.iter()
.any(|alias| string_equal(alias, command_name));
if !primary_name_matches && !alias_matches {
continue;
}
parent_commands.push(command);
return Some(
find_command(
&command.subcommands,
remaining_message,
case_insensitive,
parent_commands,
)
.unwrap_or_else(|| {
parent_commands.pop();
(command, command_name, remaining_message)
}),
);
}
None
}
pub async fn dispatch_message<'a, U: Send + Sync, E>(
framework: crate::FrameworkContext<'a, U, E>,
ctx: &'a serenity::Context,
msg: &'a serenity::Message,
trigger: crate::MessageDispatchTrigger,
invocation_data: &'a tokio::sync::Mutex<Box<dyn std::any::Any + Send + Sync>>,
parent_commands: &'a mut Vec<&'a crate::Command<U, E>>,
) -> Result<(), crate::FrameworkError<'a, U, E>> {
if let Some(ctx) = parse_invocation(
framework,
ctx,
msg,
trigger,
invocation_data,
parent_commands,
)
.await?
{
crate::catch_unwind_maybe(run_invocation(ctx))
.await
.map_err(|payload| crate::FrameworkError::CommandPanic {
payload,
ctx: ctx.into(),
})??;
} else if let Some(non_command_message) = framework.options.prefix_options.non_command_message {
non_command_message(&framework, ctx, msg)
.await
.map_err(|e| crate::FrameworkError::NonCommandMessage {
error: e,
ctx,
framework,
msg,
})?;
}
Ok(())
}
pub async fn parse_invocation<'a, U: Send + Sync, E>(
framework: crate::FrameworkContext<'a, U, E>,
ctx: &'a serenity::Context,
msg: &'a serenity::Message,
trigger: crate::MessageDispatchTrigger,
invocation_data: &'a tokio::sync::Mutex<Box<dyn std::any::Any + Send + Sync>>,
parent_commands: &'a mut Vec<&'a crate::Command<U, E>>,
) -> Result<Option<crate::PrefixContext<'a, U, E>>, crate::FrameworkError<'a, U, E>> {
if msg.author.bot && framework.options.prefix_options.ignore_bots {
return Ok(None);
}
if framework.bot_id == msg.author.id && !framework.options.prefix_options.execute_self_messages
{
return Ok(None);
}
if msg.kind == serenity::MessageType::ThreadCreated
&& framework.options.prefix_options.ignore_thread_creation
{
return Ok(None);
}
let (prefix, msg_content) = match strip_prefix(framework, ctx, msg).await {
Some(x) => x,
None => return Ok(None),
};
let msg_content = msg_content.trim_start();
let (command, invoked_command_name, args) = find_command(
&framework.options.commands,
msg_content,
framework.options.prefix_options.case_insensitive_commands,
parent_commands,
)
.ok_or(crate::FrameworkError::UnknownCommand {
ctx,
msg,
prefix,
msg_content,
framework,
invocation_data,
trigger,
})?;
let action = match command.prefix_action {
Some(x) => x,
None => return Ok(None),
};
Ok(Some(crate::PrefixContext {
serenity_context: ctx,
msg,
prefix,
invoked_command_name,
args,
framework,
data: framework.user_data,
parent_commands,
command,
invocation_data,
trigger,
action,
__non_exhaustive: (),
}))
}
pub async fn run_invocation<U, E>(
ctx: crate::PrefixContext<'_, U, E>,
) -> Result<(), crate::FrameworkError<'_, U, E>> {
if ctx.trigger == crate::MessageDispatchTrigger::MessageEdit && !ctx.command.invoke_on_edit {
return Ok(());
}
if ctx.trigger == crate::MessageDispatchTrigger::MessageEditFromInvalid
&& !ctx.framework.options.prefix_options.execute_untracked_edits
{
return Ok(());
}
if ctx.command.subcommand_required {
return Err(crate::FrameworkError::SubcommandRequired {
ctx: crate::Context::Prefix(ctx),
});
}
super::common::check_permissions_and_cooldown(ctx.into()).await?;
let _typing_broadcaster = if ctx.command.broadcast_typing {
Some(ctx.msg.channel_id.start_typing(&ctx.serenity_context.http))
} else {
None
};
(ctx.framework.options.pre_command)(crate::Context::Prefix(ctx)).await;
if let Some(edit_tracker) = &ctx.framework.options.prefix_options.edit_tracker {
edit_tracker
.write()
.unwrap()
.track_command(ctx.msg, ctx.command.track_deletion);
}
(ctx.action)(ctx).await?;
(ctx.framework.options.post_command)(crate::Context::Prefix(ctx)).await;
Ok(())
}