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.event
と Bot.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.event
でon_message
イベントをそのまま定義してはいけない - 対策法その1: 処理の最後に
Bot.process_commands
を入れる - 対策法その2:
Bot.listen
(Cogの中ではcommands.Cog.listener
) を使う
Twitter