【discord.py v2.0】ボタンやセレクトメニュー等を利用する方法

# はじめに

　この記事は、discord.py v2.0より利用できるようになったBot UIキットについての解説を行う。
Bot UIキットは、Botにボタンやセレクトメニュー、テキストインプットなどのv2.0で追加された部品を扱う際に利用する。
　v2.0で追加されたBOT UIキットに関連するアップデートは非常に豊富であるため、当記事ではよく利用するパターンと要素を抜粋して紹介する。

# クイックスタート

　はじめにBOT UIキットの基本となるdiscord.ui.Viewの最小構成を紹介する。

```py
import discord
from discord.ext import commands

bot = commands.Bot(command_prefix="?", intents=discord.Intents.all())

class SampleView(discord.ui.View): # UIキットを利用するためにdiscord.ui.Viewを継承する
    def __init__(self, timeout=180): # Viewにはtimeoutがあり、初期値は180(s)である
        super().__init__(timeout=timeout)

@bot.command()
async def test(ctx):
    view = SampleView(timeout=None)
    await ctx.send(view=view)

bot.run("YOUR BOT TOKEN")
```

　上記のプログラムは`?test`というコマンドを入力すると、そのチャンネルにViewを返すというものである。ここで言うViewとはdiscord.ui.Viewを継承し、作成したSampleViewクラスである。このプログラムでは、Viewに部品を設定していないため、見かけ上BOTからは何も返ってこない。これ以降、継承したクラスにボタンやセレクトメニューなどの部品を配置していくことになる。
　補足として、上記プログラムのコメントでも明記しているようにViewにはタイムアウトが存在し、タイムアウトするとViewの要素(ボタンなど)のイベントを取得出来なくなってしまう。デフォルトでは180(s)だが、Noneにすることによってタイムアウトを無効化することができる。

# ボタンの追加

　これ以降のサンプルでは、簡略化のためdiscord.ui.Viewを継承したSampleViewクラスのみを提示する。前後のコード等は前章のクイックスタートを参照のこと。

```py
class SampleView(discord.ui.View):
    def __init__(self, timeout=180):
        super().__init__(timeout=timeout)
    
    @discord.ui.button(label="OK", style=discord.ButtonStyle.success)
    async def ok(self, button: discord.ui.Button, interaction: discord.Interaction):
        await interaction.response.send_message(f"{interaction.user.mention} OK!")

    @discord.ui.button(label="NG", style=discord.ButtonStyle.gray)
    async def ng(self, button: discord.ui.Button, interaction: discord.Interaction):
        await interaction.response.send_message(f"{interaction.user.mention} NG")
```
![e94cbb5ff622eac383e38bc5a9847c04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635660/c8a9ced2-687a-7608-bf97-60fecb7e4b93.png)
　上記のプログラムでは、「OK」「NG」の2種類のボタンを作成し、ボタンを押したユーザーに対してそれぞれメンション付きで「OK!」と「NG」を返信するものである。
　`@discord.ui.button`のlabelには、ボタンの名前を記述し、styleにはボタンの色などのスタイルを指定する。スタイルの種類に関しては[公式ドキュメント](https://discordpy.readthedocs.io/ja/latest/interactions/api.html?highlight=modal#discord.ButtonStyle)を参照。
　引数interactionには、押されたボタンに関する情報が格納されており、例のようにinteraction.userなどとすることでボタンを押したユーザーの情報を取得することができる。また、discordではinteractionが発行されてから3秒以内にinteractionのresponseを返すように推奨しており、これを満たさない場合は、ボタンの下部に「インタラクションに失敗しました」というメッセージが表示される。あくまでも推奨されているだけなので、返さないと処理が無効化されるということはない。今回は、responseとしてsend_message関数を利用して、ユーザーにメンションを返している。

# 「インタラクションに失敗しました」対策
![4d60d439a7ede0e3a3a1172772df73d2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635660/353adf8c-81a5-b552-a816-376468464723.png)
　前の章で述べたようにinteractionは3秒以内にresponseを発行しないと「インタラクションに失敗しました」というメッセージを表示してしまう問題に対する対策は空メッセージを送ることである。

```py
await interaction.response.send_message("")
```

　上記の例のように空メッセージを送ると、responseはされたものとして判定され「インタラクションに失敗しました」というメッセージを表示させることを防止する。これはボタンの入力により、メッセージを返したくない場合に非常に有効である。

# セレクトメニューの追加

```py
class SampleView(discord.ui.View):
    def __init__(self, timeout=180):
        super().__init__(timeout=timeout)

    @discord.ui.select(
        cls=discord.ui.Select,
        placeholder="What is your favorite fruit?",
        options=[
            discord.SelectOption(label="Banana"),
            discord.SelectOption(label="Apple"),
            discord.SelectOption(label="Pineapple"),
            discord.SelectOption(label="Grapefruit"),
            discord.SelectOption(label="Orange"),
        ]
    )
    async def select(self, interaction: discord.Interaction, select: discord.ui.Select):
        await interaction.response.send_message(f"{interaction.user.mention} {select.values[0]}")
```
![b7f8b35bf27bb5cecfe4b71249491673.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635660/8ad1b932-c071-cccf-1f61-1d7627b29085.png)
![5209ac4f38475ead56b0720c9dec79a7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635660/b1e9998a-9e00-3dd8-e6f3-4ffa255cde21.png)
　上記のプログラムでは、5種類の選択肢を持つセレクトメニューを追加し、ボタンを押したユーザーに対してそれぞれメンション付きで選択した選択肢を返信する。
　`@discord.ui.select`のclsには、セレクトメニューの種類を表すクラスを指定する。今回、指定しているdiscord.ui.Selectは一番基本的なクラスで、ユーザーが任意に選択肢を設定することができる。選択肢はdiscord.ui.Selectの場合は、optionsにdiscord.SelectOptionのリストとしてそれぞれlabelを設定することで実現できる。選択肢のクラスは他にも、サーバー内のロールやチャンネルを選択できるもの等、複数種類存在する。詳細は[公式ドキュメント](https://discordpy.readthedocs.io/ja/latest/interactions/api.html?highlight=ui%20view#select-menus)を参照。

# おわりに

　当記事で紹介した要素以外にもv2.0で追加されたUI関連のアップデートでは、TextInputなどをModal(Modelではない)を利用して設置することができる。また、こちらのほうも機会があれば紹介しようと思う。最後に改めて、詳細な情報を得られたい方は[公式ドキュメント](https://discordpy.readthedocs.io/ja/latest/index.html)を参照されたい。