import { AutocompleteInteraction } from "discord.js"; import { resolveUser } from "@systems/users"; import { getCharacters, getCharacterByName } from "@systems/characters"; import { getPersistentPreference, getSessionBorrow } from "@systems/borrow"; import { cfg } from "@systems/config"; import fs from "fs"; import path from "path"; // ─── Autocomplete subsets ───────────────────────────────────────────────────── // Character names — own chars + shared chars accessible to this user 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) => ({ name: `${c.class} ${c.level} ${c.name}`, value: c.name, })); // Include shared chars (scan all users' sharedWith arrays) 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)) { sharedChars.push({ name: `${char.class} ${char.level} ${char.name} (shared by ${ownerKey})`, value: char.name, }); } } } } catch {} const all = [...ownChars, ...sharedChars] .filter((c) => c.name.toLowerCase().includes(focused.toLowerCase())) .slice(0, 25); await interaction.respond(all); } // Usermap keys — all registered users (officer commands) 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([]); } } // TG slots — active slot hours 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); } // ─── 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; // Route by option name if (optionName === "char_name") { return await autocompleteCharNames(interaction, focusedValue); } if (optionName === "name") { // Officer user key args return await autocompleteUserKeys(interaction, focusedValue); } if (optionName === "slot") { return await autocompleteSlots(interaction, focusedValue); } await interaction.respond([]); } catch (err) { console.error("[autocomplete] error:", err); try { await interaction.respond([]); } catch {} } }