diff --git a/src/app/api/guess/route.ts b/src/app/api/guess/route.ts index a66e6f9..a5a664a 100644 --- a/src/app/api/guess/route.ts +++ b/src/app/api/guess/route.ts @@ -1,6 +1,5 @@ import { authOptions } from "@/lib/auth"; import { findDiscordMember } from "@/lib/members"; -import type { Match } from "@/types"; import { getServerSession } from "next-auth/next"; import { NextResponse } from "next/server"; @@ -10,6 +9,11 @@ export async function POST(request: Request) { return new Response("Unauthorized", { status: 401 }); } const { input, guessedIds } = await request.json(); - const match: Match = await findDiscordMember(input, guessedIds); - return NextResponse.json(match); + + try { + return NextResponse.json(await findDiscordMember(input, guessedIds)); + } catch (error) { + console.error("Error finding Discord member:", error); + return NextResponse.json({ error: "Failed to look up member" }, { status: 503 }); + } } diff --git a/src/app/lib/members.ts b/src/app/lib/members.ts index 0062aa7..38f738a 100644 --- a/src/app/lib/members.ts +++ b/src/app/lib/members.ts @@ -57,124 +57,55 @@ function getDisplayName(member: DiscordGuildMember): string { return member.nick || member.user.global_name || member.user.username; } -function normalizeInput(input: string): string { - return input.toLowerCase().trim(); -} - function memberMatchesInput(member: DiscordGuildMember, normalizedInput: string): boolean { - const username = member.user.username.toLowerCase(); - const globalName = member.user.global_name?.toLowerCase() || ""; - const nickname = member.nick?.toLowerCase() || ""; - - return username === normalizedInput || globalName === normalizedInput || nickname === normalizedInput; + return [member.user.username, member.user.global_name, member.nick].some( + (name) => name?.toLowerCase() === normalizedInput, + ); } export async function getPayingMemberCount(): Promise { - const payingMemberRoleId = process.env.DISCORD_REQUIRED_ROLE_ID; - if (!payingMemberRoleId) { - throw new Error("Missing DISCORD_REQUIRED_ROLE_ID environment variable"); - } + const payingMemberRoleId = requireEnv("DISCORD_REQUIRED_ROLE_ID"); const members = await getDiscordMembers(); return members.filter((m) => m.roles.includes(payingMemberRoleId)).length; } -export async function findDiscordMember(input: string, guessedIds: string[] = []): Promise { - const payingMemberRoleId = process.env.DISCORD_REQUIRED_ROLE_ID; +export async function findDiscordMember(input: string, guessedIds: string[]): Promise { + const payingMemberRoleId = requireEnv("DISCORD_REQUIRED_ROLE_ID"); + const normalizedInput = input.toLowerCase().trim(); + const guessed = new Set(guessedIds); - if (!payingMemberRoleId) { - console.error("Missing required Discord environment variables"); + const candidates = (await getDiscordMembers()).filter((member) => memberMatchesInput(member, normalizedInput)); + const isPaying = (member: DiscordGuildMember) => member.roles.includes(payingMemberRoleId); + const isUnguessed = (member: DiscordGuildMember) => !guessed.has(member.user.id); + + const member = + candidates.find((m) => isPaying(m) && isUnguessed(m)) ?? + candidates.find((m) => !isPaying(m) && isUnguessed(m)) ?? + candidates.find(isPaying) ?? + candidates[0]; + + if (!member) { + return { input, name: undefined, description: undefined, correct: false }; + } + + const name = getDisplayName(member); + + if (!isPaying(member)) { return { + id: member.user.id, input, - name: undefined, + name, description: undefined, + translationKey: "notPayingMember", correct: false, }; } - try { - const members = await getDiscordMembers(); - const normalizedInput = normalizeInput(input); - - const allMatchingMembers = members.filter((member) => memberMatchesInput(member, normalizedInput)); - - if (allMatchingMembers.length === 0) { - return { input, name: undefined, description: undefined, correct: false }; - } - - const payingMembers = allMatchingMembers.filter((member) => member.roles.includes(payingMemberRoleId)); - const nonPayingMembers = allMatchingMembers.filter((member) => !member.roles.includes(payingMemberRoleId)); - - const unguessedPayingMember = payingMembers.find((member) => !guessedIds.includes(member.user.id)); - - if (unguessedPayingMember) { - const displayName = getDisplayName(unguessedPayingMember); - const username = unguessedPayingMember.user.username; - - return { - id: unguessedPayingMember.user.id, - input, - name: displayName, - description: displayName === username ? undefined : username, - correct: true, - }; - } - - const unguessedNonPayingMember = nonPayingMembers.find((member) => !guessedIds.includes(member.user.id)); - - if (unguessedNonPayingMember) { - return { - id: unguessedNonPayingMember.user.id, - input, - name: getDisplayName(unguessedNonPayingMember), - description: undefined, - translationKey: "notPayingMember", - correct: false, - }; - } - - // If we are here, all matching members (paying or not) have been guessed. - // Return the first paying member match to signal a duplicate to the client. - if (payingMembers.length > 0) { - const firstPayingMatch = payingMembers[0]; - const displayName = getDisplayName(firstPayingMatch); - const username = firstPayingMatch.user.username; - - return { - id: firstPayingMatch.user.id, - input, - name: displayName, - description: displayName === username ? undefined : username, - correct: true, // It is a correct member, but already guessed. - }; - } - - // If no paying members matched, return the first non-paying member. - if (nonPayingMembers.length > 0) { - const firstNonPayingMatch = nonPayingMembers[0]; - return { - id: firstNonPayingMatch.user.id, - input, - name: getDisplayName(firstNonPayingMatch), - description: undefined, - translationKey: "notPayingMember", - correct: false, - }; - } - - // Should not be reached if allMatchingMembers.length > 0 - return { - input, - name: undefined, - description: undefined, - correct: false, - }; - } catch (error) { - console.error("Error finding Discord member:", error); - return { - input, - name: undefined, - description: undefined, - correct: false, - }; - } + return { + id: member.user.id, + input, + name, + description: name === member.user.username ? undefined : member.user.username, + correct: true, + }; }