const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits } = require('discord.js'); module.exports = { data: new SlashCommandBuilder() .setName('shop-manage') .setDescription('Manage the XP shop (Admin only)') .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) .addSubcommand(sub => sub.setName('add') .setDescription('Add a new shop item') .addStringOption(opt => opt.setName('name') .setDescription('Item name') .setRequired(true) ) .addIntegerOption(opt => opt.setName('price') .setDescription('Price in XP') .setRequired(true) .setMinValue(1) ) .addStringOption(opt => opt.setName('type') .setDescription('Item type') .setRequired(true) .addChoices( { name: '🏅 Badge', value: 'badge' }, { name: '🏷️ Title', value: 'title' }, { name: '🎨 Background', value: 'background' }, { name: '⚡ XP Booster', value: 'booster' }, { name: '👑 Role', value: 'role' }, { name: '✨ Special', value: 'special' } ) ) .addStringOption(opt => opt.setName('description') .setDescription('Item description') .setRequired(false) ) .addIntegerOption(opt => opt.setName('stock') .setDescription('Limited stock (leave empty for unlimited)') .setRequired(false) .setMinValue(1) ) .addIntegerOption(opt => opt.setName('level_required') .setDescription('Minimum level to purchase') .setRequired(false) .setMinValue(1) ) .addIntegerOption(opt => opt.setName('prestige_required') .setDescription('Minimum prestige to purchase') .setRequired(false) .setMinValue(1) ) .addRoleOption(opt => opt.setName('role_required') .setDescription('Role required to purchase') .setRequired(false) ) .addRoleOption(opt => opt.setName('grant_role') .setDescription('Role to grant on purchase (for role type items)') .setRequired(false) ) .addNumberOption(opt => opt.setName('booster_multiplier') .setDescription('XP multiplier for boosters (e.g., 1.5 for 50% bonus)') .setRequired(false) .setMinValue(1.1) .setMaxValue(5) ) .addIntegerOption(opt => opt.setName('booster_hours') .setDescription('Duration in hours for boosters') .setRequired(false) .setMinValue(1) .setMaxValue(168) ) .addStringOption(opt => opt.setName('badge_emoji') .setDescription('Emoji for badge items') .setRequired(false) ) ) .addSubcommand(sub => sub.setName('remove') .setDescription('Remove a shop item') .addIntegerOption(opt => opt.setName('item_id') .setDescription('The ID of the item to remove') .setRequired(true) ) ) .addSubcommand(sub => sub.setName('edit') .setDescription('Edit an existing shop item') .addIntegerOption(opt => opt.setName('item_id') .setDescription('The ID of the item to edit') .setRequired(true) ) .addStringOption(opt => opt.setName('name') .setDescription('New item name') .setRequired(false) ) .addIntegerOption(opt => opt.setName('price') .setDescription('New price in XP') .setRequired(false) .setMinValue(1) ) .addStringOption(opt => opt.setName('description') .setDescription('New item description') .setRequired(false) ) .addIntegerOption(opt => opt.setName('stock') .setDescription('New stock amount (-1 for unlimited)') .setRequired(false) .setMinValue(-1) ) .addBooleanOption(opt => opt.setName('enabled') .setDescription('Enable or disable the item') .setRequired(false) ) ) .addSubcommand(sub => sub.setName('list') .setDescription('List all shop items with details') ) .addSubcommand(sub => sub.setName('stats') .setDescription('View shop statistics') ), async execute(interaction, supabase) { if (!supabase) { return interaction.reply({ content: 'Database not configured.', ephemeral: true }); } const subcommand = interaction.options.getSubcommand(); switch (subcommand) { case 'add': return handleAdd(interaction, supabase); case 'remove': return handleRemove(interaction, supabase); case 'edit': return handleEdit(interaction, supabase); case 'list': return handleList(interaction, supabase); case 'stats': return handleStats(interaction, supabase); } } }; async function handleAdd(interaction, supabase) { await interaction.deferReply({ ephemeral: true }); const guildId = interaction.guildId; const name = interaction.options.getString('name'); const price = interaction.options.getInteger('price'); const itemType = interaction.options.getString('type'); const description = interaction.options.getString('description') || ''; const stock = interaction.options.getInteger('stock') || null; const levelRequired = interaction.options.getInteger('level_required') || 0; const prestigeRequired = interaction.options.getInteger('prestige_required') || 0; const roleRequired = interaction.options.getRole('role_required'); const grantRole = interaction.options.getRole('grant_role'); const boosterMultiplier = interaction.options.getNumber('booster_multiplier'); const boosterHours = interaction.options.getInteger('booster_hours'); const badgeEmoji = interaction.options.getString('badge_emoji'); const itemData = {}; if (itemType === 'role' && grantRole) { itemData.role_id = grantRole.id; itemData.role_name = grantRole.name; } if (itemType === 'booster') { itemData.multiplier = boosterMultiplier || 1.5; itemData.duration_hours = boosterHours || 24; } if (itemType === 'badge' && badgeEmoji) { itemData.emoji = badgeEmoji; } try { const { data: newItem, error } = await supabase .from('shop_items') .insert({ guild_id: guildId, name: name, description: description, item_type: itemType, price: price, stock: stock, level_required: levelRequired, prestige_required: prestigeRequired, role_required: roleRequired?.id || null, item_data: itemData, enabled: true }) .select() .single(); if (error) throw error; const embed = new EmbedBuilder() .setColor(0x00ff00) .setTitle('✅ Item Added to Shop') .addFields( { name: 'Item ID', value: `#${newItem.id}`, inline: true }, { name: 'Name', value: name, inline: true }, { name: 'Price', value: `${price.toLocaleString()} XP`, inline: true }, { name: 'Type', value: itemType, inline: true }, { name: 'Stock', value: stock ? stock.toString() : 'Unlimited', inline: true } ); if (description) { embed.addFields({ name: 'Description', value: description, inline: false }); } if (levelRequired > 0 || prestigeRequired > 0) { const reqs = []; if (levelRequired > 0) reqs.push(`Level ${levelRequired}`); if (prestigeRequired > 0) reqs.push(`Prestige ${prestigeRequired}`); embed.addFields({ name: 'Requirements', value: reqs.join(', '), inline: true }); } embed.setTimestamp(); await interaction.editReply({ embeds: [embed] }); } catch (error) { console.error('Shop add error:', error); await interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(0xff0000) .setDescription('Failed to add item to shop.') ] }); } } async function handleRemove(interaction, supabase) { await interaction.deferReply({ ephemeral: true }); const guildId = interaction.guildId; const itemId = interaction.options.getInteger('item_id'); try { const { data: item } = await supabase .from('shop_items') .select('*') .eq('id', itemId) .eq('guild_id', guildId) .maybeSingle(); if (!item) { return interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(0xff6b6b) .setDescription('Item not found.') ] }); } await supabase .from('shop_items') .delete() .eq('id', itemId) .eq('guild_id', guildId); await interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(0x00ff00) .setTitle('🗑️ Item Removed') .setDescription(`**${item.name}** has been removed from the shop.`) .setTimestamp() ] }); } catch (error) { console.error('Shop remove error:', error); await interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(0xff0000) .setDescription('Failed to remove item.') ] }); } } async function handleEdit(interaction, supabase) { await interaction.deferReply({ ephemeral: true }); const guildId = interaction.guildId; const itemId = interaction.options.getInteger('item_id'); const newName = interaction.options.getString('name'); const newPrice = interaction.options.getInteger('price'); const newDescription = interaction.options.getString('description'); const newStock = interaction.options.getInteger('stock'); const enabled = interaction.options.getBoolean('enabled'); try { const { data: item } = await supabase .from('shop_items') .select('*') .eq('id', itemId) .eq('guild_id', guildId) .maybeSingle(); if (!item) { return interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(0xff6b6b) .setDescription('Item not found.') ] }); } const updates = { updated_at: new Date().toISOString() }; const changes = []; if (newName !== null) { updates.name = newName; changes.push(`Name: ${item.name} → ${newName}`); } if (newPrice !== null) { updates.price = newPrice; changes.push(`Price: ${item.price} → ${newPrice} XP`); } if (newDescription !== null) { updates.description = newDescription; changes.push(`Description updated`); } if (newStock !== null) { updates.stock = newStock === -1 ? null : newStock; changes.push(`Stock: ${item.stock || 'Unlimited'} → ${newStock === -1 ? 'Unlimited' : newStock}`); } if (enabled !== null) { updates.enabled = enabled; changes.push(`Enabled: ${item.enabled} → ${enabled}`); } if (changes.length === 0) { return interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(0xfbbf24) .setDescription('No changes specified.') ] }); } await supabase .from('shop_items') .update(updates) .eq('id', itemId); await interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(0x00ff00) .setTitle('✏️ Item Updated') .setDescription(`**${item.name}** has been updated.`) .addFields({ name: 'Changes', value: changes.join('\n') }) .setTimestamp() ] }); } catch (error) { console.error('Shop edit error:', error); await interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(0xff0000) .setDescription('Failed to edit item.') ] }); } } async function handleList(interaction, supabase) { await interaction.deferReply({ ephemeral: true }); const guildId = interaction.guildId; try { const { data: items, error } = await supabase .from('shop_items') .select('*') .eq('guild_id', guildId) .order('item_type') .order('price'); if (error || !items || items.length === 0) { return interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(0xfbbf24) .setTitle('📋 Shop Items') .setDescription('No items in the shop yet.\n\nUse `/shop-manage add` to create items.') ] }); } const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle('📋 Shop Items (Admin View)') .setDescription(`Total items: **${items.length}**`) .setTimestamp(); const itemLines = items.map(item => { const status = item.enabled ? '✅' : '❌'; const stock = item.stock !== null ? ` [${item.stock} left]` : ''; const reqs = []; if (item.level_required > 0) reqs.push(`L${item.level_required}`); if (item.prestige_required > 0) reqs.push(`P${item.prestige_required}`); const reqStr = reqs.length > 0 ? ` (${reqs.join('/')})` : ''; return `${status} \`#${item.id}\` **${item.name}** - ${item.price.toLocaleString()} XP${stock}${reqStr}`; }); const chunks = []; let current = ''; for (const line of itemLines) { if ((current + '\n' + line).length > 1000) { chunks.push(current); current = line; } else { current = current ? current + '\n' + line : line; } } if (current) chunks.push(current); for (let i = 0; i < Math.min(chunks.length, 5); i++) { embed.addFields({ name: i === 0 ? 'Items' : '\u200b', value: chunks[i], inline: false }); } if (chunks.length > 5) { embed.setFooter({ text: `Showing first ${Math.min(items.length, 25)} items` }); } await interaction.editReply({ embeds: [embed] }); } catch (error) { console.error('Shop list error:', error); await interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(0xff0000) .setDescription('Failed to list items.') ] }); } } async function handleStats(interaction, supabase) { await interaction.deferReply({ ephemeral: true }); const guildId = interaction.guildId; try { const { data: items } = await supabase .from('shop_items') .select('*') .eq('guild_id', guildId); const { data: purchases } = await supabase .from('user_inventory') .select('*, shop_items(*)') .eq('guild_id', guildId); const { data: balances } = await supabase .from('user_balance') .select('*') .eq('guild_id', guildId); const totalItems = items?.length || 0; const enabledItems = items?.filter(i => i.enabled).length || 0; const totalPurchases = purchases?.length || 0; const totalSpent = balances?.reduce((sum, b) => sum + (b.total_spent || 0), 0) || 0; const uniqueBuyers = new Set(purchases?.map(p => p.user_id)).size; const itemCounts = {}; for (const p of purchases || []) { const name = p.shop_items?.name || 'Unknown'; itemCounts[name] = (itemCounts[name] || 0) + 1; } const topItems = Object.entries(itemCounts) .sort(([, a], [, b]) => b - a) .slice(0, 5) .map(([name, count], i) => `${i + 1}. **${name}** (${count} purchases)`) .join('\n'); const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle('📊 Shop Statistics') .addFields( { name: '🛍️ Total Items', value: `${totalItems} (${enabledItems} active)`, inline: true }, { name: '🛒 Total Purchases', value: totalPurchases.toString(), inline: true }, { name: '👥 Unique Buyers', value: uniqueBuyers.toString(), inline: true }, { name: '💰 Total XP Spent', value: totalSpent.toLocaleString(), inline: true } ) .setTimestamp(); if (topItems) { embed.addFields({ name: '🏆 Top Items', value: topItems, inline: false }); } await interaction.editReply({ embeds: [embed] }); } catch (error) { console.error('Shop stats error:', error); await interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(0xff0000) .setDescription('Failed to get statistics.') ] }); } }