mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-03 20:24:03 +00:00
fix: validate telegram action integers
This commit is contained in:
@@ -407,6 +407,24 @@ describe("handleTelegramAction", () => {
|
||||
expect(reactMessageTelegram).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("soft-fails fractional reaction message ids", async () => {
|
||||
const result = await handleTelegramAction(
|
||||
{
|
||||
action: "react",
|
||||
chatId: "123",
|
||||
messageId: 456.5,
|
||||
emoji: "✅",
|
||||
},
|
||||
reactionConfig("minimal"),
|
||||
);
|
||||
|
||||
expect(resultDetails(result)).toMatchObject({
|
||||
ok: false,
|
||||
reason: "missing_message_id",
|
||||
});
|
||||
expect(reactMessageTelegram).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("removes reactions on empty emoji", async () => {
|
||||
await handleTelegramAction(
|
||||
{
|
||||
@@ -482,6 +500,52 @@ describe("handleTelegramAction", () => {
|
||||
expect(options.messageThreadId).toBe(11);
|
||||
});
|
||||
|
||||
it("treats null primary id aliases as absent", async () => {
|
||||
await handleTelegramAction(
|
||||
{
|
||||
action: "sendSticker",
|
||||
to: "123",
|
||||
fileId: "sticker",
|
||||
replyToMessageId: null,
|
||||
replyTo: 9,
|
||||
messageThreadId: null,
|
||||
threadId: 11,
|
||||
},
|
||||
telegramConfig({ actions: { sticker: true } }),
|
||||
);
|
||||
const call = mockCall(sendStickerTelegram, 0, "sticker null aliases");
|
||||
const options = requireRecord(call[2], "sticker null alias options");
|
||||
expect(options.replyToMessageId).toBe(9);
|
||||
expect(options.messageThreadId).toBe(11);
|
||||
});
|
||||
|
||||
it("rejects fractional Telegram thread and reply ids before sending", async () => {
|
||||
await expect(
|
||||
handleTelegramAction(
|
||||
{
|
||||
action: "sendMessage",
|
||||
to: "123",
|
||||
content: "hello",
|
||||
replyToMessageId: 9.5,
|
||||
},
|
||||
telegramConfig(),
|
||||
),
|
||||
).rejects.toThrow("replyToMessageId must be a positive integer.");
|
||||
await expect(
|
||||
handleTelegramAction(
|
||||
{
|
||||
action: "sendSticker",
|
||||
to: "123",
|
||||
fileId: "sticker",
|
||||
threadId: 11.5,
|
||||
},
|
||||
telegramConfig({ actions: { sticker: true } }),
|
||||
),
|
||||
).rejects.toThrow("threadId must be a positive integer.");
|
||||
expect(sendDurableMessageBatch).not.toHaveBeenCalled();
|
||||
expect(sendStickerTelegram).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("removes reactions when remove flag set", async () => {
|
||||
const cfg = reactionConfig("extensive");
|
||||
await handleTelegramAction(
|
||||
@@ -991,6 +1055,22 @@ describe("handleTelegramAction", () => {
|
||||
expect(details.pollId).toBe("poll-1");
|
||||
});
|
||||
|
||||
it("rejects fractional poll durations before sending", async () => {
|
||||
await expect(
|
||||
handleTelegramAction(
|
||||
{
|
||||
action: "poll",
|
||||
to: "@testchannel",
|
||||
question: "Ready?",
|
||||
answers: ["Yes", "No"],
|
||||
durationSeconds: 60.5,
|
||||
},
|
||||
telegramConfig(),
|
||||
),
|
||||
).rejects.toThrow("durationSeconds must be a positive integer.");
|
||||
expect(sendPollTelegram).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("accepts shared poll action aliases", async () => {
|
||||
await handleTelegramAction(
|
||||
{
|
||||
@@ -1467,6 +1547,36 @@ describe("handleTelegramAction", () => {
|
||||
expect(requireRecord(call[2], "delete message options").token).toBe("tok");
|
||||
});
|
||||
|
||||
it("rejects fractional message ids before mutating messages", async () => {
|
||||
const cfg = {
|
||||
channels: { telegram: { botToken: "tok" } },
|
||||
} as OpenClawConfig;
|
||||
|
||||
await expect(
|
||||
handleTelegramAction(
|
||||
{
|
||||
action: "deleteMessage",
|
||||
chatId: "123",
|
||||
messageId: 456.5,
|
||||
},
|
||||
cfg,
|
||||
),
|
||||
).rejects.toThrow("messageId must be a positive integer.");
|
||||
await expect(
|
||||
handleTelegramAction(
|
||||
{
|
||||
action: "editMessage",
|
||||
chatId: "123",
|
||||
messageId: 456.5,
|
||||
content: "updated",
|
||||
},
|
||||
cfg,
|
||||
),
|
||||
).rejects.toThrow("messageId must be a positive integer.");
|
||||
expect(deleteMessageTelegram).not.toHaveBeenCalled();
|
||||
expect(editMessageTelegram).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("surfaces non-fatal delete warnings", async () => {
|
||||
deleteMessageTelegram.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { AgentToolResult } from "openclaw/plugin-sdk/agent-core";
|
||||
import { readBooleanParam } from "openclaw/plugin-sdk/boolean-param";
|
||||
import {
|
||||
jsonResult,
|
||||
readNumberParam,
|
||||
readPositiveIntegerParam,
|
||||
readReactionParams,
|
||||
readStringArrayParam,
|
||||
readStringOrNumberParam,
|
||||
@@ -97,7 +97,9 @@ type TelegramForumTopicIconColor = (typeof TELEGRAM_FORUM_TOPIC_ICON_COLORS)[num
|
||||
function readTelegramForumTopicIconColor(
|
||||
params: Record<string, unknown>,
|
||||
): TelegramForumTopicIconColor | undefined {
|
||||
const iconColor = readNumberParam(params, "iconColor", { integer: true });
|
||||
const iconColor = readPositiveIntegerParam(params, "iconColor", {
|
||||
message: "iconColor must be one of Telegram's supported forum topic colors.",
|
||||
});
|
||||
if (iconColor == null) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -124,8 +126,12 @@ function readTelegramChatId(params: Record<string, unknown>) {
|
||||
|
||||
function readTelegramThreadId(params: Record<string, unknown>) {
|
||||
return (
|
||||
readNumberParam(params, "messageThreadId", { integer: true }) ??
|
||||
readNumberParam(params, "threadId", { integer: true })
|
||||
readPositiveIntegerParam(params, "messageThreadId", {
|
||||
message: "messageThreadId must be a positive integer.",
|
||||
}) ??
|
||||
readPositiveIntegerParam(params, "threadId", {
|
||||
message: "threadId must be a positive integer.",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -147,8 +153,12 @@ function formatTelegramDeliveryTarget(to: string, messageThreadId?: number | nul
|
||||
|
||||
function readTelegramReplyToMessageId(params: Record<string, unknown>) {
|
||||
return (
|
||||
readNumberParam(params, "replyToMessageId", { integer: true }) ??
|
||||
readNumberParam(params, "replyTo", { integer: true })
|
||||
readPositiveIntegerParam(params, "replyToMessageId", {
|
||||
message: "replyToMessageId must be a positive integer.",
|
||||
}) ??
|
||||
readPositiveIntegerParam(params, "replyTo", {
|
||||
message: "replyTo must be a positive integer.",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -353,9 +363,19 @@ export async function handleTelegramAction(
|
||||
});
|
||||
}
|
||||
const chatId = readTelegramChatId(params);
|
||||
const messageId =
|
||||
readNumberParam(params, "messageId", { integer: true }) ??
|
||||
resolveReactionMessageId({ args: params });
|
||||
let explicitMessageId: number | undefined;
|
||||
try {
|
||||
explicitMessageId = readPositiveIntegerParam(params, "messageId", {
|
||||
message: "messageId must be a positive integer.",
|
||||
});
|
||||
} catch {
|
||||
return jsonResult({
|
||||
ok: false,
|
||||
reason: "missing_message_id",
|
||||
hint: "Telegram reaction requires a valid messageId (or inbound context fallback). Do not retry.",
|
||||
});
|
||||
}
|
||||
const messageId = explicitMessageId ?? resolveReactionMessageId({ args: params });
|
||||
if (typeof messageId !== "number" || !Number.isFinite(messageId) || messageId <= 0) {
|
||||
return jsonResult({
|
||||
ok: false,
|
||||
@@ -547,16 +567,18 @@ export async function handleTelegramAction(
|
||||
const allowMultiselect =
|
||||
readBooleanParam(params, "allowMultiselect") ?? readBooleanParam(params, "pollMulti");
|
||||
const durationSeconds =
|
||||
readNumberParam(params, "durationSeconds", { integer: true }) ??
|
||||
readNumberParam(params, "pollDurationSeconds", {
|
||||
integer: true,
|
||||
strict: true,
|
||||
readPositiveIntegerParam(params, "durationSeconds", {
|
||||
message: "durationSeconds must be a positive integer.",
|
||||
}) ??
|
||||
readPositiveIntegerParam(params, "pollDurationSeconds", {
|
||||
message: "pollDurationSeconds must be a positive integer.",
|
||||
});
|
||||
const durationHours =
|
||||
readNumberParam(params, "durationHours", { integer: true }) ??
|
||||
readNumberParam(params, "pollDurationHours", {
|
||||
integer: true,
|
||||
strict: true,
|
||||
readPositiveIntegerParam(params, "durationHours", {
|
||||
message: "durationHours must be a positive integer.",
|
||||
}) ??
|
||||
readPositiveIntegerParam(params, "pollDurationHours", {
|
||||
message: "pollDurationHours must be a positive integer.",
|
||||
});
|
||||
const replyToMessageId = readTelegramReplyToMessageId(params);
|
||||
const messageThreadId = readTelegramThreadId(params);
|
||||
@@ -607,10 +629,12 @@ export async function handleTelegramAction(
|
||||
throw new Error("Telegram deleteMessage is disabled.");
|
||||
}
|
||||
const chatId = readTelegramChatId(params);
|
||||
const messageId = readNumberParam(params, "messageId", {
|
||||
required: true,
|
||||
integer: true,
|
||||
const messageId = readPositiveIntegerParam(params, "messageId", {
|
||||
message: "messageId must be a positive integer.",
|
||||
});
|
||||
if (messageId === undefined) {
|
||||
throw new Error("messageId required");
|
||||
}
|
||||
const token = resolveTelegramToken(cfg, { accountId }).token;
|
||||
if (!token) {
|
||||
throw new Error(
|
||||
@@ -634,10 +658,12 @@ export async function handleTelegramAction(
|
||||
throw new Error("Telegram editMessage is disabled.");
|
||||
}
|
||||
const chatId = readTelegramChatId(params);
|
||||
const messageId = readNumberParam(params, "messageId", {
|
||||
required: true,
|
||||
integer: true,
|
||||
const messageId = readPositiveIntegerParam(params, "messageId", {
|
||||
message: "messageId must be a positive integer.",
|
||||
});
|
||||
if (messageId === undefined) {
|
||||
throw new Error("messageId required");
|
||||
}
|
||||
const content =
|
||||
readStringParam(params, "content", { allowEmpty: false }) ??
|
||||
readStringParam(params, "message", { required: true, allowEmpty: false });
|
||||
@@ -722,7 +748,10 @@ export async function handleTelegramAction(
|
||||
);
|
||||
}
|
||||
const query = readStringParam(params, "query", { required: true });
|
||||
const limit = readNumberParam(params, "limit", { integer: true }) ?? 5;
|
||||
const limit =
|
||||
readPositiveIntegerParam(params, "limit", {
|
||||
message: "limit must be a positive integer.",
|
||||
}) ?? 5;
|
||||
const results = telegramActionRuntime.searchStickers(query, limit);
|
||||
return jsonResult({
|
||||
ok: true,
|
||||
|
||||
@@ -94,6 +94,7 @@ describe("readNumberParam", () => {
|
||||
|
||||
it("throws for invalid present positive integer params", () => {
|
||||
expect(readPositiveIntegerParam({ timeoutMs: "42" }, "timeoutMs")).toBe(42);
|
||||
expect(readPositiveIntegerParam({ timeoutMs: null }, "timeoutMs")).toBeUndefined();
|
||||
expect(() => readPositiveIntegerParam({ timeoutMs: "42.5" }, "timeoutMs")).toThrow(
|
||||
"timeoutMs must be a positive integer",
|
||||
);
|
||||
|
||||
@@ -218,7 +218,7 @@ export function readPositiveIntegerParam(
|
||||
positiveInteger: true,
|
||||
strict: true,
|
||||
});
|
||||
if (value === undefined && readParamRaw(params, key) !== undefined) {
|
||||
if (value === undefined && readParamRaw(params, key) != null) {
|
||||
throw new ToolInputError(options.message ?? `${key} must be a positive integer`);
|
||||
}
|
||||
if (value !== undefined && options.max !== undefined && value > options.max) {
|
||||
|
||||
@@ -13,6 +13,7 @@ export {
|
||||
jsonResult,
|
||||
parseAvailableTags,
|
||||
readNumberParam,
|
||||
readPositiveIntegerParam,
|
||||
readReactionParams,
|
||||
readStringArrayParam,
|
||||
readStringOrNumberParam,
|
||||
|
||||
Reference in New Issue
Block a user