外部處理器

外部處理器是你的程式碼與 Telegram 之間的橋樑。當建構器的邏輯不夠用時,你可以將特定反應交給自己的伺服器處理:伺服器接收事件並以 Telegram Bot API 格式回覆指令,而 GetMyBot 負責傳輸、投遞以及所有 Telegram 相關操作。

這是什麼

GetMyBot 中的反應以視覺化方式組建:觸發器 → 條件 → 動作。複雜或非標準的邏輯——自有資料庫、計算、呼叫第三方系統、ML、分支場景——在建構器中難以表達。外部處理器突破了這個上限:你連接自己的後端,並將整個反應的處理權交給它。

外部服務從 GetMyBot 接收事件(觸發器觸發、用戶回覆文字、點擊按鈕),並以指令告訴機器人要傳送什麼。GetMyBot 用自己的機器人 token 執行這些指令——在此之上有速率限制、重試、去重以及對話日誌。

運作模型

  • GetMyBot 是 WebSocket 伺服器。 你的服務主動連接到它,不需要公開地址或 webhook 端點。GetMyBot 不會主動向你的服務發起連線,因此不存在 SSRF 攻擊面。
  • 認證使用整合 token。 服務在連接時提供 token;GetMyBot 與其雜湊值進行比對並保持連線。
  • 事件與指令採用 Telegram Bot API 格式。 服務接收事件,並以「方法 + 參數」陣列的形式回覆,與操作普通機器人完全相同。
  • 指令由 GetMyBot 用自己的 token 執行。 服務從不直接與 Telegram 通訊:指令經過驗證後進入 GetMyBot 的內部 outbox,再由機器人 token 傳送(帶速率限制、重試、去重與日誌)。

一個連線服務一個整合的所有對話——不同用戶的事件透過多路複用區分,以 session_id 欄位識別。

設定整合

  1. 開啟左側選單中的整合區塊,點選新增整合
  2. 選擇外部處理器服務,並設定名稱。
  3. 建立後,卡片會顯示:
    • WebSocket 地址 — 格式為 wss://api.mybot.app/ext/ws/<integrationID>,其中 <integrationID> 是此整合的 UUID。你的服務透過此地址連接。路徑中沒有 ID 則無法建立連線。
    • 連接 token僅在建立時顯示一次。請複製並妥善儲存;token 只以雜湊形式存放,無法再次查看。
    • 重新產生 token — 此按鈕會產生新 token 並立即使舊 token 失效。重新產生後,請在你的服務中更新 token。
    • 上線狀態指示器 — 顯示你的服務目前是否保持活躍連線。

影響協議的額外整合參數:

  • session_ttl_seconds — 會話的存活時間(秒,預設為 3600)。
  • on_unavailable_reaction_id — 備用反應,當服務離線時觸發(請參閱可靠性)。

與反應的綁定

外部處理器不是以獨立觸發器連接,而是以動作的方式。在反應編輯器中新增移交給外部處理器動作,並選擇對應的整合。

對話可由 GetMyBot 任何現有的觸發器開啟——指令、文字、按鈕點擊、連結參數、傳入的 Web 請求、排程。當此類觸發器觸發並到達該動作時,GetMyBot 會開啟一個代理會話,並向服務傳送 session.open 事件。

如果此時服務未連接,GetMyBot 會觸發整合設定中的備用反應on_unavailable_reaction_id)。若未設定備用反應,則不會發生任何事(靜默,不向用戶顯示錯誤)。

連接與認證

端點:GET /ext/ws/{integrationID},透過 WebSocket(wss)。integrationID 是「外部處理器」類型整合的 UUID。訊框大小上限為 256 KiB。

認證有兩種方式。

1. 握手時的標頭(建議)。Authorization 標頭中傳遞 token:

Authorization: Bearer <token>

2. Auth 訊框。 若未傳遞 Bearer 標頭,請在連線建立後的 10 秒內傳送第一個訊框:

{ "type": "auth", "token": "<token>" }

若 10 秒內未收到 auth 訊框,連線將以代碼 4401 關閉。任何方式中的無效 token 同樣以 4401 關閉。

