Pular para o conteúdo principal

Help Command

Pycord's commands extension comes with a built-in help command. In this guide, we will take a look at them as well as learn how to create your own. Let's dive in!

note

This guide demonstrates using object-oriented programming and subclassing to make a help command for your Pycord bot. It is important to understand these two concepts before continuing.

Learning Resources:

The Wrong Way

A popular way to create a help command is to disable the built-in help command and create your own. This is not recommended as it will lead to a lot of confusion.

The Wrong Way
bot = commands.Bot(command_prefix='!', help_command=None)
# OR
bot.help_command = None
# OR
bot.remove_command("help")

@bot.command()
async def help(ctx, *, argument=None):
...

While it's possible to create a help command this way, doing so does not utilize the full capabilities of making one with Pycord. Making a help command with subclassing and OOP will save time and effort.

Types of Help Commands

There are two types of built-in help commands:

DefaultHelpCommand is the command enabled by default. It isn't the best looking, but MinimalHelpCommand can help make it look a bit better.

```python title="Default Help Command" import discord from discord.ext import commands
bot = commands.Bot(command_prefix='!', help_command=commands.DefaultHelpCommand()) # help_command is DefaultHelpCommand by default so it isn't necessary to enable it like this
# We enable it here for the sake of understanding

...

bot.run("token")
```

<DiscordComponent>
<DiscordMessage profile="bob">
!help
</DiscordMessage>

<DiscordMessage profile="robocord">
<code>
No Category:
<br />
<div style={{ paddingLeft: "2em" }}>
!help Shows this message
<br />
!ping
</div>
<br />
Type !help command for more info on a command.
<br />
You can also type !help category for more info on a category.
</code>
</DiscordMessage>
</DiscordComponent>

Updating Built-in Help Commands

Let's try to make the MinimalHelpCommand look better. We can do this by putting its content in an embed.

Minimal Help Command Example
bot = commands.Bot(command_prefix='!')


class MyNewHelp(commands.MinimalHelpCommand):
async def send_pages(self):
destination = self.get_destination()
for page in self.paginator.pages:
emby = discord.Embed(description=page)
await destination.send(embed=emby)

bot.help_command = MyNewHelp()

Let's go through the code.

First, we create a new class called MyNewHelp. This class is a subclass of MinimalHelpCommand.

Next, we override the send_pages method. This method is responsible for sending the help command to the user. We override this method because we don't want to change the content of the pages, just how they are sent.

We use the get_destination method to get the destination of the message. This is the channel or use that the help command is to be sent to.

We use the paginator.pages property to get the different pages of the help command. We put the page in an embed, and then send the embed to the destination channel.

Finally, we set this as our new help command using bot.help_command = MyNewHelp().

RobocordBot10/24/2024
Use !help [command] for more info on a command.
You can also use !help [category] for more info on a category.

No Category
help ping

Creating Your Help Command

Not satisfied with the built-in help commands? You can create your own!

For this, we will subclass the HelpCommand class.

There are 4 methods that we need to override:

  • HelpCommand.send_bot_help(mapping) that gets called with <prefix>help
  • HelpCommand.send_command_help(command) that gets called with <prefix>help <command>
  • HelpCommand.send_group_help(group) that gets called with <prefix>help <group>
  • HelpCommand.send_cog_help(cog) that gets called with <prefix>help <cog>

Bot Help

Bot Help Example
class MyHelp(commands.HelpCommand):
async def send_bot_help(self, mapping):
embed = discord.Embed(title="Help")
for cog, commands in mapping.items():
command_signatures = [self.get_command_signature(c) for c in commands]
if command_signatures:
cog_name = getattr(cog, "qualified_name", "No Category")
embed.add_field(name=cog_name, value="\n".join(command_signatures), inline=False)

channel = self.get_destination()
await channel.send(embed=embed)

bot.help_command = MyHelp()

Let's go through the code.

  • First, we create a new class called MyHelp. This class is a subclass of HelpCommand.

  • Next, we override the send_bot_help method. This method is responsible for sending the main help command to the user.

  • We create an embed with the title "Help".

  • We iterate through mapping.items(), which returns a list of tuples, the first element being the cog and the second element being a list of commands.

  • By using self.get_command_signature(c) we get the signature of the command, also known as the parameters or arguments.

  • There is a chance that the cog is empty, so we use if command_signatures:.

  • We get the name of the cog using getattr(cog, "qualified_name", "No Category"). This calls the cog's attribute qualified_name which returns "No Category" if the cog has no name.

  • We add a field to the embed with the name of the cog and the value of the command signatures.

  • Finally, we send the embed to the channel.

Let's improve the code a little.

Improved Bot Help Example
class MyHelp(commands.HelpCommand):
def get_command_signature(self, command):
return '%s%s %s' % (self.context.clean_prefix, command.qualified_name, command.signature)

async def send_bot_help(self, mapping):
embed = discord.Embed(title="Help", color=discord.Color.blurple())

for cog, commands in mapping.items():
filtered = await self.filter_commands(commands, sort=True)
command_signatures = [self.get_command_signature(c) for c in filtered]

if command_signatures:
cog_name = getattr(cog, "qualified_name", "No Category")
embed.add_field(name=cog_name, value="\n".join(command_signatures), inline=False)

channel = self.get_destination()
await channel.send(embed=embed)

bot.help_command = MyHelp()

Now the help command won't show commands that the user can't use, as well as aliases for commands. Let's get the help command to show command aliases.

