const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js'); const { getServerMode, getEmbedColor, EMBED_COLORS } = require('../utils/modeHelper'); const activeTrades = new Map(); module.exports = { data: new SlashCommandBuilder() .setName('trade') .setDescription('Trade items with another user') .addUserOption(option => option.setName('user') .setDescription('User to trade with') .setRequired(true) ) .addStringOption(option => option.setName('offer') .setDescription('What you\'re offering (item name)') .setRequired(true) ) .addStringOption(option => option.setName('request') .setDescription('What you want in return (item name)') .setRequired(true) ), async execute(interaction, supabase, client) { const partner = interaction.options.getUser('user'); const offer = interaction.options.getString('offer'); const request = interaction.options.getString('request'); const initiator = interaction.user; const mode = await getServerMode(supabase, interaction.guildId); const guildId = interaction.guildId; if (partner.id === initiator.id) { return interaction.reply({ content: "You can't trade with yourself!", ephemeral: true }); } if (partner.bot) { return interaction.reply({ content: "You can't trade with bots!", ephemeral: true }); } if (!supabase) { return interaction.reply({ content: 'Trade system unavailable.', ephemeral: true }); } const tradeKey = `${guildId}-${initiator.id}`; if (activeTrades.has(tradeKey)) { return interaction.reply({ content: 'You already have an active trade!', ephemeral: true }); } const { data: initiatorItem } = await supabase .from('user_inventory') .select('*, shop_items(*)') .eq('guild_id', guildId) .eq('user_id', initiator.id) .ilike('shop_items.name', `%${offer}%`) .gt('quantity', 0) .maybeSingle(); if (!initiatorItem) { return interaction.reply({ content: `You don't have "${offer}" in your inventory!`, ephemeral: true }); } const { data: partnerItem } = await supabase .from('user_inventory') .select('*, shop_items(*)') .eq('guild_id', guildId) .eq('user_id', partner.id) .ilike('shop_items.name', `%${request}%`) .gt('quantity', 0) .maybeSingle(); if (!partnerItem) { return interaction.reply({ content: `${partner.username} doesn't have "${request}" in their inventory!`, ephemeral: true }); } activeTrades.set(tradeKey, { partner: partner.id, offer: initiatorItem, request: partnerItem }); const embed = new EmbedBuilder() .setColor(getEmbedColor(mode)) .setTitle('🔄 Trade Request') .setDescription(`${initiator} wants to trade with ${partner}!`) .addFields( { name: `${initiator.username} Offers`, value: `${initiatorItem.shop_items?.emoji || '📦'} ${initiatorItem.shop_items?.name}`, inline: true }, { name: `${partner.username} Offers`, value: `${partnerItem.shop_items?.emoji || '📦'} ${partnerItem.shop_items?.name}`, inline: true } ) .setFooter({ text: 'Trade expires in 60 seconds' }) .setTimestamp(); const row = new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId(`trade_accept_${initiator.id}`) .setLabel('Accept') .setStyle(ButtonStyle.Success), new ButtonBuilder() .setCustomId(`trade_decline_${initiator.id}`) .setLabel('Decline') .setStyle(ButtonStyle.Danger) ); const message = await interaction.reply({ content: `${partner}`, embeds: [embed], components: [row], fetchReply: true }); const collector = message.createMessageComponentCollector({ filter: i => i.user.id === partner.id && i.customId.includes(initiator.id), time: 60000, max: 1 }); collector.on('collect', async (i) => { const tradeData = activeTrades.get(tradeKey); activeTrades.delete(tradeKey); if (!tradeData) return; if (i.customId.startsWith('trade_decline')) { const declineEmbed = new EmbedBuilder() .setColor(EMBED_COLORS.error) .setTitle('🔄 Trade Declined') .setDescription(`${partner} declined the trade.`) .setTimestamp(); await i.update({ embeds: [declineEmbed], components: [] }); return; } try { await supabase.rpc('execute_trade', { p_guild_id: guildId, p_user1_id: initiator.id, p_user2_id: partner.id, p_item1_id: tradeData.offer.item_id, p_item2_id: tradeData.request.item_id }); const successEmbed = new EmbedBuilder() .setColor(EMBED_COLORS.success) .setTitle('🔄 Trade Complete!') .setDescription(`${initiator} and ${partner} successfully traded items!`) .addFields( { name: `${initiator.username} received`, value: `${partnerItem.shop_items?.emoji || '📦'} ${partnerItem.shop_items?.name}`, inline: true }, { name: `${partner.username} received`, value: `${initiatorItem.shop_items?.emoji || '📦'} ${initiatorItem.shop_items?.name}`, inline: true } ) .setTimestamp(); await i.update({ embeds: [successEmbed], components: [] }); } catch (e) { await supabase .from('user_inventory') .update({ quantity: tradeData.offer.quantity - 1 }) .eq('id', tradeData.offer.id); await supabase .from('user_inventory') .update({ quantity: tradeData.request.quantity - 1 }) .eq('id', tradeData.request.id); await supabase.from('user_inventory').upsert({ guild_id: guildId, user_id: partner.id, item_id: tradeData.offer.item_id, quantity: 1 }, { onConflict: 'guild_id,user_id,item_id' }); await supabase.from('user_inventory').upsert({ guild_id: guildId, user_id: initiator.id, item_id: tradeData.request.item_id, quantity: 1 }, { onConflict: 'guild_id,user_id,item_id' }); const successEmbed = new EmbedBuilder() .setColor(EMBED_COLORS.success) .setTitle('🔄 Trade Complete!') .setDescription(`${initiator} and ${partner} successfully traded items!`) .setTimestamp(); await i.update({ embeds: [successEmbed], components: [] }); } }); collector.on('end', async (collected) => { if (collected.size === 0) { activeTrades.delete(tradeKey); const timeoutEmbed = new EmbedBuilder() .setColor(EMBED_COLORS.warning) .setTitle('🔄 Trade Expired') .setDescription(`${partner} didn't respond in time.`) .setTimestamp(); await interaction.editReply({ embeds: [timeoutEmbed], components: [] }); } }); }, };