這是一篇 Discord Bot 的進階教學,會使用 [Python Cog 架構](https://hackmd.io/@smallshawn95/python_discord_bot_cog)撰寫。
在之前的 View 教學中,曾經提到 Item 元素跟 View 息息相關,此次教學要來詳細介紹和實作 Discord 中的 Button,如果還沒看過 View 教學的讀者可以點此前往,[Python Discord Bot 進階教學 — View 篇](https://hackmd.io/@smallshawn95/python_discord_bot_view)。
:::success
:book: **更多 Python Discord Bot 教學系列和程式範例**
https://github.com/smallshawn95/Python-Discord-Bot-Teach.git
:::
---
[TOC]
---
## 一、Button 簡介:
Discord Button 於 2021 年 10 月推出,具有多種使用方式,可以傳送訊息、觸發特定事件、將使用者傳送到指定網址等等。
Discord Button 推出之前,開法者如果想要讓 Bot 可以利用 GUI 介面來實現投票、換頁、切歌等等功能,通常只能使用簡單的 Emoji,並且需要訊息輔助說明每個 Emoji 的用途,而現在的 Button 則在一開始就可以設定顯示文字、顏色等等,讓使用者可以直觀了解用途,也讓開發者可以根據自己需求創作。
更多詳細資訊可以參考 [Discord.py Button 官方文檔](https://discordpy.readthedocs.io/en/stable/interactions/api.html#id1)。
## 二、Button 撰寫:
### Class
使用時機為函式中直接宣告時,設置 `url` 參數會變超連結按鈕,撰寫 Button 交互事件需要新增一個回呼函式或者搭配持續監聽事件。
```python
discord.ui.Button(label: str, style: discord.ButtonStyle, emoji: Union[PartialEmoji, Emoji, str], custom_id: str, disabled: bool, url: str, row: int)
```
* **label 標籤**
Button 顯示的文字。
* **style 風格**
Button 的風格,參考[註一](#註一)。
* **emoji 表情符號**
Button 的表情符號。
* **custom_id 交互 ID**
交互期間收到的 Button ID,如果設置為 URL Button 則沒有自定義 ID。
* **disabled 禁用**
Button 是否禁用。
* **url 網址**
Button 導航使用者前往的網址。
* **row 相對行號**
Discord 訊息允許 Item 元素最多 5 行,預設情況會自動排序,如果要控制相對位置,則行號 row = 1 會在 row = 3 之前,行號必須介於 0 到 4 之間。
### 裝飾器
使用時機為添加在 View 類別中,撰寫 Button 交互事件只需要在裝飾器底下即可。
```python
@discord.ui.button(label: str, style: discord.ButtonStyle, emoji: Union[PartialEmoji, Emoji, str], custom_id: str, disabled: bool, row: int)
```
* **label 標籤**
Button 顯示的文字。
* **style 風格**
Button 的風格,參考[註一](#註一)。
* **emoji 表情符號**
Button 的表情符號。
* **custom_id 交互 ID**
交互期間收到的 Button ID,如果設置為 URL Button 則沒有自定義 ID。
* **disabled 禁用**
Button 是否禁用。
* **row 相對行號**
Discord 訊息允許 Item 元素最多 5 行,預設情況會自動排序,如果要控制相對位置,則行號 row = 1 會在 row = 3 之前,行號必須介於 0 到 4 之間。
## 三、Button 交互方式:
### 回呼函式(Callback)
額外撰寫一個交互函式,並讓 Button 呼叫此函式。
| 優點 | 缺點 |
| :-: | :-: |
| 多個按鈕可以共用同個函式 | 可讀性較差,查看按鈕功能需要自行對照函式 |
* 回呼函式
```python=
async def button_callback(interaction: discord.Interaction):
await interaction.response.edit_message(content = "Hello, world!")
```
* 主函式
```python=
@app_commands.command(name = "button_interaction_callback", description = "Button 回呼函式交互")
async def button_interaction_callback(self, interaction: discord.Interaction):
# 宣告 View
view = discord.ui.View()
# 使用 class 方式宣告 Button
button = discord.ui.Button(
label = "Click",
style = discord.ButtonStyle.blurple
)
# Button 連接回呼函式
button.callback = button_callback
# 將 Button 添加到 View 中
view.add_item(button)
await interaction.response.send_message(view = view)
```


### 被裝飾函式(Decorator)
在 Button 裝飾器底下撰寫一個交互函式。
| 優點 | 缺點 |
| :---: | :---: |
| 按鈕與函式綁定,不用額外對照函式 | 多一個 `button: discord.ui.Button` 參數 |
* 自定 View
```python=
class ButtonView(discord.ui.View):
def __init__(self, timeout: float | None = 180):
super().__init__(timeout = timeout)
# 使用裝飾器方式創建 Button,交互函式直接寫在裝飾器底下
@discord.ui.button(
label = "Click",
style = discord.ButtonStyle.blurple
)
async def button_decorator(self, interaction: discord.Interaction, button: discord.ui.Button):
await interaction.response.edit_message(content = "Hello, world!")
```
* 主函式
```python=
@app_commands.command(name = "button_interaction_decorator", description = "Button 被裝飾函式交互")
async def button_interaction_decorator(self, interaction: discord.Interaction):
# 宣告自定 View
view = ButtonView()
await interaction.response.send_message(view = view)
```


### 持續監聽事件(Event Listener)
`on_interaction()` 是一個交互持續監聽事件,幫 Button 自定義設置 `custom_id` 參數,比對交互時的 `custom_id` 即可執行相對應的操作。
| 優點 | 缺點 |
| :---: | :---: |
| 按鈕不會超時,可以製作永久按鈕 | 需要設置 `custom_id` 參數,按鈕操作塞在同一函式的可讀性較差 |
* 持續監聽函式
```python=
@commands.Cog.listener()
async def on_interaction(self, interaction: discord.Interaction):
# interaction.data 是一個包含交互資訊的字典
# 有些交互不包含 custom_id,需要判斷式處理來防止出錯
if "custom_id" in interaction.data:
if interaction.data["custom_id"] == "hello_world":
await interaction.response.edit_message(content = "Hello, world!")
```
* 主函式
```python=
@app_commands.command(name = "button_interaction_on", description = "Button 持續監聽交互")
async def button_interaction_on(self, interaction: discord.Interaction):
# 宣告 View
view = discord.ui.View()
# 使用 class 方式宣告 Button 並設置 custom_id
button = discord.ui.Button(
label = "Hello, world!",
style = discord.ButtonStyle.blurple,
custom_id = "hello_world"
)
# 將 Button 添加到 View 中
view.add_item(button)
await interaction.response.send_message(view = view)
```


## 四、文章註解:
### 註一
底下列出 Discord Button 的所有風格,詳細內容可參考 [Discord.py ButtonStyle 官方文檔](https://discordpy.readthedocs.io/en/stable/interactions/api.html#discord.ButtonStyle)。
| 名稱 | 樣式 |
| :-----: | :-----: |
| primary / blurple | 藍色 |
| secondary / grey / gray | 灰色 |
| success / green | 綠色 |
| danger / red | 紅色 |
| link / url | 超連結 |

---
:::info
📢 **歡迎加入我的 Discord 伺服器**
https://discord.gg/Jtd3eVrFJs
:::
*Copyright © 2023 SmallShawn95. All rights reserved.*