Command Help

This function is called when the user uses <prefix>help <command>.

Command Help Example
class MyHelp(commands.HelpCommand):
async def send_command_help(self, command):
embed = discord.Embed(title=self.get_command_signature(command), color=discord.Color.random())
if command.help:
embed.description = command.help
if alias := command.aliases:
embed.add_field(name="Aliases", value=", ".join(alias), inline=False)

channel = self.get_destination()
await channel.send(embed=embed)

bot.help_command = MyHelp()

Let's quickly go through the code we haven't discussed yet.

  • In line 3, we create an embed with a title the signature of the command (so that the title of the embed looks like <command> <parameter> [parameter]), and a random color.

  • In lines 4 and 5, we get the command's help description and add it to the embed. The help description of a command can be specified in the docstrings of a command function. For example:

    @bot.command()
    async def ping(ctx):
    """Returns the latency of the bot."""
    await ctx.send(f"Pong! {round(bot.latency * 1000)}ms")
  • Line 6 is shorthand for

    alias = command.aliases
    if alias:
    ...

    # is the same as

    if alias := command.aliases:
    ...

    A very helpful (but not well-known) Python shorthand!

  • In line 7, we get the aliases of the command and add them to the embed.

Cog Help

This is pretty easy!

Custom Cog Help Example
class MyHelp(commands.HelpCommand):
def get_command_signature(self, command):
return '%s%s %s' % (self.context.clean_prefix, command.qualified_name, command.signature)

async def send_cog_help(self, cog):
embed = discord.Embed(title=cog.qualified_name or "No Category", description=cog.description, color=discord.Color.blurple())

if filtered_commands := await self.filter_commands(cog.get_commands()):
for command in filtered_commands:
embed.add_field(name=self.get_command_signature(command), value=command.help or "No Help Message Found... ")

await self.get_destination().send(embed=embed)

Once again, we create an embed, get the commands that the user can use, and add them to the embed.

Group Help

Similar to the cog help, this is pretty easy!

class MyHelp(commands.HelpCommand):
def get_command_signature(self, command):
return '%s%s %s' % (self.context.clean_prefix, command.qualified_name, command.signature)

async def send_group_help(self, group):
embed = discord.Embed(title=self.get_command_signature(group), description=group.help, color=discord.Color.blurple())

if filtered_commands := await self.filter_commands(group.commands):
for command in filtered_commands:
embed.add_field(name=self.get_command_signature(command), value=command.help or "No Help Message Found... ")

await self.get_destination().send(embed=embed)

Add all of these methods together, and you have a fully functioning help command!

Click to see the final code for this section
:::note
The following code has been slightly edited from the tutorial version.

:::

```python title="Custom Help Command Example"
class SupremeHelpCommand(commands.HelpCommand):
def get_command_signature(self, command):
return '%s%s %s' % (self.context.clean_prefix, command.qualified_name, command.signature)

async def send_bot_help(self, mapping):
embed = discord.Embed(title="Help", color=discord.Color.blurple())
for cog, commands in mapping.items():
filtered = await self.filter_commands(commands, sort=True)
if command_signatures := [
self.get_command_signature(c) for c in filtered
]:
cog_name = getattr(cog, "qualified_name", "No Category")
embed.add_field(name=cog_name, value="\n".join(command_signatures), inline=False)

channel = self.get_destination()
await channel.send(embed=embed)

async def send_command_help(self, command):
embed = discord.Embed(title=self.get_command_signature(command) , color=discord.Color.blurple())
if command.help:
embed.description = command.help
if alias := command.aliases:
embed.add_field(name="Aliases", value=", ".join(alias), inline=False)

channel = self.get_destination()
await channel.send(embed=embed)

async def send_help_embed(self, title, description, commands): # a helper function to add commands to an embed
embed = discord.Embed(title=title, description=description or "No help found...")

if filtered_commands := await self.filter_commands(commands):
for command in filtered_commands:
embed.add_field(name=self.get_command_signature(command), value=command.help or "No help found...")

await self.get_destination().send(embed=embed)

async def send_group_help(self, group):
title = self.get_command_signature(group)
await self.send_help_embed(title, group.help, group.commands)

async def send_cog_help(self, cog):
title = cog.qualified_name or "No"
await self.send_help_embed(f'{title} Category', cog.description, cog.get_commands())

bot.help_command = SupremeHelpCommand()
```

Command Attributes

How can you add cooldowns, set aliases, and change the name of help commands? Command attributes can help you do all of that, and more!

Help Command Attributes
attributes = {
'name': "help",
'aliases': ["helpme"],
'cooldown': commands.CooldownMapping.from_cooldown(3, 5, commands.BucketType.user),
}

# You need to use CooldownMapping.from_cooldown(rate, per, type)

bot.help_command = MyHelp(command_attrs=attributes)

Error Handling

When a user attempts to use a command that does not exist, we need to inform the user. We can do this by overriding the send_error_message function.

Custom Help Error Example
class MyHelp(commands.HelpCommand):
async def send_error_message(self, error):
embed = discord.Embed(title="Error", description=error, color=discord.Color.red())
channel = self.get_destination()

await channel.send(embed=embed)
BobDotCom10/24/2024
!help update\_pycord
RobocordBot10/24/2024
Error
Command 'update\_pycord' not found.

Credits

Most of the content from this guide is from InterStella0's walkthrough guide on subclassing HelpCommand. Thanks to InterStella0 for making this guide amazing.