Trade: Add TradeStore and trade flow

Adding a TradeStore to keep track of active trades and their states.
Also implements the core trade flow and embed logic.
This commit is contained in:
2022-11-23 17:35:29 +01:00
parent 78fe0857e8
commit c866bc08eb
3 changed files with 188 additions and 56 deletions

View File

@@ -1,5 +1,5 @@
const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require("discord.js"); 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 { UserUtils, CardUtils } = require("../util");
const { TradeStore } = require("../stores"); const { TradeStore } = require("../stores");
@@ -37,12 +37,12 @@ module.exports = {
), ),
permissionLevel: 0, permissionLevel: 0,
async execute(interaction) { async execute(interaction) {
await interaction.deferReply();
let user1 = await UserUtils.getUserByDiscordId(interaction.member.id); let user1 = await UserUtils.getUserByDiscordId(interaction.member.id);
let trade = await TradeStore.getTradeByUser(user1.id); let trade = await TradeStore.getTradeByUser(user1.id);
switch (interaction.options.getSubcommand()) { switch (interaction.options.getSubcommand()) {
case "start": case "start":
await interaction.deferReply();
let user2 = await UserUtils.getUserByDiscordId(interaction.options.getUser("user").id); let user2 = await UserUtils.getUserByDiscordId(interaction.options.getUser("user").id);
//Attach usernames for convenience //Attach usernames for convenience
user2.name = interaction.options.getUser("user").username; user2.name = interaction.options.getUser("user").username;
@@ -50,23 +50,22 @@ module.exports = {
this.startTrade(interaction, user1, user2); this.startTrade(interaction, user1, user2);
break; break;
case "view": case "view":
await interaction.deferReply();
this.viewTrade(interaction, trade); this.viewTrade(interaction, trade);
break; break;
case "add": case "add":
if (!trade) { 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; return;
} }
await interaction.deferReply();
let card = await Card.findOne({ let card = await Card.findOne({
where: where:
{ identifier: interaction.options.getString("card") , userId: user1.id } { identifier: interaction.options.getString("card") , userId: user1.id }
}, ,
include: [ include: [
{ model: Character, include: [{ model: Band }] }, { model: Character },
{ model: User} { model: User}
]); ]}
);
this.addCardToTrade(interaction, trade, card); this.addCardToTrade(interaction, trade, card);
break; break;
} }
@@ -100,94 +99,206 @@ module.exports = {
//find card by identifier if owned by user //find card by identifier if owned by user
if (!card) { if (!card) {
await interaction.reply({ content: "You don't own this card", ephemeral: true }); await interaction.editReply({ 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!" });
return; return;
} }
if (trade.user1.id === interaction.member.id) { if (trade.user1.id === card.userId) {
trade.user1Cards.push(card); trade.user1Cards.push(card);
} else { }
if (trade.user2.id === card.userId) {
trade.user2Cards.push(card); 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) { async viewTrade(interaction, trade) {
if (!trade) { if (!trade) {
await interaction.editReply({ content: "This Trade does not exist!" }); await interaction.editReply({ content: "No active Trade" });
return; return;
} }
//delete existing trade message
if (trade.embed) {
await trade.embed.delete();
}
let user1Cards = "No cards" let user1Cards = "";
let user2Cards = "No cards" let user2Cards = "";
// for each of user1's cards in the trade // for each of user1's cards in the trade
for (card of trade.user1Cards) { for (card of trade.user1Cards) {
//get the card object let cardStr = CardUtils.getShortString(card);
let card = await Card.findOne({ user1Cards += `\n${cardStr}`;
where: {
identifier: cardIdentifier
},
include: [
{ model: Character, include: [{ model: Band }] },
{ model: User}
]
});
//add it to the list
user1Cards += `${card.identifier}\n`;
} }
// for each of user2's cards in the trade // for each of user2's cards in the trade
for (card of trade.user2Cards) { for (card of trade.user2Cards) {
//get the card object let cardStr = CardUtils.getShortString(card);
let card = await Card.findOne({ 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() const embed = new EmbedBuilder()
.setTitle(`Trade [${trade.id}] ${trade.user1.name} with ${trade.user2.name}`) .setTitle(`Trade [${trade.id}] ${trade.user1.name} with ${trade.user2.name}`)
.setDescription("DUMMY DESCRIPTION") .setDescription("DUMMY DESCRIPTION")
.addFields( .addFields(
{ name: `${trade.user1.name}'s cards`, value: user1Cards }, { name: `${trade.user1.name}'s cards ${trade.user1Locked ? '🔒 Locked' : '🔓'}`, value: user1Cards || "No cards" },
{ name: `${trade.user2.name}'s cards`, value: `DATA2` } { 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' }); .setFooter({ text: `TRADE`, iconURL: 'https://cdn.discordapp.com/attachments/856904078754971658/1017431187234508820/fp.png' });
const row = new ActionRowBuilder(); let row = new ActionRowBuilder();
row.addComponents( row = this.addComponentsToRow(row, trade);
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 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 //button collector
const filter = (button) => button.user.id === trade.user1.discordId || button.user.id === trade.user2.discordId; const filter = (button) => button.user.id === trade.user1.discordId || button.user.id === trade.user2.discordId;
const collector = reply.createMessageComponentCollector({ filter, time: tradeTimeout }); const collector = reply.createMessageComponentCollector({ filter, time: tradeTimeout });
collector.on('collect', async (button) => { 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}`) { if (button.customId === `cancel-trade-${trade.id}`) {
await TradeStore.removeTrade(trade);
collector.stop("cancel"); 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) => { collector.on('end', async (collected, reason) => {
console.log(`Collected ${collected.size} items, reason: ${reason}`); console.log(`Collected ${collected.size} items, reason: ${reason}`);
if (reason === "time") { if (reason === "time") {
await TradeStore.removeTrade(trade); trade.state = TradeStore.States.EXPIRED;
await trade.embed.edit({ content: `Trade ${trade.id} expired`, embeds: [], components: [] });
} }
if (reason === "cancel") { 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);
}); });
} }
} }

View File

@@ -16,6 +16,15 @@ const QUALITY_NAMES = {
6 : "Shiny" 6 : "Shiny"
} }
const QUALITY_SYMBOLS = {
1 : "<a:shyyOrcaTrain:1005123718542020729><:shyyDead:859552234697916426><:shyyDead:859552234697916426><:shyyDead:859552234697916426><:shyyDead:859552234697916426>",
2 : "<a:shyyOrcaTrain:1005123718542020729><a:shyyOrcaTrain:1005123718542020729><:shyyDead:859552234697916426><:shyyDead:859552234697916426><:shyyDead:859552234697916426>",
3 : "<a:shyyOrcaTrain:1005123718542020729><a:shyyOrcaTrain:1005123718542020729><a:shyyOrcaTrain:1005123718542020729><:shyyDead:859552234697916426><:shyyDead:859552234697916426>",
4 : "<a:shyyOrcaTrain:1005123718542020729><a:shyyOrcaTrain:1005123718542020729><a:shyyOrcaTrain:1005123718542020729><a:shyyOrcaTrain:1005123718542020729><:shyyDead:859552234697916426>",
5 : "<a:shyyOrcaTrain:1005123718542020729><a:shyyOrcaTrain:1005123718542020729><a:shyyOrcaTrain:1005123718542020729><a:shyyOrcaTrain:1005123718542020729><a:shyyOrcaTrain:1005123718542020729>",
6 : "⭐⭐⭐⭐⭐"
}
const CURRENCY_SYMBOLS = { const CURRENCY_SYMBOLS = {
1 : "🎶", 1 : "🎶",
2 : "💎" 2 : "💎"
@@ -56,5 +65,6 @@ const QUALITY_VALUES = {
exports.QUALITY = QUALITY; exports.QUALITY = QUALITY;
exports.QUALITY_NAMES = QUALITY_NAMES; exports.QUALITY_NAMES = QUALITY_NAMES;
exports.CURRENCY_SYMBOLS = CURRENCY_SYMBOLS; exports.CURRENCY_SYMBOLS = CURRENCY_SYMBOLS;
exports.QUALITY_SYMBOLS = QUALITY_SYMBOLS;
exports.CURRENCY_NAMES = CURRENCY_NAMES; exports.CURRENCY_NAMES = CURRENCY_NAMES;
exports.QUALITY_VALUES = QUALITY_VALUES; exports.QUALITY_VALUES = QUALITY_VALUES;

View File

@@ -17,6 +17,14 @@ module.exports = {
async getTradeByUser(userId) { async getTradeByUser(userId) {
return this.activeTrades.find(trade => trade.user1.id === userId || trade.user2.id === 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 { Trade: class Trade {
constructor(id, user1, user2) { constructor(id, user1, user2) {
@@ -26,8 +34,11 @@ module.exports = {
this.embed = null; this.embed = null;
this.user1Cards = []; this.user1Cards = [];
this.user2Cards = []; this.user2Cards = [];
this.user1Accept = false; this.user1Locked = false;
this.user2Accept = false; this.user2Locked = false;
this.user1Accepted = false;
this.user2Accepted = false;
this.state = 0;
} }
} }
} }