const MINECRAFT_USERNAME = /^[A-Za-z0-9_]{3,16}$/; const MINECRAFT_UUID = /^[0-9a-f]{32}$/i; const DISCORD_NICKNAME_LIMIT = 32; export interface JavaProfile { uuid: string; username: string; } export async function lookupJavaProfile( username: string, request: typeof fetch = fetch, ): Promise { const candidate = username.trim(); if (!MINECRAFT_USERNAME.test(candidate)) return null; const response = await request( `https://api.mojang.com/users/profiles/minecraft/${encodeURIComponent(candidate)}`, { headers: { accept: "application/json" }, cache: "no-store" }, ); if (response.status === 204 || response.status === 404) return null; if (!response.ok) throw new Error(`Mojang profile lookup failed (${response.status})`); const profile: unknown = await response.json(); if (!profile || typeof profile !== "object") return null; const { id, name } = profile as { id?: unknown; name?: unknown }; if (typeof id !== "string" || !MINECRAFT_UUID.test(id)) return null; if (typeof name !== "string" || !MINECRAFT_USERNAME.test(name)) return null; return { uuid: id.toLowerCase(), username: name }; } export function formatDiscordNickname(firstName: string, minecraftUsername: string) { const suffix = ` (${minecraftUsername})`; const availableCharacters = DISCORD_NICKNAME_LIMIT - [...suffix].length; const shortenedName = [...firstName.trim()].slice(0, Math.max(1, availableCharacters)).join("").trimEnd(); return `${shortenedName}${suffix}`; } export function formatManagedDiscordNickname( firstName: string, minecraftUsername: string | null, ) { if (minecraftUsername) return formatDiscordNickname(firstName, minecraftUsername); return formatDiscordNickname(firstName, "TBD"); } export interface DiscordGuildIdentity { id: string; username: string; globalName: string | null; nickname: string | null; } export async function getGuildMemberIdentity( input: { guildId: string; discordUserId: string; botToken: string }, request: typeof fetch = fetch, ): Promise { const response = await request( `https://discord.com/api/v10/guilds/${input.guildId}/members/${input.discordUserId}`, { headers: { authorization: `Bot ${input.botToken}`, accept: "application/json", }, cache: "no-store", }, ); if (!response.ok) throw new Error(`Discord guild member lookup failed (${response.status})`); const payload: unknown = await response.json(); if (!payload || typeof payload !== "object") throw new Error("Discord guild member lookup returned invalid data"); const member = payload as { nick?: unknown; user?: unknown }; if (!member.user || typeof member.user !== "object") throw new Error("Discord guild member lookup omitted user data"); const user = member.user as { id?: unknown; username?: unknown; global_name?: unknown }; if (typeof user.id !== "string" || typeof user.username !== "string") { throw new Error("Discord guild member lookup returned invalid user data"); } return { id: user.id, username: user.username, globalName: typeof user.global_name === "string" ? user.global_name : null, nickname: typeof member.nick === "string" ? member.nick : null, }; } export async function updateGuildNickname( input: { guildId: string; discordUserId: string; nickname: string; botToken: string; }, request: typeof fetch = fetch, ) { const response = await request( `https://discord.com/api/v10/guilds/${input.guildId}/members/${input.discordUserId}`, { method: "PATCH", headers: { authorization: `Bot ${input.botToken}`, "content-type": "application/json", }, body: JSON.stringify({ nick: input.nickname }), }, ); if (!response.ok) { throw new Error(`Discord nickname update failed (${response.status})`); } }