forked from syntaxbullet/AuroraBot-discord
feat: Introduced an admin listing command and shop interaction module, replacing the sell command, and added a type-checking script.
This commit is contained in:
77
src/commands/admin/listing.ts
Normal file
77
src/commands/admin/listing.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
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 { createErrorEmbed, createWarningEmbed } from "@lib/embeds";
|
||||
|
||||
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("itemid")
|
||||
.setDescription("The ID of the item to list")
|
||||
.setRequired(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("itemid", 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: [createWarningEmbed(`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<ButtonBuilder>().addComponents(buyButton);
|
||||
|
||||
try {
|
||||
await targetChannel.send({ embeds: [embed], components: [actionRow] });
|
||||
await interaction.editReply({ content: `✅ Listing for **${item.name}** posted in ${targetChannel}.` });
|
||||
} catch (error) {
|
||||
console.error("Failed to send listing message:", error);
|
||||
await interaction.editReply({ content: "", embeds: [createErrorEmbed("Failed to post the listing.")] });
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,125 +0,0 @@
|
||||
import { createCommand } from "@/lib/utils";
|
||||
import {
|
||||
SlashCommandBuilder,
|
||||
EmbedBuilder,
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
ComponentType,
|
||||
type BaseGuildTextChannel,
|
||||
type ButtonInteraction,
|
||||
PermissionFlagsBits,
|
||||
MessageFlags
|
||||
} from "discord.js";
|
||||
import { userService } from "@/modules/user/user.service";
|
||||
import { inventoryService } from "@/modules/inventory/inventory.service";
|
||||
import type { items } from "@db/schema";
|
||||
import { createErrorEmbed, createWarningEmbed } from "@lib/embeds";
|
||||
|
||||
export const sell = createCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("sell")
|
||||
.setDescription("Post an item for sale in the current channel so regular users can buy it")
|
||||
.addNumberOption(option =>
|
||||
option.setName("itemid")
|
||||
.setDescription("The ID of the item to sell")
|
||||
.setRequired(true)
|
||||
)
|
||||
.addChannelOption(option =>
|
||||
option.setName("channel")
|
||||
.setDescription("The channel to post the item in")
|
||||
.setRequired(false)
|
||||
)
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||
execute: async (interaction) => {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
|
||||
const itemId = interaction.options.getNumber("itemid", 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: [createWarningEmbed(`Item "${item.name}" is not for sale (no price set).`)] });
|
||||
return;
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(`Item for sale: ${item.name}`)
|
||||
.setDescription(item.description || "No description available.")
|
||||
.addFields({ name: "Price", value: `${item.price} 🪙`, inline: true })
|
||||
.setColor("Yellow")
|
||||
.setThumbnail(item.iconUrl || null)
|
||||
.setImage(item.imageUrl || null);
|
||||
|
||||
const buyButton = new ButtonBuilder()
|
||||
.setCustomId("buy")
|
||||
.setLabel("Buy")
|
||||
.setStyle(ButtonStyle.Success);
|
||||
|
||||
const actionRow = new ActionRowBuilder<ButtonBuilder>().addComponents(buyButton);
|
||||
|
||||
try {
|
||||
const message = await targetChannel.send({ embeds: [embed], components: [actionRow] });
|
||||
await interaction.editReply({ content: `Item posted in ${targetChannel}.` });
|
||||
|
||||
// Create a collector on the specific message
|
||||
const collector = message.createMessageComponentCollector({
|
||||
componentType: ComponentType.Button,
|
||||
filter: (i) => i.customId === "buy",
|
||||
});
|
||||
|
||||
collector.on("collect", async (i) => {
|
||||
await handleBuyInteraction(i, item);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("Failed to send sell message:", error);
|
||||
await interaction.editReply({ content: "", embeds: [createErrorEmbed("Failed to post the item for sale.")] });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function handleBuyInteraction(interaction: ButtonInteraction, item: typeof items.$inferSelect) {
|
||||
try {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
|
||||
const userId = interaction.user.id;
|
||||
const user = await userService.getUserById(userId);
|
||||
|
||||
if (!user) {
|
||||
await interaction.editReply({ content: "", embeds: [createErrorEmbed("User profile not found.")] });
|
||||
return;
|
||||
}
|
||||
|
||||
if ((user.balance ?? 0n) < (item.price ?? 0n)) {
|
||||
await interaction.editReply({ content: "", embeds: [createWarningEmbed(`You don't have enough money! You need ${item.price} 🪙.`)] });
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await inventoryService.buyItem(userId, item.id, 1n);
|
||||
|
||||
if (!result.success) {
|
||||
await interaction.editReply({ content: "", embeds: [createErrorEmbed("Transaction failed. Please try again.")] });
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.editReply({ content: `Successfully bought **${item.name}** for ${item.price} 🪙!` });
|
||||
} catch (error) {
|
||||
console.error("Error processing purchase:", error);
|
||||
if (interaction.deferred || interaction.replied) {
|
||||
await interaction.editReply({ content: "", embeds: [createErrorEmbed("An error occurred while processing your purchase.")] });
|
||||
} else {
|
||||
await interaction.reply({ embeds: [createErrorEmbed("An error occurred while processing your purchase.")], flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,10 @@ const event: Event<Events.InteractionCreate> = {
|
||||
await import("@/modules/trade/trade.interaction").then(m => m.handleTradeInteraction(interaction));
|
||||
return;
|
||||
}
|
||||
if (interaction.customId.startsWith("shop_buy_") && interaction.isButton()) {
|
||||
await import("@/modules/economy/shop.interaction").then(m => m.handleShopInteraction(interaction));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
40
src/modules/economy/shop.interaction.ts
Normal file
40
src/modules/economy/shop.interaction.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { ButtonInteraction, MessageFlags } from "discord.js";
|
||||
import { inventoryService } from "@/modules/inventory/inventory.service";
|
||||
import { userService } from "@/modules/user/user.service";
|
||||
import { createErrorEmbed, createWarningEmbed } from "@/lib/embeds";
|
||||
|
||||
export async function handleShopInteraction(interaction: ButtonInteraction) {
|
||||
if (!interaction.customId.startsWith("shop_buy_")) return;
|
||||
|
||||
try {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
|
||||
const itemId = parseInt(interaction.customId.replace("shop_buy_", ""));
|
||||
if (isNaN(itemId)) {
|
||||
await interaction.editReply({ embeds: [createErrorEmbed("Invalid Item ID.")] });
|
||||
return;
|
||||
}
|
||||
|
||||
const item = await inventoryService.getItem(itemId);
|
||||
if (!item || !item.price) {
|
||||
await interaction.editReply({ embeds: [createErrorEmbed("Item not found or not for sale.")] });
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await userService.getOrCreateUser(interaction.user.id, interaction.user.username);
|
||||
|
||||
// Double check balance here too, although service handles it, we want a nice message
|
||||
if ((user.balance ?? 0n) < item.price) {
|
||||
await interaction.editReply({ embeds: [createWarningEmbed(`You need ${item.price} 🪙 to buy this item. You have ${user.balance} 🪙.`)] });
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await inventoryService.buyItem(user.id, item.id, 1n);
|
||||
|
||||
await interaction.editReply({ content: `✅ **Success!** You bought **${item.name}** for ${item.price} 🪙.` });
|
||||
|
||||
} catch (error: any) {
|
||||
console.error("Shop Purchase Error:", error);
|
||||
await interaction.editReply({ embeds: [createErrorEmbed(error.message || "An error occurred while processing your purchase.")] });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user