前提
- WSL, Ubuntu22.04.1
- Python 3.10.6
- discord.py 2.1.0
という環境で行っています.
やりたいこと
全てのコマンドの名前とその説明を表示してくれるコマンドを作ります.そのbotで使えるコマンドが何かを全体的に表示してほしいってやつですね.
利用するプログラムは以下に載せておきます.
プログラム
import os
import pprint
import discord
from discord import Interaction, app_commands
from discord.app_commands import CommandTree, Group
class MyGroup(Group):
def __init__(self):
super().__init__(name="group")
@app_commands.command(description="subcommand description")
async def subcommand(self, interaction: Interaction):
await interaction.response.send_message("this is group subcommand")
@app_commands.command(description="another command description")
async def another_command(self, interaction: Interaction):
await interaction.response.send_message("this is group another command")
class MyClient(discord.Client):
def __init__(self, *, intents: discord.Intents):
super().__init__(intents=intents)
self.tree = CommandTree(self)
async def setup_hook(self) -> None:
self.tree.add_command(MyGroup())
commands = await self.tree.sync()
pprint.pprint(commands)
async def on_ready(self):
print(f"Logged in as {client.user} (ID: {client.user.id})")
print("------")
intents = discord.Intents.default()
client = MyClient(intents=intents)
client.run(os.getenv("TOKEN"))
コマンドの取得
コマンドの名前とその説明が欲しいので,まずは全てのコマンドを取得します.そのために,CommandTree
のget_commands
メソッドかwalk_commands
メソッドを使います.
get_commands
get_commands
メソッドはCommandTreeに登録されている,ContextMenu, Command, Groupを全てリストとして取得します.ContextMenu, Commandについてはそのままコマンドの名前とその説明を表示すればいいです.しかし,Groupはグループでしかないので,そのグループに属するコマンドを個別に取得する必要があります.Group内のコマンドを取得するにはGroupのwalk_commands
メソッドを利用します.CommandTreeの方と一緒なのでそちらも説明します.
walk_commands
walk_commands
メソッドは,CommandTreeやGroup内で登録されているコマンドを全て取得するイテレータです.深さ優先探索のようにコマンドを取得する感じです.
実装
さて,コマンドを取得する方法はわかったので,helpコマンドを実装していきましょう.
get_commands
とwalk_commands
の方を使ったhelpの表示を実装していきます.説明しやすいのでwalk_commands
の方から説明していきます.
walk_commands
コマンドの名前はname
,説明はdescription
で取得できます.ここでdescription
は何も設定していない場合...
が入りますが,これはピリオド3つではなく一文字のものです.UnicodeでいうとU+20E8
らしいです.初期状態の場合,何も表示しないようにしています.
プログラムは以下の通りです.
import os
import pprint
import discord
from discord import Interaction, app_commands
from discord.app_commands import CommandTree, Group
class MyGroup(Group):
def __init__(self):
super().__init__(name="group")
@app_commands.command(description="subcommand description")
async def subcommand(self, interaction: Interaction):
await interaction.response.send_message("this is group subcommand")
@app_commands.command(description="another command description")
async def another_command(self, interaction: Interaction):
await interaction.response.send_message("this is group another command")
class MyClient(discord.Client):
def __init__(self, *, intents: discord.Intents):
super().__init__(intents=intents)
self.tree = CommandTree(self)
async def setup_hook(self) -> None:
self.tree.add_command(MyGroup())
commands = await self.tree.sync()
pprint.pprint(commands)
async def on_ready(self):
print(f"Logged in as {client.user} (ID: {client.user.id})")
print("------")
intents = discord.Intents.default()
client = MyClient(intents=intents)
+ @client.tree.command(description="help: use walk_commands")
+ async def help_walk(interaction: Interaction):
+ help_text = ""
+ for cmd in client.tree.walk_commands():
+ help_text += f"{cmd.name}\n"
+ if cmd.description != "…":
+ help_text += f"\t{cmd.description}\n"
+ await interaction.response.send_message(help_text)
client.run(os.getenv("TOKEN"))
実行結果は以下の通りです.
コマンド名とその名前が表示されましたね.ただし,グループのコマンドかそうでないかの区別が難しいです.Groupにはparent
という,親のGroupを判定するプロパティがあります.そのため,コマンドがGroupであるとき,その後のコマンドのparentがそのGroupであるかを判定することで,Groupのコマンドであるかどうかが判別できます.しかしそれをやるには労力がかかると思います.GroupはGroupで全部のコマンドを出力してくれれば楽ですが,walk_commandsではそれを考えず全てを取得します.そこでget_commandsの方を使います.
get_commands
get_commandsは先ほども記述した通り,ContextMenu, Command, Groupだけを取得します.つまりGroupだけ別で,Group内のコマンドを出力する,といった具合に分割できます.こうすることでGroup内のコマンドであることをわかりやすくすることができます.
また,Group内のコマンド表示,およびCommandTree事態に登録されているコマンドの表示はおおむね一致しているため,関数を作成してみました.両者の違いとして,Group内のコマンドはより一層ネストが深いので,コマンドの表示のテキストを返す関数において,引数に文字列の先頭に文字を指定できるようにし,そこにタブ文字を入れました.ここでは,Group内にさらにGroupはないという想定ですし,そもそもwalk_commandsしか使えないのでさらなるネストにするのは面倒です.そのため,このような関数に,コマンドの名前と詳細を表示するようにしました.
context menuにはdescriptionがありませんので注意してください
import os
import pprint
import discord
from discord import Interaction, app_commands
from discord.app_commands import CommandTree, Group, Command
class MyGroup(Group):
def __init__(self):
super().__init__(name="group")
@app_commands.command(description="subcommand description")
async def subcommand(self, interaction: Interaction):
await interaction.response.send_message("this is group subcommand")
@app_commands.command(description="another command description")
async def another_command(self, interaction: Interaction):
await interaction.response.send_message("this is group another command")
class MyClient(discord.Client):
def __init__(self, *, intents: discord.Intents):
super().__init__(intents=intents)
self.tree = CommandTree(self)
async def setup_hook(self) -> None:
self.tree.add_command(MyGroup())
commands = await self.tree.sync()
pprint.pprint(commands)
async def on_ready(self):
print(f"Logged in as {client.user} (ID: {client.user.id})")
print("------")
intents = discord.Intents.default()
client = MyClient(intents=intents)
@client.tree.command(description="help: use walk_commands")
async def help_walk(interaction: Interaction):
help_text = ""
for cmd in client.tree.walk_commands():
help_text += f"{cmd.name}\n"
if cmd.description != "…":
help_text += f"\t{cmd.description}\n"
await interaction.response.send_message(help_text)
+ @client.tree.command(description="help: use get_commnads")
+ async def help_getcmd(interaction: Interaction):
+ def get_group_command_help_text(group: Group):
+ help_text = get_command_help_text(group)
+ for cmd in group.walk_commands():
+ help_text += get_command_help_text(cmd, head="\t")
+ return help_text
+
+ def get_command_help_text(cmd: Group | Command, head=""):
+ help_text = f"{head}{cmd.name}\n"
+ if cmd.description != "…":
+ help_text += f"{head}\t{cmd.description}\n"
+ return help_text
+
+ help_text = ""
+ commands = client.tree.get_commands()
+ for cmd in commands:
+ if isinstance(cmd, Group):
+ help_text += get_group_command_help_text(cmd)
+ else:
+ help_text += get_command_help_text(cmd)
+
+ await interaction.response.send_message(help_text)
client.run(os.getenv("TOKEN"))
実行してみると,
このようになりました.Groupないでさらにネストしているので,見やすくなった,と思います.
以上となります.discord.pyのslash commandでコマンドhelpの表示を見ていきました.
お疲れ様でした.