iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🖋️

Migrating to discord.py commands framework

に公開

Introduction

This article explains how to migrate to the discord.py commands framework.
Please feel free to point out any mistakes you find.

Environment

- Python v3.8.3-final
- discord.py v1.6.0-final
- aiohttp v3.6.3
- system info: Windows 10 10.0.17134

(Information from python -m discord -v)

Initial Code

We will assume we are starting from the following code:

import discord

client = discord.Client()
RESPONSES = {
    "おは": "おはよう!",
    "おやすみ": "おやすみ!",
    "落ち": "おつかれ!"
}

@client.event
async def on_ready():
    print('We have logged in as {0.user}'.format(client))

@client.event
async def on_message(message):
    if message.author == client.user:
        return

    for rk, rv in RESPONSES.items():
        if rk in message.content:
            await message.reply(rv)
    
    if message.content.startswith('$ping'):
        await message.channel.send('Pong!')
    elif message.content.startswith('$greet'):
        await message.channel.send(f'Hello, `{message.content[7:]}`.')

client.run('Th1sIsN0tT0k3n.B3cause.1fiShowB0tWillG3tH4cked')

1. Imports

To import the commands framework, do the following:

  import discord
+ from discord.ext import commands

You should keep the regular discord import. (It is needed for things like checking instances with isinstance or creating Embeds, although it is not necessary for this specific example.)

Why use from~import?

Actually,

import discord.ext.commands

would work, but then you would have to write

discord.ext.commands.Bot(command_prefix="!")

or

@bot.command()
@discord.ext.commands.has_permissions(administrator=True)

every time, which is tedious, so we use from~import instead.

2. Converting Client to Bot

Next, let's convert the Client to a Bot.
You don't need to worry about the migration; you can migrate simply by replacing discord.Client with commands.Bot.
To use the Bot, you need to decide on a prefix (the character at the start of a command, such as / or !).
In this case, we'll use $.

Things that are helpful to remember

You can also pass a list or a function to the prefix.
Using a list allows you to create aliases, while using a function allows for dynamic prefixes (that change depending on the situation).

Specifying a List

bot = commands.Bot(command_prefix=["mybot ", "mybot."])

When using a list as a prefix, the check is performed from the beginning of the list, so you need to specify longer prefixes first.
For example, if you specify b. and b. , it will be judged by the b. that comes first, and b. help will be recognized as a command called help.

Specifying a Function

The function specified for the prefix is passed the Bot object and the Message object.
You then need to return the prefix.

SETTINGS = {123456789: "!", 987654321: "?"}

def prefix(bot, message):
    return SETTINGS.get(message.guild.id, "!?")

bot = commands.Bot(command_prefix=prefix)

Also, discord.py has several prefix helper functions, so it's also an option to use those.

# When mentioned. Works like @Bot help.
bot = commands.Bot(command_prefix=commands.when_mentioned)

# When mentioned + alpha. Works like @Bot help or bot.help.
bot = commands.Bot(command_prefix=commands.when_mentioned_or("bot."))
- client = discord.Client()
+ bot = commands.Bot(command_prefix="$")

Additionally, since we've changed client to bot, we'll go ahead and replace client with bot throughout the code.

- @client.event
+ @bot.event
  async def on_ready():
-     print('We have logged in as {0.user}'.format(client))
+     print('We have logged in as {0.user}'.format(bot))

- @client.event
+ @bot.event
  async def on_message(message):
-     if message.author == client.user:
+     if message.author == bot.user:
          return

================================================================

- client.run('Th1sIsN0tT0k3n.B3cause.1fiShowB0tWillG3tH4cked')
+ bot.run('Th1sIsN0tT0k3n.B3cause.1fiShowB0tWillG3tH4cked')

3. Defining Commands

As the name "commands" framework suggests, this framework allows you to easily create commands.
Using @bot.command is the common way to create commands.[1]
The function name becomes the command name itself.
Also, note that @bot.command requires parentheses ().

@bot.command()
async def ping(ctx):
    await ctx.reply("Pong!")

@bot.command()
async def greet(ctx, name):
    await ctx.reply(f"Hello, `{name}`.")
Things that are helpful to remember

If the function name is likely to conflict, such as list, specify name= as an argument.

@bot.command(name="greet")
async def command_greet(ctx, name):
    await ctx.reply(f"Hello, `{name}`.")

To add aliases, specify aliases=.

@bot.command(aliases=["hi", "hello"])
async def greet(ctx, name):
    await ctx.reply(f"Hello, `{name}`.")

If you want to accept input while ignoring spaces, add *,.

@bot.command()
async def greet(ctx, *, name):
    await ctx.reply(f"Hello, `{name}`.")

These can all be used together.

@bot.command(name="greet", aliases=["hi", "hello"])
async def command_greet(ctx, *, name):
    await ctx.reply(f"Hello, `{name}`.")

Since we have defined the commands, we will remove the command logic from on_message.

  @bot.event
  async def on_message(message):
      if message.author == bot.user:
          return
  
      for rk, rv in RESPONSES.items():
          if rk in message.content:
              await message.channel.send(rv)

-     if message.content.startswith('$ping'):
-         await message.channel.send('Pong!')
-     elif message.content.startswith('$greet'):
-         await message.channel.send(f'Hello, `{message.content[7:]}`.')

4. Running the Commands

Defining on_message will prevent commands from being executed. In such cases, use process_commands.

  @bot.event
  async def on_message(message):
      if message.author == bot.user:
          return
    
      for rk, rv in RESPONSES.items():
          if rk in message.content:
              await message.channel.send(rv)
  
+     await bot.process_commands(message)

This completes the migration.

Final Code

import discord
from discord.ext import commands

bot = commands.Bot(command_prefix="$")
RESPONSES = {
    "おは": "おはよう!",
    "おやすみ": "おやすみ!",
    "落ち": "おつかれ!"
}

@bot.event
async def on_ready():
    print('We have logged in as {0.user}'.format(bot))

@bot.command()
async def ping(ctx):
    await ctx.reply("Pong!")

@bot.command()
async def greet(ctx, name):
    await ctx.reply(f"Hello, `{name}`.")

@bot.event
async def on_message(message):
    if message.author == bot.user:
        return

    for rk, rv in RESPONSES.items():
        if rk in message.content:
            await message.reply(rv)
    
    await bot.process_commands(message)


bot.run('Th1sIsN0tT0k3n.B3cause.1fiShowB0tWillG3tH4cked')

Conclusion

I hope this article is helpful.
Thank you for reading.

脚注
  1. There is also a method where you define it with @commands.command and then add it with bot.add_command, but since it's not used that often, I will omit it. ↩︎

Discussion