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!
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.
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.
- Default Help Command
- Minimal 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")
import discord
from discord.ext import commands
bot = commands.Bot(command_prefix='!', help_command=commands.MinimalHelpCommand())
...
bot.run("token")
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.
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()
.
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
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 ofHelpCommand
.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 theparameters
orarguments
.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 attributequalified_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.
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>
.
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:```python
@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!
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
The following code has been slightly edited from the tutorial version.
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!
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.
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)
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.