Skip to content

Prefixed help command

attrs class PrefixedHelpCommand

A help command for all prefixed commands in a bot.

Attr attributes:

Name Type Description
client Client

The client to use for the help command.

show_hidden bool

Should hidden commands be shown?

show_disabled bool

Should disabled commands be shown?

run_checks bool

Should only commands that's checks pass be shown?

show_self bool

Should this command be shown in the help message?

show_usage bool

Should usage for commands be shown?

show_aliases bool

Should aliases for commands be shown?

show_prefix bool

Should the prefix be shown?

embed_color Color

The colour to show in the Embeds.

embed_title str

The title to use in the embed. {username} will be replaced by the client's username.

not_found_message str

The message to send when a command was not found. {cmd_name} will be replaced by the requested command.

fallback_help_string str

The text to display when a command does not have a help string defined.

fallback_brief_string str

The text to display when a command does not have a brief string defined.

Source code in naff/ext/prefixed_help.py
@attrs.define(slots=True)
class PrefixedHelpCommand:
    """A help command for all prefixed commands in a bot."""

    client: "Client" = attrs.field()
    """The client to use for the help command."""

    show_hidden: bool = attrs.field(default=False, kw_only=True)
    """Should hidden commands be shown?"""
    show_disabled: bool = attrs.field(default=False, kw_only=True)
    """Should disabled commands be shown?"""
    run_checks: bool = attrs.field(default=False, kw_only=True)
    """Should only commands that's checks pass be shown?"""
    show_self: bool = attrs.field(default=False, kw_only=True)
    """Should this command be shown in the help message?"""
    show_usage: bool = attrs.field(default=False, kw_only=True)
    """Should usage for commands be shown?"""
    show_aliases: bool = attrs.field(default=False, kw_only=True)
    """Should aliases for commands be shown?"""
    show_prefix: bool = attrs.field(default=False, kw_only=True)
    """Should the prefix be shown?"""
    embed_color: Color = attrs.field(default=BrandColors.BLURPLE, kw_only=True)
    """The colour to show in the Embeds."""

    embed_title: str = attrs.field(default="{username} Help Command", kw_only=True)
    """The title to use in the embed. {username} will be replaced by the client's username."""
    not_found_message: str = attrs.field(default="Sorry! No command called `{cmd_name}` was found.", kw_only=True)
    """The message to send when a command was not found. {cmd_name} will be replaced by the requested command."""
    fallback_help_string: str = attrs.field(default="No help message available.", kw_only=True)
    """The text to display when a command does not have a help string defined."""
    fallback_brief_string: str = attrs.field(default="No help message available.", kw_only=True)
    """The text to display when a command does not have a brief string defined."""

    _cmd: PrefixedCommand = attrs.field(init=False, default=None)

    def __attrs_post_init__(self) -> None:
        if not self._cmd:
            self._cmd = self._callback  # type: ignore

    def register(self) -> None:
        """Registers the help command."""
        if not isinstance(self._cmd.callback, functools.partial):
            # prevent wrap-nesting
            self._cmd.callback = functools.partial(self._cmd.callback, self)

        # replace existing help command if found
        if "help" in self.client.prefixed_commands:
            logger.warning("Replacing existing help command.")
            del self.client.prefixed_commands["help"]

        self.client.add_prefixed_command(self._cmd)  # type: ignore

    async def send_help(self, ctx: PrefixedContext, cmd_name: str | None) -> None:
        """
        Send a help message to the given context.

        Args:
            ctx: The context to use.
            cmd_name: An optional command name to send help for.
        """
        await self._callback.callback(ctx, cmd_name)  # type: ignore

    @prefixed_command(name="help")
    async def _callback(self, ctx: PrefixedContext, *, cmd_name: str = None) -> None:
        if cmd_name:
            return await self._help_specific(ctx, cmd_name)
        await self._help_list(ctx)

    async def _help_list(self, ctx: PrefixedContext) -> None:
        cmds = await self._gather(ctx)

        output = []
        for cmd in cmds.values():
            _temp = self._generate_command_string(cmd, ctx)
            _temp += f"\n{cmd.brief or self.fallback_brief_string}"

            output.append(self._sanitise_mentions(_temp))
        if len("\n".join(output)) > 500:
            paginator = Paginator.create_from_list(self.client, output, page_size=500)
            paginator.default_color = self.embed_color
            paginator.default_title = self.embed_title.format(username=self.client.user.username)
            await paginator.send(ctx)
        else:
            embed = Embed(
                title=self.embed_title.format(username=self.client.user.username),
                description="\n".join(output),
                color=self.embed_color,
            )
            await ctx.reply(embeds=embed)

    async def _help_specific(self, ctx: PrefixedContext, cmd_name: str) -> None:
        cmds = await self._gather(ctx)

        if cmd := cmds.get(cmd_name.lower()):
            _temp = self._generate_command_string(cmd, ctx)
            _temp += f"\n{cmd.help or self.fallback_help_string}"
            await ctx.reply(self._sanitise_mentions(_temp))
        else:
            await ctx.reply(self.not_found_message.format(cmd_name=cmd_name))

    async def _gather(self, ctx: PrefixedContext | None = None) -> dict[str, PrefixedCommand]:
        """
        Gather commands based on the rules set out in the class attributes.

        Args:
            ctx: The context to use to establish usability.
        Returns:
            dict[str, PrefixedCommand]: A list of commands fit the class attribute configuration.
        """
        out: dict[str, PrefixedCommand] = {}

        for cmd in frozenset(self.client.prefixed_commands.values()):
            if not cmd.enabled and not self.show_disabled:
                continue

            if cmd == self._cmd and not self.show_self:
                continue

            if cmd.hidden and not self.show_hidden:
                continue

            if ctx and cmd.checks and not self.run_checks:
                # cmd._can_run would check the cooldowns, we don't want that so manual calling is required
                for _c in cmd.checks:
                    if not await _c(ctx):
                        continue

                if cmd.extension and cmd.extension.extension_checks:
                    for _c in cmd.extension.extension_checks:
                        if not await _c(ctx):
                            continue

            out[cmd.qualified_name] = cmd

        return out

    def _sanitise_mentions(self, text: str) -> str:
        """
        Replace mentions with a format that won't ping or look weird in code blocks.

        args:
            The text to sanitise.
        """
        mappings = {
            "@everyone": "@\u200beveryone",
            "@here": "@\u200bhere",
            f"<@{self.client.user.id}>": f"@{self.client.user.username}",
            f"<@!{self.client.user.id}>": f"@{self.client.user.username}",
        }
        for source, target in mappings.items():
            text = text.replace(source, target)

        return text

    def _generate_command_string(self, cmd: PrefixedCommand, ctx: PrefixedContext) -> str:
        """
        Generate a string based on a command, class attributes, and the context.

        args:
            cmd: The command in question.
            ctx: The context for this command.
        """
        _temp = f"`{ctx.prefix if self.show_prefix else ''}{cmd.qualified_name}`"

        if cmd.aliases and self.show_aliases:
            _temp += "|" + "|".join([f"`{a}`" for a in cmd.aliases])

        if cmd.usage and self.show_usage:
            _temp += f" {cmd.usage}"
        return _temp

method register(self)

Registers the help command.

Source code in naff/ext/prefixed_help.py
def register(self) -> None:
    """Registers the help command."""
    if not isinstance(self._cmd.callback, functools.partial):
        # prevent wrap-nesting
        self._cmd.callback = functools.partial(self._cmd.callback, self)

    # replace existing help command if found
    if "help" in self.client.prefixed_commands:
        logger.warning("Replacing existing help command.")
        del self.client.prefixed_commands["help"]

    self.client.add_prefixed_command(self._cmd)  # type: ignore

async method send_help(self, ctx, cmd_name)

Send a help message to the given context.

Parameters:

Name Type Description Default
ctx PrefixedContext

The context to use.

required
cmd_name str | None

An optional command name to send help for.

required
Source code in naff/ext/prefixed_help.py
async def send_help(self, ctx: PrefixedContext, cmd_name: str | None) -> None:
    """
    Send a help message to the given context.

    Args:
        ctx: The context to use.
        cmd_name: An optional command name to send help for.
    """
    await self._callback.callback(ctx, cmd_name)  # type: ignore