mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:00:43 +00:00
fix(telegram): fail soft on benign delete errors
This commit is contained in:
@@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Config: log the "newer OpenClaw" version warning once per process instead of once per config snapshot read. (#75927) Thanks @romneyda.
|
||||
- Telegram/message actions: treat benign delete-message 400s as no-op warnings instead of runtime errors, so stale or already-removed messages do not create noisy delete failures. Fixes #73726. Thanks @Avicennasis.
|
||||
- Gateway/chat history: merge Claude CLI transcript imports for Anthropic-routed sessions that still have a Claude CLI binding, so local chat history does not hide CLI JSONL turns. Fixes #75850. Thanks @alfredjbclaw.
|
||||
- Media: trim serialized JSON suffixes after local `MEDIA:` directive file extensions, so generated-image metadata cannot pollute the parsed media path and cause false `ENOENT` delivery failures. Fixes #75182. Thanks @TnzGit and @hclsys.
|
||||
- Cron: make scheduler reload schedule comparison tolerate malformed persisted jobs, so one bad cron entry no longer aborts the whole tick. Fixes #75886. Thanks @samfox-ai.
|
||||
|
||||
@@ -801,6 +801,38 @@ describe("handleTelegramAction", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("surfaces non-fatal delete warnings", async () => {
|
||||
deleteMessageTelegram.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
warning: "Message 456 was not deleted: 400: Bad Request: message can't be deleted",
|
||||
} as unknown as Awaited<ReturnType<typeof deleteMessageTelegram>>);
|
||||
const cfg = {
|
||||
channels: { telegram: { botToken: "tok" } },
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = await handleTelegramAction(
|
||||
{
|
||||
action: "deleteMessage",
|
||||
chatId: "123",
|
||||
messageId: 456,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
|
||||
const textPayload = result.content.find((item) => item.type === "text");
|
||||
expect(textPayload?.type).toBe("text");
|
||||
const parsed = JSON.parse((textPayload as { type: "text"; text: string }).text) as {
|
||||
ok: boolean;
|
||||
deleted?: boolean;
|
||||
warning?: string;
|
||||
};
|
||||
expect(parsed).toMatchObject({
|
||||
ok: false,
|
||||
deleted: false,
|
||||
warning: "Message 456 was not deleted: 400: Bad Request: message can't be deleted",
|
||||
});
|
||||
});
|
||||
|
||||
it("respects deleteMessage gating", async () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
|
||||
@@ -494,11 +494,14 @@ export async function handleTelegramAction(
|
||||
"Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or channels.telegram.botToken.",
|
||||
);
|
||||
}
|
||||
await telegramActionRuntime.deleteMessageTelegram(chatId ?? "", messageId ?? 0, {
|
||||
const result = await telegramActionRuntime.deleteMessageTelegram(chatId ?? "", messageId ?? 0, {
|
||||
cfg,
|
||||
token,
|
||||
accountId: accountId ?? undefined,
|
||||
});
|
||||
if (!result.ok) {
|
||||
return jsonResult({ ok: false, deleted: false, warning: result.warning });
|
||||
}
|
||||
return jsonResult({ ok: true, deleted: true });
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ const {
|
||||
const {
|
||||
buildInlineKeyboard,
|
||||
createForumTopicTelegram,
|
||||
deleteMessageTelegram,
|
||||
editForumTopicTelegram,
|
||||
editMessageTelegram,
|
||||
pinMessageTelegram,
|
||||
@@ -2053,6 +2054,43 @@ describe("reactMessageTelegram", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleteMessageTelegram", () => {
|
||||
it.each([
|
||||
"400: Bad Request: message to delete not found",
|
||||
"400: Bad Request: message can't be deleted",
|
||||
"MESSAGE_ID_INVALID",
|
||||
"MESSAGE_DELETE_FORBIDDEN",
|
||||
] as const)("returns a warning for benign delete no-op error: %s", async (message) => {
|
||||
const deleteMessage = vi.fn().mockRejectedValue(new Error(message));
|
||||
const api = { deleteMessage } as unknown as { deleteMessage: typeof deleteMessage };
|
||||
|
||||
const result = await deleteMessageTelegram("123", 456, {
|
||||
cfg: TELEGRAM_TEST_CFG,
|
||||
token: "tok",
|
||||
api,
|
||||
});
|
||||
|
||||
expect(deleteMessage).toHaveBeenCalledWith("123", 456);
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
warning: expect.stringContaining(message),
|
||||
});
|
||||
});
|
||||
|
||||
it("throws non-benign delete errors", async () => {
|
||||
const deleteMessage = vi.fn().mockRejectedValue(new Error("500: Internal Server Error"));
|
||||
const api = { deleteMessage } as unknown as { deleteMessage: typeof deleteMessage };
|
||||
|
||||
await expect(
|
||||
deleteMessageTelegram("123", 456, {
|
||||
cfg: TELEGRAM_TEST_CFG,
|
||||
token: "tok",
|
||||
api,
|
||||
}),
|
||||
).rejects.toThrow(/Internal Server Error/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sendStickerTelegram", () => {
|
||||
const positiveSendCases = [
|
||||
{
|
||||
|
||||
@@ -178,6 +178,8 @@ const PARSE_ERR_RE = /can't parse entities|parse entities|find end of the entity
|
||||
const THREAD_NOT_FOUND_RE = /400:\s*Bad Request:\s*message thread not found/i;
|
||||
const MESSAGE_NOT_MODIFIED_RE =
|
||||
/400:\s*Bad Request:\s*message is not modified|MESSAGE_NOT_MODIFIED/i;
|
||||
const MESSAGE_DELETE_NOOP_RE =
|
||||
/message to delete not found|message can't be deleted|MESSAGE_ID_INVALID|MESSAGE_DELETE_FORBIDDEN/i;
|
||||
const CHAT_NOT_FOUND_RE = /400: Bad Request: chat not found/i;
|
||||
const sendLogger = createSubsystemLogger("telegram/send");
|
||||
const diagLogger = createSubsystemLogger("telegram/diagnostic");
|
||||
@@ -373,6 +375,10 @@ function isTelegramMessageNotModifiedError(err: unknown): boolean {
|
||||
return MESSAGE_NOT_MODIFIED_RE.test(formatErrorMessage(err));
|
||||
}
|
||||
|
||||
function isTelegramMessageDeleteNoopError(err: unknown): boolean {
|
||||
return MESSAGE_DELETE_NOOP_RE.test(formatErrorMessage(err));
|
||||
}
|
||||
|
||||
function hasMessageThreadIdParam(params?: TelegramThreadScopedParams): boolean {
|
||||
if (!params) {
|
||||
return false;
|
||||
@@ -1072,7 +1078,7 @@ export async function deleteMessageTelegram(
|
||||
chatIdInput: string | number,
|
||||
messageIdInput: string | number,
|
||||
opts: TelegramDeleteOpts,
|
||||
): Promise<{ ok: true }> {
|
||||
): Promise<{ ok: true } | { ok: false; warning: string }> {
|
||||
const { cfg, account, api } = resolveTelegramApiContext(opts);
|
||||
const rawTarget = String(chatIdInput);
|
||||
const chatId = await resolveAndPersistChatId({
|
||||
@@ -1090,7 +1096,21 @@ export async function deleteMessageTelegram(
|
||||
verbose: opts.verbose,
|
||||
shouldRetry: (err) => isRecoverableTelegramNetworkError(err, { context: "send" }),
|
||||
});
|
||||
await requestWithDiag(() => api.deleteMessage(chatId, messageId), "deleteMessage");
|
||||
try {
|
||||
await requestWithDiag(() => api.deleteMessage(chatId, messageId), "deleteMessage", {
|
||||
shouldLog: (err) => !isTelegramMessageDeleteNoopError(err),
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
if (!isTelegramMessageDeleteNoopError(err)) {
|
||||
throw err;
|
||||
}
|
||||
const detail = formatErrorMessage(err);
|
||||
logVerbose(`[telegram] Delete skipped for message ${messageId} in chat ${chatId}: ${detail}`);
|
||||
return {
|
||||
ok: false,
|
||||
warning: `Message ${messageId} was not deleted: ${detail}`,
|
||||
};
|
||||
}
|
||||
logVerbose(`[telegram] Deleted message ${messageId} from chat ${chatId}`);
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user