From 8c1f80981ba88151d6c41a4248510457307c75a2 Mon Sep 17 00:00:00 2001 From: syntaxbullet Date: Thu, 18 Dec 2025 14:34:47 +0100 Subject: [PATCH] feat: Introduce a dedicated autocomplete handler for commands and refactor the inventory `use` command to utilize it. --- src/commands/inventory/use.ts | 60 +++++++++++++-------------------- src/events/interactionCreate.ts | 11 ++++++ src/lib/types.ts | 3 +- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/commands/inventory/use.ts b/src/commands/inventory/use.ts index e44be34..1c5bff0 100644 --- a/src/commands/inventory/use.ts +++ b/src/commands/inventory/use.ts @@ -19,34 +19,6 @@ export const use = createCommand({ .setAutocomplete(true) ), execute: async (interaction) => { - if (!interaction.isChatInputCommand()) { - if (interaction.isAutocomplete()) { - const focusedValue = interaction.options.getFocused(); - const userId = interaction.user.id; - - // Fetch owned items that are usable - const userInventory = await DrizzleClient.query.inventory.findMany({ - where: eq(inventory.userId, BigInt(userId)), - with: { - item: true - }, - limit: 10 - }); - - const filtered = userInventory.filter(entry => { - const matchName = entry.item.name.toLowerCase().includes(focusedValue.toLowerCase()); - const usageData = entry.item.usageData as ItemUsageData | null; - const isUsable = usageData && usageData.effects && usageData.effects.length > 0; - return matchName && isUsable; - }); - - await interaction.respond( - filtered.map(entry => ({ name: `${entry.item.name} (${entry.quantity})`, value: entry.item.id })) - ); - } - return; - } - await interaction.deferReply(); const itemId = interaction.options.getNumber("item", true); @@ -55,14 +27,6 @@ export const use = createCommand({ try { const result = await inventoryService.useItem(user.id, itemId); - // Check for side effects like Role assignment that need Discord API access - // The service returns the usageData, so we can re-check simple effects or just check the results log? - // Actually, we put "TEMP_ROLE" inside results log, AND we can check usageData here for strict role assignment if we want to separate concerns. - // But for now, let's rely on the service to have handled database state, and we handle Discord state here if needed? - // WAIT - I put the role assignment placeholder in the service but it returned a result string. - // The service cannot assign the role directly because it doesn't have the member object easily (requires fetching). - // So we should iterate results or usageData here. - const usageData = result.usageData; if (usageData) { for (const effect of usageData.effects) { @@ -91,5 +55,29 @@ export const use = createCommand({ } catch (error: any) { await interaction.editReply({ embeds: [createErrorEmbed(error.message)] }); } + }, + autocomplete: async (interaction) => { + const focusedValue = interaction.options.getFocused(); + const userId = interaction.user.id; + + // Fetch owned items that are usable + const userInventory = await DrizzleClient.query.inventory.findMany({ + where: eq(inventory.userId, BigInt(userId)), + with: { + item: true + }, + limit: 10 + }); + + const filtered = userInventory.filter(entry => { + const matchName = entry.item.name.toLowerCase().includes(focusedValue.toLowerCase()); + const usageData = entry.item.usageData as ItemUsageData | null; + const isUsable = usageData && usageData.effects && usageData.effects.length > 0; + return matchName && isUsable; + }); + + await interaction.respond( + filtered.map(entry => ({ name: `${entry.item.name} (${entry.quantity})`, value: entry.item.id })) + ); } }); diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index 5277510..3963e8e 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -19,6 +19,17 @@ const event: Event = { } } + if (interaction.isAutocomplete()) { + const command = KyokoClient.commands.get(interaction.commandName); + if (!command || !command.autocomplete) return; + try { + await command.autocomplete(interaction); + } catch (error) { + console.error(`Error handling autocomplete for ${interaction.commandName}:`, error); + } + return; + } + if (!interaction.isChatInputCommand()) return; const command = KyokoClient.commands.get(interaction.commandName); diff --git a/src/lib/types.ts b/src/lib/types.ts index d25cb88..97f40b6 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,8 +1,9 @@ -import type { ChatInputCommandInteraction, ClientEvents, SlashCommandBuilder, SlashCommandOptionsOnlyBuilder, SlashCommandSubcommandsOnlyBuilder } from "discord.js"; +import type { AutocompleteInteraction, ChatInputCommandInteraction, ClientEvents, SlashCommandBuilder, SlashCommandOptionsOnlyBuilder, SlashCommandSubcommandsOnlyBuilder } from "discord.js"; export interface Command { data: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder | SlashCommandSubcommandsOnlyBuilder; execute: (interaction: ChatInputCommandInteraction) => Promise | void; + autocomplete?: (interaction: AutocompleteInteraction) => Promise | void; category?: string; }