fix: clear Telegram DM draft after materialize (#36746) (thanks @joelnishanth)

This commit is contained in:
Ayaan Zaidi
2026-03-06 11:13:30 +05:30
committed by Ayaan Zaidi
parent e11a0775e7
commit 8c2633a46f
3 changed files with 58 additions and 12 deletions

View File

@@ -239,6 +239,27 @@ describe("createTelegramDraftStream", () => {
});
});
it("clears draft after materializing to avoid duplicate display in DM", async () => {
const api = createMockDraftApi();
const stream = createDraftStream(api, {
thread: { id: 42, scope: "dm" },
previewTransport: "draft",
});
stream.update("Hello");
await stream.flush();
const materializedId = await stream.materialize?.();
expect(materializedId).toBe(17);
expect(api.sendMessage).toHaveBeenCalledWith(123, "Hello", { message_thread_id: 42 });
// Draft should be cleared with empty string after real message is sent.
const draftCalls = api.sendMessageDraft.mock.calls;
const clearCall = draftCalls.find((call) => call[2] === "");
expect(clearCall).toBeDefined();
expect(clearCall?.[0]).toBe(123);
expect(clearCall?.[3]).toEqual({ message_thread_id: 42 });
});
it("retries materialize send without thread when dm thread lookup fails", async () => {
const api = createMockDraftApi();
api.sendMessage
@@ -258,6 +279,10 @@ describe("createTelegramDraftStream", () => {
expect(materializedId).toBe(55);
expect(api.sendMessage).toHaveBeenNthCalledWith(1, 123, "Hello", { message_thread_id: 42 });
expect(api.sendMessage).toHaveBeenNthCalledWith(2, 123, "Hello", undefined);
const draftCalls = api.sendMessageDraft.mock.calls;
const clearCall = draftCalls.find((call) => call[2] === "");
expect(clearCall).toBeDefined();
expect(clearCall?.[3]).toBeUndefined();
expect(warn).toHaveBeenCalledWith(
"telegram stream preview materialize send failed with message_thread_id, retrying without thread",
);

View File

@@ -150,13 +150,16 @@ export function createTelegramDraftStream(params: {
parse_mode: sendArgs.renderedParseMode,
}
: replyParams;
const usedThreadParams =
"message_thread_id" in (sendParams ?? {}) &&
typeof (sendParams as { message_thread_id?: unknown }).message_thread_id === "number";
try {
return await params.api.sendMessage(chatId, sendArgs.renderedText, sendParams);
return {
sent: await params.api.sendMessage(chatId, sendArgs.renderedText, sendParams),
usedThreadParams,
};
} catch (err) {
const hasThreadParam =
"message_thread_id" in (sendParams ?? {}) &&
typeof (sendParams as { message_thread_id?: unknown }).message_thread_id === "number";
if (!hasThreadParam || !THREAD_NOT_FOUND_RE.test(String(err))) {
if (!usedThreadParams || !THREAD_NOT_FOUND_RE.test(String(err))) {
throw err;
}
const threadlessParams = {
@@ -164,11 +167,14 @@ export function createTelegramDraftStream(params: {
};
delete threadlessParams.message_thread_id;
params.warn?.(sendArgs.fallbackWarnMessage);
return await params.api.sendMessage(
chatId,
sendArgs.renderedText,
Object.keys(threadlessParams).length > 0 ? threadlessParams : undefined,
);
return {
sent: await params.api.sendMessage(
chatId,
sendArgs.renderedText,
Object.keys(threadlessParams).length > 0 ? threadlessParams : undefined,
),
usedThreadParams: false,
};
}
};
const sendMessageTransportPreview = async ({
@@ -186,7 +192,7 @@ export function createTelegramDraftStream(params: {
}
return true;
}
const sent = await sendRenderedMessageWithThreadFallback({
const { sent } = await sendRenderedMessageWithThreadFallback({
renderedText,
renderedParseMode,
fallbackWarnMessage:
@@ -369,7 +375,7 @@ export function createTelegramDraftStream(params: {
}
const renderedParseMode = lastSentText ? lastSentParseMode : undefined;
try {
const sent = await sendRenderedMessageWithThreadFallback({
const { sent, usedThreadParams } = await sendRenderedMessageWithThreadFallback({
renderedText,
renderedParseMode,
fallbackWarnMessage:
@@ -378,6 +384,20 @@ export function createTelegramDraftStream(params: {
const sentId = sent?.message_id;
if (typeof sentId === "number" && Number.isFinite(sentId)) {
streamMessageId = Math.trunc(sentId);
// Clear the draft so Telegram's input area doesn't briefly show a
// stale copy alongside the newly materialized real message.
if (resolvedDraftApi != null && streamDraftId != null) {
const clearDraftId = streamDraftId;
const clearThreadParams =
usedThreadParams && threadParams?.message_thread_id != null
? { message_thread_id: threadParams.message_thread_id }
: undefined;
try {
await resolvedDraftApi(chatId, clearDraftId, "", clearThreadParams);
} catch {
// Best-effort cleanup; draft clear failure is cosmetic.
}
}
return streamMessageId;
}
} catch (err) {