From 64d9c8e7375e5ea39664708914c3bd030d8f3d96 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Thu, 22 Feb 2024 18:38:20 +0000 Subject: [PATCH] Add trade command --- src/buttonEvents/Trade.ts | 174 ++++++++++++++++++++++++++++++++++ src/commands/trade.ts | 127 +++++++++++++++++++++++++ src/constants/EmbedColours.ts | 1 + src/registry.ts | 6 +- 4 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 src/buttonEvents/Trade.ts create mode 100644 src/commands/trade.ts diff --git a/src/buttonEvents/Trade.ts b/src/buttonEvents/Trade.ts new file mode 100644 index 0000000..beb6669 --- /dev/null +++ b/src/buttonEvents/Trade.ts @@ -0,0 +1,174 @@ +import { ButtonInteraction, EmbedBuilder } from "discord.js"; +import { ButtonEvent } from "../type/buttonEvent"; +import { CoreClient } from "../client/client"; +import Inventory from "../database/entities/app/Inventory"; +import EmbedColours from "../constants/EmbedColours"; + +export default class Trade extends ButtonEvent { + public override async execute(interaction: ButtonInteraction) { + const action = interaction.customId.split(" ")[1]; + + switch (action) { + case "accept": + await this.AcceptTrade(interaction); + break; + case "decline": + await this.DeclineTrade(interaction); + break; + } + } + + private async AcceptTrade(interaction: ButtonInteraction) { + const giveUserId = interaction.customId.split(" ")[2]; + const receiveUserId = interaction.customId.split(" ")[3]; + const giveCardNumber = interaction.customId.split(" ")[4]; + const receiveCardNumber = interaction.customId.split(" ")[5]; + const expiry = interaction.customId.split(" ")[6]; + const timeoutId = interaction.customId.split(" ")[7]; + + const expiryDate = new Date(expiry); + + if (expiryDate < new Date()) { + await interaction.reply("Trade has expired"); + return; + } + + if (interaction.user.id !== receiveUserId) { + await interaction.reply("You are not the user who the trade is intended for"); + return; + } + + const giveItem = CoreClient.Cards + .flatMap(x => x.cards) + .find(x => x.id === giveCardNumber); + + const receiveItem = CoreClient.Cards + .flatMap(x => x.cards) + .find(x => x.id === receiveCardNumber); + + if (!giveItem || !receiveItem) { + await interaction.reply("One or more of the items you are trying to trade does not exist."); + return; + } + + const giveUser = interaction.client.users.cache.get(giveUserId) || await interaction.client.users.fetch(giveUserId); + const receiveUser = interaction.client.users.cache.get(receiveUserId) || await interaction.client.users.fetch(receiveUserId); + + const giveUserInventory1 = await Inventory.FetchOneByCardNumberAndUserId(giveUserId, giveCardNumber); + const receiveUserInventory1 = await Inventory.FetchOneByCardNumberAndUserId(receiveUserId, receiveCardNumber); + + if (!giveUserInventory1 || !receiveUserInventory1) { + await interaction.reply("One or more of the items you are trying to trade does not exist."); + return; + } + + if (giveUserInventory1.Quantity < 1 || receiveUserInventory1.Quantity < 1) { + await interaction.reply("One or more of the items you are trying to trade does not exist."); + return; + } + + giveUserInventory1.SetQuantity(giveUserInventory1.Quantity - 1); + receiveUserInventory1.SetQuantity(receiveUserInventory1.Quantity - 1); + + await giveUserInventory1.Save(Inventory, giveUserInventory1); + await receiveUserInventory1.Save(Inventory, receiveUserInventory1); + + let giveUserInventory2 = await Inventory.FetchOneByCardNumberAndUserId(receiveUserId, giveCardNumber); + let receiveUserInventory2 = await Inventory.FetchOneByCardNumberAndUserId(giveUserId, receiveCardNumber); + + if (!giveUserInventory2) { + giveUserInventory2 = new Inventory(receiveUserId, giveCardNumber, 1); + } else { + giveUserInventory2.SetQuantity(giveUserInventory2.Quantity + 1); + } + + if (!receiveUserInventory2) { + receiveUserInventory2 = new Inventory(giveUserId, receiveCardNumber, 1); + } else { + receiveUserInventory2.SetQuantity(receiveUserInventory2.Quantity + 1); + } + + await giveUserInventory2.Save(Inventory, giveUserInventory2); + await receiveUserInventory2.Save(Inventory, receiveUserInventory2); + + clearTimeout(timeoutId); + + const tradeEmbed = new EmbedBuilder() + .setTitle("Trade Accepted") + .setDescription(`Trade initiated between ${receiveUser.username} and ${giveUser.username}`) + .setColor(EmbedColours.Success) + .addFields([ + { + name: `${receiveUser.username} is giving`, + value: `${giveItem.id}: ${giveItem.name}`, + inline: true, + }, + { + name: `${giveUser.username} is giving`, + value: `${receiveItem.id}: ${receiveItem.name}`, + inline: true, + }, + { + name: "Complete", + value: new Date().toLocaleString(), + } + ]); + + await interaction.update({ embeds: [ tradeEmbed ]}); + } + + private async DeclineTrade(interaction: ButtonInteraction) { + const giveUserId = interaction.customId.split(" ")[2]; + const receiveUserId = interaction.customId.split(" ")[3]; + const giveCardNumber = interaction.customId.split(" ")[4]; + const receiveCardNumber = interaction.customId.split(" ")[5]; + // No need to get expiry date + const timeoutId = interaction.customId.split(" ")[7]; + + if (interaction.user.id !== receiveUserId || interaction.user.id !== giveUserId) { + await interaction.reply("You are not the user who the trade is intended for"); + return; + } + + const giveUser = interaction.client.users.cache.get(giveUserId) || await interaction.client.users.fetch(giveUserId); + const receiveUser = interaction.client.users.cache.get(receiveUserId) || await interaction.client.users.fetch(receiveUserId); + + const giveItem = CoreClient.Cards + .flatMap(x => x.cards) + .find(x => x.id === giveCardNumber); + + const receiveItem = CoreClient.Cards + .flatMap(x => x.cards) + .find(x => x.id === receiveCardNumber); + + if (!giveItem || !receiveItem) { + await interaction.reply("One or more of the items you are trying to trade does not exist."); + return; + } + + clearTimeout(timeoutId); + + const tradeEmbed = new EmbedBuilder() + .setTitle("Trade Declined") + .setDescription(`Trade initiated between ${receiveUser.username} and ${giveUser.username}`) + .setColor(EmbedColours.Error) + .addFields([ + { + name: `${receiveUser.username} is giving`, + value: `${giveItem.id}: ${giveItem.name}`, + inline: true, + }, + { + name: `${giveUser.username} is giving`, + value: `${receiveItem.id}: ${receiveItem.name}`, + inline: true, + }, + { + name: "Declined", + value: new Date().toLocaleString(), + } + ]); + + await interaction.update({ embeds: [ tradeEmbed ]}); + } +} \ No newline at end of file diff --git a/src/commands/trade.ts b/src/commands/trade.ts new file mode 100644 index 0000000..21e57c4 --- /dev/null +++ b/src/commands/trade.ts @@ -0,0 +1,127 @@ +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder, SlashCommandBuilder } from "discord.js"; +import { Command } from "../type/command"; +import Inventory from "../database/entities/app/Inventory"; +import { CoreClient } from "../client/client"; +import EmbedColours from "../constants/EmbedColours"; + +export default class Trade extends Command { + constructor() { + super(); + + this.CommandBuilder = new SlashCommandBuilder() + .setName("trade") + .setDescription("Initiate a trade with another user.") + .addUserOption(x => + x + .setName("user") + .setDescription("User to trade with") + .setRequired(true)) + .addStringOption(x => + x + .setName("give") + .setDescription("Item to give") + .setRequired(true)) + .addStringOption(x => + x + .setName("receive") + .setDescription("Item to receive") + .setRequired(true)); + } + + public override async execute(interaction: CommandInteraction) { + const user = interaction.options.getUser("user")!; + const give = interaction.options.get("give")!; + const receive = interaction.options.get("receive")!; + + const giveItemEntity = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, give.value!.toString()); + const receiveItemEntity = await Inventory.FetchOneByCardNumberAndUserId(user.id, receive.value!.toString()); + + if (!giveItemEntity) { + await interaction.reply("You do not have the item you are trying to trade."); + return; + } + + if (!receiveItemEntity) { + await interaction.reply("The user you are trying to trade with does not have the item you are trying to trade for."); + return; + } + + const giveItem = CoreClient.Cards + .flatMap(x => x.cards) + .find(x => x.id === give.value!.toString()); + + const receiveItem = CoreClient.Cards + .flatMap(x => x.cards) + .find(x => x.id === receive.value!.toString()); + + if (!giveItem || !receiveItem) { + await interaction.reply("One or more of the items you are trying to trade does not exist."); + return; + } + + const now = new Date(); + const expiry = now.setMinutes(now.getMinutes() + 15); + + const tradeEmbed = new EmbedBuilder() + .setTitle("Trade Offer") + .setDescription(`Trade initiated between ${interaction.user.username} and ${user.username}`) + .setColor(EmbedColours.Grey) + .addFields([ + { + name: `${interaction.user.username} is giving`, + value: `${giveItem.id}: ${giveItem.name}`, + inline: true, + }, + { + name: `${user.username} is giving`, + value: `${receiveItem.id}: ${receiveItem.name}`, + inline: true, + }, + { + name: "Expires", + value: new Date(expiry).toLocaleString(), + } + ]); + + const timeoutId = setTimeout(async () => this.autoDecline(interaction, interaction.user.username, user.username, giveItem.id, receiveItem.id, giveItem.name, receiveItem.name), 1000 * 60 * 15); // 15 minutes + + const row = new ActionRowBuilder() + .addComponents([ + new ButtonBuilder() + .setCustomId(`trade accept ${interaction.user.id} ${user.id} ${giveItem.id} ${receiveItem.id} ${expiry} ${timeoutId}`) + .setLabel("Accept") + .setStyle(ButtonStyle.Success), + new ButtonBuilder() + .setCustomId(`trade decline ${interaction.user.id} ${user.id} ${giveItem.id} ${receiveItem.id} ${expiry} ${timeoutId}`) + .setLabel("Decline") + .setStyle(ButtonStyle.Danger), + ]); + + await interaction.reply({ content: `${user}`, embeds: [ tradeEmbed ], components: [ row ] }); + } + + private async autoDecline(interaction: CommandInteraction, giveUsername: string, receiveUsername: string, giveCardNumber: string, receiveCardNumber: string, giveCardName: string, receiveCardName: string) { + const tradeEmbed = new EmbedBuilder() + .setTitle("Trade Expired") + .setDescription(`Trade initiated between ${receiveUsername} and ${giveUsername}`) + .setColor(EmbedColours.Error) + .addFields([ + { + name: `${receiveUsername} is giving`, + value: `${giveCardNumber}: ${giveCardName}`, + inline: true, + }, + { + name: `${giveUsername} is giving`, + value: `${receiveCardNumber}: ${receiveCardName}`, + inline: true, + }, + { + name: "Expired", + value: new Date().toLocaleString(), + } + ]); + + await interaction.editReply({ embeds: [ tradeEmbed ]}); + } +} \ No newline at end of file diff --git a/src/constants/EmbedColours.ts b/src/constants/EmbedColours.ts index a54d56f..36777e3 100644 --- a/src/constants/EmbedColours.ts +++ b/src/constants/EmbedColours.ts @@ -1,5 +1,6 @@ export default class EmbedColours { public static readonly Ok = 0x3050ba; + public static readonly Success = 0x50c878; public static readonly Error = 0xff0000; public static readonly Grey = 0xd3d3d3; public static readonly BronzeCard = 0xcd7f32; diff --git a/src/registry.ts b/src/registry.ts index 2fc657d..1087cda 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -8,6 +8,7 @@ import Gdrivesync from "./commands/gdrivesync"; import Give from "./commands/give"; import Inventory from "./commands/inventory"; import Resync from "./commands/resync"; +import Trade from "./commands/trade"; import View from "./commands/view"; // Test Command Imports @@ -18,6 +19,7 @@ import Droprarity from "./commands/stage/droprarity"; import Claim from "./buttonEvents/Claim"; import InventoryButtonEvent from "./buttonEvents/Inventory"; import Reroll from "./buttonEvents/Reroll"; +import TradeButtonEvent from "./buttonEvents/Trade"; export default class Registry { public static RegisterCommands() { @@ -28,6 +30,7 @@ export default class Registry { CoreClient.RegisterCommand("give", new Give()); CoreClient.RegisterCommand("inventory", new Inventory()); CoreClient.RegisterCommand("resync", new Resync()); + CoreClient.RegisterCommand("trade", new Trade()); CoreClient.RegisterCommand("view", new View()); // Test Commands @@ -41,7 +44,8 @@ export default class Registry { public static RegisterButtonEvents() { CoreClient.RegisterButtonEvent("claim", new Claim()); - CoreClient.RegisterButtonEvent("inventory", new InventoryButtonEvent); + CoreClient.RegisterButtonEvent("inventory", new InventoryButtonEvent()); CoreClient.RegisterButtonEvent("reroll", new Reroll()); + CoreClient.RegisterButtonEvent("trade", new TradeButtonEvent()); } } \ No newline at end of file