refactor(discord): split allowlist resolution flow

This commit is contained in:
Peter Steinberger
2026-02-21 20:01:14 +01:00
parent 25e89cc863
commit 22940b7b98
2 changed files with 227 additions and 162 deletions

View File

@@ -108,17 +108,19 @@ export function patchAllowlistUsersInConfigEntries<
if (!Array.isArray(users) || users.length === 0) { if (!Array.isArray(users) || users.length === 0) {
continue; continue;
} }
const additions = resolveAllowlistIdAdditions({
existing: users,
resolvedMap: params.resolvedMap,
});
const resolvedUsers = const resolvedUsers =
params.strategy === "canonicalize" params.strategy === "canonicalize"
? canonicalizeAllowlistWithResolvedIds({ ? canonicalizeAllowlistWithResolvedIds({
existing: users, existing: users,
resolvedMap: params.resolvedMap, resolvedMap: params.resolvedMap,
}) })
: mergeAllowlist({ existing: users, additions }); : mergeAllowlist({
existing: users,
additions: resolveAllowlistIdAdditions({
existing: users,
resolvedMap: params.resolvedMap,
}),
});
nextEntries[entryKey] = { nextEntries[entryKey] = {
...entryConfig, ...entryConfig,
users: resolvedUsers, users: resolvedUsers,

View File

@@ -12,6 +12,7 @@ import { resolveDiscordChannelAllowlist } from "../resolve-channels.js";
import { resolveDiscordUserAllowlist } from "../resolve-users.js"; import { resolveDiscordUserAllowlist } from "../resolve-users.js";
type GuildEntries = Record<string, DiscordGuildEntry>; type GuildEntries = Record<string, DiscordGuildEntry>;
type ChannelResolutionInput = { input: string; guildKey: string; channelKey?: string };
function toGuildEntries(value: unknown): GuildEntries { function toGuildEntries(value: unknown): GuildEntries {
if (!value || typeof value !== "object") { if (!value || typeof value !== "object") {
@@ -34,19 +35,12 @@ function toAllowlistEntries(value: unknown): string[] | undefined {
return value.map((entry) => String(entry).trim()).filter((entry) => Boolean(entry)); return value.map((entry) => String(entry).trim()).filter((entry) => Boolean(entry));
} }
export async function resolveDiscordAllowlistConfig(params: { function hasGuildEntries(value: GuildEntries): boolean {
token: string; return Object.keys(value).length > 0;
guildEntries: unknown; }
allowFrom: unknown;
fetcher: typeof fetch;
runtime: RuntimeEnv;
}): Promise<{ guildEntries: GuildEntries | undefined; allowFrom: string[] | undefined }> {
let guildEntries = toGuildEntries(params.guildEntries);
let allowFrom = toAllowlistEntries(params.allowFrom);
if (Object.keys(guildEntries).length > 0) { function collectChannelResolutionInputs(guildEntries: GuildEntries): ChannelResolutionInput[] {
try { const entries: ChannelResolutionInput[] = [];
const entries: Array<{ input: string; guildKey: string; channelKey?: string }> = [];
for (const [guildKey, guildCfg] of Object.entries(guildEntries)) { for (const [guildKey, guildCfg] of Object.entries(guildEntries)) {
if (guildKey === "*") { if (guildKey === "*") {
continue; continue;
@@ -66,21 +60,35 @@ export async function resolveDiscordAllowlistConfig(params: {
}); });
} }
} }
if (entries.length > 0) { return entries;
}
async function resolveGuildEntriesByChannelAllowlist(params: {
token: string;
guildEntries: GuildEntries;
fetcher: typeof fetch;
runtime: RuntimeEnv;
}): Promise<GuildEntries> {
const entries = collectChannelResolutionInputs(params.guildEntries);
if (entries.length === 0) {
return params.guildEntries;
}
try {
const resolved = await resolveDiscordChannelAllowlist({ const resolved = await resolveDiscordChannelAllowlist({
token: params.token, token: params.token,
entries: entries.map((entry) => entry.input), entries: entries.map((entry) => entry.input),
fetcher: params.fetcher, fetcher: params.fetcher,
}); });
const nextGuilds = { ...guildEntries }; const sourceByInput = new Map(entries.map((entry) => [entry.input, entry]));
const nextGuilds = { ...params.guildEntries };
const mapping: string[] = []; const mapping: string[] = [];
const unresolved: string[] = []; const unresolved: string[] = [];
for (const entry of resolved) { for (const entry of resolved) {
const source = entries.find((item) => item.input === entry.input); const source = sourceByInput.get(entry.input);
if (!source) { if (!source) {
continue; continue;
} }
const sourceGuild = guildEntries[source.guildKey] ?? {}; const sourceGuild = params.guildEntries[source.guildKey] ?? {};
if (!entry.resolved || !entry.guildId) { if (!entry.resolved || !entry.guildId) {
unresolved.push(entry.input); unresolved.push(entry.input);
continue; continue;
@@ -118,19 +126,27 @@ export async function resolveDiscordAllowlistConfig(params: {
} }
} }
} }
guildEntries = nextGuilds;
summarizeMapping("discord channels", mapping, unresolved, params.runtime); summarizeMapping("discord channels", mapping, unresolved, params.runtime);
} return nextGuilds;
} catch (err) { } catch (err) {
params.runtime.log?.( params.runtime.log?.(
`discord channel resolve failed; using config entries. ${formatErrorMessage(err)}`, `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<string[] | undefined> {
const allowEntries = const allowEntries =
allowFrom?.filter((entry) => String(entry).trim() && String(entry).trim() !== "*") ?? []; params.allowFrom?.filter((entry) => String(entry).trim() && String(entry).trim() !== "*") ?? [];
if (allowEntries.length > 0) { if (allowEntries.length === 0) {
return params.allowFrom;
}
try { try {
const resolvedUsers = await resolveDiscordUserAllowlist({ const resolvedUsers = await resolveDiscordUserAllowlist({
token: params.token, token: params.token,
@@ -138,19 +154,21 @@ export async function resolveDiscordAllowlistConfig(params: {
fetcher: params.fetcher, fetcher: params.fetcher,
}); });
const { resolvedMap, mapping, unresolved } = buildAllowlistResolutionSummary(resolvedUsers); const { resolvedMap, mapping, unresolved } = buildAllowlistResolutionSummary(resolvedUsers);
allowFrom = canonicalizeAllowlistWithResolvedIds({ const allowFrom = canonicalizeAllowlistWithResolvedIds({
existing: allowFrom, existing: params.allowFrom,
resolvedMap, resolvedMap,
}); });
summarizeMapping("discord users", mapping, unresolved, params.runtime); summarizeMapping("discord users", mapping, unresolved, params.runtime);
return allowFrom;
} catch (err) { } catch (err) {
params.runtime.log?.( params.runtime.log?.(
`discord user resolve failed; using config entries. ${formatErrorMessage(err)}`, `discord user resolve failed; using config entries. ${formatErrorMessage(err)}`,
); );
return params.allowFrom;
} }
} }
if (Object.keys(guildEntries).length > 0) { function collectGuildUserEntries(guildEntries: GuildEntries): Set<string> {
const userEntries = new Set<string>(); const userEntries = new Set<string>();
for (const guild of Object.values(guildEntries)) { for (const guild of Object.values(guildEntries)) {
if (!guild || typeof guild !== "object") { if (!guild || typeof guild !== "object") {
@@ -162,8 +180,19 @@ export async function resolveDiscordAllowlistConfig(params: {
addAllowlistUserEntriesFromConfigEntry(userEntries, channel); addAllowlistUserEntriesFromConfigEntry(userEntries, channel);
} }
} }
return userEntries;
}
if (userEntries.size > 0) { async function resolveGuildEntriesByUserAllowlist(params: {
token: string;
guildEntries: GuildEntries;
fetcher: typeof fetch;
runtime: RuntimeEnv;
}): Promise<GuildEntries> {
const userEntries = collectGuildUserEntries(params.guildEntries);
if (userEntries.size === 0) {
return params.guildEntries;
}
try { try {
const resolvedUsers = await resolveDiscordUserAllowlist({ const resolvedUsers = await resolveDiscordUserAllowlist({
token: params.token, token: params.token,
@@ -171,9 +200,8 @@ export async function resolveDiscordAllowlistConfig(params: {
fetcher: params.fetcher, fetcher: params.fetcher,
}); });
const { resolvedMap, mapping, unresolved } = buildAllowlistResolutionSummary(resolvedUsers); const { resolvedMap, mapping, unresolved } = buildAllowlistResolutionSummary(resolvedUsers);
const nextGuilds = { ...params.guildEntries };
const nextGuilds = { ...guildEntries }; for (const [guildKey, guildConfig] of Object.entries(params.guildEntries)) {
for (const [guildKey, guildConfig] of Object.entries(guildEntries ?? {})) {
if (!guildConfig || typeof guildConfig !== "object") { if (!guildConfig || typeof guildConfig !== "object") {
continue; continue;
} }
@@ -195,18 +223,53 @@ export async function resolveDiscordAllowlistConfig(params: {
} }
nextGuilds[guildKey] = nextGuild as DiscordGuildEntry; nextGuilds[guildKey] = nextGuild as DiscordGuildEntry;
} }
guildEntries = nextGuilds;
summarizeMapping("discord channel users", mapping, unresolved, params.runtime); summarizeMapping("discord channel users", mapping, unresolved, params.runtime);
return nextGuilds;
} catch (err) { } catch (err) {
params.runtime.log?.( params.runtime.log?.(
`discord channel user resolve failed; using config entries. ${formatErrorMessage(err)}`, `discord channel user resolve failed; using config entries. ${formatErrorMessage(err)}`,
); );
} return params.guildEntries;
} }
} }
export async function resolveDiscordAllowlistConfig(params: {
token: string;
guildEntries: unknown;
allowFrom: unknown;
fetcher: typeof fetch;
runtime: RuntimeEnv;
}): Promise<{ guildEntries: GuildEntries | undefined; allowFrom: string[] | undefined }> {
let guildEntries = toGuildEntries(params.guildEntries);
let allowFrom = toAllowlistEntries(params.allowFrom);
if (hasGuildEntries(guildEntries)) {
guildEntries = await resolveGuildEntriesByChannelAllowlist({
token: params.token,
guildEntries,
fetcher: params.fetcher,
runtime: params.runtime,
});
}
allowFrom = await resolveAllowFromByUserAllowlist({
token: params.token,
allowFrom,
fetcher: params.fetcher,
runtime: params.runtime,
});
if (hasGuildEntries(guildEntries)) {
guildEntries = await resolveGuildEntriesByUserAllowlist({
token: params.token,
guildEntries,
fetcher: params.fetcher,
runtime: params.runtime,
});
}
return { return {
guildEntries: Object.keys(guildEntries).length > 0 ? guildEntries : undefined, guildEntries: hasGuildEntries(guildEntries) ? guildEntries : undefined,
allowFrom, allowFrom,
}; };
} }