Webhook 媒體相簿:整合照片展示而不遺失訊息
透過 webhook 接收媒體相簿,應先獨立儲存每則子訊息,再用相簿識別碼建立整合畫面。不要按相簿 ID 對子訊息去重,否則同一相簿內的不同照片會被刪走。若載荷沒有提供預期總數,等候一段沒有新資料的時間可以觸發處理,但不代表所有照片已經到齊。
重點
- 訊息 ID 用來逐則儲存,相簿 ID 用來關聯。
- 兩種識別碼都需要帳戶及對話範圍。
- 先持久化子訊息,再確認投遞;整合工作在背景執行。
- 計時器觸發後仍保留相簿狀態,容許延遲項目加入。
相簿 ID 並不是訊息 ID
Telegram 官方 Bot API 文件將 Message.media_group_id 定義為可選欄位,識別訊息所屬、在該聊天內唯一的媒體群組。它描述訊息之間的關係,不會取代每則訊息本身的識別碼。
UnifyPort 使用另一套契約。標準 webhook 參考列出可選的 data.message.album,包含 id、index 及可選的 total。相簿子項仍保留自己的 data.message.id。文件內的相簿例子來自 WhatsApp;不能因此假定每個平台都會提供相簿資料,亦不能假定 Telegram 原始欄位會出現在標準載荷內。
如尚未完成附件提取,可先看媒體附件對應指南。整合相簿是額外的展示層,不是另一套檔案下載協定。
| 識別方式 | 建議範圍 | 用途 |
|---|---|---|
| 一般事件 ID | 工作區及事件流 | 辨識同一事件的重複投遞 |
| 子訊息 ID | 工作區、平台、訊息帳戶、對話 | 儲存或更新一則訊息 |
| 相簿 ID | 工作區、平台、訊息帳戶、對話 | 關聯多個子項 |
| 附件 URL | 所屬子訊息紀錄 | 定位媒體,不作為分組鍵 |
以假設提交三張照片為例:B 到達兩次、C 到達一次,只代表兩個不同子項,而非三個。HTTP 請求數量不能代表相簿完整度。
先建立子訊息紀錄
以下 JavaScript 從已驗證的事件建立應用程式儲存鍵。回傳屬性是本地設計,並非新 API 欄位;程式不包括驗證簽署或持久化。
function albumKeys(workspaceKey, event) {
if (event.type !== 'message.received') return null;
const conversation = event.data?.conversation;
const message = event.data?.message;
if (!event.provider || !event.account_id ||
!conversation?.id || !message?.id) {
throw new Error('Missing message identity');
}
const scope = [workspaceKey, event.provider,
event.account_id, conversation.id];
return {
childKey: JSON.stringify([...scope, message.id]),
albumKey: message.album?.id
? JSON.stringify([...scope, message.album.id])
: null
};
}
建議採用以下交易流程:
- 按投遞契約驗證原始請求。啟用
signing_secret後,以時間戳、句點及原始本文驗證 HMAC-SHA256,並檢查時間戳是否足夠新。 - 儲存事件,以完整訊息識別範圍建立唯一約束,再新增或更新子訊息。保留說明文字、附件、
sent_at及已有的相簿資料。 - 將子項關聯至相簿。沒有相簿 ID 就保留為獨立訊息,不按相同文字或相近到達時間猜測關係。
- 提交紀錄及持久化工作項目後才回傳 2xx,由背景 worker 更新畫面及下載媒体。
使用同一交易或持久化 outbox,避免程式中斷後出現「訊息已儲存、整合工作卻遺漏」。每則子訊息的說明文字都要保留,不能讓最後一次投遞覆蓋唯一的相簿說明。
決定處理時機,不作完整性承諾
以下是應用程式狀態建議,不是平台事件或 API 欄位。
| 觀察 | 建議處理 |
|---|---|
| total 一致,而不同子項數量已達該值 | 標記預期數量已達到,檔案就緒狀態另計 |
| 缺少 total | 按本地設定的靜默時間處理暫定快照 |
| total 有衝突或 index 重複 | 保留子訊息,標記資料待查 |
| 處理後又收到新子項 | 更新畫面,執行延遲項目策略 |
| 同一子項再次投遞 | 冪等更新,不增加不同子項數量 |
同一相簿的更新應順序執行,或使用交易版本檢查,避免多個 worker 同時讀取舊狀態而重複建立後續工作。儲存每次處理決定及所包含的子訊息識別碼。新照片延遲到達時,明確選擇更新畫面、補充分析或交由人手檢查,不要自動再次向客戶發訊息。
保留 album.index 用於展示,但不要從單一例子推斷文件未說明的索引起點。索引缺少或衝突,不應令子項消失。等候時間只是應用程式的延遲政策,不是平台的完成訊號。
檔案就緒狀態要分開
即使已收到預期數量的訊息,也可能仍有檔案未能使用。UnifyPort 文件說明附件 URL 是臨時網址,過大檔案亦可能不含 URL。應及時安排下載,分別顯示附件成功或失敗狀態。
下載連結失效復原指南解釋了為何不能直接以 Telegram Bot API 的檔案更新方法處理 UnifyPort 附件 URL。不要為了等相簿完整而無限延後儲存已可下載的檔案。
UnifyPort 是非官方介面,標準事件流不保證各平台資料完全一致。它沒有 REST 訊息歷史讀取 API,亦不保證重播遺漏載荷。需要原生機械人契約時,應使用官方 Bot API,不要在同一解析器混用兩套結構。
驗收與常見問題
建議測試重複子項、次序錯亂、不同對話的相同相簿 ID、缺少總數、資料衝突,以及處理後才到達的照片。這些是建議測試,不是已執行結果。
可以用 album.id 對子訊息去重嗎?
不可以。不同訊息可以共用它,應使用連同範圍的子訊息 ID 儲存。
total 達標是否代表檔案已下载?
不是。total 在提供時描述預期分組大小,檔案持久化是另一個階段。
沒有 total 的相簿應否丟棄?
不應丟棄。保留子項,按明確策略產生暫定畫面,並容許後續補入。
下一步與來源
先依標準事件參考完成子訊息儲存,再啟用相簿自動化。
核對日期:2026-09-29。
令訊息接入變成一條穩定嘅產品管線。
先用統一嘅 API 跑通發送,再用標準事件將所有入站訊息接返去業務系統。