mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 07:47:39 +00:00
refactor(telegram): simplify send/dispatch/target handling (#17819)
Merged via /review-pr -> /prepare-pr -> /merge-pr.
Prepared head SHA: fcb7aeeca3
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
This commit is contained in:
@@ -55,6 +55,10 @@ import {
|
||||
resolveTelegramGroupAllowFromContext,
|
||||
resolveTelegramThreadSpec,
|
||||
} from "./bot/helpers.js";
|
||||
import {
|
||||
evaluateTelegramGroupBaseAccess,
|
||||
evaluateTelegramGroupPolicyAccess,
|
||||
} from "./group-access.js";
|
||||
import { buildInlineKeyboard } from "./send.js";
|
||||
|
||||
const EMPTY_RESPONSE_FALLBACK = "No response generated. Please try again.";
|
||||
@@ -172,68 +176,71 @@ async function resolveTelegramCommandAuth(params: {
|
||||
effectiveGroupAllow,
|
||||
hasGroupAllowOverride,
|
||||
} = groupAllowContext;
|
||||
const senderIdRaw = msg.from?.id;
|
||||
const senderId = senderIdRaw ? String(senderIdRaw) : "";
|
||||
const senderId = msg.from?.id ? String(msg.from.id) : "";
|
||||
const senderUsername = msg.from?.username ?? "";
|
||||
|
||||
const isGroupSenderAllowed = () =>
|
||||
senderIdRaw != null &&
|
||||
isSenderAllowed({
|
||||
allow: effectiveGroupAllow,
|
||||
senderId: String(senderIdRaw),
|
||||
senderUsername,
|
||||
});
|
||||
|
||||
const rejectNotAuthorized = async () => {
|
||||
const sendAuthMessage = async (text: string) => {
|
||||
await withTelegramApiErrorLogging({
|
||||
operation: "sendMessage",
|
||||
fn: () => bot.api.sendMessage(chatId, "You are not authorized to use this command."),
|
||||
fn: () => bot.api.sendMessage(chatId, text),
|
||||
});
|
||||
return null;
|
||||
};
|
||||
const rejectNotAuthorized = async () => {
|
||||
return await sendAuthMessage("You are not authorized to use this command.");
|
||||
};
|
||||
|
||||
if (isGroup && groupConfig?.enabled === false) {
|
||||
await withTelegramApiErrorLogging({
|
||||
operation: "sendMessage",
|
||||
fn: () => bot.api.sendMessage(chatId, "This group is disabled."),
|
||||
});
|
||||
return null;
|
||||
const baseAccess = evaluateTelegramGroupBaseAccess({
|
||||
isGroup,
|
||||
groupConfig,
|
||||
topicConfig,
|
||||
hasGroupAllowOverride,
|
||||
effectiveGroupAllow,
|
||||
senderId,
|
||||
senderUsername,
|
||||
enforceAllowOverride: requireAuth,
|
||||
requireSenderForAllowOverride: true,
|
||||
});
|
||||
if (!baseAccess.allowed) {
|
||||
if (baseAccess.reason === "group-disabled") {
|
||||
return await sendAuthMessage("This group is disabled.");
|
||||
}
|
||||
if (baseAccess.reason === "topic-disabled") {
|
||||
return await sendAuthMessage("This topic is disabled.");
|
||||
}
|
||||
return await rejectNotAuthorized();
|
||||
}
|
||||
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 (!isGroupSenderAllowed()) {
|
||||
|
||||
const policyAccess = evaluateTelegramGroupPolicyAccess({
|
||||
isGroup,
|
||||
chatId,
|
||||
cfg,
|
||||
telegramCfg,
|
||||
topicConfig,
|
||||
groupConfig,
|
||||
effectiveGroupAllow,
|
||||
senderId,
|
||||
senderUsername,
|
||||
resolveGroupPolicy,
|
||||
enforcePolicy: useAccessGroups,
|
||||
useTopicAndGroupOverrides: false,
|
||||
enforceAllowlistAuthorization: requireAuth,
|
||||
allowEmptyAllowlistEntries: true,
|
||||
requireSenderForAllowlistAuthorization: true,
|
||||
checkChatAllowlist: useAccessGroups,
|
||||
});
|
||||
if (!policyAccess.allowed) {
|
||||
if (policyAccess.reason === "group-policy-disabled") {
|
||||
return await sendAuthMessage("Telegram group commands are disabled.");
|
||||
}
|
||||
if (
|
||||
policyAccess.reason === "group-policy-allowlist-no-sender" ||
|
||||
policyAccess.reason === "group-policy-allowlist-unauthorized"
|
||||
) {
|
||||
return await rejectNotAuthorized();
|
||||
}
|
||||
}
|
||||
|
||||
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 (!isGroupSenderAllowed()) {
|
||||
return await rejectNotAuthorized();
|
||||
}
|
||||
}
|
||||
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;
|
||||
if (policyAccess.reason === "group-chat-not-allowed") {
|
||||
return await sendAuthMessage("This group is not allowed.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,11 +259,7 @@ async function resolveTelegramCommandAuth(params: {
|
||||
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 await rejectNotAuthorized();
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -357,6 +360,60 @@ export const registerTelegramNativeCommands = ({
|
||||
// Keep hidden commands callable by registering handlers for the full catalog.
|
||||
syncTelegramMenuCommands({ bot, runtime, commandsToRegister });
|
||||
|
||||
const resolveCommandRuntimeContext = (params: {
|
||||
msg: NonNullable<TelegramNativeCommandContext["message"]>;
|
||||
isGroup: boolean;
|
||||
isForum: boolean;
|
||||
resolvedThreadId?: number;
|
||||
}) => {
|
||||
const { msg, isGroup, isForum, resolvedThreadId } = params;
|
||||
const chatId = msg.chat.id;
|
||||
const messageThreadId = (msg as { message_thread_id?: number }).message_thread_id;
|
||||
const threadSpec = resolveTelegramThreadSpec({
|
||||
isGroup,
|
||||
isForum,
|
||||
messageThreadId,
|
||||
});
|
||||
const parentPeer = buildTelegramParentPeer({ isGroup, resolvedThreadId, chatId });
|
||||
const route = resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "telegram",
|
||||
accountId,
|
||||
peer: {
|
||||
kind: isGroup ? "group" : "direct",
|
||||
id: isGroup ? buildTelegramGroupPeerId(chatId, resolvedThreadId) : String(chatId),
|
||||
},
|
||||
parentPeer,
|
||||
});
|
||||
const mediaLocalRoots = getAgentScopedMediaLocalRoots(cfg, route.agentId);
|
||||
const tableMode = resolveMarkdownTableMode({
|
||||
cfg,
|
||||
channel: "telegram",
|
||||
accountId: route.accountId,
|
||||
});
|
||||
const chunkMode = resolveChunkMode(cfg, "telegram", route.accountId);
|
||||
return { chatId, threadSpec, route, mediaLocalRoots, tableMode, chunkMode };
|
||||
};
|
||||
const buildCommandDeliveryBaseOptions = (params: {
|
||||
chatId: string | number;
|
||||
mediaLocalRoots?: readonly string[];
|
||||
threadSpec: ReturnType<typeof resolveTelegramThreadSpec>;
|
||||
tableMode: ReturnType<typeof resolveMarkdownTableMode>;
|
||||
chunkMode: ReturnType<typeof resolveChunkMode>;
|
||||
}) => ({
|
||||
chatId: String(params.chatId),
|
||||
token: opts.token,
|
||||
runtime,
|
||||
bot,
|
||||
mediaLocalRoots: params.mediaLocalRoots,
|
||||
replyToMode,
|
||||
textLimit,
|
||||
thread: params.threadSpec,
|
||||
tableMode: params.tableMode,
|
||||
chunkMode: params.chunkMode,
|
||||
linkPreview: telegramCfg.linkPreview,
|
||||
});
|
||||
|
||||
if (commandsToRegister.length > 0 || pluginCatalog.commands.length > 0) {
|
||||
if (typeof (bot as unknown as { command?: unknown }).command !== "function") {
|
||||
logVerbose("telegram: bot.command unavailable; skipping native handlers");
|
||||
@@ -397,11 +454,19 @@ export const registerTelegramNativeCommands = ({
|
||||
topicConfig,
|
||||
commandAuthorized,
|
||||
} = auth;
|
||||
const messageThreadId = (msg as { message_thread_id?: number }).message_thread_id;
|
||||
const threadSpec = resolveTelegramThreadSpec({
|
||||
isGroup,
|
||||
isForum,
|
||||
messageThreadId,
|
||||
const { threadSpec, route, mediaLocalRoots, tableMode, chunkMode } =
|
||||
resolveCommandRuntimeContext({
|
||||
msg,
|
||||
isGroup,
|
||||
isForum,
|
||||
resolvedThreadId,
|
||||
});
|
||||
const deliveryBaseOptions = buildCommandDeliveryBaseOptions({
|
||||
chatId,
|
||||
mediaLocalRoots,
|
||||
threadSpec,
|
||||
tableMode,
|
||||
chunkMode,
|
||||
});
|
||||
const threadParams = buildTelegramThreadParams(threadSpec) ?? {};
|
||||
|
||||
@@ -455,18 +520,6 @@ export const registerTelegramNativeCommands = ({
|
||||
});
|
||||
return;
|
||||
}
|
||||
const parentPeer = buildTelegramParentPeer({ isGroup, resolvedThreadId, chatId });
|
||||
const route = resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "telegram",
|
||||
accountId,
|
||||
peer: {
|
||||
kind: isGroup ? "group" : "direct",
|
||||
id: isGroup ? buildTelegramGroupPeerId(chatId, resolvedThreadId) : String(chatId),
|
||||
},
|
||||
parentPeer,
|
||||
});
|
||||
const mediaLocalRoots = getAgentScopedMediaLocalRoots(cfg, route.agentId);
|
||||
const baseSessionKey = route.sessionKey;
|
||||
// DMs: use raw messageThreadId for thread sessions (not resolvedThreadId which is for forums)
|
||||
const dmThreadId = threadSpec.scope === "dm" ? threadSpec.id : undefined;
|
||||
@@ -478,11 +531,6 @@ export const registerTelegramNativeCommands = ({
|
||||
})
|
||||
: 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,
|
||||
@@ -530,7 +578,6 @@ export const registerTelegramNativeCommands = ({
|
||||
typeof telegramCfg.blockStreaming === "boolean"
|
||||
? !telegramCfg.blockStreaming
|
||||
: undefined;
|
||||
const chunkMode = resolveChunkMode(cfg, "telegram", route.accountId);
|
||||
|
||||
const deliveryState = {
|
||||
delivered: false,
|
||||
@@ -552,17 +599,7 @@ export const registerTelegramNativeCommands = ({
|
||||
deliver: async (payload, _info) => {
|
||||
const result = await deliverReplies({
|
||||
replies: [payload],
|
||||
chatId: String(chatId),
|
||||
token: opts.token,
|
||||
runtime,
|
||||
bot,
|
||||
mediaLocalRoots,
|
||||
replyToMode,
|
||||
textLimit,
|
||||
thread: threadSpec,
|
||||
tableMode,
|
||||
chunkMode,
|
||||
linkPreview: telegramCfg.linkPreview,
|
||||
...deliveryBaseOptions,
|
||||
});
|
||||
if (result.delivered) {
|
||||
deliveryState.delivered = true;
|
||||
@@ -586,17 +623,7 @@ export const registerTelegramNativeCommands = ({
|
||||
if (!deliveryState.delivered && deliveryState.skippedNonSilent > 0) {
|
||||
await deliverReplies({
|
||||
replies: [{ text: EMPTY_RESPONSE_FALLBACK }],
|
||||
chatId: String(chatId),
|
||||
token: opts.token,
|
||||
runtime,
|
||||
bot,
|
||||
mediaLocalRoots,
|
||||
replyToMode,
|
||||
textLimit,
|
||||
thread: threadSpec,
|
||||
tableMode,
|
||||
chunkMode,
|
||||
linkPreview: telegramCfg.linkPreview,
|
||||
...deliveryBaseOptions,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -640,24 +667,20 @@ export const registerTelegramNativeCommands = ({
|
||||
return;
|
||||
}
|
||||
const { senderId, commandAuthorized, isGroup, isForum, resolvedThreadId } = auth;
|
||||
const messageThreadId = (msg as { message_thread_id?: number }).message_thread_id;
|
||||
const threadSpec = resolveTelegramThreadSpec({
|
||||
isGroup,
|
||||
isForum,
|
||||
messageThreadId,
|
||||
const { threadSpec, mediaLocalRoots, tableMode, chunkMode } =
|
||||
resolveCommandRuntimeContext({
|
||||
msg,
|
||||
isGroup,
|
||||
isForum,
|
||||
resolvedThreadId,
|
||||
});
|
||||
const deliveryBaseOptions = buildCommandDeliveryBaseOptions({
|
||||
chatId,
|
||||
mediaLocalRoots,
|
||||
threadSpec,
|
||||
tableMode,
|
||||
chunkMode,
|
||||
});
|
||||
const parentPeer = buildTelegramParentPeer({ isGroup, resolvedThreadId, chatId });
|
||||
const route = resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "telegram",
|
||||
accountId,
|
||||
peer: {
|
||||
kind: isGroup ? "group" : "direct",
|
||||
id: isGroup ? buildTelegramGroupPeerId(chatId, resolvedThreadId) : String(chatId),
|
||||
},
|
||||
parentPeer,
|
||||
});
|
||||
const mediaLocalRoots = getAgentScopedMediaLocalRoots(cfg, route.agentId);
|
||||
const from = isGroup
|
||||
? buildTelegramGroupFrom(chatId, threadSpec.id)
|
||||
: `telegram:${chatId}`;
|
||||
@@ -676,26 +699,10 @@ export const registerTelegramNativeCommands = ({
|
||||
accountId,
|
||||
messageThreadId: threadSpec.id,
|
||||
});
|
||||
const tableMode = resolveMarkdownTableMode({
|
||||
cfg,
|
||||
channel: "telegram",
|
||||
accountId: route.accountId,
|
||||
});
|
||||
const chunkMode = resolveChunkMode(cfg, "telegram", route.accountId);
|
||||
|
||||
await deliverReplies({
|
||||
replies: [result],
|
||||
chatId: String(chatId),
|
||||
token: opts.token,
|
||||
runtime,
|
||||
bot,
|
||||
mediaLocalRoots,
|
||||
replyToMode,
|
||||
textLimit,
|
||||
thread: threadSpec,
|
||||
tableMode,
|
||||
chunkMode,
|
||||
linkPreview: telegramCfg.linkPreview,
|
||||
...deliveryBaseOptions,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user