iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0

第一週先使用 !hello!ask 了解 Discord Bot 如何接收文字指令。

文字指令的優點是程式簡單,但使用者必須記得指令名稱與輸入格式。如果少打一個空格、忘記參數,Bot 才會在收到訊息後發現問題。

今天開始改用 Slash Command。使用者在 Discord 輸入 / 時,就能直接看到 Bot 提供的指令、說明與參數提示。

這次要改哪些地方?

  • 確認 Bot 具有 applications.commands Scope
  • 建立 /hello 指令
  • 了解 CommandTree.sync() 的用途
  • 使用 Ephemeral 回覆只有操作者需要看到的訊息

/helpdefer() 和 Followup 也會先放進文章,但它們是後面指令增加、模型開始處理耗時工作時才會真正用到的功能。


文字指令與 Slash Command 的差別

文字指令是一般 Discord 訊息:

!hello
!ask 請介紹 Python

Bot 必須讀取訊息內容,再由 commands.Bot 判斷這是不是一個指令。因此,程式和 Discord Developer Portal 都要啟用 Message Content Intent。

Slash Command 則是 Discord 原生的應用程式指令:

/hello
/help

使用者輸入 / 後,可以直接從選單看到指令說明。需要參數時,Discord 也會顯示欄位,減少輸入格式錯誤。

這兩種指令可以同時存在。這次保留 !hello,正式操作再逐步移到 Slash Command。

確認 applications.commands Scope

建立邀請連結時,Bot 除了 bot Scope,也需要 applications.commands,Slash Command 才能出現在伺服器中。

可以到 Discord Developer Portal 的 OAuth2 頁面重新確認:

OAuth2 → URL Generator → Scopes

勾選:

  • bot
  • applications.commands

如果先前的邀請連結沒有包含 applications.commands,可以重新產生連結並再次授權 Bot。

https://ithelp.ithome.com.tw/upload/images/20260922/20183880JxfDLXMsNl.png

建立第一個 /hello

目前的 bot.py 已經使用自訂的 MyBot 類別。Slash Command 會註冊在 bot.tree

@bot.tree.command(
    name="hello",
    description="確認機器人是否在線",
)
async def slash_hello(interaction: discord.Interaction):
    await interaction.response.send_message(
        f"你好,{interaction.user.mention}!",
        ephemeral=True,
    )

這裡的函式名稱使用 slash_hello,是為了和原本的文字指令函式 hello 區分。Discord 實際顯示的名稱仍由 name="hello" 決定。

Slash Command 收到的不是文字指令使用的 ctx,而是 discord.Interaction。其中包含執行指令的使用者、伺服器、頻道與這次互動的回覆狀態。

什麼是 ephemeral?

程式將 ephemeral 設為 True

await interaction.response.send_message(
    "Bot 已正常上線。",
    ephemeral=True,
)

Ephemeral 訊息只有執行指令的使用者看得到,不會占用整個頻道。

它適合用在:

  • 指令使用說明
  • 參數錯誤提示
  • 只有操作者需要知道的狀態
  • 不希望干擾其他成員的回覆

如果內容是會議結果或 Agent 的公開討論,就不應使用 Ephemeral,否則其他成員看不到。

https://ithelp.ithome.com.tw/upload/images/20260922/20183880QlMZqOBYoy.jpg

為什麼需要同步 CommandTree?

只在 Python 中寫好 Slash Command 還不夠,還要把指令資料同步到 Discord。

目前可以放在 setup_hook()

class MyBot(commands.Bot):
    async def setup_hook(self):
        synced_commands = await self.tree.sync()
        print(f"已同步 {len(synced_commands)} 個斜線指令")

setup_hook() 會在 Bot 登入過程中執行一次,適合處理指令同步等初始化工作。

self.tree.sync() 沒有指定 Guild 時會同步全域指令。全域指令適合正式使用,但更新後不一定立刻顯示。

開發階段也可以只同步到測試伺服器。Guild 指令通常比較適合快速測試,但需要額外提供 Guild ID。這個系列先保留全域同步,讓程式碼維持簡單。

不建議在 on_ready() 每次觸發時都重複同步。Bot 重新連線時,on_ready() 可能再次執行;初始化工作放在 setup_hook() 會比較清楚。

使用 Embed 建立 /help

