import { AutocompleteInteraction } from "discord.js"; import { resolveUser } from "@systems/users"; import { getCharacters } from "@systems/characters"; import { cfg } from "@systems/config"; import { getNationEmoji } from "@systems/emojis"; import fs from "fs"; import path from "path"; import { Nation } from "@types"; import { Paths } from "@helpers/paths"; let _charCache: Record | null = null; function getCharCache(): Record | null { if (!_charCache) { try { _charCache = JSON.parse(fs.readFileSync(Paths.data("characters.json"), "utf8")); } catch { _charCache = {}; } } return _charCache; } export function invalidateCharCache(): void { _charCache = null; } // ─── Autocomplete subsets ───────────────────────────────────────────────────── async function autocompleteCharNames( interaction: AutocompleteInteraction, focused: string ): Promise { const member = await interaction.guild!.members.fetch(interaction.user.id); const user = await resolveUser(member); if (!user.userKey) return interaction.respond([]); const ownChars = getCharacters(user.userKey).map((c) => { const nationEmoji = c.nation ? (getNationEmoji(c.nation) || c.nation) : ""; return { name: `${c.class} ${c.level} ${c.name} ${nationEmoji}`.trim(), value: c.name, }; }); const sharedChars: { name: string; value: string }[] = []; try { const chars = JSON.parse( fs.readFileSync(path.join(__dirname, "../../data/characters.json"), "utf8") ); for (const [ownerKey, data] of Object.entries(chars) as [string, any][]) { if (ownerKey === user.userKey) continue; for (const char of data.characters ?? []) { if (char.sharedWith?.includes(user.userKey)) { const nationEmoji = char.nation ? (getNationEmoji(char.nation) || char.nation) : ""; sharedChars.push({ name: `${char.class} ${char.level} ${char.name} 🔗 ${nationEmoji}`.trim(), value: char.name, }); } } } } catch {} const all = [...ownChars, ...sharedChars] .filter((c) => c.name.toLowerCase().includes(focused.toLowerCase())) .slice(0, 25); await interaction.respond(all); } async function autocompleteUserKeys( interaction: AutocompleteInteraction, focused: string ): Promise { try { const usermap = JSON.parse( fs.readFileSync(path.join(__dirname, "../../data/usermap.json"), "utf8") ); const choices = Object.entries(usermap) .map(([, entry]: [string, any]) => { const fileKey = typeof entry === "string" ? entry : entry.file; const alias = typeof entry === "object" ? (entry.aliases?.[0] ?? fileKey) : fileKey; return { name: `${alias} (${fileKey})`, value: fileKey }; }) .filter((c) => c.name.toLowerCase().includes(focused.toLowerCase())) .slice(0, 25); await interaction.respond(choices); } catch { await interaction.respond([]); } } async function autocompleteSlots( interaction: AutocompleteInteraction, focused: string ): Promise { const slots = cfg("slots") .filter((s) => s.active) .map((s) => ({ name: `${s.tgHour}:00`, value: String(s.tgHour) })) .filter((s) => s.name.includes(focused)); await interaction.respond(slots); } async function autocompleteCharNamesForNation( interaction: AutocompleteInteraction, focused: string, nation: Nation | null ): Promise { const chars = JSON.parse( fs.readFileSync(Paths.data("characters.json"), "utf8") ); const results: { name: string; value: string }[] = []; for (const data of Object.values(chars) as any[]) { for (const char of data.characters ?? []) { if (nation && char.nation !== nation) continue; if (!char.name.toLowerCase().includes(focused.toLowerCase())) continue; const nationEmoji = char.nation ? (getNationEmoji(char.nation) || char.nation) : ""; results.push({ name: `${char.class} ${char.level} ${char.name} ${nationEmoji}`.trim(), value: char.name, }); } } await interaction.respond(results.slice(0, 25)); } // ─── Router ─────────────────────────────────────────────────────────────────── export async function handleAutocomplete(interaction: AutocompleteInteraction): Promise { try { const focused = interaction.options.getFocused(true); const optionName = focused.name; const focusedValue = focused.value as string; if (optionName === "char_name") return await autocompleteCharNames(interaction, focusedValue); if (optionName === "name") return await autocompleteUserKeys(interaction, focusedValue); if (optionName === "slot") return await autocompleteSlots(interaction, focusedValue); if (optionName === "owner") return await autocompleteUserKeys(interaction, focusedValue); if (optionName === "char_name") { const commandName = interaction.commandName; const subGroup = interaction.options.getSubcommandGroup(false); const sub = interaction.options.getSubcommand(false); if (sub === "set" && subGroup === "bringer") { // Filter by selected nation const nation = interaction.options.getString("nation") as Nation | null; return await autocompleteCharNamesForNation(interaction, focusedValue, nation); } return await autocompleteCharNames(interaction, focusedValue); } await interaction.respond([]); } catch (err) { console.error("[autocomplete] error:", err); try { await interaction.respond([]); } catch {} } }