From 22940b7b98e5526d4cff4806502545094d35acd9 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 21 Feb 2026 20:01:14 +0100 Subject: [PATCH] refactor(discord): split allowlist resolution flow --- src/channels/allowlists/resolve-utils.ts | 12 +- src/discord/monitor/provider.allowlist.ts | 377 +++++++++++++--------- 2 files changed, 227 insertions(+), 162 deletions(-) diff --git a/src/channels/allowlists/resolve-utils.ts b/src/channels/allowlists/resolve-utils.ts index 183571ea420..fdfef0fa0e0 100644 --- a/src/channels/allowlists/resolve-utils.ts +++ b/src/channels/allowlists/resolve-utils.ts @@ -108,17 +108,19 @@ export function patchAllowlistUsersInConfigEntries< if (!Array.isArray(users) || users.length === 0) { continue; } - const additions = resolveAllowlistIdAdditions({ - existing: users, - resolvedMap: params.resolvedMap, - }); const resolvedUsers = params.strategy === "canonicalize" ? canonicalizeAllowlistWithResolvedIds({ existing: users, resolvedMap: params.resolvedMap, }) - : mergeAllowlist({ existing: users, additions }); + : mergeAllowlist({ + existing: users, + additions: resolveAllowlistIdAdditions({ + existing: users, + resolvedMap: params.resolvedMap, + }), + }); nextEntries[entryKey] = { ...entryConfig, users: resolvedUsers, diff --git a/src/discord/monitor/provider.allowlist.ts b/src/discord/monitor/provider.allowlist.ts index 4bc6cc3a6d8..556a3da3305 100644 --- a/src/discord/monitor/provider.allowlist.ts +++ b/src/discord/monitor/provider.allowlist.ts @@ -12,6 +12,7 @@ import { resolveDiscordChannelAllowlist } from "../resolve-channels.js"; import { resolveDiscordUserAllowlist } from "../resolve-users.js"; type GuildEntries = Record; +type ChannelResolutionInput = { input: string; guildKey: string; channelKey?: string }; function toGuildEntries(value: unknown): GuildEntries { if (!value || typeof value !== "object") { @@ -34,6 +35,204 @@ function toAllowlistEntries(value: unknown): string[] | undefined { return value.map((entry) => String(entry).trim()).filter((entry) => Boolean(entry)); } +function hasGuildEntries(value: GuildEntries): boolean { + return Object.keys(value).length > 0; +} + +function collectChannelResolutionInputs(guildEntries: GuildEntries): ChannelResolutionInput[] { + const entries: ChannelResolutionInput[] = []; + for (const [guildKey, guildCfg] of Object.entries(guildEntries)) { + if (guildKey === "*") { + continue; + } + const channels = guildCfg?.channels ?? {}; + const channelKeys = Object.keys(channels).filter((key) => key !== "*"); + if (channelKeys.length === 0) { + const input = /^\d+$/.test(guildKey) ? `guild:${guildKey}` : guildKey; + entries.push({ input, guildKey }); + continue; + } + for (const channelKey of channelKeys) { + entries.push({ + input: `${guildKey}/${channelKey}`, + guildKey, + channelKey, + }); + } + } + return entries; +} + +async function resolveGuildEntriesByChannelAllowlist(params: { + token: string; + guildEntries: GuildEntries; + fetcher: typeof fetch; + runtime: RuntimeEnv; +}): Promise { + const entries = collectChannelResolutionInputs(params.guildEntries); + if (entries.length === 0) { + return params.guildEntries; + } + try { + const resolved = await resolveDiscordChannelAllowlist({ + token: params.token, + entries: entries.map((entry) => entry.input), + fetcher: params.fetcher, + }); + const sourceByInput = new Map(entries.map((entry) => [entry.input, entry])); + const nextGuilds = { ...params.guildEntries }; + const mapping: string[] = []; + const unresolved: string[] = []; + for (const entry of resolved) { + const source = sourceByInput.get(entry.input); + if (!source) { + continue; + } + const sourceGuild = params.guildEntries[source.guildKey] ?? {}; + if (!entry.resolved || !entry.guildId) { + unresolved.push(entry.input); + continue; + } + mapping.push( + entry.channelId + ? `${entry.input}→${entry.guildId}/${entry.channelId}` + : `${entry.input}→${entry.guildId}`, + ); + const existing = nextGuilds[entry.guildId] ?? {}; + const mergedChannels = { + ...sourceGuild.channels, + ...existing.channels, + }; + const mergedGuild: DiscordGuildEntry = { + ...sourceGuild, + ...existing, + channels: mergedChannels, + }; + nextGuilds[entry.guildId] = mergedGuild; + + if (source.channelKey && entry.channelId) { + const sourceChannel = sourceGuild.channels?.[source.channelKey]; + if (sourceChannel) { + nextGuilds[entry.guildId] = { + ...mergedGuild, + channels: { + ...mergedChannels, + [entry.channelId]: { + ...sourceChannel, + ...mergedChannels[entry.channelId], + }, + }, + }; + } + } + } + summarizeMapping("discord channels", mapping, unresolved, params.runtime); + return nextGuilds; + } catch (err) { + params.runtime.log?.( + `discord channel resolve failed; using config entries. ${formatErrorMessage(err)}`, + ); + return params.guildEntries; + } +} + +async function resolveAllowFromByUserAllowlist(params: { + token: string; + allowFrom: string[] | undefined; + fetcher: typeof fetch; + runtime: RuntimeEnv; +}): Promise { + const allowEntries = + params.allowFrom?.filter((entry) => String(entry).trim() && String(entry).trim() !== "*") ?? []; + if (allowEntries.length === 0) { + return params.allowFrom; + } + try { + const resolvedUsers = await resolveDiscordUserAllowlist({ + token: params.token, + entries: allowEntries.map((entry) => String(entry)), + fetcher: params.fetcher, + }); + const { resolvedMap, mapping, unresolved } = buildAllowlistResolutionSummary(resolvedUsers); + const allowFrom = canonicalizeAllowlistWithResolvedIds({ + existing: params.allowFrom, + resolvedMap, + }); + summarizeMapping("discord users", mapping, unresolved, params.runtime); + return allowFrom; + } catch (err) { + params.runtime.log?.( + `discord user resolve failed; using config entries. ${formatErrorMessage(err)}`, + ); + return params.allowFrom; + } +} + +function collectGuildUserEntries(guildEntries: GuildEntries): Set { + const userEntries = new Set(); + for (const guild of Object.values(guildEntries)) { + if (!guild || typeof guild !== "object") { + continue; + } + addAllowlistUserEntriesFromConfigEntry(userEntries, guild); + const channels = (guild as { channels?: Record }).channels ?? {}; + for (const channel of Object.values(channels)) { + addAllowlistUserEntriesFromConfigEntry(userEntries, channel); + } + } + return userEntries; +} + +async function resolveGuildEntriesByUserAllowlist(params: { + token: string; + guildEntries: GuildEntries; + fetcher: typeof fetch; + runtime: RuntimeEnv; +}): Promise { + const userEntries = collectGuildUserEntries(params.guildEntries); + if (userEntries.size === 0) { + return params.guildEntries; + } + try { + const resolvedUsers = await resolveDiscordUserAllowlist({ + token: params.token, + entries: Array.from(userEntries), + fetcher: params.fetcher, + }); + const { resolvedMap, mapping, unresolved } = buildAllowlistResolutionSummary(resolvedUsers); + const nextGuilds = { ...params.guildEntries }; + for (const [guildKey, guildConfig] of Object.entries(params.guildEntries)) { + if (!guildConfig || typeof guildConfig !== "object") { + continue; + } + const nextGuild = { ...guildConfig } as Record; + const users = (guildConfig as { users?: string[] }).users; + if (Array.isArray(users) && users.length > 0) { + nextGuild.users = canonicalizeAllowlistWithResolvedIds({ + existing: users, + resolvedMap, + }); + } + const channels = (guildConfig as { channels?: Record }).channels ?? {}; + if (channels && typeof channels === "object") { + nextGuild.channels = patchAllowlistUsersInConfigEntries({ + entries: channels, + resolvedMap, + strategy: "canonicalize", + }); + } + nextGuilds[guildKey] = nextGuild as DiscordGuildEntry; + } + summarizeMapping("discord channel users", mapping, unresolved, params.runtime); + return nextGuilds; + } catch (err) { + params.runtime.log?.( + `discord channel user resolve failed; using config entries. ${formatErrorMessage(err)}`, + ); + return params.guildEntries; + } +} + export async function resolveDiscordAllowlistConfig(params: { token: string; guildEntries: unknown; @@ -44,169 +243,33 @@ export async function resolveDiscordAllowlistConfig(params: { let guildEntries = toGuildEntries(params.guildEntries); let allowFrom = toAllowlistEntries(params.allowFrom); - if (Object.keys(guildEntries).length > 0) { - try { - const entries: Array<{ input: string; guildKey: string; channelKey?: string }> = []; - for (const [guildKey, guildCfg] of Object.entries(guildEntries)) { - if (guildKey === "*") { - continue; - } - const channels = guildCfg?.channels ?? {}; - const channelKeys = Object.keys(channels).filter((key) => key !== "*"); - if (channelKeys.length === 0) { - const input = /^\d+$/.test(guildKey) ? `guild:${guildKey}` : guildKey; - entries.push({ input, guildKey }); - continue; - } - for (const channelKey of channelKeys) { - entries.push({ - input: `${guildKey}/${channelKey}`, - guildKey, - channelKey, - }); - } - } - if (entries.length > 0) { - const resolved = await resolveDiscordChannelAllowlist({ - token: params.token, - entries: entries.map((entry) => entry.input), - fetcher: params.fetcher, - }); - const nextGuilds = { ...guildEntries }; - const mapping: string[] = []; - const unresolved: string[] = []; - for (const entry of resolved) { - const source = entries.find((item) => item.input === entry.input); - if (!source) { - continue; - } - const sourceGuild = guildEntries[source.guildKey] ?? {}; - if (!entry.resolved || !entry.guildId) { - unresolved.push(entry.input); - continue; - } - mapping.push( - entry.channelId - ? `${entry.input}→${entry.guildId}/${entry.channelId}` - : `${entry.input}→${entry.guildId}`, - ); - const existing = nextGuilds[entry.guildId] ?? {}; - const mergedChannels = { - ...sourceGuild.channels, - ...existing.channels, - }; - const mergedGuild: DiscordGuildEntry = { - ...sourceGuild, - ...existing, - channels: mergedChannels, - }; - nextGuilds[entry.guildId] = mergedGuild; - - if (source.channelKey && entry.channelId) { - const sourceChannel = sourceGuild.channels?.[source.channelKey]; - if (sourceChannel) { - nextGuilds[entry.guildId] = { - ...mergedGuild, - channels: { - ...mergedChannels, - [entry.channelId]: { - ...sourceChannel, - ...mergedChannels[entry.channelId], - }, - }, - }; - } - } - } - guildEntries = nextGuilds; - summarizeMapping("discord channels", mapping, unresolved, params.runtime); - } - } catch (err) { - params.runtime.log?.( - `discord channel resolve failed; using config entries. ${formatErrorMessage(err)}`, - ); - } + if (hasGuildEntries(guildEntries)) { + guildEntries = await resolveGuildEntriesByChannelAllowlist({ + token: params.token, + guildEntries, + fetcher: params.fetcher, + runtime: params.runtime, + }); } - const allowEntries = - allowFrom?.filter((entry) => String(entry).trim() && String(entry).trim() !== "*") ?? []; - if (allowEntries.length > 0) { - try { - const resolvedUsers = await resolveDiscordUserAllowlist({ - token: params.token, - entries: allowEntries.map((entry) => String(entry)), - fetcher: params.fetcher, - }); - const { resolvedMap, mapping, unresolved } = buildAllowlistResolutionSummary(resolvedUsers); - allowFrom = canonicalizeAllowlistWithResolvedIds({ - existing: allowFrom, - resolvedMap, - }); - summarizeMapping("discord users", mapping, unresolved, params.runtime); - } catch (err) { - params.runtime.log?.( - `discord user resolve failed; using config entries. ${formatErrorMessage(err)}`, - ); - } - } + allowFrom = await resolveAllowFromByUserAllowlist({ + token: params.token, + allowFrom, + fetcher: params.fetcher, + runtime: params.runtime, + }); - if (Object.keys(guildEntries).length > 0) { - const userEntries = new Set(); - for (const guild of Object.values(guildEntries)) { - if (!guild || typeof guild !== "object") { - continue; - } - addAllowlistUserEntriesFromConfigEntry(userEntries, guild); - const channels = (guild as { channels?: Record }).channels ?? {}; - for (const channel of Object.values(channels)) { - addAllowlistUserEntriesFromConfigEntry(userEntries, channel); - } - } - - if (userEntries.size > 0) { - try { - const resolvedUsers = await resolveDiscordUserAllowlist({ - token: params.token, - entries: Array.from(userEntries), - fetcher: params.fetcher, - }); - const { resolvedMap, mapping, unresolved } = buildAllowlistResolutionSummary(resolvedUsers); - - const nextGuilds = { ...guildEntries }; - for (const [guildKey, guildConfig] of Object.entries(guildEntries ?? {})) { - if (!guildConfig || typeof guildConfig !== "object") { - continue; - } - const nextGuild = { ...guildConfig } as Record; - const users = (guildConfig as { users?: string[] }).users; - if (Array.isArray(users) && users.length > 0) { - nextGuild.users = canonicalizeAllowlistWithResolvedIds({ - existing: users, - resolvedMap, - }); - } - const channels = (guildConfig as { channels?: Record }).channels ?? {}; - if (channels && typeof channels === "object") { - nextGuild.channels = patchAllowlistUsersInConfigEntries({ - entries: channels, - resolvedMap, - strategy: "canonicalize", - }); - } - nextGuilds[guildKey] = nextGuild as DiscordGuildEntry; - } - guildEntries = nextGuilds; - summarizeMapping("discord channel users", mapping, unresolved, params.runtime); - } catch (err) { - params.runtime.log?.( - `discord channel user resolve failed; using config entries. ${formatErrorMessage(err)}`, - ); - } - } + if (hasGuildEntries(guildEntries)) { + guildEntries = await resolveGuildEntriesByUserAllowlist({ + token: params.token, + guildEntries, + fetcher: params.fetcher, + runtime: params.runtime, + }); } return { - guildEntries: Object.keys(guildEntries).length > 0 ? guildEntries : undefined, + guildEntries: hasGuildEntries(guildEntries) ? guildEntries : undefined, allowFrom, }; }