🖋️

discord.py - commandsフレームワークへの移行

2021/02/19に公開

はじめに

この記事はdiscord.pyのcommandsフレームワークに移行する方法を書いたものです。
間違いを見つけたらどんどん指摘してください。

環境

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

python -m discord -vの情報)

最初のコード

以下のコードから始めるモノとします:

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. インポート

commandsフレームワークをインポートするには

  import discord
+ from discord.ext import commands

のようにします。
普通のdiscordは残しておきます。(isinstanceでの判定やEmbedの作成に必要になります。が、今回は不必要です。)

なぜfrom〜importを使うのか?

実際、

import discord.ext.commands

でも動きはしますが、
毎回

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

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

と書く必要があり、面倒なためfrom~importを使っています。

2. ClientをBotにする

ClientをBotにしていきます。
移行にはそんな心配は要らず、discord.Clientをcommands.Botに置き換えるだけで移行できます。
Botを使うにはプレフィックス(/!など、コマンドの最初につける文字)を決める必要があります。
今回は$にしました。

覚えておくと役に立つこと

プレフィックスにはListや関数も渡すことが出来ます。
Listを使えばエイリアスを作成でき、
関数を使えば動的(場合によって変わる)なプレフィックスができます。

Listを指定する

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

Listをプレフィックスにした場合、最初の方から判定が行われるため長いプレフィックスを先に指定する必要があります。
例えば、b.b. を指定した場合、先にあるb.で判定されてしまい、
b. help helpというコマンドとして認識されてしまいます。

関数を指定する

プレフィックスに指定する関数にはBotオブジェクトとMessageオブジェクトが渡されます。
そして、プレフィックスを返す必要があります。

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

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

bot = commands.Bot(command_prefix=prefix)

また、discord.pyにはいくつかプレフィックス用の関数 があるのでそれを使うのも手です。

# メンション時。@Bot helpのように動作する。
bot = commands.Bot(command_prefix=commands.when_mentioned)

# メンション時+α。@Bot helpやbot.helpのように動作する。
bot = commands.Bot(command_prefix=commands.when_mentioned_or("bot."))
- client = discord.Client()
+ bot = commands.Bot(command_prefix="$")

また、clientをbotにしたため、clientをbotに置き換えていきます。

- @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. コマンドを定義する

「commands」フレームワークの名の通り、このフレームワークはコマンドを簡単に作ることができます。
コマンドを作るには@bot.commandが一般的です[1]
関数名がそのままコマンド名になります。
また、@bot.commandには()が必要なので注意。

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

@bot.command()
async def greet(ctx, name):
    await ctx.reply(f"Hello, `{name}`.")
覚えておくと役に立つこと

listのように関数名がかぶりそうなときはname=を引数に指定します。

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

エイリアスを追加するにはaliases=を指定します。

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

空白を無視して受け取りたいときは*,をつけます。

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

これらは全て一緒に使うことが出来ます。

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

コマンドを定義したため、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. コマンドを動作させる

on_messageを定義するとコマンドが実行されなくなります。
このようなときには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)

これで移行は終了です。

最終コード

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')

終わりに

この記事が役に立てば幸いです。
読んでいただきありがとうございました。

脚注
  1. @commands.commandで定義後、bot.add_commandで追加する方法もありますがそれほど使わないので割愛。 ↩︎

Discussion