CLAUDE.md · yagop/node-telegram-bot-api
CLAUDE.md from yagop/node-telegram-bot-api: 184 lines covering What this is, Commands, Architecture, Critical invariants and more.
Template by yagop
Chalk Labs
Need help deploying CLAUDE.md · yagop/node-telegram-bot-api?
Chalk Labs sets up agents like this inside real teams: connected to your tools, tested on your data, with a human in the loop where it matters.
Install
Works with Claude Code. Read the repository before you give any tool access to your data.
curl -fsSL https://raw.githubusercontent.com/yagop/node-telegram-bot-api/master/CLAUDE.md -o CLAUDE.md
Read it and adapt it to your project before use.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: prompt-engineering
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
What's in it
A CLAUDE.md file: project memory Claude Code loads at the start of every session.
- For
- Claude Code
- Length
- 184 lines
- File
CLAUDE.md
Sections
- What this is
- Commands
- Architecture
- Critical invariants
- Testing
- Project skills (.claude/skills/)
First lines (shown under the repo's MIT license)
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What this is
`node-telegram-bot-api` v2: a from-scratch, runtime-agnostic TypeScript Telegram Bot API client.
Node 18.2+, also runs on Bun, Deno, Cloudflare Workers, and Vercel/Deno Edge. The source is
ESM/web-standard; the **published** package is dual-module - `zshy` emits both ESM (`*.js`/`*.d.ts`)
and CJS (`*.cjs`/`*.d.cts`), exposed via the `exports` map's `import`/`require` conditions, so it can
be `import`ed or `require`d. `src/core` stays Node-free, so the edge story is unchanged. There is
**no v1 compatibility**; the v1->v2 migration cheatsheet lives in `CHANGELOG.md`.
Current development happens on `feat/v2-core`; the base/release branch is `master`.
## Commands
Bun is the primary toolchain (it is a devDependency and runs the unit suite and generator).
```sh
bun install # install deps (CI uses --frozen-lockfile)
npm run check # full local gate: typecheck (x3) + lint:core + check:edge + unit tests
npm run typecheck # tsc --noEmit over src (strict)
npm run typecheck:test # tsc over test/ (needs bun types installed)
npm run lint:core # FAILS if src/core touches node:* or Node globals (see below)
npm run check:edge # FAILS if src/core bundles a Node builtin (even transitively)
npm test # unit tests via Bun (test/unit)
npm run test:node:unit # unit tests via Node's node:test + tsx (CI runs this on Node 22/24/26)
npm run test:e2e # LIVE tests against api.telegram.org (see Testing)
npm run build # zshy -> dist/ dual ESM+CJS (core, node, types subpaths); postbuild repairs CJS source-map refs
npm run generate:types # regenerate generated sources from live Bot API docs
```
Run a single unit test:
```sh
bun test test/unit/transport.test.ts # one file
bun test -t "429 then success retries" # by test-name substring
```
## Architecture
Three source folders, one published package, exposed via subpath exports (`.`, `./node`, `./types`):
```text
src/
core/ runtime-agnostic. Bot, Context, compose, the single Api client, Transport,
serializeParams + encodeForm, InputFile, the markup / entity / media builders
(optional sugar), longPoll, webhookCallback, framework webhook adapters
(Express / Next.js), errors. Web-standard APIs only.
-> zero node:* imports; runs on Node 18.2+ / Bun / Deno / Workers / edge.
node/ the ONLY folder allowed to import node:*. Node-only sugar: fromPath() (fs
uploads), createWebhookServer() (node:http -> delegates to core's
webhookCallback), the managed run() polling runner, the DEBUG stderr sink.
types/ the generated schema (re-exported by core): discriminated Update, the generated
Api method signatures / *Params / *Result types, expanded MessageEntity.
```
The `exports` map points `.` at the core, `./node` at the Node helpers, and `./types` at the
schema; each subpath ships ESM + CJS with matching typings:
```jsonc
// package.json
"exports": {
".": {
"import": { "types": "./dist/core/index.d.ts", "default": "./dist/core/index.js" },
"require": { "types": "./dist/core/index.d.cts", "default": "./dist/core/index.cjs" }
},
"./node": { /* same shape -> dist/node/* */ },
"./types": { /* same shape -> dist/types/* */ }
}
```
`import { Bot } from "node-telegram-bot-api"` is the runtime-agnostic core; `import { fromPath }
from "node-telegram-bot-api/node"` opts into the Node helpers. The core-vs-node isolation is a CI
rule (`lint:core` + `check:edge`), not a package boundary, so the edge bundle never drags in a Node
builtin.
### The Api client
One **generated** `Api` class - no `Proxy`, no `RawApi`/`Api` split. One concrete method per Bot
API method, each a one-liner over a shared `request()`, taking a single params object plus an
optional trailing `AbortSignal` (`getMe(signal?)`, `sendMessage(params, signal?)`). Real methods
give correct stack traces, are greppable, and need no casts; adding a method is a regenerate, never
a hand-written body. `Bot` holds an `Api`; `ctx.api` and `bot.api` expose it. The whole client
ships regardless of which methods a bot calls (no per-method tree-shaking - a deliberate trade for
the single discoverable `api.*` namespace).
### Request pipeline (the only place serialization happens)
`Api.request` -> `serializeParams` (one `JSON.stringify` + `attach://` walk for nested files) ->
`encodeForm` -> `Transport` (injectable `fetch`, 429/transient retry with jittered backoff,
`{ok,result}` envelope unwrap). Structured fields (`reply_markup`, entities, `reply_parameters`,
media, ...) are **plain typed objects/arrays** that `serializeParams` stringifies once. Builders
(`InlineKeyboardBuilder`, `EntityBuilder`, `MediaGroupBuilder`, plus the sticker / profile-photo /
story builders) are **optional sugar** whose `.build()` returns the same plain shape - a literal
object works identically. `EntityType` is the typo-proof constant for entity kinds.
Every request goes out as form encoding: `x-www-form-urlencoded`, or `multipart/form-data` iff an
`InputFile` is present (the only thing that flips the body type). `encodeForm` has exactly three
branches: attach an `InputFile` as a part / spread a form-part composite (its JSON string + nested
parts) / set a string. `InputFile` wraps web-standard data only (`Blob | Uint8Array |
ReadableStream<Uint8Array>`); `fromPath()` (`/node`) is the only fs-backed constructor. File-bearing
params are `InputFile | string` - a bare string is always a `file_id`/URL, never a path. Nested
files (`sendMediaGroup`, sticker sets, ...) are hoisted to `attach://<name>` parts by
`serializeParams`.
### Dispatch
koa-style middleware over a per-update `Context`. `bot.use(mw)` registers middleware; `bot.on(kind)`,
`bot.command(name)`, `bot.hears(trigger)` are filter middleware in the same chain - registration
order wins, and each can wrap everything downstream via `await next()` (sessions, auth, rate-limit,
error boundaries). `Context` bundles the raw `update`, the typed `api`, a mutable `state` bag, and
chat-inferring shortcuts (`ctx.reply`, `ctx.answerCallbackQuery`). Update sources are async
generators - `longPoll(api, opts, signal)` replaces the old polling class. Two entry points share
**one** dispatch path: `bot.startPolling()` pumps `longPoll`; `bot.handleUpdate(update)` handles a
single update and is what `webhookCallback` calls.
### Webhooks
`webhookCallback(bot)` is a pure `(Request) => Promise<Response>` (Cloudflare Workers, Deno Deploy,
Vercel Edge, Bun.serve, Next.js App Router). Thin adapters mount it on an existing server:
`registerExpressWebhook`, `nodeFrameworkWebhook` (an `(req, res)` handler), `nextAppWebhook`. The
Node `createWebhookServer` (`/node`) adapts a `node:http` request and delegates to the same
callback - no duplicate request-handling logic.
### Transport & errors
`Transport` is the only module touching `fetch`; it is injectable (`opts.fetch`) so tests pass a
fake instead of monkeypatching globals. It merges the per-request timeout with the caller's signal
via `combineSignals` over `AbortSignal.timeout` (deliberately not `AbortSignal.any`, which needs
Node 18.17/20.3), unwraps the envelope, and retries: `429`s honor `retry_after` up to
`maxRetryAfterMs` (default 60s; a longer flood-wait surfaces immediately as a `TelegramApiError`),
transient failures (`NetworkError`/`TimeoutError`/5xx, classified by `isTransientError`) retry up to
`maxRetries` (default 2) with exponential jittered backoff. Opt-in proactive rate limiting
(`opts.rateLimit: { global?, perChat? }`) via a token-bucket keyed on `chat_id` - off by default,
zero overhead when unset.
Errors (`src/core/errors.ts`): a `TelegramBotError` base preserving `cause`, plus `NetworkError`,
`TimeoutError`, `ParseError`, `TelegramApiError` (structured `errorCode`/`description`/`parameters`
+ a `retryAfter` getter). Callers branch on values (`err.errorCode === 429`, `err.retryAfter`),
never on message text.
## Critical invariants
- **`src/core/api.ts` and `src/types/schemas.ts` are GENERATED** by `scripts/api-parser.ts` from
the live Bot API docs. Do not hand-edit them; change the generator and run `npm run generate:types`.
Adding a Bot API method = regenerate, never a hand-written method body. (See the `update-bot-api`
skill for the full flow, including the hand-added `Bot`/`Context` sugar.)
- **`src/core` must stay Node-free.** No `node:*` imports, no bare-builtin imports, no Node globals
(`Buffer`, `process`, `setImmediate`, ...). Enforced by both `lint:core` (static) and `check:edge`
(bundler). Anything needing Node goes in `src/node`.
- **Keep generated runtime lists in lockstep with their source types** (e.g. `UPDATE_TYPES`,
`MESSAGE_TYPES`) via the both-direction compile-time asserts already in place; `satisfies` alone
misses absent keys.
- **ASCII only** in code, comments, and commit messages: use `-`, `->`, `...` - never em dashes or
smart quotes (a commit hook rejects them; emoji are fine).Signals
- Stars
- 9,214
- Forks
- 1,645
- Open issues
- 0
- Last commit (this folder)
- Sep 6, 2026 (1 month ago)
- Last release
- Aug 24, 2026 (2 months ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/yagop/node-telegram-bot-api
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
Done for you
Rather not set it up yourself?
Chalk Labs scopes, builds and runs agents for your team: the task audit, the eval harness, the integrations and the weekly ops report. Fixed scope, priced before we start.
See how we build AI agents →Read the case studies
Chalk Labs is a two-founder AI and growth studio. We build AI agents, CRMs and MVPs for Web3 and AI companies, and have shipped 50+ product and token launches.
- launches shipped
- 50+
- launches shipped
- media placements
- 500+
- media placements
- founders on every account
- 2
- founders on every account
Press we've placed: Forbes · CoinDesk · Decrypt · Cointelegraph · The Block
Rahil Jain, Co-founder, Chalk Labs. Ex-Goldman Sachs engineer. You talk to the person who builds it.
Get CLAUDE.md · yagop/node-telegram-bot-api running in your business
Tell us what you want to automate. A founder replies within one working day with a plan, not a pitch deck.
Cite this page
CLAUDE.md · yagop/node-telegram-bot-api. Agent Mart. https://agentmart.dev/templates/yagop-node-telegram-bot-api-claude-md (last updated 2026-10-24)