Telegram 发消息 API(Python):发送与读取聊天消息

发布于 2026年10月9日

很多人第一次做 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 的接口文档。查看定价了解每次请求的成本。