Merge pull request #729 from clawdbot/fix/telegram-command-mentions
fix: normalize telegram command mentions
This commit is contained in:
@@ -71,4 +71,17 @@ describe("control command parsing", () => {
|
|||||||
expect(hasControlCommand("prefix /send on")).toBe(false);
|
expect(hasControlCommand("prefix /send on")).toBe(false);
|
||||||
expect(hasControlCommand("/send on")).toBe(true);
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { ClawdbotConfig } from "../config/types.js";
|
import type { ClawdbotConfig } from "../config/types.js";
|
||||||
import {
|
import {
|
||||||
|
type CommandNormalizeOptions,
|
||||||
listChatCommands,
|
listChatCommands,
|
||||||
listChatCommandsForConfig,
|
listChatCommandsForConfig,
|
||||||
normalizeCommandBody,
|
normalizeCommandBody,
|
||||||
@@ -8,11 +9,12 @@ import {
|
|||||||
export function hasControlCommand(
|
export function hasControlCommand(
|
||||||
text?: string,
|
text?: string,
|
||||||
cfg?: ClawdbotConfig,
|
cfg?: ClawdbotConfig,
|
||||||
|
options?: CommandNormalizeOptions,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (!text) return false;
|
if (!text) return false;
|
||||||
const trimmed = text.trim();
|
const trimmed = text.trim();
|
||||||
if (!trimmed) return false;
|
if (!trimmed) return false;
|
||||||
const normalizedBody = normalizeCommandBody(trimmed);
|
const normalizedBody = normalizeCommandBody(trimmed, options);
|
||||||
if (!normalizedBody) return false;
|
if (!normalizedBody) return false;
|
||||||
const lowered = normalizedBody.toLowerCase();
|
const lowered = normalizedBody.toLowerCase();
|
||||||
const commands = cfg ? listChatCommandsForConfig(cfg) : listChatCommands();
|
const commands = cfg ? listChatCommandsForConfig(cfg) : listChatCommands();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
listChatCommandsForConfig,
|
listChatCommandsForConfig,
|
||||||
listNativeCommandSpecs,
|
listNativeCommandSpecs,
|
||||||
listNativeCommandSpecsForConfig,
|
listNativeCommandSpecsForConfig,
|
||||||
|
normalizeCommandBody,
|
||||||
shouldHandleTextCommands,
|
shouldHandleTextCommands,
|
||||||
} from "./commands-registry.js";
|
} from "./commands-registry.js";
|
||||||
|
|
||||||
@@ -92,4 +93,26 @@ describe("commands registry", () => {
|
|||||||
}),
|
}),
|
||||||
).toBe(true);
|
).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");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -361,7 +361,14 @@ export function buildCommandText(commandName: string, args?: string): string {
|
|||||||
return trimmedArgs ? `/${commandName} ${trimmedArgs}` : `/${commandName}`;
|
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();
|
const trimmed = raw.trim();
|
||||||
if (!trimmed.startsWith("/")) return trimmed;
|
if (!trimmed.startsWith("/")) return trimmed;
|
||||||
|
|
||||||
@@ -378,17 +385,26 @@ export function normalizeCommandBody(raw: string): string {
|
|||||||
})()
|
})()
|
||||||
: singleLine;
|
: 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);
|
const exact = TEXT_ALIAS_MAP.get(lowered);
|
||||||
if (exact) return exact.canonical;
|
if (exact) return exact.canonical;
|
||||||
|
|
||||||
const tokenMatch = normalized.match(/^\/([^\s]+)(?:\s+([\s\S]+))?$/);
|
const tokenMatch = commandBody.match(/^\/([^\s]+)(?:\s+([\s\S]+))?$/);
|
||||||
if (!tokenMatch) return normalized;
|
if (!tokenMatch) return commandBody;
|
||||||
const [, token, rest] = tokenMatch;
|
const [, token, rest] = tokenMatch;
|
||||||
const tokenKey = `/${token.toLowerCase()}`;
|
const tokenKey = `/${token.toLowerCase()}`;
|
||||||
const tokenSpec = TEXT_ALIAS_MAP.get(tokenKey);
|
const tokenSpec = TEXT_ALIAS_MAP.get(tokenKey);
|
||||||
if (!tokenSpec) return normalized;
|
if (!tokenSpec) return commandBody;
|
||||||
if (rest && !tokenSpec.acceptsArgs) return normalized;
|
if (rest && !tokenSpec.acceptsArgs) return commandBody;
|
||||||
const normalizedRest = rest?.trimStart();
|
const normalizedRest = rest?.trimStart();
|
||||||
return normalizedRest
|
return normalizedRest
|
||||||
? `${tokenSpec.canonical} ${normalizedRest}`
|
? `${tokenSpec.canonical} ${normalizedRest}`
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { hasControlCommand } from "../auto-reply/command-detection.js";
|
|||||||
import {
|
import {
|
||||||
buildCommandText,
|
buildCommandText,
|
||||||
listNativeCommandSpecsForConfig,
|
listNativeCommandSpecsForConfig,
|
||||||
|
normalizeCommandBody,
|
||||||
} from "../auto-reply/commands-registry.js";
|
} from "../auto-reply/commands-registry.js";
|
||||||
import { formatAgentEnvelope } from "../auto-reply/envelope.js";
|
import { formatAgentEnvelope } from "../auto-reply/envelope.js";
|
||||||
import {
|
import {
|
||||||
@@ -557,7 +558,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
|||||||
!wasMentioned &&
|
!wasMentioned &&
|
||||||
!hasAnyMention &&
|
!hasAnyMention &&
|
||||||
commandAuthorized &&
|
commandAuthorized &&
|
||||||
hasControlCommand(msg.text ?? msg.caption ?? "", cfg);
|
hasControlCommand(msg.text ?? msg.caption ?? "", cfg, { botUsername });
|
||||||
const effectiveWasMentioned = wasMentioned || shouldBypassMention;
|
const effectiveWasMentioned = wasMentioned || shouldBypassMention;
|
||||||
const canDetectMention = Boolean(botUsername) || mentionRegexes.length > 0;
|
const canDetectMention = Boolean(botUsername) || mentionRegexes.length > 0;
|
||||||
if (isGroup && requireMention && canDetectMention) {
|
if (isGroup && requireMention && canDetectMention) {
|
||||||
@@ -682,10 +683,11 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
|||||||
].filter((entry): entry is string => Boolean(entry));
|
].filter((entry): entry is string => Boolean(entry));
|
||||||
const groupSystemPrompt =
|
const groupSystemPrompt =
|
||||||
systemPromptParts.length > 0 ? systemPromptParts.join("\n\n") : undefined;
|
systemPromptParts.length > 0 ? systemPromptParts.join("\n\n") : undefined;
|
||||||
|
const commandBody = normalizeCommandBody(rawBody, { botUsername });
|
||||||
const ctxPayload = {
|
const ctxPayload = {
|
||||||
Body: combinedBody,
|
Body: combinedBody,
|
||||||
RawBody: rawBody,
|
RawBody: rawBody,
|
||||||
CommandBody: rawBody,
|
CommandBody: commandBody,
|
||||||
From: isGroup
|
From: isGroup
|
||||||
? buildTelegramGroupFrom(chatId, messageThreadId)
|
? buildTelegramGroupFrom(chatId, messageThreadId)
|
||||||
: `telegram:${chatId}`,
|
: `telegram:${chatId}`,
|
||||||
|
|||||||
Reference in New Issue
Block a user