Merge pull request #729 from clawdbot/fix/telegram-command-mentions

fix: normalize telegram command mentions
This commit is contained in:
Ayaan Zaidi
2026-01-11 21:14:01 +05:30
committed by GitHub
5 changed files with 65 additions and 9 deletions

View File

@@ -71,4 +71,17 @@ describe("control command parsing", () => {
expect(hasControlCommand("prefix /send on")).toBe(false);
expect(hasControlCommand("/send on")).toBe(true);
});
it("ignores telegram commands addressed to other bots", () => {
expect(
hasControlCommand("/help@otherbot", undefined, {
botUsername: "clawdbot",
}),
).toBe(false);
expect(
hasControlCommand("/help@clawdbot", undefined, {
botUsername: "clawdbot",
}),
).toBe(true);
});
});

View File

@@ -1,5 +1,6 @@
import type { ClawdbotConfig } from "../config/types.js";
import {
type CommandNormalizeOptions,
listChatCommands,
listChatCommandsForConfig,
normalizeCommandBody,
@@ -8,11 +9,12 @@ import {
export function hasControlCommand(
text?: string,
cfg?: ClawdbotConfig,
options?: CommandNormalizeOptions,
): boolean {
if (!text) return false;
const trimmed = text.trim();
if (!trimmed) return false;
const normalizedBody = normalizeCommandBody(trimmed);
const normalizedBody = normalizeCommandBody(trimmed, options);
if (!normalizedBody) return false;
const lowered = normalizedBody.toLowerCase();
const commands = cfg ? listChatCommandsForConfig(cfg) : listChatCommands();

View File

@@ -7,6 +7,7 @@ import {
listChatCommandsForConfig,
listNativeCommandSpecs,
listNativeCommandSpecsForConfig,
normalizeCommandBody,
shouldHandleTextCommands,
} from "./commands-registry.js";
@@ -92,4 +93,26 @@ describe("commands registry", () => {
}),
).toBe(true);
});
it("normalizes telegram-style command mentions for the current bot", () => {
expect(
normalizeCommandBody("/help@clawdbot", { botUsername: "clawdbot" }),
).toBe("/help");
expect(
normalizeCommandBody("/help@clawdbot args", {
botUsername: "clawdbot",
}),
).toBe("/help args");
expect(
normalizeCommandBody("/help@clawdbot: args", {
botUsername: "clawdbot",
}),
).toBe("/help args");
});
it("keeps telegram-style command mentions for other bots", () => {
expect(
normalizeCommandBody("/help@otherbot", { botUsername: "clawdbot" }),
).toBe("/help@otherbot");
});
});

View File

@@ -361,7 +361,14 @@ export function buildCommandText(commandName: string, args?: string): string {
return trimmedArgs ? `/${commandName} ${trimmedArgs}` : `/${commandName}`;
}
export function normalizeCommandBody(raw: string): string {
export type CommandNormalizeOptions = {
botUsername?: string;
};
export function normalizeCommandBody(
raw: string,
options?: CommandNormalizeOptions,
): string {
const trimmed = raw.trim();
if (!trimmed.startsWith("/")) return trimmed;
@@ -378,17 +385,26 @@ export function normalizeCommandBody(raw: string): string {
})()
: singleLine;
const lowered = normalized.toLowerCase();
const normalizedBotUsername = options?.botUsername?.trim().toLowerCase();
const mentionMatch = normalizedBotUsername
? normalized.match(/^\/([^\s@]+)@([^\s]+)(.*)$/)
: null;
const commandBody =
mentionMatch && mentionMatch[2].toLowerCase() === normalizedBotUsername
? `/${mentionMatch[1]}${mentionMatch[3] ?? ""}`
: normalized;
const lowered = commandBody.toLowerCase();
const exact = TEXT_ALIAS_MAP.get(lowered);
if (exact) return exact.canonical;
const tokenMatch = normalized.match(/^\/([^\s]+)(?:\s+([\s\S]+))?$/);
if (!tokenMatch) return normalized;
const tokenMatch = commandBody.match(/^\/([^\s]+)(?:\s+([\s\S]+))?$/);
if (!tokenMatch) return commandBody;
const [, token, rest] = tokenMatch;
const tokenKey = `/${token.toLowerCase()}`;
const tokenSpec = TEXT_ALIAS_MAP.get(tokenKey);
if (!tokenSpec) return normalized;
if (rest && !tokenSpec.acceptsArgs) return normalized;
if (!tokenSpec) return commandBody;
if (rest && !tokenSpec.acceptsArgs) return commandBody;
const normalizedRest = rest?.trimStart();
return normalizedRest
? `${tokenSpec.canonical} ${normalizedRest}`

View File

@@ -17,6 +17,7 @@ import { hasControlCommand } from "../auto-reply/command-detection.js";
import {
buildCommandText,
listNativeCommandSpecsForConfig,
normalizeCommandBody,
} from "../auto-reply/commands-registry.js";
import { formatAgentEnvelope } from "../auto-reply/envelope.js";
import {
@@ -557,7 +558,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
!wasMentioned &&
!hasAnyMention &&
commandAuthorized &&
hasControlCommand(msg.text ?? msg.caption ?? "", cfg);
hasControlCommand(msg.text ?? msg.caption ?? "", cfg, { botUsername });
const effectiveWasMentioned = wasMentioned || shouldBypassMention;
const canDetectMention = Boolean(botUsername) || mentionRegexes.length > 0;
if (isGroup && requireMention && canDetectMention) {
@@ -682,10 +683,11 @@ export function createTelegramBot(opts: TelegramBotOptions) {
].filter((entry): entry is string => Boolean(entry));
const groupSystemPrompt =
systemPromptParts.length > 0 ? systemPromptParts.join("\n\n") : undefined;
const commandBody = normalizeCommandBody(rawBody, { botUsername });
const ctxPayload = {
Body: combinedBody,
RawBody: rawBody,
CommandBody: rawBody,
CommandBody: commandBody,
From: isGroup
? buildTelegramGroupFrom(chatId, messageThreadId)
: `telegram:${chatId}`,