daima3629

on_message を使うとコマンドが動作しなくなります。どうしてですか。
公式リファレンスのFAQにはこんな項目があります。
内容は 「commandsフレームワーク使用時には Bot.event を用いて on_message イベントをそのまま定義してはいけない」 というもの。
これを行ってしまうと全てのコマンドが実行されなくなると書いてあります。
しかし、この理由は細かく説明されていません。今回はそちらについて詳しく説明していこうと思います。

Bot.process_commands とは

該当FAQ内では対策として2つの選択肢が書かれています。
その1つ目が、「 on_message イベント処理の最後に await bot.process_commands(message) 」をつけるということです。
これは一体何なのでしょうか。


想像できる方もいらっしゃると思いますが、これは コマンドを発火させるための関数 です。
これを実行しなければどんなコマンドも実行されることはありません。少しだけもともとの on_message イベントの実装を見てみましょう。

async def on_message(self, message):
    await self.process_commands(message)

非常に単純ですね。たった一行しかありません。そして、その一行ではちゃんと process_commands を実行しています。
ではこれを踏まえて process_commands を使うように一般的な on_message を書き換えてみましょう。

@bot.event
async def on_message(message):
    if message.content == "/neko":
        await message.channel.send("にゃーん")
    
    await bot.process_commands(message)  # 基本的にはすべての処理の最後につける

これでコマンドも正常に動作するようになります。 このように、 process_commands メソッドはコマンドを実行する上でとても重要な役割を果たしているため、 on_message をそのまま上書きしてしまうとコマンドが動作しなくなってしまうんだ、と分かりましたね。

Bot.listenを使う

ここまで1つ目の対策を説明しましたが、2つ目の対策は「 Bot.listen を用いる」ということです。
「なにそれおいしいの」って思った方もいるんでしょう。実はこいつは Bot.event とよく似ていて、しかし完全に異なる点が一つあるものです。
ここでは Bot.eventBot.listen の違いを説明しながら、これが対策方法の一つになる理由を説明します。


そもそも Bot.event は何をするものだったでしょうか。これは デコレーター として使うメソッドですね。
一応使い方をサンプルコードで説明しておきます。

@bot.event
async def on_message(message):
    if message.content == "/neko":
        await message.channel.send("にゃーん")

こんな感じでしたね。イベント名が名前となっている関数の上につけるものでした。
実はこのメソッド、ボットにあらかじめ定義されているイベント処理を 上書き するものです。
だから process_commands の記述も上書きされてしまい消えてしまっていたわけですね。
さて、この問題を解決してくれるスーパーマンが Bot.listen です。 使い方をサンプルコードで見てみましょう。

@bot.listen()  # かっこが必要
async def on_message(message):
    if message.content == "/neko":
        await message.channel.send("にゃーん")

Bot.event を使うときと違う点は、

  • デコレーターのあとに かっこ をつける

これだけです。
さて、処理的には何が違うかと言うと、

  • Bot.event → イベントの処理を 上書き する
  • Bot.listen → イベントの処理を 追加 する

処理を追加するということは、 もとからある処理は消えない ということです。
要するにイベントに対する処理を2つ3つと増やしていけるわけですね。
よりシンプルに、余計なものを記述したくない方はこちらの方法を使うほうがいいかもしれませんね。

ちなみに、 Bot.listen を使うと関数名を好きなものに変えることもできます。

@bot.listen("on_message")  # かっこの中にイベント名を入れる
async def on_message_neko(message):
    if message.content == "/neko":
        await message.channel.send("にゃーん")

このように bot.listen のあとのかっこの中に文字列でイベント名を入れることで関数名は自由にすることができます!

おまけ: commands.Cog.listener

ここまで読んでくださった方の中にはこんな疑問を抱いた方もいるでしょう。
「Cogの中ではどうやって Bot.listen を使えばいいんだ!?」
おっしゃるとおりで、Cogの中ではそのままBotオブジェクトにアクセスすることができないため、 Bot.listen を使えません。
もちろん process_commands を直で書く方法もありますが、こっちはなんか嫌だ!という人のために第3の手段をお教えしましょう。
その名も commands.Cog.listener !使い方をサンプルコードで見てみましょう。

from discord.ext import commands

class MyCog(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.Cog.listener()
    async def on_message(message):
        if message.content == "/neko":
            await message.channel.send("にゃーん")

こんな感じです。使い方は Bot.listen と同じで、もちろんかっこの中にイベント名を入れれば関数名を自由に変えることが可能です。
Cogの中ではぜひこちらを使ってみてください!

まとめ

  • commandsフレームワーク使用時は Bot.eventon_message イベントをそのまま定義してはいけない
  • 対策法その1: 処理の最後に Bot.process_commands を入れる
  • 対策法その2: Bot.listen (Cogの中では commands.Cog.listener ) を使う

参考リンク

comments powered by Disqus