From 3cc1c7ba836d5c878552e22c792eab18e7d96e30 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 17 Mar 2026 03:04:27 +0000 Subject: [PATCH] refactor(telegram): share plugin base config --- extensions/telegram/src/channel.setup.ts | 71 ++---------- extensions/telegram/src/channel.ts | 95 ++++------------ extensions/telegram/src/shared.ts | 137 +++++++++++++++++++++++ 3 files changed, 167 insertions(+), 136 deletions(-) create mode 100644 extensions/telegram/src/shared.ts diff --git a/extensions/telegram/src/channel.setup.ts b/extensions/telegram/src/channel.setup.ts index c349f5ec053..0ed71ae568c 100644 --- a/extensions/telegram/src/channel.setup.ts +++ b/extensions/telegram/src/channel.setup.ts @@ -1,69 +1,12 @@ -import { - buildChannelConfigSchema, - getChatChannelMeta, - TelegramConfigSchema, - type ChannelPlugin, -} from "../../../src/plugin-sdk-internal/telegram.js"; +import { type ChannelPlugin } from "openclaw/plugin-sdk/telegram"; import { type ResolvedTelegramAccount } from "./accounts.js"; -import { - findTelegramTokenOwnerAccountId, - formatDuplicateTelegramTokenReason, - telegramConfigAccessors, - telegramConfigBase, -} from "./plugin-shared.js"; import type { TelegramProbe } from "./probe.js"; import { telegramSetupAdapter } from "./setup-core.js"; import { telegramSetupWizard } from "./setup-surface.js"; +import { createTelegramPluginBase } from "./shared.js"; -export const telegramSetupPlugin: ChannelPlugin = { - id: "telegram", - meta: { - ...getChatChannelMeta("telegram"), - quickstartAllowFrom: true, - }, - setupWizard: telegramSetupWizard, - 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: { - ...telegramConfigBase, - 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, - }), - ...telegramConfigAccessors, - }, - setup: telegramSetupAdapter, -}; +export const telegramSetupPlugin: ChannelPlugin = + createTelegramPluginBase({ + setupWizard: telegramSetupWizard, + setup: telegramSetupAdapter, + }); diff --git a/extensions/telegram/src/channel.ts b/extensions/telegram/src/channel.ts index d73e63b0996..45cd93cd9e5 100644 --- a/extensions/telegram/src/channel.ts +++ b/extensions/telegram/src/channel.ts @@ -1,27 +1,18 @@ -import { parseTelegramTopicConversation } from "../../../src/acp/conversation-id.js"; -import { resolveExecApprovalCommandDisplay } from "../../../src/infra/exec-approval-command-display.js"; -import { buildExecApprovalPendingReplyPayload } from "../../../src/infra/exec-approval-reply.js"; -import { - type OutboundSendDeps, - resolveOutboundSendDep, -} from "../../../src/infra/outbound/send-deps.js"; import { buildAccountScopedAllowlistConfigEditor, collectAllowlistProviderGroupPolicyWarnings, collectOpenGroupPolicyRouteAllowlistWarnings, createScopedDmSecurityResolver, -} from "../../../src/plugin-sdk-internal/channel-config.js"; +} from "openclaw/plugin-sdk/compat"; import { buildAgentSessionKey, resolveThreadSessionKeys, type RoutePeer, -} from "../../../src/plugin-sdk-internal/core.js"; +} from "openclaw/plugin-sdk/core"; import { - buildChannelConfigSchema, buildTokenChannelStatusSummary, clearAccountEntryFields, DEFAULT_ACCOUNT_ID, - getChatChannelMeta, listTelegramDirectoryGroupsFromConfig, listTelegramDirectoryPeersFromConfig, PAIRING_APPROVED_MESSAGE, @@ -29,14 +20,22 @@ import { resolveConfiguredFromCredentialStatuses, resolveTelegramGroupRequireMention, resolveTelegramGroupToolPolicy, - TelegramConfigSchema, - type ChannelPlugin, type ChannelMessageActionAdapter, + type ChannelPlugin, type OpenClawConfig, -} from "../../../src/plugin-sdk-internal/telegram.js"; +} from "openclaw/plugin-sdk/telegram"; +import { parseTelegramTopicConversation } from "../../../src/acp/conversation-id.js"; +import { resolveExecApprovalCommandDisplay } from "../../../src/infra/exec-approval-command-display.js"; +import { buildExecApprovalPendingReplyPayload } from "../../../src/infra/exec-approval-reply.js"; +import { + type OutboundSendDeps, + resolveOutboundSendDep, +} from "../../../src/infra/outbound/send-deps.js"; import { normalizeMessageChannel } from "../../../src/utils/message-channel.js"; +import { inspectTelegramAccount } from "./account-inspect.js"; import { listTelegramAccountIds, + resolveDefaultTelegramAccountId, resolveTelegramAccount, type ResolvedTelegramAccount, } from "./accounts.js"; @@ -51,17 +50,17 @@ import { monitorTelegramProvider } from "./monitor.js"; import { looksLikeTelegramTargetId, normalizeTelegramMessagingTarget } from "./normalize.js"; import { sendTelegramPayloadMessages } from "./outbound-adapter.js"; import { parseTelegramReplyToMessageId, parseTelegramThreadId } from "./outbound-params.js"; -import { - findTelegramTokenOwnerAccountId, - formatDuplicateTelegramTokenReason, - telegramConfigAccessors, - telegramConfigBase, -} from "./plugin-shared.js"; import { probeTelegram, type TelegramProbe } from "./probe.js"; import { getTelegramRuntime } from "./runtime.js"; import { sendTypingTelegram } from "./send.js"; import { telegramSetupAdapter } from "./setup-core.js"; import { telegramSetupWizard } from "./setup-surface.js"; +import { + createTelegramPluginBase, + findTelegramTokenOwnerAccountId, + formatDuplicateTelegramTokenReason, + telegramConfigAccessors, +} from "./shared.js"; import { collectTelegramStatusIssues } from "./status-issues.js"; import { parseTelegramTarget } from "./targets.js"; @@ -69,8 +68,6 @@ type TelegramSendFn = ReturnType< typeof getTelegramRuntime >["channel"]["telegram"]["sendMessageTelegram"]; -const meta = getChatChannelMeta("telegram"); - type TelegramSendOptions = NonNullable[2]>; function buildTelegramSendOptions(params: { @@ -327,12 +324,10 @@ function readTelegramAllowlistConfig(account: ResolvedTelegramAccount) { } export const telegramPlugin: ChannelPlugin = { - id: "telegram", - meta: { - ...meta, - quickstartAllowFrom: true, - }, - setupWizard: telegramSetupWizard, + ...createTelegramPluginBase({ + setupWizard: telegramSetupWizard, + setup: telegramSetupAdapter, + }), pairing: { idLabel: "telegramUserId", normalizeAllowEntry: (entry) => entry.replace(/^(telegram|tg):/i, ""), @@ -350,49 +345,6 @@ export const telegramPlugin: ChannelPlugin { - 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, - }), - ...telegramConfigAccessors, - }, allowlist: { supportsScope: ({ scope }) => scope === "dm" || scope === "group" || scope === "all", readConfig: ({ cfg, accountId }) => @@ -548,7 +500,6 @@ export const telegramPlugin: ChannelPlugin listTelegramDirectoryGroupsFromConfig(params), }, actions: telegramMessageActions, - setup: telegramSetupAdapter, outbound: { deliveryMode: "direct", chunker: (text, limit) => getTelegramRuntime().channel.text.chunkMarkdownText(text, limit), diff --git a/extensions/telegram/src/shared.ts b/extensions/telegram/src/shared.ts new file mode 100644 index 00000000000..a1c7945520d --- /dev/null +++ b/extensions/telegram/src/shared.ts @@ -0,0 +1,137 @@ +import { createScopedChannelConfigBase } from "openclaw/plugin-sdk/compat"; +import { + createScopedAccountConfigAccessors, + formatAllowFromLowercase, +} from "openclaw/plugin-sdk/compat"; +import { + buildChannelConfigSchema, + getChatChannelMeta, + normalizeAccountId, + TelegramConfigSchema, + type ChannelPlugin, + type OpenClawConfig, +} from "openclaw/plugin-sdk/telegram"; +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 telegramConfigAccessors = createScopedAccountConfigAccessors({ + resolveAccount: ({ cfg, accountId }) => resolveTelegramAccount({ cfg, accountId }), + resolveAllowFrom: (account: ResolvedTelegramAccount) => account.config.allowFrom, + formatAllowFrom: (allowFrom) => + formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(telegram|tg):/i }), + resolveDefaultTo: (account: ResolvedTelegramAccount) => account.config.defaultTo, +}); + +export const telegramConfigBase = createScopedChannelConfigBase({ + sectionKey: TELEGRAM_CHANNEL, + listAccountIds: listTelegramAccountIds, + resolveAccount: (cfg, accountId) => resolveTelegramAccount({ cfg, accountId }), + inspectAccount: (cfg, accountId) => inspectTelegramAccount({ cfg, accountId }), + defaultAccountId: resolveDefaultTelegramAccountId, + clearBaseFields: ["botToken", "tokenFile", "name"], +}); + +export function createTelegramPluginBase(params: { + setupWizard: NonNullable["setupWizard"]>; + setup: NonNullable["setup"]>; +}): Pick< + ChannelPlugin, + "id" | "meta" | "setupWizard" | "capabilities" | "reload" | "configSchema" | "config" | "setup" +> { + return { + 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: { + ...telegramConfigBase, + 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, + }), + ...telegramConfigAccessors, + }, + setup: params.setup, + }; +}