目前只有少量指令,其實使用純文字就夠了。這裡先準備一個 Embed 版本,之後指令增加時不用重新整理顯示方式。

@bot.tree.command(
    name="help",
    description="顯示機器人指令說明",
)
async def help_command(interaction: discord.Interaction):
    embed = discord.Embed(
        title="機器人指令說明",
        description="以下是目前可以使用的指令:",
        color=discord.Color.green(),
    )

    embed.add_field(
        name="/hello",
        value="確認機器人是否在線。",
        inline=False,
    )
    embed.add_field(
        name="!ask 問題",
        value="將問題交給本機 Qwen。",
        inline=False,
    )

    await interaction.response.send_message(
        embed=embed,
        ephemeral=True,
    )

Embed 可以將標題、說明與不同指令分開顯示。這裡仍使用 Ephemeral,因為說明頁只需要讓查詢者看到。

https://ithelp.ithome.com.tw/upload/images/20260922/20183880NNH7dIxdC5.jpg

模型需要時間時,先使用 defer()

/hello/help 都能立刻產生結果,所以可以直接呼叫:

interaction.response.send_message()

但 Qwen 推論可能需要數秒,第一次執行還可能包含模型載入時間。這種指令不適合等模型完成後才回應 Discord。

可以先使用 defer() 告訴 Discord:「這個指令已經收到,Bot 還在處理。」

await interaction.response.defer(thinking=True)

result = await llm_service.chat(question)

await interaction.followup.send(result.content[:1900])

呼叫 defer(thinking=True) 後,Discord 會顯示 Bot 正在處理。

一次 Interaction 只能有一個初始回應。defer() 已經占用初始回應,因此模型完成後不能再次呼叫 interaction.response.send_message(),而要改用:

interaction.followup.send()

可以把流程記成:

收到 Slash Command
    ↓
defer() 先確認收到
    ↓
執行耗時工作
    ↓
followup.send() 傳送結果

今天先把流程記下來,等模型問答改成 Slash Command 時再實際使用。

統一處理 Slash Command 錯誤

這段同樣先當作後續會用到的準備。指令一多,如果每個地方都各自處理錯誤,訊息很容易不一致。

文字指令使用 on_command_error(),Slash Command 則可以註冊 bot.tree.error

@bot.tree.error
async def on_app_command_error(interaction, error):
    message = "指令執行時發生錯誤,請稍後再試。"

    if interaction.response.is_done():
        await interaction.followup.send(
            message,
            ephemeral=True,
        )
    else:
        await interaction.response.send_message(
            message,
            ephemeral=True,
        )

這裡先檢查 interaction.response.is_done(),原因和前面的 defer() 相同:

  • 尚未初次回覆:使用 interaction.response.send_message()
  • 已經回覆或 defer:改用 interaction.followup.send()

正式專案還可以針對冷卻時間、權限不足與參數錯誤顯示不同訊息,並只把完整例外寫入終端機日誌。

啟動並測試

啟動 Bot:

source .venv/bin/activate
python bot.py

終端機預期會看到同步數量,例如:

已同步 2 個斜線指令
機器人已上線:Bot 名稱

接著在 Discord 輸入 /,確認選單中是否出現:

  • /hello
  • /help

如果指令沒有出現,可以依序檢查:

  1. Bot 邀請連結是否包含 applications.commands
  2. setup_hook() 是否有執行 self.tree.sync()
  3. 終端機是否出現同步錯誤。
  4. 使用的 Discord 伺服器是否為 Bot 已加入的伺服器。

全域指令更新可能不會立即反映,因此不要只靠重複啟動 Bot 判斷同步失敗。

https://ithelp.ithome.com.tw/upload/images/20260922/20183880dnQ8N4Rfki.png

https://ithelp.ithome.com.tw/upload/images/20260922/20183880DTzDZCUxe5.png

Slash Command 接好後

這篇先完成 Slash Command 的程式與同步方式。/hello/help 是否出現在 Discord,仍要等實際操作後確認並補上畫面。

後面把模型問答改成 Slash Command 時,才會實際用 defer() 先回應,再透過 Followup 傳送結果。下一篇開始建立專案資料與 /projects


上一篇
DAY 7|整理設定,替 Discord Bot 加上測試
下一篇
DAY 9|建立專案資料與 /projects
系列文
AI 公司模擬器:Discord x Multi-Agent 架構實作9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言