refactor: share allowFrom formatter scaffolding

This commit is contained in:
Peter Steinberger
2026-03-07 23:00:03 +00:00
parent c91bfa830a
commit feac26c3b7
13 changed files with 94 additions and 48 deletions

View File

@@ -1,6 +1,7 @@
import {
buildAccountScopedDmSecurityPolicy,
collectOpenGroupPolicyRestrictSendersWarnings,
formatNormalizedAllowFromEntries,
} from "openclaw/plugin-sdk";
import type {
ChannelAccountSnapshot,
@@ -118,11 +119,10 @@ export const bluebubblesPlugin: ChannelPlugin<ResolvedBlueBubblesAccount> = {
String(entry),
),
formatAllowFrom: ({ allowFrom }) =>
allowFrom
.map((entry) => String(entry).trim())
.filter(Boolean)
.map((entry) => entry.replace(/^bluebubbles:/i, ""))
.map((entry) => normalizeBlueBubblesHandle(entry)),
formatNormalizedAllowFromEntries({
allowFrom,
normalizeEntry: (entry) => normalizeBlueBubblesHandle(entry.replace(/^bluebubbles:/i, "")),
}),
},
actions: bluebubblesMessageActions,
security: {

View File

@@ -1,6 +1,7 @@
import {
buildAccountScopedDmSecurityPolicy,
collectOpenGroupPolicyConfiguredRouteWarnings,
formatAllowFromLowercase,
} from "openclaw/plugin-sdk";
import {
applyAccountNameToChannelSection,
@@ -116,11 +117,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
(resolveDiscordAccount({ cfg, accountId }).config.dm?.allowFrom ?? []).map((entry) =>
String(entry),
),
formatAllowFrom: ({ allowFrom }) =>
allowFrom
.map((entry) => String(entry).trim())
.filter(Boolean)
.map((entry) => entry.toLowerCase()),
formatAllowFrom: ({ allowFrom }) => formatAllowFromLowercase({ allowFrom }),
resolveDefaultTo: ({ cfg, accountId }) =>
resolveDiscordAccount({ cfg, accountId }).config.defaultTo?.trim() || undefined,
},

View File

@@ -1,4 +1,7 @@
import { collectOpenGroupPolicyRestrictSendersWarnings } from "openclaw/plugin-sdk";
import {
collectOpenGroupPolicyRestrictSendersWarnings,
formatAllowFromLowercase,
} from "openclaw/plugin-sdk";
import type { ChannelMeta, ChannelPlugin, ClawdbotConfig } from "openclaw/plugin-sdk/feishu";
import {
buildProbeChannelStatusSummary,
@@ -251,11 +254,7 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
const account = resolveFeishuAccount({ cfg, accountId });
return (account.config?.allowFrom ?? []).map((entry) => String(entry));
},
formatAllowFrom: ({ allowFrom }) =>
allowFrom
.map((entry) => String(entry).trim())
.filter(Boolean)
.map((entry) => entry.toLowerCase()),
formatAllowFrom: ({ allowFrom }) => formatAllowFromLowercase({ allowFrom }),
},
security: {
collectWarnings: ({ cfg, accountId }) => {

View File

@@ -1,6 +1,7 @@
import {
buildAccountScopedDmSecurityPolicy,
buildOpenGroupPolicyConfigureRouteAllowlistWarning,
formatNormalizedAllowFromEntries,
} from "openclaw/plugin-sdk";
import {
applyAccountNameToChannelSection,
@@ -72,10 +73,10 @@ export const googlechatDock: ChannelDock = {
String(entry),
),
formatAllowFrom: ({ allowFrom }) =>
allowFrom
.map((entry) => String(entry))
.filter(Boolean)
.map(formatAllowFromEntry),
formatNormalizedAllowFromEntries({
allowFrom,
normalizeEntry: formatAllowFromEntry,
}),
},
groups: {
resolveRequireMention: resolveGoogleChatGroupRequireMention,
@@ -183,10 +184,10 @@ export const googlechatPlugin: ChannelPlugin<ResolvedGoogleChatAccount> = {
}).config.dm?.allowFrom ?? []
).map((entry) => String(entry)),
formatAllowFrom: ({ allowFrom }) =>
allowFrom
.map((entry) => String(entry))
.filter(Boolean)
.map(formatAllowFromEntry),
formatNormalizedAllowFromEntries({
allowFrom,
normalizeEntry: formatAllowFromEntry,
}),
resolveDefaultTo: ({ cfg, accountId }) =>
resolveGoogleChatAccount({ cfg, accountId }).config.defaultTo?.trim() || undefined,
},

