fix(telegram): HTML mode, message splitting, and access control#23
Open
arne1101 wants to merge 1 commit intoghostwright:mainfrom
Open
fix(telegram): HTML mode, message splitting, and access control#23arne1101 wants to merge 1 commit intoghostwright:mainfrom
arne1101 wants to merge 1 commit intoghostwright:mainfrom
Conversation
…ss control Four bugs/gaps in the Telegram channel fixed: 1. HTML send mode with plain-text fallback: sendMessage() was using MarkdownV2 with a custom escaper that failed on parentheses and other edge cases. Switched to HTML mode with a markdownToTelegramHtml() converter. On any send failure (malformed HTML), retries the chunk as plain text so the user always gets a response. 2. Message splitting: Telegram rejects messages over 4096 characters with no error surfaced to the user. Added splitMessage() which chunks at newlines near the 4000-char limit (hard split as fallback). 3. try/catch in command handlers: /start, /status, /help called ctx.reply() without error handling, which caused "Unhandled promise rejection" in Telegraf on any transient network error. 4. allowed_user_ids access control: new optional config field that whitelists Telegram user IDs. When set, any user not on the list gets "Unauthorized." and the message handler is never invoked. Applies to text messages, callback queries, and all slash commands. Config schema updated in schemas.ts and wired through in index.ts. Utility functions (splitMessage, stripHtml, markdownToTelegramHtml) extracted to telegram-utils.ts to keep telegram.ts under the 300-line guideline. All 848 tests pass. bun run lint and bun run typecheck clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #14
Summary
Four reliability fixes for the Telegram channel. These were found and validated in a real deployment.
1. Non-blocking connect() - fixes the startup hang (closes #14)
connect()awaitedbot.launch()directly. Telegraf'slaunch()runs an infinite polling loop that only resolves whenbot.stop()is called, soconnect()never returned. This blockedrouter.connectAll(), which meant the scheduler,/triggerendpoint, and the "is ready" log never ran.Fix: Use Telegraf's
onLaunchcallback, which fires aftergetMe()succeeds but before the polling loop starts. This letsconnect()resolve as soon as the bot is authenticated with Telegram, without waiting for polling to end.This is more robust than a fire-and-forget approach: fire-and-forget would set
connectionState = "connected"before Telegram validates the token, hiding auth errors. With the callback, a bad token still surfaces as a rejected promise.2. Switch to HTML send mode with plain-text fallback
sendMessage()used MarkdownV2 with a customescapeMarkdownV2()function. The escaper fails on edge cases -- parentheses inside URLs, certain punctuation patterns -- causing a400: can't parse entitieserror that swallows the response silently.Fix: Switched to HTML mode with a
markdownToTelegramHtml()converter. If the HTML send still fails, the chunk is retried as plain text so the user always gets a response.3. Message splitting for the 4096-char limit
Telegram rejects messages over 4096 characters with a
400error. The original code sent the full response in one call with no error handling, so long responses disappeared without a trace.Fix: Added
splitMessage()which chunks at newlines near the 4000-char limit, with a hard split as fallback. Long responses are now delivered as sequential chunks.4. try/catch in command handlers
/start,/status,/helpcalledctx.reply()without wrapping it, causing unhandled promise rejections in Telegraf on any transient send error.Fix: Each
ctx.reply()call is now wrapped in try/catch. Failures are silently ignored since the command responses are non-critical.5.
allowed_user_idsaccess controlNo way to restrict which Telegram users can interact with the bot. Anyone who discovers the bot can send it messages.
Fix: New optional
allowed_user_ids: number[]field inchannels.yaml. When set, users not on the list receive "Unauthorized." and the message handler is never invoked. Applies to text messages, callback queries, and all slash commands. Empty list or absent field = allow all (preserves existing behavior).Utility functions (
splitMessage,stripHtml,markdownToTelegramHtml) are extracted to a newtelegram-utils.tsto keeptelegram.tswithin the 300-line guideline.Test plan
bun test- 848 tests, all pass (78 existing tests unchanged + 38 new tests for the Telegram channel)bun run lint- cleanbun run typecheck- cleanisAllowed()logic (all allowed, whitelist pass, whitelist block), command handler authorization,splitMessage()edge cases,stripHtml(),markdownToTelegramHtml()conversions