From 7a53ce379a2760c1bd724535441f008218d3207f Mon Sep 17 00:00:00 2001 From: EugeneTes Date: Tue, 22 Sep 2026 08:47:37 +0000 Subject: [PATCH] Prepend reply header ahead of forward header in normalizeMessage --- src/telegram/inbound.test.ts | 71 ++++++++++++++++++++++++++++++++++++ src/telegram/inbound.ts | 4 ++ 2 files changed, 75 insertions(+) diff --git a/src/telegram/inbound.test.ts b/src/telegram/inbound.test.ts index f8e1446..e607683 100644 --- a/src/telegram/inbound.test.ts +++ b/src/telegram/inbound.test.ts @@ -134,6 +134,77 @@ describe("normalizeMessage", () => { ); expect(r.input?.text).toBe("[Forwarded from Alice]\n(photo attached)"); }); + + it("prepends a reply header when the user replies to a bot message", async () => { + const r = await normalizeMessage( + { + text: "workspace/foo.csv", + reply_to_message: { from: { id: 42, is_bot: true }, text: "Which file did you mean?" }, + }, + deps({ botUserId: 42 }), + ); + expect(r.input?.text).toBe('[Reply to your message: "Which file did you mean?"]\nworkspace/foo.csv'); + }); + + it("uses 'your own earlier message' when replying to the owner's own message", async () => { + const r = await normalizeMessage( + { + text: "still true", + reply_to_message: { from: { id: 99, is_bot: false }, text: "the dog's name is Rex" }, + }, + deps({ botUserId: 42 }), + ); + expect(r.input?.text).toBe('[Reply to your own earlier message: "the dog\'s name is Rex"]\nstill true'); + }); + + it("uses the quote wording and text on reply-with-quote", async () => { + const r = await normalizeMessage( + { + text: "what does this mean?", + reply_to_message: { from: { id: 42, is_bot: true }, text: "long assistant explanation" }, + quote: { text: "geothermal power plant" }, + }, + deps({ botUserId: 42 }), + ); + expect(r.input?.text).toBe('[Reply quoting your message: "geothermal power plant"]\nwhat does this mean?'); + }); + + it("puts the reply header before the forward header when both are present", async () => { + const r = await normalizeMessage( + { + text: "this one", + reply_to_message: { from: { id: 42, is_bot: true }, text: "which article?" }, + forward_origin: { type: "channel", chat: { title: "Daily News" } }, + }, + deps({ botUserId: 42 }), + ); + expect(r.input?.text).toBe('[Reply to your message: "which article?"]\n[Forwarded from Daily News]\nthis one'); + }); + + it("keeps the reply header on a photo-only forwarded reply", async () => { + const r = await normalizeMessage( + { + photo: [{ file_id: "p2", width: 1, height: 1 }], + reply_to_message: { from: { id: 42, is_bot: true }, text: "which of these?" }, + forward_origin: { type: "user", sender_user: { first_name: "Alice" } }, + }, + deps({ botUserId: 42 }), + ); + expect(r.input?.text).toBe( + '[Reply to your message: "which of these?"]\n[Forwarded from Alice]\n(photo attached)', + ); + }); + + it("adds a reply header on a photo-only reply with no body text", async () => { + const r = await normalizeMessage( + { + photo: [{ file_id: "p2", width: 1, height: 1 }], + reply_to_message: { from: { id: 42, is_bot: true }, text: "which of these?" }, + }, + deps({ botUserId: 42 }), + ); + expect(r.input?.text).toBe('[Reply to your message: "which of these?"]\n(photo attached)'); + }); }); describe("saveToInbox", () => { diff --git a/src/telegram/inbound.ts b/src/telegram/inbound.ts index 7fc12b6..3461c4b 100644 --- a/src/telegram/inbound.ts +++ b/src/telegram/inbound.ts @@ -109,6 +109,10 @@ export async function normalizeMessage(msg: InboundMessage, deps: InboundDeps): const label = describeForwardOrigin(msg.forward_origin); text = text ? `[Forwarded from ${label}]\n${text}` : `[Forwarded from ${label}]`; } + const replyHeader = buildReplyHeader(msg.reply_to_message, msg.quote, deps.botUserId); + if (replyHeader && (text || images.length > 0)) { + text = text ? `${replyHeader}\n${text}` : replyHeader; + } return { input: text ? { text, images: images.length > 0 ? images : undefined } : null, transcript,