import { createCommand } from "@/lib/utils"; import { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, type BaseGuildTextChannel, PermissionFlagsBits, MessageFlags } from "discord.js"; import { inventoryService } from "@/modules/inventory/inventory.service"; import { createSuccessEmbed, createErrorEmbed } from "@lib/embeds"; import { UserError } from "@/lib/errors"; import { items } from "@/db/schema"; import { ilike, isNotNull, and } from "drizzle-orm"; import { DrizzleClient } from "@/lib/DrizzleClient"; export const listing = createCommand({ data: new SlashCommandBuilder() .setName("listing") .setDescription("Post an item listing in the channel for users to buy") .addNumberOption(option => option.setName("item") .setDescription("The item to list") .setRequired(true) .setAutocomplete(true) ) .addChannelOption(option => option.setName("channel") .setDescription("The channel to post the listing in (defaults to current)") .setRequired(false) ) .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), execute: async (interaction) => { await interaction.deferReply({ flags: MessageFlags.Ephemeral }); const itemId = interaction.options.getNumber("item", true); const targetChannel = (interaction.options.getChannel("channel") as BaseGuildTextChannel) || interaction.channel as BaseGuildTextChannel; if (!targetChannel || !targetChannel.isSendable()) { await interaction.editReply({ content: "", embeds: [createErrorEmbed("Target channel is invalid or not sendable.")] }); return; } const item = await inventoryService.getItem(itemId); if (!item) { await interaction.editReply({ content: "", embeds: [createErrorEmbed(`Item with ID ${itemId} not found.`)] }); return; } if (!item.price) { await interaction.editReply({ content: "", embeds: [createErrorEmbed(`Item "${item.name}" is not for sale (no price set).`)] }); return; } const embed = new EmbedBuilder() .setTitle(`Shop: ${item.name}`) .setDescription(item.description || "No description available.") .addFields({ name: "Price", value: `${item.price} 🪙`, inline: true }) .setColor("Green") .setThumbnail(item.iconUrl || null) .setImage(item.imageUrl || null) .setFooter({ text: "Click the button below to purchase instantly." }); const buyButton = new ButtonBuilder() .setCustomId(`shop_buy_${item.id}`) .setLabel(`Buy for ${item.price} 🪙`) .setStyle(ButtonStyle.Success) .setEmoji("🛒"); const actionRow = new ActionRowBuilder().addComponents(buyButton); try { await targetChannel.send({ embeds: [embed], components: [actionRow] }); await interaction.editReply({ content: `✅ Listing for **${item.name}** posted in ${targetChannel}.` }); } catch (error: any) { if (error instanceof UserError) { await interaction.reply({ embeds: [createErrorEmbed(error.message)], ephemeral: true }); } else { console.error("Error creating listing:", error); await interaction.reply({ embeds: [createErrorEmbed("An unexpected error occurred.")], ephemeral: true }); } } }, autocomplete: async (interaction) => { const focusedValue = interaction.options.getFocused(); const results = await DrizzleClient.select({ id: items.id, name: items.name, price: items.price }) .from(items) .where( and( ilike(items.name, `%${focusedValue}%`), isNotNull(items.price) ) ) .limit(20); await interaction.respond( results.map(item => ({ name: `${item.name} (Price: ${item.price})`, value: item.id })) ); } });