iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0

開場故事

你可以把 Telegram plugin 想像成一個前台。

有人敲門、前台先看證件、驗明正身、確認這個人有沒有資格進來,然後才把訊息交給後面的工作台。
工作台不是立刻回一段字而已,它會先判斷這是哪個 session、哪個 thread、是不是要 streaming、要不要 typing、要不要先送 preview、最後還有沒有 fallback。

所以第 22 天要看的不是「Telegram 收到 webhook 後呼叫了誰」,而是:

Telegram 的訊息流,從收到到回覆,到底經過哪些關卡?

這裡最重要的一件事是:
OpenClaw 不把 webhook 當終點,它只是入口。

今天要解的問題

  • Telegram webhook 進來時先做了什麼保護?
  • 為什麼要先檢查 secret 再讀 body?
  • body 讀完後怎麼交給 bot?
  • 訊息進入 reply pipeline 後,怎麼處理 streaming、typing、reaction、fallback?
  • 為什麼一個 reply 會牽涉到 delivery、cleanup、status reaction?

架構總覽

Telegram 的訊息流,我會切成六段:

  1. webhook 入口
  2. request guards
  3. body parse 與 secret 驗證
  4. bot / dispatch / reply pipeline
  5. preview、typing、reaction、delivery
  6. cleanup 與 fallback

這幾段看起來像是很多 function,但本質上只有一句話:

先確保這包 update 是真的,再把它交給 OpenClaw 的工作流。

原始碼節錄

先看 webhook 入口怎麼守門。

📄 原始碼:extensions/telegram/src/webhook.ts:911-927

const handler = grammy.webhookCallback(bot, "callback", {
  secretToken: secret,
  onTimeout: "return",
  timeoutMilliseconds: TELEGRAM_WEBHOOK_CALLBACK_TIMEOUT_MS,
});

if (
  !applyBasicWebhookRequestGuards({
    req,
    res,
    rateLimiter: telegramWebhookRateLimiter,
    rateLimitKey: resolveTelegramWebhookRateLimitKey(req, path, opts.config),
  })
) {
  return;
}

const secretHeader = resolveSingleHeaderValue(req.headers["x-telegram-bot-api-secret-token"]);
if (!hasValidTelegramWebhookSecret(secretHeader, secret)) {
  res.shouldKeepAlive = false;
  res.setHeader("Connection", "close");
  respondText(401, "unauthorized");
  return;
}

這裡先做兩件事:

  • 限流
  • 驗證 webhook secret

再看 body 怎麼進來。

📄 原始碼:extensions/telegram/src/webhook.ts:931-946

const body = await readJsonBodyWithLimit(req, {
  maxBytes: TELEGRAM_WEBHOOK_MAX_BODY_BYTES,
  timeoutMs: TELEGRAM_WEBHOOK_BODY_TIMEOUT_MS,
  emptyObjectOnEmpty: false,
});
if (!body.ok) {
  if (body.code === "PAYLOAD_TOO_LARGE") {
    respondText(413, body.error);
    return;
  }
  if (body.code === "REQUEST_BODY_TIMEOUT") {
    respondText(408, body.error);
    return;
  }
  if (body.code === "CONNECTION_CLOSED") {
    respondText(400, body.error);
    return;
  }
  respondText(400, body.error);
  return;
}

Telegram update 還沒真的進 bot 之前,OpenClaw 先用 body limit 和 timeout 把入口收緊。
這不是保守而已,這是讓 webhook 入口在網路世界裡不要太好欺負。

最後才把 body 交給 handler。

📄 原始碼:extensions/telegram/src/webhook.ts(本機版本行號已變動,僅標到檔案)

await handler(body.value, reply, secretHeader, unauthorized);
if (!replied) {
  respondText(200);
}

這一步結束後,才算真的把 update 送進 Telegram bot 的流程。

接著看 reply side。

📄 原始碼:extensions/telegram/src/bot-message-dispatch.ts:2119-2119

const { onModelSelected, ...replyPipeline } = (
  telegramDeps.createChannelReplyPipeline ?? createChannelReplyPipeline
)({
  cfg,
  agentId: route.agentId,
  channel: "telegram",
  accountId: route.accountId,
  typing: {
    start: sendTyping,
    onStartError: (err) => {
      logTypingFailure({
        log: logVerbose,
        channel: "telegram",
        target: String(chatId),
        error: err,
      });
    },
  },
});

這裡就是 Telegram 開始進入 OpenClaw 共用 reply pipeline 的地方。

再往後,真正的分發是這樣:

📄 原始碼:extensions/telegram/src/bot-message-dispatch.ts:1787-1789

