From f1df31eeef1c27c2fda1ea4b29bde4f2bfe514d0 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 17 Mar 2026 05:22:43 +0000 Subject: [PATCH] refactor(discord): share setup wizard base --- extensions/discord/src/setup-surface.ts | 237 +++++------------------- 1 file changed, 44 insertions(+), 193 deletions(-) diff --git a/extensions/discord/src/setup-surface.ts b/extensions/discord/src/setup-surface.ts index be5a374d0fa..9c1ce7f5f1c 100644 --- a/extensions/discord/src/setup-surface.ts +++ b/extensions/discord/src/setup-surface.ts @@ -1,24 +1,12 @@ import { - DEFAULT_ACCOUNT_ID, - noteChannelLookupFailure, - noteChannelLookupSummary, type OpenClawConfig, - parseMentionOrPrefixedId, - patchChannelConfigForAccount, promptLegacyChannelAllowFrom, resolveSetupAccountId, - setLegacyChannelDmPolicyWithAllowFrom, - setSetupChannelEnabled, type WizardPrompter, } from "openclaw/plugin-sdk/setup"; -import { type ChannelSetupDmPolicy, type ChannelSetupWizard } from "openclaw/plugin-sdk/setup"; +import { type ChannelSetupWizard } from "openclaw/plugin-sdk/setup"; import { formatDocsLink } from "../../../src/terminal/links.js"; -import { inspectDiscordAccount } from "./account-inspect.js"; -import { - listDiscordAccountIds, - resolveDefaultDiscordAccountId, - resolveDiscordAccount, -} from "./accounts.js"; +import { resolveDefaultDiscordAccountId, resolveDiscordAccount } from "./accounts.js"; import { normalizeDiscordSlug } from "./monitor/allow-list.js"; import { resolveDiscordChannelAllowlist, @@ -26,7 +14,7 @@ import { } from "./resolve-channels.js"; import { resolveDiscordUserAllowlist } from "./resolve-users.js"; import { - discordSetupAdapter, + createDiscordSetupWizardBase, DISCORD_TOKEN_HELP_LINES, parseDiscordAllowFromId, setDiscordGuildChannelAllowlist, @@ -91,186 +79,49 @@ async function promptDiscordAllowFrom(params: { }); } -const discordDmPolicy: ChannelSetupDmPolicy = { - label: "Discord", - channel, - policyKey: "channels.discord.dmPolicy", - allowFromKey: "channels.discord.allowFrom", - getCurrent: (cfg) => - cfg.channels?.discord?.dmPolicy ?? cfg.channels?.discord?.dm?.policy ?? "pairing", - setPolicy: (cfg, policy) => - setLegacyChannelDmPolicyWithAllowFrom({ - cfg, - channel, - dmPolicy: policy, - }), - promptAllowFrom: promptDiscordAllowFrom, -}; +async function resolveDiscordGroupAllowlist(params: { + cfg: OpenClawConfig; + accountId: string; + credentialValues: { token?: string }; + entries: string[]; +}) { + const token = + resolveDiscordAccount({ cfg: params.cfg, accountId: params.accountId }).token || + (typeof params.credentialValues.token === "string" ? params.credentialValues.token : ""); + if (!token || params.entries.length === 0) { + return params.entries.map((input) => ({ + input, + resolved: false, + })); + } + return await resolveDiscordChannelAllowlist({ + token, + entries: params.entries, + }); +} -export const discordSetupWizard: ChannelSetupWizard = { - channel, - status: { - configuredLabel: "configured", - unconfiguredLabel: "needs token", - configuredHint: "configured", - unconfiguredHint: "needs token", - configuredScore: 2, - unconfiguredScore: 1, - resolveConfigured: ({ cfg }) => - listDiscordAccountIds(cfg).some( - (accountId) => inspectDiscordAccount({ cfg, accountId }).configured, - ), - }, - credentials: [ - { - inputKey: "token", - providerHint: channel, - credentialLabel: "Discord bot token", - preferredEnvVar: "DISCORD_BOT_TOKEN", - helpTitle: "Discord bot token", - helpLines: DISCORD_TOKEN_HELP_LINES, - envPrompt: "DISCORD_BOT_TOKEN detected. Use env var?", - keepPrompt: "Discord token already configured. Keep it?", - inputPrompt: "Enter Discord bot token", - allowEnv: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID, - inspect: ({ cfg, accountId }) => { - const account = inspectDiscordAccount({ cfg, accountId }); - return { - accountConfigured: account.configured, - hasConfiguredValue: account.tokenStatus !== "missing", - resolvedValue: account.token?.trim() || undefined, - envValue: - accountId === DEFAULT_ACCOUNT_ID - ? process.env.DISCORD_BOT_TOKEN?.trim() || undefined - : undefined, - }; - }, +export const discordSetupWizard: ChannelSetupWizard = createDiscordSetupWizardBase(async () => ({ + discordSetupWizard: { + dmPolicy: { + promptAllowFrom: promptDiscordAllowFrom, }, - ], - groupAccess: { - label: "Discord channels", - placeholder: "My Server/#general, guildId/channelId, #support", - currentPolicy: ({ cfg, accountId }) => - resolveDiscordAccount({ cfg, accountId }).config.groupPolicy ?? "allowlist", - currentEntries: ({ cfg, accountId }) => - Object.entries(resolveDiscordAccount({ cfg, accountId }).config.guilds ?? {}).flatMap( - ([guildKey, value]) => { - const channels = value?.channels ?? {}; - const channelKeys = Object.keys(channels); - if (channelKeys.length === 0) { - const input = /^\d+$/.test(guildKey) ? `guild:${guildKey}` : guildKey; - return [input]; - } - return channelKeys.map((channelKey) => `${guildKey}/${channelKey}`); - }, - ), - updatePrompt: ({ cfg, accountId }) => - Boolean(resolveDiscordAccount({ cfg, accountId }).config.guilds), - setPolicy: ({ cfg, accountId, policy }) => - patchChannelConfigForAccount({ - cfg, - channel, - accountId, - patch: { groupPolicy: policy }, - }), - resolveAllowlist: async ({ cfg, accountId, credentialValues, entries, prompter }) => { - const token = - resolveDiscordAccount({ cfg, accountId }).token || - (typeof credentialValues.token === "string" ? credentialValues.token : ""); - let resolved: DiscordChannelResolution[] = entries.map((input) => ({ - input, - resolved: false, - })); - if (!token || entries.length === 0) { - return resolved; - } - try { - resolved = await resolveDiscordChannelAllowlist({ - token, + groupAccess: { + resolveAllowlist: async ({ cfg, accountId, credentialValues, entries }) => + await resolveDiscordGroupAllowlist({ + cfg, + accountId, + credentialValues, entries, - }); - const resolvedChannels = resolved.filter((entry) => entry.resolved && entry.channelId); - const resolvedGuilds = resolved.filter( - (entry) => entry.resolved && entry.guildId && !entry.channelId, - ); - const unresolved = resolved.filter((entry) => !entry.resolved).map((entry) => entry.input); - await noteChannelLookupSummary({ - prompter, - label: "Discord channels", - resolvedSections: [ - { - title: "Resolved channels", - values: resolvedChannels - .map((entry) => entry.channelId) - .filter((value): value is string => Boolean(value)), - }, - { - title: "Resolved guilds", - values: resolvedGuilds - .map((entry) => entry.guildId) - .filter((value): value is string => Boolean(value)), - }, - ], - unresolved, - }); - } catch (error) { - await noteChannelLookupFailure({ - prompter, - label: "Discord channels", - error, - }); - } - return resolved; + }), }, - applyAllowlist: ({ cfg, accountId, resolved }) => { - const allowlistEntries: Array<{ guildKey: string; channelKey?: string }> = []; - for (const entry of resolved as DiscordChannelResolution[]) { - const guildKey = - entry.guildId ?? - (entry.guildName ? normalizeDiscordSlug(entry.guildName) : undefined) ?? - "*"; - const channelKey = - entry.channelId ?? - (entry.channelName ? normalizeDiscordSlug(entry.channelName) : undefined); - if (!channelKey && guildKey === "*") { - continue; - } - allowlistEntries.push({ guildKey, ...(channelKey ? { channelKey } : {}) }); - } - return setDiscordGuildChannelAllowlist(cfg, accountId, allowlistEntries); + allowFrom: { + resolveEntries: async ({ cfg, accountId, credentialValues, entries }) => + await resolveDiscordAllowFromEntries({ + token: + resolveDiscordAccount({ cfg, accountId }).token || + (typeof credentialValues.token === "string" ? credentialValues.token : ""), + entries, + }), }, - }, - allowFrom: { - credentialInputKey: "token", - helpTitle: "Discord allowlist", - helpLines: [ - "Allowlist Discord DMs by username (we resolve to user ids).", - "Examples:", - "- 123456789012345678", - "- @alice", - "- alice#1234", - "Multiple entries: comma-separated.", - `Docs: ${formatDocsLink("/discord", "discord")}`, - ], - message: "Discord allowFrom (usernames or ids)", - placeholder: "@alice, 123456789012345678", - invalidWithoutCredentialNote: "Bot token missing; use numeric user ids (or mention form) only.", - parseId: parseDiscordAllowFromId, - resolveEntries: async ({ cfg, accountId, credentialValues, entries }) => - await resolveDiscordAllowFromEntries({ - token: - resolveDiscordAccount({ cfg, accountId }).token || - (typeof credentialValues.token === "string" ? credentialValues.token : ""), - entries, - }), - apply: async ({ cfg, accountId, allowFrom }) => - patchChannelConfigForAccount({ - cfg, - channel, - accountId, - patch: { dmPolicy: "allowlist", allowFrom }, - }), - }, - dmPolicy: discordDmPolicy, - disable: (cfg) => setSetupChannelEnabled(cfg, channel, false), -}; + } as ChannelSetupWizard, +}));