diff --git a/commands/trade.js b/commands/trade.js index bf1a4cc..049bc2a 100644 --- a/commands/trade.js +++ b/commands/trade.js @@ -1,5 +1,5 @@ const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require("discord.js"); -const { Card, User } = require("../models"); +const { Card, User, Character } = require("../models"); const { UserUtils, CardUtils } = require("../util"); const { TradeStore } = require("../stores"); @@ -37,12 +37,12 @@ module.exports = { ), permissionLevel: 0, async execute(interaction) { + await interaction.deferReply(); let user1 = await UserUtils.getUserByDiscordId(interaction.member.id); let trade = await TradeStore.getTradeByUser(user1.id); switch (interaction.options.getSubcommand()) { case "start": - await interaction.deferReply(); let user2 = await UserUtils.getUserByDiscordId(interaction.options.getUser("user").id); //Attach usernames for convenience user2.name = interaction.options.getUser("user").username; @@ -50,23 +50,22 @@ module.exports = { this.startTrade(interaction, user1, user2); break; case "view": - await interaction.deferReply(); this.viewTrade(interaction, trade); break; case "add": if (!trade) { - await interaction.reply({ content: "You don't have an active trade", ephemeral: true }); + await interaction.editReply({ content: "You don't have an active trade", ephemeral: true }); return; } - await interaction.deferReply(); let card = await Card.findOne({ where: { identifier: interaction.options.getString("card") , userId: user1.id } - }, + , include: [ - { model: Character, include: [{ model: Band }] }, + { model: Character }, { model: User} - ]); + ]} + ); this.addCardToTrade(interaction, trade, card); break; } @@ -100,94 +99,206 @@ module.exports = { //find card by identifier if owned by user if (!card) { - await interaction.reply({ content: "You don't own this card", ephemeral: true }); - return; - } - if (card.userId !== trade.user1.id) { - await interaction.editReply({ content: "You don't own this Card!" }); + await interaction.editReply({ content: "You don't own this card", ephemeral: true }); return; } - if (trade.user1.id === interaction.member.id) { + if (trade.user1.id === card.userId) { trade.user1Cards.push(card); - } else { + } + if (trade.user2.id === card.userId) { trade.user2Cards.push(card); } - this.viewTrade(interaction, trade); + + await interaction.editReply({ content: `User ${interaction.member.user.username} added ${card.identifier} to the trade` }); + await this.viewTrade(interaction, trade); }, async viewTrade(interaction, trade) { if (!trade) { - await interaction.editReply({ content: "This Trade does not exist!" }); + await interaction.editReply({ content: "No active Trade" }); return; } - //delete existing trade message - if (trade.embed) { - await trade.embed.delete(); - } - let user1Cards = "No cards" - let user2Cards = "No cards" + let user1Cards = ""; + let user2Cards = ""; + // for each of user1's cards in the trade for (card of trade.user1Cards) { - //get the card object - let card = await Card.findOne({ - where: { - identifier: cardIdentifier - }, - include: [ - { model: Character, include: [{ model: Band }] }, - { model: User} - ] - }); - //add it to the list - user1Cards += `${card.identifier}\n`; + let cardStr = CardUtils.getShortString(card); + user1Cards += `\n${cardStr}`; } // for each of user2's cards in the trade for (card of trade.user2Cards) { - //get the card object - let card = await Card.findOne({ + let cardStr = CardUtils.getShortString(card); + user2Cards += cardStr; + } + + let color = 0xff000c; + let tradeLocked = trade.user1Locked && trade.user2Locked; + let tradeAccepted = trade.user1accepted && trade.user2accepted; + + if (trade.state === TradeStore.States.LOCKED) { + //color orange + color = 0xffa500; + } + + if (trade.state === TradeStore.States.ACCEPTED) { + //color green + color = 0x00ff00; + } + + if (trade.state == TradeStore.States.CANCELLED || trade.state == TradeStore.States.EXPIRED) { + //color red + color = 0xff0000; + } + const embed = new EmbedBuilder() .setTitle(`Trade [${trade.id}] ${trade.user1.name} with ${trade.user2.name}`) .setDescription("DUMMY DESCRIPTION") .addFields( - { name: `${trade.user1.name}'s cards`, value: user1Cards }, - { name: `${trade.user2.name}'s cards`, value: `DATA2` } + { name: `${trade.user1.name}'s cards ${trade.user1Locked ? '🔒 Locked' : '🔓'}`, value: user1Cards || "No cards" }, + { name: `${trade.user2.name}'s cards ${trade.user2Locked ? '🔒 Locked' : '🔓'}`, value: user2Cards || "No cards" } ) - .setColor(0x00ff00) + .setColor(color) .setFooter({ text: `TRADE`, iconURL: 'https://cdn.discordapp.com/attachments/856904078754971658/1017431187234508820/fp.png' }); - const row = new ActionRowBuilder(); - row.addComponents( - new ButtonBuilder() - .setCustomId(`cancel-trade-${trade.id}`) - .setLabel('Cancel') - .setStyle(ButtonStyle.Danger) - ); - let reply = await interaction.editReply({ embeds: [embed], components: [row] }); - trade.embed = reply; + let row = new ActionRowBuilder(); + row = this.addComponentsToRow(row, trade); + let reply; + if (trade.embed) { + reply = await trade.embed.edit({ embeds: [embed], components: [row] }); + return; + } else { + reply = await interaction.editReply({ embeds: [embed], components: [row] }); + await this.attachCollectors(trade, reply, interaction); + } + + trade.embed = reply; + if (trade.cancelled || trade.expired) { + await TradeStore.removeTrade(trade); + } + }, + + addComponentsToRow(row, trade) { + //Anything before State.ACCEPTED can still be cancelled + if (trade.state < TradeStore.States.ACCEPTED) { + row.addComponents( + new ButtonBuilder() + .setCustomId(`cancel-trade-${trade.id}`) + .setLabel('Cancel') + .setStyle(ButtonStyle.Danger) + ); + } + + //Anything before State.LOCKED can still be locked + if (trade.state < TradeStore.States.LOCKED) { + row.addComponents( + new ButtonBuilder() + .setCustomId(`lock-trade-${trade.id}`) + .setLabel('Lock') + .setStyle(ButtonStyle.Success) + .setEmoji('🔒') + ); + } + + let acceptCount = 0; + if (trade.user1accepted) { + acceptCount++; + } + if (trade.user2accepted) { + acceptCount++; + } + + //Only trades in state locked can be accepted + if (trade.state === TradeStore.States.LOCKED) { + row.addComponents( + new ButtonBuilder() + .setCustomId(`accept-trade-${trade.id}`) + .setLabel(`Accept (${acceptCount}/2)`) + .setStyle(ButtonStyle.Success) + .setEmoji('✅') + ); + } + + //if trade is accepted, add a button to finalize it + if (trade.state === TradeStore.States.ACCEPTED) { + row.addComponents( + new ButtonBuilder() + .setCustomId(`trade-completed-${trade.id}`) + .setLabel('Completed') + .setStyle(ButtonStyle.Success) + .setDisabled(true) + ); + } + return row; + }, + + async attachCollectors(trade, reply, interaction) { //button collector const filter = (button) => button.user.id === trade.user1.discordId || button.user.id === trade.user2.discordId; const collector = reply.createMessageComponentCollector({ filter, time: tradeTimeout }); collector.on('collect', async (button) => { + //if interaction member is neither user1 nor user2, ignore + if (button.user.id !== trade.user1.discordId && button.user.id !== trade.user2.discordId) { + return; + } + if (button.customId === `cancel-trade-${trade.id}`) { - await TradeStore.removeTrade(trade); collector.stop("cancel"); } + if (button.customId === `lock-trade-${trade.id}`) { + //if interaction member is user1 + if (button.user.id === trade.user1.discordId) { + trade.user1Locked = true; + } + //if interaction member is user2 + + if (button.user.id === trade.user2.discordId) { + trade.user2Locked = true; + } + + if (trade.user1Locked && trade.user2Locked) { + trade.state = TradeStore.States.LOCKED; + } + await button.deferUpdate(); + await this.viewTrade(interaction, trade); + } + if (button.customId === `accept-trade-${trade.id}`) { + //if interaction member is user1 + if (button.user.id === trade.user1.discordId) { + trade.user1accepted = true; + } + //if interaction member is user2 + + if (button.user.id === trade.user2.discordId) { + trade.user2accepted = true; + } + + if (trade.user1accepted && trade.user2accepted) { + trade.state = TradeStore.States.ACCEPTED; + collector.stop("accepted"); + } + + await button.deferUpdate(); + await this.viewTrade(interaction, trade); + } }); collector.on('end', async (collected, reason) => { console.log(`Collected ${collected.size} items, reason: ${reason}`); if (reason === "time") { - await TradeStore.removeTrade(trade); - await trade.embed.edit({ content: `Trade ${trade.id} expired`, embeds: [], components: [] }); + trade.state = TradeStore.States.EXPIRED; } if (reason === "cancel") { - await trade.embed.edit({ content: `Trade ${trade.id} cancelled`, embeds: [], components: [] }); + trade.state = TradeStore.States.CANCELLED; } + if (reason === "accepted") { + //TODO: perform trade on database + } + await this.viewTrade(interaction, trade); }); - } } \ No newline at end of file diff --git a/config/constants.js b/config/constants.js index b64cf54..8debd8e 100644 --- a/config/constants.js +++ b/config/constants.js @@ -16,6 +16,15 @@ const QUALITY_NAMES = { 6 : "Shiny" } +const QUALITY_SYMBOLS = { + 1 : "<:shyyDead:859552234697916426><:shyyDead:859552234697916426><:shyyDead:859552234697916426><:shyyDead:859552234697916426>", + 2 : "<:shyyDead:859552234697916426><:shyyDead:859552234697916426><:shyyDead:859552234697916426>", + 3 : "<:shyyDead:859552234697916426><:shyyDead:859552234697916426>", + 4 : "<:shyyDead:859552234697916426>", + 5 : "", + 6 : "⭐⭐⭐⭐⭐" +} + const CURRENCY_SYMBOLS = { 1 : "🎶", 2 : "💎" @@ -56,5 +65,6 @@ const QUALITY_VALUES = { exports.QUALITY = QUALITY; exports.QUALITY_NAMES = QUALITY_NAMES; exports.CURRENCY_SYMBOLS = CURRENCY_SYMBOLS; +exports.QUALITY_SYMBOLS = QUALITY_SYMBOLS; exports.CURRENCY_NAMES = CURRENCY_NAMES; exports.QUALITY_VALUES = QUALITY_VALUES; \ No newline at end of file diff --git a/stores/tradeStore.js b/stores/tradeStore.js index d0f9931..313bc12 100644 --- a/stores/tradeStore.js +++ b/stores/tradeStore.js @@ -17,6 +17,14 @@ module.exports = { async getTradeByUser(userId) { return this.activeTrades.find(trade => trade.user1.id === userId || trade.user2.id === userId); }, + + States: { + OPEN: 0, + LOCKED: 1, + ACCEPTED: 2, + CANCELLED: 3, + EXPIRED: 4 + }, Trade: class Trade { constructor(id, user1, user2) { @@ -26,8 +34,11 @@ module.exports = { this.embed = null; this.user1Cards = []; this.user2Cards = []; - this.user1Accept = false; - this.user2Accept = false; + this.user1Locked = false; + this.user2Locked = false; + this.user1Accepted = false; + this.user2Accepted = false; + this.state = 0; } } } \ No newline at end of file