forked from syntaxbullet/aurorabot
- Create withCommandErrorHandling utility in bot/lib/commandUtils.ts - Migrate economy commands: daily, exam, pay, trivia - Migrate inventory command: use - Migrate admin/moderation commands: warn, case, cases, clearwarning, warnings, note, notes, create_color, listing, webhook, refresh, terminal, featureflags, settings, prune - Add 9 unit tests for the utility - Update AGENTS.md with new recommended error handling pattern
121 lines
4.9 KiB
TypeScript
121 lines
4.9 KiB
TypeScript
import { createCommand } from "@shared/lib/utils";
|
|
import {
|
|
SlashCommandBuilder,
|
|
type BaseGuildTextChannel,
|
|
PermissionFlagsBits,
|
|
MessageFlags
|
|
} from "discord.js";
|
|
import { inventoryService } from "@shared/modules/inventory/inventory.service";
|
|
import { createErrorEmbed } from "@lib/embeds";
|
|
import { items } from "@db/schema";
|
|
import { ilike, isNotNull, and, inArray } from "drizzle-orm";
|
|
import { DrizzleClient } from "@shared/db/DrizzleClient";
|
|
import { getShopListingMessage } from "@/modules/economy/shop.view";
|
|
import { EffectType, LootType } from "@shared/lib/constants";
|
|
import { withCommandErrorHandling } from "@lib/commandUtils";
|
|
|
|
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 withCommandErrorHandling(
|
|
interaction,
|
|
async () => {
|
|
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;
|
|
}
|
|
|
|
// Prepare context for lootboxes
|
|
const context: { referencedItems: Map<number, { name: string; rarity: string }> } = { referencedItems: new Map() };
|
|
|
|
const usageData = item.usageData as any;
|
|
const lootboxEffect = usageData?.effects?.find((e: any) => e.type === EffectType.LOOTBOX);
|
|
|
|
if (lootboxEffect && lootboxEffect.pool) {
|
|
const itemIds = lootboxEffect.pool
|
|
.filter((drop: any) => drop.type === LootType.ITEM && drop.itemId)
|
|
.map((drop: any) => drop.itemId);
|
|
|
|
if (itemIds.length > 0) {
|
|
// Remove duplicates
|
|
const uniqueIds = [...new Set(itemIds)] as number[];
|
|
|
|
const referencedItems = await DrizzleClient.select({
|
|
id: items.id,
|
|
name: items.name,
|
|
rarity: items.rarity
|
|
}).from(items).where(inArray(items.id, uniqueIds));
|
|
|
|
for (const ref of referencedItems) {
|
|
context.referencedItems.set(ref.id, { name: ref.name, rarity: ref.rarity || 'C' });
|
|
}
|
|
}
|
|
}
|
|
|
|
const listingMessage = getShopListingMessage({
|
|
...item,
|
|
rarity: item.rarity || undefined,
|
|
formattedPrice: `${item.price} 🪙`,
|
|
price: item.price
|
|
}, context);
|
|
|
|
await targetChannel.send(listingMessage as any);
|
|
await interaction.editReply({ content: `✅ Listing for **${item.name}** posted in ${targetChannel}.` });
|
|
},
|
|
{ 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
|
|
}))
|
|
);
|
|
}
|
|
});
|