Introduces a new server mode configuration system (Federation/Standalone) with associated command changes, dynamic status rotation for the bot, and adds new commands and features. Replit-Commit-Author: Agent Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: b08e6ba5-7498-4b9f-b1c9-7dc11b362ddd Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/R9PkDi8 Replit-Helium-Checkpoint-Created: true
218 lines
7.1 KiB
JavaScript
218 lines
7.1 KiB
JavaScript
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: [] });
|
|
}
|
|
});
|
|
},
|
|
};
|