import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from"; import { createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers"; import { createChannelPluginBase } from "openclaw/plugin-sdk/core"; import { buildChannelConfigSchema, getChatChannelMeta, normalizeAccountId, TelegramConfigSchema, type ChannelPlugin, type OpenClawConfig, } from "../runtime-api.js"; import { inspectTelegramAccount } from "./account-inspect.js"; import { listTelegramAccountIds, resolveDefaultTelegramAccountId, resolveTelegramAccount, type ResolvedTelegramAccount, } from "./accounts.js"; export const TELEGRAM_CHANNEL = "telegram" as const; export function findTelegramTokenOwnerAccountId(params: { cfg: OpenClawConfig; accountId: string; }): string | null { const normalizedAccountId = normalizeAccountId(params.accountId); const tokenOwners = new Map(); for (const id of listTelegramAccountIds(params.cfg)) { const account = inspectTelegramAccount({ cfg: params.cfg, accountId: id }); const token = (account.token ?? "").trim(); if (!token) { continue; } const ownerAccountId = tokenOwners.get(token); if (!ownerAccountId) { tokenOwners.set(token, account.accountId); continue; } if (account.accountId === normalizedAccountId) { return ownerAccountId; } } return null; } export function formatDuplicateTelegramTokenReason(params: { accountId: string; ownerAccountId: string; }): string { return ( `Duplicate Telegram bot token: account "${params.accountId}" shares a token with ` + `account "${params.ownerAccountId}". Keep one owner account per bot token.` ); } export const telegramConfigAdapter = createScopedChannelConfigAdapter({ sectionKey: TELEGRAM_CHANNEL, listAccountIds: listTelegramAccountIds, resolveAccount: (cfg, accountId) => resolveTelegramAccount({ cfg, accountId }), inspectAccount: (cfg, accountId) => inspectTelegramAccount({ cfg, accountId }), defaultAccountId: resolveDefaultTelegramAccountId, clearBaseFields: ["botToken", "tokenFile", "name"], resolveAllowFrom: (account: ResolvedTelegramAccount) => account.config.allowFrom, formatAllowFrom: (allowFrom) => formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(telegram|tg):/i }), resolveDefaultTo: (account: ResolvedTelegramAccount) => account.config.defaultTo, }); export function createTelegramPluginBase(params: { setupWizard: NonNullable["setupWizard"]>; setup: NonNullable["setup"]>; }): Pick< ChannelPlugin, "id" | "meta" | "setupWizard" | "capabilities" | "reload" | "configSchema" | "config" | "setup" > { return createChannelPluginBase({ id: TELEGRAM_CHANNEL, meta: { ...getChatChannelMeta(TELEGRAM_CHANNEL), quickstartAllowFrom: true, }, setupWizard: params.setupWizard, capabilities: { chatTypes: ["direct", "group", "channel", "thread"], reactions: true, threads: true, media: true, polls: true, nativeCommands: true, blockStreaming: true, }, reload: { configPrefixes: ["channels.telegram"] }, configSchema: buildChannelConfigSchema(TelegramConfigSchema), config: { ...telegramConfigAdapter, isConfigured: (account, cfg) => { if (!account.token?.trim()) { return false; } return !findTelegramTokenOwnerAccountId({ cfg, accountId: account.accountId }); }, unconfiguredReason: (account, cfg) => { if (!account.token?.trim()) { return "not configured"; } const ownerAccountId = findTelegramTokenOwnerAccountId({ cfg, accountId: account.accountId, }); if (!ownerAccountId) { return "not configured"; } return formatDuplicateTelegramTokenReason({ accountId: account.accountId, ownerAccountId, }); }, describeAccount: (account, cfg) => ({ accountId: account.accountId, name: account.name, enabled: account.enabled, configured: Boolean(account.token?.trim()) && !findTelegramTokenOwnerAccountId({ cfg, accountId: account.accountId }), tokenSource: account.tokenSource, }), }, setup: params.setup, }) as Pick< ChannelPlugin, "id" | "meta" | "setupWizard" | "capabilities" | "reload" | "configSchema" | "config" | "setup" >; }