架構總覽
discli 是一個以 Click 與 discord.py 建置的 Python CLI。它有兩種模式:
- 一次性指令(發起後不需維持連線的 CLI 指令),用於單次操作;
- 持續式 serve 模式,用於與 AI 代理即時雙向溝通。
高階請求流程
每次呼叫 discli 都會依照以下流程,從使用者輸入到 Discord API 呼叫:
flowchart LR
A[使用者 / 代理] --> B[discli CLI]
B --> C[Click 框架]
C --> D[權限檢查]
D --> E[Token 解析]
E --> F[discord.py 用戶端]
F --> G[Discord API]- 使用者(或 AI 代理)呼叫一個
discli指令。 - Click 解析參數與全域選項(
--token、--json、--yes、--profile)。 - 檢查目前啟用的權限設定檔,確認指令是否被允許。
- 從旗標、環境變數或設定檔解析機器人權杖(token)。
- 單次指令會驗證 HTTP 用戶端並直接執行;即時指令會建立 Gateway 用戶端並套用對應的 intent。
- Discord API 處理請求。
模組依賴關係圖
flowchart TD
CLI[cli.py] --> CMD[commands/*]
CLI --> PERM[permission group]
CLI --> AUDIT[audit group]
CMD --> CLIENT[client.py]
CLIENT --> SEC[security.py]
CLIENT --> DISCORD[discord.py]
CMD --> UTILS[utils.py]
CMD --> SEC
CLI --> CFG[config.py]
SEC --> CFG_DIR["~/.discli/"]
CFG --> CFG_DIR
style CLI fill:#4f46e5,color:#fff
style CMD fill:#7c3aed,color:#fff
style CLIENT fill:#2563eb,color:#fff
style SEC fill:#dc2626,color:#fff
style CFG fill:#059669,color:#fff
style UTILS fill:#d97706,color:#fff模組角色
| 模組 | 檔案 | 用途 |
|---|---|---|
| CLI 進入點 | cli.py | Click 根命令群組。註冊所有指令群組,定義全域選項,並掛載 permission 與 audit 子命令。 |
| 命令群組 | commands/*.py | 每個檔案定義一個 Click 命令群組。一次性動作使用 run_rest();即時狀態與語音動作走 Gateway 路徑。 |
| Client | client.py | 解析 token、分離 REST 與 Gateway 生命週期、執行權限檢查,並建立 Gateway 功能所需的最小 intents。 |
| 安全模組 | security.py | 權限設定檔(允許/拒絕清單)、is_command_allowed() 檢查、audit_log() JSONL 紀錄、RateLimiter(漏桶速率限制)、check_user_permission() Discord 層權限檢查,以及高風險操作確認。 |
| Config | config.py | 讀取與寫入 ~/.discli/config.json。處理權杖持久化與設定整併。 |
| Utils | utils.py | output() 支援 --json;resolve_channel() 與 resolve_guild() 將名稱/ID 轉為 discord.py 物件。 |
| Serve | commands/serve.py | 持續式機器人模式。由 stdin 讀取 JSONL,將 JSONL 輸出到 stdout。約 1100 行,涵蓋 25+ 個動作、事件轉發、斜線指令註冊,以及 1.5 秒批次輸出。 |
兩種運作模式
模式一:一次性 CLI 指令
這是預設模式。每個指令建立一個短生命週期的 HTTP 驗證 discord.Client,執行單一操作後即退出,不會開啟 Gateway 會話。
sequenceDiagram
participant User
participant CLI as discli CLI
participant Client as discord.Client
participant API as Discord API
User->>CLI: discli message send "#general" "Hello"
CLI->>CLI: 解析參數、檢查權限並解析 token
CLI->>Client: 建立用戶端並驗證 token
Client->>API: 透過 HTTP 送出訊息
API-->>Client: 200 OK
Client->>Client: 關閉連線
CLI-->>User: 「訊息已送出至 #general」這種方式簡潔且無狀態。它會受 Discord HTTP 速率限制影響,但不會送出 Gateway IDENTIFY、不會消耗 session-start 配額,且不需要不相關的 Gateway intents。
最適用於: 腳本、自動排程任務、單次操作、串接其他工具。
模式二:持續式 serve 模式
discli serve 會啟動長生命週期機器人,透過 stdin/stdout 使用逐行 JSON(JSONL)溝通。機器人會維持與 Discord Gateway 連線並即時轉發事件。
sequenceDiagram
participant Agent as AI Agent
participant Serve as discli serve
participant API as Discord API
Serve->>API: Gateway 連線
API-->>Serve: on_ready
Serve-->>Agent: {"event":"ready","bot_name":"..."}
API-->>Serve: 新增訊息事件
Serve-->>Agent: {"event":"message","content":"Hi bot",...}
Agent->>Serve: {"action":"reply","channel_id":"...","message_id":"...","content":"Hello!"}
Serve->>API: 送出回覆
API-->>Serve: 200 OK
Serve-->>Agent: {"event":"response","status":"ok","message_id":"..."}Serve 模式會維持持續狀態:
- Typing 指示器:逐頻道啟停(透過動作)
- 串流編輯:每 1.5 秒批次推送內容更新,以配合 Discord 的速率限制
- Slash 指令註冊:啟動時逐伺服器同步
- 互動 token:用於延遲 slash 指令回應
最適用於: AI 代理、聊天機器人、即時監控、互動式應用。
Serve 模式改用執行緒型 stdin 讀取器,而非 asyncio.connect_read_pipe,因為當 stdin 由父行程透過 pipe 提供時,後者在 Windows 會失敗。讀取器執行緒將每行資料放進 queue.Queue,而 asyncio 事件迴圈透過 run_in_executor 取回。
命令註冊
所有命令群組皆在 cli.py 中透過 main.add_command() 註冊:
main.add_command(channel_group)main.add_command(config_group)main.add_command(dm_group)main.add_command(listen_cmd)main.add_command(member_group)main.add_command(message_group)main.add_command(reaction_group)main.add_command(role_group)main.add_command(server_group)main.add_command(poll_group)main.add_command(thread_group)main.add_command(typing_cmd)main.add_command(serve_cmd)main.add_command(permission_group)main.add_command(audit_group)每個命令群組都對應 commands/ 中的一個檔案。新增命令群組採同樣流程:先在新檔案定義 Click 命令,再在 cli.py 註冊該群組。
主要設計決策
| 決策 | 理由 |
|---|---|
| Click 優於 argparse | Click 提供巢狀命令群組、內建說明產生與 context 傳遞,與 discli 的 discli <group> <command> 架構天然匹配。 |
| 每個 CLI 指令建立暫時 client | 命令可維持無狀態,且降低連線管理複雜度。1 到 2 秒的啟停成本可接受於腳本使用情境。 |
| serve 通訊使用 JSONL | 逐行 JSON 在任何語言都容易解析、可串流、格式明確。任意代理框架都能讀寫。 |
| 權限設定檔放在 CLI 層 | 可在發出任何 Discord API 呼叫前即阻擋誤用。即使機器人在 Discord 層有權限,readonly 設定檔也無法傳送訊息。 |
| 安全模組獨立 | 權限檢查、稽核紀錄與速率限制集中在同一模組,讓所有指令路徑都走同一套規則。 |