Add the dependency
In your project’s ziran.toml; pin ref to a commit for reproducible builds.
[dependencies.telegram]
git = "https://github.com/ziranlang/telegram.git"
ref = "master"Overview
Exports are package-qualified: telegram/tl, telegram/mtproto,
telegram/client, telegram/auth, telegram/messages, telegram/members and
telegram/session.
Use ziran add https://github.com/ziranlang/telegram.git once this repository
is published. For development, resolve the canonical checkout through an
ignored ziran.local.toml override and commit the generated lock file.
client owns an authenticated encrypted connection and accepts TL objects as
JSON. Bytes use base64; int128 and int256 use hexadecimal. Integer fields
are serialized without converting through floating point. auth adds phone
number, code and SRP two-step verification, private session snapshots and
authorization transfer between data centers. Account applications need their
own API ID and hash from Telegram's app configuration.
session provides a single-account, single-thread event interface for CLI
and native hosts: Create, Send, Receive, Execute. Its high-level JSON
request names preserve compatibility with existing mirror callers. Account
history, bot membership, text sends, exact callback bytes, reactions, read
receipts, edits, deletions and service messages are normalized in Ziran. The
same connection receives updates and recovers account and channel sequence
gaps. It does not start a bot-token poller.
Before setTdlibParameters, create an owner-controlled directory with mode
0700 and provide its path as database_directory. That historical request name
initializes this package, without TDLib. The session pins the directory and
stores account.json with mode 0600 using fsync and an atomic rename. Existing
unsafe or corrupt account files are rejected and preserved. Codes and passwords
are not included in account snapshots. Drain and apply received events before
calling Receive again: update cursors are checkpointed after the queue is
drained so interrupted processing can replay safely.
The high-level session covers the operations used by account chat mirrors;
it is not a complete Telegram application. loadChats announces every chat
with its full name, newest message and mute state. downloadMessageFile
saves a message's photo or document to a new owner-only file, connecting to
the file's data center when it differs from the account's;
downloadChatPhoto does the same for a chat's small profile photo.
sendMessage sends text, or uploads a local photo or document
(inputMessagePhoto / inputMessageDocument with a path), optionally as a
reply. forwardMessages forwards messages between chats and searchChats
finds people, bots, groups and channels by name, @username or +phone.
getChat reports a group's member count, description and the basic group it
was upgraded from. searchChatMembers
lists a group's recent members (up to 200) with their roles, custom titles
and users; getUser gives one user's presence, bot message access, active
username and profile photo; getChatMessageCount counts a chat's photos,
videos, files, audio, links, voice messages or GIFs with TDLib's search
filters.
Normalized messages carry reply_to_message_id and forward_info. Other API
methods can be called through client and the bundled TL schema. Secret
chats, account registration and interactive email enrollment do not have
high-level session helpers.
Run make check test for offline TL, SRP, normalization and update-sequence
checks. make rpc-test is an explicit network test: it performs an encrypted
help.getConfig request against Telegram's test DC without signing into an
account or sending a message.
Protocol references: authorization, update sequences, SRP, and MTProto 2.0.
telegram/src/tl.zi
tl
Bounded, schema-driven TL binary serialization. It preserves signed 64-bit integers and optional fields without a JavaScript/JSON-number round trip.
Import with #import "telegram/tl".
Definition :: struct { name: string; id: u32; fields: string; result: string; }definitions: [4096]Definitiondefinition_count: s32initialized: boolBuffer :: struct { bytes: []u8; at: s32; valid: bool; size: s32; }failure: stringWhy the last Decode or DecodeResponse failed, for error messages.
Fail :: (buffer: *Buffer, reason: string)Start :: (bytes: []u8) -> BufferJoin :: (a: string, b: string) -> stringFind :: (text: string, needle: u8, start: s32) -> s32Init :: () -> boolByName :: (name: string) -> s32ById :: (id: u32) -> s32Byte :: (buffer: *Buffer, value: u8)ReadByte :: (buffer: *Buffer) -> u8Word :: (buffer: *Buffer, value: u32)ReadWord :: (buffer: *Buffer) -> u32Long :: (buffer: *Buffer, value: s64)ReadLong :: (buffer: *Buffer) -> s64Bytes :: (buffer: *Buffer, value: []u8)ReadBytes :: (buffer: *Buffer) -> s32Field :: struct { name: string; type: string; flag: s32; bit: s32; }ParseField :: (token: string) -> FieldNext :: (text: string, at: *s32) -> stringEncodeText :: (buffer: *Buffer, kind: string, json: string, at: s32)EncodeType :: (buffer: *Buffer, kind: string, json: string, at: s32, depth: s32)DecodeType :: (buffer: *Buffer, kind: string, out: *Vec(u8), depth: s32)Double :: (buffer: *Buffer, out: *Vec(u8))A double (video durations, coordinates, ratings) is written with up to nine decimals. JSON has no NaN or infinity, and values too large to round exactly that way are not real Telegram values; both become null.
Encode :: (json: string, bytes: []u8) -> s32Decode :: (bytes: []u8) -> stringFinished :: (buffer: *Buffer, kind: string) -> boolA failed or partly read reply records why in failure.
DecodeResponse :: (method: string, bytes: []u8) -> stringtelegram/src/encoding.zi
encoding
Import with #import "telegram/encoding".
Alphabet :: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"HexAlphabet :: "0123456789abcdef"Hex :: (bytes: []u8) -> stringNibble :: (b: u8) -> s32Unhex :: (text: string, bytes: []u8) -> boolBase64 :: (bytes: []u8, url: bool) -> stringDigit :: (b: u8) -> s32Unbase64 :: (text: string, bytes: []u8) -> s32Quote :: (text: string) -> stringString :: (json: string, at: s32, name: string) -> stringInteger :: (text: string) -> s64Raw :: (json: string, at: s32) -> stringNumber :: (json: string, at: s32, name: string) -> s64Kind :: (json: string) -> stringCopy :: (text: string) -> stringTrim :: (text: string) -> stringtelegram/src/mtproto.zi
mtproto
MTProto 2.0 intermediate TCP transport and authenticated key exchange. All protocol and cryptography are Ziran; sockets and entropy are OS services.
Import with #import "telegram/mtproto".
Connection :: struct { socket: Socket.LinuxSocket; key: [256]u8; key_id: s64; salt: s64; session: s64; sequence: s32; last_id: s64; clock: s64; dc: s32; test: bool; ready: bool; error: string; received: [128]s64; receive_index: s32; }Builder :: () -> Vec(u8)Now :: () -> s64Integer :: (bytes: []u8) -> s64NewId :: (connection: *Connection) -> s64Address :: (dc: s32, test: bool) -> [4]u8Open :: (connection: *Connection, dc: s32, test: bool) -> boolSendPacket :: (connection: *Connection, bytes: []u8) -> boolExact :: (connection: *Connection, bytes: []u8, timeout: s32) -> boolReceivePacket :: (connection: *Connection, bytes: []u8, timeout: s32) -> s32Unencrypted :: (connection: *Connection, json: string) -> stringAddMod :: (a: u64, b: u64, n: u64) -> u64MultiplyMod :: (a: u64, b: u64, n: u64) -> u64Gcd :: (a: u64, b: u64) -> u64Factor :: (n: u64) -> u64Natural :: (value: u64) -> stringRsaPad :: (data: []u8, test: bool, output: []u8) -> boolPrime :: "c71caeb9c6b1c9048e6c522f70f13f73980d40238e3e21c14934d037563d930f48198a0aa7c14058229493d22530f4dbfa336f6e0ac925139543aed44cce7c3720fd51f69458705ac68cd4fe6b6b13abdc9746512969328454f18faf8c595f642477fe96bb2a941d5bcd1d4ac8cc49880708fa9b378e3c4f3a9060bee67cf9a4a4a695811051907e162753b56b0f6b410dba74d8a84b2a14b3144e0ef1284754fd17ed950d5965b4b9dd46582db1178d169c6bc465b0d6ff9ca3928fef5b9ae4e418fc15e83ebea0f87fa9ff5eed70050ded2849f47bf959d956850ce929851f0d8115f635b105ee2e4e15d04b2454bf6f4fadf034b10403119cd8e3b92fcc5b"ValidDh :: (prime: []u8, g: s32, public: []u8) -> boolKeyExchange :: (connection: *Connection) -> boolDerive :: (connection: *Connection, msg_key: []u8, incoming: bool, key: []u8, iv: []u8)Encrypt :: (connection: *Connection, body: []u8, content: bool, output: []u8) -> s32Decrypt :: (connection: *Connection, input: []u8, output: []u8) -> s32telegram/src/client.zi
client
A synchronous RPC client with a separately drainable update queue. The caller owns one Client and calls it from one thread. API methods are TL objects; bytes fields use base64 and int128/int256 fields use hex.
Import with #import "telegram/client".
Client :: struct { connection: MT.Connection; api_id: s32; api_hash: string; device: string; initialized: bool; response: string; pending: s64; method: string; retry: bool; updates: [256]string; head: s32; tail: s32; gap: bool; }Error :: (code: s32, message: string) -> stringConnect :: (client: *Client, dc: s32, test: bool) -> boolQueue :: (client: *Client, value: string)NextUpdate :: (client: *Client) -> stringSend :: (client: *Client, json: string, content: bool) -> s64Ack :: (client: *Client, id: s64)Unpack :: (body: []u8, output: []u8) -> s32Handle :: (client: *Client, body: []u8, server_id: s64, depth: s32)Poll :: (client: *Client, timeout_ms: s32) -> boolRequest :: (client: *Client, query: string) -> stringtelegram/src/auth.zi
auth
Account authorization over the Ziran MTProto client. The caller stores Snapshot in private storage; phone codes and passwords are never persisted.
Import with #import "telegram/auth".
Account :: struct { client: RPC.Client; phone: string; code_hash: string; state: string; user: string; error: string; password_parameters: string; qr: string; qr_expires: s64; }HashBytes :: (bytes: []u8, output: []u8)Salted :: (data: []u8, salt: []u8, output: []u8)PasswordProof :: (parameters: string, password: string, entropy: []u8) -> stringSnapshot :: (account: *Account) -> stringRestore :: (account: *Account, snapshot: string) -> boolResumePassword :: (account: *Account, parameters: string) -> boolRecover a pending two-factor challenge using the saved authorization key. No phone code or password is stored in the snapshot.
Connect :: (account: *Account, api_id: s32, api_hash: string, device: string, snapshot: string) -> boolRequest :: (account: *Account, query: string) -> stringAdvance :: (account: *Account, reply: string) -> boolPhone :: (account: *Account, phone: string) -> boolCode :: (account: *Account, code: string) -> boolPassword :: (account: *Account, password: string) -> booltelegram/src/messages.zi
messages
Normalize Telegram API messages without losing callback bytes or peer IDs.
Import with #import "telegram/messages".
ChatId :: (peer: string) -> s64Peer :: (chat: s64) -> stringNormalize :: (message: string, chat: s64, self: s64) -> stringtelegram/src/members.zi
members
Normalize chat members, user presence and shared-media counts into the TDLib-style objects the session returns.
Import with #import "telegram/members".
Flag :: (json: string, at: s32, name: string) -> boolBool :: (value: bool) -> stringUser :: (json: string, at: s32) -> stringA user's presence, bot access and active username. Photo IDs are 64-bit, so they travel as text like chat photo IDs do.
Members :: (reply: string, limit: s64) -> stringMembers from channels.channelParticipants or messages.chatFull, with each member's user alongside so callers need no lookup per member.
Filter :: (name: string) -> stringTDLib message filters and the API filters that count the same messages.
CountQuery :: (peer: string, filter: string) -> stringCount :: (reply: string) -> stringOne search counter, from the vector messages.getSearchCounters returns.
telegram/src/session.zi
session
One account connection, sign-in state and event queue for CLI/native clients. This small JSON interface is implemented entirely over Ziran MTProto/TL.
Import with #import "telegram/session".
Join :: (a: string, b: string) -> stringaccount: Auth.Accountdirectory: stringdirectory_fd: s32last_snapshot: stringqueue: [8192]stringhead: s32overflow: booltail: s32peer_count: s32peer_hashes: [4096]s64peer_ids: [4096]s64channel_gap: [4096]boolchannel_pts: [4096]s64channel_saved: [4096]s64offset_date: [2]s64offset_id: [2]s64offset_peer: [2]stringdate: s64next_difference: s64next_sync: s64pts: s64qts: s64seq: s64saved_date: s64saved_pts: s64saved_qts: s64saved_seq: s64recovering: boolstarted: boolsend_success: stringPush :: (value: string)Authorization :: ()Create :: () -> s32Private :: (fd: s32, kind: u32) -> boolLoad :: () -> stringRestoreUpdates :: (snapshot: string)Checkpoint :: ()Snapshot :: () -> stringSave :: () -> boolReply :: (value: string, extra: string)Error :: (text: string, extra: string, code: s64)Telegram's own error code is kept (404 ends loadChats, 420 is a flood wait); local and transport failures report 400.
Cache :: (reply: string)InputPeer :: (id: s64) -> stringRequest :: (query: string) -> stringPeerSlot :: (id: s64) -> s32Message :: (message: string, chat: s64)Advance :: (update: string, chat: s64) -> boolUpdate :: (update: string)State :: (value: string)Difference :: () -> boolChannelDifference :: (slot: s32) -> boolAnnounce :: (reply: string, folder: s32) -> s32Announces each dialog in a messages.getDialogs reply as a chat: its title (a person's full name), type, newest message and mute state.
Dialogs :: (folder: s32) -> stringmedia: [6]RPC.ClientMedia lives on its own data center; each gets one connection authorized with an authorization exported from the account's data center.
media_ready: [6]boolRelease :: (value: string)Large replies are released at once instead of living until exit.
FileRequest :: (dc: s32, query: string) -> stringPhotoSize :: (message: string, photo: s32) -> stringThe largest photo size, as (thumb type, bytes).
Download :: (request: string) -> stringDownloads a message's photo or document to a new private file at destination. Files larger than limit bytes are refused before download.
Fetch :: (place: string, dc: s32, destination: string, mime: string, name: string) -> stringSaves the file at location on data center dc to a new owner-only file.
ChatPhoto :: (request: string) -> stringA chat's small profile photo (160 by 160 JPEG) as a new owner-only file.
upload_error: stringUploads a local file in 512 KiB parts and returns its InputFile.
Upload :: (path: string, name: string) -> stringReplyTo :: (request: string) -> stringSentId :: (reply: string) -> s64The new message's ID from a send reply; other updates apply as usual.
Pending :: (chat: s64, id: s64) -> stringMedia :: (content: string, file: string, name: string) -> stringUploaded voice notes carry Telegram's audio/voice attribute instead of arriving as a downloadable document. Photo and document behavior stays.
SendMedia :: (request: string, chat: s64, peer: string) -> stringA photo or document from a local path, with an optional caption.
Forward :: (request: string, peer: string) -> stringForwards messages of one chat to another as the account.
found_ids: [64]s64People, bots, groups and channels matching a name, @username or +phone.
found_count: s32Found :: (out: *Vec(u8), reply: string, comma: *string)SearchChats :: (request: string) -> stringGroupQuery :: (request: string) -> stringPrivate groups may start with just their creator. Keep access hashes inside the account transport, as with every other chat operation.
TextQuery :: (request: string, peer: string) -> stringA durable outbox can retain Telegram's deduplication identity across restarts. Other callers retain a fresh cryptographically random message identity.
GroupReply :: (reply: string, selected: s64) -> stringDispatch :: (request: string) -> stringSend :: (client: s32, request: string)Receive :: (timeout: float64) -> stringExecute :: (request: string) -> string