Telegram 发消息 API(Python):发送与读取聊天消息
很多人第一次做 Telegram 自动化,都会先去创建一个 Bot。但 Bot 有天然的限制:用户没主动找过它,它就没法先发消息;它不能按手机号找人;也读不到任意会话的历史记录。如果你要做的是客服收件箱、CRM 同步、给已有客户发通知,或者社群运营,更合适的做法是直接以普通用户账号的身份收发消息。Telegram Messaging API 就是这样一套 REST 接口:先登录一次个人账号(不是 Bot),之后每个请求都带上 sessionId 即可。
本文用 Python 完整走一遍:发送文本与媒体、发送前校验收件人、轮询新消息、拉取聊天记录,以及编辑、点赞、撤回已发消息。请只在你本人拥有或获得授权的账号上使用,并遵守 Telegram 的服务条款。
开始之前需要准备什么
- 一个 API Key:在控制台复制即可,新账户赠送免费点数,下面所有示例都可以直接跑通。
- 一个已连接的会话,也就是已登录 Telegram 账号对应的
sessionId。登录只需做一次(验证码,开了两步验证的账号再加一个密码),完整步骤见Telegram 登录 API 教程。
如果你只想知道某个手机号或用户名在不在 Telegram 上,并不需要发消息,那么不依赖会话的 Telegram 号码检测更省事、成本也更低。
请求格式
所有接口都在 https://api.opendata-api.com/api/project/telegram-messaging/<operation> 下,API Key 通过 key 传递,会话通过 sessionId 传递。GET 与 DELETE 接口的其余参数都放在查询字符串里;POST 与 PUT 接口则要把该接口的 JSON 内容作为字符串放进 body 字段。先看一个 curl 例子:
curl -X POST 'https://api.opendata-api.com/api/project/telegram-messaging/send-message?key=YOUR_API_KEY&sessionId=YOUR_SESSION_ID' \
-H 'Content-Type: application/json' \
-d '{"body":"{\"to\":\"me\",\"text\":\"Hello from the API\"}"}'
to 填 me 表示发到账号自己的「收藏夹」(Saved Messages),不会打扰任何人,用来做第一次测试最安全。
每个响应都被包了两层,形如 {"code": 200, "message": "success", "data": {"code": 200, "data": {...}}}:外层由平台返回,内层来自消息服务,它始终返回 HTTP 200,失败时在自己的 code、title、detail 里说明原因。下面这个小封装会拆开两层、在任何失败时抛出异常,后面每一步都复用它:
import json
import requests
API_KEY = "YOUR_API_KEY"
SESSION_ID = "YOUR_SESSION_ID"
BASE = "https://api.opendata-api.com/api/project/telegram-messaging"
SUCCESS_CODES = {200, 201, 202, 204}
def call(method, operation, payload=None, **params):
"""Call one operation and return the innermost payload (dict, list or None). JSON payloads go in a string `body` field."""
params["key"] = API_KEY
kwargs = {"params": params, "timeout": 60}
if payload is not None:
kwargs["json"] = {"body": json.dumps(payload)}
resp = requests.request(method, f"{BASE}/{operation}", **kwargs)
if not resp.ok:
raise RuntimeError(f"{operation} failed: HTTP {resp.status_code} {resp.text}")
body = resp.json()
# Peel both envelopes: {"code": 200, "data": {"code": 201, "data": {...}}}
while isinstance(body, dict) and "code" in body:
if body["code"] not in SUCCESS_CODES:
raise RuntimeError(f"{operation} failed: {body}")
body = body.get("data")
return body
me = call("GET", "get-account", sessionId=SESSION_ID)
print(me)
get-account 会返回当前登录账号的数字 ID、手机号、用户名、姓名,以及是否为 Bot。正式发消息之前先调用一次,可以确认这个会话到底连的是哪个账号,避免用错号。
第一步:发送文本消息
send-message 用 to 指定收件人、text 指定内容。to 支持 @用户名 或不带 @ 的用户名、国际格式的 +手机号,以及 me(收藏夹)。纯数字的用户 ID 只有在该账号已经认识这个对象时才能用。
call("POST", "send-message", {"to": "@some_username", "text": "Your order has shipped."}, sessionId=SESSION_ID)
call("POST", "send-message", {"to": "+15551234567", "text": "Hi, this is Anna from support."}, sessionId=SESSION_ID)
调用成功会返回 to、text 和 sent 作为确认。调用前会话必须已完成登录,否则调用会失败,call() 会抛出异常。如果发送报错,先用 get-session 看看会话状态,再决定要不要重试。
第二步:发送前先校验收件人
给一个根本不存在的用户名发消息,既浪费调用,也得不到任何有用信息。check-exists 会解析收件人并告诉你是否存在,存在时同时返回解析出的 ID、用户名和显示名;解析不到时返回 exists=false 和 reason。
def peer_exists(peer):
info = call("GET", "check-exists", sessionId=SESSION_ID, peer=peer)
return bool(info.get("exists")), info
ok, info = peer_exists("+15551234567") # requests encodes the plus sign for you
print(ok, info)
如果你自己拼查询字符串,别忘了把加号写成 %2B,否则它会被当成空格。建议每个收件人只校验一次并缓存结果,只给确认存在的对象发消息。
第三步:发送图片、文件和语音
send-media 通过 base64 内容上传文件。type 可选 image、video、audio、voice、document,文件内容放进 data;还可以用 caption 加说明文字、fileName 指定文档名、mimeType 覆盖自动识别的类型。
import base64
with open("invoice.pdf", "rb") as fh:
encoded = base64.b64encode(fh.read()).decode("ascii")
call("POST", "send-media", {
"to": "@some_username",
"type": "document",
"data": encoded,
"fileName": "invoice.pdf",
"caption": "Invoice for October",
}, sessionId=SESSION_ID)
文件越大,请求体越大,所以建议在网络稳定的环境里上传,并把超时时间设得宽松一些。
第四步:读取收到的新消息
list-inbox 返回会话在线期间实时收到的消息,按时间倒序,不包含账号自己发出的消息。每条消息带有消息 ID、chatJid、发送者、类型、文本和时间戳,limit 控制返回条数(默认 50)。收件箱本质上是一个滚动窗口,所以最自然的做法是轮询加按消息 ID 去重:
import time
seen = set()
def poll_once():
items = call("GET", "list-inbox", sessionId=SESSION_ID, limit=50) or []
for msg in reversed(items): # oldest first
if msg["id"] in seen:
continue
seen.add(msg["id"])
print(msg["chatJid"], msg.get("text"))
call("POST", "mark-read", {"peer": msg["chatJid"]}, sessionId=SESSION_ID)
while True:
poll_once()
time.sleep(5)
设计时要留意两个限制:收件箱每个会话只保留最近 500 条消息,而且服务重启后这些记录会被清空。所以请把它当作实时消息流,而不是存储,读到的内容要及时写进你自己的数据库。收件箱里看到的 chatJid(形如 user:<id>、chat:<id>、channel:<id>)可以直接作为拉取历史、标记已读和群组管理的对象标识。
第五步:拉取聊天记录
比实时消息流更早的内容,用 chat-history。它直接从 Telegram 取回某个对象最近的消息,而不只是本次会话期间收到的,按时间倒序。每条消息包含数字 id、chatJid、发送者、fromMe 标记、类型、文本和时间戳,默认 limit 为 20。
history = call("GET", "chat-history", sessionId=SESSION_ID, peer="@some_username", limit=50)
peer 可以是 @用户名、+手机号、me,也可以是从收件箱或会话列表里拿到的 user:<id> / chat:<id> / channel:<id>。想先看看有哪些会话,可以调用 list-chats,注意它是根据会话连接之后观察到的消息生成的。
第六步:编辑、点赞与撤回消息
从聊天记录或收件箱拿到数字 messageId 之后,就可以对消息做进一步操作:
call("POST", "edit-message", {"peer": "@some_username", "messageId": 123, "text": "Corrected text"}, sessionId=SESSION_ID)
call("POST", "react-message", {"peer": "@some_username", "messageId": 123, "emoji": "👍"}, sessionId=SESSION_ID)
call("POST", "revoke-message", {"peer": "@some_username", "messageId": 123}, sessionId=SESSION_ID)
只有账号自己发出的消息才能编辑。revoke-message 会对所有人删除该消息;react-message 的 emoji 传空字符串则表示取消自己的表情回应。
不止一对一聊天:群组与频道
同一个会话还能管理社群:list-groups 列出账号加入的群组和频道,create-group 按成员列表创建基础群,group-invite 导出邀请链接,在账号拥有管理员权限的前提下,add-participants、update-admins 可以处理成员与管理员。
错误处理与最佳实践
- 要看内层
code,不能只看 HTTP 状态码。 消息服务即使拒绝了请求也会返回 HTTP 200,结果放在内层的code里,并附带title和detail,call()会据此抛出异常。平台层的401表示 API Key 缺失或无效,403表示点数不足或账户未启用。 - 发送失败不要盲目重试。 发送类接口没有幂等参数,超时之后应先读一下最近的聊天记录,确认消息有没有已经发出去,再决定是否重发。
- 处理会话掉线。 一旦调用开始失败,先看
get-session。账号只是离线的话,这个调用会帮你重新连上。如果已保存的登录已经没了,例如用logout=true调用过disconnect-session,就按登录教程重新创建会话。 - 控制发送节奏。 每条消息之间加上间隔,先校验收件人,也不要给很多人发完全相同的文本。
- 保护好
sessionId。 它等同于该账号的操作权限,请放在环境变量或密钥管理服务里,不要提交进代码仓库。 - 用完及时断开。
disconnect-session默认只断开连接、保留已保存的登录;加上logout=true会远程退出 Telegram 并删除已保存的登录。
合规提醒
请只给期待收到你消息的人发送:客户、已订阅用户、事务性通知的接收者。未经许可的群发违反 Telegram 的条款,账号可能因此被限制,而且各地的隐私与反垃圾信息法规同样适用。手机号属于个人信息,只处理你有合法依据使用的号码。想了解 WhatsApp 上的同类流程,可以看如何通过 API 发送 WhatsApp 消息;想在发消息之前先跨渠道核验联系人,请阅读线索核验工作流。
常见问题
不用 Bot Token,能用 Python 发 Telegram 消息吗?
可以。Telegram Messaging API 以普通用户账号的身份登录(手机验证码或扫码),之后用该账号的 sessionId 调用 send-message 即可发送,全程不需要创建 Bot,也不需要 Bot Token。
怎么给某个手机号或用户名发消息?
把它填进 send-message 的 to 字段:可以是 @用户名 或不带 @ 的用户名、国际格式的 +手机号,也可以填 me 发到收藏夹。纯数字的用户 ID 只有在账号已经认识该对象时才有效。
怎么读取别人发来的 Telegram 消息?
用 list-inbox 获取会话在线期间收到的实时消息,用 chat-history 直接从 Telegram 拉取某个对象更早的消息。收件箱每个会话只保留最近 500 条且重启后清空,需要留存的内容请自行入库。
Telegram Bot API 和这套接口有什么区别? Bot 是一个权限受限的独立身份,比如不能主动发起对话。这套接口则以普通用户账号的身份工作,可以给用户名和手机号发消息、读取聊天记录、管理群组,具体能做什么取决于该账号在 Telegram 上本身的权限。
重启之后会话还在吗?
登录状态会保留。连接成功后,会话的 id(可从 get-session 读取)是由账号派生的稳定 ID,之后用 create-session 的 restore 模式就能重新连接,不需要新的验证码;不过收件箱和会话列表的记录会在重启后清空。
可以对 Telegram 账号做自动化吗? 自动化只适用于你本人拥有或已获授权的账号,并且必须遵守 Telegram 的服务条款以及你所在地区的法律法规。以上只是一般性建议,不构成法律意见,具体场景请自行核对平台规则。
立即体验
在 Telegram Messaging 页面查看完整接口列表,也可以阅读 send-message、send-media、list-inbox 与 chat-history 的接口文档。查看定价了解每次请求的成本。