Token 在建立整合時產生,僅顯示一次,以 sha256 雜湊形式存放,比對時使用常數時間比較。Token 輪換:

POST /api/bots/{botID}/integrations/{integrationID}/rotate-token

端點返回新的明文 token;舊 token 立即失效。

事件:機器人 → 服務

每個事件是一個 JSON 訊框(EventEnvelope)。type 欄位為以下之一:session.openmessagecallbacksession.cancelsession.expired

完整的信封欄位:

{
  "type": "session.open",
  "session_id": "string",
  "bot_id": "string",
  "user": {
    "tg_user_id": 123456789,
    "first_name": "Ivan",
    "username": "ivan",
    "params": { "utm": "promo" }
  },
  "chat": { "id": 123456789, "type": "private" },
  "update": { "...": "raw Telegram Update (raw JSON)" },
  "event": { "...": "normalized GetMyBot event (raw JSON)" }
}
  • session_id — 會話(對話)的識別符。
  • bot_id — 機器人識別符。
  • user — 用戶資料:tg_user_id(int64)、可選的 first_nameusername,以及 params——用戶參數(包括來自連結的參數),鍵值對映射。
  • chat — 聊天:id(int64)與 type(例如 "private")。
  • update — 原始 Telegram Update(raw JSON)。在 session.openmessagecallback 中存在。
  • event — GetMyBot 正規化後的事件(raw JSON)。

各類型的內容有所不同:

  • session.open — 觸發器觸發,開啟新對話。包含 update(原始 Update)、event(完整正規化事件),user.params 已填入。
  • message — 當服務「保持等待」(expect = text/any)且用戶傳送訊息時傳送。內容與 session.open 相同。
  • callback — 用戶點擊了服務之前傳送的按鈕。update 為原始資料;user 中只填入 tg_user_idevent 僅包含服務在按鈕中設定的原始 callback_data:
{ "callback_data": "<服務在按鈕中設定的原始值>" }
  • session.cancel / session.expired — 最小信封:session_idbot_iduser.tg_user_idchat.idcancel 還包含 chat.type)。沒有 update/event 欄位。這些事件僅在服務上線時投遞——不會進入離線佇列。

指令:服務 → 機器人

指令是一個 JSON 訊框(CommandEnvelope)。type 欄位為以下之一:executesession.closeauthpingpong

{
  "type": "execute",
  "session_id": "string",
  "methods": [
    { "method": "sendMessage", "params": { "text": "Hello" } }
  ],
  "expect": "none"
}
  • session_id — 指令所屬的會話。
  • token — 僅用於 type: "auth"(請參閱連接與認證)。
  • methods{ "method": "<Telegram Bot API 方法名>", "params": { ... } } 物件的陣列。方法名與 Telegram Bot API 完全一致(例如 sendMessage),params 是該方法的參數物件。
  • expect — 服務是否等待用戶回覆:"none""text""any"(空值視為 none)。
  • session.close — 明確關閉對話。

指令不直接傳送至 Telegram:GetMyBot 驗證後將其放入自己的 outbox,再由機器人 token 傳送(帶速率限制、重試、去重與日誌)。

execute 的處理方式

在以下情況下,execute 指令會被整體丟棄:session_id 為空;會話未找到或狀態不是 open;會話屬於其他整合;會話已過期。

之後:

  • 速率限制: 每個會話每分鐘最多 60 個 execute(滑動視窗)。超過限制則丟棄。
  • 每個 execute 最多 30 個方法;多餘的會被靜默丟棄。
  • 每個方法都會檢查白名單(請參閱允許的方法)——不在清單中的方法會被拒絕。
  • 聊天防護: 如果 params 中的 chat_id from_chat_id 的值不為 0 且不等於會話的 chat_id,則整個方法被拒絕(不能向其他聊天傳送訊息)。chat_id 可以不指定:GetMyBot 會強制填入當前會話的聊天。
  • 帶有 callback_data 的 inline 按鈕會自動 token 化(內部格式 x:<token>);按鈕的存活時間 = 會話 TTL 之前。url/webapp/switch_inline 類按鈕不受影響。

