Skip to content

Commit 16fe70e

Browse files
committed
fix(telegram): preserve message_thread_id on media caption parse fallback
ClawSweeper P1: the media wrapper passed removeMessageThreadIdParam( plainMediaParams) even for the first attempt, so an HTML caption parse error for a topic-scoped media send retried without message_thread_id and could deliver topic media to the group root. Keep the thread for caption parse fallback; only strip it for the allowed thread-not-found retry.
1 parent f313c27 commit 16fe70e

3 files changed

Lines changed: 66 additions & 6 deletions

File tree

extensions/telegram/src/draft-stream.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,11 @@ export function createTelegramDraftStream(params: {
685685
// Rewind WITHOUT deleting; the old id is captured above.
686686
resetStreamToNewMessage();
687687
if (typeof supersededMessageId === "number" && Number.isFinite(supersededMessageId)) {
688-
scheduleDetachedDelete(supersededMessageId, supersededVisibleSince, REPOSITION_DELETE_DELAY_MS);
688+
scheduleDetachedDelete(
689+
supersededMessageId,
690+
supersededVisibleSince,
691+
REPOSITION_DELETE_DELAY_MS,
692+
);
689693
return supersededMessageId;
690694
}
691695
return undefined;
@@ -704,9 +708,7 @@ export function createTelegramDraftStream(params: {
704708
return streamMessageId;
705709
};
706710

707-
const finalizeToPreview = async (
708-
preview: TelegramDraftPreview,
709-
): Promise<number | undefined> => {
711+
const finalizeToPreview = async (preview: TelegramDraftPreview): Promise<number | undefined> => {
710712
const text = preview.text.trimEnd();
711713
if (!text) {
712714
return undefined;

extensions/telegram/src/send.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1969,6 +1969,56 @@ describe("sendMessageTelegram", () => {
19691969
expect(result).toEqual({ messageId: "91", chatId });
19701970
});
19711971

1972+
it("keeps message_thread_id on plain caption fallback for forum topic media sends", async () => {
1973+
const chatId = "-100123";
1974+
const caption = "hi **boss**";
1975+
const sendPhoto = vi
1976+
.fn()
1977+
.mockRejectedValueOnce(createHtmlParseError("sendPhoto"))
1978+
.mockResolvedValueOnce({
1979+
message_id: 92,
1980+
chat: { id: chatId },
1981+
});
1982+
const api = { sendPhoto } as unknown as {
1983+
sendPhoto: typeof sendPhoto;
1984+
};
1985+
1986+
mockLoadedMedia({
1987+
buffer: Buffer.from("fake-image"),
1988+
contentType: "image/jpeg",
1989+
fileName: "photo.jpg",
1990+
});
1991+
1992+
const result = await sendMessageTelegram(chatId, caption, {
1993+
cfg: TELEGRAM_TEST_CFG,
1994+
token: "tok",
1995+
api,
1996+
mediaUrl: "https://example.com/photo.jpg",
1997+
messageThreadId: 271,
1998+
});
1999+
2000+
expectMediaSendCall(
2001+
firstMockCall(sendPhoto, "first send photo call"),
2002+
"send photo call",
2003+
chatId,
2004+
{
2005+
caption: "hi <b>boss</b>",
2006+
parse_mode: "HTML",
2007+
message_thread_id: 271,
2008+
},
2009+
);
2010+
expectMediaSendCall(
2011+
mockCall(sendPhoto, 1, "plain caption retry call"),
2012+
"plain caption retry call",
2013+
chatId,
2014+
{
2015+
caption,
2016+
message_thread_id: 271,
2017+
},
2018+
);
2019+
expect(result).toEqual({ messageId: "92", chatId });
2020+
});
2021+
19722022
it("sends video notes when requested and regular videos otherwise", async () => {
19732023
const chatId = "123";
19742024

@@ -3135,6 +3185,7 @@ describe("sendMessageTelegram", () => {
31353185
await expect(
31363186
sendMessageTelegram(chatId, "photo", {
31373187
token: "tok",
3188+
cfg: TELEGRAM_TEST_CFG,
31383189
api,
31393190
mediaUrl: "https://example.com/photo.jpg",
31403191
messageThreadId: 271,
@@ -3543,6 +3594,7 @@ describe("sendStickerTelegram", () => {
35433594
await expect(
35443595
sendStickerTelegram(chatId, "fileId123", {
35453596
token: "tok",
3597+
cfg: TELEGRAM_TEST_CFG,
35463598
api,
35473599
messageThreadId: 271,
35483600
}),

extensions/telegram/src/send.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,12 +1512,18 @@ export async function sendMessageTelegram(
15121512
opts.verbose,
15131513
allowThreadlessFallback,
15141514
async (effectiveParams, retryLabel) => {
1515-
const threadlessPlainMediaParams = removeMessageThreadIdParam(plainMediaParams);
1515+
// Keep message_thread_id on the caption parse fallback so topic-scoped
1516+
// media stays in the forum topic. withTelegramThreadFallback already
1517+
// strips the thread for the thread-not-found retry via effectiveParams;
1518+
// mirror that state onto the plain-caption fallback.
1519+
const effectivePlainMediaParams = hasMessageThreadIdParam(effectiveParams)
1520+
? plainMediaParams
1521+
: removeMessageThreadIdParam(plainMediaParams);
15161522
return await sendMedia(
15171523
retryLabel,
15181524
mediaSender.sender,
15191525
effectiveParams,
1520-
threadlessPlainMediaParams,
1526+
effectivePlainMediaParams,
15211527
);
15221528
},
15231529
);

0 commit comments

Comments
 (0)