const result = await (telegramDeps.deliverReplies ?? deliverReplies)({
  ...deliveryBaseOptions,
  replies: [payload],
  onVoiceRecording: sendRecordVoice,
  silent: silentErrorReplies && payload.isError === true,
  mediaLoader: telegramDeps.loadWebMedia,
});
if (result.delivered) {
  deliveryState.markDelivered();
}

這段很重要。

它告訴你 reply 不只是「把字發出去」。
它還要經過:

  • delivery base options
  • voice recording
  • silent error reply policy
  • media loading
  • delivery state tracking

最後還有 cleanup 和 fallback:

📄 原始碼:extensions/telegram/src/bot-message-dispatch.ts:2955-2964

if (
  dispatchError ||
  (!deliverySummary.delivered &&
    (deliverySummary.skippedNonSilent > 0 || deliverySummary.failedNonSilent > 0))
) {
  const fallbackText = dispatchError
    ? "Something went wrong while processing your request. Please try again."
    : EMPTY_RESPONSE_FALLBACK;
  const result = await (telegramDeps.deliverReplies ?? deliverReplies)({
    replies: [{ text: fallbackText }],
    ...deliveryBaseOptions,
    silent: silentErrorReplies && (dispatchError != null || hadErrorReplyFailureOrSkip),
    mediaLoader: telegramDeps.loadWebMedia,
  });
  sentFallback = result.delivered;
}

OpenClaw 的習慣不是「失敗就算了」。
它會盡量補一個看得見的回覆,至少讓使用者知道系統有反應。

白話拆解

1. webhook 不是訊息處理的開始,而是風險控制的開始

你如果把 webhook 想成「外部來一個 JSON,裡面有 update」,就太天真了。

真正先做的事其實是:

  • 限流
  • 驗證 secret
  • 限制 body 大小
  • 限制讀取時間

這些動作都是在保護後面的主流程。

2. body 讀進來後,不代表可以信任

Telegram webhook 進來的 payload 只是「看起來像 update」。

OpenClaw 會把它交給 bot,但 bot 之後還會根據:

  • route
  • session
  • allowlist
  • conversation binding
  • reply mode

來決定這包訊息到底怎麼處理。

3. reply pipeline 是共用的,不是 Telegram 特製的

這一點很關鍵。

Telegram 的送出流程會用到 Telegram-specific delivery,但「reply 決策」本身是 OpenClaw 共用邏輯:

  • 要不要 typing
  • 要不要 streaming
  • 要不要 preview
  • 要不要 fallback

所以 Telegram 不是自己重造一套答案生成器。
它只是把自己的 UI 能力接到共用 pipeline 上。

4. cleanup 很重要,因為 preview 是暫態世界

Telegram 很適合做 preview:

  • 先送一段草稿
  • 等模型完成再 finalize
  • 不需要時把暫時訊息清掉

但這件事很容易留下骯髒狀態。

所以你會看到它很在意:

  • 已送出的 preview
  • archived previews
  • reasoning lane cleanup
  • fallback 是否已送出

這不是多做,而是在避免聊天室變成垃圾場。

設計取捨

  • 好處是入口安全,能先擋掉大部分垃圾流量
  • 好處是 reply flow 很完整,從 typing 到 delivery 都有記錄
  • 好處是失敗時有 fallback,不會讓使用者一頭霧水
  • 好處是 streaming 和 preview 可以共存
  • 代價是流程很長,讀 code 時要一直記得自己在哪一段
  • 代價是 webhook、bot、delivery、cleanup 四層都要一起看,不能只看一個檔案

Telegram 這條線很像一條生產線。
你只看最前面的輸入口,會覺得很簡單;你一路跟到最後,才知道它其實很會收尾。

今天的結論

  • Telegram webhook 入口先做限流、secret 驗證、body size 和 timeout 保護
  • body 不是一讀到就信,還要交給 bot / route / session / allowlist 去解讀
  • reply pipeline 是 OpenClaw 共用能力,Telegram 只是把它送到 Telegram UI
  • delivery 不是單純 send message,而是帶著 preview、typing、media、fallback 的完整流程
  • OpenClaw 對訊息流的要求不是「能回」,而是「能安全地回、穩定地回、收得乾淨」

下一步

第 23 天我要換到 LINE。
你會看到同樣是 plugin,但 LINE 的驗證方式、事件模型、rich message 表達法,跟 Telegram 完全不是同一種氣質。


上一篇
第 21 天:Telegram plugin 怎麼接到 OpenClaw 的核心
下一篇
第 23 天:LINE plugin 為什麼不是,Telegram 的複製貼上
系列文
30 天走進 OpenClaw:一個 AI Agent 的誕生、掙扎與進化23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言