Files
discord-rpg-concept/src/commands/economy/sell.ts

125 lines
5.0 KiB
TypeScript

import { createCommand } from "@/lib/utils";
import {
SlashCommandBuilder,
EmbedBuilder,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
ComponentType,
type BaseGuildTextChannel,
type ButtonInteraction,
PermissionFlagsBits
} 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({ ephemeral: true });
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({ ephemeral: true });
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.")], ephemeral: true });
}
}
}