stoat-rs 0.2.5

Stoat API Wrapper
Documentation
use std::fmt::Debug;

use async_trait::async_trait;
use stoat_models::v0::Message;

use crate::{
    Error,
    builders::SendMessageBuilder,
    commands::{Command, Context, Converter, command::CommandHandle},
};

#[async_trait]
pub trait HelpCommand<
    E: From<Error> + Clone + Debug + Send + Sync + 'static,
    S: Debug + Clone + Send + Sync + 'static,
>: Debug + Send + Sync
{
    async fn create_global_help(
        &self,
        context: Context<E, S>,
        commands: Vec<Command<E, S>>,
        builder: &mut SendMessageBuilder,
    ) -> Result<(), E>;
    async fn create_command_help(
        &self,
        context: Context<E, S>,
        command: Command<E, S>,
        builder: &mut SendMessageBuilder,
    ) -> Result<(), E>;
    async fn create_group_help(
        &self,
        context: Context<E, S>,
        command: Command<E, S>,
        builder: &mut SendMessageBuilder,
    ) -> Result<(), E>;

    async fn filter_commands(
        &self,
        context: Context<E, S>,
        commands: Vec<Command<E, S>>,
    ) -> Result<Vec<Command<E, S>>, E> {
        let mut filtered = Vec::new();

        for command in commands {
            if command.hidden {
                continue;
            };

            if command.can_run(context.clone()).await.is_ok_and(|b| b) {
                filtered.push(command);
            };
        }

        Ok(filtered)
    }

    #[allow(unused_variables)]
    async fn send_help_command(
        &self,
        context: Context<E, S>,
        builder: SendMessageBuilder,
    ) -> Result<Message, E> {
        Ok(builder.build().await?)
    }

    #[allow(unused_variables)]
    async fn after_help_command(&self, context: Context<E, S>, message: Message) -> Result<(), E> {
        Ok(())
    }

    async fn get_channel(&self, context: Context<E, S>) -> Result<String, E> {
        Ok(context.message.channel.clone())
    }

    async fn no_command_found(
        &self,
        context: Context<E, S>,
        name: String,
        builder: &mut SendMessageBuilder,
    ) -> Result<(), E>;
}

#[derive(Debug)]
pub struct DefaultHelpCommand;

#[async_trait]
impl<
    E: From<Error> + Clone + Debug + Send + Sync + 'static,
    S: Debug + Clone + Send + Sync + 'static,
> HelpCommand<E, S> for DefaultHelpCommand
{
    async fn create_global_help(
        &self,
        _context: Context<E, S>,
        commands: Vec<Command<E, S>>,
        builder: &mut SendMessageBuilder,
    ) -> Result<(), E> {
        let mut lines = vec!["```".to_string()];

        for command in commands {
            lines.push(format!(
                "{} - {}",
                &command.name,
                command
                    .description
                    .as_ref()
                    .map(|desc| desc.split('\n').next().unwrap())
                    .unwrap_or("No description")
            ));
        }

        lines.push("```".to_string());

        builder.content(lines.join("\n"));

        Ok(())
    }

    async fn create_command_help(
        &self,
        context: Context<E, S>,
        command: Command<E, S>,
        builder: &mut SendMessageBuilder,
    ) -> Result<(), E> {
        let mut lines = vec!["```".to_string(), format!("{}:", &command.name)];

        let mut usage = command.parents.clone();
        usage.push(command.name.clone());
        usage.push(command.signature.clone().unwrap_or_default());

        lines.push(format!(
            "    Usage: {}{}",
            context.clean_prefix(),
            usage.join(" ")
        ));

        if !command.aliases.is_empty() {
            lines.push(format!("    Aliases: {}", command.aliases.join(", ")));
        }

        if let Some(description) = command.description.clone() {
            lines.push("".to_string());
            lines.push(description);
        }

        lines.push("```".to_string());

        builder.content(lines.join("\n"));

        Ok(())
    }

    async fn create_group_help(
        &self,
        context: Context<E, S>,
        command: Command<E, S>,
        builder: &mut SendMessageBuilder,
    ) -> Result<(), E> {
        let mut lines = vec!["```".to_string(), format!("{}:", &command.name)];

        let mut usage = command.parents.clone();
        usage.push(command.name.clone());
        usage.push(command.signature.clone().unwrap_or_default());

        lines.push(format!(
            "    Usage: {}{}",
            context.clean_prefix(),
            usage.join(" ")
        ));

        if !command.aliases.is_empty() {
            lines.push(format!("    Aliases: {}", command.aliases.join(", ")));
        }

        if let Some(description) = command.description.clone() {
            lines.push("".to_string());
            lines.push(description);
            lines.push("".to_string());
        }

        let children = self
            .filter_commands(context.clone(), command.children())
            .await?;

        if !children.is_empty() {
            lines.push("Commands:".to_string());
        };

        for command in children {
            lines.push(format!(
                "    {} - {}",
                &command.name,
                command
                    .description
                    .as_ref()
                    .map(|desc| desc.split('\n').next().unwrap())
                    .unwrap_or("No description")
            ));
        }

        lines.push("```".to_string());

        builder.content(lines.join("\n"));

        Ok(())
    }

    async fn no_command_found(
        &self,
        _context: Context<E, S>,
        name: String,
        builder: &mut SendMessageBuilder,
    ) -> Result<(), E> {
        builder.content(format!("Command `{name}` not found."));

        Ok(())
    }
}

#[derive(Clone)]
struct HelpCommandImpl;

#[async_trait]
impl<
    E: From<Error> + Clone + Debug + Send + Sync + 'static,
    S: Debug + Clone + Send + Sync + 'static,
> CommandHandle<(), E, S> for HelpCommandImpl
{
    async fn handle(&self, context: Context<E, S>) -> Result<(), E> {
        let args = Vec::<String>::from_context(&context).await?;

        let channel_id = context.help_command.get_channel(context.clone()).await?;
        let mut builder = SendMessageBuilder::new(context.http.clone(), channel_id);

        let commands = context
            .help_command
            .filter_commands(context.clone(), context.commands.get_commands())
            .await?;

        if args.is_empty() {
            context
                .help_command
                .create_global_help(context.clone(), commands, &mut builder)
                .await?;
        } else {
            if let Some(command) = context.commands.get_command_from_slice(&args) {
                if command.children.is_empty() {
                    context
                        .help_command
                        .create_command_help(context.clone(), command, &mut builder)
                        .await?;
                } else {
                    context
                        .help_command
                        .create_group_help(context.clone(), command, &mut builder)
                        .await?;
                }
            } else {
                context
                    .help_command
                    .no_command_found(context.clone(), args.join(" "), &mut builder)
                    .await?;
            }
        }

        let message = context
            .help_command
            .send_help_command(context.clone(), builder)
            .await?;
        context
            .help_command
            .after_help_command(context.clone(), message)
            .await?;

        Ok(())
    }
}

pub(crate) fn help_command<
    E: From<Error> + Clone + Debug + Send + Sync + 'static,
    S: Debug + Clone + Send + Sync + 'static,
>() -> Command<E, S> {
    Command::new("help", HelpCommandImpl)
        .signature("<command>")
        .description("Shows help for a command, group or all commands")
}