feat: add Markdown formatting for outbound messages

Wrap outbound sendMessage calls with parse_mode: 'Markdown' so that
Claude's natural formatting (*bold*, _italic_, `code`, etc.) renders
correctly in Telegram instead of showing raw asterisks and underscores.

Falls back to plain text if Telegram rejects the Markdown formatting.
This commit is contained in:
James Schindler
2026-03-10 11:58:00 -04:00
parent 2eb5871454
commit 9a4fb61f6e

View File

@@ -1,4 +1,4 @@
import { Bot } from 'grammy'; import { Api, Bot } from 'grammy';
import { ASSISTANT_NAME, TRIGGER_PATTERN } from '../config.js'; import { ASSISTANT_NAME, TRIGGER_PATTERN } from '../config.js';
import { readEnvFile } from '../env.js'; import { readEnvFile } from '../env.js';
@@ -17,6 +17,29 @@ export interface TelegramChannelOpts {
registeredGroups: () => Record<string, RegisteredGroup>; registeredGroups: () => Record<string, RegisteredGroup>;
} }
/**
* Send a message with Telegram Markdown parse mode, falling back to plain text.
* Claude's output naturally matches Telegram's Markdown v1 format:
* *bold*, _italic_, `code`, ```code blocks```, [links](url)
*/
async function sendTelegramMessage(
api: { sendMessage: Api['sendMessage'] },
chatId: string | number,
text: string,
options: { message_thread_id?: number } = {},
): Promise<void> {
try {
await api.sendMessage(chatId, text, {
...options,
parse_mode: 'Markdown',
});
} catch (err) {
// Fallback: send as plain text if Markdown parsing fails
logger.debug({ err }, 'Markdown send failed, falling back to plain text');
await api.sendMessage(chatId, text, options);
}
}
export class TelegramChannel implements Channel { export class TelegramChannel implements Channel {
name = 'telegram'; name = 'telegram';
@@ -203,10 +226,11 @@ export class TelegramChannel implements Channel {
// Telegram has a 4096 character limit per message — split if needed // Telegram has a 4096 character limit per message — split if needed
const MAX_LENGTH = 4096; const MAX_LENGTH = 4096;
if (text.length <= MAX_LENGTH) { if (text.length <= MAX_LENGTH) {
await this.bot.api.sendMessage(numericId, text); await sendTelegramMessage(this.bot.api, numericId, text);
} else { } else {
for (let i = 0; i < text.length; i += MAX_LENGTH) { for (let i = 0; i < text.length; i += MAX_LENGTH) {
await this.bot.api.sendMessage( await sendTelegramMessage(
this.bot.api,
numericId, numericId,
text.slice(i, i + MAX_LENGTH), text.slice(i, i + MAX_LENGTH),
); );