### Install teleproto with yarn Source: https://docs.teleproto.dev/installation Install the teleproto package using yarn. ```bash yarn add teleproto ``` -------------------------------- ### Install teleproto with pnpm Source: https://docs.teleproto.dev/installation Install the teleproto package using pnpm. ```bash pnpm add teleproto ``` -------------------------------- ### Set up a new teleproto project Source: https://docs.teleproto.dev/quick-start Creates a new project directory, initializes a Node.js package, and installs the teleproto package along with TypeScript and tsx as dev dependencies. ```bash mkdir my-bot && cd my-bot npm init -y npm install teleproto npm install -D tsx typescript @types/node ``` -------------------------------- ### resetLoginEmail example Source: https://docs.teleproto.dev/authentication Starts the email reset process by sending a confirmation code to the phone. Use this when you lost access to the recovery email. The code shows the call with a phone number string. ```typescript // You lost access to the email tied to 2FA. // Telegram will text or call the phone to confirm. await client.resetLoginEmail("+15551234567"); ``` -------------------------------- ### Install teleproto via npm Source: https://docs.teleproto.dev/ Install the teleproto package using npm. The package is pure JavaScript with no native build step, and TypeScript types are included. ```bash npm install teleproto ``` -------------------------------- ### StringSession usage with TelegramClient Source: https://docs.teleproto.dev/sessions Shows how to create a StringSession from an environment variable, start a TelegramClient, save the session to a string, and reuse it on the next run. The example uses an empty string for a fresh login and prints the saved session for storage in a secret manager or env var. ```typescript import { TelegramClient } from "teleproto"; import { StringSession } from "teleproto/sessions"; // First run: empty string means "no saved state, log in from scratch" const session = new StringSession(process.env.TELEPROTO_SESSION ?? ""); const client = new TelegramClient(session, apiId, apiHash, { connectionRetries: 5, }); await client.start({ /* resolvers... */ }); // Save this somewhere safe (secret manager, env var) and reuse next run const saved = client.session.save(); console.log("TELEPROTO_SESSION=", saved); // Next run: // new StringSession(process.env.TELEPROTO_SESSION!) // skips the entire auth flow. ``` -------------------------------- ### Store API credentials in .env Source: https://docs.teleproto.dev/installation Example .env file with TG_API_ID and TG_API_HASH. Never commit these to git. ```dotenv TG_API_ID=1234567 TG_API_HASH=0123456789abcdef0123456789abcdef ``` -------------------------------- ### catchUp() after a restart (catchup.ts) Source: https://docs.teleproto.dev/internals/updates Example of calling client.catchUp() after connect() to replay missed updates. Without it, connecting only subscribes to new updates; catchUp() uses persisted pts/qts/date to fetch the difference until differenceEmpty. ```typescript await client.connect(); await client.catchUp(); // replay what happened while the process was down // The session persisted pts/qts/date at last shutdown; // catchUp() runs getDifference from that state until Telegram // answers differenceEmpty. Handlers fire for each replayed event // exactly as if it had arrived live. ``` -------------------------------- ### getMe and getPeerId usage Source: https://docs.teleproto.dev/concepts/entities Shows how to use getMe() to retrieve the logged-in user's info and getPeerId() to normalize peer-shaped input to a numeric id. The example logs the username or first name, and demonstrates both the bare id and the marked form with the -100 prefix. ```typescript const me = await client.getMe(); console.log("Logged in as", me.username ?? me.firstName); // Normalized numeric id — channels come back negative, // or with the -100 prefix when addMark is true. const id = await client.getPeerId("durov"); // 123456789 const marked = await client.getPeerId("@somechannel", true); // -1001234567890 ``` -------------------------------- ### TelegramClient options for tuning file transfer (knobs.ts) Source: https://docs.teleproto.dev/internals/file-transfer Configuration example from knobs.ts for tuning transfer performance. Sets connectionRetries, maxConcurrentDownloads as a client-wide ceiling on parallel chunks, and downloadPool options: poolSize for parallel senders kept hot, workers for in-flight parts per sender, and requestDeadlineMs for the per-part deadline. The comments note that sendFile/uploadFile also accept a workers option for per-call chunk parallelism. ```javascript const client = new TelegramClient(session, apiId, apiHash, { connectionRetries: 5, // per-call chunk parallelism for uploads // (sendFile / uploadFile also take { workers }) maxConcurrentDownloads: 6, // client-wide ceiling on parallel chunks downloadPool: { poolSize: 4, // parallel senders the pool keeps hot workers: 8, // in-flight parts per sender requestDeadlineMs: 30_000, // per-part deadline; raise on slow links }, }); ``` -------------------------------- ### Dockerfile for teleproto with persistent session volume Source: https://docs.teleproto.dev/production Use this Dockerfile to build a Node 20 Alpine image, install production dependencies, and mount a volume at /app/sessions for StoreSession persistence. The CMD runs the compiled entry point. Mount a volume so session data survives container restarts. ```dockerfile # Dockerfile FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --omit=dev COPY . . # StoreSession writes here — mount a volume so it survives restarts VOLUME ["/app/sessions"] CMD ["node", "dist/index.js"] ``` -------------------------------- ### Handle CallbackQuery event with inline button Source: https://docs.teleproto.dev/guides/events Two-part example: first sends a message with an inline button using NewMessage, then handles the button tap with CallbackQuery. The CallbackQuery handler uses event.answer() to close the loading spinner and optionally show a message. The pattern /^click:/ matches the button's data. ```TypeScript import { Button } from "teleproto/tl/custom/button"; import { CallbackQuery, CallbackQueryEvent, NewMessage } from "teleproto/events"; // Send a message with one inline button. client.addEventHandler(async (event) => { if (event.message.message !== "/menu") return; await event.message.reply({ message: "pick one", buttons: [Button.inline("Click me", Buffer.from("click:1"))], }); }, new NewMessage({ pattern: /^\/menu$/ })); // Handle the tap. client.addEventHandler(async (event: CallbackQueryEvent) => { await event.answer({ message: "you tapped it", alert: false, cacheTime: 0, }); }, new CallbackQuery({ pattern: /^click:/ })); ``` -------------------------------- ### Hybrid bot with grammY and teleproto (hybrid.ts) Source: https://docs.teleproto.dev/concepts/bot-api-vs-mtproto Shows a hybrid setup where a Bot API bot (grammY) handles public commands and a teleproto userbot performs heavy lifting, coordinating via a private channel. The bot sends a digest command to the channel, and the userbot listens for it, fetches history, and processes it. ```TypeScript // Bot account (Bot API) posts a public command response. import { Bot } from "grammy"; const bot = new Bot(process.env.BOT_TOKEN!); bot.command("digest", async (ctx) => { await ctx.reply("Generating digest..."); // Tell the userbot to do the heavy lifting via a private channel. await ctx.api.sendMessage(process.env.COORD_CHANNEL!, `digest:${ctx.chat.id}`); }); bot.start(); // Userbot (teleproto) reads the coordination channel and does the work. import { TelegramClient, Api } from "teleproto"; import { StoreSession } from "teleproto/sessions"; import { NewMessage } from "teleproto/events"; const client = new TelegramClient( new StoreSession("userbot"), Number(process.env.TG_API_ID), process.env.TG_API_HASH!, { connectionRetries: 5 }, ); await client.start({ /* ...auth params... */ }); client.addEventHandler(async (event) => { const text = event.message.message ?? ""; if (!text.startsWith("digest:")) return; const chatId = text.slice("digest:".length); const history = await client.getMessages("@somechannel", { limit: 100 }); // ...summarise, then push back through the Bot API... }, new NewMessage({ chats: [Number(process.env.COORD_CHANNEL_ID)] })); ``` -------------------------------- ### User authentication with client.start() in teleproto Source: https://docs.teleproto.dev/authentication Interactive login flow using client.start() with callbacks for phone number, login code, 2FA password, and optional first/last names. onError returning true retries the step; false or nothing aborts. firstAndLastNames only fires for new accounts. Saves the session string after login. ```TypeScript import { TelegramClient } from "teleproto"; import { StringSession } from "teleproto/sessions"; import { createInterface } from "node:readline/promises"; const rl = createInterface({ input: process.stdin, output: process.stdout }); const apiId = Number(process.env.API_ID); const apiHash = process.env.API_HASH!; const session = new StringSession(process.env.SESSION ?? ""); const client = new TelegramClient(session, apiId, apiHash, { connectionRetries: 5, }); await client.start({ phoneNumber: () => rl.question("Phone: "), phoneCode: () => rl.question("Login code: "), password: (hint) => rl.question(`2FA password${hint ? ` (${hint})` : ""}: `), firstAndLastNames: async () => [ await rl.question("First name: "), await rl.question("Last name: "), ], onError: (err) => { console.error(err); return false; }, }); console.log("Logged in as", (await client.getMe()).username); console.log("Save this:", client.session.save()); rl.close(); ``` -------------------------------- ### Sign in with client.start() and StoreSession Source: https://docs.teleproto.dev/quick-start Initializes a TelegramClient with a StoreSession and performs the interactive login flow. The client.start() method prompts for phone number, SMS code, and optional 2FA password, then prints the signed-in username. Subsequent runs skip the prompts because the session is persisted. ```typescript import "node:process"; import { TelegramClient } from "teleproto"; import { StoreSession } from "teleproto/sessions"; import { NewMessage } from "teleproto/events"; import { createInterface } from "node:readline/promises"; const apiId = Number(process.env.TG_API_ID); const apiHash = process.env.TG_API_HASH!; // Disk-backed session — persists under ./bot-session/ const session = new StoreSession("bot-session"); const client = new TelegramClient(session, apiId, apiHash, { connectionRetries: 5, }); const rl = createInterface({ input: process.stdin, output: process.stdout }); await client.start({ phoneNumber: () => rl.question("Phone: "), phoneCode: () => rl.question("Code from Telegram: "), password: () => rl.question("2FA password (if set): "), onError: (err) => { console.error(err); return false; }, }); rl.close(); console.log("Signed in as", (await client.getMe()).username); ``` -------------------------------- ### First connection with TelegramClient Source: https://docs.teleproto.dev/ Creates a TelegramClient with a StringSession, prompts for phone, 2FA password, and code, then prints user info and session string. Save the session string for future logins. ```typescript import { TelegramClient } from "teleproto"; import { StringSession } from "teleproto/sessions"; import { createInterface } from "node:readline/promises"; const rl = createInterface({ input: process.stdin, output: process.stdout }); const apiId = 0; const apiHash = ""; const session = new StringSession(""); const client = new TelegramClient(session, apiId, apiHash, { connectionRetries: 5, }); await client.start({ phoneNumber: () => rl.question("Phone: "), password: () => rl.question("2FA password: "), phoneCode: () => rl.question("Code: "), onError: console.error, }); console.log(await client.getMe()); console.log("Session string:", client.session.save()); rl.close(); ``` -------------------------------- ### Creating Peer and InputPeer objects Source: https://docs.teleproto.dev/concepts/entities Shows how to instantiate Peer and InputPeer objects for users, chats, and channels. The access_hash is per-account and must not be persisted across logins. ```typescript import { Api } from "teleproto"; // Peer — identity only, no auth material new Api.PeerUser({ userId }); new Api.PeerChat({ chatId }); new Api.PeerChannel({ channelId }); // InputPeer — what Api.* methods actually want new Api.InputPeerUser({ userId, accessHash }); new Api.InputPeerChat({ chatId }); new Api.InputPeerChannel({ channelId, accessHash }); new Api.InputPeerSelf(); ``` -------------------------------- ### Combining filters with func Source: https://docs.teleproto.dev/guides/events Uses the func option on NewMessage to run a cross-field predicate after cheap structural filters. The example checks for a long message from a specific sender. ```typescript import { NewMessage, NewMessageEvent } from "teleproto/events"; // func runs after the cheap filters. Use it for cross-field logic. client.addEventHandler((event: NewMessageEvent) => { console.log("long message from a friend:", event.message.message); }, new NewMessage({ incoming: true, func: (e) => (e.message.message ?? "").length > 200 && e.message.senderId?.toString() === "123456789", })); ``` -------------------------------- ### Cap concurrent downloads with maxConcurrentDownloads Source: https://docs.teleproto.dev/guides/media Set maxConcurrentDownloads on TelegramClientParams to cap parallel chunk requests across the entire client. This example also sets connectionRetries to 5. ```typescript import { TelegramClient } from "teleproto"; // Cap concurrent download chunks across the whole client const client = new TelegramClient(session, apiId, apiHash, { connectionRetries: 5, maxConcurrentDownloads: 6, }); ``` -------------------------------- ### One-shot request-reply pattern Source: https://docs.teleproto.dev/guides/events Implements a one-shot request-reply flow without a dispatcher or FSM: register a handler, resolve a promise when the predicate matches, and remove the handler. The example sends a message and waits for a reply. ```typescript import { NewMessage, NewMessageEvent } from "teleproto/events"; function waitFor(predicate: (e: NewMessageEvent) => boolean) { return new Promise((resolve) => { const handler = (e: NewMessageEvent) => { if (!predicate(e)) return; client.removeEventHandler(handler, new NewMessage({})); resolve(e); }; client.addEventHandler(handler, new NewMessage({})); }); } await client.sendMessage("me", { message: "say something" }); const reply = await waitFor((e) => !e.message.out); console.log("got:", reply.message.message); ``` -------------------------------- ### Forward messages with forwardMessages Source: https://docs.teleproto.dev/guides/messages Moves one or more messages to a destination entity. Pass an array of message ids and the source peer via fromPeer. The example forwards message #1234 from @telegram to your saved messages. ```typescript // Forward message #1234 from a source channel to your saved messages. await client.forwardMessages("me", { messages: [1234], fromPeer: "@telegram", }); ``` -------------------------------- ### Configure TelegramClient with new options Source: https://docs.teleproto.dev/migrating-from-gramjs Shows how to pass the new TeleProto-specific options to the TelegramClient constructor: maxConcurrentDownloads, downloadPool, reCaptchaCallback, and testServers. Use this when you need to cap parallel downloads, tune the per-file worker pool, handle reCaptcha challenges, or connect to Telegram's test data centres. ```typescript const client = new TelegramClient(session, apiId, apiHash, { connectionRetries: 5, // cap parallel downloads across the whole client maxConcurrentDownloads: 4, // per-file worker pool, retry policy, etc. downloadPool: { workers: 4 }, // called when Telegram challenges a request with a reCaptcha reCaptchaCallback: async (siteKey) => { return await solveCaptcha(siteKey); // your provider }, // route through Telegram's test DCs instead of production testServers: false, }); ``` -------------------------------- ### Handle authentication errors with typed errors Source: https://docs.teleproto.dev/authentication Use this snippet to handle authentication errors when starting a client. It catches specific typed errors like SessionRevokedError, AuthKeyUnregisteredError, and UserDeactivatedError to decide between re-login and giving up. ```TypeScript import { errors } from "teleproto"; try { await client.start({ /* resolvers */ }); } catch (err) { if (err instanceof errors.SessionRevokedError) { // terminated from another device — clear the saved string, re-login } else if (err instanceof errors.AuthKeyUnregisteredError) { // key no longer known to Telegram — same treatment } else if (err instanceof errors.UserDeactivatedError) { // the account itself was deactivated — stop retrying } else { throw err; } } ``` -------------------------------- ### Raw request anatomy: messages.getHistory Source: https://docs.teleproto.dev/concepts/raw-api A raw call consists of a constructor for the TL method, an invoke, and a return type that is usually a union. The example shows constructing Api.messages.GetHistory and invoking it, then narrowing the result union with instanceof. ```typescript import bigInt from "big-integer"; import { Api } from "teleproto"; const req = new Api.messages.GetHistory({ peer: await client.getInputEntity("@telegram"), limit: 20, offsetId: 0, offsetDate: 0, addOffset: 0, maxId: 0, minId: 0, hash: bigInt.zero, }); const result = await client.invoke(req); // result is Api.messages.TypeMessages — a union. Narrow by className: if (result instanceof Api.messages.Messages || result instanceof Api.messages.MessagesSlice || result instanceof Api.messages.ChannelMessages) { for (const msg of result.messages) { if (msg instanceof Api.Message) { console.log(msg.id, msg.message); } } } ``` -------------------------------- ### Track upload progress with progressCallback Source: https://docs.teleproto.dev/guides/media Pass progressCallback to sendFile to receive (transferred, total) as bigInt-compatible values. Coerce with Number() for percentage math; the example prints a progress bar and only updates on whole percent changes. ```typescript let lastPrinted = 0; await client.sendFile("me", { file: "./archive.zip", workers: 4, progressCallback: (transferred, total) => { const pct = Math.floor((Number(transferred) / Number(total)) * 100); if (pct === lastPrinted) return; lastPrinted = pct; const bar = "#".repeat(pct / 2).padEnd(50, "-"); process.stdout.write(`\r[${bar}] ${pct}%`); }, }); process.stdout.write("\n"); ``` -------------------------------- ### Download photos from channel messages Source: https://docs.teleproto.dev/guides/media Iterates over the last 200 messages of a channel and downloads each photo to a file. Uses `Api.MessageMediaPhoto` to filter for photos and `outputFile` to specify the destination path. Omit `outputFile` to get a Buffer instead. ```typescript import { Api } from "teleproto"; // Pull every photo from the last 200 messages of a channel for await (const message of client.iterMessages("@some_channel", { limit: 200, })) { if (!(message.media instanceof Api.MessageMediaPhoto)) continue; // outputFile is optional — omit it and you get a Buffer back await client.downloadMedia(message, { outputFile: `./photos/${message.id}.jpg`, }); } ``` -------------------------------- ### Listen for !echo messages and reply Source: https://docs.teleproto.dev/quick-start Registers a NewMessage handler that replies to incoming messages starting with !echo, ignoring outgoing messages. Then keeps the process alive until SIGINT and disconnects cleanly. Note that client.disconnected is a boolean getter, not a Promise. ```typescript client.addEventHandler(async (event) => { // ignore our own outgoing messages if (event.message.out) return; const text = (event.message.message || "").trim(); await event.message.reply({ message: `you said: ${text}` }); }, new NewMessage({ pattern: /^!echo\b/ })); // Keep the process alive. client.disconnected is a boolean getter, // not a Promise — don't await it. await new Promise((resolve) => { process.once("SIGINT", () => resolve()); }); await client.disconnect(); ``` -------------------------------- ### StringSession usage with legacy and new formats Source: https://docs.teleproto.dev/migrating-from-gramjs Shows how to construct a StringSession from an existing 352-character GramJS/Telethon string and how to save a new session in the version-prefixed format. Both formats are read on load. ```typescript import { StringSession } from "teleproto/sessions"; // Your existing 352-char GramJS/Telethon string still works. const session = new StringSession(legacyTelethonString); // New saves come out in the version-prefixed format // ("1" + base64). Both formats are read on load. const fresh = session.save(); ``` -------------------------------- ### MTProto file transfer pipeline: upload and download part flow Source: https://docs.teleproto.dev/internals/file-transfer Shows the high-level part-based flow for uploads and downloads in MTProto. Uploads split files into parts of at most 512 KB, using upload.saveFilePart for files up to 10 MB and upload.saveBigFilePart above that, then commit with messages.sendMedia only after every part is stored. Downloads fetch parts via upload.getFile with offset and limit, handle CDN via upload.getCdnFile with hashes checked against the master DC, and reassemble parts into a Buffer or flush to outputFile. ```text upload: sendFile("./video.mp4") 1. split into parts of at most 512 KB 2. per part: upload.saveFilePart (files <= 10 MB) upload.saveBigFilePart (files > 10 MB) 3. commit: messages.sendMedia -- only after every part is stored download: downloadMedia(message) 1. per part: upload.getFile { offset, limit } (parallel in-flight requests, sequential offsets) 2. CDN case: upload.getCdnFile, hashes checked against master DC 3. parts reassembled -> Buffer, or flushed to outputFile ``` -------------------------------- ### Iterate dialogs with iterDialogs Source: https://docs.teleproto.dev/guides/chats Use iterDialogs to paginate through the user's chat list, including DMs, groups, channels, and saved messages. The example logs each dialog's unread count and title, with options for limit and ignoring pinned dialogs. ```typescript for await (const dialog of client.iterDialogs({ limit: 200, ignorePinned: false, })) { console.log( `[${dialog.unreadCount.toString().padStart(4)}] ${dialog.title}`, ); } ``` -------------------------------- ### client.api facade: typed async TL methods Source: https://docs.teleproto.dev/concepts/raw-api Demonstrates the client.api facade, which mirrors the entire TL schema as camelCase async methods with full types and JSDoc. It behaves identically to client.invoke under the hood. ```typescript // Every TL method, as a plain typed async call. Params and result // are fully typed; JSDoc from core.telegram.org shows up in your IDE. const config = await client.api.help.getConfig({}); await client.api.messages.sendMessage({ peer: await client.getInputEntity("me"), message: "hi from the facade", silent: true, }); // Identical wire behavior to client.invoke(new Api.messages.SendMessage(...)), // minus the constructor ceremony. ``` -------------------------------- ### Run the bot with tsx Source: https://docs.teleproto.dev/quick-start Loads environment variables from .env and runs the bot script with tsx. On first run, you'll be prompted for phone number and code; the bot then replies to messages matching !echo. ```bash # load env vars from .env (use dotenv-cli, direnv, or your shell) export $(grep -v '^#' .env | xargs) npx tsx bot.ts ``` -------------------------------- ### Pin and unpin messages with pinMessage and unpinMessage Source: https://docs.teleproto.dev/guides/messages Pins and later unpins a message. notify: false avoids pinging every chat member (Telegram's default behavior); pmOneside: true pins only on your side of a 1:1 conversation. The example sends a note, pins it, then unpins it. ```typescript const note = await client.sendMessage("me", { message: "read me first" }); await client.pinMessage("me", note.id, { notify: false, pmOneside: true }); // ...later await client.unpinMessage("me", note.id); ``` -------------------------------- ### Gap detection and getDifference logic Source: https://docs.teleproto.dev/internals/updates Pseudocode for how teleproto decides whether an incoming update is expected, too high (gap), or too low (dedup). Shows when to call updates.getDifference vs channels.getDifference. ```text incoming update carries (pts, pts_count) expected: local_pts + pts_count == update.pts match: apply, set local_pts = update.pts too high: a gap -- buffer briefly, then updates.getDifference(local_pts, ...) for common state channels.getDifference(channel, pts) for that channel only too low: already applied -- drop (dedup) ``` -------------------------------- ### Custom Session: Extending Session for Redis storage Source: https://docs.teleproto.dev/sessions Shows the required abstract surface for a custom session and a partial Redis-backed implementation. The client only talks to the Session surface, so any backing store works. Entity hooks may return Promises. ```typescript import { Session } from "teleproto/sessions"; export class RedisSession extends Session { // Required surface (abstract on Session): // setDC(dcId, serverAddress, port) // get dcId(): number // get serverAddress(): string // get port(): number // get authKey(): AuthKey | undefined // setAuthKey(authKey, dcId?) // getAuthKey(dcId?): AuthKey // getInputEntity(key): any (may return a Promise) // processEntities(tlo): void (may return a Promise) // load(): Promise // save(): unknown // close(): void // delete(): Promise // // Plus the testServers flag. async load() { /* read from redis */ } save() { /* serialize and write to redis */ } // ...rest of the surface } const client = new TelegramClient(new RedisSession(), apiId, apiHash, { connectionRetries: 5, }); ``` -------------------------------- ### Send file from a path Source: https://docs.teleproto.dev/guides/media Sends a file by providing a filesystem path. teleproto reads and streams the file automatically. ```typescript // Path on disk — teleproto reads and streams it await client.sendFile("me", { file: "./report.pdf", caption: "monthly numbers", }); ``` -------------------------------- ### Connection state machine transitions Source: https://docs.teleproto.dev/internals/connection State machine table for teleproto connections since v1.228. Shows transitions from idle to connecting, ready, broken, and failed, with the trigger for each transition. Note that connecting has a real deadline: a hung dial times out and moves to the next address for that DC. ```text state -> next on -------------------------------------------------------------------- idle -> connecting connect(): dial the DC address connecting -> ready TCP up, handshake done, InitConnection sent connecting -> connecting attempt timed out: next / alternate address connecting -> failed connectionRetries exhausted ready -> broken socket error, missed pong, server close broken -> connecting autoReconnect (default) after retryDelay ``` -------------------------------- ### StoreSession: Disk-backed session persistence Source: https://docs.teleproto.dev/sessions Creates a StoreSession that persists to a directory in process.cwd(). No explicit save() is needed; writes happen on every change. The name 'session' is reserved and throws. ```typescript import { TelegramClient } from "teleproto"; import { StoreSession } from "teleproto/sessions"; // Persists everything to ./my-account/ on disk const session = new StoreSession("my-account"); const client = new TelegramClient(session, apiId, apiHash, { connectionRetries: 5, }); await client.start({ /* resolvers... */ }); // No explicit save() — StoreSession writes through on every change. ``` -------------------------------- ### pool.ts - Multi-account client pool with teleproto Source: https://docs.teleproto.dev/production Creates a pool of TelegramClient instances, one per account, using a Map and StoreSession. Logs in on demand and fans out with Promise.all. Requires distinct StoreSession names; 'session' is reserved and will throw. ```typescript import { TelegramClient } from "teleproto"; import { StoreSession } from "teleproto/sessions"; const apiId = Number(process.env.TG_API_ID); const apiHash = process.env.TG_API_HASH!; const clients = new Map(); async function getClient(name: string) { const existing = clients.get(name); if (existing) return existing; const client = new TelegramClient( new StoreSession(name), // persists to .// apiId, apiHash, { connectionRetries: 5 }, ); await client.connect(); if (!(await client.isUserAuthorized())) { throw new Error(`session "${name}" not logged in — run interactive login first`); } clients.set(name, client); return client; } // fan out a request across every account const me = await Promise.all( ["account-1", "account-2", "account-3"].map(async (name) => { const c = await getClient(name); return c.getMe(); }), ); ``` -------------------------------- ### Counter scopes and movement (pts, qts, seq) Source: https://docs.teleproto.dev/internals/updates Table showing which counter tracks which scope and what moves it. Use to understand when each counter advances and why channels have independent pts. ```text counter scope moves on --------------------------------------------------------------------- pts account-wide common state new / edited / deleted messages, (DMs, small groups) read markers, ... pts each channel / supergroup the same events, per channel (independent counter each) qts secret-chat & bot events encrypted events, bot callbacks seq the update container stream container-level bookkeeping ``` -------------------------------- ### Two-factor authentication login and settings update (login-2fa.ts) Source: https://docs.teleproto.dev/authentication Handles 2FA during login by providing a password resolver that shows the server-side hint. After login, manage 2FA with client.updateTwoFaSettings, passing currentPassword on accounts that already have 2FA, and optionally newPassword, hint, and email. ```typescript await client.start({ phoneNumber: () => rl.question("Phone: "), phoneCode: () => rl.question("Code: "), password: (hint) => rl.question(`2FA password${hint ? ` (hint: ${hint})` : ""}: `), onError: console.error, }); // Manage 2FA after login await client.updateTwoFaSettings({ currentPassword: process.env.OLD_2FA, newPassword: process.env.NEW_2FA, hint: "favourite mountain", email: "recovery@example.com", }); ``` -------------------------------- ### Download media to a Buffer Source: https://docs.teleproto.dev/guides/media Downloads media into an in-memory Buffer by omitting `outputFile`. The result is `Buffer | undefined` depending on the media type. Useful for piping to S3, hashing, or thumbnail generation. ```typescript // No outputFile -> Buffer in memory const bytes = await client.downloadMedia(message); // bytes is Buffer | undefined depending on the media type ``` -------------------------------- ### Download profile photo with downloadProfilePhoto Source: https://docs.teleproto.dev/guides/media Use client.downloadProfilePhoto to fetch a user's avatar as a Buffer. Pass isBig: true for the hi-res variant; omit it for the small chat-list version. ```typescript // Hi-res variant of the user's avatar const bytes = await client.downloadProfilePhoto("@durov", { isBig: true }); // Buffer of the raw JPEG ``` -------------------------------- ### Send file from a URL Source: https://docs.teleproto.dev/guides/media Sends a file by providing a URL. teleproto fetches the content and forwards it. ```typescript // URL — teleproto fetches and forwards await client.sendFile("me", { file: "https://example.com/cover.png", caption: "from the web", }); ``` -------------------------------- ### Import path changes from telegram to teleproto Source: https://docs.teleproto.dev/migrating-from-gramjs Shows the before and after import statements when migrating from GramJS to teleproto. Replace the 'telegram' package path with 'teleproto' for the main client, sessions, and events modules. ```diff // before import { TelegramClient } from "telegram"; import { StringSession } from "telegram/sessions"; import { NewMessage } from "telegram/events"; // after import { TelegramClient } from "teleproto"; import { StringSession } from "teleproto/sessions"; import { NewMessage } from "teleproto/events"; ```