fix duplicate displayname/nickname matching issue

the matching logic now handles cases where there are multiple people with the same nickname or global name
This commit is contained in:
leander committed 2025-06-22 13:10:43 +02:00
1 parent 7715db4029
commit d096be3357
4 files changed
+68 -25

No files matched your search

+55 -18
View File
@@ -81,7 +81,7 @@ function memberMatchesInput(member: DiscordGuildMember, normalizedInput: string)
return username === normalizedInput || globalName === normalizedInput || nickname === normalizedInput;
}
export async function findDiscordMember(input: string): Promise<Match> {
export async function findDiscordMember(input: string, guessedIds: string[] = []): Promise<Match> {
const payingMemberRoleId = process.env.DISCORD_REQUIRED_ROLE_ID;
if (!payingMemberRoleId) {
@@ -96,31 +96,25 @@ export async function findDiscordMember(input: string): Promise<Match> {
try {
const members = await getDiscordMembers();
const payingMembers = members.filter((member) => member.roles.includes(payingMemberRoleId));
const normalizedInput = normalizeInput(input);
// First check if any member matches the input (regardless of role)
const anyMatchingMember = members.find((member) => memberMatchesInput(member, normalizedInput));
const allMatchingMembers = members.filter((member) => memberMatchesInput(member, normalizedInput));
// If member exists but doesn't have the paying member role
if (anyMatchingMember && !anyMatchingMember.roles.includes(payingMemberRoleId)) {
return {
input,
name: getDisplayName(anyMatchingMember),
description: undefined,
translationKey: "notPayingMember",
correct: false,
};
if (allMatchingMembers.length === 0) {
return { input, name: undefined, description: undefined, correct: false };
}
// Find matching member among paying members
const matchingMember = payingMembers.find((member) => memberMatchesInput(member, normalizedInput));
const payingMembers = allMatchingMembers.filter((member) => member.roles.includes(payingMemberRoleId));
const nonPayingMembers = allMatchingMembers.filter((member) => !member.roles.includes(payingMemberRoleId));
if (matchingMember) {
const displayName = getDisplayName(matchingMember);
const username = matchingMember.user.username;
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,
@@ -128,6 +122,49 @@ export async function findDiscordMember(input: string): Promise<Match> {
};
}
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,
+2 -2
View File
@@ -9,7 +9,7 @@ export async function POST(request: Request) {
if (!session) {
return new Response("Unauthorized", { status: 401 });
}
const { input } = await request.json();
const match: Match = await findDiscordMember(input);
const { input, guessedIds } = await request.json();
const match: Match = await findDiscordMember(input, guessedIds);
return NextResponse.json(match);
}
+10 -5
View File
@@ -73,7 +73,7 @@ export default function HomeClient({ session }: { session: Session }) {
e.preventDefault();
const trimmedInput = input.trim();
if (!trimmedInput) return;
if (matches.some((m) => m.input.toLowerCase() === trimmedInput.toLowerCase())) {
if (matches.some((m) => m.input.toLowerCase() === trimmedInput.toLowerCase() && !m.name)) {
setRepeatMessage(t.alreadySubmitted);
return;
}
@@ -81,19 +81,24 @@ export default function HomeClient({ session }: { session: Session }) {
setLoading(true);
try {
const guessedIds = matches.filter((m) => m.correct && m.id).map((m) => m.id!);
const res = await fetch("/api/guess", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ input: trimmedInput }),
body: JSON.stringify({ input: trimmedInput, guessedIds }),
});
if (res.ok) {
const data = await res.json();
if (data.name && matches.some((m) => m.name && m.name.toLowerCase() === data.name.toLowerCase())) {
const data = (await res.json()) as Match;
if (data.id && matches.some((m) => m.id && m.id === data.id)) {
setRepeatMessage(t.alreadySubmitted);
setInput("");
return;
}
setMatches((prev) => [...prev, { input: trimmedInput, ...data }]);
setMatches((prev) => [...prev, data]);
if (data.correct && !timerStarted) {
startTimer();
}
+1
View File
@@ -1,4 +1,5 @@
export interface Match {
id?: string;
input: string;
name: string | undefined;
description: string | undefined;