View File

@@ -1,6 +1,7 @@
import {
buildAccountScopedDmSecurityPolicy,
buildOpenGroupPolicyWarning,
formatNormalizedAllowFromEntries,
} from "openclaw/plugin-sdk";
import {
buildBaseAccountStatusSnapshot,
@@ -118,7 +119,10 @@ export const ircPlugin: ChannelPlugin<ResolvedIrcAccount, IrcProbe> = {
(entry) => String(entry),
),
formatAllowFrom: ({ allowFrom }) =>
allowFrom.map((entry) => normalizeIrcAllowEntry(String(entry))).filter(Boolean),
formatNormalizedAllowFromEntries({
allowFrom,
normalizeEntry: normalizeIrcAllowEntry,
}),
resolveDefaultTo: ({ cfg, accountId }) =>
resolveIrcAccount({ cfg: cfg as CoreConfig, accountId }).config.defaultTo?.trim() ||
undefined,

View File

@@ -1,6 +1,7 @@
import {
buildAccountScopedDmSecurityPolicy,
collectOpenGroupPolicyRestrictSendersWarnings,
formatNormalizedAllowFromEntries,
} from "openclaw/plugin-sdk";
import {
applyAccountNameToChannelSection,
@@ -279,7 +280,10 @@ export const mattermostPlugin: ChannelPlugin<ResolvedMattermostAccount> = {
String(entry),
),
formatAllowFrom: ({ allowFrom }) =>
allowFrom.map((entry) => formatAllowEntry(String(entry))).filter(Boolean),
formatNormalizedAllowFromEntries({
allowFrom,
normalizeEntry: formatAllowEntry,
}),
},
security: {
resolveDmPolicy: ({ cfg, accountId, account }) => {

View File

@@ -1,4 +1,7 @@
import { collectOpenGroupPolicyRestrictSendersWarnings } from "openclaw/plugin-sdk";
import {
collectOpenGroupPolicyRestrictSendersWarnings,
formatAllowFromLowercase,
} from "openclaw/plugin-sdk";
import type {
ChannelMessageActionName,
ChannelPlugin,
@@ -126,11 +129,7 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
configured: account.configured,
}),
resolveAllowFrom: ({ cfg }) => cfg.channels?.msteams?.allowFrom ?? [],
formatAllowFrom: ({ allowFrom }) =>
allowFrom
.map((entry) => String(entry).trim())
.filter(Boolean)
.map((entry) => entry.toLowerCase()),
formatAllowFrom: ({ allowFrom }) => formatAllowFromLowercase({ allowFrom }),
resolveDefaultTo: ({ cfg }) => cfg.channels?.msteams?.defaultTo?.trim() || undefined,
},
security: {

View File

@@ -1,6 +1,7 @@
import {
buildAccountScopedDmSecurityPolicy,
collectOpenGroupPolicyRouteAllowlistWarnings,
formatAllowFromLowercase,
} from "openclaw/plugin-sdk";
import {
applyAccountNameToChannelSection,
@@ -112,11 +113,10 @@ export const nextcloudTalkPlugin: ChannelPlugin<ResolvedNextcloudTalkAccount> =
resolveNextcloudTalkAccount({ cfg: cfg as CoreConfig, accountId }).config.allowFrom ?? []
).map((entry) => String(entry).toLowerCase()),
formatAllowFrom: ({ allowFrom }) =>
allowFrom
.map((entry) => String(entry).trim())
.filter(Boolean)
.map((entry) => entry.replace(/^(nextcloud-talk|nc-talk|nc):/i, ""))
.map((entry) => entry.toLowerCase()),
formatAllowFromLowercase({
allowFrom,
stripPrefixRe: /^(nextcloud-talk|nc-talk|nc):/i,
}),
},
security: {
resolveDmPolicy: ({ cfg, accountId, account }) => {

View File

@@ -1,6 +1,7 @@
import {
buildAccountScopedDmSecurityPolicy,
collectOpenGroupPolicyConfiguredRouteWarnings,
formatAllowFromLowercase,
} from "openclaw/plugin-sdk";
import {
applyAccountNameToChannelSection,
@@ -166,11 +167,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
}),
resolveAllowFrom: ({ cfg, accountId }) =>
(resolveSlackAccount({ cfg, accountId }).dm?.allowFrom ?? []).map((entry) => String(entry)),
formatAllowFrom: ({ allowFrom }) =>
allowFrom
.map((entry) => String(entry).trim())
.filter(Boolean)
.map((entry) => entry.toLowerCase()),
formatAllowFrom: ({ allowFrom }) => formatAllowFromLowercase({ allowFrom }),
resolveDefaultTo: ({ cfg, accountId }) =>
resolveSlackAccount({ cfg, accountId }).config.defaultTo?.trim() || undefined,
},

View File

@@ -1,6 +1,7 @@
import {
buildAccountScopedDmSecurityPolicy,
collectOpenGroupPolicyRouteAllowlistWarnings,
formatAllowFromLowercase,
} from "openclaw/plugin-sdk";
import {
applyAccountNameToChannelSection,
@@ -179,11 +180,7 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount, TelegramProb
String(entry),
),
formatAllowFrom: ({ allowFrom }) =>
allowFrom
.map((entry) => String(entry).trim())
.filter(Boolean)
.map((entry) => entry.replace(/^(telegram|tg):/i, ""))
.map((entry) => entry.toLowerCase()),
formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(telegram|tg):/i }),
resolveDefaultTo: ({ cfg, accountId }) => {
const val = resolveTelegramAccount({ cfg, accountId }).config.defaultTo;
return val != null ? String(val) : undefined;

View File

@@ -1,5 +1,10 @@
import { describe, expect, it } from "vitest";
import { isAllowedParsedChatSender, isNormalizedSenderAllowed } from "./allow-from.js";
import {
formatAllowFromLowercase,
formatNormalizedAllowFromEntries,
isAllowedParsedChatSender,
isNormalizedSenderAllowed,
} from "./allow-from.js";
function parseAllowTarget(
entry: string,
@@ -102,3 +107,34 @@ describe("isNormalizedSenderAllowed", () => {
).toBe(false);
});
});
describe("formatAllowFromLowercase", () => {
it("trims, strips prefixes, and lowercases entries", () => {
expect(
formatAllowFromLowercase({
allowFrom: [" Telegram:UserA ", "tg:UserB", " "],
stripPrefixRe: /^(telegram|tg):/i,
}),
).toEqual(["usera", "userb"]);
});
});
describe("formatNormalizedAllowFromEntries", () => {
it("applies custom normalization after trimming", () => {
expect(
formatNormalizedAllowFromEntries({
allowFrom: [" @Alice ", "", " @Bob "],
normalizeEntry: (entry) => entry.replace(/^@/, "").toLowerCase(),
}),
).toEqual(["alice", "bob"]);
});
it("filters empty normalized entries", () => {
expect(
formatNormalizedAllowFromEntries({
allowFrom: ["@", "valid"],
normalizeEntry: (entry) => entry.replace(/^@$/, ""),
}),
).toEqual(["valid"]);
});
});

View File

@@ -9,6 +9,17 @@ export function formatAllowFromLowercase(params: {
.map((entry) => entry.toLowerCase());
}
export function formatNormalizedAllowFromEntries(params: {
allowFrom: Array<string | number>;
normalizeEntry: (entry: string) => string | undefined | null;
}): string[] {
return params.allowFrom
.map((entry) => String(entry).trim())
.filter(Boolean)
.map((entry) => params.normalizeEntry(entry))
.filter((entry): entry is string => Boolean(entry));
}
export function isNormalizedSenderAllowed(params: {
senderId: string | number;
allowFrom: Array<string | number>;

View File

@@ -274,6 +274,7 @@ export {
} from "../routing/session-key.js";
export {
formatAllowFromLowercase,
formatNormalizedAllowFromEntries,
isAllowedParsedChatSender,
isNormalizedSenderAllowed,
} from "./allow-from.js";