mod register;
pub use register::*;
#[cfg(feature = "chrono")]
mod paginate;
#[cfg(feature = "chrono")]
pub use paginate::*;
use crate::{serenity::CreateAllowedMentions, serenity_prelude as serenity, CreateReply};
use std::fmt::{self, Display};
pub async fn on_error<U, E: Into<Box<dyn std::error::Error + Send + Sync>>>(
error: crate::FrameworkError<'_, U, E>,
) -> Result<(), serenity::Error> {
match error {
crate::FrameworkError::Setup { error, .. } => {
eprintln!("Error in user data setup: {}", display_error(error));
}
crate::FrameworkError::EventHandler { error, event, .. } => tracing::error!(
"User event event handler encountered an error on {} event: {}",
event.snake_case_name(),
display_error(error)
),
crate::FrameworkError::Command { ctx, error } => {
let error = display_error(error).to_string();
eprintln!("An error occured in a command: {}", error);
let mentions = CreateAllowedMentions::new()
.everyone(false)
.all_roles(false)
.all_users(false);
ctx.send(
CreateReply::default()
.content(error)
.allowed_mentions(mentions)
.ephemeral(true),
)
.await?;
}
crate::FrameworkError::SubcommandRequired { ctx } => {
let subcommands = ctx
.command()
.subcommands
.iter()
.map(|s| &*s.name)
.collect::<Vec<_>>();
let response = format!(
"You must specify one of the following subcommands: {}",
subcommands.join(", ")
);
ctx.send(CreateReply::default().content(response).ephemeral(true))
.await?;
}
crate::FrameworkError::CommandPanic { ctx, payload: _ } => {
let embed = serenity::CreateEmbed::default()
.title("Internal error")
.color((255, 0, 0))
.description("An unexpected internal error has occurred.");
ctx.send(CreateReply::default().embed(embed).ephemeral(true))
.await?;
}
crate::FrameworkError::ArgumentParse { ctx, input, error } => {
let usage = match &ctx.command().help_text {
Some(help_text) => &**help_text,
None => "Please check the help menu for usage information",
};
let response = if let Some(input) = input {
format!(
"**Cannot parse `{}` as argument: {}**\n{}",
input, error, usage
)
} else {
format!("**{}**\n{}", error, usage)
};
let mentions = CreateAllowedMentions::new()
.everyone(false)
.all_roles(false)
.all_users(false);
ctx.send(
CreateReply::default()
.content(response)
.allowed_mentions(mentions)
.ephemeral(true),
)
.await?;
}
crate::FrameworkError::CommandStructureMismatch { ctx, description } => {
tracing::error!(
"Error: failed to deserialize interaction arguments for `/{}`: {}",
ctx.command().name,
description,
);
}
crate::FrameworkError::CommandCheckFailed { ctx, error } => match error {
Some(error) => tracing::error!(
"A command check failed in command {} for user {}: {}",
ctx.command().name,
ctx.author().name,
display_error(error),
),
None => tracing::error!(
"A command check failed in command {} for user {}",
ctx.command().name,
ctx.author().name,
),
},
crate::FrameworkError::CooldownHit {
remaining_cooldown,
ctx,
} => {
let msg = format!(
"You're too fast. Please wait {} seconds before retrying",
remaining_cooldown.as_secs()
);
ctx.send(CreateReply::default().content(msg).ephemeral(true))
.await?;
}
crate::FrameworkError::MissingBotPermissions {
missing_permissions,
ctx,
} => {
let msg = format!(
"Command cannot be executed because the bot is lacking permissions: {}",
missing_permissions,
);
ctx.send(CreateReply::default().content(msg).ephemeral(true))
.await?;
}
crate::FrameworkError::MissingUserPermissions {
missing_permissions,
ctx,
} => {
let response = if let Some(missing_permissions) = missing_permissions {
format!(
"You're lacking permissions for `{}{}`: {}",
ctx.prefix(),
ctx.command().name,
missing_permissions,
)
} else {
format!(
"You may be lacking permissions for `{}{}`. Not executing for safety",
ctx.prefix(),
ctx.command().name,
)
};
ctx.send(CreateReply::default().content(response).ephemeral(true))
.await?;
}
crate::FrameworkError::PermissionFetchFailed { ctx } => {
ctx.say("An error occurred when fetching permissions.")
.await?;
}
crate::FrameworkError::NotAnOwner { ctx } => {
let response = "Only bot owners can call this command";
ctx.send(CreateReply::default().content(response).ephemeral(true))
.await?;
}
crate::FrameworkError::GuildOnly { ctx } => {
let response = "You cannot run this command in DMs.";
ctx.send(CreateReply::default().content(response).ephemeral(true))
.await?;
}
crate::FrameworkError::DmOnly { ctx } => {
let response = "You cannot run this command outside DMs.";
ctx.send(CreateReply::default().content(response).ephemeral(true))
.await?;
}
crate::FrameworkError::NsfwOnly { ctx } => {
let response = "You cannot run this command outside NSFW channels.";
ctx.send(CreateReply::default().content(response).ephemeral(true))
.await?;
}
crate::FrameworkError::DynamicPrefix { error, msg, .. } => {
tracing::error!(
"Dynamic prefix failed for message {:?}: {}",
msg.content,
display_error(error)
);
}
crate::FrameworkError::UnknownCommand {
msg, content_start, ..
} => {
let (prefix, msg_content) = msg.content.split_at(content_start.into());
tracing::warn!(
"Recognized prefix `{}`, but didn't recognize command name in `{}`",
prefix,
msg_content,
);
}
crate::FrameworkError::UnknownInteraction { interaction, .. } => {
tracing::warn!("received unknown interaction \"{}\"", interaction.data.name);
}
crate::FrameworkError::NonCommandMessage { error, .. } => {
tracing::warn!(
"error in non-command message handler: {}",
display_error(error)
);
}
crate::FrameworkError::__NonExhaustive(unreachable) => match unreachable {},
}
Ok(())
}
#[allow(clippy::unused_async)] pub async fn autocomplete_command<'a, U, E>(
ctx: crate::Context<'a, U, E>,
partial: &'a str,
) -> serenity::CreateAutocompleteResponse {
let commands = ctx.framework().options.commands.iter();
let filtered_commands = commands
.filter(|cmd| cmd.name.starts_with(partial))
.take(25);
let choices = filtered_commands
.map(|cmd| serenity::AutocompleteChoice::from(cmd.name.as_ref()))
.collect();
serenity::CreateAutocompleteResponse::new().set_choices(choices)
}
#[cfg(feature = "cache")]
pub async fn servers<U, E>(ctx: crate::Context<'_, U, E>) -> Result<(), serenity::Error> {
use std::fmt::Write as _;
let show_private_guilds = ctx.framework().options().owners.contains(&ctx.author().id);
let mut hidden_guilds = 0;
let mut hidden_guilds_members = 0;
let mut shown_guilds = Vec::<(String, u64)>::new();
for guild_id in ctx.cache().guilds() {
match ctx.cache().guild(guild_id) {
Some(guild) => {
let is_public = guild.features.iter().any(|x| x == "DISCOVERABLE");
if !is_public && !show_private_guilds {
hidden_guilds += 1; } else {
shown_guilds.push((guild.name.clone(), guild.member_count))
}
}
None => hidden_guilds += 1, }
}
shown_guilds.sort_by_key(|(_, member)| u64::MAX - member);
let mut response = format!(
"I am currently in {} servers!\n",
shown_guilds.len() + hidden_guilds
);
if show_private_guilds {
response.insert_str(0, "_Showing private guilds because you are a bot owner_\n");
}
let mut guilds = shown_guilds.into_iter().peekable();
while let Some((name, member_count)) = guilds.peek() {
let line = format!("- **{}** ({} members)\n", name, member_count);
if response.len() + line.len() > 1940 {
for (_remaining_guild_name, members) in guilds {
hidden_guilds += 1;
hidden_guilds_members += members;
}
break;
}
response += &line;
guilds.next(); }
if hidden_guilds > 0 {
let _ = writeln!(
response,
"- {} remaining servers with {} members total",
hidden_guilds, hidden_guilds_members
);
}
if response.len() > 2000 {
let mut truncate_at = 2000;
while !response.is_char_boundary(truncate_at) {
truncate_at -= 1;
}
response.truncate(truncate_at);
}
let reply = CreateReply::default()
.content(response)
.ephemeral(show_private_guilds);
ctx.send(reply).await?;
Ok(())
}
fn display_error(e: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> impl Display {
struct DisplayError(Box<dyn std::error::Error + Send + Sync>);
impl Display for DisplayError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut e: &(dyn std::error::Error + 'static) = &*self.0;
Display::fmt(e, f)?;
while let Some(new_e) = e.source() {
f.write_str(": ")?;
Display::fmt(new_e, f)?;
e = new_e;
}
Ok(())
}
}
DisplayError(e.into())
}