Telegram user: add group tool policy + DM open validation

This commit is contained in:
Muhammed Mukhthar CM 2026-01-24 14:42:11 +00:00
parent 63929bd70c
commit 39a4627550
3 changed files with 83 additions and 6 deletions

View File

@ -8,9 +8,11 @@ import {
formatPairingApproveHint, formatPairingApproveHint,
normalizeAccountId, normalizeAccountId,
setAccountEnabledInConfigSection, setAccountEnabledInConfigSection,
type ChannelGroupContext,
type ChannelPlugin, type ChannelPlugin,
type ChannelSetupInput, type ChannelSetupInput,
type ClawdbotConfig, type ClawdbotConfig,
type GroupToolPolicyConfig,
} from "clawdbot/plugin-sdk"; } from "clawdbot/plugin-sdk";
import { import {
@ -51,6 +53,36 @@ type TelegramUserSetupInput = ChannelSetupInput & {
apiHash?: string; apiHash?: string;
}; };
function normalizeTelegramUserGroupKey(raw?: string | null): string | undefined {
if (!raw) return undefined;
const trimmed = raw.trim();
if (!trimmed) return undefined;
const withoutPrefix = trimmed.replace(/^telegram-user:group:/i, "");
const [base] = withoutPrefix.split(/:topic:/i);
const normalized = base?.trim();
return normalized ? normalized : undefined;
}
function resolveTelegramUserGroupToolPolicy(
params: ChannelGroupContext,
): GroupToolPolicyConfig | undefined {
const account = resolveTelegramUserAccount({
cfg: params.cfg as CoreConfig,
accountId: params.accountId,
});
const groups = account.config.groups ?? {};
const groupId = normalizeTelegramUserGroupKey(params.groupId);
const groupChannel = normalizeTelegramUserGroupKey(params.groupChannel);
const candidates = [groupId, groupChannel, "*"].filter(
(value): value is string => Boolean(value),
);
for (const key of candidates) {
const entry = groups[key];
if (entry?.tools) return entry.tools;
}
return undefined;
}
const isSessionLinked = async (accountId: string): Promise<boolean> => { const isSessionLinked = async (accountId: string): Promise<boolean> => {
const sessionPath = resolveTelegramUserSessionPath(accountId); const sessionPath = resolveTelegramUserSessionPath(accountId);
return fs.existsSync(sessionPath); return fs.existsSync(sessionPath);
@ -142,6 +174,21 @@ export const telegramUserPlugin: ChannelPlugin<ResolvedTelegramUserAccount> = {
raw.replace(/^(telegram-user|telegram|tg):/i, "").toLowerCase(), raw.replace(/^(telegram-user|telegram|tg):/i, "").toLowerCase(),
}; };
}, },
collectWarnings: ({ account, cfg }) => {
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
if (groupPolicy !== "open") return [];
const groupAllowlistConfigured =
account.config.groups && Object.keys(account.config.groups).length > 0;
if (groupAllowlistConfigured) {
return [
`- Telegram user groups: groupPolicy="open" allows any member in allowed groups to trigger (mention-gated). Set channels.telegram-user.groupPolicy="allowlist" + channels.telegram-user.groupAllowFrom to restrict senders.`,
];
}
return [
`- Telegram user groups: groupPolicy="open" with no channels.telegram-user.groups allowlist; any group can add + ping (mention-gated). Set channels.telegram-user.groupPolicy="allowlist" + channels.telegram-user.groupAllowFrom or configure channels.telegram-user.groups.`,
];
},
}, },
groups: { groups: {
resolveRequireMention: ({ cfg, groupId, accountId }) => resolveRequireMention: ({ cfg, groupId, accountId }) =>
@ -151,6 +198,7 @@ export const telegramUserPlugin: ChannelPlugin<ResolvedTelegramUserAccount> = {
groupId, groupId,
accountId, accountId,
}), }),
resolveToolPolicy: resolveTelegramUserGroupToolPolicy,
}, },
threading: { threading: {
resolveReplyToMode: ({ cfg }) => cfg.channels?.["telegram-user"]?.replyToMode ?? "first", resolveReplyToMode: ({ cfg }) => cfg.channels?.["telegram-user"]?.replyToMode ?? "first",

View File

@ -1,5 +1,12 @@
import { z } from "zod"; import { z } from "zod";
import {
DmPolicySchema,
GroupPolicySchema,
ToolPolicySchema,
requireOpenAllowFrom,
} from "clawdbot/plugin-sdk";
const allowFromEntry = z.union([z.string(), z.number()]); const allowFromEntry = z.union([z.string(), z.number()]);
const TelegramUserTopicSchema = z const TelegramUserTopicSchema = z
@ -16,6 +23,7 @@ const TelegramUserGroupSchema = z
.object({ .object({
requireMention: z.boolean().optional(), requireMention: z.boolean().optional(),
skills: z.array(z.string()).optional(), skills: z.array(z.string()).optional(),
tools: ToolPolicySchema,
topics: z.record(z.string(), TelegramUserTopicSchema.optional()).optional(), topics: z.record(z.string(), TelegramUserTopicSchema.optional()).optional(),
enabled: z.boolean().optional(), enabled: z.boolean().optional(),
allowFrom: z.array(allowFromEntry).optional(), allowFrom: z.array(allowFromEntry).optional(),
@ -23,23 +31,43 @@ const TelegramUserGroupSchema = z
}) })
.strict(); .strict();
const TelegramUserAccountSchema = z const TelegramUserAccountSchemaBase = z
.object({ .object({
name: z.string().optional(), name: z.string().optional(),
enabled: z.boolean().optional(), enabled: z.boolean().optional(),
apiId: z.number().int().positive().optional(), apiId: z.number().int().positive().optional(),
apiHash: z.string().optional(), apiHash: z.string().optional(),
dmPolicy: z.enum(["pairing", "allowlist", "open", "disabled"]).optional(), dmPolicy: DmPolicySchema.optional().default("pairing"),
allowFrom: z.array(allowFromEntry).optional(), allowFrom: z.array(allowFromEntry).optional(),
replyToMode: z.enum(["off", "first", "all"]).optional(), replyToMode: z.enum(["off", "first", "all"]).optional(),
textChunkLimit: z.number().int().positive().optional(), textChunkLimit: z.number().int().positive().optional(),
mediaMaxMb: z.number().positive().optional(), mediaMaxMb: z.number().positive().optional(),
groupAllowFrom: z.array(allowFromEntry).optional(), groupAllowFrom: z.array(allowFromEntry).optional(),
groupPolicy: z.enum(["open", "allowlist", "disabled"]).optional(), groupPolicy: GroupPolicySchema.optional().default("allowlist"),
groups: z.record(z.string(), TelegramUserGroupSchema.optional()).optional(), groups: z.record(z.string(), TelegramUserGroupSchema.optional()).optional(),
}) })
.strict(); .strict();
export const TelegramUserConfigSchema = TelegramUserAccountSchema.extend({ const TelegramUserAccountSchema = TelegramUserAccountSchemaBase.superRefine((value, ctx) => {
accounts: z.record(z.string(), TelegramUserAccountSchema.optional()).optional(), requireOpenAllowFrom({
policy: value.dmPolicy,
allowFrom: value.allowFrom,
ctx,
path: ["allowFrom"],
message:
'channels.telegram-user.dmPolicy="open" requires channels.telegram-user.allowFrom to include "*"',
});
});
export const TelegramUserConfigSchema = TelegramUserAccountSchemaBase.extend({
accounts: z.record(z.string(), TelegramUserAccountSchema.optional()).optional(),
}).superRefine((value, ctx) => {
requireOpenAllowFrom({
policy: value.dmPolicy,
allowFrom: value.allowFrom,
ctx,
path: ["allowFrom"],
message:
'channels.telegram-user.dmPolicy="open" requires channels.telegram-user.allowFrom to include "*"',
});
}); });

View File

@ -1,4 +1,4 @@
import type { DmPolicy, GroupPolicy } from "clawdbot/plugin-sdk"; import type { DmPolicy, GroupPolicy, GroupToolPolicyConfig } from "clawdbot/plugin-sdk";
export type TelegramUserTopicConfig = { export type TelegramUserTopicConfig = {
requireMention?: boolean; requireMention?: boolean;
@ -11,6 +11,7 @@ export type TelegramUserTopicConfig = {
export type TelegramUserGroupConfig = { export type TelegramUserGroupConfig = {
requireMention?: boolean; requireMention?: boolean;
skills?: string[]; skills?: string[];
tools?: GroupToolPolicyConfig;
topics?: Record<string, TelegramUserTopicConfig>; topics?: Record<string, TelegramUserTopicConfig>;
enabled?: boolean; enabled?: boolean;
allowFrom?: Array<string | number>; allowFrom?: Array<string | number>;