允許的方法

服務透過機器人執行指令,因此可用方法受到白名單限制——僅限於目前對話的傳送與訊息操作。不在清單中的方法會被靜默忽略(不是錯誤)。

  • 傳送:sendMessagesendPhotosendDocumentsendVideosendAudiosendMediaGroupsendAnimationsendVoicesendLocationsendChatAction
  • 編輯與刪除:editMessageTexteditMessageCaptioneditMessageReplyMarkupdeleteMessage
  • 回應點擊:answerCallbackQuery
  • 轉發與複製:forwardMessagecopyMessage
  • 置頂:pinChatMessageunpinChatMessage

帳號層級的方法(setWebhookgetUpdateslogOutclosesetMyCommands 等)不被允許

等待與會話生命週期

每次 execute 之後,會話的命運取決於 expect 以及指令中是否包含帶有 callback_data 的 inline 按鈕:

  • expect = textany — 用戶的下一條訊息將以 message 事件轉發給服務。
  • expect = none 且指令中沒有 callback 按鈕 — 會話自動關閉。這是「終止」訊息。
  • expect = none,但有 callback 按鈕 — 會話保持開放:點擊事件會被捕捉,直到按鈕 token 的存活時間(TTL)到期。

重要:expect 不影響按鈕點擊的接收。 token 化按鈕的點擊與 expect 的值無關——只要會話和按鈕 token 有效就會被捕捉。expect 只控制服務是否等待文字/任意回覆。

對話邊界

對話上下文的存活時間恰好與會話開放的時間一樣長:

  • 活躍等待中的文字回覆expect = text/any)——以 message 事件傳給服務,而不是觸發機器人的普通反應。
  • token 化按鈕的點擊——以 callback 事件傳給服務,含原始 callback_data。GetMyBot 始終自動確認點擊(answerCallbackQuery);會驗證點擊的歸屬(機器人 + 用戶 + 會話聊天)。點擊不會關閉會話。
  • /cancel(以及 /cancel@bot 和「取消」)在活躍的外部會話中——會話關閉,向服務傳送 session.cancel,向用戶傳送「已取消。」。
  • 每個用戶一個對話。 開啟新會話會取代該用戶之前開啟的會話——向舊會話傳送 session.cancel
  • TTL 到期。 計時器到期後,會話標記為 expired,若服務在線則向服務傳送 session.expired

限制與逾時

  • 訊框大小: 256 KiB。
  • 會話 TTL: 預設 3600 秒(可透過整合的 session_ttl_seconds 參數設定)。
  • 指令頻率: 每個會話每分鐘 60 個 execute(滑動視窗)。
  • 每個 execute 的方法數: 最多 30 個(多餘的被丟棄)。
  • 每個整合的開放會話數: 最多 1000 個。
  • 離線佇列: 每個會話最多 100 個事件。
  • Heartbeat: 每 30 秒一個 ping;無活動逾時為 60 秒。
  • Auth 訊框逾時: 10 秒。

可靠性(heartbeat、離線、佇列、fallback)

  • Heartbeat。 伺服器每 30 秒傳送一個 {"type":"ping"} 訊框。若超過 60 秒沒有客戶端活動,連線關閉。客戶端可傳送自己的 ping/pong 保持活躍。連線中斷不會終止對話:會話在資料庫中存活,直到 TTL 到期或重新連接。
  • 重連由服務負責。 若連線中斷,你的服務必須重新連接。資料庫中的開放會話在 TTL 到期前可以跨重啟存活。
  • 啟動時離線的 fallback。 若反應觸發時服務未連接,則觸發整合設定中的備用反應(on_unavailable_reaction_id)。若未設定,則什麼都不發生(靜默)。
  • 離線佇列。 因離線而未投遞的 message/callback 事件會放入離線佇列(最多 100 個事件,FIFO,存活至會話到期),並在重連時補送。session.open/session.cancel/session.expired 事件不進入佇列
  • 投遞保證——至多一次(at-most-once)。 當佇列溢出或過期時,事件會被丟棄。請設計你的邏輯,使得遺漏個別事件不會破壞場景。

