feat: rewrite library in strict TypeScript with generated API types#1302
feat: rewrite library in strict TypeScript with generated API types#1302yagop wants to merge 49 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.
|
GitHub is deprecating Node.js 20 on Actions runners. The following timeline applies:
This triggered a warning: Changes made in the last commit:
More info: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/ |
…conditionally deleteMessageReaction/deleteAllMessageReactions were soft-skipping because they never identified whose reaction to remove. The bot's own reaction is a user reaction, so pass the bot's user_id (resolved once via getMe in the before hook). Drop the try/softSkip so these hard-fail on regression. getCustomEmojiStickers no longer skips when TEST_CUSTOM_EMOJI_ID is unset; the env var now falls back to a default id, matching the TEST_STICKER_SET_NAME pattern. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reorganize test/integration/telegram.test.ts into one describe() block per Bot API method, and add live-API coverage for the high-value untested methods: chat management (title/description/permissions/pinning), invite links, message editing parameters, forwarding/copying parameters, member moderation (tolerating the expected ETELEGRAM refusals so no real member is harmed), and misc queries. Mutations capture-and-restore chat state; capability-gated and irreversible methods (setChatPhoto, subscription links, answerCallbackQuery) soft-skip. Also add a glob-based integration runner so the npm script picks up every test/integration/*.test.ts, and a type-checker-based coverage audit (scripts/coverage-audit.mjs → docs/integration-coverage.md) that reports which methods and option parameters are exercised: now 74/175 methods and 66/419 option params. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the softSkip and isTelegramError helpers along with every try/catch and .catch in the suite. Tolerant tests that the live chat supports become straight-line happy-path assertions; methods Telegram structurally refuses (owner moderation, Stars subscription links, invalid callback ids) become assert.rejects(..., TelegramError). setChatPhoto/deleteChatPhoto now set a photo and clean it up so both make real assertions and leave the chat as found. Full live run: 91 pass, 0 skipped, 0 fail. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cbdcbfa to
36b152b
Compare
… audit Add integration tests driving previously-uncovered Bot API methods: - Sticker-set lifecycle: createNewStickerSet -> exercise (add/replace/ position/emoji/keywords/title/thumbnail/upload, plus mask- and custom-emoji-typed sets) -> deleteStickerSet, each with a real assertion. - Forum-topic methods via unconditional ETELEGRAM error-path tests (and optional env-gated happy paths), chat-join-request error paths, and the remaining send/edit option-param coverage. Regenerate docs/integration-coverage.md (methods 74->104/175) and annotate every remaining gap with a testability classification (DESTRUCTIVE / SPECIAL-SETUP / ENV-LIMITED / TESTABLE). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…verage audit Add real, asserting integration tests for the gaps in docs/integration-coverage.md that are exercisable with the standard TEST_GROUP_ID / TEST_USER_ID fixtures: getAvailableGifts, getMyStarBalance, getStarTransactions, setChatMenuButton and setMyDefaultAdministratorRights (with save/restore), plus previously untested option params on sendMessage, sendLocation, sendVenue, sendDice, sendContact, sendPoll, getUpdates, promoteChatMember and answerCallbackQuery. Gaps tagged DESTRUCTIVE / SPECIAL-SETUP / ENV-LIMITED are intentionally left uncovered — they need context a bare token + group cannot manufacture (business connections, inline/callback queries, forum or channel chats, the stars/gifts economy, payment providers). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the hand-written Zod schemas (src/types/schemas.ts) and option types (src/types/options.ts) with a single docs-faithful, generated src/types/schemas.ts. - scripts/api-parser.ts scrapes https://core.telegram.org/bots/api with Bun's HTMLRewriter and emits plain TypeScript `type` aliases: every documented object, "one of" unions, and per method a `<Method>Params` (full request) and `<Method>Result` (reply type parsed from prose). Strict: unmappable types / unresolved replies are hard errors, never `unknown`; no index signatures. Run via `npm run generate:types`. - telegram.ts consumes the generated types: each method's options arg is `Omit<<Method>Params, <positional args>>` inline, and the 15 former `Promise<unknown>` returns now use real reply types. - Tests drop runtime `.parse` validation in favour of compile-time typing; this surfaced two real test bugs (allow_revoting -> allows_revoting, correct_option_id -> correct_option_ids). - Remove the zod dependency. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace hand-rolled inline option types and inline return types on every TelegramBot API method with the generated docs-faithful types: - Options/positional args now derive from `<Method>Params` (bare or `Omit<…Params, …positional>`); `Record<string, unknown>`/`unknown` positional args (result, button, checklist, content, sticker, gift settings, admin rights) now use their real generated member types. - Return types now use `Promise<<Method>Result>` instead of inline `Promise<boolean>` / `Promise<Message>` etc. - Normalize the setWebhook endpoint string (was "setWebHook") to match its siblings and map to SetWebhookResult. Multipart upload helpers (sendMediaGroup, sendPaidMedia, editMessageMedia, createNewStickerSet, addStickerToSet, setStickerSetThumbnail) keep their curated shapes — the generated InputMedia/InputSticker (`media: string`) can't carry a FileInput. Local helpers (getFileLink, downloadFile, getFileStream, polling/webhook lifecycle) are unchanged. The integration test's ad-hoc `Record<string, boolean>` AdminRights alias now uses the generated ChatAdministratorRights. typecheck clean; node + bun unit suites 62/62; build OK. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ams` Add `satisfies <Method>Params` to the inline object literal passed to this._form(...) for 99 methods, so the constructed request is checked against the generated request type at compile time. Expand every such literal with more than one member to multiline for readability. satisfies is omitted where it cannot hold: - payloads that stringify a structured field (the field becomes a string), - 7 methods whose signature accepts the broader ChatId (number | string) while the docs type chat_id/from_chat_id as number (sendMessageDraft, approve/declineSuggestedPost, repostStory, readBusinessMessage, …). Pulls the remaining positional-only methods' `*Params` into the imports. typecheck clean; node + bun unit suites 62/62; build OK. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…arams
Move the remaining manual stringify() of structured fields into a new
_fixJsonFields normalizer in _request (button, menu_button, checklist,
custom_emoji_ids, emoji_list, keywords, mask_position, results, sticker),
so the method bodies pass typed values through and can assert their
payload with `satisfies <Method>Params`:
savePreparedKeyboardButton, setChatMenuButton, editMessageChecklist,
getCustomEmojiStickers, setStickerEmojiList, setStickerKeywords,
setStickerMaskPosition, answerInlineQuery, deleteBusinessMessages.
- replaceStickerInSet: `form` is now Omit<ReplaceStickerInSetParams, …>
with the docs-required `sticker` (the param is now required).
- approveSuggestedPost, declineSuggestedPost, readBusinessMessage,
repostStory: narrow chat_id/from_chat_id from ChatId to number to
match the API, enabling satisfies.
Also type the sole argument of the no-positional methods (getWebHookInfo,
getMe, logOut, close, getForumTopicIconStickers, removeMyProfilePhoto,
getMyStarBalance, getAvailableGifts) as their generated *Params instead
of `{}`.
typecheck clean; node + bun unit suites 62/62; build OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On 429 the client still retries (up to maxRetriesOn429) but now sleeps min(retry_after + 1, maxRetryAfterSeconds) seconds, so a long flood-wait (e.g. retry_after 1847) no longer blocks the process for ~31 minutes. New request.maxRetryAfterSeconds option (default 60; Infinity restores the old always-wait behavior). Refactor: request() is the retry loop, _attempt() performs one HTTP round-trip (timeout + abort + parse). Introduce ERROR_CODE_TOO_MANY_REQUESTS and the default/buffer constants. Adds test/unit/http.test.ts covering retry, the sleep cap, Infinity, and maxRetriesOn429: 0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- sendPoll: allow_adding_options requires a non-anonymous poll, so set is_anonymous:false (Telegram returned ANONYMOUS_OPEN_INVALID). - getChatMenuButton: chat_id targets a private chat only, so use USER_ID instead of the supergroup (was rejected with "invalid chat_id specified"). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ci.yml unit-test matrix: add "26". - telegram.ts: remove 40 type imports left unused after the args/return rewiring (data types now reached via *Params/*Result). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The only Bot API method documented in schemas.ts that lacked an implementation. Adds the method (errors serialized via _fixJsonFields, asserted with `satisfies SetPassportDataErrorsParams`); all 176 documented methods are now implemented. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove maxRetryAfterSeconds — on 429 the client again sleeps the full retry_after + 1 (up to maxRetriesOn429). Keeps the request()/_attempt() split. Test covers retry, give-up-after-maxRetries, and no-retry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Targets a dedicated test bot; saves the current name, sets a new one, asserts getMyName reflects it, then restores. (setMyProfilePhoto is left uncovered: the endpoint hangs server-side for a standard bot token — requires managed-bot/business capability.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reflects setMyName/setMyDefaultAdministratorRights coverage and the new setPassportDataErrors method (110/176 methods). Fix the stale options.ts source-of-truth reference (now schemas.ts) in both the doc and the generator. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…on guide
Review the rewrite against v0.67.0 and clean up the docs/examples to match.
- feat(telegram): typed `on()` event overloads via a new `TelegramBotEvents`
map (exported alongside `EventMetadata`); handlers now infer their payload
(e.g. `bot.on('callback_query', q => …)`) instead of `unknown`
- docs: add doc/migration.md (0.6x → 1.0 breaking changes: ESM-only, bundled
types, removed option aliases, error `.response` shape, NTBA_FIX_350, …)
- docs: fix CHANGELOG (drop inaccurate Zod claims; the generated types carry
no runtime validation) and correct the removed-items list
- docs: README now shows ESM/TS usage; drop the misleading `@types/...` note;
fix usage.md (NTBA_FIX_350 removed, error `.response` shape)
- examples: rewrite in TypeScript (polling, webhook/{express,https,heroku},
game) with idiomatic `import TelegramBot from 'node-telegram-bot-api'`; add
examples/README.md, tsconfig.json and an express type shim; remove the
defunct Zeit-Now and OpenShift-2 webhook examples
- docs(setMyProfilePhoto): warn that a static photo must be JPEG
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- add a happy-path test uploading a static .jpeg, with removeMyProfilePhoto cleanup (no API to read back the bot's current photo, so not a round-trip) - add a fixture test/data/chat_photo.jpeg - add a negative test: a static .png is rejected — Telegram's photos.uploadProfilePhoto backend only accepts JPEG and nothing in the stack converts it, so the call returns 504 (ETELEGRAM) or stalls; a short-timeout bot bounds the wait and we assert it never resolves to true - move the coverage audit doc/index: docs/ -> doc/ (consolidate the two dirs); point scripts/coverage-audit.mjs at the new path and regenerate - PR template: document the TEST_CUSTOM_EMOJI_ID default Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add integration tests for the no-fixture gaps surfaced in doc/integration-coverage.md (saved as doc/missing-tests-plan.md): - new happy-path blocks: createInvoiceLink (XTR/Stars link), unbanChatMember (only_if_banned no-op), getChatGifts (read-only) - param top-ups on existing blocks: setMyName language_code, sendPoll country_codes + hide_results_until_closes, copyMessage caption_entities/reply_parameters/video_start_timestamp, stopMessageLiveLocation reply_markup Gaps needing a Premium/paid/special fixture (business connection, Stars subscription, BotFather game, managed bot, channel suggested post, forum supergroup) are left to the plan doc rather than added as reject-only tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
TEST_GROUP_ID is now a real forum supergroup with the bot holding can_manage_topics, so the forum methods no longer reject. Convert the 11 reject-path forum blocks to happy-path tests and add a new deleteForumTopic block. Per-topic blocks create a throwaway topic in before() and delete it in after() (tolerating ETELEGRAM). General-topic blocks restore General to its default via a shared restoreGeneral helper: hiding General auto-closes it and unhiding does not reopen it, so the restore order is unhide -> reopen -> rename "General". Verified live against the forum group: 13 forum tests pass, typecheck clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| "file-type": "^3.9.0", | ||
| "mime": "^1.6.0", | ||
| "pump": "^2.0.0" | ||
| "node": ">=18" |
There was a problem hiding this comment.
https://nodejs.org/en/about/previous-releases
Support "Maintenance" LTS onwards i.e. >= 22 (which will be supported for at-least another year) before switching over to >= 24 in 2027.
Add integration coverage for previously-untested option params, raising option-param coverage from 172/419 (41%) to 209/419 (50%): - allow_paid_broadcast: sendMessage, sendDice, sendLocation, sendVenue, sendContact, sendMediaGroup, sendPoll - forwardMessage: video_start_timestamp - promoteChatMember: all can_* flags + is_anonymous (via the owner-rejection assertion, exercising every flag on the wire) - getChatGifts: all exclude_*/sort_by_price/offset/limit - createInvoiceLink: photo_url/size/width/height, subscription_period - setChatMenuButton: chat_id (targets a private chat, reads back, resets) - copyMessages: add a real assertion to the previously empty result loop message_thread_id was intentionally not added: TEST_GROUP_ID is not a forum-enabled chat (createForumTopic returns "the chat is not a forum"), so those cases are not exercisable. createInvoiceLink need_*/send_*_to_provider are inapplicable to XTR (Stars) invoices (STARS_INVOICE_INVALID). Regenerate doc/integration-coverage.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add .claude/skills/run-tests/SKILL.md documenting how to run the unit and integration suites: bun (preferred, auto-loads .env) vs node, the required NODE_TELEGRAM_TOKEN/TEST_GROUP_ID/TEST_USER_ID env vars, scoping a run to one method after a method change, methods that need special chat conditions (e.g. a forum-enabled TEST_GROUP_ID), and when a full-suite run is required (core pipeline changes). Move the test how-to detail out of CLAUDE.md into the skill, leaving a pointer. Un-ignore .claude/skills/ in .gitignore so the skill is shared (the rest of .claude stays ignored). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Full rewrite of
node-telegram-bot-apifrom Babel/CommonJS to strict TypeScript. Hand-written source insrc/compiles todist/(the published artifact). The public API surface is preserved; the implementation is modernised end-to-end.Warning
Breaking changes. This is a major release (
1.0.0):"type": "module"with animport-onlyexportsmap.require('node-telegram-bot-api')no longer works — consumers must use ESM (import).engines.node >= 18), to rely on built-infetch/FormData/Blob.Project setup
"type": "module",NodeNextresolution — every relative import carries a.jsextension.tsconfig.json(strict) for typecheck oversrc/+test/;tsconfig.build.jsonemitsdist/fromsrc/only.engines.nodebumped to>= 18.npm run typecheckunderstrictis the static-analysis gate.Source (
src/)TelegramBotported to TypeScript with one method per Bot API method.AbortController+ native timers (nobl/pump); polling and webhook remain mutually exclusive.node:http/httpswith optionalsecretTokenauth, a health endpoint, and a 50 MB payload cap.src/http.ts) unified on built-infetch+FormData+Blob; it builds the URL, picksx-www-form-urlencodedvsmultipart/form-data, unwraps Telegram's{ ok, result, description, error_code }envelope, and retries on429honoringretry_after.FatalError/ParseError/TelegramError) preserves the prototype chain after re-throw.prepareFile()/prepareFiles()handle filesystem paths, Buffers and streams uniformly, falling back to treating a plain string as a Telegramfile_idor public URL.src/internal/holds tiny zero-dep replacements:debug, magic-bytefile-typesniffing, andmimelookup.Types (
src/types/)src/types/schemas.tsis generated byscripts/api-parser.ts(npm run generate:types), which scrapes https://core.telegram.org/bots/api and emits plain TypeScripttypealiases — one per documented object, abstract "one of" objects as unions, and per method a<Method>Params+<Method>Result.unknown) and types carry no index signatures.telegram.tsmethod types its trailing options argument inline asOmit<<Method>Params, ...positional args>, so the positional-vs-options split lives next to the method.Dependencies
@cypress/request,@cypress/request-promise,bl,pump,eventemitter3,file-type,mime,debug,array.prototype.findindex, thebabel-*toolchain,mocha,istanbul,is,is-ci,node-static.typescript,tsx,@types/node. No runtime dependencies.Tests
node:test+node:assert/strict(runs identically on Node and Bun).globalThis.fetchto assert the wire format (URL, body params, structured-field serialization, multipart uploads) — no network, no token:errors,utils,schemas,http,format-send-data, andtelegram(dispatch +onText+ reply listeners).test/integration/telegram.test.ts) hits the realapi.telegram.org, throttles ~1.1s between calls, and deliberately skips irreversible mutations. RequiresNODE_TELEGRAM_TOKEN,TEST_GROUP_ID,TEST_USER_ID.Checklist
npm run typecheckis cleannpm run test:node:unitpassesReferences
feat/typescript.