fix(telegram): fall back to text when voice messages forbidden (#1725)

* fix(telegram): fall back to text when voice messages forbidden

When TTS auto mode is enabled, slash commands like /status would fail
silently because sendVoice was rejected with VOICE_MESSAGES_FORBIDDEN.
The entire reply would fail without any text being sent.

This adds error handling to catch VOICE_MESSAGES_FORBIDDEN specifically
and fall back to sending the text content as a regular message instead
of failing completely.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: handle telegram voice fallback errors (#1725) (thanks @foeken)

---------

Co-authored-by: Echo <andre.foeken@Donut.local>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Andre Foeken
2026-01-25 14:18:41 +01:00
committed by GitHub
parent 8257ec6a1f
commit 9bd5def32c
3 changed files with 146 additions and 3 deletions

View File

@@ -155,9 +155,44 @@ export async function deliverReplies(params: {
// Voice message - displays as round playable bubble (opt-in via [[audio_as_voice]])
// Switch typing indicator to record_voice before sending.
await params.onVoiceRecording?.();
await bot.api.sendVoice(chatId, file, {
...mediaParams,
});
try {
await bot.api.sendVoice(chatId, file, {
...mediaParams,
});
} catch (voiceErr) {
// Fall back to text if voice messages are forbidden in this chat.
// This happens when the recipient has Telegram Premium privacy settings
// that block voice messages (Settings > Privacy > Voice Messages).
const errMsg = formatErrorMessage(voiceErr);
if (errMsg.includes("VOICE_MESSAGES_FORBIDDEN")) {
if (!reply.text?.trim()) {
throw voiceErr;
}
logVerbose(
"telegram sendVoice forbidden (recipient has voice messages blocked in privacy settings); falling back to text",
);
// Send the text content instead of the voice message.
if (reply.text) {
const chunks = chunkText(reply.text);
for (const chunk of chunks) {
await sendTelegramText(bot, chatId, chunk.html, runtime, {
replyToMessageId:
replyToId && (replyToMode === "all" || !hasReplied) ? replyToId : undefined,
messageThreadId,
textMode: "html",
plainText: chunk.text,
linkPreview,
});
if (replyToId && !hasReplied) {
hasReplied = true;
}
}
}
// Skip this media item; continue with next.
continue;
}
throw voiceErr;
}
} else {
// Audio file - displays with metadata (title, duration) - DEFAULT
await bot.api.sendAudio(chatId, file, {