mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 07:00:42 +00:00
refactor(telegram): centralize access authorization
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import type { DmPolicy, OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import {
|
||||
expandAllowFromWithAccessGroups,
|
||||
parseAccessGroupAllowFromEntry,
|
||||
} from "openclaw/plugin-sdk/security-runtime";
|
||||
import { isSenderAllowed, normalizeAllowFrom } from "./bot-access.js";
|
||||
import {
|
||||
isSenderAllowed,
|
||||
normalizeAllowFrom,
|
||||
normalizeDmAllowFromWithStore,
|
||||
type NormalizedAllowFrom,
|
||||
} from "./bot-access.js";
|
||||
|
||||
export async function expandTelegramAllowFromWithAccessGroups(params: {
|
||||
cfg?: OpenClawConfig;
|
||||
@@ -34,3 +39,34 @@ export async function expandTelegramAllowFromWithAccessGroups(params: {
|
||||
? expanded.filter((entry) => parseAccessGroupAllowFromEntry(entry) == null)
|
||||
: expanded;
|
||||
}
|
||||
|
||||
export async function resolveTelegramDmAllow(params: {
|
||||
cfg?: OpenClawConfig;
|
||||
allowFrom?: Array<string | number>;
|
||||
groupAllowOverride?: Array<string | number>;
|
||||
storeAllowFrom?: string[];
|
||||
dmPolicy?: DmPolicy;
|
||||
accountId?: string;
|
||||
senderId?: string;
|
||||
}): Promise<{
|
||||
allowFrom?: Array<string | number>;
|
||||
expandedAllowFrom: string[];
|
||||
effectiveAllow: NormalizedAllowFrom;
|
||||
}> {
|
||||
const allowFrom = params.groupAllowOverride ?? params.allowFrom;
|
||||
const expandedAllowFrom = await expandTelegramAllowFromWithAccessGroups({
|
||||
cfg: params.cfg,
|
||||
allowFrom,
|
||||
accountId: params.accountId,
|
||||
senderId: params.senderId,
|
||||
});
|
||||
return {
|
||||
allowFrom,
|
||||
expandedAllowFrom,
|
||||
effectiveAllow: normalizeDmAllowFromWithStore({
|
||||
allowFrom: expandedAllowFrom,
|
||||
storeAllowFrom: params.storeAllowFrom,
|
||||
dmPolicy: params.dmPolicy,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,11 @@ import {
|
||||
mergeDmAllowFromSources,
|
||||
type AllowlistMatch,
|
||||
} from "openclaw/plugin-sdk/allow-from";
|
||||
import type {
|
||||
DmPolicy,
|
||||
TelegramDirectConfig,
|
||||
TelegramGroupConfig,
|
||||
} from "openclaw/plugin-sdk/config-types";
|
||||
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
@@ -67,6 +72,17 @@ export const normalizeDmAllowFromWithStore = (params: {
|
||||
dmPolicy?: string;
|
||||
}): NormalizedAllowFrom => normalizeAllowFrom(mergeDmAllowFromSources(params));
|
||||
|
||||
export function resolveTelegramEffectiveDmPolicy(params: {
|
||||
isGroup: boolean;
|
||||
groupConfig?: TelegramDirectConfig | TelegramGroupConfig;
|
||||
dmPolicy?: DmPolicy;
|
||||
}): DmPolicy {
|
||||
if (!params.isGroup && params.groupConfig && "dmPolicy" in params.groupConfig) {
|
||||
return params.groupConfig.dmPolicy ?? params.dmPolicy ?? "pairing";
|
||||
}
|
||||
return params.dmPolicy ?? "pairing";
|
||||
}
|
||||
|
||||
export const isSenderAllowed = (params: {
|
||||
allow: NormalizedAllowFrom;
|
||||
senderId?: string;
|
||||
|
||||
@@ -7,10 +7,7 @@ import {
|
||||
resolveInboundDebounceMs,
|
||||
} from "openclaw/plugin-sdk/channel-inbound-debounce";
|
||||
import { resolveStoredModelOverride } from "openclaw/plugin-sdk/command-auth";
|
||||
import {
|
||||
resolveCommandAuthorization,
|
||||
resolveCommandAuthorizedFromAuthorizers,
|
||||
} from "openclaw/plugin-sdk/command-auth-native";
|
||||
import { resolveCommandAuthorizedFromAuthorizers } from "openclaw/plugin-sdk/command-auth-native";
|
||||
import { buildCommandsMessagePaginated } from "openclaw/plugin-sdk/command-status";
|
||||
import { replaceConfigFile } from "openclaw/plugin-sdk/config-mutation";
|
||||
import type { DmPolicy, OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
@@ -34,12 +31,16 @@ import {
|
||||
resolveSessionStoreEntry,
|
||||
updateSessionStore,
|
||||
} from "openclaw/plugin-sdk/session-store-runtime";
|
||||
import { expandTelegramAllowFromWithAccessGroups } from "./access-groups.js";
|
||||
import {
|
||||
expandTelegramAllowFromWithAccessGroups,
|
||||
resolveTelegramDmAllow,
|
||||
} from "./access-groups.js";
|
||||
import { resolveTelegramAccount, resolveTelegramMediaRuntimeOptions } from "./accounts.js";
|
||||
import { withTelegramApiErrorLogging } from "./api-logging.js";
|
||||
import {
|
||||
isSenderAllowed,
|
||||
normalizeDmAllowFromWithStore,
|
||||
resolveTelegramEffectiveDmPolicy,
|
||||
type NormalizedAllowFrom,
|
||||
} from "./bot-access.js";
|
||||
import {
|
||||
@@ -71,9 +72,10 @@ import {
|
||||
import { resolveMedia } from "./bot/delivery.resolve-media.js";
|
||||
import {
|
||||
getTelegramTextParts,
|
||||
buildTelegramGroupFrom,
|
||||
buildTelegramGroupPeerId,
|
||||
buildTelegramParentPeer,
|
||||
isTelegramCommandsAllowFromConfigured,
|
||||
resolveTelegramCommandAuthorization,
|
||||
resolveTelegramForumFlag,
|
||||
resolveTelegramForumThreadId,
|
||||
resolveTelegramGroupAllowFromContext,
|
||||
@@ -728,13 +730,11 @@ export const registerTelegramHandlers = ({
|
||||
readChannelAllowFromStore: telegramDeps.readChannelAllowFromStore,
|
||||
resolveTelegramGroupConfig,
|
||||
}));
|
||||
// Use direct config dmPolicy override if available for DMs
|
||||
const effectiveDmPolicy =
|
||||
!params.isGroup &&
|
||||
groupAllowContext.groupConfig &&
|
||||
"dmPolicy" in groupAllowContext.groupConfig
|
||||
? (groupAllowContext.groupConfig.dmPolicy ?? telegramCfg.dmPolicy ?? "pairing")
|
||||
: (telegramCfg.dmPolicy ?? "pairing");
|
||||
const effectiveDmPolicy = resolveTelegramEffectiveDmPolicy({
|
||||
isGroup: params.isGroup,
|
||||
groupConfig: groupAllowContext.groupConfig,
|
||||
dmPolicy: telegramCfg.dmPolicy,
|
||||
});
|
||||
return { dmPolicy: effectiveDmPolicy, ...groupAllowContext };
|
||||
};
|
||||
|
||||
@@ -831,27 +831,15 @@ export const registerTelegramHandlers = ({
|
||||
const { chatId, isGroup, senderId, senderUsername, context, cfg } = params;
|
||||
const useAccessGroups = cfg.commands?.useAccessGroups !== false;
|
||||
const dmAllowFrom = context.groupAllowOverride ?? allowFrom;
|
||||
const commandsAllowFrom = cfg.commands?.allowFrom;
|
||||
const commandsAllowFromConfigured =
|
||||
commandsAllowFrom != null &&
|
||||
typeof commandsAllowFrom === "object" &&
|
||||
(Array.isArray(commandsAllowFrom.telegram) || Array.isArray(commandsAllowFrom["*"]));
|
||||
if (commandsAllowFromConfigured) {
|
||||
return resolveCommandAuthorization({
|
||||
ctx: {
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
OriginatingChannel: "telegram",
|
||||
AccountId: accountId,
|
||||
ChatType: isGroup ? "group" : "direct",
|
||||
From: isGroup
|
||||
? buildTelegramGroupFrom(chatId, context.resolvedThreadId)
|
||||
: `telegram:${chatId}`,
|
||||
SenderId: senderId || undefined,
|
||||
SenderUsername: senderUsername || undefined,
|
||||
},
|
||||
if (isTelegramCommandsAllowFromConfigured(cfg)) {
|
||||
return resolveTelegramCommandAuthorization({
|
||||
cfg,
|
||||
commandAuthorized: false,
|
||||
accountId,
|
||||
chatId,
|
||||
isGroup,
|
||||
resolvedThreadId: context.resolvedThreadId,
|
||||
senderId,
|
||||
senderUsername,
|
||||
}).isAuthorizedSender;
|
||||
}
|
||||
|
||||
@@ -896,7 +884,6 @@ export const registerTelegramHandlers = ({
|
||||
});
|
||||
};
|
||||
|
||||
// Handle emoji reactions to messages.
|
||||
bot.on("message_reaction", async (ctx) => {
|
||||
try {
|
||||
const reaction = ctx.messageReaction;
|
||||
@@ -915,7 +902,6 @@ export const registerTelegramHandlers = ({
|
||||
const isGroup = reaction.chat.type === "group" || reaction.chat.type === "supergroup";
|
||||
const isForum = reaction.chat.is_forum === true;
|
||||
|
||||
// Resolve reaction notification mode (default: "own").
|
||||
const reactionMode = telegramCfg.reactionNotifications ?? "own";
|
||||
if (reactionMode === "off") {
|
||||
return;
|
||||
@@ -963,7 +949,6 @@ export const registerTelegramHandlers = ({
|
||||
}
|
||||
}
|
||||
|
||||
// Detect added reactions.
|
||||
const oldEmojis = new Set(
|
||||
reaction.old_reaction
|
||||
.filter((r): r is ReactionTypeEmoji => r.type === "emoji")
|
||||
@@ -977,7 +962,6 @@ export const registerTelegramHandlers = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// Build sender label.
|
||||
const senderName = user
|
||||
? [user.first_name, user.last_name].filter(Boolean).join(" ").trim() || user.username
|
||||
: undefined;
|
||||
@@ -1001,7 +985,6 @@ export const registerTelegramHandlers = ({
|
||||
: undefined;
|
||||
const peerId = isGroup ? buildTelegramGroupPeerId(chatId, resolvedThreadId) : String(chatId);
|
||||
const parentPeer = buildTelegramParentPeer({ isGroup, resolvedThreadId, chatId });
|
||||
// Fresh config for bindings lookup; other routing inputs are payload-derived.
|
||||
const route = resolveAgentRoute({
|
||||
cfg: telegramDeps.getRuntimeConfig(),
|
||||
channel: "telegram",
|
||||
@@ -1011,7 +994,6 @@ export const registerTelegramHandlers = ({
|
||||
});
|
||||
const sessionKey = route.sessionKey;
|
||||
|
||||
// Enqueue system event for each added reaction.
|
||||
for (const r of addedReactions) {
|
||||
const emoji = r.emoji;
|
||||
const text = `Telegram reaction added: ${emoji} by ${senderLabel} on msg ${messageId}`;
|
||||
@@ -1047,14 +1029,11 @@ export const registerTelegramHandlers = ({
|
||||
oversizeLogMessage,
|
||||
} = params;
|
||||
|
||||
// Text fragment handling - Telegram splits long pastes into multiple inbound messages (~4096 chars).
|
||||
// We buffer “near-limit” messages and append immediately-following parts.
|
||||
const text = typeof msg.text === "string" ? msg.text : undefined;
|
||||
const isCommandLike = (text ?? "").trim().startsWith("/");
|
||||
if (text && !isCommandLike) {
|
||||
const nowMs = Date.now();
|
||||
const senderId = msg.from?.id != null ? String(msg.from.id) : "unknown";
|
||||
// Use resolvedThreadId for forum groups, dmThreadId for DM topics
|
||||
const threadId = resolvedThreadId ?? dmThreadId;
|
||||
const key = `text:${chatId}:${threadId ?? "main"}:${senderId}`;
|
||||
const existing = textFragmentBuffer.get(key);
|
||||
@@ -1087,7 +1066,6 @@ export const registerTelegramHandlers = ({
|
||||
}
|
||||
}
|
||||
|
||||
// Not appendable (or limits exceeded): flush buffered entry first, then continue normally.
|
||||
clearTimeout(existing.timer);
|
||||
textFragmentBuffer.delete(key);
|
||||
textFragmentProcessing = textFragmentProcessing
|
||||
@@ -1111,7 +1089,6 @@ export const registerTelegramHandlers = ({
|
||||
}
|
||||
}
|
||||
|
||||
// Media group handling - buffer multi-image messages
|
||||
const mediaGroupId = msg.media_group_id;
|
||||
if (mediaGroupId) {
|
||||
const existing = mediaGroupBuffer.get(mediaGroupId);
|
||||
@@ -1186,8 +1163,6 @@ export const registerTelegramHandlers = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip sticker-only messages where the sticker was skipped (animated/video)
|
||||
// These have no media and no text content to process.
|
||||
const hasText = Boolean(getTelegramTextParts(msg).text.trim());
|
||||
if (msg.sticker && !media && !hasText) {
|
||||
logVerbose("telegram: skipping sticker-only message (unsupported sticker type)");
|
||||
@@ -1240,7 +1215,6 @@ export const registerTelegramHandlers = ({
|
||||
typeof (ctx as { answerCallbackQuery?: unknown }).answerCallbackQuery === "function"
|
||||
? () => ctx.answerCallbackQuery()
|
||||
: () => bot.api.answerCallbackQuery(callback.id);
|
||||
// Answer immediately to prevent Telegram from retrying while we process
|
||||
await withTelegramApiErrorLogging({
|
||||
operation: "answerCallbackQuery",
|
||||
runtime,
|
||||
@@ -1573,7 +1547,6 @@ export const registerTelegramHandlers = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// Model selection callback handler (mdl_prov, mdl_list_*, mdl_sel_*, mdl_back)
|
||||
const modelCallback = parseModelCallbackData(data);
|
||||
if (modelCallback) {
|
||||
if (
|
||||
@@ -1660,7 +1633,6 @@ export const registerTelegramHandlers = ({
|
||||
const { provider, page } = modelCallback;
|
||||
const modelSet = byProvider.get(provider);
|
||||
if (!modelSet || modelSet.size === 0) {
|
||||
// Provider not found or no models - show providers list
|
||||
const providerInfos: ProviderInfo[] = providers.map((p) => ({
|
||||
id: p,
|
||||
count: byProvider.get(p)?.size ?? 0,
|
||||
@@ -1681,7 +1653,6 @@ export const registerTelegramHandlers = ({
|
||||
const totalPages = calculateTotalPages(models.length, pageSize);
|
||||
const safePage = Math.max(1, Math.min(page, totalPages));
|
||||
|
||||
// Resolve current model from session (prefer overrides)
|
||||
const currentModel = sessionState.model;
|
||||
|
||||
const buttons = buildModelsKeyboard({
|
||||
@@ -1744,7 +1715,6 @@ export const registerTelegramHandlers = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// Directly set model override in session
|
||||
try {
|
||||
// Use the fresh runtimeCfg (loaded at callback entry) so store path
|
||||
// and default-model resolution stay consistent with the next
|
||||
@@ -1782,7 +1752,6 @@ export const registerTelegramHandlers = ({
|
||||
throw new TelegramRetryableCallbackError(err);
|
||||
}
|
||||
|
||||
// Update message to show success with visual feedback
|
||||
const escapeHtml = (text: string) =>
|
||||
text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
const actionText = isDefaultSelection
|
||||
@@ -1832,7 +1801,6 @@ export const registerTelegramHandlers = ({
|
||||
}
|
||||
});
|
||||
|
||||
// Handle group migration to supergroup (chat ID changes)
|
||||
bot.on("message:migrate_to_chat_id", async (ctx) => {
|
||||
try {
|
||||
const msg = ctx.message;
|
||||
@@ -1854,7 +1822,6 @@ export const registerTelegramHandlers = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if old chat ID has config and migrate it
|
||||
const currentConfig = telegramDeps.getRuntimeConfig();
|
||||
const migration = migrateTelegramGroupConfig({
|
||||
cfg: currentConfig,
|
||||
@@ -1927,16 +1894,12 @@ export const registerTelegramHandlers = ({
|
||||
effectiveGroupAllow,
|
||||
hasGroupAllowOverride,
|
||||
} = eventAuthContext;
|
||||
// For DMs, prefer per-DM/topic allowFrom (groupAllowOverride) over account-level allowFrom
|
||||
const dmAllowFrom = groupAllowOverride ?? allowFrom;
|
||||
const expandedDmAllowFrom = await expandTelegramAllowFromWithAccessGroups({
|
||||
const dmAllow = await resolveTelegramDmAllow({
|
||||
cfg,
|
||||
allowFrom: dmAllowFrom,
|
||||
groupAllowOverride,
|
||||
allowFrom,
|
||||
accountId,
|
||||
senderId: event.senderId,
|
||||
});
|
||||
const effectiveDmAllow = normalizeDmAllowFromWithStore({
|
||||
allowFrom: expandedDmAllowFrom,
|
||||
storeAllowFrom,
|
||||
dmPolicy,
|
||||
});
|
||||
@@ -1969,7 +1932,7 @@ export const registerTelegramHandlers = ({
|
||||
dmPolicy,
|
||||
msg: event.msg,
|
||||
chatId: event.chatId,
|
||||
effectiveDmAllow,
|
||||
effectiveDmAllow: dmAllow.effectiveAllow,
|
||||
accountId,
|
||||
bot,
|
||||
logger,
|
||||
@@ -2031,9 +1994,6 @@ export const registerTelegramHandlers = ({
|
||||
});
|
||||
});
|
||||
|
||||
// Handle channel posts — enables bot-to-bot communication via Telegram channels.
|
||||
// Telegram bots cannot see other bot messages in groups, but CAN in channels.
|
||||
// This handler normalizes channel_post updates into the standard message pipeline.
|
||||
bot.on("channel_post", async (ctx) => {
|
||||
const post = ctx.channelPost;
|
||||
if (!post) {
|
||||
|
||||
@@ -8,10 +8,17 @@ import type { TelegramDirectConfig, TelegramGroupConfig } from "openclaw/plugin-
|
||||
import { deriveLastRoutePolicy } from "openclaw/plugin-sdk/routing";
|
||||
import { normalizeAccountId, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { expandTelegramAllowFromWithAccessGroups } from "./access-groups.js";
|
||||
import {
|
||||
expandTelegramAllowFromWithAccessGroups,
|
||||
resolveTelegramDmAllow,
|
||||
} from "./access-groups.js";
|
||||
import { mergeTelegramAccountConfig, resolveDefaultTelegramAccountId } from "./accounts.js";
|
||||
import { withTelegramApiErrorLogging } from "./api-logging.js";
|
||||
import { firstDefined, normalizeAllowFrom, normalizeDmAllowFromWithStore } from "./bot-access.js";
|
||||
import {
|
||||
firstDefined,
|
||||
normalizeAllowFrom,
|
||||
resolveTelegramEffectiveDmPolicy,
|
||||
} from "./bot-access.js";
|
||||
import { resolveTelegramInboundBody } from "./bot-message-context.body.js";
|
||||
import {
|
||||
buildTelegramInboundContextPayload,
|
||||
@@ -217,12 +224,11 @@ export const buildTelegramMessageContext = async ({
|
||||
const telegramGroupConfig = isGroup
|
||||
? (groupConfig as TelegramGroupConfig | undefined)
|
||||
: undefined;
|
||||
// Use direct config dmPolicy override if available for DMs
|
||||
const effectiveDmPolicy =
|
||||
!isGroup && groupConfig && "dmPolicy" in groupConfig
|
||||
? (groupConfig.dmPolicy ?? dmPolicy)
|
||||
: dmPolicy;
|
||||
// Fresh config for bindings lookup; other routing inputs are payload-derived.
|
||||
const effectiveDmPolicy = resolveTelegramEffectiveDmPolicy({
|
||||
isGroup,
|
||||
groupConfig,
|
||||
dmPolicy,
|
||||
});
|
||||
const freshCfg =
|
||||
loadFreshConfig?.() ??
|
||||
(runtime?.getRuntimeConfig ?? (await loadTelegramMessageContextRuntime()).getRuntimeConfig)();
|
||||
@@ -255,16 +261,16 @@ export const buildTelegramMessageContext = async ({
|
||||
});
|
||||
return null;
|
||||
}
|
||||
// Calculate groupAllowOverride first - it's needed for both DM and group allowlist checks
|
||||
const groupAllowOverride = firstDefined(topicConfig?.allowFrom, groupConfig?.allowFrom);
|
||||
// For DMs, prefer per-DM/topic allowFrom (groupAllowOverride) over account-level allowFrom
|
||||
const dmAllowFrom = groupAllowOverride ?? allowFrom;
|
||||
const [expandedDmAllowFrom, expandedGroupAllowFrom] = await Promise.all([
|
||||
expandTelegramAllowFromWithAccessGroups({
|
||||
const [dmAllow, expandedGroupAllowFrom] = await Promise.all([
|
||||
resolveTelegramDmAllow({
|
||||
cfg: freshCfg,
|
||||
allowFrom: dmAllowFrom,
|
||||
groupAllowOverride,
|
||||
allowFrom,
|
||||
accountId: account.accountId,
|
||||
senderId,
|
||||
storeAllowFrom,
|
||||
dmPolicy: effectiveDmPolicy,
|
||||
}),
|
||||
expandTelegramAllowFromWithAccessGroups({
|
||||
cfg: freshCfg,
|
||||
@@ -273,12 +279,6 @@ export const buildTelegramMessageContext = async ({
|
||||
senderId,
|
||||
}),
|
||||
]);
|
||||
const effectiveDmAllow = normalizeDmAllowFromWithStore({
|
||||
allowFrom: expandedDmAllowFrom,
|
||||
storeAllowFrom,
|
||||
dmPolicy: effectiveDmPolicy,
|
||||
});
|
||||
// Group sender checks are explicit and must not inherit DM pairing-store entries.
|
||||
const effectiveGroupAllow = normalizeAllowFrom(expandedGroupAllowFrom);
|
||||
const hasGroupAllowOverride = groupAllowOverride !== undefined;
|
||||
const senderUsername = msg.from?.username ?? "";
|
||||
@@ -353,7 +353,7 @@ export const buildTelegramMessageContext = async ({
|
||||
dmPolicy: effectiveDmPolicy,
|
||||
msg,
|
||||
chatId,
|
||||
effectiveDmAllow,
|
||||
effectiveDmAllow: dmAllow.effectiveAllow,
|
||||
accountId: account.accountId,
|
||||
bot,
|
||||
logger,
|
||||
@@ -417,7 +417,6 @@ export const buildTelegramMessageContext = async ({
|
||||
mainSessionKey: route.mainSessionKey,
|
||||
}),
|
||||
};
|
||||
// Compute requireMention after access checks and final route selection.
|
||||
const activationOverride = resolveGroupActivation({
|
||||
chatId,
|
||||
messageThreadId: resolvedThreadId,
|
||||
@@ -456,7 +455,7 @@ export const buildTelegramMessageContext = async ({
|
||||
routeAgentId: route.agentId,
|
||||
sessionKey,
|
||||
effectiveGroupAllow,
|
||||
effectiveDmAllow,
|
||||
effectiveDmAllow: dmAllow.effectiveAllow,
|
||||
groupConfig,
|
||||
topicConfig,
|
||||
requireMention,
|
||||
@@ -473,7 +472,6 @@ export const buildTelegramMessageContext = async ({
|
||||
return null;
|
||||
}
|
||||
|
||||
// ACK reactions
|
||||
const ackReaction = resolveAckReaction(cfg, route.agentId, {
|
||||
channel: "telegram",
|
||||
accountId: account.accountId,
|
||||
@@ -494,7 +492,6 @@ export const buildTelegramMessageContext = async ({
|
||||
shouldBypassMention: bodyResult.shouldBypassMention,
|
||||
}),
|
||||
);
|
||||
// Status Reactions controller (lifecycle reactions)
|
||||
const statusReactionsConfig = cfg.messages?.statusReactions;
|
||||
const statusReactionsEnabled =
|
||||
statusReactionsConfig?.enabled === true && Boolean(reactionApi) && shouldSendAckReaction;
|
||||
@@ -546,7 +543,6 @@ export const buildTelegramMessageContext = async ({
|
||||
]);
|
||||
}
|
||||
},
|
||||
// Telegram replaces atomically — no removeReaction needed
|
||||
},
|
||||
initialEmoji: ackReaction,
|
||||
emojis: resolvedStatusReactionEmojis ?? undefined,
|
||||
@@ -557,7 +553,6 @@ export const buildTelegramMessageContext = async ({
|
||||
})
|
||||
: null;
|
||||
|
||||
// When status reactions are enabled, setQueued() replaces the simple ack reaction
|
||||
const ackReactionPromise: Promise<boolean> | null = statusReactionController
|
||||
? shouldSendAckReaction
|
||||
? Promise.resolve(statusReactionController.setQueued()).then(
|
||||
@@ -608,7 +603,7 @@ export const buildTelegramMessageContext = async ({
|
||||
: {}),
|
||||
locationData: bodyResult.locationData,
|
||||
options,
|
||||
dmAllowFrom,
|
||||
dmAllowFrom: dmAllow.allowFrom,
|
||||
effectiveGroupAllow,
|
||||
commandAuthorized: bodyResult.commandAuthorized,
|
||||
topicName,
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
} from "openclaw/plugin-sdk/agent-runtime";
|
||||
import { resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-streaming";
|
||||
import {
|
||||
resolveCommandAuthorization,
|
||||
resolveCommandAuthorizedFromAuthorizers,
|
||||
resolveNativeCommandSessionTargets,
|
||||
} from "openclaw/plugin-sdk/command-auth-native";
|
||||
@@ -51,10 +50,10 @@ import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import { expandTelegramAllowFromWithAccessGroups } from "./access-groups.js";
|
||||
import { resolveTelegramDmAllow } from "./access-groups.js";
|
||||
import { resolveTelegramAccount } from "./accounts.js";
|
||||
import { withTelegramApiErrorLogging } from "./api-logging.js";
|
||||
import { isSenderAllowed, normalizeDmAllowFromWithStore } from "./bot-access.js";
|
||||
import { isSenderAllowed, resolveTelegramEffectiveDmPolicy } from "./bot-access.js";
|
||||
import type { TelegramBotDeps } from "./bot-deps.js";
|
||||
import type { TelegramMediaRef } from "./bot-message-context.js";
|
||||
import type { TelegramMessageContextOptions } from "./bot-message-context.types.js";
|
||||
@@ -75,6 +74,8 @@ import {
|
||||
buildSenderName,
|
||||
buildTelegramGroupFrom,
|
||||
extractTelegramForumFlag,
|
||||
isTelegramCommandsAllowFromConfigured,
|
||||
resolveTelegramCommandAuthorization,
|
||||
resolveTelegramForumFlag,
|
||||
resolveTelegramGroupAllowFromContext,
|
||||
resolveTelegramThreadSpec,
|
||||
@@ -343,9 +344,7 @@ async function cleanupTelegramProgressPlaceholder(params: {
|
||||
runtime: params.runtime,
|
||||
fn: () => params.bot.api.deleteMessage(params.chatId, progressMessageId),
|
||||
});
|
||||
} catch {
|
||||
// Best-effort cleanup before fallback or suppression exits.
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function resolveTelegramNativeCommandThreadContext(params: {
|
||||
@@ -514,54 +513,46 @@ async function resolveTelegramCommandAuth(params: {
|
||||
effectiveGroupAllow,
|
||||
hasGroupAllowOverride,
|
||||
} = groupAllowContext;
|
||||
// Use direct config dmPolicy override if available for DMs
|
||||
const effectiveDmPolicy =
|
||||
!isGroup && groupConfig && "dmPolicy" in groupConfig
|
||||
? (groupConfig.dmPolicy ?? telegramCfg.dmPolicy ?? "pairing")
|
||||
: (telegramCfg.dmPolicy ?? "pairing");
|
||||
const effectiveDmPolicy = resolveTelegramEffectiveDmPolicy({
|
||||
isGroup,
|
||||
groupConfig,
|
||||
dmPolicy: telegramCfg.dmPolicy,
|
||||
});
|
||||
const requireTopic =
|
||||
!isGroup && groupConfig && "requireTopic" in groupConfig ? groupConfig.requireTopic : undefined;
|
||||
if (!isGroup && requireTopic === true && dmThreadId == null) {
|
||||
logVerbose(`Blocked telegram command in DM ${chatId}: requireTopic=true but no topic present`);
|
||||
return null;
|
||||
}
|
||||
// For DMs, prefer per-DM/topic allowFrom (groupAllowOverride) over account-level allowFrom
|
||||
const dmAllowFrom = groupAllowOverride ?? allowFrom;
|
||||
const commandsAllowFrom = cfg.commands?.allowFrom;
|
||||
const commandsAllowFromConfigured =
|
||||
commandsAllowFrom != null &&
|
||||
typeof commandsAllowFrom === "object" &&
|
||||
(Array.isArray(commandsAllowFrom.telegram) || Array.isArray(commandsAllowFrom["*"]));
|
||||
const dmAllow = await resolveTelegramDmAllow({
|
||||
cfg,
|
||||
groupAllowOverride,
|
||||
allowFrom,
|
||||
accountId,
|
||||
senderId,
|
||||
storeAllowFrom: isGroup ? [] : storeAllowFrom,
|
||||
dmPolicy: effectiveDmPolicy,
|
||||
});
|
||||
const commandsAllowFromConfigured = isTelegramCommandsAllowFromConfigured(cfg);
|
||||
const commandsAllowFromAccess = commandsAllowFromConfigured
|
||||
? resolveCommandAuthorization({
|
||||
ctx: {
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
OriginatingChannel: "telegram",
|
||||
AccountId: accountId,
|
||||
ChatType: isGroup ? "group" : "direct",
|
||||
From: isGroup ? buildTelegramGroupFrom(chatId, resolvedThreadId) : `telegram:${chatId}`,
|
||||
SenderId: senderId || undefined,
|
||||
SenderUsername: senderUsername || undefined,
|
||||
},
|
||||
? resolveTelegramCommandAuthorization({
|
||||
cfg,
|
||||
// commands.allowFrom is the only auth source when configured.
|
||||
commandAuthorized: false,
|
||||
accountId,
|
||||
chatId,
|
||||
isGroup,
|
||||
resolvedThreadId,
|
||||
senderId,
|
||||
senderUsername,
|
||||
})
|
||||
: null;
|
||||
const ownerAccess = resolveCommandAuthorization({
|
||||
ctx: {
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
OriginatingChannel: "telegram",
|
||||
AccountId: accountId,
|
||||
ChatType: isGroup ? "group" : "direct",
|
||||
From: isGroup ? buildTelegramGroupFrom(chatId, resolvedThreadId) : `telegram:${chatId}`,
|
||||
SenderId: senderId || undefined,
|
||||
SenderUsername: senderUsername || undefined,
|
||||
},
|
||||
const ownerAccess = resolveTelegramCommandAuthorization({
|
||||
cfg,
|
||||
commandAuthorized: false,
|
||||
accountId,
|
||||
chatId,
|
||||
isGroup,
|
||||
resolvedThreadId,
|
||||
senderId,
|
||||
senderUsername,
|
||||
});
|
||||
|
||||
const sendAuthMessage = async (text: string) => {
|
||||
@@ -629,19 +620,8 @@ async function resolveTelegramCommandAuth(params: {
|
||||
}
|
||||
}
|
||||
|
||||
const expandedDmAllowFrom = await expandTelegramAllowFromWithAccessGroups({
|
||||
cfg,
|
||||
allowFrom: dmAllowFrom,
|
||||
accountId,
|
||||
senderId,
|
||||
});
|
||||
const dmAllow = normalizeDmAllowFromWithStore({
|
||||
allowFrom: expandedDmAllowFrom,
|
||||
storeAllowFrom: isGroup ? [] : storeAllowFrom,
|
||||
dmPolicy: effectiveDmPolicy,
|
||||
});
|
||||
const senderAllowed = isSenderAllowed({
|
||||
allow: dmAllow,
|
||||
allow: dmAllow.effectiveAllow,
|
||||
senderId,
|
||||
senderUsername,
|
||||
});
|
||||
@@ -654,7 +634,7 @@ async function resolveTelegramCommandAuth(params: {
|
||||
: resolveCommandAuthorizedFromAuthorizers({
|
||||
useAccessGroups,
|
||||
authorizers: [
|
||||
{ configured: dmAllow.hasEntries, allowed: senderAllowed },
|
||||
{ configured: dmAllow.effectiveAllow.hasEntries, allowed: senderAllowed },
|
||||
...(isGroup
|
||||
? [{ configured: effectiveGroupAllow.hasEntries, allowed: groupSenderAllowed }]
|
||||
: []),
|
||||
@@ -1168,7 +1148,6 @@ export const registerTelegramNativeCommands = ({
|
||||
CommandTargetSessionKey: commandTargetSessionKey,
|
||||
MessageThreadId: threadSpec.id,
|
||||
IsForum: isForum,
|
||||
// Originating context for sub-agent announce routing
|
||||
OriginatingChannel: "telegram" as const,
|
||||
OriginatingTo: originatingTo,
|
||||
});
|
||||
@@ -1348,9 +1327,7 @@ export const registerTelegramNativeCommands = ({
|
||||
if (typeof maybeMessageId === "number") {
|
||||
progressMessageId = maybeMessageId;
|
||||
}
|
||||
} catch {
|
||||
// Fall back to the normal final reply path if the placeholder send fails.
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const sessionFileContext = await resolveTelegramCommandSessionFile({
|
||||
@@ -1430,9 +1407,7 @@ export const registerTelegramNativeCommands = ({
|
||||
groupId: isGroup ? String(chatId) : undefined,
|
||||
});
|
||||
return;
|
||||
} catch {
|
||||
// Fall through to cleanup + normal delivered reply if editing fails.
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
await cleanupTelegramProgressPlaceholder({
|
||||
bot,
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { Chat, Message } from "@grammyjs/types";
|
||||
import { formatLocationText } from "openclaw/plugin-sdk/channel-inbound";
|
||||
import {
|
||||
resolveCommandAuthorization,
|
||||
type CommandAuthorization,
|
||||
} from "openclaw/plugin-sdk/command-auth-native";
|
||||
import type {
|
||||
OpenClawConfig,
|
||||
TelegramAccountConfig,
|
||||
@@ -380,6 +384,42 @@ export function buildTelegramGroupFrom(chatId: number | string, messageThreadId?
|
||||
return `telegram:group:${buildTelegramGroupPeerId(chatId, messageThreadId)}`;
|
||||
}
|
||||
|
||||
export function isTelegramCommandsAllowFromConfigured(cfg: OpenClawConfig): boolean {
|
||||
const commandsAllowFrom = cfg.commands?.allowFrom;
|
||||
return (
|
||||
commandsAllowFrom != null &&
|
||||
typeof commandsAllowFrom === "object" &&
|
||||
(Array.isArray(commandsAllowFrom.telegram) || Array.isArray(commandsAllowFrom["*"]))
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveTelegramCommandAuthorization(params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
chatId: number;
|
||||
isGroup: boolean;
|
||||
resolvedThreadId?: number;
|
||||
senderId?: string;
|
||||
senderUsername?: string;
|
||||
}): CommandAuthorization {
|
||||
return resolveCommandAuthorization({
|
||||
ctx: {
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
OriginatingChannel: "telegram",
|
||||
AccountId: params.accountId,
|
||||
ChatType: params.isGroup ? "group" : "direct",
|
||||
From: params.isGroup
|
||||
? buildTelegramGroupFrom(params.chatId, params.resolvedThreadId)
|
||||
: `telegram:${params.chatId}`,
|
||||
SenderId: params.senderId || undefined,
|
||||
SenderUsername: params.senderUsername || undefined,
|
||||
},
|
||||
cfg: params.cfg,
|
||||
commandAuthorized: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Build parentPeer for forum topic binding inheritance.
|
||||
* When a message comes from a forum topic, the peer ID includes the topic suffix
|
||||
|
||||
Reference in New Issue
Block a user