Pabal API
Pabal API(MTProto)
构建像应用一样连接服务器的客户端时需要的内容:连接信息、服务器公钥、登录流程、更新和支持范围。
Pabal 应用通过 MTProto 2.0 与服务器通信。如果你要开发以同样方式连接的程序——其他应用、以真人账号运行的自动化程序、研究用客户端——请阅读本文档。如果只是开发机器人,更简单的 Bot API 就足够了。
Pabal 遵循 Telegram 的公开协议,因此协议和方法的详细定义以 MTProto 和 API 方法文档为准。本文档记录的是连接 Pabal 服务器时的不同之处以及支持范围。
连接信息
| 项目 | 值 |
|---|---|
| 地址 | 122.34.175.215 |
| 端口 | 8443(TCP) |
| DC | 1~5 都是同一个地址。连接哪个 DC 都可以,通常使用 2 |
| 协议 | MTProto 2.0,API Layer 216 |
| 传输方式 | Abridged、Intermediate、Padded Intermediate、Full——各自都包括混淆(obfuscated2)版本 |
| 服务器公钥 | server-key.pem · fingerprint 8724853375441383205 |
| api_id · api_hash | 不做校验。填任何值都可以 |
目前还没有 HTTP 传输、WebSocket 传输和 MTProxy。客户端设备的时钟必须准确——MTProto 的消息编号由时间生成,时钟偏差过大时,服务器会丢弃消息。
服务器公钥
MTProto 客户端首次连接时,会用服务器的 RSA 公钥加密授权密钥交换过程。Telegram 客户端内置的是 Telegram 的公钥,因此要连接 Pabal,就必须用这个密钥替换(或同时加入)。这个密钥可以防止其他服务器冒充 Pabal 服务器。
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA4hH74xPQsUwr/pyXPdF4tVicYr6QbfeDrKC7mUOVrPLSL4FtmgGn
w+O4u6lVvOf3Udd1KY6+OL4fUZdMBlLzwsoGLoniiVR09dnvyXHE8LhQSS+i1LmI
oJbhQwXplLnUJf272fLXkD23e7ppKLkjYk+jeYObueCy5HYMSThklVeXEzbZVGZv
47o/mjU2vyFoRpa6wCIE4rpj1UIPtOMpekMI/TocIlGVJ+ch6cAVNxIDro53a1eG
/1oZRLQH4oViEGxeMMjBMY5gk5HPkZvjbqy4h8TjXEz7O5o0BRsSqG/OPtP/dRQV
X4ewPhj2WCU4e6l5X59ZKIcv4sQLOwClqQIDAQAB
-----END RSA PUBLIC KEY-----
curl -s -o server-key.pem https://pabal.me/docs/server-key.pem
这个密钥在服务器首次启动时生成一次,之后不会改变。请确认服务器日志中 Loaded RSA key … (fingerprint …) 的值与上面的 fingerprint 一致。
用 Telethon 连接
下面是用 Python 的 Telethon 登录真人账号并收发消息的示例。请先在应用中注册账号(见下文)。
# pabal_client.py — pip install telethon==1.42.0
# 在同一文件夹中放入 server-key.pem(上面下载的服务器公钥)
import asyncio
from telethon import TelegramClient, events
from telethon.crypto import rsa
rsa.add_key(open("server-key.pem").read(), old=False) # Pabal 服务器的公钥
client = TelegramClient("pabal", api_id=1, api_hash="0" * 32) # 登录状态保存在 pabal.session 中
client.session.set_dc(2, "122.34.175.215", 8443)
@client.on(events.NewMessage(incoming=True))
async def show(event):
sender = await event.get_sender()
print(f"{sender.first_name}: {event.raw_text}")
async def main():
await client.start(phone=lambda: input("手机号码 (+86…): ")) # 第一次需要输入一次验证码
me = await client.get_me()
print(f"已登录:{me.first_name} (id {me.id})")
await client.send_message("BotFather", "/help")
await client.run_until_disconnected()
asyncio.run(main())
- 请使用 Telethon 1.42。这是使用 Layer 216 的版本。更新的版本会尝试用更高的 Layer 解析响应,从而以
TypeNotFoundError失败。 - Telethon 禁止了注册新账号(
sign_up())。请在应用中注册,或者直接调用auth.signUp。 - 登录状态保存在
pabal.session文件中,之后不会再询问验证码。这个文件就是账号的钥匙,请妥善保管。
登录流程
图示说明
- 两个阶段:上方是生成加密所用授权密钥的阶段(协议),下方是把账号绑定到该密钥上的登录阶段(API)。上方的阶段由库自动完成。
- 登录绑定在授权密钥上:用一个已经登录的密钥打开多个会话,这些会话全都处于登录状态。丢失密钥(删除会话文件)后需要重新登录。
- 验证码的发送途径由服务器运营方决定:短信或管理员转交时返回
sentCodeTypeSms;电子邮件时返回sentCodeTypeSetUpEmailRequired,客户端会询问电子邮件地址并调用account.sendVerifyEmailCode(中间一行)。 - 红色虚线是新号码的路径:验证码正确但账号不存在时,会返回
authorizationSignUpRequired,填入名字并调用auth.signUp即可完成注册。只有在验证码输入正确之后才能注册。 - 机器人不走下方的阶段,而是用一次
auth.importBotAuthorization(机器人令牌)登录。
| 错误 | 何时出现 |
|---|---|
PHONE_NUMBER_INVALID | 号码有误,或者在服务器关闭新注册的状态下使用了新号码 |
PHONE_NUMBER_BANNED | 被运营方封禁的号码 |
FLOOD_WAIT_n (420) | 请求验证码过于频繁。请在 n 秒后重试 |
PHONE_CODE_INVALID | 验证码错误(超过规定次数后会被锁定) |
PHONE_CODE_EXPIRED | 验证码已过期或被锁定,或者 phone_code_hash 未知——请从 sendCode 重新开始 |
EMAIL_INVALID, EMAIL_NOT_ALLOWED | 电子邮件地址有误 / 已有账号,但该地址不是已登记的登录邮箱 |
AUTH_KEY_UNREGISTERED (401) | 用未登录的密钥调用了需要登录的方法 |
在运营方开启了测试号码的服务器上,+99966XYYYY 形式的号码无需实际发送验证码,用验证码 XXXXX(X 重复五次)即可登录。这是只用于开发服务器的功能,在正式运营的服务器上是关闭的。
接收更新
- 实时:连接保持打开时,服务器会立即以
updateShortMessage、updates发送新消息、修改和删除。发出请求的会话会通过 RPC 响应收到结果,所以同样的内容不会再以推送形式重复送达。 - pts:每个用户的变化都带有序号(pts)。如果客户端收到的 pts 出现空缺,就说明错过了什么。
- 补齐:先用
updates.getState记下当前的 pts,重新连接后用updates.getDifference(pts, date, qts)获取这段时间内的新消息、删除以及相关的用户和群组。如果客户端持有的 pts 比服务器还超前(例如服务器数据被重置),会返回differenceTooLong,这时请重新加载对话列表。 - Telethon 之类的库会自动完成上述全部过程。
ID 与对端
| 对象 | ID | 备注 |
|---|---|---|
| 用户 | 从 100001 开始 | peerUser。@BotFather 为 100000 |
| 机器人 | 与用户使用同一套编号 | user.bot = true。令牌前面的数字就是机器人 ID |
| 普通群组 | 从 1000001 开始 | peerChat。在 Bot API 中显示为负数(-chat_id) |
| 消息 | 每个消息箱从 1 开始 | 一对一对话中,每个参与者都有自己的副本和自己的编号。即使是同一条消息,两个人看到的编号也可能不同 |
请把服务器给出的 access_hash 原样保存下来再使用(它会随用户名查询、对话列表和更新一起送达)。
文件
- 上传:用
upload.saveFilePart分块上传,再用inputFileUploaded… 引用。用于大文件的upload.saveBigFilePart暂不支持,因此实际上限为 10MB。 - 发送:在
messages.sendMedia中使用inputMediaUploadedPhoto(新照片)或inputMediaPhoto(服务器上已有的照片)。其他媒体返回MEDIA_INVALID。 - 下载:在
upload.getFile中使用inputPhotoFileLocation(消息中的照片)、inputPeerPhotoFileLocation(头像)。每次最多 1MB。 - 头像:
photos.uploadProfilePhoto、photos.updateProfilePhoto、photos.getUserPhotos、photos.deletePhotos。 - 照片只以原图一种尺寸保存(不单独生成缩略图)。
支持范围
服务器为 Layer 216 的 408 个方法都提供了处理程序,全部能够读取请求,但用真实客户端端到端验证过的是下面这些方法。其余方法的响应格式正确,但内容可能为空,或者不会被记录。
| 领域 | 已验证的方法 |
|---|---|
| 连接 | initConnection, invokeWithLayer, help.getConfig, auth.bindTempAuthKey(PFS 临时密钥), auth.exportAuthorization/importAuthorization |
| 登录 | auth.sendCode, auth.signIn, auth.signUp, auth.logOut, auth.importBotAuthorization, account.sendVerifyEmailCode |
| 用户、联系人 | users.getUsers, users.getFullUser, contacts.resolveUsername, contacts.importContacts, contacts.search |
| 消息 | messages.sendMessage, messages.sendMedia(照片), messages.getHistory, messages.getDialogs, messages.getMessages, messages.editMessage, messages.deleteMessages |
| 群组 | messages.createChat, messages.deleteChatUser, messages.editChatTitle(messages.addChatUser 只用官方应用验证过) |
| 机器人 | messages.getBotCallbackAnswer, messages.setBotCallbackAnswer, 带按钮(reply_markup)的消息 |
| 更新 | updates.getState, updates.getDifference, 实时推送 |
| 文件、照片 | upload.saveFilePart, upload.getFile, photos.*(见上文) |
我们还验证了官方 Telegram Desktop 6.2.6 无需修改即可完成注册、登录、聊天、照片、群组和重新连接。应用启动时调用的约 60 个方法,其响应格式会另外进行检查。
错误
错误以标准的 rpc_error(error_code + error_message)返回。error_message 始终是大写字母、数字和下划线的形式(PEER_ID_INVALID),必要时会附上 : 说明。
| 代码 | 含义 |
|---|---|
400 | 请求有误——PEER_ID_INVALID、MESSAGE_ID_INVALID、MEDIA_INVALID、USERNAME_NOT_OCCUPIED … |
401 | 需要登录——AUTH_KEY_UNREGISTERED |
403 | 没有权限——例如不是成员的群组 |
420 | FLOOD_WAIT_n——等待 n 秒 |
500 | 服务器内部错误 |
传输错误 -404 | 服务器不认识这个授权密钥——生成新密钥后重新登录(永久密钥)或重新绑定(临时密钥) |
尚不支持的功能
- 频道和超级群组(
channels.*会响应,但在应用中无法正常显示)、私密聊天、通话 - 照片以外的媒体、
upload.saveBigFilePart、缩略图 - 两步验证(SRP)——无法设置,需要两步验证的操作会被拒绝
- HTTP 和 WebSocket 传输、MTProxy、发送
msgs_ack、更换bad_server_salt - 拆分到多台服务器——由一台服务器同时承担 DC 1~5
让官方 Telegram 应用连接 Pabal
Telegram 应用在构建时就把服务器地址和公钥内置进去。因此不能在设置界面中修改,而是需要修改源代码后重新构建。Pabal 应用(Pabal.app)就是这样做出来的。如果你是服务器运营方,请参阅服务器安装——连接应用。