Add help command
This commit is contained in:
@@ -16,6 +16,7 @@ secrecy = "0.10.3"
|
||||
serenity = "0.12.4"
|
||||
thiserror = "2.0.11"
|
||||
tokio = { version = "1.43.0", features = ["full"] }
|
||||
futures = "0.3.31"
|
||||
|
||||
[features]
|
||||
default = ["mysql", "postgres", "sqlite"]
|
||||
|
||||
@@ -19,6 +19,7 @@ where
|
||||
|
||||
let app_data = AppData {
|
||||
repository_provider,
|
||||
qualified_command_names: commands::qualified_command_names(&commands),
|
||||
};
|
||||
|
||||
let options = FrameworkOptions::<AppData<R>, AppError<R::BackendError>> {
|
||||
|
||||
@@ -18,6 +18,7 @@ pub enum AppStartError {
|
||||
|
||||
pub struct AppData<R> {
|
||||
repository_provider: R,
|
||||
qualified_command_names: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -61,3 +62,9 @@ where
|
||||
self.repository_provider.get().await
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> AppData<R> {
|
||||
pub fn qualified_command_names(&self) -> &[String] {
|
||||
&self.qualified_command_names
|
||||
}
|
||||
}
|
||||
|
||||
173
cipher_discord_bot/src/commands/help.rs
Normal file
173
cipher_discord_bot/src/commands/help.rs
Normal file
@@ -0,0 +1,173 @@
|
||||
use futures::Stream;
|
||||
use cipher_core::repository::RepositoryProvider;
|
||||
use poise::CreateReply;
|
||||
use serenity::all::Color;
|
||||
use serenity::all::CreateEmbed;
|
||||
use serenity::futures::StreamExt;
|
||||
|
||||
use crate::app::AppCommand;
|
||||
use crate::app::AppContext;
|
||||
use crate::app::AppError;
|
||||
|
||||
async fn autocomplete_command<'a, R> (
|
||||
ctx: AppContext<'a, R, R::BackendError>,
|
||||
partial: &'a str,
|
||||
) -> impl Stream<Item = String> + 'a
|
||||
where
|
||||
R: RepositoryProvider,
|
||||
{
|
||||
futures::stream::iter(ctx.data().qualified_command_names())
|
||||
.filter(move |name| futures::future::ready(name.contains(partial)))
|
||||
.map(|name| name.to_string())
|
||||
}
|
||||
|
||||
/// Show help message.
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn help<R: RepositoryProvider + Sync>(
|
||||
ctx: AppContext<'_, R, R::BackendError>,
|
||||
#[rename = "command"]
|
||||
#[description = "Command to get help for."]
|
||||
#[autocomplete = "autocomplete_command"]
|
||||
option_query: Option<String>,
|
||||
#[description = "Show hidden commands. Defaults to False."] all: Option<bool>,
|
||||
#[description = "Hide reply from other users. Defaults to True."] ephemeral: Option<bool>,
|
||||
) -> Result<(), AppError<R::BackendError>> {
|
||||
let embed = if let Some(query) = &option_query {
|
||||
let option_command = poise::find_command(
|
||||
&ctx.framework().options.commands,
|
||||
query,
|
||||
true,
|
||||
&mut Vec::new(),
|
||||
);
|
||||
|
||||
if let Some((command, _, _)) = option_command {
|
||||
command_help_embed(command, all.unwrap_or(false))
|
||||
} else {
|
||||
CreateEmbed::new()
|
||||
.title("Help")
|
||||
.description(format!("Could not find command `/{}`", query))
|
||||
.color(Color::BLURPLE)
|
||||
}
|
||||
} else {
|
||||
root_help_embed(&ctx, all.unwrap_or(false))
|
||||
};
|
||||
|
||||
let reply = CreateReply::default()
|
||||
.embed(embed)
|
||||
.ephemeral(ephemeral.unwrap_or(true));
|
||||
|
||||
ctx.send(reply).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn root_help_embed<R>(ctx: &AppContext<'_, R, R::BackendError>, all: bool) -> CreateEmbed
|
||||
where
|
||||
R: RepositoryProvider,
|
||||
{
|
||||
let mut commands_field_value = String::new();
|
||||
for command in &ctx.framework().options.commands {
|
||||
if !all && command.hide_in_help {
|
||||
continue;
|
||||
}
|
||||
|
||||
commands_field_value.push('`');
|
||||
commands_field_value.push('/');
|
||||
commands_field_value.push_str(&command.name);
|
||||
commands_field_value.push('`');
|
||||
|
||||
if let Some(command_description) = &command.description {
|
||||
commands_field_value.push_str(" - ");
|
||||
commands_field_value.push_str(command_description);
|
||||
}
|
||||
|
||||
commands_field_value.push('\n');
|
||||
}
|
||||
commands_field_value.pop();
|
||||
|
||||
let mut embed = CreateEmbed::new()
|
||||
.title("Help")
|
||||
.color(Color::BLURPLE);
|
||||
|
||||
if !commands_field_value.is_empty() {
|
||||
embed = embed.field("Commands", commands_field_value, false);
|
||||
}
|
||||
|
||||
embed = embed.field("More", "For information on specific commands `/help <command>`.", false);
|
||||
|
||||
embed
|
||||
}
|
||||
|
||||
fn command_help_embed<R>(command: &AppCommand<R, R::BackendError>, all: bool) -> CreateEmbed
|
||||
where
|
||||
R: RepositoryProvider,
|
||||
{
|
||||
let mut required_parameters_field_value = String::new();
|
||||
let mut optional_parameters_field_value = String::new();
|
||||
for parameter in &command.parameters {
|
||||
let parameters_field_value = match parameter.required {
|
||||
true => &mut required_parameters_field_value,
|
||||
false => &mut optional_parameters_field_value,
|
||||
};
|
||||
|
||||
parameters_field_value.push('`');
|
||||
parameters_field_value.push_str(¶meter.name);
|
||||
parameters_field_value.push('`');
|
||||
|
||||
if let Some(parameter_description) = ¶meter.description {
|
||||
parameters_field_value.push_str(" - ");
|
||||
parameters_field_value.push_str(parameter_description);
|
||||
}
|
||||
|
||||
parameters_field_value.push('\n');
|
||||
}
|
||||
required_parameters_field_value.pop(); // Removes final newline
|
||||
optional_parameters_field_value.pop(); // Removes final newline
|
||||
|
||||
let mut subcommands_field_value = String::new();
|
||||
for subcommand in &command.subcommands {
|
||||
if !all && subcommand.hide_in_help {
|
||||
continue;
|
||||
}
|
||||
|
||||
subcommands_field_value.push('`');
|
||||
subcommands_field_value.push_str(&subcommand.name);
|
||||
subcommands_field_value.push('`');
|
||||
|
||||
if let Some(command_description) = &subcommand.description {
|
||||
subcommands_field_value.push_str(" - ");
|
||||
subcommands_field_value.push_str(command_description);
|
||||
}
|
||||
|
||||
subcommands_field_value.push('\n');
|
||||
}
|
||||
subcommands_field_value.pop(); // Removes final newline
|
||||
|
||||
let mut embed = CreateEmbed::new()
|
||||
.title(format!("Help `/{}`", command.qualified_name))
|
||||
.color(Color::BLURPLE);
|
||||
|
||||
if let Some(category) = &command.category {
|
||||
embed = embed.field("Category", category, false);
|
||||
}
|
||||
|
||||
if !required_parameters_field_value.is_empty() {
|
||||
embed = embed.field("Required Parameters", required_parameters_field_value, false);
|
||||
}
|
||||
|
||||
if !optional_parameters_field_value.is_empty() {
|
||||
embed = embed.field("Optional Parameters", optional_parameters_field_value, false);
|
||||
}
|
||||
|
||||
if !subcommands_field_value.is_empty() {
|
||||
embed = embed.field("Subcommands", subcommands_field_value, false);
|
||||
}
|
||||
|
||||
if let Some(description) = &command.description {
|
||||
embed = embed.description(description);
|
||||
}
|
||||
|
||||
embed = embed.field("More", "For information on specific commands `/help <command>`.", false);
|
||||
|
||||
embed
|
||||
}
|
||||
@@ -2,6 +2,7 @@ use cipher_core::repository::RepositoryProvider;
|
||||
|
||||
use crate::app::AppCommand;
|
||||
|
||||
mod help;
|
||||
mod ping;
|
||||
mod profile;
|
||||
|
||||
@@ -10,7 +11,35 @@ where
|
||||
R: RepositoryProvider + Send + Sync + 'static,
|
||||
{
|
||||
vec![
|
||||
help::help(),
|
||||
ping::ping(),
|
||||
profile::profile(),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn qualified_command_names<R>(commands: &[AppCommand<R, R::BackendError>]) -> Vec<String>
|
||||
where
|
||||
R: RepositoryProvider,
|
||||
{
|
||||
let mut prefix = String::new();
|
||||
let mut names = Vec::new();
|
||||
qualified_command_names_inner(&mut prefix, commands, &mut names);
|
||||
names
|
||||
}
|
||||
|
||||
fn qualified_command_names_inner<R>(prefix: &mut String, commands: &[AppCommand<R, R::BackendError>], names: &mut Vec<String>)
|
||||
where
|
||||
R: RepositoryProvider,
|
||||
{
|
||||
for command in commands {
|
||||
if command.subcommands.is_empty() {
|
||||
names.push(format!("{}{}", prefix, command.qualified_name.clone()));
|
||||
} else {
|
||||
let old_len = prefix.len();
|
||||
prefix.push_str(&command.qualified_name);
|
||||
prefix.push(' ');
|
||||
qualified_command_names_inner(prefix, &command.subcommands, names);
|
||||
prefix.truncate(old_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user