mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 02:52:43 +00:00
fix(telegram): suppress message_thread_id for private chat sends (#17242)
Private chats (positive numeric chat IDs) never support forum topics. Sending message_thread_id to a private chat causes Telegram to reject the request with '400: Bad Request: message thread not found', silently dropping the message. Guard all three send functions (sendMessageTelegram, sendStickerTelegram, sendPollTelegram) to omit thread-related parameters when the target is a private chat. Root cause: the auto-reply pipeline can set messageThreadId from a previous forum-group context, then reuse it when sending a DM. Tests: add private-chat suppression assertions; update existing thread- retry tests to use group chat IDs so the retry path is still exercised.
This commit is contained in:
committed by
Peter Steinberger
parent
2ed43fd7b4
commit
b4a90bb743
@@ -850,8 +850,55 @@ describe("sendMessageTelegram", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("suppresses message_thread_id for private chat sends (#17242)", async () => {
|
||||||
|
// Private chats have positive numeric IDs; they never support forum topics.
|
||||||
|
const chatId = "123456789";
|
||||||
|
const sendMessage = vi.fn().mockResolvedValue({
|
||||||
|
message_id: 56,
|
||||||
|
chat: { id: chatId },
|
||||||
|
});
|
||||||
|
const api = { sendMessage } as unknown as {
|
||||||
|
sendMessage: typeof sendMessage;
|
||||||
|
};
|
||||||
|
|
||||||
|
await sendMessageTelegram(chatId, "hello private", {
|
||||||
|
token: "tok",
|
||||||
|
api,
|
||||||
|
messageThreadId: 271,
|
||||||
|
});
|
||||||
|
|
||||||
|
// message_thread_id must NOT appear in private chats -- Telegram rejects it
|
||||||
|
// with "400: Bad Request: message thread not found".
|
||||||
|
expect(sendMessage).toHaveBeenCalledWith(chatId, "hello private", {
|
||||||
|
parse_mode: "HTML",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps message_thread_id for group chat sends (#17242)", async () => {
|
||||||
|
// Group/supergroup chats have negative IDs.
|
||||||
|
const chatId = "-1001234567890";
|
||||||
|
const sendMessage = vi.fn().mockResolvedValue({
|
||||||
|
message_id: 57,
|
||||||
|
chat: { id: chatId },
|
||||||
|
});
|
||||||
|
const api = { sendMessage } as unknown as {
|
||||||
|
sendMessage: typeof sendMessage;
|
||||||
|
};
|
||||||
|
|
||||||
|
await sendMessageTelegram(chatId, "hello group", {
|
||||||
|
token: "tok",
|
||||||
|
api,
|
||||||
|
messageThreadId: 271,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sendMessage).toHaveBeenCalledWith(chatId, "hello group", {
|
||||||
|
parse_mode: "HTML",
|
||||||
|
message_thread_id: 271,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("retries without message_thread_id when Telegram reports missing thread", async () => {
|
it("retries without message_thread_id when Telegram reports missing thread", async () => {
|
||||||
const chatId = "123";
|
const chatId = "-100123";
|
||||||
const threadErr = new Error("400: Bad Request: message thread not found");
|
const threadErr = new Error("400: Bad Request: message thread not found");
|
||||||
const sendMessage = vi
|
const sendMessage = vi
|
||||||
.fn()
|
.fn()
|
||||||
@@ -963,7 +1010,7 @@ describe("sendMessageTelegram", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("retries media sends without message_thread_id when thread is missing", async () => {
|
it("retries media sends without message_thread_id when thread is missing", async () => {
|
||||||
const chatId = "123";
|
const chatId = "-100123";
|
||||||
const threadErr = new Error("400: Bad Request: message thread not found");
|
const threadErr = new Error("400: Bad Request: message thread not found");
|
||||||
const sendPhoto = vi
|
const sendPhoto = vi
|
||||||
.fn()
|
.fn()
|
||||||
@@ -1084,7 +1131,7 @@ describe("sendStickerTelegram", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("retries sticker sends without message_thread_id when thread is missing", async () => {
|
it("retries sticker sends without message_thread_id when thread is missing", async () => {
|
||||||
const chatId = "123";
|
const chatId = "-100123";
|
||||||
const threadErr = new Error("400: Bad Request: message thread not found");
|
const threadErr = new Error("400: Bad Request: message thread not found");
|
||||||
const sendSticker = vi
|
const sendSticker = vi
|
||||||
.fn()
|
.fn()
|
||||||
@@ -1337,7 +1384,7 @@ describe("sendPollTelegram", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const res = await sendPollTelegram(
|
const res = await sendPollTelegram(
|
||||||
"123",
|
"-100123",
|
||||||
{ question: "Q", options: ["A", "B"] },
|
{ question: "Q", options: ["A", "B"] },
|
||||||
{ token: "t", api: api as unknown as Bot["api"], messageThreadId: 99 },
|
{ token: "t", api: api as unknown as Bot["api"], messageThreadId: 99 },
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -196,6 +196,17 @@ function isTelegramMessageNotModifiedError(err: unknown): boolean {
|
|||||||
return MESSAGE_NOT_MODIFIED_RE.test(formatErrorMessage(err));
|
return MESSAGE_NOT_MODIFIED_RE.test(formatErrorMessage(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Telegram private chats have positive numeric IDs.
|
||||||
|
* Groups and supergroups have negative IDs (typically -100… for supergroups).
|
||||||
|
* Private chats never support forum topics, so `message_thread_id` must
|
||||||
|
* not be included in API calls targeting them (#17242).
|
||||||
|
*/
|
||||||
|
function isTelegramPrivateChat(chatId: string): boolean {
|
||||||
|
const n = Number(chatId);
|
||||||
|
return Number.isFinite(n) && n > 0;
|
||||||
|
}
|
||||||
|
|
||||||
function hasMessageThreadIdParam(params?: Record<string, unknown>): boolean {
|
function hasMessageThreadIdParam(params?: Record<string, unknown>): boolean {
|
||||||
if (!params) {
|
if (!params) {
|
||||||
return false;
|
return false;
|
||||||
@@ -428,9 +439,10 @@ export async function sendMessageTelegram(
|
|||||||
const mediaUrl = opts.mediaUrl?.trim();
|
const mediaUrl = opts.mediaUrl?.trim();
|
||||||
const replyMarkup = buildInlineKeyboard(opts.buttons);
|
const replyMarkup = buildInlineKeyboard(opts.buttons);
|
||||||
|
|
||||||
|
const isPrivate = isTelegramPrivateChat(chatId);
|
||||||
const threadParams = buildTelegramThreadReplyParams({
|
const threadParams = buildTelegramThreadReplyParams({
|
||||||
targetMessageThreadId: target.messageThreadId,
|
targetMessageThreadId: isPrivate ? undefined : target.messageThreadId,
|
||||||
messageThreadId: opts.messageThreadId,
|
messageThreadId: isPrivate ? undefined : opts.messageThreadId,
|
||||||
replyToMessageId: opts.replyToMessageId,
|
replyToMessageId: opts.replyToMessageId,
|
||||||
quoteText: opts.quoteText,
|
quoteText: opts.quoteText,
|
||||||
});
|
});
|
||||||
@@ -919,9 +931,10 @@ export async function sendStickerTelegram(
|
|||||||
const target = parseTelegramTarget(to);
|
const target = parseTelegramTarget(to);
|
||||||
const chatId = normalizeChatId(target.chatId);
|
const chatId = normalizeChatId(target.chatId);
|
||||||
|
|
||||||
|
const isPrivate = isTelegramPrivateChat(chatId);
|
||||||
const threadParams = buildTelegramThreadReplyParams({
|
const threadParams = buildTelegramThreadReplyParams({
|
||||||
targetMessageThreadId: target.messageThreadId,
|
targetMessageThreadId: isPrivate ? undefined : target.messageThreadId,
|
||||||
messageThreadId: opts.messageThreadId,
|
messageThreadId: isPrivate ? undefined : opts.messageThreadId,
|
||||||
replyToMessageId: opts.replyToMessageId,
|
replyToMessageId: opts.replyToMessageId,
|
||||||
});
|
});
|
||||||
const hasThreadParams = Object.keys(threadParams).length > 0;
|
const hasThreadParams = Object.keys(threadParams).length > 0;
|
||||||
@@ -997,9 +1010,10 @@ export async function sendPollTelegram(
|
|||||||
// Normalize the poll input (validates question, options, maxSelections)
|
// Normalize the poll input (validates question, options, maxSelections)
|
||||||
const normalizedPoll = normalizePollInput(poll, { maxOptions: 10 });
|
const normalizedPoll = normalizePollInput(poll, { maxOptions: 10 });
|
||||||
|
|
||||||
|
const isPrivate = isTelegramPrivateChat(chatId);
|
||||||
const threadParams = buildTelegramThreadReplyParams({
|
const threadParams = buildTelegramThreadReplyParams({
|
||||||
targetMessageThreadId: target.messageThreadId,
|
targetMessageThreadId: isPrivate ? undefined : target.messageThreadId,
|
||||||
messageThreadId: opts.messageThreadId,
|
messageThreadId: isPrivate ? undefined : opts.messageThreadId,
|
||||||
replyToMessageId: opts.replyToMessageId,
|
replyToMessageId: opts.replyToMessageId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user