mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
fix: sanitize native command names for Telegram API (#19257)
Merged via /review-pr -> /prepare-pr -> /merge-pr.
Prepared head SHA: b608be3488
Co-authored-by: akramcodez <179671552+akramcodez@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
This commit is contained in:
@@ -21,7 +21,7 @@ describe("telegram custom commands schema", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("rejects custom commands with invalid names", () => {
|
||||
it("normalizes hyphens in custom command names", () => {
|
||||
const res = OpenClawSchema.safeParse({
|
||||
channels: {
|
||||
telegram: {
|
||||
@@ -30,17 +30,13 @@ describe("telegram custom commands schema", () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.success).toBe(false);
|
||||
if (res.success) {
|
||||
expect(res.success).toBe(true);
|
||||
if (!res.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
expect(
|
||||
res.error.issues.some(
|
||||
(issue) =>
|
||||
issue.path.join(".") === "channels.telegram.customCommands.0.command" &&
|
||||
issue.message.includes("invalid"),
|
||||
),
|
||||
).toBe(true);
|
||||
expect(res.data.channels?.telegram?.customCommands).toEqual([
|
||||
{ command: "bad_name", description: "Override status" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ export function normalizeTelegramCommandName(value: string): string {
|
||||
return "";
|
||||
}
|
||||
const withoutSlash = trimmed.startsWith("/") ? trimmed.slice(1) : trimmed;
|
||||
return withoutSlash.trim().toLowerCase();
|
||||
return withoutSlash.trim().toLowerCase().replace(/-/g, "_");
|
||||
}
|
||||
|
||||
export function normalizeTelegramCommandDescription(value: string): string {
|
||||
|
||||
@@ -100,5 +100,7 @@ export function syncTelegramMenuCommands(params: {
|
||||
});
|
||||
};
|
||||
|
||||
void sync().catch(() => {});
|
||||
void sync().catch((err) => {
|
||||
runtime.error?.(`Telegram command sync failed: ${String(err)}`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -149,6 +149,37 @@ describe("registerTelegramNativeCommands", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("normalizes hyphenated native command names for Telegram registration", async () => {
|
||||
const setMyCommands = vi.fn().mockResolvedValue(undefined);
|
||||
const command = vi.fn();
|
||||
|
||||
registerTelegramNativeCommands({
|
||||
...buildParams({}),
|
||||
bot: {
|
||||
api: {
|
||||
setMyCommands,
|
||||
sendMessage: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
command,
|
||||
} as unknown as Parameters<typeof registerTelegramNativeCommands>[0]["bot"],
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(setMyCommands).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const registeredCommands = setMyCommands.mock.calls[0]?.[0] as Array<{
|
||||
command: string;
|
||||
description: string;
|
||||
}>;
|
||||
expect(registeredCommands.some((entry) => entry.command === "export_session")).toBe(true);
|
||||
expect(registeredCommands.some((entry) => entry.command === "export-session")).toBe(false);
|
||||
|
||||
const registeredHandlers = command.mock.calls.map(([name]) => name);
|
||||
expect(registeredHandlers).toContain("export_session");
|
||||
expect(registeredHandlers).not.toContain("export-session");
|
||||
});
|
||||
|
||||
it("passes agent-scoped media roots for plugin command replies with media", async () => {
|
||||
const commandHandlers = new Map<string, (ctx: unknown) => Promise<void>>();
|
||||
const sendMessage = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
@@ -17,7 +17,11 @@ import { createReplyPrefixOptions } from "../channels/reply-prefix.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { ChannelGroupPolicy } from "../config/group-policy.js";
|
||||
import { resolveMarkdownTableMode } from "../config/markdown-tables.js";
|
||||
import { resolveTelegramCustomCommands } from "../config/telegram-custom-commands.js";
|
||||
import {
|
||||
normalizeTelegramCommandName,
|
||||
resolveTelegramCustomCommands,
|
||||
TELEGRAM_COMMAND_NAME_PATTERN,
|
||||
} from "../config/telegram-custom-commands.js";
|
||||
import type {
|
||||
ReplyToMode,
|
||||
TelegramAccountConfig,
|
||||
@@ -310,7 +314,7 @@ export const registerTelegramNativeCommands = ({
|
||||
})
|
||||
: [];
|
||||
const reservedCommands = new Set(
|
||||
listNativeCommandSpecs().map((command) => command.name.toLowerCase()),
|
||||
listNativeCommandSpecs().map((command) => normalizeTelegramCommandName(command.name)),
|
||||
);
|
||||
for (const command of skillCommands) {
|
||||
reservedCommands.add(command.name.toLowerCase());
|
||||
@@ -326,7 +330,7 @@ export const registerTelegramNativeCommands = ({
|
||||
const pluginCommandSpecs = getPluginCommandSpecs();
|
||||
const existingCommands = new Set(
|
||||
[
|
||||
...nativeCommands.map((command) => command.name),
|
||||
...nativeCommands.map((command) => normalizeTelegramCommandName(command.name)),
|
||||
...customCommands.map((command) => command.command),
|
||||
].map((command) => command.toLowerCase()),
|
||||
);
|
||||
@@ -338,10 +342,23 @@ export const registerTelegramNativeCommands = ({
|
||||
runtime.error?.(danger(issue));
|
||||
}
|
||||
const allCommandsFull: Array<{ command: string; description: string }> = [
|
||||
...nativeCommands.map((command) => ({
|
||||
command: command.name,
|
||||
description: command.description,
|
||||
})),
|
||||
...nativeCommands
|
||||
.map((command) => {
|
||||
const normalized = normalizeTelegramCommandName(command.name);
|
||||
if (!TELEGRAM_COMMAND_NAME_PATTERN.test(normalized)) {
|
||||
runtime.error?.(
|
||||
danger(
|
||||
`Native command "${command.name}" is invalid for Telegram (resolved to "${normalized}"). Skipping.`,
|
||||
),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
command: normalized,
|
||||
description: command.description,
|
||||
};
|
||||
})
|
||||
.filter((cmd): cmd is { command: string; description: string } => cmd !== null),
|
||||
...(nativeEnabled ? pluginCatalog.commands : []),
|
||||
...customCommands,
|
||||
];
|
||||
@@ -419,7 +436,8 @@ export const registerTelegramNativeCommands = ({
|
||||
logVerbose("telegram: bot.command unavailable; skipping native handlers");
|
||||
} else {
|
||||
for (const command of nativeCommands) {
|
||||
bot.command(command.name, async (ctx: TelegramNativeCommandContext) => {
|
||||
const normalizedCommandName = normalizeTelegramCommandName(command.name);
|
||||
bot.command(normalizedCommandName, async (ctx: TelegramNativeCommandContext) => {
|
||||
const msg = ctx.message;
|
||||
if (!msg) {
|
||||
return;
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
listNativeCommandSpecs,
|
||||
listNativeCommandSpecsForConfig,
|
||||
} from "../auto-reply/commands-registry.js";
|
||||
import { normalizeTelegramCommandName } from "../config/telegram-custom-commands.js";
|
||||
import {
|
||||
answerCallbackQuerySpy,
|
||||
commandSpy,
|
||||
@@ -72,7 +73,7 @@ describe("createTelegramBot", () => {
|
||||
}>;
|
||||
const skillCommands = resolveSkillCommands(config);
|
||||
const native = listNativeCommandSpecsForConfig(config, { skillCommands }).map((command) => ({
|
||||
command: command.name,
|
||||
command: normalizeTelegramCommandName(command.name),
|
||||
description: command.description,
|
||||
}));
|
||||
expect(registered.slice(0, native.length)).toEqual(native);
|
||||
@@ -113,7 +114,7 @@ describe("createTelegramBot", () => {
|
||||
}>;
|
||||
const skillCommands = resolveSkillCommands(config);
|
||||
const native = listNativeCommandSpecsForConfig(config, { skillCommands }).map((command) => ({
|
||||
command: command.name,
|
||||
command: normalizeTelegramCommandName(command.name),
|
||||
description: command.description,
|
||||
}));
|
||||
const nativeStatus = native.find((command) => command.command === "status");
|
||||
|
||||
Reference in New Issue
Block a user