Skip to content

Telegram

Telegram 两种模式都支持:

模式行为
Notification告警流水线单向 sendMessage
IM bridge通过 getUpdates 长轮询做双向对话。

两种模式用同一个 bot token(8…:AA…,从 @BotFather 拿)—— Telegram 用 path 里的 token 鉴权,没有额外签名机制。

面向非飞书/钉钉地区的用户

对海外团队,或运维恰好都用 Telegram 的混合部署,Ongrid 推荐 Telegram 作为 IM 通道。Provider 加入见 ADR-031

Notification 模式

notify.Sender{chat_id, text} POST 到 https://api.telegram.org/bot<TOKEN>/sendMessage。凭据是 bot token(path 里),chat_id(数字)是目标会话。

go
// internal/pkg/notify/webhook.go
NewTelegramSender(name, endpoint, chatID, client)

endpoint 就是字面量 …/bot<TOKEN>/sendMessagechatID 是目标会话(私聊就是对方的数字 user id,群就是 Telegram 给的那个负数)。

配置:

  1. 私聊 @BotFather/newbot → 起一个 username → 复制 token。
  2. 把 bot 加进目标会话(群/频道)或先私聊一下,让它认识你。
  3. 拿 chat ID。最快办法:在会话里随便发一句,然后 curl https://api.telegram.org/bot<TOKEN>/getUpdates,读 result[0].message.chat.id
  4. 在 Ongrid:Settings → Channels → New → Provider = telegram → Endpoint = https://api.telegram.org/bot<TOKEN>/sendMessage,chat_id = 第 3 步那个数。

IM bridge 模式(双向)

入站走 manager 主动外联的 HTTPS 长轮询。因为是外联调用,Telegram 在 NAT、防火墙、HTTPS 代理后面都能跑。setWebhook(替代方案)要求 Telegram 反向到达 manager —— 这对大多数私有云部署不适用,对国内访问也不靠谱。

只支持 stream

校验器拒绝其他值:

telegram only supports stream mode

UI 里 Telegram 不会暴露 webhook 模式选项。

字段映射

im_apps 行复用现有列,没有 schema 变更:

im_appsTelegram 含义
provider"telegram"
mode"stream"(校验器写死)
app_idBot username(比如 ongridbot)。用于展示和去重。
app_secretBotFather token8…:AA…)。落盘加密。
allow_from必填,逗号分隔的数字 user ID。
verify_token不用。
encrypt_key不用。

allow_from

非空的 Telegram 数字 user ID 列表,允许对话。校验器丢掉非数字 token(这样写错 alice 会以清晰的"必填"错误冒出来,而不是变成静默空白名单),并拒掉负数 token(那些是群 / 超级群的 chat ID,不该放在发送方白名单里)。

telegram requires allow_from — at least one numeric Telegram user ID (the bot is publicly reachable; an empty allowlist would let anyone command the agent)

怎么查数字 user ID:

  • 私聊 @userinfobot,它会把你的数字 ID 回过来。
  • 或者注册完之后在会话里发一条消息,跑 curl https://api.telegram.org/bot<TOKEN>/getUpdates,读 result[0].message.from.id

telegram: / tg: 前缀会被静默剥掉(兼容 OpenClaw),所以 telegram:123456789123456789 是同一条。

不在白名单则静默丢

非白名单发送者的消息在 WARN 等级丢日志:

text
telegram inbound from non-allowlisted sender — ignored
  user_id=42  user_name=alice  chat_id=42

不回复、不发占位、bot 甚至不确认自己存在。这与 OpenClaw 的 allowFrom 行为一致 —— 回复会泄露这个 username 背后有一个带工具的 Agent,会勾人来探。

配置步骤

  1. 私聊 @BotFather/newbot → 起一个名字 + 以 bot 结尾的唯一 username。复制 token。
  2. (可选)@BotFather → /setprivacy → Disable,如果想让 bot 看到群里所有消息(默认只看 @mention)。
  3. 通过 @userinfobot 查到你自己的数字 user ID。
  4. 在 Ongrid:Settings → IM bridge → New → Provider = telegram → Mode = stream → App ID = bot username → App secret = BotFather token → allow_from = 你的数字 ID(和队友的)。保存并 Enable
  5. 从白名单账号私聊 bot。bot 先回一条占位,然后随着 Agent 推理在原地编辑。

代理这件事

Telegram API 主机(api.telegram.org)在大多数网络可达,但在国内被墙。provider 用零值 http.Client,所以会遵守 manager 进程环境里的 HTTPS_PROXY / HTTP_PROXY / NO_PROXY —— getUpdates 走哪个代理,sendMessage 就走哪个。

docker-compose 部署下,代理写进 docker-compose.override.yml 持久化:

yaml
services:
  manager:
    environment:
      HTTPS_PROXY: http://your-proxy:8080
      NO_PROXY: localhost,127.0.0.1,manager,mysql,prometheus,loki,tempo,grafana,qdrant

不要把代理写进主 docker-compose.yml

docker-compose.yml 是随每个 release 发的。override 文件是按环境的,能扛过 make package 和安装脚本重跑。

需要知道的小坑

message is not modified 是无害的

editMessageText 的 payload 和当前消息文本完全一致时,Telegram 会返 HTTP 400(message is not modified)。流式输出在节流 tick 或最终 flush 时可能重复同一段 chunk。客户端只吞这一种错误字符串并返回 nil —— 其他(400/403/5xx)照常上报。见 EditMessageText

每个 bot 只允许一个 poller

Telegram 拒绝并发 getUpdatesStreamSupervisor 每个 im_app 行强制只起一个 client。如果你复制了一个 Telegram app 行,只有一个能成功轮询 —— 另一个会拿到 409 Conflict 然后退避。

限流重试

429 Too Many Requests 最多重试 3 次。等待时间遵守 Telegram 的 parameters.retry_after,上限 60s。5xx 错误以 1s / 2s / 4s 退避重试。硬 4xx(400/401/403)重试 —— 直接冒到 supervisor,通常意味着 token / chat-id 出问题了。见 maxCallRetries

长轮询超时

服务端长轮询等 25s(pollTimeoutSec),客户端再加 10s buffer。慢网络只是看到更长的轮询周期;supervisor 不会做忙等重连。

只接文本

贴纸、图片、语音、文件都被静默丢。只有 message.text 事件触发 Agent。bridge 有意保持 S1(文本进文本出);富媒体留待未来扩展。