registerTelegramNativeCommands() calls listSkillCommandsForAgents() without passing agentIds, causing ALL agents' skill commands to be registered on EVERY Telegram bot. When multiple agents share skill names (e.g. two agents both have a "butler" skill), the shared `used` Set in listSkillCommandsForAgents causes de-duplication suffixes (_2, _3) and all commands appear on every bot regardless of agent binding. This fix uses the existing resolveAgentRoute() (already imported) to find the bound agent for the current Telegram accountId, then passes that agentId to listSkillCommandsForAgents(). The function already accepts an optional agentIds parameter — it just wasn't wired from the Telegram registration path. Before: All agents' skill commands registered on every Telegram bot, causing /butler_2, /housekeeper_2 dedup suffixes and potential BOT_COMMANDS_TOO_MUCH errors when total exceeds 100. After: Each Telegram bot only registers skill commands for its own bound agent. No cross-agent dedup, no command limit overflow. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
624 lines
22 KiB
TypeScript
624 lines
22 KiB
TypeScript
import type { Bot, Context } from "grammy";
|
|
|
|
import { resolveEffectiveMessagesConfig } from "../agents/identity.js";
|
|
import { resolveChunkMode } from "../auto-reply/chunk.js";
|
|
import {
|
|
buildCommandTextFromArgs,
|
|
findCommandByNativeName,
|
|
listNativeCommandSpecs,
|
|
listNativeCommandSpecsForConfig,
|
|
parseCommandArgs,
|
|
resolveCommandArgMenu,
|
|
} from "../auto-reply/commands-registry.js";
|
|
import { listSkillCommandsForAgents } from "../auto-reply/skill-commands.js";
|
|
import type { CommandArgs } from "../auto-reply/commands-registry.js";
|
|
import { resolveTelegramCustomCommands } from "../config/telegram-custom-commands.js";
|
|
import { dispatchReplyWithBufferedBlockDispatcher } from "../auto-reply/reply/provider-dispatcher.js";
|
|
import { finalizeInboundContext } from "../auto-reply/reply/inbound-context.js";
|
|
import { danger, logVerbose } from "../globals.js";
|
|
import { resolveMarkdownTableMode } from "../config/markdown-tables.js";
|
|
import { withTelegramApiErrorLogging } from "./api-logging.js";
|
|
import {
|
|
normalizeTelegramCommandName,
|
|
TELEGRAM_COMMAND_NAME_PATTERN,
|
|
} from "../config/telegram-custom-commands.js";
|
|
import { resolveAgentRoute } from "../routing/resolve-route.js";
|
|
import { resolveThreadSessionKeys } from "../routing/session-key.js";
|
|
import { resolveCommandAuthorizedFromAuthorizers } from "../channels/command-gating.js";
|
|
import {
|
|
executePluginCommand,
|
|
getPluginCommandSpecs,
|
|
matchPluginCommand,
|
|
} from "../plugins/commands.js";
|
|
import type { ChannelGroupPolicy } from "../config/group-policy.js";
|
|
import type {
|
|
ReplyToMode,
|
|
TelegramAccountConfig,
|
|
TelegramGroupConfig,
|
|
TelegramTopicConfig,
|
|
} from "../config/types.js";
|
|
import type { OpenClawConfig } from "../config/config.js";
|
|
import type { RuntimeEnv } from "../runtime.js";
|
|
import { deliverReplies } from "./bot/delivery.js";
|
|
import { buildInlineKeyboard } from "./send.js";
|
|
import {
|
|
buildSenderName,
|
|
buildTelegramGroupFrom,
|
|
buildTelegramGroupPeerId,
|
|
resolveTelegramForumThreadId,
|
|
} from "./bot/helpers.js";
|
|
import { firstDefined, isSenderAllowed, normalizeAllowFromWithStore } from "./bot-access.js";
|
|
import { readTelegramAllowFromStore } from "./pairing-store.js";
|
|
|
|
const EMPTY_RESPONSE_FALLBACK = "No response generated. Please try again.";
|
|
|
|
type TelegramNativeCommandContext = Context & { match?: string };
|
|
|
|
type TelegramCommandAuthResult = {
|
|
chatId: number;
|
|
isGroup: boolean;
|
|
isForum: boolean;
|
|
resolvedThreadId?: number;
|
|
senderId: string;
|
|
senderUsername: string;
|
|
groupConfig?: TelegramGroupConfig;
|
|
topicConfig?: TelegramTopicConfig;
|
|
commandAuthorized: boolean;
|
|
};
|
|
|
|
type RegisterTelegramNativeCommandsParams = {
|
|
bot: Bot;
|
|
cfg: OpenClawConfig;
|
|
runtime: RuntimeEnv;
|
|
accountId: string;
|
|
telegramCfg: TelegramAccountConfig;
|
|
allowFrom?: Array<string | number>;
|
|
groupAllowFrom?: Array<string | number>;
|
|
replyToMode: ReplyToMode;
|
|
textLimit: number;
|
|
useAccessGroups: boolean;
|
|
nativeEnabled: boolean;
|
|
nativeSkillsEnabled: boolean;
|
|
nativeDisabledExplicit: boolean;
|
|
resolveGroupPolicy: (chatId: string | number) => ChannelGroupPolicy;
|
|
resolveTelegramGroupConfig: (
|
|
chatId: string | number,
|
|
messageThreadId?: number,
|
|
) => { groupConfig?: TelegramGroupConfig; topicConfig?: TelegramTopicConfig };
|
|
shouldSkipUpdate: (ctx: unknown) => boolean;
|
|
opts: { token: string };
|
|
};
|
|
|
|
async function resolveTelegramCommandAuth(params: {
|
|
msg: NonNullable<TelegramNativeCommandContext["message"]>;
|
|
bot: Bot;
|
|
cfg: OpenClawConfig;
|
|
telegramCfg: TelegramAccountConfig;
|
|
allowFrom?: Array<string | number>;
|
|
groupAllowFrom?: Array<string | number>;
|
|
useAccessGroups: boolean;
|
|
resolveGroupPolicy: (chatId: string | number) => ChannelGroupPolicy;
|
|
resolveTelegramGroupConfig: (
|
|
chatId: string | number,
|
|
messageThreadId?: number,
|
|
) => { groupConfig?: TelegramGroupConfig; topicConfig?: TelegramTopicConfig };
|
|
requireAuth: boolean;
|
|
}): Promise<TelegramCommandAuthResult | null> {
|
|
const {
|
|
msg,
|
|
bot,
|
|
cfg,
|
|
telegramCfg,
|
|
allowFrom,
|
|
groupAllowFrom,
|
|
useAccessGroups,
|
|
resolveGroupPolicy,
|
|
resolveTelegramGroupConfig,
|
|
requireAuth,
|
|
} = params;
|
|
const chatId = msg.chat.id;
|
|
const isGroup = msg.chat.type === "group" || msg.chat.type === "supergroup";
|
|
const messageThreadId = (msg as { message_thread_id?: number }).message_thread_id;
|
|
const isForum = (msg.chat as { is_forum?: boolean }).is_forum === true;
|
|
const resolvedThreadId = resolveTelegramForumThreadId({
|
|
isForum,
|
|
messageThreadId,
|
|
});
|
|
const storeAllowFrom = await readTelegramAllowFromStore().catch(() => []);
|
|
const { groupConfig, topicConfig } = resolveTelegramGroupConfig(chatId, resolvedThreadId);
|
|
const groupAllowOverride = firstDefined(topicConfig?.allowFrom, groupConfig?.allowFrom);
|
|
const effectiveGroupAllow = normalizeAllowFromWithStore({
|
|
allowFrom: groupAllowOverride ?? groupAllowFrom,
|
|
storeAllowFrom,
|
|
});
|
|
const hasGroupAllowOverride = typeof groupAllowOverride !== "undefined";
|
|
const senderIdRaw = msg.from?.id;
|
|
const senderId = senderIdRaw ? String(senderIdRaw) : "";
|
|
const senderUsername = msg.from?.username ?? "";
|
|
|
|
if (isGroup && groupConfig?.enabled === false) {
|
|
await withTelegramApiErrorLogging({
|
|
operation: "sendMessage",
|
|
fn: () => bot.api.sendMessage(chatId, "This group is disabled."),
|
|
});
|
|
return null;
|
|
}
|
|
if (isGroup && topicConfig?.enabled === false) {
|
|
await withTelegramApiErrorLogging({
|
|
operation: "sendMessage",
|
|
fn: () => bot.api.sendMessage(chatId, "This topic is disabled."),
|
|
});
|
|
return null;
|
|
}
|
|
if (requireAuth && isGroup && hasGroupAllowOverride) {
|
|
if (
|
|
senderIdRaw == null ||
|
|
!isSenderAllowed({
|
|
allow: effectiveGroupAllow,
|
|
senderId: String(senderIdRaw),
|
|
senderUsername,
|
|
})
|
|
) {
|
|
await withTelegramApiErrorLogging({
|
|
operation: "sendMessage",
|
|
fn: () => bot.api.sendMessage(chatId, "You are not authorized to use this command."),
|
|
});
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if (isGroup && useAccessGroups) {
|
|
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
|
const groupPolicy = telegramCfg.groupPolicy ?? defaultGroupPolicy ?? "open";
|
|
if (groupPolicy === "disabled") {
|
|
await withTelegramApiErrorLogging({
|
|
operation: "sendMessage",
|
|
fn: () => bot.api.sendMessage(chatId, "Telegram group commands are disabled."),
|
|
});
|
|
return null;
|
|
}
|
|
if (groupPolicy === "allowlist" && requireAuth) {
|
|
if (
|
|
senderIdRaw == null ||
|
|
!isSenderAllowed({
|
|
allow: effectiveGroupAllow,
|
|
senderId: String(senderIdRaw),
|
|
senderUsername,
|
|
})
|
|
) {
|
|
await withTelegramApiErrorLogging({
|
|
operation: "sendMessage",
|
|
fn: () => bot.api.sendMessage(chatId, "You are not authorized to use this command."),
|
|
});
|
|
return null;
|
|
}
|
|
}
|
|
const groupAllowlist = resolveGroupPolicy(chatId);
|
|
if (groupAllowlist.allowlistEnabled && !groupAllowlist.allowed) {
|
|
await withTelegramApiErrorLogging({
|
|
operation: "sendMessage",
|
|
fn: () => bot.api.sendMessage(chatId, "This group is not allowed."),
|
|
});
|
|
return null;
|
|
}
|
|
}
|
|
|
|
const dmAllow = normalizeAllowFromWithStore({
|
|
allowFrom: allowFrom,
|
|
storeAllowFrom,
|
|
});
|
|
const senderAllowed = isSenderAllowed({
|
|
allow: dmAllow,
|
|
senderId,
|
|
senderUsername,
|
|
});
|
|
const commandAuthorized = resolveCommandAuthorizedFromAuthorizers({
|
|
useAccessGroups,
|
|
authorizers: [{ configured: dmAllow.hasEntries, allowed: senderAllowed }],
|
|
modeWhenAccessGroupsOff: "configured",
|
|
});
|
|
if (requireAuth && !commandAuthorized) {
|
|
await withTelegramApiErrorLogging({
|
|
operation: "sendMessage",
|
|
fn: () => bot.api.sendMessage(chatId, "You are not authorized to use this command."),
|
|
});
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
chatId,
|
|
isGroup,
|
|
isForum,
|
|
resolvedThreadId,
|
|
senderId,
|
|
senderUsername,
|
|
groupConfig,
|
|
topicConfig,
|
|
commandAuthorized,
|
|
};
|
|
}
|
|
|
|
export const registerTelegramNativeCommands = ({
|
|
bot,
|
|
cfg,
|
|
runtime,
|
|
accountId,
|
|
telegramCfg,
|
|
allowFrom,
|
|
groupAllowFrom,
|
|
replyToMode,
|
|
textLimit,
|
|
useAccessGroups,
|
|
nativeEnabled,
|
|
nativeSkillsEnabled,
|
|
nativeDisabledExplicit,
|
|
resolveGroupPolicy,
|
|
resolveTelegramGroupConfig,
|
|
shouldSkipUpdate,
|
|
opts,
|
|
}: RegisterTelegramNativeCommandsParams) => {
|
|
const boundRoute = resolveAgentRoute({ cfg, channel: "telegram", accountId });
|
|
const boundAgentIds = boundRoute?.agentId ? [boundRoute.agentId] : undefined;
|
|
const skillCommands =
|
|
nativeEnabled && nativeSkillsEnabled
|
|
? listSkillCommandsForAgents({ cfg, agentIds: boundAgentIds })
|
|
: [];
|
|
const nativeCommands = nativeEnabled
|
|
? listNativeCommandSpecsForConfig(cfg, { skillCommands, provider: "telegram" })
|
|
: [];
|
|
const reservedCommands = new Set(
|
|
listNativeCommandSpecs().map((command) => command.name.toLowerCase()),
|
|
);
|
|
for (const command of skillCommands) {
|
|
reservedCommands.add(command.name.toLowerCase());
|
|
}
|
|
const customResolution = resolveTelegramCustomCommands({
|
|
commands: telegramCfg.customCommands,
|
|
reservedCommands,
|
|
});
|
|
for (const issue of customResolution.issues) {
|
|
runtime.error?.(danger(issue.message));
|
|
}
|
|
const customCommands = customResolution.commands;
|
|
const pluginCommandSpecs = getPluginCommandSpecs();
|
|
const pluginCommands: Array<{ command: string; description: string }> = [];
|
|
const existingCommands = new Set(
|
|
[
|
|
...nativeCommands.map((command) => command.name),
|
|
...customCommands.map((command) => command.command),
|
|
].map((command) => command.toLowerCase()),
|
|
);
|
|
const pluginCommandNames = new Set<string>();
|
|
for (const spec of pluginCommandSpecs) {
|
|
const normalized = normalizeTelegramCommandName(spec.name);
|
|
if (!normalized || !TELEGRAM_COMMAND_NAME_PATTERN.test(normalized)) {
|
|
runtime.error?.(
|
|
danger(
|
|
`Plugin command "/${spec.name}" is invalid for Telegram (use a-z, 0-9, underscore; max 32 chars).`,
|
|
),
|
|
);
|
|
continue;
|
|
}
|
|
const description = spec.description.trim();
|
|
if (!description) {
|
|
runtime.error?.(danger(`Plugin command "/${normalized}" is missing a description.`));
|
|
continue;
|
|
}
|
|
if (existingCommands.has(normalized)) {
|
|
runtime.error?.(
|
|
danger(`Plugin command "/${normalized}" conflicts with an existing Telegram command.`),
|
|
);
|
|
continue;
|
|
}
|
|
if (pluginCommandNames.has(normalized)) {
|
|
runtime.error?.(danger(`Plugin command "/${normalized}" is duplicated.`));
|
|
continue;
|
|
}
|
|
pluginCommandNames.add(normalized);
|
|
existingCommands.add(normalized);
|
|
pluginCommands.push({ command: normalized, description });
|
|
}
|
|
const allCommands: Array<{ command: string; description: string }> = [
|
|
...nativeCommands.map((command) => ({
|
|
command: command.name,
|
|
description: command.description,
|
|
})),
|
|
...pluginCommands,
|
|
...customCommands,
|
|
];
|
|
|
|
if (allCommands.length > 0) {
|
|
withTelegramApiErrorLogging({
|
|
operation: "setMyCommands",
|
|
runtime,
|
|
fn: () => bot.api.setMyCommands(allCommands),
|
|
}).catch(() => {});
|
|
|
|
if (typeof (bot as unknown as { command?: unknown }).command !== "function") {
|
|
logVerbose("telegram: bot.command unavailable; skipping native handlers");
|
|
} else {
|
|
for (const command of nativeCommands) {
|
|
bot.command(command.name, async (ctx: TelegramNativeCommandContext) => {
|
|
const msg = ctx.message;
|
|
if (!msg) return;
|
|
if (shouldSkipUpdate(ctx)) return;
|
|
const auth = await resolveTelegramCommandAuth({
|
|
msg,
|
|
bot,
|
|
cfg,
|
|
telegramCfg,
|
|
allowFrom,
|
|
groupAllowFrom,
|
|
useAccessGroups,
|
|
resolveGroupPolicy,
|
|
resolveTelegramGroupConfig,
|
|
requireAuth: true,
|
|
});
|
|
if (!auth) return;
|
|
const {
|
|
chatId,
|
|
isGroup,
|
|
isForum,
|
|
resolvedThreadId,
|
|
senderId,
|
|
senderUsername,
|
|
groupConfig,
|
|
topicConfig,
|
|
commandAuthorized,
|
|
} = auth;
|
|
const messageThreadId = (msg as { message_thread_id?: number }).message_thread_id;
|
|
const threadIdForSend = isGroup ? resolvedThreadId : messageThreadId;
|
|
|
|
const commandDefinition = findCommandByNativeName(command.name, "telegram");
|
|
const rawText = ctx.match?.trim() ?? "";
|
|
const commandArgs = commandDefinition
|
|
? parseCommandArgs(commandDefinition, rawText)
|
|
: rawText
|
|
? ({ raw: rawText } satisfies CommandArgs)
|
|
: undefined;
|
|
const prompt = commandDefinition
|
|
? buildCommandTextFromArgs(commandDefinition, commandArgs)
|
|
: rawText
|
|
? `/${command.name} ${rawText}`
|
|
: `/${command.name}`;
|
|
const menu = commandDefinition
|
|
? resolveCommandArgMenu({
|
|
command: commandDefinition,
|
|
args: commandArgs,
|
|
cfg,
|
|
})
|
|
: null;
|
|
if (menu && commandDefinition) {
|
|
const title =
|
|
menu.title ??
|
|
`Choose ${menu.arg.description || menu.arg.name} for /${commandDefinition.nativeName ?? commandDefinition.key}.`;
|
|
const rows: Array<Array<{ text: string; callback_data: string }>> = [];
|
|
for (let i = 0; i < menu.choices.length; i += 2) {
|
|
const slice = menu.choices.slice(i, i + 2);
|
|
rows.push(
|
|
slice.map((choice) => {
|
|
const args: CommandArgs = {
|
|
values: { [menu.arg.name]: choice.value },
|
|
};
|
|
return {
|
|
text: choice.label,
|
|
callback_data: buildCommandTextFromArgs(commandDefinition, args),
|
|
};
|
|
}),
|
|
);
|
|
}
|
|
const replyMarkup = buildInlineKeyboard(rows);
|
|
await withTelegramApiErrorLogging({
|
|
operation: "sendMessage",
|
|
runtime,
|
|
fn: () =>
|
|
bot.api.sendMessage(chatId, title, {
|
|
...(replyMarkup ? { reply_markup: replyMarkup } : {}),
|
|
...(threadIdForSend != null ? { message_thread_id: threadIdForSend } : {}),
|
|
}),
|
|
});
|
|
return;
|
|
}
|
|
const route = resolveAgentRoute({
|
|
cfg,
|
|
channel: "telegram",
|
|
accountId,
|
|
peer: {
|
|
kind: isGroup ? "group" : "dm",
|
|
id: isGroup ? buildTelegramGroupPeerId(chatId, resolvedThreadId) : String(chatId),
|
|
},
|
|
});
|
|
const baseSessionKey = route.sessionKey;
|
|
// DMs: use raw messageThreadId for thread sessions (not resolvedThreadId which is for forums)
|
|
const dmThreadId = !isGroup ? messageThreadId : undefined;
|
|
const threadKeys =
|
|
dmThreadId != null
|
|
? resolveThreadSessionKeys({ baseSessionKey, threadId: String(dmThreadId) })
|
|
: null;
|
|
const sessionKey = threadKeys?.sessionKey ?? baseSessionKey;
|
|
const tableMode = resolveMarkdownTableMode({
|
|
cfg,
|
|
channel: "telegram",
|
|
accountId: route.accountId,
|
|
});
|
|
const skillFilter = firstDefined(topicConfig?.skills, groupConfig?.skills);
|
|
const systemPromptParts = [
|
|
groupConfig?.systemPrompt?.trim() || null,
|
|
topicConfig?.systemPrompt?.trim() || null,
|
|
].filter((entry): entry is string => Boolean(entry));
|
|
const groupSystemPrompt =
|
|
systemPromptParts.length > 0 ? systemPromptParts.join("\n\n") : undefined;
|
|
const conversationLabel = isGroup
|
|
? msg.chat.title
|
|
? `${msg.chat.title} id:${chatId}`
|
|
: `group:${chatId}`
|
|
: (buildSenderName(msg) ?? String(senderId || chatId));
|
|
const ctxPayload = finalizeInboundContext({
|
|
Body: prompt,
|
|
RawBody: prompt,
|
|
CommandBody: prompt,
|
|
CommandArgs: commandArgs,
|
|
From: isGroup ? buildTelegramGroupFrom(chatId, resolvedThreadId) : `telegram:${chatId}`,
|
|
To: `slash:${senderId || chatId}`,
|
|
ChatType: isGroup ? "group" : "direct",
|
|
ConversationLabel: conversationLabel,
|
|
GroupSubject: isGroup ? (msg.chat.title ?? undefined) : undefined,
|
|
GroupSystemPrompt: isGroup ? groupSystemPrompt : undefined,
|
|
SenderName: buildSenderName(msg),
|
|
SenderId: senderId || undefined,
|
|
SenderUsername: senderUsername || undefined,
|
|
Surface: "telegram",
|
|
MessageSid: String(msg.message_id),
|
|
Timestamp: msg.date ? msg.date * 1000 : undefined,
|
|
WasMentioned: true,
|
|
CommandAuthorized: commandAuthorized,
|
|
CommandSource: "native" as const,
|
|
SessionKey: `telegram:slash:${senderId || chatId}`,
|
|
AccountId: route.accountId,
|
|
CommandTargetSessionKey: sessionKey,
|
|
MessageThreadId: threadIdForSend,
|
|
IsForum: isForum,
|
|
// Originating context for sub-agent announce routing
|
|
OriginatingChannel: "telegram" as const,
|
|
OriginatingTo: `telegram:${chatId}`,
|
|
});
|
|
|
|
const disableBlockStreaming =
|
|
typeof telegramCfg.blockStreaming === "boolean"
|
|
? !telegramCfg.blockStreaming
|
|
: undefined;
|
|
const chunkMode = resolveChunkMode(cfg, "telegram", route.accountId);
|
|
|
|
const deliveryState = {
|
|
delivered: false,
|
|
skippedNonSilent: 0,
|
|
};
|
|
|
|
await dispatchReplyWithBufferedBlockDispatcher({
|
|
ctx: ctxPayload,
|
|
cfg,
|
|
dispatcherOptions: {
|
|
responsePrefix: resolveEffectiveMessagesConfig(cfg, route.agentId).responsePrefix,
|
|
deliver: async (payload, _info) => {
|
|
const result = await deliverReplies({
|
|
replies: [payload],
|
|
chatId: String(chatId),
|
|
token: opts.token,
|
|
runtime,
|
|
bot,
|
|
replyToMode,
|
|
textLimit,
|
|
messageThreadId: threadIdForSend,
|
|
tableMode,
|
|
chunkMode,
|
|
linkPreview: telegramCfg.linkPreview,
|
|
});
|
|
if (result.delivered) {
|
|
deliveryState.delivered = true;
|
|
}
|
|
},
|
|
onSkip: (_payload, info) => {
|
|
if (info.reason !== "silent") deliveryState.skippedNonSilent += 1;
|
|
},
|
|
onError: (err, info) => {
|
|
runtime.error?.(danger(`telegram slash ${info.kind} reply failed: ${String(err)}`));
|
|
},
|
|
},
|
|
replyOptions: {
|
|
skillFilter,
|
|
disableBlockStreaming,
|
|
},
|
|
});
|
|
if (!deliveryState.delivered && deliveryState.skippedNonSilent > 0) {
|
|
await deliverReplies({
|
|
replies: [{ text: EMPTY_RESPONSE_FALLBACK }],
|
|
chatId: String(chatId),
|
|
token: opts.token,
|
|
runtime,
|
|
bot,
|
|
replyToMode,
|
|
textLimit,
|
|
messageThreadId: threadIdForSend,
|
|
tableMode,
|
|
chunkMode,
|
|
linkPreview: telegramCfg.linkPreview,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
for (const pluginCommand of pluginCommands) {
|
|
bot.command(pluginCommand.command, async (ctx: TelegramNativeCommandContext) => {
|
|
const msg = ctx.message;
|
|
if (!msg) return;
|
|
if (shouldSkipUpdate(ctx)) return;
|
|
const chatId = msg.chat.id;
|
|
const rawText = ctx.match?.trim() ?? "";
|
|
const commandBody = `/${pluginCommand.command}${rawText ? ` ${rawText}` : ""}`;
|
|
const match = matchPluginCommand(commandBody);
|
|
if (!match) {
|
|
await withTelegramApiErrorLogging({
|
|
operation: "sendMessage",
|
|
runtime,
|
|
fn: () => bot.api.sendMessage(chatId, "Command not found."),
|
|
});
|
|
return;
|
|
}
|
|
const auth = await resolveTelegramCommandAuth({
|
|
msg,
|
|
bot,
|
|
cfg,
|
|
telegramCfg,
|
|
allowFrom,
|
|
groupAllowFrom,
|
|
useAccessGroups,
|
|
resolveGroupPolicy,
|
|
resolveTelegramGroupConfig,
|
|
requireAuth: match.command.requireAuth !== false,
|
|
});
|
|
if (!auth) return;
|
|
const { resolvedThreadId, senderId, commandAuthorized, isGroup } = auth;
|
|
const messageThreadId = (msg as { message_thread_id?: number }).message_thread_id;
|
|
const threadIdForSend = isGroup ? resolvedThreadId : messageThreadId;
|
|
|
|
const result = await executePluginCommand({
|
|
command: match.command,
|
|
args: match.args,
|
|
senderId,
|
|
channel: "telegram",
|
|
isAuthorizedSender: commandAuthorized,
|
|
commandBody,
|
|
config: cfg,
|
|
});
|
|
const tableMode = resolveMarkdownTableMode({
|
|
cfg,
|
|
channel: "telegram",
|
|
accountId,
|
|
});
|
|
const chunkMode = resolveChunkMode(cfg, "telegram", accountId);
|
|
|
|
await deliverReplies({
|
|
replies: [result],
|
|
chatId: String(chatId),
|
|
token: opts.token,
|
|
runtime,
|
|
bot,
|
|
replyToMode,
|
|
textLimit,
|
|
messageThreadId: threadIdForSend,
|
|
tableMode,
|
|
chunkMode,
|
|
linkPreview: telegramCfg.linkPreview,
|
|
});
|
|
});
|
|
}
|
|
}
|
|
} else if (nativeDisabledExplicit) {
|
|
withTelegramApiErrorLogging({
|
|
operation: "setMyCommands",
|
|
runtime,
|
|
fn: () => bot.api.setMyCommands([]),
|
|
}).catch(() => {});
|
|
}
|
|
};
|