跳转到内容

Telegram 对话 Bot

Telegram Bot API 同时提供收消息与发消息接口。本文主路径使用生产环境更常见的 Webhook,本地调试可改用 getUpdates;核验日期为 2026-09-08,未使用真实 Bot 发送消息。

  • getUpdates(长轮询)和 Webhook 是互斥的更新接收方式;设置 Webhook 后不能继续用 getUpdates
  • 私聊中用户必须先启动 Bot。群聊中能收到哪些普通消息受 Bot Privacy Mode、管理员权限和消息类型影响。
  • 默认建议只处理私聊、命令、对 Bot 的回复或显式提及,不要为了 AI 对话直接关闭 Privacy Mode。
  • Bot API 本身不提供 LLM;普通规则回复和 AI 回复使用同一套 sendMessage
  1. 在 Telegram 中能访问官方 @BotFather
  2. 有一个 HTTPS 公网回调地址;本地调试也可暂不配置 Webhook,使用 getUpdates
  3. 服务端能保存 Bot Token 和 Webhook Secret,不把 Token 写入前端或访问日志。
  4. 已准备测试私聊或测试群,并明确群聊触发规则。
  1. @BotFather 发送 /newbot,按提示设置名称和唯一用户名,保存 Bot Token。
  2. 私聊机器人并点击 Start;群聊场景把机器人加入测试群。
  3. 在 BotFather 的 Bot Settings 中确认 Group Privacy。普通场景保持启用,只通过命令、提及或回复触发。
  4. 生成不少于 128 位随机强度的 WEBHOOK_SECRET,调用 setWebhook 注册 HTTPS URL。
  5. 服务端校验 X-Telegram-Bot-Api-Secret-Token,快速返回 2xx,并在后台处理更新。

Webhook 会收到 Update JSON。消息文本通常位于 message.text,稳定事件 ID 是 update_id;回复目标来自 message.chat.id,论坛主题还要保留 message.message_thread_id

{
"update_id": 900000001,
"message": {
"message_id": 27,
"from": { "id": 10001 },
"chat": { "id": -100000000001, "type": "supergroup" },
"text": "/ping"
}
}

update_id 做事件去重。会话键推荐使用 telegram:{chat.id}:{message_thread_id || 0};私聊可再加入用户 ID。不要把显示名或用户名作为身份主键。

Terminal window
curl -X POST 'https://api.telegram.org/botBOT_TOKEN/setWebhook' \
-H 'Content-Type: application/json' \
-d '{"url":"https://bot.example.com/telegram/events","secret_token":"WEBHOOK_SECRET","allowed_updates":["message"]}'

调试长轮询前先调用 deleteWebhook,然后用 getUpdates 携带 offset=上一条 update_id + 1。生产中不要同时启动多个没有协调 offset 的轮询实例。

处理 /ping 时调用 sendMessage,并传入原消息 ID 形成明确回复:

async function reply(update, text) {
const message = update.message;
const response = await fetch(`https://api.telegram.org/bot${process.env.BOT_TOKEN}/sendMessage`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chat_id: message.chat.id,
message_thread_id: message.message_thread_id,
text,
reply_parameters: { message_id: message.message_id },
}),
});
const result = await response.json();
if (!response.ok || !result.ok) throw new Error(JSON.stringify(result));
}

Webhook 路由应先验证 Secret、记录去重键并返回 2xx,再异步执行 reply(update, '/ping' ? 'pong' : ...)。重复事件不能重复调用发送接口。

message.text 标准化后交给厂商无关的 generateReply

const conversationKey = `telegram:${message.chat.id}:${message.message_thread_id ?? 0}`;
const answer = await generateReply({
messages: await history.withUserMessage(conversationKey, message.text),
signal: AbortSignal.timeout(20_000),
});
await reply(update, answer);
  • 群聊只在命令、提及或回复 Bot 时调用模型。
  • 将 Bot 命令从模型自由文本中分流,权限相关命令走确定性业务逻辑。
  • 长答案按 Telegram 当前长度限制切分,保持代码块完整,并限制消息发送速率。
  • 发生模型超时时返回简短可重试提示,不在 Webhook 请求内无限等待。
  • 同时校验 Webhook Secret 与 URL 路由随机性;Webhook 地址不要含 Bot Token。
  • Token 泄露后立即通过 @BotFather 撤销,替换服务端配置并重新注册 Webhook。
  • 使用 update_id 做事件去重,对同一会话串行或加锁,防止上下文乱序。
  • 忽略 Bot 自身或其他 Bot 消息,限制输入长度、附件大小和每日模型预算。
  • 防范提示词注入;用户内容不能修改工具白名单或服务端权限。
  • 429 读取 retry_after 后退避,不做紧密重试。

getUpdates 没有数据:检查是否仍设置了 Webhook;两者不能同时使用。

群里只收到命令,收不到普通文本:这是 Privacy Mode 的常见表现。优先采用命令、提及或回复触发;只有明确需要且完成隐私评估后才调整模式。

Webhook 一直重试:确认服务在平台时限内返回 2xx。模型调用放到队列或后台任务,并确保 update_id 去重。

机器人回复到了错误主题:论坛群需要把入站的 message_thread_id 传回 sendMessage