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 レイヤー 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("電話番号 (+8190…): ")) # 初回に一度だけコードを入力
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 を使ってください。レイヤー 216 を話すバージョンです。より新しいバージョンはより高いレイヤーでレスポンスを読もうとするため、
TypeNotFoundErrorで失敗します。 - 新規アカウントの登録は、Telethon 側で無効にされています(
sign_up())。アプリで登録するか、auth.signUpを直接呼んでください。 - ログインは
pabal.sessionファイルに保存され、次回からはコードを聞かれません。このファイルはアカウントの鍵なので、大切に守ってください。
ログインの流れ
図の説明
- 2 つの段階:上は暗号化に使う認証キーを作る段階(プロトコル)、下はそのキーにアカウントを結び付けるログインの段階(API)です。上の段階は、ライブラリが自動的に処理します。
- ログインは認証キーに紐づきます:一度ログインしたキーで複数のセッションを開いても、すべてログイン状態です。キーを失うと(セッションファイルを削除した場合など)、再度ログインする必要があります。
- コードの届け方は、サーバーの運営者が決めます:SMS・管理者による伝達なら
sentCodeTypeSms、メールならsentCodeTypeSetUpEmailRequiredが返り、クライアントがメールアドレスを尋ねてaccount.sendVerifyEmailCodeを呼びます(中央の行)。 - 赤い点線は新しい番号の経路です:コードは正しいのにアカウントがない場合は
authorizationSignUpRequiredが返り、名前を入れてauth.signUpを呼ぶと登録が完了します。登録は、コードが正しかった場合にのみ可能です。 - ボットは下の段階の代わりに、
auth.importBotAuthorization(ボットトークン)の 1 回でログインします。
| エラー | 発生する場面 |
|---|---|
PHONE_NUMBER_INVALID | 番号が正しくない、またはサーバーが新規登録を停止している状態での新しい番号 |
PHONE_NUMBER_BANNED | 運営者がブロックした番号、または停止されたアカウントの番号(停止されたまま退会した場合は既定で 1 年 — 運営者は延長や無期限にできます) |
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 を 5 回)でログインできます。開発用サーバー専用の機能で、本番サーバーでは無効になっています。
アップデートの受信
- リアルタイム:接続が開いていれば、サーバーは新しいメッセージ・編集・削除を
updateShortMessage、updatesですぐに送ります。リクエストを送ったセッションは、その結果を RPC のレスポンスとして受け取るため、同じものがプッシュでもう一度届くことはありません。 - pts:ユーザーごとに、変化に通し番号(pts)が付きます。受け取った pts に抜けがあれば、クライアントは何かを見逃しています。
- 追いつき:
updates.getStateで現在の pts を覚えておき、再接続したらupdates.getDifference(pts, date, qts)で、その間の新しいメッセージ・削除と、関連するユーザー・グループを受け取ります。クライアントの持つ pts がサーバーより進んでいる場合(サーバーのデータが初期化された場合など)はdifferenceTooLongが返るので、会話一覧を読み込み直してください。 - プッシュ通知 — Android:接続していない Android アプリは、Firebase のプッシュで起こされます。アプリが
account.registerDevice(token_type2 = FCM トークン、secret= 256 バイトのデバイス鍵)で登録すると、サーバーは新しいメッセージや着信をそのデバイス鍵で暗号化して FCM で送り、起きたアプリはupdates.getDifferenceで追いつきます。プッシュが届くのは、サーバー運営者の Firebase プロジェクトでビルドされたアプリだけです。 - プッシュ通知 — iPhone:iPhone アプリは同じリクエストで 2 つを登録します —
token_type1(APNs の通知トークン)と 9(PushKit の VoIP トークン)で、secretはどちらもそのアカウントの 256 バイトのデバイス鍵です。新しいメッセージは APNs の通知で送られます。Apple に見える表向きの文言はアプリ名と「You have a new notification」のような中身のない言葉だけで、メッセージはデバイス鍵で暗号化してpに載せて送り、アプリの通知拡張(Notification Service Extension)がそれを復号して表示します。着信は VoIP プッシュで CallKit の着信画面を表示し(運営者が VoIP を有効にしたビルドから。それまでは通知で届きます)、別の端末で既読にしたものや削除したものはバックグラウンドプッシュで知らせます。プッシュが届くのは、サーバー運営者の APNs キーと同じチームが署名し、バンドル ID も同じアプリだけです。それ以外のtoken_typeにはboolTrueと応答するだけで、送信はしません。 - Telethon のようなライブラリは、この処理をすべて自動で行います。
ID とピア
| 対象 | ID | メモ |
|---|---|---|
| 人 | 100001 から | peerUser。@BotFather は 100000 |
| ボット | 人と同じ番号体系 | user.bot = true。トークンの先頭の数字がボット ID |
| 基本グループ | 1000001 から | peerChat。Bot API では負の数(-chat_id)として見えます |
| メッセージ | サーバー全体で 1 つの連番 | 1 対 1 の会話では、2 つのコピーが同じ番号を持ちます。そのため、ピアを指定せずメッセージ番号だけを受け取るリクエスト(messages.getMessages、messages.deleteMessages)も、2 人にとって同じメッセージを指します。チャンネルだけは、チャンネルごとに 1 から数えます |
access_hash は、サーバーから受け取った値をそのまま保存しておいて使ってください(ユーザー名の検索・会話一覧・アップデートと一緒に届きます)。
ファイル
- アップロード:
upload.saveFilePartでファイルをパート(1 つ 512KB まで、4,000 個まで)に分けてアップロードし、inputFileUploaded… で参照します。10MB を超えるファイルはupload.saveBigFilePartでアップロードします — パートの総数を先に伝えるため、サーバーが受け付けないサイズなら、すべて送り終える前にFILE_TOO_BIGで拒否します。パートは届いたそばからディスクに書き込まれ、メッセージがそのファイルを指したときに 1 つにまとめられます。 - 送信:
messages.sendMediaにinputMediaUploadedPhoto(新しい写真)、inputMediaPhoto(サーバー上の写真)、inputMediaUploadedDocument(新しいファイル)、inputMediaDocument(サーバー上のファイル)。それ以外のメディアはMEDIA_INVALID。messages.uploadMediaで送らずに先にアップロードしておくこともでき(返ってきた media を後からそのまま送ります)、messages.sendMultiMediaは受け付けますが、まとまらずに 1 件ずつ送られます(grouped_id なし)。 - 受信:
upload.getFileにinputPhotoFileLocation(メッセージの写真)、inputDocumentFileLocation(メッセージのファイル)、inputPeerPhotoFileLocation(プロフィール写真)。一度に最大 1MB。受け取る側も、ファイル全体をメモリに読み込まず、要求した範囲だけを読みます。 - プロフィール写真:
photos.uploadProfilePhoto、photos.updateProfilePhoto、photos.getUserPhotos、photos.deletePhotos。 - 写真は原本の 1 サイズだけで保存します(サムネイルは別途作りません)。ファイルには手を加えずそのまま置き、送信側が付けた
documentAttribute*はバイトのまま受信側に渡されます — 動画の長さや音声の波形をサーバーが理解する必要はなく、そのまま伝えるだけです。
サポート範囲
サーバーにはレイヤー 216 の 469 メソッド分のハンドラーがあり、すべてリクエストを読み取れますが、実際のクライアントで最後まで確認したものは下のメソッドです。それ以外は形式どおりに応答しますが、内容が空だったり、記録されなかったりすることがあります。
| 分野 | 確認したメソッド |
|---|---|
| 接続 | 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.saveBigFilePart(10MB 超)、upload.getFile、photos.*(上記) |
公式の Telegram Desktop 6.2.6 が、登録・ログイン・会話・写真・グループ・再接続まで、改変なしで動作することも確認しました。アプリの起動時に呼ばれる約 60 個のメソッドは、レスポンスの形式を個別に検査しています。
その後に加えたもの — 端末の一覧と終了(account.getAuthorizations、account.resetAuthorization、auth.resetAuthorizations)、デスクトップアプリのデータのエクスポート(account.initTakeoutSession、invokeWithTakeout、invokeWithMessagesRange、account.finishTakeoutSession — アカウントごとに 1 日 3 回、超えると 420 TAKEOUT_INIT_DELAY_n)、番号の変更(account.sendChangePhoneCode、account.changePhone — 運営者が有効にするまでは 400 PHONE_CHANGE_DISABLED、ボットには BOT_METHOD_INVALID) — はサーバーのテストで確認しました。実際のアプリで最後まで確かめるのはこれからです。
エラー
エラーは標準の 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 | 権限がない — メンバーではないグループなど |
406 | 今はできない — ログインして 24 時間たっていない端末が、ほかの端末を終了させようとしたとき FRESH_RESET_AUTHORISATION_FORBIDDEN |
420 | FLOOD_WAIT_n — n 秒待つ |
500 | サーバー内部エラー |
トランスポートエラー -404 | サーバーがこの認証キーを知らない — 新しいキーを作って再ログイン(永続キー)、または再バインド(一時キー) |
ウォッチ API
サーバーの /api/watch/v1/ は、Pabal の Apple Watch アプリ専用の HTTPS JSON の入口です。ウォッチは MTProto を使えないため別に用意したもので、公開仕様ではなく準備中のため、予告なく変わります。Pabal に接続するプログラムを作るなら、このページの MTProto を使ってください。サーバーの運営者はサーバーのインストール — Apple Watch の入口をご覧ください。
まだないもの
- チャンネル・スーパーグループ(
channels.*は応答するが、アプリで正しく表示されない)、シークレットチャット、グループ通話 - アプリ内で再生される動画・音声メッセージ、ひとまとめに表示されるアルバム(grouped_id)、ステッカー、サムネイル(ファイルは原本のみ)
- 2 段階認証(SRP)— 設定できず、2 段階認証が必要な操作は拒否されます
- HTTP・WebSocket トランスポート、MTProxy、
msgs_ackの送信、サーバーソルトの定期的な切り替え(誤ったソルトにはbad_server_saltで正しい値を返します) - 複数サーバーへの分散 — 1 台のサーバーが DC 1〜5 をすべて担当します
公式 Telegram アプリを Pabal に接続するには
Telegram アプリは、サーバーのアドレスと公開鍵をビルド時に組み込みます。そのため、設定画面ではなく、ソースを修正して再ビルドする必要があります。Pabal アプリ(Pabal.app)は、そのようにして作られたものです。サーバーの運営者なら、サーバーのインストール — アプリの接続を参照してください。