Draft: feat: rewrite library in TypeScript with Zod runtime typing#1302
Draft: feat: rewrite library in TypeScript with Zod runtime typing#1302yagop wants to merge 22 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR is a draft full rewrite of node-telegram-bot-api targeting Node 18+, migrating the implementation to strict TypeScript, adding Zod-based runtime schemas for Bot API payloads, and modernizing HTTP/polling/webhook internals around native fetch, FormData, and AbortController, while aiming to keep the public API compatible.
Changes:
- Replaces legacy CommonJS/Babel implementation with TypeScript ESM modules and a new typed core (
TelegramBot, polling, webhook, HTTP transport). - Adds comprehensive Zod schemas + exported inferred types for Telegram payloads.
- Migrates tests from Mocha to Node’s built-in
node:testrunner, adding unit + integration suites (with a local mock server fallback).
Reviewed changes
Copilot reviewed 35 out of 38 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| tsconfig.json | Adds strict TS config for development/typechecking. |
| tsconfig.build.json | Defines build emit configuration for dist/. |
| package.json | Switches to ESM package, adds TS build/test scripts, updates deps/engines/exports. |
| index.js | Removes legacy Node-version-based entrypoint logic. |
| src/index.ts | Adds new public TS entrypoint with exports + default export. |
| src/telegram.ts | New TypeScript TelegramBot implementation with typed methods and event dispatch. |
| src/http.ts | New fetch-based HTTP client, body building, and error mapping. |
| src/polling.ts | New polling implementation using AbortController and timers. |
| src/webhook.ts | New webhook server using node:http/https, health endpoint, optional secret token. |
| src/utils.ts | Adds file preparation helpers, stable stringify, stream-to-buffer helpers, deprecate helper. |
| src/utils.js | Removes legacy deprecate helper JS file. |
| src/errors.ts | New typed error hierarchy with code and preserved prototype chain. |
| src/errors.js | Removes legacy JS error implementations. |
| src/internal/debug.ts | Adds a tiny debug-compatible shim. |
| src/internal/mime.ts | Adds minimal built-in MIME lookup (replacing external dep). |
| src/internal/file-type.ts | Adds minimal magic-byte file-type detection (replacing external dep). |
| src/types/schemas.ts | Adds Zod schemas + inferred types for Telegram payloads and message type constants. |
| src/types/options.ts | Adds TS option/request payload types for common API methods. |
| src/types/index.ts | Re-exports schemas and option types from a single types entrypoint. |
| src/telegramWebHook.js | Removes legacy JS webhook implementation. |
| src/telegramPolling.js | Removes legacy JS polling implementation. |
| test/unit/errors.test.ts | Adds unit tests for the new error hierarchy. |
| test/unit/utils.test.ts | Adds unit tests for new utils (stringify/prepareFile/prepareFiles/streamToBuffer). |
| test/unit/format-send-data.test.ts | Adds unit tests for request body formatting via HttpClient + file pipeline. |
| test/unit/schemas.test.ts | Adds unit tests validating key Zod schemas and passthrough behavior. |
| test/unit/telegram.test.ts | Adds unit tests for TelegramBot request formatting and update dispatch behavior. |
| test/integration/mock-server.ts | Adds a local Telegram-compatible mock API server for integration tests. |
| test/integration/telegram.test.ts | Adds integration tests that probe live API availability and otherwise use the mock server. |
| test/utils.js | Removes legacy Mocha test utilities (mock/static servers, request helpers). |
| test/test.format-send-data.js | Removes legacy Mocha suite for _formatSendData. |
| test/telegram.js | Removes legacy large Mocha integration suite. |
| test/mocha.opts | Removes Mocha configuration file. |
| .travis.yml | Removes Travis CI configuration. |
| .eslintrc | Removes legacy ESLint configuration (Airbnb + Babel parser). |
| .eslintignore | Removes legacy ESLint ignore file. |
| .babelrc | Removes Babel configuration. |
| .gitignore | Updates ignore list (adds dist, .claude). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (!content.type) throw new FatalError("content.type is required"); | ||
| const qs: Record<string, unknown> = { | ||
| ...options, | ||
| business_connection_id: businessConnectionId, | ||
| active_period: activePeriod, | ||
| }; | ||
| const { formData, fileIds } = await prepareFiles(content.type, [content as never], {}, this.options.filepath); | ||
| const inputContent: Record<string, unknown> = { ...content }; | ||
| inputContent[content.type] = fileIds[0] ?? `attach://${content.type}_0`; | ||
| qs.content = stringify(inputContent); | ||
| return this._request("postStory", { qs, formData }); |
| if (!content.type) throw new FatalError("content.type is required"); | ||
| const qs: Record<string, unknown> = { | ||
| ...options, | ||
| business_connection_id: businessConnectionId, | ||
| story_id: storyId, | ||
| }; | ||
| const { formData, fileIds } = await prepareFiles(content.type, [content as never], {}, this.options.filepath); | ||
| const inputContent: Record<string, unknown> = { ...content }; | ||
| inputContent[content.type] = fileIds[0] ?? `attach://${content.type}_0`; | ||
| qs.content = stringify(inputContent); | ||
| return this._request("editStory", { qs, formData }); | ||
| } |
| this.host = options.host ?? "0.0.0.0"; | ||
| this.port = options.port ?? 8443; | ||
| this.healthEndpoint = options.healthEndpoint ?? "/healthz"; | ||
| this._healthRegex = new RegExp(this.healthEndpoint); | ||
| this._secretToken = options.secretToken; |
| if (!url.includes(this.bot.token)) { | ||
| debug("WebHook request unauthorized"); | ||
| res.statusCode = 401; | ||
| res.end(); | ||
| return; | ||
| } |
| req.on("data", (chunk: Buffer) => { | ||
| total += chunk.length; | ||
| if (total > MAX_PAYLOAD_BYTES) { | ||
| reject(new FatalError("Webhook payload exceeds 50MB safety cap")); |
| const response = await fetch(`https://api.telegram.org/bot${TOKEN}/getMe`, { | ||
| signal: ctrl.signal, | ||
| }); | ||
| clearTimeout(timer); | ||
| return response.ok; |
Full rewrite of node-telegram-bot-api targeting Node 18+, replacing the
Babel/CommonJS source with strict TypeScript and Zod-validated payload
schemas. The public API stays the same; the implementation is modernised
end-to-end.
Project setup
- type: module + dual ESM/CJS exports via tsconfig + tsc emit
- tsconfig.json (strict) + tsconfig.build.json for dist output
- Bumped engines to node >=18 to take advantage of built-in fetch/FormData
Source (src/)
- TelegramBot ported to TypeScript with all 187 Bot API methods typed
- Polling rewritten with AbortController + native timers (no bl/pump)
- WebHook rewritten using node:http/https with optional secret-token auth
- HTTP transport unified on built-in fetch + FormData + Blob
- Errors hierarchy preserves prototype chain after re-throw
- prepareFile()/prepareFiles() handle paths, buffers and streams uniformly
Types (src/types/)
- Zod schemas for User, Chat, Message (recursive), Update, CallbackQuery,
InlineQuery, Reaction, Payments, Stickers, ForumTopic, ChatMember,
ChatBoost, BusinessConnection, etc., cross-referenced against the
go-telegram/bot models package
- Inferred TypeScript types exported alongside each schema
- .passthrough() on top-level objects keeps the library forward-compatible
with new Telegram fields
Dependencies
- Removed: @cypress/request, @cypress/request-promise, bl, pump,
eventemitter3, file-type, mime, debug, array.prototype.findindex,
babel-* packages, mocha, istanbul, is, is-ci, node-static
- Added: zod (runtime types) + tsx + typescript (dev only)
- Tiny zero-dep replacements live in src/internal/{debug,mime,file-type}.ts
Tests
- Migrated from Mocha+Babel to Node's built-in node:test runner
- 5 unit suites: errors, utils, schemas, format-send-data (via stubbed
fetch), TelegramBot dispatch + onText + reply listeners
- Integration suite auto-detects whether api.telegram.org is reachable;
falls back to a local Bot-API-shaped mock server when offline.
TEST_TELEGRAM_TOKEN env var is required (no hardcoded fallback)
- 62/62 tests passing, tsc --noEmit clean
CI (.github/workflows/ci.yml)
- typecheck job runs tsc --noEmit
- unit-node matrix runs npm test on Node 18, 20 and 22
- unit-bun runs the same suite under latest Bun (uses node:test compat)
- build job verifies dist/{index,telegram}.{js,d.ts} after tsc emit
- integration job is gated on workflow_dispatch input + repo secret
- concurrency cancels superseded in-flight runs
6803248 to
41cfeac
Compare
- Track package-lock.json (remove from .gitignore) so setup-node@v4 npm cache step no longer fails the CI job - Add @types/node dev dependency — was missing, causing tsc to exit 2 - Remove CJS require export — build only emits ESM .js via NodeNext - startPolling(): default restart to false (idempotent by default) - postStory/editStory: extract file from content[content.type] as media so prepareFiles() actually picks up the upload - webhook: replace RegExp health-check with exact pathname comparison to prevent regex metachar injection via healthEndpoint config - webhook: check pathname only (not full URL) for token auth to prevent bypass via query-string token - webhook: only wrap non-BaseError errors in FatalError to avoid EFATAL: EFATAL double-prefix messages - probeLive(): move clearTimeout into finally so the timer always clears Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The project had no appveyor.yml so AppVeyor fell back to its default which uses Node 8 — incompatible with every dependency (tsx, typescript, esbuild all require >=18). Explicit config installs Node 18/20/22 x64 and runs typecheck + unit tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Windows cmd.exe doesn't expand globs, and Node's --test only got native glob support in v22 — so the existing node --test --import tsx test/unit/*.test.ts worked on Linux (bash expansion) and on Node 22 Windows, but failed on Node 18/20 Windows with 'Could not find test/unit/*.test.ts'. Replace with a tiny wrapper that resolves the file list via fs.readdirSync and forwards to node --test. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Added: - deleteMessageReaction - deleteAllMessageReactions
|
Already review and fix types from method "getMe" to "sendPhoto" (list from oficial docs Telegram Bot API). I will continue checking manual the rest of the methods for missing types and have it all in place. |
…HttpClient - Drop the FORCE_MOCK / mock-server harness; integration suite now hits the real Bot API and exercises ~60 methods (sends, file variants, media group, forwarding/copying, editing, deletes, reactions, chat info, invite link round-trip, idempotent self-management, stickers, listeners, error mapping) - Honor Telegram's 429 retry_after inside HttpClient.request so callers no longer have to wrap calls in their own retry loop; gated by a new request.maxRetriesOn429 option (default 2, set to 0 to opt out) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…actions Both methods are exercised against a real bot-sent message, with a soft-skip on ETELEGRAM so the suite stays green when the chat lacks the required admin rights or the endpoint isn't available for the bot. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Strings are compiled with `new RegExp(pattern)` when the listener is added, so processUpdate() no longer needs to defensively re-wrap each entry on every text message it dispatches. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Rename scripts to test:{node,bun}:{unit,integration}
- New test:bun:integration with --timeout 120000 to accommodate the
in-library 429 retry path (Bun's default 5s per-test timeout was too tight)
- Add softSkip(t, reason) helper that no-ops on Bun, where node:test's
t.skip() throws NotImplementedError; keeps the same 'skipped' fidelity
on Node and lets Bun pass the test silently
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Scripts were split into test:node:{unit,integration} and
test:bun:{unit,integration}, so the previous npm test / npm run
test:integration invocations no longer exist. Update GitHub
Actions and AppVeyor to call the new names.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Reviewed up to the "sendLocation" method, added support for the sendLivePhoto method + the files to run the test. |
Replace the obsolete `npm run doc` checkbox with the current
test:node:{unit,integration} + typecheck checklist, and add a
short guide covering the four runner commands (Node + Bun ×
unit + integration), the required env vars
(NODE_TELEGRAM_TOKEN, TEST_GROUP_ID, TEST_USER_ID) and the
optional sticker/emoji overrides.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Continue the type-correctness pass against the official Telegram Bot API docs: - Replace generic Record<string, unknown> form/option arguments with explicit inline shapes for getMe / getFile / deleteWebHook / getWebHookInfo, the chat-member admin methods (ban/unban/restrict/ promote/setChatMemberTag, banChatSenderChat/unbanChatSenderChat), invite-link methods (export/create/edit/revoke), join-request approval/decline, getUserProfilePhotos/Audios, setMessageReaction, setUserEmojiStatus, sendMessageDraft, etc. - Refine sendLivePhoto to accept per-file FileMeta so callers can set distinct content-type/filename for the live photo and the still - Add SentGuestMessage and BotAccessSettings Zod schemas + inferred TypeScript types - Add getUserPersonalChatMessages helper - Extend test/unit/telegram.test.ts coverage for the new shapes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Continue the type-correctness pass against the official Telegram Bot API docs, covering the remaining methods left in src/telegram.ts: - Payments / Stars / Games: createInvoiceLink, answerShippingQuery, answerPreCheckoutQuery, getMyStarBalance, getStarTransactions, refundStarPayment, editUserStarSubscription, sendGame, setGameScore, getGameHighScores - Delete messages: deleteMessage, deleteMessages, deleteMessageReaction, deleteAllMessageReactions - Gifts / verification / business accounts / stories: sendGift, giftPremiumSubscription, verifyUser / verifyChat / remove*, readBusinessMessage, deleteBusinessMessages, setBusinessAccount*, getBusinessAccountGifts, getUserGifts, getChatGifts, convertGiftToStars / upgradeGift / transferGift, postStory, repostStory, editStory, deleteStory Also fixes the deleteAllMessageReactions integration test, which was passing an unsupported `message_id` parameter (the API removes reactions chat-wide by user/actor, not per-message). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…rams Serialize suggested_post_parameters, link_preview_options, and story areas in the request pipeline. Extend _fixEntitiesField to cover description_entities, question_entities, title_entities, and text_entities. Apply all fix helpers consistently to both form and query-string request bodies.
…emas sendPoll now accepts InputPollOption[] instead of string[], supporting per-option text formatting and media. Fill in missing fields on SendVenue/SendContact/SendLocation/SendPoll options (business_connection_id, suggested_post_parameters, correct_option_ids, etc). Add Zod schemas for all InputMedia discriminated unions used by sendMediaGroup, editMessageMedia, and sendPoll.
|
Reviewed up to the "sendPoll" , more changes in progress |
Full rewrite of node-telegram-bot-api targeting Node 18+, replacing the Babel/CommonJS source with strict TypeScript and Zod-validated payload schemas. The public API stays the same; the implementation is modernised end-to-end.
Project setup
Source (src/)
Types (src/types/)
Dependencies
Tests
npm run docDescription
References