export type DiscordUser = { nick: string | null; user: { global_name: string | null; username: string; id: string }; }; export type CachedDiscordUser = { username: string; uid: string; display: string; }; const normalize = (member: DiscordUser): CachedDiscordUser => ({ username: member.user.username, display: member.nick ?? member.user.global_name ?? member.user.username, uid: member.user.id, }); type DiscordUsersDependencies = { adminCheck: () => Promise; forbidden: () => never; getCached: () => Promise; setCached: (value: string) => Promise | unknown; getMembers: (after: string) => Promise; }; export function createDiscordUsersService( dependencies: DiscordUsersDependencies, ) { return async function getDiscordUsers() { if (!(await dependencies.adminCheck())) dependencies.forbidden(); const cached = await dependencies.getCached(); if (cached) return JSON.parse(cached) as CachedDiscordUser[]; const members: CachedDiscordUser[] = []; let after = "0"; while (true) { const batch = await dependencies.getMembers(after); if (!batch.length) break; members.push(...batch.map(normalize)); after = batch.at(-1)!.user.id; if (batch.length < 1000) break; } await dependencies.setCached(JSON.stringify(members)); return members; }; } export function createDiscordMemberAddHandler(dependencies: { getCached: () => Promise; setCached: (value: string) => Promise | unknown; }) { return async function onMemberAdd(member: DiscordUser) { const cached: CachedDiscordUser[] = JSON.parse( (await dependencies.getCached()) ?? "[]", ); if (!cached.some((user) => user.uid === member.user.id)) { cached.push(normalize(member)); await dependencies.setCached(JSON.stringify(cached)); } }; }