はじめに
この記事ではPythonのDiscordでBOTを作成する為のライブラリのpy-cord
を利用したUIの作成について説明させていただいています。
前提知識
主にpy-cord
でコマンドを実装したことがある方であればおおよそわかる内容かと思います。
もし、一度も実装した事がない場合はこちらの記事などを参考にして基本的なコマンドを実装してみてください。
python自体のクラス継承なども登場しますが、ある程度解説するので必須ではありません。
環境
- python 3.10.6
- py-cord 2.1.3
もっとも簡単な方法
py-cord
では簡潔な記述でボタンを実装するデコレータが用意されているため、最も簡単な実装では以下のようにボタンを実装することができます。
この情報はほとんどPycord公式のガイドとなりますので、そちらで参考してもいいと思います。
ボタンの場合
from discord.ext import commands
import discord
intent = discord.Intents.default()
intent.messages = True
bot = commands.Bot(
debug_guilds=[000000000], # Guild id
intent=intent
)
# Viewクラスを継承してButtonを持ったViewを作成します。
class TestView(discord.ui.View):
# デコレータを使ってButtonオブジェクトをViewのitemsに格納する
@discord.ui.button(label="ボタン1", style=discord.ButtonStyle.green)
async def first_button(self, button: discord.ui.Button, interaction: discord.Interaction):
button.disabled = True # ボタンの無効化
await interaction.response.edit_message(content="ボタンが押されました", view=self)
# Viewを呼び出すためのコマンドを定義
@bot.slash_command(name="view")
async def view_test(ctx: discord.ApplicationContext):
view = TestView()
await ctx.interaction.response.send_message(content="テストのボタンです", view=view)
bot.run("TOKEN")
簡単に解説するとデコレータのlabel
がボタンの表示名で、first_button
関数の引数になっているbutton
はself
(Viewのこと)に存在するbuttonオブジェクトになります。
つまり、この動作ではボタン1
のラベルのボタンが押された時そのボタンを無効化(グレーアウト)し、ボタンがおされた事を通知する動作となります。
この記述方法であれば
- Viewを継承クラスを用意する
- デコレータを使ってボタンの動作を定義する
- コマンドからViewを呼び出す
の3ステップで記述することが可能でとても扱いやすいです。
ドロップダウンメニューの場合
# Viewクラスを継承してButtonを持ったViewを
class TestView(discord.ui.View):
# デコレータを使ってSelectオブジェクトをViewのitemsに格納する
@discord.ui.select(placeholder="選択", options=[
discord.SelectOption(label="選択肢1", value="1"),
discord.SelectOption(label="選択肢2", value="2"),
discord.SelectOption(label="選択肢3", value="3"),
])
# ユーザーがドロップダウンメニューから選択したときの動作
async def select_menu(self, select: discord.ui.Select, interaction: discord.Interaction):
await interaction.response.edit_message(content=f"あなたが選択したのは {select.values[0]} です", view=self)
@bot.slash_command(name="view")
async def view_test(ctx: discord.ApplicationContext):
view = TestView()
await ctx.interaction.response.send_message(content="テストのドロップダウンメニューです", view=view)
こちらも簡単に解説するとデコレータのplaceholder
がドロップダウンメニューを選択していない時に表示されるラベルで、メニューを押した時にoptions
の配列から各labelを表示します。
select_menu
関数の引数になっているselect
はself
(Viewのこと)に存在するSelect
オブジェクトになります。
この動作ではselect
のラベルのメニューが選択された時、そのvalueの値を取得して選択したものを通知しています。
こちらも同様に以下の3ステップで実装可能です。
- Viewを継承クラスを用意する
- デコレータを使ってドロップダウンメニューの動作を定義する
- コマンドからViewを呼び出す
必ず任意のボタンを持たせた状態での実装
上記のオブジェクトはどちらのViewを継承しており、自作のViewクラスを更に継承することで任意のボタンを必ず持たせたViewを作成することが可能になります。
例えば、かならず終了する
ボタンがあるViewを送信したいときなどに便利です。
# 必ず表示させたいボタンを持ったViewを定義します
class BaseView(discord.ui.View):
# rowを4に設定し一番下に表示されるようにする
@discord.ui.button(label="終了する", style=discord.ButtonStyle.red, row=4)
# もし、コールバック関数の中で使用しない引数が存在してしまう場合は _ にすると親切です
async def exit_button(self, _: discord.ui.Button, interaction: discord.Interaction):
await interaction.response.edit_message(content="お疲れ様でした", view=None)
await interaction.delete_original_message(delay=10)
# BaseViewを継承したクラスを定義します
class ButtonTest(BaseView):
@discord.ui.button(label="ボタン1", style=discord.ButtonStyle.green)
async def first_button(self, button: discord.ui.Button, interaction: discord.Interaction):
button.disabled = True
await interaction.response.edit_message(content="ボタンが押されました", view=self)
class SelectTest(BaseView):
@discord.ui.select(placeholder="選択", options=[
discord.SelectOption(label="選択肢1", value="1"),
discord.SelectOption(label="選択肢2", value="2"),
discord.SelectOption(label="選択肢3", value="3"),
])
async def select_callback(self, select: discord.ui.Select, interaction: discord.Interaction):
await interaction.response.edit_message(content=f"あなたが選択したのは {select.values[0]} です", view=self)
@bot.slash_command(name="testbutton")
async def view_test(ctx: discord.ApplicationContext):
view = ButtonTest()
await ctx.interaction.response.send_message(content="テストのボタンです", view=view)
@bot.slash_command(name="testselect")
async def view_test(ctx: discord.ApplicationContext):
view = SelectTest()
await ctx.interaction.response.send_message(content="テストのドロップダウンメニューです", view=view)
これによりどちらのコマンドを呼び出した場合にも終了する
ボタンが表示され動作します。
row
で指定できる数は0~4で各ボタンなどの順番を決めるのに役立ちます。
動的な実装を行う場合
先程は定数的なボタンやドロップダウンメニューを実装する方法を解説しました。
例えば「ユーザーがコマンドにて値を入力してそれを何かしらの方法で値を保持し、それをドロップダウンメニューで選択できるようにする。」といった動作をさせる場合はデコレータを利用した方法だと行うことができません。
このような動作を実装する場合はButtonクラスやSelectクラスを継承する必要が出てきます。
ここではSelect
クラスを継承して動的なメニュー表示を実装する方法を解説します。
# ユーザー入力を保存するためのlistを定義
options: list[discord.SelectOption] = []
# Selectクラスを継承し、処理の実装などを行います。
class UserInputSelect(discord.ui.Select):
def __init__(self, options: list[discord.SelectOption] = ...) -> None:
# placeholderは定数で良いのでここでは選択としています。
super().__init__(placeholder="選択", options=options)
# callbackメソッドをオーバーライドして処理を実装します。
async def callback(self, interaction: discord.Interaction):
value = self.values[0]
await interaction.response.edit_message(content=f"あなたが選択したのは{value}です")
class TestView(discord.ui.View):
def __init__(self, options: list[discord.SelectOption]):
# 5分後にボタンなどを無効にする様に設定
super().__init__(timeout=300, disable_on_timeout=True)
# optionsを使ってオブジェクトを作成しitemsに格納する
self.add_item(UserInputSelect(options=options))
# ドロップダウンメニューにデータを追加するためのコマンドを実装
@bot.slash_command(name="add_option")
async def add_option(ctx: discord.ApplicationContext, label: str, value: str):
# 受け取った値からoptionsに値を追加していきます。
options.append(discord.SelectOption(label=label, value=value))
await ctx.interaction.response.send_message(content=f"ラベル:{label}, 値:{value} で登録しました")
@bot.slash_command(name="select_menu")
async def select_menu(ctx: discord.ApplicationContext):
if len(options) > 0:
view = TestView(options=options)
await ctx.interaction.response.send_message(content="ドロップダウンメニュー", view=view)
else:
await ctx.interaction.response.send_message(content="まだ値が入力されていません。")
Select
クラスを継承することで任意の処理をオーバーライドしたcallback
に記述することが可能です。
options
を受け取りオブジェクトを作成し、add_item
メソッドにてitems
に格納しています。
デコレータを使った場合内部的に同じような事が行われているため、とても簡潔に記述することが可能となっています。
disable_on_timeout
はpy-cord 2.1からの機能みたいなので実装する場合は注意してください。
このオプションはtimeout
で設定された秒数が経過後にボタンなどをすべて無効にする処理を行うかの設定です。
不要な場合はFalse
にするかこの引数自体を消してしまっても問題ありません。
参考にさせて頂いたもの