2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

前提

  • WSL, Ubuntu22.04.1
  • Python 3.10.6
  • discord.py 2.1.0

という環境で行っています.

やりたいこと

全てのコマンドの名前とその説明を表示してくれるコマンドを作ります.そのbotで使えるコマンドが何かを全体的に表示してほしいってやつですね.

利用するプログラムは以下に載せておきます.

プログラム
python
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"))

コマンドの取得

コマンドの名前とその説明が欲しいので,まずは全てのコマンドを取得します.そのために,CommandTreeget_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_commandswalk_commandsの方を使ったhelpの表示を実装していきます.説明しやすいのでwalk_commandsの方から説明していきます.

walk_commands

コマンドの名前はname,説明はdescriptionで取得できます.ここでdescriptionは何も設定していない場合...が入りますが,これはピリオド3つではなく一文字のものです.UnicodeでいうとU+20E8らしいです.初期状態の場合,何も表示しないようにしています.

プログラムは以下の通りです.

python
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"))

実行結果は以下の通りです.

image.png

コマンド名とその名前が表示されましたね.ただし,グループのコマンドかそうでないかの区別が難しいです.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がありませんので注意してください

python
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"))

実行してみると,

image.png

このようになりました.Groupないでさらにネストしているので,見やすくなった,と思います.

以上となります.discord.pyのslash commandでコマンドhelpの表示を見ていきました.
お疲れ様でした.

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?