Files
openclaw/extensions/telegram/src/group-access.ts
2026-03-16 21:16:32 -07:00

206 lines
7.6 KiB
TypeScript

import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import type { ChannelGroupPolicy } from "openclaw/plugin-sdk/config-runtime";
import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/config-runtime";
import type {
TelegramAccountConfig,
TelegramDirectConfig,
TelegramGroupConfig,
TelegramTopicConfig,
} from "openclaw/plugin-sdk/config-runtime";
import { evaluateMatchedGroupAccessForPolicy } from "openclaw/plugin-sdk/group-access";
import { isSenderAllowed, type NormalizedAllowFrom } from "./bot-access.js";
import { firstDefined } from "./bot-access.js";
export type TelegramGroupBaseBlockReason =
| "group-disabled"
| "topic-disabled"
| "group-override-unauthorized";
export type TelegramGroupBaseAccessResult =
| { allowed: true }
| { allowed: false; reason: TelegramGroupBaseBlockReason };
function isGroupAllowOverrideAuthorized(params: {
effectiveGroupAllow: NormalizedAllowFrom;
senderId?: string;
senderUsername?: string;
requireSenderForAllowOverride: boolean;
}): boolean {
if (!params.effectiveGroupAllow.hasEntries) {
return false;
}
const senderId = params.senderId ?? "";
if (params.requireSenderForAllowOverride && !senderId) {
return false;
}
return isSenderAllowed({
allow: params.effectiveGroupAllow,
senderId,
senderUsername: params.senderUsername ?? "",
});
}
export const evaluateTelegramGroupBaseAccess = (params: {
isGroup: boolean;
groupConfig?: TelegramGroupConfig | TelegramDirectConfig;
topicConfig?: TelegramTopicConfig;
hasGroupAllowOverride: boolean;
effectiveGroupAllow: NormalizedAllowFrom;
senderId?: string;
senderUsername?: string;
enforceAllowOverride: boolean;
requireSenderForAllowOverride: boolean;
}): TelegramGroupBaseAccessResult => {
// Check enabled flags for both groups and DMs
if (params.groupConfig?.enabled === false) {
return { allowed: false, reason: "group-disabled" };
}
if (params.topicConfig?.enabled === false) {
return { allowed: false, reason: "topic-disabled" };
}
if (!params.isGroup) {
// For DMs, check allowFrom override if present
if (params.enforceAllowOverride && params.hasGroupAllowOverride) {
if (
!isGroupAllowOverrideAuthorized({
effectiveGroupAllow: params.effectiveGroupAllow,
senderId: params.senderId,
senderUsername: params.senderUsername,
requireSenderForAllowOverride: params.requireSenderForAllowOverride,
})
) {
return { allowed: false, reason: "group-override-unauthorized" };
}
}
return { allowed: true };
}
if (!params.enforceAllowOverride || !params.hasGroupAllowOverride) {
return { allowed: true };
}
if (
!isGroupAllowOverrideAuthorized({
effectiveGroupAllow: params.effectiveGroupAllow,
senderId: params.senderId,
senderUsername: params.senderUsername,
requireSenderForAllowOverride: params.requireSenderForAllowOverride,
})
) {
return { allowed: false, reason: "group-override-unauthorized" };
}
return { allowed: true };
};
export type TelegramGroupPolicyBlockReason =
| "group-policy-disabled"
| "group-policy-allowlist-no-sender"
| "group-policy-allowlist-empty"
| "group-policy-allowlist-unauthorized"
| "group-chat-not-allowed";
export type TelegramGroupPolicyAccessResult =
| { allowed: true; groupPolicy: "open" | "disabled" | "allowlist" }
| {
allowed: false;
reason: TelegramGroupPolicyBlockReason;
groupPolicy: "open" | "disabled" | "allowlist";
};
export const resolveTelegramRuntimeGroupPolicy = (params: {
providerConfigPresent: boolean;
groupPolicy?: TelegramAccountConfig["groupPolicy"];
defaultGroupPolicy?: TelegramAccountConfig["groupPolicy"];
}) =>
resolveOpenProviderRuntimeGroupPolicy({
providerConfigPresent: params.providerConfigPresent,
groupPolicy: params.groupPolicy,
defaultGroupPolicy: params.defaultGroupPolicy,
});
export const evaluateTelegramGroupPolicyAccess = (params: {
isGroup: boolean;
chatId: string | number;
cfg: OpenClawConfig;
telegramCfg: TelegramAccountConfig;
topicConfig?: TelegramTopicConfig;
groupConfig?: TelegramGroupConfig;
effectiveGroupAllow: NormalizedAllowFrom;
senderId?: string;
senderUsername?: string;
resolveGroupPolicy: (chatId: string | number) => ChannelGroupPolicy;
enforcePolicy: boolean;
useTopicAndGroupOverrides: boolean;
enforceAllowlistAuthorization: boolean;
allowEmptyAllowlistEntries: boolean;
requireSenderForAllowlistAuthorization: boolean;
checkChatAllowlist: boolean;
}): TelegramGroupPolicyAccessResult => {
const { groupPolicy: runtimeFallbackPolicy } = resolveTelegramRuntimeGroupPolicy({
providerConfigPresent: params.cfg.channels?.telegram !== undefined,
groupPolicy: params.telegramCfg.groupPolicy,
defaultGroupPolicy: params.cfg.channels?.defaults?.groupPolicy,
});
const fallbackPolicy =
firstDefined(params.telegramCfg.groupPolicy, params.cfg.channels?.defaults?.groupPolicy) ??
runtimeFallbackPolicy;
const groupPolicy = params.useTopicAndGroupOverrides
? (firstDefined(
params.topicConfig?.groupPolicy,
params.groupConfig?.groupPolicy,
params.telegramCfg.groupPolicy,
params.cfg.channels?.defaults?.groupPolicy,
) ?? runtimeFallbackPolicy)
: fallbackPolicy;
if (!params.isGroup || !params.enforcePolicy) {
return { allowed: true, groupPolicy };
}
if (groupPolicy === "disabled") {
return { allowed: false, reason: "group-policy-disabled", groupPolicy };
}
// Check chat-level allowlist first so that groups explicitly listed in the
// `groups` config are not blocked by the sender-level "empty allowlist" guard.
let chatExplicitlyAllowed = false;
if (params.checkChatAllowlist) {
const groupAllowlist = params.resolveGroupPolicy(params.chatId);
if (groupAllowlist.allowlistEnabled && !groupAllowlist.allowed) {
return { allowed: false, reason: "group-chat-not-allowed", groupPolicy };
}
// The chat is explicitly allowed when it has a dedicated entry in the groups
// config (groupConfig is set). A wildcard ("*") match alone does not count
// because it only enables the group — sender-level filtering still applies.
if (groupAllowlist.allowlistEnabled && groupAllowlist.allowed && groupAllowlist.groupConfig) {
chatExplicitlyAllowed = true;
}
}
if (groupPolicy === "allowlist" && params.enforceAllowlistAuthorization) {
const senderId = params.senderId ?? "";
const senderAuthorization = evaluateMatchedGroupAccessForPolicy({
groupPolicy,
requireMatchInput: params.requireSenderForAllowlistAuthorization,
hasMatchInput: Boolean(senderId),
allowlistConfigured:
chatExplicitlyAllowed ||
params.allowEmptyAllowlistEntries ||
params.effectiveGroupAllow.hasEntries,
allowlistMatched:
(chatExplicitlyAllowed && !params.effectiveGroupAllow.hasEntries) ||
isSenderAllowed({
allow: params.effectiveGroupAllow,
senderId,
senderUsername: params.senderUsername ?? "",
}),
});
if (!senderAuthorization.allowed && senderAuthorization.reason === "missing_match_input") {
return { allowed: false, reason: "group-policy-allowlist-no-sender", groupPolicy };
}
if (!senderAuthorization.allowed && senderAuthorization.reason === "empty_allowlist") {
return { allowed: false, reason: "group-policy-allowlist-empty", groupPolicy };
}
if (!senderAuthorization.allowed && senderAuthorization.reason === "not_allowlisted") {
return { allowed: false, reason: "group-policy-allowlist-unauthorized", groupPolicy };
}
}
return { allowed: true, groupPolicy };
};