关于 telegram-bots
面向使用官方 Bot API 构建 Telegram 机器人的开发者参考技能,重点讲解更新投递、限流、支付与运行保障,并附有可运行的 Python 测试夹具。
主要功能
- 01更新投递与去重指南
- 02Webhook 与轮询的取舍
- 03限流和可恢复广播
- 04Stars 支付处理
- 05可运行的 Python 故障测试夹具
适用场景
- 01设计可处理重复更新的处理器
- 02选择 Webhook 或长轮询
- 03构建可恢复的高流量广播
- 04实现 Telegram Stars 购买流程
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ssheleg/ telegram-dev --skill telegram-bots安装后在 Claude Code 中输入/telegram-bots确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
fixtures/update_delivery.pyupdate_pump.pyreferences/frameworks.mdhigh-load.mdlimits-and-files.mdpayments-stars.mdupdates-and-delivery.md详细介绍
Telegram 机器人 — 官方 Bot API
Telegram 保存聊天内容。你的数据库保存之后要处理的一切。 每个严重的机器人缺陷,都是某条更新被处理了两次,或者某条更新从未送达,因为没人去获取它。
Bot API 本身不会导致机器人故障。故障出在衔接处:崩溃后更新再次送达;因为默认设置无人查阅,成员事件从未到达;根据错误信号确认付款;广播在第 31 位用户处触发 429,然后悄无声息地停止。
本文依据 2026-08-25 的 Bot API 文档编写,当时版本为 10.3(于 2026-08-24 发布)。引用版本前,请重新查看 core.telegram.org/bots/api-changelog:此 API 大约每月发布一次。2026-10-19 再次确认:10.3 仍是最新版本。供机器读取时,Telegram 会以 Markdown 格式提供文档,路径不变,末尾加上 .md:core.telegram.org/bots/api.md;每种方法各有一个文件(/bots/api/methods/getupdates.md),另有类型索引(/bots/api/types.md)。这些是官方且可解析的文档,但它们是 Markdown,而不是 JSON schema。
需要时再查阅的深入资料:
| 阅读 | 适用场景 |
|---|---|
references/updates-and-delivery.md | 配置长轮询或 webhook 时——了解交付约定、顺序、去重,以及两者之间的迁移 |
references/payments-stars.md | 收款时——了解 Stars、发票、预结账时限、退款和订阅 |
references/limits-and-files.md | 大量发送消息或传输文件时——了解所有已公布的限制及相应处理方式 |
references/frameworks.md | 选择或审查库时——了解 aiogram、grammY、Telegraf、python-telegram-bot,以及每个库隐藏了哪些细节 |
references/high-load.md | 机器人已有实际流量,或 Telegram 变慢时机器人就停止响应所有人——了解每个聊天的队列、上限、队列外的付款处理、关闭和告警 |
可运行,并与此文件一同发布:
fixtures/update_delivery.py — 正确的处理程序必须满足的九项不变量,每项都附有会导致失败的变异版本。运行 python3 fixtures/update_delivery.py --self-test,观察以下故障:重复处理再次送达的更新;在偏移量衔接处崩溃导致更新丢失;确认更新后工作进程死亡导致更新丢失;将再次送达的更新回复为“重复”,但实际工作从未发生;在标记完成前崩溃导致回复重复;再次送达的 outbox 行被发送两次;两条更新导致同一笔扣款被授予两次;429 导致回复被丢弃;一笔付款被授予两次。仅使用标准库。
fixtures/update_pump.py — 防止一个慢处理程序拖住所有聊天的九项规则(轮询不会等待处理程序、每个聊天内保持顺序、上限、付款通道、从到达时开始测量限流、入队时不替换、等待中的回调会得到答复、去重、每个聊天的上限)。运行 --self-test 会逐项移除规则并观察失败。
API 选型只需决定一次
| 需求 | 使用 | 原因 |
|---|---|---|
| 用户添加的机器人,在它加入的聊天中运行 | Bot API — 本技能 | HTTP、令牌、无需电话号码、没有封号风险 |
| 读取机器人无法看到的历史记录,或以个人身份操作 | MTProto — telegram-userbots | 用户账号,以及随之而来的一切代价 |
| Telegram 内的网页界面 | Mini App — telegram-miniapps | 一个页面,以及必须验证的 initData |
机器人不能在未关闭隐私模式或未被提及的情况下读取群组消息,不能查看从未加入的聊天,不能读取加入前的历史记录,不能代表用户行事,也不能下载超过 20 MB 的文件。如果你需要这些功能,就该查看 telegram-userbots——并先阅读其中的拒绝说明,因为用户账号带来的风险是机器人令牌没有的。
更新就是事件,并且至少会送达一次
# aiogram 3.x — the shape, not the framework
async def webhook(update: Update, db) -> web.Response:
await db.inbox_put(update.update_id, raw_body) # INSERT on a primary key,
return web.Response(status=200) # durable BEFORE the ack
# off the request path: a worker takes a pending row, does the work, marks it
# done — and a crashed attempt leaves the row pending for the next sweep
update_id是你唯一可用的幂等键。 它在重新投递时保持稳定,用于标识一条更新——但它只表示身份,并不保证永远单调递增:闲置约一周后,计数器可能从一个新的随机基数重新开始,因此切勿在长时间间隔后比较 update_id 来判断哪条“更新”。更新中的其他内容都无法标识这条更新:相隔一秒的两条完全相同的消息是两个事件,而同一事件投递两次仍是一个事件。- 先写入收件箱,再确认——认领表示已收到,而非已完成。 通过主键上的
INSERT写入一条持久化记录(绝不要先SELECT再INSERT:在 webhook 下,最多max_connections个连接(默认 40)可以同时处理同一条更新的投递),只有写入后才允许返回 200。任务在请求之外通过工作器租约运行:工作器退出后,记录会保持待处理状态,扫描任务会重试。先认领、再在请求内处理,是一种悄无声息的数据丢失模式——崩溃后认领记录仍在,Telegram 重新投递时读到“重复”,更新就此丢失,而所有测试样例依然通过。 - 更新保留 24 小时,不会更久。机器人停机一天,就会丢失这些更新,而且不会收到任何提示——应根据自己的状态进行对账,绝不要假定队列已排空。
- 根据 2026-08-25 在本机上对 8 个正在运行的 Telegram 机器人进行的测量:8 个中没有一个按
update_id去重。 长轮询会掩盖这个问题,直到首次在“处理完成”和“确认偏移量”之间发生崩溃。
allowed_updates 默认会丢弃三种类型
await bot.set_webhook(
url=f"{origin}/tg/{SECRET_PATH}",
secret_token=WEBHOOK_SECRET, # 1-256 chars
allowed_updates=["message", "callback_query", "chat_member",
"pre_checkout_query", "my_chat_member"],
drop_pending_updates=False,
)
默认值为空列表;不传此参数时也会使用该值——其含义是*“所有更新类型,但不包括 chat_member、message_reaction 和 message_reaction_count”*。因此,跟踪用户加入和离开的机器人什么也收不到,请求却返回 ok: true,日志也没有异常。明确列出你要处理的每种类型,新增类型时重新运行 setWebhook:allowed_updates 是在订阅时设置的,而不是在处理程序中设置的。
使用 webhook 或轮询,绝不要两者并用
设置 webhook 后,getUpdates 将无法工作。这正是迁移时的风险:使用生产令牌在本地运行 getUpdates,可能会悄无声息地接管,也可能失败,取决于哪一方最后进行了切换。一个令牌只能有一个消费者。
- webhook 是回调,因此需要采用与支付 webhook 相同的防护措施:以恒定时间比较
X-Telegram-Bot-Api-Secret-Token与通过secret_token设置的值;验证失败时返回不带详细信息的 401。URL 不是秘密,标头才是。 - 快速响应,稍后处理。 Telegram 会在收到非 2xx 响应或响应过慢时重试,重试即是再次投递同一个
update_id。先确认,再处理——上文的认领机制正是安全性的保障。 drop_pending_updates=True是一项决策,不是清理操作。 它会丢弃所有已排队的更新,包括已经完成支付的更新。- 长轮询适用于开发和低流量机器人;如果你有公开的 HTTPS 端点并且关注延迟,则适合使用 webhook。两种方式都见
references/updates-and-delivery.md。
限制是设计约束,而不是错误路径
| 限制 | 数值 | 对设计的影响 |
|---|---|---|
| 单个聊天 | ~1 条消息/秒 | 可容忍突发流量,之后会被拒绝 |
| 单个群组 | 20 条消息/分钟 | 聊天频繁的群组机器人需要队列 |
| 批量发送,面向所有用户 | ~30 条消息/秒 | 向 100k 名用户广播需要数小时,而不是一个循环 |
| 付费广播 | 最多 1000 条/秒 | 超出免费的 30 条/秒后,每条消息收费 0.1 Stars |
遇到 429 时,响应会携带以秒为单位的 parameters.retry_after。严格等待这么长时间,然后重试相同的调用——固定退避时间要么浪费等待时间,要么触发下一次限制。广播是一项带有速率限制器和恢复点的任务,而不是一个 for 循环;如果无法恢复,那么在用户 60 000 处崩溃后就会从头开始,之前的每位用户都会收到两次消息。
金钱:支付成功才算付款
数字商品和服务只能使用 Telegram Stars销售,货币代码为 XTR,且其 provider_token 留空。
sendInvoice / createInvoiceLink → pre_checkout_query → successful_payment
answer within 10s deliver here
- **
answerPreCheckoutQuery的处理窗口为十秒。**错过窗口,交易就会取消——因此该处理程序应基于你自己的状态进行验证,不做任何耗时操作。任何可能耗时一秒的操作都应放在successful_payment之后。 - **在
successful_payment时交付,绝不要在预结账时交付。**预结账只是一个询问;此时付款尚未完成。 - **
refundStarPayment可用于退款,且退款不等于删除。**按扣款 ID 撤回已授予的权益,处理方式与银行卡退款完全相同——其中的计算和顺序问题与stripe-billing涵盖的问题相同。
完整流程、订阅和交易账本:
references/payments-stars.md。
文件有大小上限,而且比你想的更低
- **通过
getFile下载:20 MB。**超过此大小,Bot API 会拒绝,任何框架都无法绕过这一限制。 - **上传:**通过云端 Bot API 上传上限为 50 MB。
- 本地 Bot API 服务可取消这两项上限,并会改变代码收到的文件路径。这是基础设施层面的决策,而不是一个标志——
references/limits-and-files.md。 - **
file_id既不是 URL,也不会在不同机器人之间保持稳定。**它仅对你的机器人有效;可以保存以便低成本重发,但绝不能将其用作存档引用。
哪些内容应存入数据库
| 事实 | 存储位置 | 原因 |
|---|---|---|
已见过的 update_id | 你自己的数据库,且唯一 | 唯一的幂等键 |
chat_id、user_id | 你自己的数据库 | Telegram 不会替你列出用户 |
| 你发送的消息 | 你自己的数据库,包含 message_id | 之后编辑消息需要这两个 ID |
| 用 Stars 购买的权益 | 你自己的数据库,按扣款 ID 关联 | 退款时必须能找到它 |
file_id | 你自己的数据库,作为缓存 | 方便低成本重发,不用于存档 |
Telegram 不是数据库,也没有办法枚举启动过你的机器人的用户。如果当时没有记录下来,这些信息就永远丢失了。
**当受众可能无法使用 Telegram 时,增加第二个渠道。**机器人所在的渠道并不归你所有。如果用户所在地区限制 Telegram 访问,或未来可能实施此类限制,就要为无法再联系到他们的那一天做好准备:使用你自己的 ID 而非仅使用 chat_id 来标识用户,在对用户有帮助的时机索取第二种联系方式(电子邮件、Web 账号),并确保用户可以通过 Telegram 以外的渠道访问产品。这样,即便用户拉黑机器人,你失去的也只是一个渠道,而不是用户列表。
发布前检查
- 每个处理程序都根据
update_id保持幂等(§ 更新就是事件)。 - Webhook 会验证密钥请求头,并在验证失败时返回不含详细信息的 401 (§ Webhook 还是轮询)。
allowed_updates列出了你处理的所有类型——默认值并非“全部” (§allowed_updates会丢弃三种类型)。- 遇到 429 时,会等待
retry_after指定的时长,并且广播可以恢复(§ 限制)。 - 商品在收到
successful_payment时交付,而不是在预结账时 (§ 资金)。 - 轮询不会等待处理程序完成:每个聊天一个队列,所有聊天共用一个上限,支付则在队列外处理(
references/high-load.md)。
触发词
还有一些会指向此处的短语:“grammy”、“telegraf”、“python-telegram-bot”、“getUpdates”、 “pre_checkout_query”、“高负载机器人”、“机器人卡顿”、“机器人 API”、“Telegram 机器人”、“Telegram 机器人”、“Telegram 机器人”。
从 0.5.4 的描述移至此处:“Telegram webhook”、“Telegram Stars”。