Skip to content
teleproto

Migrating from GramJS

teleproto is its own library, ideologically inspired by GramJS and going its own way from there. The public surface still resembles GramJS closely enough that most migrations are a single import swap — but a handful of additions and one rename are worth knowing about before you ship.

Why fork#

GramJS shipped the foundation. teleproto picks up where it slowed down: ongoing maintenance against newer TL layers, plus the features Telegram added to the auth and abuse flows after the original codebase stopped tracking them — email verification on first sign-in, reCaptcha challenges on suspicious requests, theFrozen* error family for accounts and methods under restriction.

The public surface stays GramJS-compatible by design. If your GramJS code compiles and runs today, the teleproto port is almost always a find-and-replace on the import path.

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";

Renamed / removed#

One rename to flag. sendReadAcknowledge is gone; the replacement is markAsRead with a different signature. The new shape takes the message id (or undefined for "everything in this chat") as a positional argument, and clearMentions moves into an options object.

markAsRead.ts
// GramJS
await client.sendReadAcknowledge(chat, { maxId: 123 });

// teleproto — single message form
await client.markAsRead(chat, 123);

// teleproto — mark the whole chat read + clear mentions
await client.markAsRead(chat, undefined, { clearMentions: true });

Nothing else from the GramJS surface has been removed outright. If a method went missing on your end after the swap, it's almost certainly a TL-layer signature change rather than a rename — see the callout at the bottom of the page.

New TelegramClient params#

TelegramClientParamspicks up four fields that weren't in upstream GramJS:

  • maxConcurrentDownloads — global cap on simultaneous file downloads.
  • downloadPoolPartial<FilePoolOptions> for tuning worker count and retry behaviour per file.
  • reCaptchaCallback (siteKey: string) => Promise<string>. Called when Telegram demands a captcha mid-request; return the solved token.
  • testServers— boolean. Connects to Telegram's test data centres instead of production. Handy for CI accounts.
client.ts
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,
});

New auth params#

UserAuthParams grows three fields to cover the challenges Telegram now puts in front of first-time sign-ins:

  • emailAddress: () => Promise<string> — supplies the email when Telegram asks for one during registration.
  • emailVerification: () => Promise<{ code: string } | { token: string }> — returns either the 6-digit code or the OAuth-style token depending on which channel Telegram used.
  • reCaptchaCallback: (siteKey: string) => Promise<string> — same signature as the client-level one, but supplied per-call.
auth.ts
await client.start({
  phoneNumber: () => prompt("Phone: "),
  phoneCode:   () => prompt("SMS code: "),
  password:    () => prompt("2FA: "),

  // Telegram now sometimes requires an email on first sign-in
  emailAddress: () => prompt("Email: "),
  emailVerification: async () => ({ code: await prompt("Email code: ") }),

  // and a captcha on suspicious flows
  reCaptchaCallback: async (siteKey) => solveCaptcha(siteKey),

  onError: console.error,
});

All three are optional. If Telegram doesn't challenge the flow, they never fire.

New error classes#

The biggest upgrade over GramJS: every Telegram RPC error is generated as its own class from the official error database. Where GramJS made you match err.errorMessage === "SESSION_REVOKED" by hand, teleproto gives you err instanceof errors.SessionRevokedError — for hundreds of error codes, including ones GramJS never modeled:

  • SessionRevokedError / AuthKeyUnregisteredError — the saved session is dead; start() now throws these instead of the old GramJS TypeError crash.
  • FrozenMethodInvalidError / FrozenParticipantMissingError— the account (or target) is restricted. GramJS predates Telegram's account-freezing entirely.
  • EmailUnconfirmedError — sign-in requires email verification the user skipped.
  • SlowModeWaitError — chat has slow mode on; carries .seconds like FloodWaitError. Wait, then retry.

Errors newer than your teleproto build fall back to the base class for their HTTP code (UnauthorizedError, FloodError, …), and .errorMessage always carries the exact server string — so no catch path goes dark.

Typed client.api facade#

GramJS has exactly one raw path: client.invoke(new Api.messages.SendMessage({...})). teleproto keeps that, and adds a typed facade on top: client.api.messages.sendMessage({...}) — every TL method as a plain async call, with params and result fully typed and core.telegram.org docs as JSDoc in your IDE. Same wire behavior, no constructor ceremony. Existing invoke code keeps working unchanged.

Session strings#

You don't need to re-authenticate users when you switch. teleproto's StringSessionreads the legacy 352-character GramJS / Telethon format directly — pass it to the constructor and it loads as IPv4. New saves come out in teleproto's version-prefixed format (a leading 1 followed by base64), which both versions of the library can read going forward.

sessions.ts
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();

Migration steps#

  1. Swap every from "telegram" import for from "teleproto" (and the same on the /sessions, /events, /errors, /tl subpaths). Uninstall telegram, install teleproto.
  2. Re-run tsc --noEmit. Newer TL layers tighten some request and response types — fix what the compiler flags before you run the code.
  3. If you match error strings anywhere (err.errorMessage === ...), replace each check with instanceof against the typed class from teleproto/errors — and add cases for the errors GramJS never modeled: SessionRevokedError, the frozen-account family, EmailUnconfirmedError, SlowModeWaitError.
  4. Search for sendReadAcknowledge and rewrite each callsite to markAsRead with the new signature.