From 936ac22ec222be0f60de22fc63884caa7fb6698d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 8 Mar 2026 19:46:20 +0000 Subject: [PATCH] refactor: share channel config adapter base --- extensions/discord/src/channel.ts | 32 ++++++---------- extensions/googlechat/src/channel.ts | 48 +++++++++-------------- extensions/slack/src/channel.ts | 32 ++++++---------- extensions/telegram/src/channel.ts | 32 ++++++---------- src/plugin-sdk/channel-config-helpers.ts | 49 ++++++++++++++++++++++++ src/plugin-sdk/index.ts | 1 + 6 files changed, 102 insertions(+), 92 deletions(-) diff --git a/extensions/discord/src/channel.ts b/extensions/discord/src/channel.ts index cd3483bce00..23a4a2ffae8 100644 --- a/extensions/discord/src/channel.ts +++ b/extensions/discord/src/channel.ts @@ -1,3 +1,4 @@ +import { createScopedChannelConfigBase } from "openclaw/plugin-sdk"; import { buildAccountScopedDmSecurityPolicy, collectOpenProviderGroupPolicyWarnings, @@ -13,7 +14,6 @@ import { collectDiscordAuditChannelIds, collectDiscordStatusIssues, DEFAULT_ACCOUNT_ID, - deleteAccountFromConfigSection, discordOnboardingAdapter, DiscordConfigSchema, getChatChannelMeta, @@ -33,7 +33,6 @@ import { resolveDefaultDiscordAccountId, resolveDiscordGroupRequireMention, resolveDiscordGroupToolPolicy, - setAccountEnabledInConfigSection, type ChannelMessageActionAdapter, type ChannelPlugin, type ResolvedDiscordAccount, @@ -63,6 +62,15 @@ const discordConfigAccessors = createScopedAccountConfigAccessors({ resolveDefaultTo: (account: ResolvedDiscordAccount) => account.config.defaultTo, }); +const discordConfigBase = createScopedChannelConfigBase({ + sectionKey: "discord", + listAccountIds: listDiscordAccountIds, + resolveAccount: (cfg, accountId) => resolveDiscordAccount({ cfg, accountId }), + inspectAccount: (cfg, accountId) => inspectDiscordAccount({ cfg, accountId }), + defaultAccountId: resolveDefaultDiscordAccountId, + clearBaseFields: ["token", "name"], +}); + export const discordPlugin: ChannelPlugin = { id: "discord", meta: { @@ -93,25 +101,7 @@ export const discordPlugin: ChannelPlugin = { reload: { configPrefixes: ["channels.discord"] }, configSchema: buildChannelConfigSchema(DiscordConfigSchema), config: { - listAccountIds: (cfg) => listDiscordAccountIds(cfg), - resolveAccount: (cfg, accountId) => resolveDiscordAccount({ cfg, accountId }), - inspectAccount: (cfg, accountId) => inspectDiscordAccount({ cfg, accountId }), - defaultAccountId: (cfg) => resolveDefaultDiscordAccountId(cfg), - setAccountEnabled: ({ cfg, accountId, enabled }) => - setAccountEnabledInConfigSection({ - cfg, - sectionKey: "discord", - accountId, - enabled, - allowTopLevel: true, - }), - deleteAccount: ({ cfg, accountId }) => - deleteAccountFromConfigSection({ - cfg, - sectionKey: "discord", - accountId, - clearBaseFields: ["token", "name"], - }), + ...discordConfigBase, isConfigured: (account) => Boolean(account.token?.trim()), describeAccount: (account) => ({ accountId: account.accountId, diff --git a/extensions/googlechat/src/channel.ts b/extensions/googlechat/src/channel.ts index 775145f5d54..f0c5dace9f0 100644 --- a/extensions/googlechat/src/channel.ts +++ b/extensions/googlechat/src/channel.ts @@ -1,3 +1,4 @@ +import { createScopedChannelConfigBase } from "openclaw/plugin-sdk"; import { buildAccountScopedDmSecurityPolicy, buildOpenGroupPolicyConfigureRouteAllowlistWarning, @@ -11,7 +12,6 @@ import { buildComputedAccountStatusSnapshot, buildChannelConfigSchema, DEFAULT_ACCOUNT_ID, - deleteAccountFromConfigSection, getChatChannelMeta, listDirectoryGroupEntriesFromMapKeys, listDirectoryUserEntriesFromAllowFrom, @@ -21,7 +21,6 @@ import { PAIRING_APPROVED_MESSAGE, resolveChannelMediaMaxBytes, resolveGoogleChatGroupRequireMention, - setAccountEnabledInConfigSection, type ChannelDock, type ChannelMessageActionAdapter, type ChannelPlugin, @@ -68,6 +67,23 @@ const googleChatConfigAccessors = createScopedAccountConfigAccessors({ resolveDefaultTo: (account: ResolvedGoogleChatAccount) => account.config.defaultTo, }); +const googleChatConfigBase = createScopedChannelConfigBase({ + sectionKey: "googlechat", + listAccountIds: listGoogleChatAccountIds, + resolveAccount: (cfg, accountId) => resolveGoogleChatAccount({ cfg, accountId }), + defaultAccountId: resolveDefaultGoogleChatAccountId, + clearBaseFields: [ + "serviceAccount", + "serviceAccountFile", + "audienceType", + "audience", + "webhookPath", + "webhookUrl", + "botUser", + "name", + ], +}); + export const googlechatDock: ChannelDock = { id: "googlechat", capabilities: { @@ -142,33 +158,7 @@ export const googlechatPlugin: ChannelPlugin = { reload: { configPrefixes: ["channels.googlechat"] }, configSchema: buildChannelConfigSchema(GoogleChatConfigSchema), config: { - listAccountIds: (cfg) => listGoogleChatAccountIds(cfg), - resolveAccount: (cfg, accountId) => resolveGoogleChatAccount({ cfg: cfg, accountId }), - defaultAccountId: (cfg) => resolveDefaultGoogleChatAccountId(cfg), - setAccountEnabled: ({ cfg, accountId, enabled }) => - setAccountEnabledInConfigSection({ - cfg: cfg, - sectionKey: "googlechat", - accountId, - enabled, - allowTopLevel: true, - }), - deleteAccount: ({ cfg, accountId }) => - deleteAccountFromConfigSection({ - cfg: cfg, - sectionKey: "googlechat", - accountId, - clearBaseFields: [ - "serviceAccount", - "serviceAccountFile", - "audienceType", - "audience", - "webhookPath", - "webhookUrl", - "botUser", - "name", - ], - }), + ...googleChatConfigBase, isConfigured: (account) => account.credentialSource !== "none", describeAccount: (account) => ({ accountId: account.accountId, diff --git a/extensions/slack/src/channel.ts b/extensions/slack/src/channel.ts index 98010e907f4..1fdf4018f28 100644 --- a/extensions/slack/src/channel.ts +++ b/extensions/slack/src/channel.ts @@ -1,3 +1,4 @@ +import { createScopedChannelConfigBase } from "openclaw/plugin-sdk"; import { buildAccountScopedDmSecurityPolicy, collectOpenProviderGroupPolicyWarnings, @@ -10,7 +11,6 @@ import { buildComputedAccountStatusSnapshot, buildChannelConfigSchema, DEFAULT_ACCOUNT_ID, - deleteAccountFromConfigSection, extractSlackToolSend, getChatChannelMeta, handleSlackMessageAction, @@ -32,7 +32,6 @@ import { resolveSlackGroupRequireMention, resolveSlackGroupToolPolicy, buildSlackThreadingToolContext, - setAccountEnabledInConfigSection, slackOnboardingAdapter, SlackConfigSchema, type ChannelPlugin, @@ -96,6 +95,15 @@ const slackConfigAccessors = createScopedAccountConfigAccessors({ resolveDefaultTo: (account: ResolvedSlackAccount) => account.config.defaultTo, }); +const slackConfigBase = createScopedChannelConfigBase({ + sectionKey: "slack", + listAccountIds: listSlackAccountIds, + resolveAccount: (cfg, accountId) => resolveSlackAccount({ cfg, accountId }), + inspectAccount: (cfg, accountId) => inspectSlackAccount({ cfg, accountId }), + defaultAccountId: resolveDefaultSlackAccountId, + clearBaseFields: ["botToken", "appToken", "name"], +}); + export const slackPlugin: ChannelPlugin = { id: "slack", meta: { @@ -144,25 +152,7 @@ export const slackPlugin: ChannelPlugin = { reload: { configPrefixes: ["channels.slack"] }, configSchema: buildChannelConfigSchema(SlackConfigSchema), config: { - listAccountIds: (cfg) => listSlackAccountIds(cfg), - resolveAccount: (cfg, accountId) => resolveSlackAccount({ cfg, accountId }), - inspectAccount: (cfg, accountId) => inspectSlackAccount({ cfg, accountId }), - defaultAccountId: (cfg) => resolveDefaultSlackAccountId(cfg), - setAccountEnabled: ({ cfg, accountId, enabled }) => - setAccountEnabledInConfigSection({ - cfg, - sectionKey: "slack", - accountId, - enabled, - allowTopLevel: true, - }), - deleteAccount: ({ cfg, accountId }) => - deleteAccountFromConfigSection({ - cfg, - sectionKey: "slack", - accountId, - clearBaseFields: ["botToken", "appToken", "name"], - }), + ...slackConfigBase, isConfigured: (account) => isSlackAccountConfigured(account), describeAccount: (account) => ({ accountId: account.accountId, diff --git a/extensions/telegram/src/channel.ts b/extensions/telegram/src/channel.ts index 2cd2bf8ff51..d8879ab5858 100644 --- a/extensions/telegram/src/channel.ts +++ b/extensions/telegram/src/channel.ts @@ -1,3 +1,4 @@ +import { createScopedChannelConfigBase } from "openclaw/plugin-sdk"; import { collectAllowlistProviderGroupPolicyWarnings, buildAccountScopedDmSecurityPolicy, @@ -12,7 +13,6 @@ import { clearAccountEntryFields, collectTelegramStatusIssues, DEFAULT_ACCOUNT_ID, - deleteAccountFromConfigSection, getChatChannelMeta, inspectTelegramAccount, listTelegramAccountIds, @@ -31,7 +31,6 @@ import { resolveTelegramAccount, resolveTelegramGroupRequireMention, resolveTelegramGroupToolPolicy, - setAccountEnabledInConfigSection, telegramOnboardingAdapter, TelegramConfigSchema, type ChannelMessageActionAdapter, @@ -100,6 +99,15 @@ const telegramConfigAccessors = createScopedAccountConfigAccessors({ resolveDefaultTo: (account: ResolvedTelegramAccount) => account.config.defaultTo, }); +const telegramConfigBase = createScopedChannelConfigBase({ + sectionKey: "telegram", + listAccountIds: listTelegramAccountIds, + resolveAccount: (cfg, accountId) => resolveTelegramAccount({ cfg, accountId }), + inspectAccount: (cfg, accountId) => inspectTelegramAccount({ cfg, accountId }), + defaultAccountId: resolveDefaultTelegramAccountId, + clearBaseFields: ["botToken", "tokenFile", "name"], +}); + export const telegramPlugin: ChannelPlugin = { id: "telegram", meta: { @@ -136,25 +144,7 @@ export const telegramPlugin: ChannelPlugin listTelegramAccountIds(cfg), - resolveAccount: (cfg, accountId) => resolveTelegramAccount({ cfg, accountId }), - inspectAccount: (cfg, accountId) => inspectTelegramAccount({ cfg, accountId }), - defaultAccountId: (cfg) => resolveDefaultTelegramAccountId(cfg), - setAccountEnabled: ({ cfg, accountId, enabled }) => - setAccountEnabledInConfigSection({ - cfg, - sectionKey: "telegram", - accountId, - enabled, - allowTopLevel: true, - }), - deleteAccount: ({ cfg, accountId }) => - deleteAccountFromConfigSection({ - cfg, - sectionKey: "telegram", - accountId, - clearBaseFields: ["botToken", "tokenFile", "name"], - }), + ...telegramConfigBase, isConfigured: (account, cfg) => { if (!account.token?.trim()) { return false; diff --git a/src/plugin-sdk/channel-config-helpers.ts b/src/plugin-sdk/channel-config-helpers.ts index 754e2a57c1a..afcd312f1c8 100644 --- a/src/plugin-sdk/channel-config-helpers.ts +++ b/src/plugin-sdk/channel-config-helpers.ts @@ -1,3 +1,7 @@ +import { + deleteAccountFromConfigSection, + setAccountEnabledInConfigSection, +} from "../channels/plugins/config-helpers.js"; import { normalizeWhatsAppAllowFromEntries } from "../channels/plugins/normalize/whatsapp.js"; import type { ChannelConfigAdapter } from "../channels/plugins/types.adapters.js"; import type { OpenClawConfig } from "../config/config.js"; @@ -55,6 +59,51 @@ export function createScopedAccountConfigAccessors(params: { }; } +export function createScopedChannelConfigBase< + ResolvedAccount, + Config extends OpenClawConfig = OpenClawConfig, +>(params: { + sectionKey: string; + listAccountIds: (cfg: Config) => string[]; + resolveAccount: (cfg: Config, accountId?: string | null) => ResolvedAccount; + defaultAccountId: (cfg: Config) => string; + inspectAccount?: (cfg: Config, accountId?: string | null) => unknown; + clearBaseFields: string[]; + allowTopLevel?: boolean; +}): Pick< + ChannelConfigAdapter, + | "listAccountIds" + | "resolveAccount" + | "inspectAccount" + | "defaultAccountId" + | "setAccountEnabled" + | "deleteAccount" +> { + return { + listAccountIds: (cfg) => params.listAccountIds(cfg as Config), + resolveAccount: (cfg, accountId) => params.resolveAccount(cfg as Config, accountId), + inspectAccount: params.inspectAccount + ? (cfg, accountId) => params.inspectAccount?.(cfg as Config, accountId) + : undefined, + defaultAccountId: (cfg) => params.defaultAccountId(cfg as Config), + setAccountEnabled: ({ cfg, accountId, enabled }) => + setAccountEnabledInConfigSection({ + cfg: cfg as Config, + sectionKey: params.sectionKey, + accountId, + enabled, + allowTopLevel: params.allowTopLevel ?? true, + }), + deleteAccount: ({ cfg, accountId }) => + deleteAccountFromConfigSection({ + cfg: cfg as Config, + sectionKey: params.sectionKey, + accountId, + clearBaseFields: params.clearBaseFields, + }), + }; +} + export function resolveWhatsAppConfigAllowFrom(params: { cfg: OpenClawConfig; accountId?: string | null; diff --git a/src/plugin-sdk/index.ts b/src/plugin-sdk/index.ts index 89340787e92..3e1ba0f03ab 100644 --- a/src/plugin-sdk/index.ts +++ b/src/plugin-sdk/index.ts @@ -195,6 +195,7 @@ export { formatResolvedUnresolvedNote } from "./resolution-notes.js"; export { buildChannelSendResult } from "./channel-send-result.js"; export type { ChannelSendRawResult } from "./channel-send-result.js"; export { createPluginRuntimeStore } from "./runtime-store.js"; +export { createScopedChannelConfigBase } from "./channel-config-helpers.js"; export { AllowFromEntrySchema, buildCatchallMultiAccountChannelSchema,