From bc87681085799aca03f2b0d1eb671a7899bd8a66 Mon Sep 17 00:00:00 2001 From: Paul Kloppers Date: Wed, 13 May 2026 22:59:09 +0200 Subject: [PATCH] fix(redstone): use plain reply in non-private chats (TEXTDRAFT_PEER_INVALID) sendMessageDraft (Bot API 9.5) only works in private chats. Group, supergroup and channel peers reject it with TEXTDRAFT_PEER_INVALID, so Redstone was silently failing the entire reply path in groups after the streaming feature landed in cab5337. Keep replyWithStream for private chats (the typing animation is the whole point); fall back to plain ctx.reply() everywhere else. Co-Authored-By: Claude Opus 4.7 --- bot/bot.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/bot/bot.ts b/bot/bot.ts index fccbca7..b43abeb 100644 --- a/bot/bot.ts +++ b/bot/bot.ts @@ -1460,10 +1460,19 @@ bot.on("message:text", async (ctx) => { // Stream the reply word-by-word so the message animates into the chat like // an LLM typing. @grammyjs/stream uses sendMessageDraft (Bot API 9.5) to push // each delta as a native, animated draft and finalizes with sendMessage. - const messages = await ctx.replyWithStream(streamWords(out), {}, { - reply_parameters: { message_id: ctx.message.message_id }, - }); - const finalMessage = messages[messages.length - 1]; + // sendMessageDraft is only supported in private chats — group / supergroup + // / channel peers return TEXTDRAFT_PEER_INVALID. Use plain reply there. + let finalMessage: { message_id: number } | undefined; + if (chatType === "private") { + const messages = await ctx.replyWithStream(streamWords(out), {}, { + reply_parameters: { message_id: ctx.message.message_id }, + }); + finalMessage = messages[messages.length - 1]; + } else { + finalMessage = await ctx.reply(out, { + reply_parameters: { message_id: ctx.message.message_id }, + }); + } if (finalMessage) { Q.insMsg.run(chatId, finalMessage.message_id, null, "out", "text", out, ctx.message.message_id); }