💨

discord.py v2.0でハイブリッドコマンド

2022/09/06に公開

discord.py2.0でハイブリッドコマンドを使う。

discordにはスラッシュコマンドと呼ばれる機能があります。botとユーザーがメッセージを使わなくてもやりとりできる機能です。
また、discord.pyには古くから「コマンド」の概念がありました。!pingというメッセージを送信するとPong!って言われて、!helpだとI won't help you!って言われる、みたいな。
(helpしてくれないbotは意地悪なので使いたくないですが())
それを簡単に作るために作られたフレームワークがdiscord.ext.commandsです。
このcommandsの機能(メッセージで反応)と、スラッシュコマンドの機能を組み合わせて、どちらでも使えるように設計されたのがhybrid_commandです。ハイブリッドを使用すると、2つのコードを分けなくても、メッセージでも反応するし、スラッシュでも反応するようになるのです。
ただ、もちろんですが、メッセージで反応させたいならmessage content intentsは必要です。

ちなみに解説するcommands.Botに関してはtree属性を最初から持っているみたいなのでいちいち定義しなくて済むようです。

基本的なコマンドを書いてみる

https://github.com/Rapptz/discord.py/blob/master/examples/app_commands/basic.py
ここに例が載ってました。英語だけど例があるだけかなりマシ。setup_hookの中でawait self.tree.syncしてますね。
これをhybridにするために普通のcommandsフレームワークも使いながら書いていきます。
とりあえず基本的な部分を作っていきましょう。

import discord
from discord import app_commands
from discord.ext import commands


class MyBot(commands.Bot):
    "コマンドの同期をするBot。クラスのオーバーライドという技術を使っています"
    async def setup_hook(self):
	await self.tree.sync(guild=MY_GUILD)


intents = discord.Intents.default()
intents.message_content = True  # メッセージコンテントのintentはオンにする
bot = MyBot(command_prefix="!", intents=intents)

@bot.hybrid_command()  # ハイブリッドコマンド
async def ping(ctx):
    await ctx.reply("Pong!")

bot.run("PleasePutYourBOT'sTokenHere")

こんな感じです。意外と簡単。
これで、!pingでも/pingでも使えるようになったはずです。

ハイブリッドグループを使う

ハイブリッドグループも普通のグループと同じように使えます。
ただ、スラッシュコマンドでは親のグループコマンドを使うことはできないので注意してください。

@bot.hybrid_group()
async def ping_special(ctx):
    if ctx.invoked_subcommand is None:
        await ctx.send("Pong special!!")
	# スラッシュコマンドではあり得ない

@ping_special.command(description="某人気キャラクター")
async def pingu(ctx):
    await ctx.send("Pingu!")

(追記:v2.0が開発中だったときに書いたこの記事ですが、今見たらfallbackという引数が追加されていました。この引数を使うと、グループの親コマンドをスラッシュコマンドで反応させるための「身代わり」を作れるみたいです)

@bot.hybrid_group(fallback="slasher") # 身代わりを slasher に設定
async def ping_special(ctx):
    if ctx.invoked_subcommand is None:
        await ctx.send("Pong special!!")
	# `!ping_special`もしくは`/ping_special slasher`とすると反応する

@ping_special.command(description="某人気キャラクター")
async def pingu(ctx):
    await ctx.send("Pingu!")

応用編 - スラッシュコマンドかどうか判別する

スラッシュコマンドの場合は、Context.interactionに値が入ります。
なのでその仕組みを利用してスラッシュコマンドかどうかを判別できます。

@bot.hybrid_command()
@app_commands.describe(name="呼んでほしい名前")
@app_commands.rename(name="user_name")
async def hello_ephemeral_slash(ctx, name: str):
    if ctx.interaction is not None:
        # スラッシュコマンド
	await ctx.send(f"Hello, {name}!", ephemeral=True)
    else:
        # 通常のコマンド
	await ctx.send(f"Hello, {name}!")

上の例を見ればわかると思うのですが、ハイブリッドでもapp_commands.describeなどのデコレータは使えます。ただそれらのデコレータはスラッシュコマンドにのみ反映されるので気をつけてください。

Discussion