上線狀態指示器

可透過以下端點查看服務是否保持活躍連線:

GET /api/bots/{botID}/integrations/{integrationID}/status

回應:

{ "online": true }

相同的指示器也顯示在後台的整合卡片上。MCP 中的對應工具為 get_integration_status

安全性

GetMyBot 使用自己的 token 執行第三方服務的指令,因此安全保護非常嚴格。

  • 無 SSRF 攻擊面。 GetMyBot 作為 WebSocket 伺服器,不主動向服務發起連線——是服務連接到 GetMyBot。平台不會訪問任何由外部控制的 URL。
  • Token 以雜湊形式存放(sha256),比對使用常數時間,支援輪換。
  • 租戶隔離。 指令嚴格綁定到所屬整合;會話綁定到機器人 + 用戶 + 聊天。其他機器人的事件不會進入你的 socket。
  • 方法白名單 + 強制 chat_id 防止機器人被用作向任意聊天發送訊息的工具。
  • 機器人 token 不對外暴露 — 服務從不直接與 Telegram 通訊。
  • 敏感資訊遮蔽。 整合 token 和敏感值在日誌中會被遮蔽。

範例

最小處理器:在對話開啟時傳送訊息並關閉會話(expect: "none")。請替換整合 ID 和 token。地址路徑中必須包含 <integrationID>

Node.js

import WebSocket from "ws";

const URL = `wss://api.mybot.app/ext/ws/${process.env.MYBOT_INTEGRATION_ID}`;
const ws = new WebSocket(URL, {
  headers: { Authorization: `Bearer ${process.env.MYBOT_TOKEN}` },
});

ws.on("message", (raw) => {
  const ev = JSON.parse(raw);
  if (ev.type === "session.open") {
    ws.send(JSON.stringify({
      type: "execute",
      session_id: ev.session_id,
      expect: "none",
      methods: [{ method: "sendMessage", params: { text: "Hello from external handler!" } }],
    }));
  }
});

Python

import json, os, asyncio, websockets

async def main():
    url = f"wss://api.mybot.app/ext/ws/{os.environ['MYBOT_INTEGRATION_ID']}"
    headers = {"Authorization": f"Bearer {os.environ['MYBOT_TOKEN']}"}
    async with websockets.connect(url, additional_headers=headers) as ws:
        async for raw in ws:
            ev = json.loads(raw)
            if ev["type"] == "session.open":
                await ws.send(json.dumps({
                    "type": "execute",
                    "session_id": ev["session_id"],
                    "expect": "none",
                    "methods": [
                        {"method": "sendMessage", "params": {"text": "Hello from external handler!"}}
                    ],
                }))

asyncio.run(main())

Inline 按鈕與 callback 處理

要捕捉點擊,請傳送帶有 callback_data 的按鈕(GetMyBot 會自動 token 化)。使用帶有按鈕的 expect: "none" 時,會話保持開放,直到按鈕 token 存活時間到期——點擊會以 callback 事件傳送,其中 event.callback_data 等於原始值。無需確認點擊(answerCallbackQuery)——GetMyBot 會自動處理。

if (ev.type === "session.open") {
  ws.send(JSON.stringify({
    type: "execute",
    session_id: ev.session_id,
    expect: "none",
    methods: [{
      method: "sendMessage",
      params: {
        text: "Click the button",
        reply_markup: { inline_keyboard: [[{ text: "Go", callback_data: "go" }]] },
      },
    }],
  }));
} else if (ev.type === "callback" && ev.event.callback_data === "go") {
  ws.send(JSON.stringify({
    type: "execute",
    session_id: ev.session_id,
    expect: "none",
    methods: [{ method: "sendMessage", params: { text: "Button clicked!" } }],
  }));
}

下一步

  • 整合 — 可連接服務的總覽。
  • 反應 — 觸發器、條件與動作的組合方式。
  • Web 請求與 webhook — 不需要對話時呼叫外部 URL 的更簡單方式。