use std::convert::TryInto as _;
use crate::serenity_prelude as serenity;
fn prefix_len_to_u16(prefix: &str) -> u16 {
prefix
.len()
.try_into()
.expect("messages should not be more than 64k bytes, let alone a prefix")
}
async fn strip_prefix<'a, U, E>(
framework: crate::FrameworkContext<'a, U, E>,
msg: &'a serenity::Message,
) -> Option<u16> {
let partial_ctx = crate::PartialContext {
guild_id: msg.guild_id,
channel_id: msg.channel_id,
author: &msg.author,
framework,
__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.as_ref()) {
return Some(prefix_len_to_u16(&prefix));
}
}
}
Err(error) => {
(framework.options.on_error)(crate::FrameworkError::DynamicPrefix {
error,
ctx: partial_ctx,
msg,
})
.await;
}
}
}
if let Some(prefix) = framework.options.prefix_options.prefix.as_deref() {
if msg.content.starts_with(prefix) {
return Some(prefix_len_to_u16(prefix));
}
}
if let Some(prefix) = framework
.options
.prefix_options
.additional_prefixes
.iter()
.find_map(|prefix| match prefix {
&crate::Prefix::Literal(prefix) => Some(prefix),
crate::Prefix::Regex(prefix) => {
let regex_match = prefix.find(&msg.content)?;
if regex_match.start() == 0 {
Some(&msg.content[..regex_match.end()])
} else {
None
}
}
crate::Prefix::__NonExhaustive => unreachable!(),
})
{
return Some(prefix_len_to_u16(prefix));
}
if let Some(dynamic_prefix) = framework.options.prefix_options.stripped_dynamic_prefix {
match dynamic_prefix(framework.serenity_context, msg, framework.user_data).await {
Ok(Some(prefix)) => return Some(prefix_len_to_u16(prefix)),
Ok(None) => {}
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(prefix_len_to_u16(mention_prefix));
}
}
None
}
pub fn find_command<'a, U, E>(
commands: &'a [crate::Command<U, E>],
remaining_message: &'a str,
case_insensitive: bool,
command_tree: &mut Vec<&'a crate::Command<U, E>>,
) -> Option<(&'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;
}
command_tree.push(command);
return Some(
find_command(
&command.subcommands,
remaining_message,
case_insensitive,
command_tree,
)
.unwrap_or((command_name, remaining_message)),
);
}
None
}
pub async fn dispatch_message<'a, U: Send + Sync, E>(
framework: crate::FrameworkContext<'a, U, E>,
msg: &'a serenity::Message,
trigger: crate::MessageDispatchTrigger,
invocation_data: &'a tokio::sync::Mutex<Box<dyn std::any::Any + Send + Sync>>,
command_tree: &'a mut Vec<&'a crate::Command<U, E>>,
) -> Result<(), crate::FrameworkError<'a, U, E>> {
if let Some(ctx) =
parse_invocation(framework, msg, trigger, invocation_data, command_tree).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, msg).await.map_err(|e| {
crate::FrameworkError::NonCommandMessage {
error: e,
framework,
msg,
}
})?;
}
Ok(())
}
pub async fn parse_invocation<'a, U: Send + Sync, E>(
framework: crate::FrameworkContext<'a, U, E>,
msg: &'a serenity::Message,
trigger: crate::MessageDispatchTrigger,
invocation_data: &'a tokio::sync::Mutex<Box<dyn std::any::Any + Send + Sync>>,
command_tree: &'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 Some(content_start) = strip_prefix(framework, msg).await else {
return Ok(None);
};
let msg_content = msg.content[content_start.into()..].trim_start();
let (invoked_command_name, args) = find_command(
&framework.options.commands,
msg_content,
framework.options.prefix_options.case_insensitive_commands,
command_tree,
)
.ok_or(crate::FrameworkError::UnknownCommand {
msg,
content_start,
framework,
invocation_data,
trigger,
})?;
if command_tree
.last()
.is_none_or(|c| c.prefix_action.is_none())
{
return Ok(None);
}
Ok(Some(crate::PrefixContext {
msg,
content_start,
invoked_command_name,
args,
framework,
command_tree,
invocation_data,
trigger,
__non_exhaustive: (),
}))
}
pub async fn run_invocation<U, E>(
ctx: crate::PrefixContext<'_, U, E>,
) -> Result<(), crate::FrameworkError<'_, U, E>> {
let command = ctx.command();
let Some(prefix_action) = command.prefix_action else {
return Ok(());
};
if ctx.trigger == crate::MessageDispatchTrigger::MessageEdit && !command.invoke_on_edit {
return Ok(());
}
if ctx.trigger == crate::MessageDispatchTrigger::MessageEditFromInvalid
&& !ctx.framework.options.prefix_options.execute_untracked_edits
{
return Ok(());
}
if 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 command.broadcast_typing {
Some(
ctx.msg
.channel_id
.start_typing(&ctx.framework.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, command.track_deletion);
}
(prefix_action)(ctx).await?;
(ctx.framework.options.post_command)(crate::Context::Prefix(ctx)).await;
Ok(())
}