From 556aa8a702fc9a4dcbcb500e166cfb374c741d4b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 7 Mar 2026 23:02:59 +0000 Subject: [PATCH] refactor: share config adapter allowFrom and defaultTo helpers --- extensions/bluebubbles/src/channel.ts | 5 ++-- extensions/discord/src/channel.ts | 8 +++--- extensions/feishu/src/channel.ts | 3 ++- extensions/googlechat/src/channel.ts | 14 +++++----- extensions/irc/src/channel.ts | 11 +++++--- extensions/line/src/channel.ts | 7 ++--- extensions/matrix/src/channel.ts | 3 ++- extensions/mattermost/src/channel.ts | 5 ++-- extensions/nextcloud-talk/src/channel.ts | 7 ++--- extensions/signal/src/channel.ts | 8 +++--- extensions/slack/src/channel.ts | 6 +++-- extensions/telegram/src/channel.ts | 12 ++++----- extensions/zalo/src/channel.ts | 9 +++---- extensions/zalouser/src/channel.ts | 10 +++---- src/plugin-sdk/channel-config-helpers.test.ts | 27 +++++++++++++++++++ src/plugin-sdk/channel-config-helpers.ts | 20 ++++++++++++-- src/plugin-sdk/index.ts | 2 ++ 17 files changed, 100 insertions(+), 57 deletions(-) create mode 100644 src/plugin-sdk/channel-config-helpers.test.ts diff --git a/extensions/bluebubbles/src/channel.ts b/extensions/bluebubbles/src/channel.ts index 35b1e7c8c89..cf43fc30fd1 100644 --- a/extensions/bluebubbles/src/channel.ts +++ b/extensions/bluebubbles/src/channel.ts @@ -2,6 +2,7 @@ import { buildAccountScopedDmSecurityPolicy, collectOpenGroupPolicyRestrictSendersWarnings, formatNormalizedAllowFromEntries, + mapAllowFromEntries, } from "openclaw/plugin-sdk"; import type { ChannelAccountSnapshot, @@ -115,9 +116,7 @@ export const bluebubblesPlugin: ChannelPlugin = { baseUrl: account.baseUrl, }), resolveAllowFrom: ({ cfg, accountId }) => - (resolveBlueBubblesAccount({ cfg: cfg, accountId }).config.allowFrom ?? []).map((entry) => - String(entry), - ), + mapAllowFromEntries(resolveBlueBubblesAccount({ cfg: cfg, accountId }).config.allowFrom), formatAllowFrom: ({ allowFrom }) => formatNormalizedAllowFromEntries({ allowFrom, diff --git a/extensions/discord/src/channel.ts b/extensions/discord/src/channel.ts index fa0f6f364ba..7c32c8df604 100644 --- a/extensions/discord/src/channel.ts +++ b/extensions/discord/src/channel.ts @@ -2,6 +2,8 @@ import { buildAccountScopedDmSecurityPolicy, collectOpenGroupPolicyConfiguredRouteWarnings, formatAllowFromLowercase, + mapAllowFromEntries, + resolveOptionalConfigString, } from "openclaw/plugin-sdk"; import { applyAccountNameToChannelSection, @@ -114,12 +116,10 @@ export const discordPlugin: ChannelPlugin = { tokenSource: account.tokenSource, }), resolveAllowFrom: ({ cfg, accountId }) => - (resolveDiscordAccount({ cfg, accountId }).config.dm?.allowFrom ?? []).map((entry) => - String(entry), - ), + mapAllowFromEntries(resolveDiscordAccount({ cfg, accountId }).config.dm?.allowFrom), formatAllowFrom: ({ allowFrom }) => formatAllowFromLowercase({ allowFrom }), resolveDefaultTo: ({ cfg, accountId }) => - resolveDiscordAccount({ cfg, accountId }).config.defaultTo?.trim() || undefined, + resolveOptionalConfigString(resolveDiscordAccount({ cfg, accountId }).config.defaultTo), }, security: { resolveDmPolicy: ({ cfg, accountId, account }) => { diff --git a/extensions/feishu/src/channel.ts b/extensions/feishu/src/channel.ts index 4e2782d0ba5..d4c3bb1c33b 100644 --- a/extensions/feishu/src/channel.ts +++ b/extensions/feishu/src/channel.ts @@ -1,6 +1,7 @@ import { collectOpenGroupPolicyRestrictSendersWarnings, formatAllowFromLowercase, + mapAllowFromEntries, } from "openclaw/plugin-sdk"; import type { ChannelMeta, ChannelPlugin, ClawdbotConfig } from "openclaw/plugin-sdk/feishu"; import { @@ -252,7 +253,7 @@ export const feishuPlugin: ChannelPlugin = { }), resolveAllowFrom: ({ cfg, accountId }) => { const account = resolveFeishuAccount({ cfg, accountId }); - return (account.config?.allowFrom ?? []).map((entry) => String(entry)); + return mapAllowFromEntries(account.config?.allowFrom); }, formatAllowFrom: ({ allowFrom }) => formatAllowFromLowercase({ allowFrom }), }, diff --git a/extensions/googlechat/src/channel.ts b/extensions/googlechat/src/channel.ts index 68f36276de0..accfed59808 100644 --- a/extensions/googlechat/src/channel.ts +++ b/extensions/googlechat/src/channel.ts @@ -2,6 +2,8 @@ import { buildAccountScopedDmSecurityPolicy, buildOpenGroupPolicyConfigureRouteAllowlistWarning, formatNormalizedAllowFromEntries, + mapAllowFromEntries, + resolveOptionalConfigString, } from "openclaw/plugin-sdk"; import { applyAccountNameToChannelSection, @@ -69,9 +71,7 @@ export const googlechatDock: ChannelDock = { outbound: { textChunkLimit: 4000 }, config: { resolveAllowFrom: ({ cfg, accountId }) => - (resolveGoogleChatAccount({ cfg: cfg, accountId }).config.dm?.allowFrom ?? []).map((entry) => - String(entry), - ), + mapAllowFromEntries(resolveGoogleChatAccount({ cfg: cfg, accountId }).config.dm?.allowFrom), formatAllowFrom: ({ allowFrom }) => formatNormalizedAllowFromEntries({ allowFrom, @@ -177,19 +177,19 @@ export const googlechatPlugin: ChannelPlugin = { credentialSource: account.credentialSource, }), resolveAllowFrom: ({ cfg, accountId }) => - ( + mapAllowFromEntries( resolveGoogleChatAccount({ cfg: cfg, accountId, - }).config.dm?.allowFrom ?? [] - ).map((entry) => String(entry)), + }).config.dm?.allowFrom, + ), formatAllowFrom: ({ allowFrom }) => formatNormalizedAllowFromEntries({ allowFrom, normalizeEntry: formatAllowFromEntry, }), resolveDefaultTo: ({ cfg, accountId }) => - resolveGoogleChatAccount({ cfg, accountId }).config.defaultTo?.trim() || undefined, + resolveOptionalConfigString(resolveGoogleChatAccount({ cfg, accountId }).config.defaultTo), }, security: { resolveDmPolicy: ({ cfg, accountId, account }) => { diff --git a/extensions/irc/src/channel.ts b/extensions/irc/src/channel.ts index 2c389d7bc31..d7ecd0fab2d 100644 --- a/extensions/irc/src/channel.ts +++ b/extensions/irc/src/channel.ts @@ -2,6 +2,8 @@ import { buildAccountScopedDmSecurityPolicy, buildOpenGroupPolicyWarning, formatNormalizedAllowFromEntries, + mapAllowFromEntries, + resolveOptionalConfigString, } from "openclaw/plugin-sdk"; import { buildBaseAccountStatusSnapshot, @@ -115,8 +117,8 @@ export const ircPlugin: ChannelPlugin = { passwordSource: account.passwordSource, }), resolveAllowFrom: ({ cfg, accountId }) => - (resolveIrcAccount({ cfg: cfg as CoreConfig, accountId }).config.allowFrom ?? []).map( - (entry) => String(entry), + mapAllowFromEntries( + resolveIrcAccount({ cfg: cfg as CoreConfig, accountId }).config.allowFrom, ), formatAllowFrom: ({ allowFrom }) => formatNormalizedAllowFromEntries({ @@ -124,8 +126,9 @@ export const ircPlugin: ChannelPlugin = { normalizeEntry: normalizeIrcAllowEntry, }), resolveDefaultTo: ({ cfg, accountId }) => - resolveIrcAccount({ cfg: cfg as CoreConfig, accountId }).config.defaultTo?.trim() || - undefined, + resolveOptionalConfigString( + resolveIrcAccount({ cfg: cfg as CoreConfig, accountId }).config.defaultTo, + ), }, security: { resolveDmPolicy: ({ cfg, accountId, account }) => { diff --git a/extensions/line/src/channel.ts b/extensions/line/src/channel.ts index ac59e457005..93cb3a41b5d 100644 --- a/extensions/line/src/channel.ts +++ b/extensions/line/src/channel.ts @@ -1,6 +1,7 @@ import { buildAccountScopedDmSecurityPolicy, collectOpenGroupPolicyRestrictSendersWarnings, + mapAllowFromEntries, } from "openclaw/plugin-sdk"; import { buildChannelConfigSchema, @@ -147,10 +148,10 @@ export const linePlugin: ChannelPlugin = { tokenSource: account.tokenSource ?? undefined, }), resolveAllowFrom: ({ cfg, accountId }) => - ( + mapAllowFromEntries( getLineRuntime().channel.line.resolveLineAccount({ cfg, accountId: accountId ?? undefined }) - .config.allowFrom ?? [] - ).map((entry) => String(entry)), + .config.allowFrom, + ), formatAllowFrom: ({ allowFrom }) => allowFrom .map((entry) => String(entry).trim()) diff --git a/extensions/matrix/src/channel.ts b/extensions/matrix/src/channel.ts index d2b81606152..3e1d0e5668c 100644 --- a/extensions/matrix/src/channel.ts +++ b/extensions/matrix/src/channel.ts @@ -1,6 +1,7 @@ import { buildAccountScopedDmSecurityPolicy, buildOpenGroupPolicyWarning, + mapAllowFromEntries, } from "openclaw/plugin-sdk"; import { applyAccountNameToChannelSection, @@ -156,7 +157,7 @@ export const matrixPlugin: ChannelPlugin = { }), resolveAllowFrom: ({ cfg, accountId }) => { const matrixConfig = resolveMatrixAccountConfig({ cfg: cfg as CoreConfig, accountId }); - return (matrixConfig.dm?.allowFrom ?? []).map((entry: string | number) => String(entry)); + return mapAllowFromEntries(matrixConfig.dm?.allowFrom); }, formatAllowFrom: ({ allowFrom }) => normalizeMatrixAllowList(allowFrom), }, diff --git a/extensions/mattermost/src/channel.ts b/extensions/mattermost/src/channel.ts index f7774173d50..320a1c24c34 100644 --- a/extensions/mattermost/src/channel.ts +++ b/extensions/mattermost/src/channel.ts @@ -2,6 +2,7 @@ import { buildAccountScopedDmSecurityPolicy, collectOpenGroupPolicyRestrictSendersWarnings, formatNormalizedAllowFromEntries, + mapAllowFromEntries, } from "openclaw/plugin-sdk"; import { applyAccountNameToChannelSection, @@ -276,9 +277,7 @@ export const mattermostPlugin: ChannelPlugin = { baseUrl: account.baseUrl, }), resolveAllowFrom: ({ cfg, accountId }) => - (resolveMattermostAccount({ cfg, accountId }).config.allowFrom ?? []).map((entry) => - String(entry), - ), + mapAllowFromEntries(resolveMattermostAccount({ cfg, accountId }).config.allowFrom), formatAllowFrom: ({ allowFrom }) => formatNormalizedAllowFromEntries({ allowFrom, diff --git a/extensions/nextcloud-talk/src/channel.ts b/extensions/nextcloud-talk/src/channel.ts index 4d200e57a58..8f0381cd8d5 100644 --- a/extensions/nextcloud-talk/src/channel.ts +++ b/extensions/nextcloud-talk/src/channel.ts @@ -2,6 +2,7 @@ import { buildAccountScopedDmSecurityPolicy, collectOpenGroupPolicyRouteAllowlistWarnings, formatAllowFromLowercase, + mapAllowFromEntries, } from "openclaw/plugin-sdk"; import { applyAccountNameToChannelSection, @@ -109,9 +110,9 @@ export const nextcloudTalkPlugin: ChannelPlugin = baseUrl: account.baseUrl ? "[set]" : "[missing]", }), resolveAllowFrom: ({ cfg, accountId }) => - ( - resolveNextcloudTalkAccount({ cfg: cfg as CoreConfig, accountId }).config.allowFrom ?? [] - ).map((entry) => String(entry).toLowerCase()), + mapAllowFromEntries( + resolveNextcloudTalkAccount({ cfg: cfg as CoreConfig, accountId }).config.allowFrom, + ).map((entry) => entry.toLowerCase()), formatAllowFrom: ({ allowFrom }) => formatAllowFromLowercase({ allowFrom, diff --git a/extensions/signal/src/channel.ts b/extensions/signal/src/channel.ts index f9a020b7f63..e204d2a0ac9 100644 --- a/extensions/signal/src/channel.ts +++ b/extensions/signal/src/channel.ts @@ -1,6 +1,8 @@ import { buildAccountScopedDmSecurityPolicy, collectOpenGroupPolicyRestrictSendersWarnings, + mapAllowFromEntries, + resolveOptionalConfigString, } from "openclaw/plugin-sdk"; import { applyAccountNameToChannelSection, @@ -143,9 +145,7 @@ export const signalPlugin: ChannelPlugin = { baseUrl: account.baseUrl, }), resolveAllowFrom: ({ cfg, accountId }) => - (resolveSignalAccount({ cfg, accountId }).config.allowFrom ?? []).map((entry) => - String(entry), - ), + mapAllowFromEntries(resolveSignalAccount({ cfg, accountId }).config.allowFrom), formatAllowFrom: ({ allowFrom }) => allowFrom .map((entry) => String(entry).trim()) @@ -153,7 +153,7 @@ export const signalPlugin: ChannelPlugin = { .map((entry) => (entry === "*" ? "*" : normalizeE164(entry.replace(/^signal:/i, "")))) .filter(Boolean), resolveDefaultTo: ({ cfg, accountId }) => - resolveSignalAccount({ cfg, accountId }).config.defaultTo?.trim() || undefined, + resolveOptionalConfigString(resolveSignalAccount({ cfg, accountId }).config.defaultTo), }, security: { resolveDmPolicy: ({ cfg, accountId, account }) => { diff --git a/extensions/slack/src/channel.ts b/extensions/slack/src/channel.ts index 831c5c386d3..31dc02efaff 100644 --- a/extensions/slack/src/channel.ts +++ b/extensions/slack/src/channel.ts @@ -2,6 +2,8 @@ import { buildAccountScopedDmSecurityPolicy, collectOpenGroupPolicyConfiguredRouteWarnings, formatAllowFromLowercase, + mapAllowFromEntries, + resolveOptionalConfigString, } from "openclaw/plugin-sdk"; import { applyAccountNameToChannelSection, @@ -166,10 +168,10 @@ export const slackPlugin: ChannelPlugin = { appTokenSource: account.appTokenSource, }), resolveAllowFrom: ({ cfg, accountId }) => - (resolveSlackAccount({ cfg, accountId }).dm?.allowFrom ?? []).map((entry) => String(entry)), + mapAllowFromEntries(resolveSlackAccount({ cfg, accountId }).dm?.allowFrom), formatAllowFrom: ({ allowFrom }) => formatAllowFromLowercase({ allowFrom }), resolveDefaultTo: ({ cfg, accountId }) => - resolveSlackAccount({ cfg, accountId }).config.defaultTo?.trim() || undefined, + resolveOptionalConfigString(resolveSlackAccount({ cfg, accountId }).config.defaultTo), }, security: { resolveDmPolicy: ({ cfg, accountId, account }) => { diff --git a/extensions/telegram/src/channel.ts b/extensions/telegram/src/channel.ts index ed4c4a6bd99..afec02f110e 100644 --- a/extensions/telegram/src/channel.ts +++ b/extensions/telegram/src/channel.ts @@ -2,6 +2,8 @@ import { buildAccountScopedDmSecurityPolicy, collectOpenGroupPolicyRouteAllowlistWarnings, formatAllowFromLowercase, + mapAllowFromEntries, + resolveOptionalConfigString, } from "openclaw/plugin-sdk"; import { applyAccountNameToChannelSection, @@ -176,15 +178,11 @@ export const telegramPlugin: ChannelPlugin - (resolveTelegramAccount({ cfg, accountId }).config.allowFrom ?? []).map((entry) => - String(entry), - ), + mapAllowFromEntries(resolveTelegramAccount({ cfg, accountId }).config.allowFrom), formatAllowFrom: ({ allowFrom }) => formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(telegram|tg):/i }), - resolveDefaultTo: ({ cfg, accountId }) => { - const val = resolveTelegramAccount({ cfg, accountId }).config.defaultTo; - return val != null ? String(val) : undefined; - }, + resolveDefaultTo: ({ cfg, accountId }) => + resolveOptionalConfigString(resolveTelegramAccount({ cfg, accountId }).config.defaultTo), }, security: { resolveDmPolicy: ({ cfg, accountId, account }) => { diff --git a/extensions/zalo/src/channel.ts b/extensions/zalo/src/channel.ts index f26d708b6e4..97fb8748fbe 100644 --- a/extensions/zalo/src/channel.ts +++ b/extensions/zalo/src/channel.ts @@ -2,6 +2,7 @@ import { buildAccountScopedDmSecurityPolicy, buildOpenGroupPolicyRestrictSendersWarning, buildOpenGroupPolicyWarning, + mapAllowFromEntries, } from "openclaw/plugin-sdk"; import type { ChannelAccountSnapshot, @@ -76,9 +77,7 @@ export const zaloDock: ChannelDock = { outbound: { textChunkLimit: 2000 }, config: { resolveAllowFrom: ({ cfg, accountId }) => - (resolveZaloAccount({ cfg: cfg, accountId }).config.allowFrom ?? []).map((entry) => - String(entry), - ), + mapAllowFromEntries(resolveZaloAccount({ cfg: cfg, accountId }).config.allowFrom), formatAllowFrom: ({ allowFrom }) => formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(zalo|zl):/i }), }, @@ -133,9 +132,7 @@ export const zaloPlugin: ChannelPlugin = { tokenSource: account.tokenSource, }), resolveAllowFrom: ({ cfg, accountId }) => - (resolveZaloAccount({ cfg: cfg, accountId }).config.allowFrom ?? []).map((entry) => - String(entry), - ), + mapAllowFromEntries(resolveZaloAccount({ cfg: cfg, accountId }).config.allowFrom), formatAllowFrom: ({ allowFrom }) => formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(zalo|zl):/i }), }, diff --git a/extensions/zalouser/src/channel.ts b/extensions/zalouser/src/channel.ts index 9f6fa003a9f..e5c0c88d81a 100644 --- a/extensions/zalouser/src/channel.ts +++ b/extensions/zalouser/src/channel.ts @@ -1,4 +1,4 @@ -import { buildAccountScopedDmSecurityPolicy } from "openclaw/plugin-sdk"; +import { buildAccountScopedDmSecurityPolicy, mapAllowFromEntries } from "openclaw/plugin-sdk"; import type { ChannelAccountSnapshot, ChannelDirectoryEntry, @@ -208,9 +208,7 @@ export const zalouserDock: ChannelDock = { outbound: { textChunkLimit: 2000 }, config: { resolveAllowFrom: ({ cfg, accountId }) => - (resolveZalouserAccountSync({ cfg: cfg, accountId }).config.allowFrom ?? []).map((entry) => - String(entry), - ), + mapAllowFromEntries(resolveZalouserAccountSync({ cfg: cfg, accountId }).config.allowFrom), formatAllowFrom: ({ allowFrom }) => formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(zalouser|zlu):/i }), }, @@ -273,9 +271,7 @@ export const zalouserPlugin: ChannelPlugin = { configured: undefined, }), resolveAllowFrom: ({ cfg, accountId }) => - (resolveZalouserAccountSync({ cfg: cfg, accountId }).config.allowFrom ?? []).map((entry) => - String(entry), - ), + mapAllowFromEntries(resolveZalouserAccountSync({ cfg: cfg, accountId }).config.allowFrom), formatAllowFrom: ({ allowFrom }) => formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(zalouser|zlu):/i }), }, diff --git a/src/plugin-sdk/channel-config-helpers.test.ts b/src/plugin-sdk/channel-config-helpers.test.ts new file mode 100644 index 00000000000..901acd3f6bb --- /dev/null +++ b/src/plugin-sdk/channel-config-helpers.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from "vitest"; +import { mapAllowFromEntries, resolveOptionalConfigString } from "./channel-config-helpers.js"; + +describe("mapAllowFromEntries", () => { + it("coerces allowFrom entries to strings", () => { + expect(mapAllowFromEntries(["user", 42, null])).toEqual(["user", "42", "null"]); + }); + + it("returns empty list for missing input", () => { + expect(mapAllowFromEntries(undefined)).toEqual([]); + }); +}); + +describe("resolveOptionalConfigString", () => { + it("trims and returns string values", () => { + expect(resolveOptionalConfigString(" room:123 ")).toBe("room:123"); + }); + + it("coerces numeric values", () => { + expect(resolveOptionalConfigString(123)).toBe("123"); + }); + + it("returns undefined for empty values", () => { + expect(resolveOptionalConfigString(" ")).toBeUndefined(); + expect(resolveOptionalConfigString(undefined)).toBeUndefined(); + }); +}); diff --git a/src/plugin-sdk/channel-config-helpers.ts b/src/plugin-sdk/channel-config-helpers.ts index 90cbd4b980f..24051922e4a 100644 --- a/src/plugin-sdk/channel-config-helpers.ts +++ b/src/plugin-sdk/channel-config-helpers.ts @@ -4,10 +4,26 @@ import { resolveIMessageAccount } from "../imessage/accounts.js"; import { normalizeAccountId } from "../routing/session-key.js"; import { resolveWhatsAppAccount } from "../web/accounts.js"; +export function mapAllowFromEntries( + allowFrom: Array | null | undefined, +): string[] { + return (allowFrom ?? []).map((entry) => String(entry)); +} + export function formatTrimmedAllowFromEntries(allowFrom: Array): string[] { return allowFrom.map((entry) => String(entry).trim()).filter(Boolean); } +export function resolveOptionalConfigString( + value: string | number | null | undefined, +): string | undefined { + if (value == null) { + return undefined; + } + const normalized = String(value).trim(); + return normalized || undefined; +} + export function resolveWhatsAppConfigAllowFrom(params: { cfg: OpenClawConfig; accountId?: string | null; @@ -33,12 +49,12 @@ export function resolveIMessageConfigAllowFrom(params: { cfg: OpenClawConfig; accountId?: string | null; }): string[] { - return (resolveIMessageAccount(params).config.allowFrom ?? []).map((entry) => String(entry)); + return mapAllowFromEntries(resolveIMessageAccount(params).config.allowFrom); } export function resolveIMessageConfigDefaultTo(params: { cfg: OpenClawConfig; accountId?: string | null; }): string | undefined { - return resolveIMessageAccount(params).config.defaultTo?.trim() || undefined; + return resolveOptionalConfigString(resolveIMessageAccount(params).config.defaultTo); } diff --git a/src/plugin-sdk/index.ts b/src/plugin-sdk/index.ts index aeee7760445..829ef4392cb 100644 --- a/src/plugin-sdk/index.ts +++ b/src/plugin-sdk/index.ts @@ -378,6 +378,8 @@ export { SILENT_REPLY_TOKEN, isSilentReplyText } from "../auto-reply/tokens.js"; export { formatInboundFromLabel } from "../auto-reply/envelope.js"; export { formatTrimmedAllowFromEntries, + mapAllowFromEntries, + resolveOptionalConfigString, formatWhatsAppConfigAllowFromEntries, resolveIMessageConfigAllowFrom, resolveIMessageConfigDefaultTo,