DiscordBOTでドロップダウンメニューやボタンを制御する

# はじめに
Discordでは様々なuiが準備されています。今回はドロップダウンメニューやボタンの使い方や制御について、この記事では紹介します。
基本的には下記で紹介したテンプレートをベースに、拡張して作成します。

https://qiita.com/archa0147/items/05c3a57f0335d0666d29

ファイル構成は下記の通り。
```:ファイル構成
.
┣━ main.py
┣━ .env
┗━ cogs
　　┣━ commands.py
　　┗━ listenner.py
```

今回はcommands.pyを修正して作成します。

# 1.ボタンとドロップダウンメニューを出力するコマンドを作成する
こんな感じのボタンやドロップダウンメニューを出力するコマンドを作成してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3388610/2f6b649f-d988-445c-ba9e-e4310c30d296.png)


```python:commands.py
import os,discord
from discord.ext import commands
from discord import app_commands

# DiscordサーバのIDを取得
GUILD_ID  = os.getenv('GUILD_ID')

# ボタンの挙動を定義するクラス
class Button(discord.ui.Button):
    def __init__(self, label,style):
        super().__init__(label=label,style=style)
    async def callback(self, interaction: discord.Interaction):
        await interaction.response.send_message(content=self.label+'ボタンが押されました。')

# ボタンのビューを定義するクラス
class ButtonView(discord.ui.View):
    def __init__(self, timeout=180):
        super().__init__(timeout=timeout)
        self.add_item(Button(label='OK', style=discord.ButtonStyle.success))
        self.add_item(Button(label='Cancel', style=discord.ButtonStyle.gray))

# ドロップダウンメニューの挙動を定義するクラス
class Dropdown(discord.ui.Select):
    def __init__(self,options):
        super().__init__(placeholder='好きな動物は何ですか？', min_values=1, max_values=1, options=options)

    async def callback(self, interaction: discord.Interaction):
        # 2回以上実行されないように非活性にする。
        self.disabled = True
        # edit_messageを使って変更したビューを反映する。
        await interaction.response.edit_message(view=self.view)
        # responseは１回のみのため、followupを使ってesonseに対して返信を行う。
        await interaction.followup.send(self.values[0]+'が選択されました。')

# ドロップダウンメニューのビューを定義するクラス
class DropdownView(discord.ui.View):
    def __init__(self):
        super().__init__()
        # ドロップダウンメニューのリスト
        options = [
            discord.SelectOption(label='dog', description='犬が好きです。'),
            discord.SelectOption(label='cat', description='猫が好きです。'),
            discord.SelectOption(label='bird', description='鳥が好きです。'),
        ]
        # viewにセレクトを追加
        self.add_item(Dropdown(options=options))

class MyCommands(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
    
    # ボタンを出力するスラッシュコマンドを追加
    @app_commands.command(name='button', description='ボタンを表示します。') 
    async def button(self,interaction: discord.Interaction):
        await interaction.response.send_message(view=ButtonView())

    # ドロップダウンメニューを出力するスラッシュコマンドを追加  
    @app_commands.command(name='select', description='ドロップダウンメニューを表示します。') 
    async def select(self,interaction: discord.Interaction):
        await interaction.response.send_message(view=DropdownView())

# BOTにcogを登録 ギルドコマンドとして登録
async def setup(bot: commands.Bot):
    await bot.add_cog(MyCommands(bot), guild=discord.Object(id=GUILD_ID))
```



イメージとしてはビューにボタンやドロップダウンメニューを配置して、それぞれのオブジェクトの`callback関数`で挙動を定義する感じです。なので一つのビューの中で複数のボタンやドロップダウンメニューなどを配置できます。
また、今回は1度だけ実行させたいので`Dropdown`の`callback関数`で`self.disabled = True`を指定して、ドロップダウンメニューを非活性化しています。何回でも実行しても問題なければ下記でOKです。

``` python
    async def callback(self, interaction: discord.Interaction):
        await interaction.response.send_message(self.values[0]+'が選択されました。')
```

# 2.対話的にuiを利用するやり方
例えば、ドロップダウンメニューを選択したのち、それで良いかどうかを確認したい場合があったとします。その場合、以下のようにドロップダウンメニューの後にOK,Cancelボタンを表示して選ばせるといった方法があります。
![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3388610/9918f339-f8c3-3315-d82e-36b00d7f06ea.gif)


以下それを実現させたコードです。
``` python:commands.py
import os,discord
from discord.ext import commands
from discord import app_commands

# DiscordサーバのIDを取得
GUILD_ID  = os.getenv('GUILD_ID')

# ボタンの挙動を定義するクラス
class Button(discord.ui.Button):
    def __init__(self, label,style):
        super().__init__(label=label,style=style)
    async def callback(self, interaction: discord.Interaction):
        await interaction.response.send_message(content=self.label+'ボタンが押されました。')

# ボタンのビューを定義するクラス
class ButtonView(discord.ui.View):
    def __init__(self, timeout=180):
        super().__init__(timeout=timeout)
        self.add_item(Button(label='OK', style=discord.ButtonStyle.success))
        self.add_item(Button(label='Cancel', style=discord.ButtonStyle.gray))

# ドロップダウンメニューの挙動を定義するクラス
class Dropdown(discord.ui.Select):
    def __init__(self,options):
        super().__init__(placeholder='好きな動物は何ですか？', min_values=1, max_values=1, options=options)

    async def callback(self, interaction: discord.Interaction):
        # edit_messageを使って表示をButtonViewへ変更する。
        await interaction.response.edit_message(content=self.values[0]+'が選択されました。よろしいですか？',view=ButtonView())

# ドロップダウンメニューのビューを定義するクラス
class DropdownView(discord.ui.View):
    def __init__(self):
        super().__init__()
        # ドロップダウンメニューのリスト
        options = [
            discord.SelectOption(label='dog', description='犬が好きです。'),
            discord.SelectOption(label='cat', description='猫が好きです。'),
            discord.SelectOption(label='bird', description='鳥が好きです。'),
        ]
        # viewにセレクトを追加
        self.add_item(Dropdown(options=options))

class MyCommands(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
    # ドロップダウンメニューを出力するスラッシュコマンドを追加  
    @app_commands.command(name='select', description='ドロップダウンメニューを表示します。') 
    async def select(self,interaction: discord.Interaction):
        await interaction.response.send_message(view=DropdownView())

# BOTにcogを登録 ギルドコマンドとして登録
async def setup(bot: commands.Bot):
    await bot.add_cog(MyCommands(bot), guild=discord.Object(id=GUILD_ID))
```

`Dropdown`の`callback関数`で使用するビューをドロップダウンメニュー(`DropdownView`)からボタン(`ButtonView`)へ変更しています。
`content`を設定することで、メッセージとともにボタンを表示することができます。
ちなみにビューを表示させたくない場合は`view=None`としてください。

# まとめ
uiを表示させるには、**viewに表示するuiを設定してsend_messageでviewを表示させるだけ**です。その他のuiは使ったことがないですが、おそらく同じような使い方だと思っています。(調べていません)
今回はここまで。