diff --git a/src/discord/resolve-allowlist-common.test.ts b/src/discord/resolve-allowlist-common.test.ts new file mode 100644 index 00000000000..338fae1bd0d --- /dev/null +++ b/src/discord/resolve-allowlist-common.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from "vitest"; +import { + buildDiscordUnresolvedResults, + filterDiscordGuilds, + findDiscordGuildByName, + resolveDiscordAllowlistToken, +} from "./resolve-allowlist-common.js"; + +describe("resolve-allowlist-common", () => { + const guilds = [ + { id: "1", name: "Main Guild", slug: "main-guild" }, + { id: "2", name: "Ops Guild", slug: "ops-guild" }, + ]; + + it("resolves and filters guilds by id or name", () => { + expect(findDiscordGuildByName(guilds, "Main Guild")?.id).toBe("1"); + expect(filterDiscordGuilds(guilds, { guildId: "2" })).toEqual([guilds[1]]); + expect(filterDiscordGuilds(guilds, { guildName: "main-guild" })).toEqual([guilds[0]]); + }); + + it("builds unresolved result rows in input order", () => { + const unresolved = buildDiscordUnresolvedResults(["a", "b"], (input) => ({ + input, + resolved: false, + })); + expect(unresolved).toEqual([ + { input: "a", resolved: false }, + { input: "b", resolved: false }, + ]); + }); + + it("normalizes allowlist token values", () => { + expect(resolveDiscordAllowlistToken(" discord-token ")).toBe("discord-token"); + expect(resolveDiscordAllowlistToken("")).toBeUndefined(); + }); +}); diff --git a/src/discord/resolve-allowlist-common.ts b/src/discord/resolve-allowlist-common.ts new file mode 100644 index 00000000000..9831e390002 --- /dev/null +++ b/src/discord/resolve-allowlist-common.ts @@ -0,0 +1,39 @@ +import type { DiscordGuildSummary } from "./guilds.js"; +import { normalizeDiscordSlug } from "./monitor/allow-list.js"; +import { normalizeDiscordToken } from "./token.js"; + +export function resolveDiscordAllowlistToken(token: string): string | undefined { + return normalizeDiscordToken(token, "channels.discord.token"); +} + +export function buildDiscordUnresolvedResults( + entries: string[], + buildResult: (input: string) => T, +): T[] { + return entries.map((input) => buildResult(input)); +} + +export function findDiscordGuildByName( + guilds: DiscordGuildSummary[], + input: string, +): DiscordGuildSummary | undefined { + const slug = normalizeDiscordSlug(input); + if (!slug) { + return undefined; + } + return guilds.find((guild) => guild.slug === slug); +} + +export function filterDiscordGuilds( + guilds: DiscordGuildSummary[], + params: { guildId?: string; guildName?: string }, +): DiscordGuildSummary[] { + if (params.guildId) { + return guilds.filter((guild) => guild.id === params.guildId); + } + if (params.guildName) { + const match = findDiscordGuildByName(guilds, params.guildName); + return match ? [match] : []; + } + return guilds; +} diff --git a/src/discord/resolve-channels.ts b/src/discord/resolve-channels.ts index ba7fbcdf8d5..b881a73b8b1 100644 --- a/src/discord/resolve-channels.ts +++ b/src/discord/resolve-channels.ts @@ -1,7 +1,11 @@ import { DiscordApiError, fetchDiscord } from "./api.js"; -import { listGuilds, type DiscordGuildSummary } from "./guilds.js"; +import { listGuilds } from "./guilds.js"; import { normalizeDiscordSlug } from "./monitor/allow-list.js"; -import { normalizeDiscordToken } from "./token.js"; +import { + buildDiscordUnresolvedResults, + filterDiscordGuilds, + resolveDiscordAllowlistToken, +} from "./resolve-allowlist-common.js"; type DiscordChannelSummary = { id: string; @@ -146,25 +150,14 @@ function preferActiveMatch(candidates: DiscordChannelSummary[]): DiscordChannelS return scored[0]?.channel ?? candidates[0]; } -function resolveGuildByName( - guilds: DiscordGuildSummary[], - input: string, -): DiscordGuildSummary | undefined { - const slug = normalizeDiscordSlug(input); - if (!slug) { - return undefined; - } - return guilds.find((guild) => guild.slug === slug); -} - export async function resolveDiscordChannelAllowlist(params: { token: string; entries: string[]; fetcher?: typeof fetch; }): Promise { - const token = normalizeDiscordToken(params.token, "channels.discord.token"); + const token = resolveDiscordAllowlistToken(params.token); if (!token) { - return params.entries.map((input) => ({ + return buildDiscordUnresolvedResults(params.entries, (input) => ({ input, resolved: false, })); @@ -187,11 +180,10 @@ export async function resolveDiscordChannelAllowlist(params: { for (const input of params.entries) { const parsed = parseDiscordChannelInput(input); if (parsed.guildOnly) { - const guildById = parsed.guildId - ? guilds.find((entry) => entry.id === parsed.guildId) - : undefined; - const guild = - guildById ?? (parsed.guild ? resolveGuildByName(guilds, parsed.guild) : undefined); + const guild = filterDiscordGuilds(guilds, { + guildId: parsed.guildId, + guildName: parsed.guild, + })[0]; if (guild) { results.push({ input, @@ -277,11 +269,10 @@ export async function resolveDiscordChannelAllowlist(params: { } if (parsed.guildId || parsed.guild) { - const guildById = parsed.guildId - ? guilds.find((entry) => entry.id === parsed.guildId) - : undefined; - const guild = - guildById ?? (parsed.guild ? resolveGuildByName(guilds, parsed.guild) : undefined); + const guild = filterDiscordGuilds(guilds, { + guildId: parsed.guildId, + guildName: parsed.guild, + })[0]; const channelQuery = parsed.channel?.trim(); if (!guild || !channelQuery) { results.push({ diff --git a/src/discord/resolve-users.ts b/src/discord/resolve-users.ts index 3d3b99a89c6..d71edf6234f 100644 --- a/src/discord/resolve-users.ts +++ b/src/discord/resolve-users.ts @@ -1,7 +1,10 @@ import { fetchDiscord } from "./api.js"; import { listGuilds, type DiscordGuildSummary } from "./guilds.js"; -import { normalizeDiscordSlug } from "./monitor/allow-list.js"; -import { normalizeDiscordToken } from "./token.js"; +import { + buildDiscordUnresolvedResults, + filterDiscordGuilds, + resolveDiscordAllowlistToken, +} from "./resolve-allowlist-common.js"; type DiscordUser = { id: string; @@ -80,9 +83,9 @@ export async function resolveDiscordUserAllowlist(params: { entries: string[]; fetcher?: typeof fetch; }): Promise { - const token = normalizeDiscordToken(params.token, "channels.discord.token"); + const token = resolveDiscordAllowlistToken(params.token); if (!token) { - return params.entries.map((input) => ({ + return buildDiscordUnresolvedResults(params.entries, (input) => ({ input, resolved: false, })); @@ -119,13 +122,11 @@ export async function resolveDiscordUserAllowlist(params: { continue; } - const guildName = parsed.guildName?.trim(); const allGuilds = await getGuilds(); - const guildList = parsed.guildId - ? allGuilds.filter((g) => g.id === parsed.guildId) - : guildName - ? allGuilds.filter((g) => g.slug === normalizeDiscordSlug(guildName)) - : allGuilds; + const guildList = filterDiscordGuilds(allGuilds, { + guildId: parsed.guildId, + guildName: parsed.guildName?.trim(), + }); let best: { member: DiscordMember; guild: DiscordGuildSummary; score: number } | null = null; let matches = 0;