mirror of https://github.com/openclaw/openclaw.git
fix(telegram): skip empty text replies instead of crashing with GrammyError 400 (#56620)
Filter whitespace-only text chunks at the bot delivery fan-in before they reach sendTelegramText(). Covers normal text replies, follow-up text, and voice fallback text paths. Media-only replies are unaffected. message_sent hook still fires with success: false for suppressed empty replies. Fixes #37278
This commit is contained in:
parent
eec290e68d
commit
4d6c8edd74
|
|
@ -92,6 +92,12 @@ function markDelivered(progress: DeliveryProgress): void {
|
|||
progress.deliveredCount += 1;
|
||||
}
|
||||
|
||||
function filterEmptyTelegramTextChunks<T extends { text: string }>(chunks: readonly T[]): T[] {
|
||||
// Telegram rejects whitespace-only text payloads; drop them before sendMessage so
|
||||
// hook-mutated or model-emitted empty replies become a no-op instead of a 400.
|
||||
return chunks.filter((chunk) => chunk.text.trim().length > 0);
|
||||
}
|
||||
|
||||
async function deliverTextReply(params: {
|
||||
bot: Bot;
|
||||
chatId: string;
|
||||
|
|
@ -108,8 +114,9 @@ async function deliverTextReply(params: {
|
|||
progress: DeliveryProgress;
|
||||
}): Promise<number | undefined> {
|
||||
let firstDeliveredMessageId: number | undefined;
|
||||
const chunks = filterEmptyTelegramTextChunks(params.chunkText(params.replyText));
|
||||
await sendChunkedTelegramReplyText({
|
||||
chunks: params.chunkText(params.replyText),
|
||||
chunks,
|
||||
progress: params.progress,
|
||||
replyToId: params.replyToId,
|
||||
replyToMode: params.replyToMode,
|
||||
|
|
@ -155,8 +162,9 @@ async function sendPendingFollowUpText(params: {
|
|||
replyToMode: ReplyToMode;
|
||||
progress: DeliveryProgress;
|
||||
}): Promise<void> {
|
||||
const chunks = filterEmptyTelegramTextChunks(params.chunkText(params.text));
|
||||
await sendChunkedTelegramReplyText({
|
||||
chunks: params.chunkText(params.text),
|
||||
chunks,
|
||||
progress: params.progress,
|
||||
replyToId: params.replyToId,
|
||||
replyToMode: params.replyToMode,
|
||||
|
|
@ -204,7 +212,7 @@ async function sendTelegramVoiceFallbackText(opts: {
|
|||
replyQuoteText?: string;
|
||||
}): Promise<number | undefined> {
|
||||
let firstDeliveredMessageId: number | undefined;
|
||||
const chunks = opts.chunkText(opts.text);
|
||||
const chunks = filterEmptyTelegramTextChunks(opts.chunkText(opts.text));
|
||||
let appliedReplyTo = false;
|
||||
for (let i = 0; i < chunks.length; i += 1) {
|
||||
const chunk = chunks[i];
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ describe("deliverReplies", () => {
|
|||
messageHookRunner.hasHooks.mockImplementation(
|
||||
(name: string) => name === "message_sending" || name === "message_sent",
|
||||
);
|
||||
messageHookRunner.runMessageSending.mockResolvedValue({ content: "" });
|
||||
messageHookRunner.runMessageSending.mockResolvedValue({ content: " " });
|
||||
|
||||
const runtime = createRuntime(false);
|
||||
const sendMessage = vi.fn();
|
||||
|
|
@ -184,7 +184,7 @@ describe("deliverReplies", () => {
|
|||
|
||||
expect(sendMessage).not.toHaveBeenCalled();
|
||||
expect(messageHookRunner.runMessageSent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ success: false, content: "" }),
|
||||
expect.objectContaining({ success: false, content: " " }),
|
||||
expect.objectContaining({ channelId: "telegram", conversationId: "123" }),
|
||||
);
|
||||
});
|
||||
|
|
@ -600,7 +600,7 @@ describe("deliverReplies", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("throws when formatted and plain fallback text are both empty", async () => {
|
||||
it("skips whitespace-only text replies without calling Telegram", async () => {
|
||||
const runtime = createRuntime();
|
||||
const sendMessage = vi.fn();
|
||||
const bot = { api: { sendMessage } } as unknown as Bot;
|
||||
|
|
@ -615,7 +615,7 @@ describe("deliverReplies", () => {
|
|||
replyToMode: "off",
|
||||
textLimit: 4000,
|
||||
}),
|
||||
).rejects.toThrow("empty formatted text and empty plain fallback");
|
||||
).resolves.toEqual({ delivered: false });
|
||||
expect(sendMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue