From e2c98386db47dbdeb39403882c8b48e4edab7ae2 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 16 Feb 2026 15:26:17 +0100 Subject: [PATCH] feat(commands/utils): adding the help command - This command will be to managed --- src/commands/utils/help.rs | 215 +++++++++++++++++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 src/commands/utils/help.rs diff --git a/src/commands/utils/help.rs b/src/commands/utils/help.rs new file mode 100644 index 0000000..ab850fc --- /dev/null +++ b/src/commands/utils/help.rs @@ -0,0 +1,215 @@ +use std::{collections::HashMap, sync::atomic::AtomicU64, time::Duration}; +use serenity::{ + all::{ + ComponentInteractionCollector, + CreateActionRow, + CreateEmbed, + CreateInteractionResponse, + CreateInteractionResponseMessage, + EditInteractionResponse, + GuildId, + InteractionContext, + MessageId, + Message, + CreateCommand, + ButtonStyle, + ReactionType, + CreateButton, + CommandInteraction, + Context, + Command, + UserId, + }, + futures::StreamExt, +}; +use sqlx::PgPool; +use crate::{ + commands::{ + CommandCategory, + CommandEntry, + SlashCommand + }, + config::EmojiConfig, + database::guild, + models::DbGuild, +}; + +pub struct Help { + pub command_id: AtomicU64, +} + +impl Help { + pub fn new() -> Self { + Self { + command_id: AtomicU64::new(0), + } + } +} + +fn create_category_buttons(categories: &HashMap>, disabled: bool) -> Vec { + let buttons: Vec = categories + .keys() + .map(|cat| { + CreateButton::new(format!("help_{}", cat.name().to_lowercase())) + .label(cat.name()) + .emoji(ReactionType::Unicode(cat.emoji().to_string())) + .style(ButtonStyle::Primary) + .disabled(disabled) + }) + .collect(); + + buttons + .chunks(5) + .map(|chunk: &[CreateButton]| CreateActionRow::Buttons(chunk.to_vec())) + .collect() +} + +fn back_button() -> CreateActionRow { + CreateActionRow::Buttons(vec![ + CreateButton::new("help_menu") + .label("Back to Home") + .emoji(ReactionType::Unicode("🏠".to_string())) + .style(ButtonStyle::Secondary) + ]) +} + +fn build_menu_embed(description: &str, color: u32) -> CreateEmbed { + CreateEmbed::new() + .title("Commands") + .description(description) + .color(color) +} + +fn build_category_embed(category: &CommandCategory, commands: &[String], color: u32) -> CreateEmbed { + CreateEmbed::new() + .title(format!("{} | {}", category.emoji(), category.name())) + .description(commands.join("\n")) + .color(color) +} + +#[serenity::async_trait] +impl SlashCommand for Help { + fn name(&self) -> &'static str { + "help" + } + + fn description(&self) -> &'static str { + "List all available commands" + } + + fn category(&self) -> &'static CommandCategory { + &CommandCategory::Utils + } + + fn command_id_ref(&self) -> &AtomicU64 { + &self.command_id + } + + fn register(&self) -> CreateCommand { + println!("\t✅ | {}", self.name()); + CreateCommand::new(self.name()) + .description(self.description()) + .contexts(vec![ + InteractionContext::Guild, + ]) + } + + async fn run( + &self, + ctx: &Context, + command: &CommandInteraction, + _database: &PgPool, + _emoji: &EmojiConfig, + ) -> Result<(), serenity::Error> { + let guild: GuildId = command.guild_id.ok_or(serenity::Error::Other("Commande non disponible en DM"))?; + let guild_id: String = guild.to_string(); + let guild_db: Option = guild::get(_database, &guild_id).await.expect("Error guild::get"); + let color: u32 = guild_db.unwrap().color as u32; + + let registered_cmds: Vec = ctx.http.get_global_commands().await?; + let cmd_ids: HashMap = registered_cmds + .into_iter() + .map(|c| (c.name, c.id.get())) + .collect(); + let mut categories: HashMap> = HashMap::new(); + + for entry in inventory::iter:: { + let cmd: Box = (entry.create)(); + let id: u64 = cmd_ids.get(cmd.name()).copied().unwrap_or(0); + + let line: String = if id != 0 { + format!(" — {}", cmd.name(), id, cmd.description()) + } else { + format!("`/{}` — {}", cmd.name(), cmd.description()) + }; + + categories + .entry(*cmd.category()) + .or_insert_with(Vec::new) + .push(line); + } + + let description: &str = "Welcome to this help command.\nThe buttons below will allow you to navigate through the different categories."; + let menu_embed: CreateEmbed = build_menu_embed(description, color); + let menu_buttons: Vec = create_category_buttons(&categories, false); + + let response: CreateInteractionResponse = CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new() + .embed(menu_embed) + .components(menu_buttons) + .ephemeral(true) + ); + + command.create_response(&ctx.http, response).await?; + + let msg: Message = command.get_response(&ctx.http).await?; + let msg_id: MessageId = msg.id; + let author_id: UserId = command.user.id; + + let mut collector = ComponentInteractionCollector::new(&ctx.shard) + .filter(move |e| e.message.id == msg_id && e.user.id == author_id) + .timeout(Duration::from_secs(120)) + .stream(); + + while let Some(click) = collector.next().await { + let custom_id: &str = click.data.custom_id.as_str(); + + if custom_id == "help_menu" { + let menu_embed: CreateEmbed = build_menu_embed(description, color); + let menu_buttons: Vec = create_category_buttons(&categories, false); + + click.create_response(&ctx.http, CreateInteractionResponse::UpdateMessage( + CreateInteractionResponseMessage::new() + .embed(menu_embed) + .components(menu_buttons) + )).await?; + continue; + } + + let matched_category = categories.iter().find(|(cat, _)| { + format!("help_{}", cat.name().to_lowercase()) == custom_id + }); + + if let Some((category, commands)) = matched_category { + let cat_embed: CreateEmbed = build_category_embed(category, commands, color); + + let components: Vec = vec![back_button()]; + click.create_response(&ctx.http, CreateInteractionResponse::UpdateMessage( + CreateInteractionResponseMessage::new() + .embed(cat_embed) + .components(components) + )).await?; + } + } + let disabled_buttons: Vec = create_category_buttons(&categories, true); + command.edit_response(&ctx.http, + EditInteractionResponse::new().components(disabled_buttons) + ).await?; + + Ok(()) + } +} + +inventory::submit! { + CommandEntry { create: || Box::new(Help::new()) } +}