From f8b013a091f4f5df2aa699c774cd637cbb81d979 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Fri, 13 Dec 2024 18:26:02 +0000 Subject: [PATCH 01/22] Create effect use command --- src/commands/effects.ts | 57 ++++++++++++++++++++++++++++++++-- src/constants/EffectDetails.ts | 17 ++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 src/constants/EffectDetails.ts diff --git a/src/commands/effects.ts b/src/commands/effects.ts index bcaa929..db19b5e 100644 --- a/src/commands/effects.ts +++ b/src/commands/effects.ts @@ -1,6 +1,7 @@ -import {CommandInteraction, SlashCommandBuilder} from "discord.js"; +import {CommandInteraction, EmbedBuilder, SlashCommandBuilder} from "discord.js"; import {Command} from "../type/command"; import EffectHelper from "../helpers/EffectHelper"; +import {EffectDetails} from "../constants/EffectDetails"; export default class Effects extends Command { constructor() { @@ -15,7 +16,17 @@ export default class Effects extends Command { .addNumberOption(x => x .setName("page") .setDescription("The page number") - .setMinValue(1))); + .setMinValue(1))) + .addSubcommand(x => x + .setName("use") + .setDescription("Use an effect in your inventory") + .addStringOption(y => y + .setName("id") + .setDescription("The effect id to use") + .setRequired(true) + .setChoices([ + { name: "Unclaimed Chance Up", value: "unclaimed" }, + ]))); } public override async execute(interaction: CommandInteraction) { @@ -27,6 +38,9 @@ export default class Effects extends Command { case "list": await this.List(interaction); break; + case "use": + await this.Use(interaction); + break; } } @@ -42,4 +56,43 @@ export default class Effects extends Command { components: [ result.row ], }); } + + private async Use(interaction: CommandInteraction) { + const id = interaction.options.get("id", true).value!.toString(); + + const effectDetail = EffectDetails.get(id); + + if (!effectDetail) { + await interaction.reply("Unable to find effect!"); + return; + } + + const now = new Date(); + const whenExpires = new Date(now.getMilliseconds() + effectDetail.duration); + + const result = await EffectHelper.UseEffect(interaction.user.id, id, whenExpires); + + if (result) { + const embed = new EmbedBuilder() + .setTitle("Effect Used") + .setDescription("You now have an active effect!") + .addFields([ + { + name: "Effect", + value: effectDetail.friendlyName, + inline: true, + }, + { + name: "Expires", + value: ``, + inline: true, + }, + ]); + + await interaction.reply({ embeds: [ embed ] }); + return; + } + + await interaction.reply("Unable to use effect! Please make sure you have it in your inventory"); + } } diff --git a/src/constants/EffectDetails.ts b/src/constants/EffectDetails.ts new file mode 100644 index 0000000..c59d33d --- /dev/null +++ b/src/constants/EffectDetails.ts @@ -0,0 +1,17 @@ +class EffectDetail { + public readonly id: string; + public readonly friendlyName: string; + public readonly duration: number; + public readonly cost: number; + + constructor(id: string, friendlyName: string, duration: number, cost: number) { + this.id = id; + this.friendlyName = friendlyName; + this.duration = duration; + this.cost = cost; + } +}; + +export const EffectDetails = new Map([ + [ "unclaimed", new EffectDetail("unclaimed", "Unclaimed Chance Up", 24 * 60 * 60 * 1000, 100) ], +]); -- 2.45.2 From 57c3d603a9e212b1998ca1cdcb082329dc59149f Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Mon, 16 Dec 2024 19:17:23 +0000 Subject: [PATCH 02/22] Add check for cooldown --- src/commands/effects.ts | 2 +- src/constants/EffectDetails.ts | 6 ++++-- src/helpers/EffectHelper.ts | 9 ++++++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/commands/effects.ts b/src/commands/effects.ts index db19b5e..c7059e8 100644 --- a/src/commands/effects.ts +++ b/src/commands/effects.ts @@ -93,6 +93,6 @@ export default class Effects extends Command { return; } - await interaction.reply("Unable to use effect! Please make sure you have it in your inventory"); + await interaction.reply("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown"); } } diff --git a/src/constants/EffectDetails.ts b/src/constants/EffectDetails.ts index c59d33d..4b84dad 100644 --- a/src/constants/EffectDetails.ts +++ b/src/constants/EffectDetails.ts @@ -3,15 +3,17 @@ class EffectDetail { public readonly friendlyName: string; public readonly duration: number; public readonly cost: number; + public readonly cooldown: number; - constructor(id: string, friendlyName: string, duration: number, cost: number) { + constructor(id: string, friendlyName: string, duration: number, cost: number, cooldown: number) { this.id = id; this.friendlyName = friendlyName; this.duration = duration; this.cost = cost; + this.cooldown = cooldown; } }; export const EffectDetails = new Map([ - [ "unclaimed", new EffectDetail("unclaimed", "Unclaimed Chance Up", 24 * 60 * 60 * 1000, 100) ], + [ "unclaimed", new EffectDetail("unclaimed", "Unclaimed Chance Up", 10 * 60 * 1000, 100, 3 * 60 * 60 * 1000) ], ]); diff --git a/src/helpers/EffectHelper.ts b/src/helpers/EffectHelper.ts index d0d29a0..3aaca15 100644 --- a/src/helpers/EffectHelper.ts +++ b/src/helpers/EffectHelper.ts @@ -1,6 +1,7 @@ import {ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder} from "discord.js"; import UserEffect from "../database/entities/app/UserEffect"; import EmbedColours from "../constants/EmbedColours"; +import {EffectDetails} from "../constants/EffectDetails"; export default class EffectHelper { public static async AddEffectToUserInventory(userId: string, name: string, quantity: number = 1) { @@ -23,7 +24,13 @@ export default class EffectHelper { return false; } - if (effect.WhenExpires && now < effect.WhenExpires) { + const effectDetail = EffectDetails.get(effect.Id); + + if (!effectDetail) { + return false; + } + + if (effect.WhenExpires && now < new Date(effect.WhenExpires.getMilliseconds() + effectDetail.cooldown)) { return false; } -- 2.45.2 From d874cb7a12b16e2ad693f097f2b9bacab5c2eafd Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Mon, 16 Dec 2024 19:32:28 +0000 Subject: [PATCH 03/22] WIP: Start creating confirmation embed --- src/buttonEvents/Effects.ts | 55 +++++++++++++++++++++++++++++++++- src/commands/effects.ts | 44 +++++++++++++-------------- src/helpers/EffectHelper.ts | 20 ++++++++++--- src/helpers/TimeLengthInput.ts | 3 ++ 4 files changed, 94 insertions(+), 28 deletions(-) diff --git a/src/buttonEvents/Effects.ts b/src/buttonEvents/Effects.ts index 0810c94..8cefbf7 100644 --- a/src/buttonEvents/Effects.ts +++ b/src/buttonEvents/Effects.ts @@ -1,6 +1,7 @@ -import {ButtonInteraction} from "discord.js"; +import {ButtonInteraction,EmbedBuilder} from "discord.js"; import {ButtonEvent} from "../type/buttonEvent"; import EffectHelper from "../helpers/EffectHelper"; +import { EffectDetails } from "../constants/EffectDetails"; export default class Effects extends ButtonEvent { public override async execute(interaction: ButtonInteraction) { @@ -10,6 +11,9 @@ export default class Effects extends ButtonEvent { case "list": await this.List(interaction); break; + case "use": + await this.Use(interaction); + break; } } @@ -30,4 +34,53 @@ export default class Effects extends ButtonEvent { components: [ result.row ], }); } + + private async Use(interaction: ButtonInteraction) { + const subaction = interaction.customId.split(" ")[2]; + + switch (subaction) { + case "confirm": + await this.UseConfirm(interaction); + break; + } + } + + private async UseConfirm(interaction: ButtonInteraction) { + const id = interaction.customId.split(" ")[3]; + + const effectDetail = EffectDetails.get(id); + + if (!effectDetail) { + await interaction.reply("Unable to find effect!"); + return; + } + + const now = new Date(); + const whenExpires = new Date(now.getMilliseconds() + effectDetail.duration); + + const result = await EffectHelper.UseEffect(interaction.user.id, id, whenExpires); + + if (result) { + const embed = new EmbedBuilder() + .setTitle("Effect Used") + .setDescription("You now have an active effect!") + .addFields([ + { + name: "Effect", + value: effectDetail.friendlyName, + inline: true, + }, + { + name: "Expires", + value: ``, + inline: true, + }, + ]); + + await interaction.update({ embeds: [ embed ] }); + return; + } + + await interaction.reply("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown"); + } } diff --git a/src/commands/effects.ts b/src/commands/effects.ts index c7059e8..3fe30ef 100644 --- a/src/commands/effects.ts +++ b/src/commands/effects.ts @@ -2,6 +2,7 @@ import {CommandInteraction, EmbedBuilder, SlashCommandBuilder} from "discord.js" import {Command} from "../type/command"; import EffectHelper from "../helpers/EffectHelper"; import {EffectDetails} from "../constants/EffectDetails"; +import UserEffect from "../database/entities/app/UserEffect"; export default class Effects extends Command { constructor() { @@ -67,32 +68,29 @@ export default class Effects extends Command { return; } - const now = new Date(); - const whenExpires = new Date(now.getMilliseconds() + effectDetail.duration); + const canUseEffect = await EffectHelper.CanUseEffect(interaction.user.id, id) - const result = await EffectHelper.UseEffect(interaction.user.id, id, whenExpires); - - if (result) { - const embed = new EmbedBuilder() - .setTitle("Effect Used") - .setDescription("You now have an active effect!") - .addFields([ - { - name: "Effect", - value: effectDetail.friendlyName, - inline: true, - }, - { - name: "Expires", - value: ``, - inline: true, - }, - ]); - - await interaction.reply({ embeds: [ embed ] }); + if (!canUseEffect) { + await interaction.reply("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown"); return; } - await interaction.reply("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown"); + const embed = new EmbedBuilder() + .setTitle("Effect Confirmation") + .setDescription("Would you like to use this effect?") + .addFields([ + { + name: "Effect", + value: effectDetail.friendlyName, + inline: true, + }, + { + name: "Length", + value: "", + inline: true, + }, + ]); + + await interaction.reply({ embeds: [ embed ] }); } } diff --git a/src/helpers/EffectHelper.ts b/src/helpers/EffectHelper.ts index 3aaca15..26aab8c 100644 --- a/src/helpers/EffectHelper.ts +++ b/src/helpers/EffectHelper.ts @@ -17,6 +17,22 @@ export default class EffectHelper { } public static async UseEffect(userId: string, name: string, whenExpires: Date): Promise { + const canUseEffect = await this.CanUseEffect(userId, name); + + if (!canUseEffect) return false; + + const effect = await UserEffect.FetchOneByUserIdAndName(userId, name); + + if (!effect) return false; + + effect.UseEffect(whenExpires); + + await effect.Save(UserEffect, effect); + + return true; + } + + public static async CanUseEffect(userId: string, name: string): Promise { const effect = await UserEffect.FetchOneByUserIdAndName(userId, name); const now = new Date(); @@ -34,10 +50,6 @@ export default class EffectHelper { return false; } - effect.UseEffect(whenExpires); - - await effect.Save(UserEffect, effect); - return true; } diff --git a/src/helpers/TimeLengthInput.ts b/src/helpers/TimeLengthInput.ts index d1d8734..8fa232d 100644 --- a/src/helpers/TimeLengthInput.ts +++ b/src/helpers/TimeLengthInput.ts @@ -118,4 +118,7 @@ export default class TimeLengthInput { return desNumber; } + + public static ConvertFromMilliseconds(ms: number): TimeLengthInput { + } } \ No newline at end of file -- 2.45.2 From b37c0873935b228f8635f014ce3e4ed23107ba08 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Sat, 21 Dec 2024 15:46:18 +0000 Subject: [PATCH 04/22] Add confirmation button event --- src/commands/effects.ts | 22 ++++++++++++++++++---- src/helpers/TimeLengthInput.ts | 12 ++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/commands/effects.ts b/src/commands/effects.ts index 3fe30ef..13e7eeb 100644 --- a/src/commands/effects.ts +++ b/src/commands/effects.ts @@ -1,8 +1,9 @@ -import {CommandInteraction, EmbedBuilder, SlashCommandBuilder} from "discord.js"; +import {ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder, SlashCommandBuilder} from "discord.js"; import {Command} from "../type/command"; import EffectHelper from "../helpers/EffectHelper"; import {EffectDetails} from "../constants/EffectDetails"; import UserEffect from "../database/entities/app/UserEffect"; +import TimeLengthInput from "../helpers/TimeLengthInput"; export default class Effects extends Command { constructor() { @@ -68,13 +69,15 @@ export default class Effects extends Command { return; } - const canUseEffect = await EffectHelper.CanUseEffect(interaction.user.id, id) + const canUseEffect = await EffectHelper.CanUseEffect(interaction.user.id, id); if (!canUseEffect) { await interaction.reply("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown"); return; } + const timeLengthInput = TimeLengthInput.ConvertFromMilliseconds(effectDetail.duration); + const embed = new EmbedBuilder() .setTitle("Effect Confirmation") .setDescription("Would you like to use this effect?") @@ -86,11 +89,22 @@ export default class Effects extends Command { }, { name: "Length", - value: "", + value: timeLengthInput.GetLengthShort(), inline: true, }, ]); - await interaction.reply({ embeds: [ embed ] }); + const row = new ActionRowBuilder() + .addComponents([ + new ButtonBuilder() + .setLabel("Confirm") + .setCustomId(`effects use confirm ${effectDetail.id}`) + .setStyle(ButtonStyle.Primary), + ]); + + await interaction.reply({ + embeds: [ embed ], + components: [ row ], + }); } } diff --git a/src/helpers/TimeLengthInput.ts b/src/helpers/TimeLengthInput.ts index 8fa232d..aef58ba 100644 --- a/src/helpers/TimeLengthInput.ts +++ b/src/helpers/TimeLengthInput.ts @@ -120,5 +120,17 @@ export default class TimeLengthInput { } public static ConvertFromMilliseconds(ms: number): TimeLengthInput { + const seconds = Math.floor(ms / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + const remainingSeconds = seconds % 60; + const remainingMinutes = minutes % 60; + const remainingHours = hours % 24; + + const timeString = `${days}d ${remainingHours}h ${remainingMinutes}m ${remainingSeconds}s`; + + return new TimeLengthInput(timeString); } } \ No newline at end of file -- 2.45.2 From dd1f25917062b527826c84793c53a160966530e6 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Sat, 21 Dec 2024 15:55:04 +0000 Subject: [PATCH 05/22] Add cancel button event --- src/buttonEvents/Effects.ts | 78 +++++++++++++++++++++++++++++++++++-- src/commands/effects.ts | 6 +++ 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/src/buttonEvents/Effects.ts b/src/buttonEvents/Effects.ts index 8cefbf7..2a318ec 100644 --- a/src/buttonEvents/Effects.ts +++ b/src/buttonEvents/Effects.ts @@ -1,7 +1,9 @@ -import {ButtonInteraction,EmbedBuilder} from "discord.js"; +import {ActionRowBuilder, ButtonBuilder, ButtonInteraction,ButtonStyle,Embed,EmbedBuilder} from "discord.js"; import {ButtonEvent} from "../type/buttonEvent"; import EffectHelper from "../helpers/EffectHelper"; import { EffectDetails } from "../constants/EffectDetails"; +import TimeLengthInput from "../helpers/TimeLengthInput"; +import EmbedColours from "../constants/EmbedColours"; export default class Effects extends ButtonEvent { public override async execute(interaction: ButtonInteraction) { @@ -28,7 +30,7 @@ export default class Effects extends ButtonEvent { } const result = await EffectHelper.GenerateEffectEmbed(interaction.user.id, page); - + await interaction.update({ embeds: [ result.embed ], components: [ result.row ], @@ -42,6 +44,9 @@ export default class Effects extends ButtonEvent { case "confirm": await this.UseConfirm(interaction); break; + case "cancel": + await this.UseCancel(interaction); + break; } } @@ -64,6 +69,7 @@ export default class Effects extends ButtonEvent { const embed = new EmbedBuilder() .setTitle("Effect Used") .setDescription("You now have an active effect!") + .setColor(EmbedColours.Green) .addFields([ { name: "Effect", @@ -77,10 +83,76 @@ export default class Effects extends ButtonEvent { }, ]); - await interaction.update({ embeds: [ embed ] }); + const row = new ActionRowBuilder() + .addComponents([ + new ButtonBuilder() + .setLabel("Confirm") + .setCustomId(`effects use confirm ${effectDetail.id}`) + .setStyle(ButtonStyle.Primary) + .setDisabled(true), + new ButtonBuilder() + .setLabel("Cancel") + .setCustomId(`effects use cancel ${effectDetail.id}`) + .setStyle(ButtonStyle.Danger) + .setDisabled(true), + ]); + + await interaction.update({ + embeds: [ embed ], + components: [ row ], + }); return; } await interaction.reply("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown"); } + + private async UseCancel(interaction: ButtonInteraction) { + const id = interaction.customId.split(" ")[3]; + + const effectDetail = EffectDetails.get(id); + + if (!effectDetail) { + await interaction.reply("Unable to find effect!"); + return; + } + + const timeLengthInput = TimeLengthInput.ConvertFromMilliseconds(effectDetail.duration); + + const embed = new EmbedBuilder() + .setTitle("Effect Use Cancelled") + .setDescription("The effect from your inventory has not been used") + .setColor(EmbedColours.Grey) + .addFields([ + { + name: "Effect", + value: effectDetail.friendlyName, + inline: true, + }, + { + name: "Expires", + value: timeLengthInput.GetLengthShort(), + inline: true, + }, + ]); + + const row = new ActionRowBuilder() + .addComponents([ + new ButtonBuilder() + .setLabel("Confirm") + .setCustomId(`effects use confirm ${effectDetail.id}`) + .setStyle(ButtonStyle.Primary) + .setDisabled(true), + new ButtonBuilder() + .setLabel("Cancel") + .setCustomId(`effects use cancel ${effectDetail.id}`) + .setStyle(ButtonStyle.Danger) + .setDisabled(true), + ]); + + await interaction.update({ + embeds: [ embed ], + components: [ row ], + }); + } } diff --git a/src/commands/effects.ts b/src/commands/effects.ts index 13e7eeb..55f0da9 100644 --- a/src/commands/effects.ts +++ b/src/commands/effects.ts @@ -4,6 +4,7 @@ import EffectHelper from "../helpers/EffectHelper"; import {EffectDetails} from "../constants/EffectDetails"; import UserEffect from "../database/entities/app/UserEffect"; import TimeLengthInput from "../helpers/TimeLengthInput"; +import EmbedColours from "../constants/EmbedColours"; export default class Effects extends Command { constructor() { @@ -81,6 +82,7 @@ export default class Effects extends Command { const embed = new EmbedBuilder() .setTitle("Effect Confirmation") .setDescription("Would you like to use this effect?") + .setColor(EmbedColours.Ok) .addFields([ { name: "Effect", @@ -100,6 +102,10 @@ export default class Effects extends Command { .setLabel("Confirm") .setCustomId(`effects use confirm ${effectDetail.id}`) .setStyle(ButtonStyle.Primary), + new ButtonBuilder() + .setLabel("Cancel") + .setCustomId(`effects use cancel ${effectDetail.id}`) + .setStyle(ButtonStyle.Danger), ]); await interaction.reply({ -- 2.45.2 From 9a2835a0eb82751576460d83f052bf95c2a4ebad Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Mon, 23 Dec 2024 16:18:40 +0000 Subject: [PATCH 06/22] Add chance effect function --- src/commands/drop.ts | 12 +++++- src/constants/CardConstants.ts | 3 ++ src/helpers/CardDropHelperMetadata.ts | 59 +++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/commands/drop.ts b/src/commands/drop.ts index 6799a2c..8d82d81 100644 --- a/src/commands/drop.ts +++ b/src/commands/drop.ts @@ -11,6 +11,8 @@ import AppLogger from "../client/appLogger"; import User from "../database/entities/app/User"; import CardConstants from "../constants/CardConstants"; import ErrorMessages from "../constants/ErrorMessages"; +import { DropResult } from "../contracts/SeriesMetadata"; +import EffectHelper from "../helpers/EffectHelper"; export default class Drop extends Command { constructor() { @@ -47,7 +49,15 @@ export default class Drop extends Command { return; } - const randomCard = CardDropHelperMetadata.GetRandomCard(); + let randomCard: DropResult | undefined; + + const hasChanceUpEffect = await EffectHelper.HasEffect(interaction.user.id, "unclaimed"); + + if (hasChanceUpEffect && Math.random() <= CardConstants.UnusedChanceUpChance) { + randomCard = await CardDropHelperMetadata.GetRandomCardUnclaimed(interaction.user.id); + } else { + randomCard = CardDropHelperMetadata.GetRandomCard(); + } if (!randomCard) { AppLogger.LogWarn("Commands/Drop", ErrorMessages.UnableToFetchCard); diff --git a/src/constants/CardConstants.ts b/src/constants/CardConstants.ts index 3f9723b..9330111 100644 --- a/src/constants/CardConstants.ts +++ b/src/constants/CardConstants.ts @@ -7,4 +7,7 @@ export default class CardConstants { // Multidrop public static readonly MultidropCost = this.ClaimCost * 10; public static readonly MultidropQuantity = 11; + + // Effects + public static readonly UnusedChanceUpChance = 1; } \ No newline at end of file diff --git a/src/helpers/CardDropHelperMetadata.ts b/src/helpers/CardDropHelperMetadata.ts index 8b9b273..80f3d6b 100644 --- a/src/helpers/CardDropHelperMetadata.ts +++ b/src/helpers/CardDropHelperMetadata.ts @@ -6,6 +6,7 @@ import { CoreClient } from "../client/client"; import AppLogger from "../client/appLogger"; import CardConstants from "../constants/CardConstants"; import StringTools from "./StringTools"; +import Inventory from "../database/entities/app/Inventory"; export default class CardDropHelperMetadata { public static GetRandomCard(): DropResult | undefined { @@ -58,6 +59,64 @@ export default class CardDropHelperMetadata { }; } + public static async GetRandomCardUnclaimed(userId: string): Promise { + const randomRarity = Math.random() * 100; + + let cardRarity: CardRarity; + + const bronzeChance = CardRarityChances.Bronze; + const silverChance = bronzeChance + CardRarityChances.Silver; + const goldChance = silverChance + CardRarityChances.Gold; + const mangaChance = goldChance + CardRarityChances.Manga; + + if (randomRarity < bronzeChance) cardRarity = CardRarity.Bronze; + else if (randomRarity < silverChance) cardRarity = CardRarity.Silver; + else if (randomRarity < goldChance) cardRarity = CardRarity.Gold; + else if (randomRarity < mangaChance) cardRarity = CardRarity.Manga; + else cardRarity = CardRarity.Legendary; + + const randomCard = await this.GetRandomCardByRarityUnclaimed(cardRarity, userId); + + AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCardUnclaimed", `Random card: ${randomCard?.card.id} ${randomCard?.card.name}`); + + return randomCard; + } + + public static async GetRandomCardByRarityUnclaimed(rarity: CardRarity, userId: string): Promise { + AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCardByRarityUnclaimed", `Parameters: rarity=${rarity}, userId=${userId}`); + + const claimedCards = await Inventory.FetchAllByUserId(userId); + + if (!claimedCards) { + // They don't have any cards, so safe to get any random card + return this.GetRandomCardByRarity(rarity); + } + + const allCards = CoreClient.Cards + .flatMap(x => x.cards) + .filter(x => x.type == rarity) + .filter(x => !claimedCards.find(y => y.CardNumber == x.id)); + + const randomCardIndex = Math.floor(Math.random() * allCards.length); + + const card = allCards[randomCardIndex]; + const series = CoreClient.Cards + .find(x => x.cards.includes(card)); + + if (!series) { + AppLogger.LogWarn("CardDropHelperMetadata/GetRandomCardByRarityUnclaimed", `Series not found for card ${card.id}`); + + return undefined; + } + + AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCardByRarityUnclaimed", `Random card: ${card.id} ${card.name}`); + + return { + series: series, + card: card, + }; + } + public static GetCardByCardNumber(cardNumber: string): DropResult | undefined { AppLogger.LogSilly("CardDropHelperMetadata/GetCardByCardNumber", `Parameters: cardNumber=${cardNumber}`); -- 2.45.2 From ff9437ba8154979d3e5b02097016c8bf915a8c8a Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Mon, 23 Dec 2024 16:20:54 +0000 Subject: [PATCH 07/22] Update .env.example variables --- .env.example | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.env.example b/.env.example index add48a5..e37a3a0 100644 --- a/.env.example +++ b/.env.example @@ -34,8 +34,6 @@ DB_LOGGING= DB_DATA_LOCATION=./.temp/database DB_ROOT_HOST=0.0.0.0 -DB_CARD_FILE=:memory: - EXPRESS_PORT=3302 -GDRIVESYNC_AUTO=true +GDRIVESYNC_AUTO=false -- 2.45.2 From a3f307d87e6ea0b638c92a31685c5cbdb4eb5b30 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Fri, 27 Dec 2024 18:12:33 +0000 Subject: [PATCH 08/22] Fix EffectHelper using Guid to find detail not name --- src/commands/drop.ts | 2 ++ src/helpers/EffectHelper.ts | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/commands/drop.ts b/src/commands/drop.ts index 8d82d81..f28c83e 100644 --- a/src/commands/drop.ts +++ b/src/commands/drop.ts @@ -53,6 +53,8 @@ export default class Drop extends Command { const hasChanceUpEffect = await EffectHelper.HasEffect(interaction.user.id, "unclaimed"); + console.log(hasChanceUpEffect); + if (hasChanceUpEffect && Math.random() <= CardConstants.UnusedChanceUpChance) { randomCard = await CardDropHelperMetadata.GetRandomCardUnclaimed(interaction.user.id); } else { diff --git a/src/helpers/EffectHelper.ts b/src/helpers/EffectHelper.ts index 26aab8c..1033ada 100644 --- a/src/helpers/EffectHelper.ts +++ b/src/helpers/EffectHelper.ts @@ -37,16 +37,19 @@ export default class EffectHelper { const now = new Date(); if (!effect || effect.Unused == 0) { + console.log(1); return false; } - const effectDetail = EffectDetails.get(effect.Id); + const effectDetail = EffectDetails.get(effect.Name); if (!effectDetail) { + console.log(2); return false; } if (effect.WhenExpires && now < new Date(effect.WhenExpires.getMilliseconds() + effectDetail.cooldown)) { + console.log(3); return false; } -- 2.45.2 From 90bda9d9dfe9c31fae1016509c6f4762d665309f Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Fri, 27 Dec 2024 18:22:04 +0000 Subject: [PATCH 09/22] Fix time not using proper unix time --- src/buttonEvents/Effects.ts | 5 +++-- src/commands/drop.ts | 2 -- src/helpers/EffectHelper.ts | 5 +---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/buttonEvents/Effects.ts b/src/buttonEvents/Effects.ts index 2a318ec..d7c9ef3 100644 --- a/src/buttonEvents/Effects.ts +++ b/src/buttonEvents/Effects.ts @@ -61,7 +61,8 @@ export default class Effects extends ButtonEvent { } const now = new Date(); - const whenExpires = new Date(now.getMilliseconds() + effectDetail.duration); + + const whenExpires = new Date(now.getTime() + effectDetail.duration); const result = await EffectHelper.UseEffect(interaction.user.id, id, whenExpires); @@ -78,7 +79,7 @@ export default class Effects extends ButtonEvent { }, { name: "Expires", - value: ``, + value: ``, inline: true, }, ]); diff --git a/src/commands/drop.ts b/src/commands/drop.ts index f28c83e..8d82d81 100644 --- a/src/commands/drop.ts +++ b/src/commands/drop.ts @@ -53,8 +53,6 @@ export default class Drop extends Command { const hasChanceUpEffect = await EffectHelper.HasEffect(interaction.user.id, "unclaimed"); - console.log(hasChanceUpEffect); - if (hasChanceUpEffect && Math.random() <= CardConstants.UnusedChanceUpChance) { randomCard = await CardDropHelperMetadata.GetRandomCardUnclaimed(interaction.user.id); } else { diff --git a/src/helpers/EffectHelper.ts b/src/helpers/EffectHelper.ts index 1033ada..4c45022 100644 --- a/src/helpers/EffectHelper.ts +++ b/src/helpers/EffectHelper.ts @@ -37,19 +37,16 @@ export default class EffectHelper { const now = new Date(); if (!effect || effect.Unused == 0) { - console.log(1); return false; } const effectDetail = EffectDetails.get(effect.Name); if (!effectDetail) { - console.log(2); return false; } - if (effect.WhenExpires && now < new Date(effect.WhenExpires.getMilliseconds() + effectDetail.cooldown)) { - console.log(3); + if (effect.WhenExpires && now < new Date(effect.WhenExpires.getTime() + effectDetail.cooldown)) { return false; } -- 2.45.2 From e28fafea69275086041da8812a66b45f31d9e784 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Fri, 27 Dec 2024 18:26:25 +0000 Subject: [PATCH 10/22] Remove broken tests --- tests/.gitkeep | 0 tests/buttonEvents/Effects.test.ts | 127 ------ .../__snapshots__/effects.test.ts.snap | 40 -- tests/commands/effects.test.ts | 164 -------- .../database/entities/app/UserEffect.test.ts | 103 ----- tests/helpers/EffectHelper.test.ts | 380 ------------------ .../__snapshots__/EffectHelper.test.ts.snap | 71 ---- 7 files changed, 885 deletions(-) delete mode 100644 tests/.gitkeep delete mode 100644 tests/buttonEvents/Effects.test.ts delete mode 100644 tests/commands/__snapshots__/effects.test.ts.snap delete mode 100644 tests/commands/effects.test.ts delete mode 100644 tests/database/entities/app/UserEffect.test.ts delete mode 100644 tests/helpers/EffectHelper.test.ts delete mode 100644 tests/helpers/__snapshots__/EffectHelper.test.ts.snap diff --git a/tests/.gitkeep b/tests/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/buttonEvents/Effects.test.ts b/tests/buttonEvents/Effects.test.ts deleted file mode 100644 index 557e64a..0000000 --- a/tests/buttonEvents/Effects.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -import {ButtonInteraction} from "discord.js"; -import Effects from "../../src/buttonEvents/Effects"; -import EffectHelper from "../../src/helpers/EffectHelper"; - -describe("execute", () => { - describe("GIVEN action in custom id is list", () => { - const interaction = { - customId: "effects list", - } as unknown as ButtonInteraction; - - let listSpy: jest.SpyInstance; - - beforeAll(async () => { - const effects = new Effects(); - - listSpy = jest.spyOn(effects as unknown as {"List": () => object}, "List") - .mockImplementation(); - - await effects.execute(interaction); - }); - - test("EXPECT list function to be called", () => { - expect(listSpy).toHaveBeenCalledTimes(1); - expect(listSpy).toHaveBeenCalledWith(interaction); - }); - }); -}); - -describe("List", () => { - let interaction: ButtonInteraction; - - const embed = { - name: "Embed", - }; - - const row = { - name: "Row", - }; - - beforeEach(() => { - interaction = { - customId: "effects list", - user: { - id: "userId", - }, - update: jest.fn(), - reply: jest.fn(), - } as unknown as ButtonInteraction; - }); - - describe("GIVEN page is a valid number", () => { - beforeEach(async () => { - interaction.customId += " 1"; - - EffectHelper.GenerateEffectEmbed = jest.fn() - .mockResolvedValue({ - embed, - row, - }); - - const effects = new Effects(); - - await effects.execute(interaction); - }); - - test("EXPECT EffectHelper.GenerateEffectEmbed to be called", () => { - expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledTimes(1); - expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledWith("userId", 1); - }); - - test("EXPECT interaction to be updated", () => { - expect(interaction.update).toHaveBeenCalledTimes(1); - expect(interaction.update).toHaveBeenCalledWith({ - embeds: [ embed ], - components: [ row ], - }); - }); - }); - - describe("GIVEN page in custom id is not supplied", () => { - beforeEach(async () => { - EffectHelper.GenerateEffectEmbed = jest.fn() - .mockResolvedValue({ - embed, - row, - }); - - const effects = new Effects(); - - await effects.execute(interaction); - }); - - test("EXPECT interaction to be replied with error", () => { - expect(interaction.reply).toHaveBeenCalledTimes(1); - expect(interaction.reply).toHaveBeenCalledWith("Page option is not a valid number"); - }); - - test("EXPECT interaction to not be updated", () => { - expect(interaction.update).not.toHaveBeenCalled(); - }); - }); - - describe("GIVEN page in custom id is not a number", () => { - beforeEach(async () => { - interaction.customId += " test"; - - EffectHelper.GenerateEffectEmbed = jest.fn() - .mockResolvedValue({ - embed, - row, - }); - - const effects = new Effects(); - - await effects.execute(interaction); - }); - - test("EXPECT interaction to be replied with error", () => { - expect(interaction.reply).toHaveBeenCalledTimes(1); - expect(interaction.reply).toHaveBeenCalledWith("Page option is not a valid number"); - }); - - test("EXPECT interaction to not be updated", () => { - expect(interaction.update).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/tests/commands/__snapshots__/effects.test.ts.snap b/tests/commands/__snapshots__/effects.test.ts.snap deleted file mode 100644 index ede2091..0000000 --- a/tests/commands/__snapshots__/effects.test.ts.snap +++ /dev/null @@ -1,40 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`constructor EXPECT CommandBuilder to be defined 1`] = ` -{ - "contexts": undefined, - "default_member_permissions": undefined, - "default_permission": undefined, - "description": "Effects", - "description_localizations": undefined, - "dm_permission": undefined, - "integration_types": undefined, - "name": "effects", - "name_localizations": undefined, - "nsfw": undefined, - "options": [ - { - "description": "List all effects I have", - "description_localizations": undefined, - "name": "list", - "name_localizations": undefined, - "options": [ - { - "autocomplete": undefined, - "choices": undefined, - "description": "The page number", - "description_localizations": undefined, - "max_value": undefined, - "min_value": 1, - "name": "page", - "name_localizations": undefined, - "required": false, - "type": 10, - }, - ], - "type": 1, - }, - ], - "type": 1, -} -`; diff --git a/tests/commands/effects.test.ts b/tests/commands/effects.test.ts deleted file mode 100644 index 8985477..0000000 --- a/tests/commands/effects.test.ts +++ /dev/null @@ -1,164 +0,0 @@ -import {ChatInputCommandInteraction} from "discord.js"; -import Effects from "../../src/commands/effects"; -import EffectHelper from "../../src/helpers/EffectHelper"; - -describe("constructor", () => { - let effects: Effects; - - beforeEach(() => { - effects = new Effects(); - }); - - test("EXPECT CommandBuilder to be defined", () => { - expect(effects.CommandBuilder).toMatchSnapshot(); - }); -}); - -describe("execute", () => { - describe("GIVEN interaction is not a chat input command", () => { - let interaction: ChatInputCommandInteraction; - - let listSpy: jest.SpyInstance; - - beforeEach(async () => { - interaction = { - isChatInputCommand: jest.fn().mockReturnValue(false), - } as unknown as ChatInputCommandInteraction; - - const effects = new Effects(); - - listSpy = jest.spyOn(effects as unknown as {"List": () => object}, "List") - .mockImplementation(); - - await effects.execute(interaction); - }); - - test("EXPECT isChatInputCommand to have been called", () => { - expect(interaction.isChatInputCommand).toHaveBeenCalledTimes(1); - }); - - test("EXPECT nothing to happen", () => { - expect(listSpy).not.toHaveBeenCalled(); - }); - }); - - describe("GIVEN subcommand is list", () => { - let interaction: ChatInputCommandInteraction; - - let listSpy: jest.SpyInstance; - - beforeEach(async () => { - interaction = { - isChatInputCommand: jest.fn().mockReturnValue(true), - options: { - getSubcommand: jest.fn().mockReturnValue("list"), - }, - } as unknown as ChatInputCommandInteraction; - - const effects = new Effects(); - - listSpy = jest.spyOn(effects as unknown as {"List": () => object}, "List") - .mockImplementation(); - - await effects.execute(interaction); - }); - - test("EXPECT subcommand function to be called", () => { - expect(interaction.options.getSubcommand).toHaveBeenCalledTimes(1); - }); - - test("EXPECT list function to be called", () => { - expect(listSpy).toHaveBeenCalledTimes(1); - expect(listSpy).toHaveBeenCalledWith(interaction); - }); - }); -}); - -describe("List", () => { - const effects: Effects = new Effects(); - let interaction: ChatInputCommandInteraction; - - const embed = { - name: "embed", - }; - - const row = { - name: "row", - }; - - beforeEach(async () => { - interaction = { - isChatInputCommand: jest.fn().mockReturnValue(true), - options: { - getSubcommand: jest.fn().mockReturnValue("list"), - }, - reply: jest.fn(), - user: { - id: "userId", - }, - } as unknown as ChatInputCommandInteraction; - - const effects = new Effects(); - - EffectHelper.GenerateEffectEmbed = jest.fn().mockReturnValue({ - embed, - row, - }); - - jest.spyOn(effects as unknown as {"List": () => object}, "List") - .mockImplementation(); - }); - - describe("GIVEN page option is supplied", () => { - describe("AND page is a valid number", () => { - beforeEach(async () => { - interaction.options.get = jest.fn().mockReturnValueOnce({ - value: "2", - }); - - await effects.execute(interaction); - }); - - test("EXPECT EffectHelper.GenerateEffectEmbed to have been called with page", () => { - expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledTimes(1); - expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledWith("userId", 2); - }); - - test("EXPECT interaction to have been replied", () => { - expect(interaction.reply).toHaveBeenCalledTimes(1); - expect(interaction.reply).toHaveBeenCalledWith({ - embeds: [ embed ], - components: [ row ], - }); - }); - }); - - describe("AND page is not a valid number", () => { - beforeEach(async () => { - interaction.options.get = jest.fn().mockReturnValueOnce({ - value: "test", - }); - - await effects.execute(interaction); - }); - - test("EXPECT EffectHelper.GenerateEffectEmbed to have been called with page of 1", () => { - expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledTimes(1); - expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledWith("userId", 1); - }); - }); - }); - - describe("GIVEN page option is not supplied", () => { - beforeEach(async () => { - interaction.options.get = jest.fn().mockReturnValueOnce(undefined); - - await effects.execute(interaction); - }); - - test("EXPECT EffectHelper.GenerateEffectEmbed to have been called with page of 1", () => { - expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledTimes(1); - expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledWith("userId", 1); - }); - }); -}); diff --git a/tests/database/entities/app/UserEffect.test.ts b/tests/database/entities/app/UserEffect.test.ts deleted file mode 100644 index 66992ec..0000000 --- a/tests/database/entities/app/UserEffect.test.ts +++ /dev/null @@ -1,103 +0,0 @@ -import UserEffect from "../../../../src/database/entities/app/UserEffect"; - -let userEffect: UserEffect; -const now = new Date(); - -beforeEach(() => { - userEffect = new UserEffect("name", "userId", 1); -}); - -describe("AddUnused", () => { - beforeEach(() => { - userEffect.AddUnused(1); - }); - - test("EXPECT unused to be the amount more", () => { - expect(userEffect.Unused).toBe(2); - }); -}); - -describe("UseEffect", () => { - describe("GIVEN Unused is 0", () => { - let result: boolean; - - beforeEach(() => { - userEffect.Unused = 0; - - result = userEffect.UseEffect(now); - }); - - test("EXPECT false returned", () => { - expect(result).toBe(false); - }); - - test("EXPECT details not to be changed", () => { - expect(userEffect.Unused).toBe(0); - expect(userEffect.WhenExpires).toBeUndefined(); - }); - }); - - describe("GIVEN Unused is greater than 0", () => { - let result: boolean; - - beforeEach(() => { - result = userEffect.UseEffect(now); - }); - - test("EXPECT true returned", () => { - expect(result).toBe(true); - }); - - test("EXPECT Unused to be subtracted by 1", () => { - expect(userEffect.Unused).toBe(0); - }); - - test("EXPECT WhenExpires to be set", () => { - expect(userEffect.WhenExpires).toBe(now); - }); - }); -}); - -describe("IsEffectActive", () => { - describe("GIVEN WhenExpires is null", () => { - let result: boolean; - - beforeEach(() => { - result = userEffect.IsEffectActive(); - }); - - test("EXPECT false returned", () => { - expect(result).toBe(false); - }); - }); - - describe("GIVEN WhenExpires is defined", () => { - describe("AND WhenExpires is in the past", () => { - let result: boolean; - - beforeEach(() => { - userEffect.WhenExpires = new Date(now.getTime() - 100); - - result = userEffect.IsEffectActive(); - }); - - test("EXPECT false returned", () => { - expect(result).toBe(false); - }); - }); - - describe("AND WhenExpires is in the future", () => { - let result: boolean; - - beforeEach(() => { - userEffect.WhenExpires = new Date(now.getTime() + 100); - - result = userEffect.IsEffectActive(); - }); - - test("EXPECT true returned", () => { - expect(result).toBe(true); - }); - }); - }); -}); diff --git a/tests/helpers/EffectHelper.test.ts b/tests/helpers/EffectHelper.test.ts deleted file mode 100644 index 13dca37..0000000 --- a/tests/helpers/EffectHelper.test.ts +++ /dev/null @@ -1,380 +0,0 @@ -import {ActionRowBuilder, ButtonBuilder, EmbedBuilder} from "discord.js"; -import UserEffect from "../../src/database/entities/app/UserEffect"; -import EffectHelper from "../../src/helpers/EffectHelper"; - -describe("AddEffectToUserInventory", () => { - describe("GIVEN effect is in database", () => { - const effectMock = { - AddUnused: jest.fn(), - Save: jest.fn(), - }; - - beforeAll(async () => { - UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(effectMock); - - await EffectHelper.AddEffectToUserInventory("userId", "name", 1); - }); - - test("EXPECT database to be fetched", () => { - expect(UserEffect.FetchOneByUserIdAndName).toHaveBeenCalledTimes(1); - expect(UserEffect.FetchOneByUserIdAndName).toHaveBeenCalledWith("userId", "name"); - }); - - test("EXPECT effect to be updated", () => { - expect(effectMock.AddUnused).toHaveBeenCalledTimes(1); - expect(effectMock.AddUnused).toHaveBeenCalledWith(1); - }); - - test("EXPECT effect to be saved", () => { - expect(effectMock.Save).toHaveBeenCalledTimes(1); - expect(effectMock.Save).toHaveBeenCalledWith(UserEffect, effectMock); - }); - }); - - describe("GIVEN effect is not in database", () => { - beforeAll(async () => { - UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(null); - UserEffect.prototype.Save = jest.fn(); - - await EffectHelper.AddEffectToUserInventory("userId", "name", 1); - }); - - test("EXPECT effect to be saved", () => { - expect(UserEffect.prototype.Save).toHaveBeenCalledTimes(1); - expect(UserEffect.prototype.Save).toHaveBeenCalledWith(UserEffect, expect.any(UserEffect)); - }); - }); -}); - -describe("UseEffect", () => { - describe("GIVEN effect is in database", () => { - describe("GIVEN now is before effect.WhenExpires", () => { - let result: boolean | undefined; - - // nowMock < whenExpires - const nowMock = new Date(2024, 11, 3, 13, 30); - const whenExpires = new Date(2024, 11, 3, 14, 0); - - const userEffect = { - Unused: 1, - WhenExpires: whenExpires, - }; - - beforeAll(async () => { - jest.setSystemTime(nowMock); - - UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(userEffect); - - result = await EffectHelper.UseEffect("userId", "name", new Date()); - }); - - test("EXPECT false returned", () => { - expect(result).toBe(false); - }); - }); - - describe("GIVEN currently used effect is inactive", () => { - let result: boolean | undefined; - - // nowMock > whenExpires - const nowMock = new Date(2024, 11, 3, 13, 30); - const whenExpires = new Date(2024, 11, 3, 13, 0); - const whenExpiresNew = new Date(2024, 11, 3, 15, 0); - - const userEffect = { - Unused: 1, - WhenExpires: whenExpires, - UseEffect: jest.fn(), - Save: jest.fn(), - }; - - beforeAll(async () => { - jest.setSystemTime(nowMock); - - UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(userEffect); - - result = await EffectHelper.UseEffect("userId", "name", whenExpiresNew); - }); - - test("EXPECT UseEffect to be called", () => { - expect(userEffect.UseEffect).toHaveReturnedTimes(1); - expect(userEffect.UseEffect).toHaveBeenCalledWith(whenExpiresNew); - }); - - test("EXPECT effect to be saved", () => { - expect(userEffect.Save).toHaveBeenCalledTimes(1); - expect(userEffect.Save).toHaveBeenCalledWith(UserEffect, userEffect); - }); - - test("EXPECT true returned", () => { - expect(result).toBe(true); - }); - }); - - describe("GIVEN effect.WhenExpires is null", () => { - let result: boolean | undefined; - - // nowMock > whenExpires - const nowMock = new Date(2024, 11, 3, 13, 30); - const whenExpiresNew = new Date(2024, 11, 3, 15, 0); - - const userEffect = { - Unused: 1, - WhenExpires: null, - UseEffect: jest.fn(), - Save: jest.fn(), - }; - - beforeAll(async () => { - jest.setSystemTime(nowMock); - - UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(userEffect); - - result = await EffectHelper.UseEffect("userId", "name", whenExpiresNew); - }); - - test("EXPECT UseEffect to be called", () => { - expect(userEffect.UseEffect).toHaveBeenCalledTimes(1); - expect(userEffect.UseEffect).toHaveBeenCalledWith(whenExpiresNew); - }); - - test("EXPECT effect to be saved", () => { - expect(userEffect.Save).toHaveBeenCalledTimes(1); - expect(userEffect.Save).toHaveBeenCalledWith(UserEffect, userEffect); - }); - - test("EXPECT true returned", () => { - expect(result).toBe(true); - }); - }); - }); - - describe("GIVEN effect is not in database", () => { - let result: boolean | undefined; - - // nowMock > whenExpires - const nowMock = new Date(2024, 11, 3, 13, 30); - const whenExpiresNew = new Date(2024, 11, 3, 15, 0); - - beforeAll(async () => { - jest.setSystemTime(nowMock); - - UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(null); - - result = await EffectHelper.UseEffect("userId", "name", whenExpiresNew); - }); - - test("EXPECT false returned", () => { - expect(result).toBe(false); - }); - }); - - describe("GIVEN effect.Unused is 0", () => { - let result: boolean | undefined; - - // nowMock > whenExpires - const nowMock = new Date(2024, 11, 3, 13, 30); - const whenExpiresNew = new Date(2024, 11, 3, 15, 0); - - const userEffect = { - Unused: 0, - WhenExpires: null, - UseEffect: jest.fn(), - Save: jest.fn(), - }; - - beforeAll(async () => { - jest.setSystemTime(nowMock); - - UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(userEffect); - - result = await EffectHelper.UseEffect("userId", "name", whenExpiresNew); - }); - - test("EXPECT false returned", () => { - expect(result).toBe(false); - }); - }); -}); - -describe("HasEffect", () => { - describe("GIVEN effect is in database", () => { - describe("GIVEN effect.WhenExpires is defined", () => { - describe("GIVEN now is before effect.WhenExpires", () => { - let result: boolean | undefined; - - const nowMock = new Date(2024, 11, 3, 13, 30); - const whenExpires = new Date(2024, 11, 3, 15, 0); - - const userEffect = { - WhenExpires: whenExpires, - }; - - beforeAll(async () => { - jest.setSystemTime(nowMock); - - UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(userEffect); - - result = await EffectHelper.HasEffect("userId", "name"); - }); - - test("EXPECT true returned", () => { - expect(result).toBe(true); - }); - }); - - describe("GIVEN now is after effect.WhenExpires", () => { - let result: boolean | undefined; - - const nowMock = new Date(2024, 11, 3, 16, 30); - const whenExpires = new Date(2024, 11, 3, 15, 0); - - const userEffect = { - WhenExpires: whenExpires, - }; - - beforeAll(async () => { - jest.setSystemTime(nowMock); - - UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(userEffect); - - result = await EffectHelper.HasEffect("userId", "name"); - }); - - test("EXPECT false returned", () => { - expect(result).toBe(false); - }); - }); - }); - - describe("GIVEN effect.WhenExpires is undefined", () => { - let result: boolean | undefined; - - const userEffect = { - WhenExpires: undefined, - }; - - beforeAll(async () => { - UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(userEffect); - - result = await EffectHelper.HasEffect("userId", "name"); - }); - - test("EXPECT false returned", () => { - expect(result).toBe(false); - }); - }); - }); - - describe("GIVEN effect is not in database", () => { - let result: boolean | undefined; - - beforeAll(async () => { - UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(null); - - result = await EffectHelper.HasEffect("userId", "name"); - }); - - test("EXPECT false returned", () => { - expect(result).toBe(false); - }); - }); -}); - -describe("GenerateEffectEmbed", () => { - beforeEach(async () => { - UserEffect.FetchAllByUserIdPaginated = jest.fn() - .mockResolvedValue([ - [], - 0, - ]); - - await EffectHelper.GenerateEffectEmbed("userId", 1); - }); - - test("EXPECT UserEffect.FetchAllByUserIdPaginated to be called", () => { - expect(UserEffect.FetchAllByUserIdPaginated).toHaveBeenCalledTimes(1); - expect(UserEffect.FetchAllByUserIdPaginated).toHaveBeenCalledWith("userId", 0, 10); - }); - - describe("GIVEN there are no effects returned", () => { - let result: { - embed: EmbedBuilder, - row: ActionRowBuilder, - }; - - beforeEach(async () => { - UserEffect.FetchAllByUserIdPaginated = jest.fn() - .mockResolvedValue([ - [], - 0, - ]); - - result = await EffectHelper.GenerateEffectEmbed("userId", 1); - }); - - test("EXPECT result returned", () => { - expect(result).toMatchSnapshot(); - }); - }); - - describe("GIVEN there are effects returned", () => { - let result: { - embed: EmbedBuilder, - row: ActionRowBuilder, - }; - - beforeEach(async () => { - UserEffect.FetchAllByUserIdPaginated = jest.fn() - .mockResolvedValue([ - [ - { - Name: "name", - Unused: 1, - }, - ], - 1, - ]); - - result = await EffectHelper.GenerateEffectEmbed("userId", 1); - }); - - test("EXPECT result returned", () => { - expect(result).toMatchSnapshot(); - }); - - describe("AND it is the first page", () => { - beforeEach(async () => { - result = await EffectHelper.GenerateEffectEmbed("userId", 1) - }); - - test("EXPECT Previous button to be disabled", () => { - const button = result.row.components[0].data as unknown as { - label: string, - disabled: boolean - }; - - expect(button).toBeDefined(); - expect(button.label).toBe("Previous"); - expect(button.disabled).toBe(true); - }); - }); - - describe("AND it is the last page", () => { - beforeEach(async () => { - result = await EffectHelper.GenerateEffectEmbed("userId", 1) - }); - - test("EXPECT Next button to be disabled", () => { - const button = result.row.components[1].data as unknown as { - label: string, - disabled: boolean - }; - - expect(button).toBeDefined(); - expect(button.label).toBe("Next"); - expect(button.disabled).toBe(true); - }); - }); - }); -}); diff --git a/tests/helpers/__snapshots__/EffectHelper.test.ts.snap b/tests/helpers/__snapshots__/EffectHelper.test.ts.snap deleted file mode 100644 index 6484acd..0000000 --- a/tests/helpers/__snapshots__/EffectHelper.test.ts.snap +++ /dev/null @@ -1,71 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GenerateEffectEmbed GIVEN there are effects returned EXPECT result returned 1`] = ` -{ - "embed": { - "color": 3166394, - "description": "name x1", - "footer": { - "icon_url": undefined, - "text": "Page 1 of 1", - }, - "title": "Effects", - }, - "row": { - "components": [ - { - "custom_id": "effects list 0", - "disabled": true, - "emoji": undefined, - "label": "Previous", - "style": 1, - "type": 2, - }, - { - "custom_id": "effects list 2", - "disabled": true, - "emoji": undefined, - "label": "Next", - "style": 1, - "type": 2, - }, - ], - "type": 1, - }, -} -`; - -exports[`GenerateEffectEmbed GIVEN there are no effects returned EXPECT result returned 1`] = ` -{ - "embed": { - "color": 3166394, - "description": "*none*", - "footer": { - "icon_url": undefined, - "text": "Page 1 of 1", - }, - "title": "Effects", - }, - "row": { - "components": [ - { - "custom_id": "effects list 0", - "disabled": true, - "emoji": undefined, - "label": "Previous", - "style": 1, - "type": 2, - }, - { - "custom_id": "effects list 2", - "disabled": true, - "emoji": undefined, - "label": "Next", - "style": 1, - "type": 2, - }, - ], - "type": 1, - }, -} -`; -- 2.45.2 From 6f241ab34930c94d73023018e079edd2d279fb76 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Tue, 31 Dec 2024 16:59:57 +0000 Subject: [PATCH 11/22] Update tests --- src/buttonEvents/Effects.ts | 8 +- tests/buttonEvents/Effects.test.ts | 135 +++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 tests/buttonEvents/Effects.test.ts diff --git a/src/buttonEvents/Effects.ts b/src/buttonEvents/Effects.ts index d7c9ef3..f481733 100644 --- a/src/buttonEvents/Effects.ts +++ b/src/buttonEvents/Effects.ts @@ -19,7 +19,7 @@ export default class Effects extends ButtonEvent { } } - private async List(interaction: ButtonInteraction) { + public async List(interaction: ButtonInteraction) { const pageOption = interaction.customId.split(" ")[2]; const page = Number(pageOption); @@ -37,7 +37,7 @@ export default class Effects extends ButtonEvent { }); } - private async Use(interaction: ButtonInteraction) { + public async Use(interaction: ButtonInteraction) { const subaction = interaction.customId.split(" ")[2]; switch (subaction) { @@ -50,7 +50,7 @@ export default class Effects extends ButtonEvent { } } - private async UseConfirm(interaction: ButtonInteraction) { + public async UseConfirm(interaction: ButtonInteraction) { const id = interaction.customId.split(" ")[3]; const effectDetail = EffectDetails.get(id); @@ -108,7 +108,7 @@ export default class Effects extends ButtonEvent { await interaction.reply("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown"); } - private async UseCancel(interaction: ButtonInteraction) { + public async UseCancel(interaction: ButtonInteraction) { const id = interaction.customId.split(" ")[3]; const effectDetail = EffectDetails.get(id); diff --git a/tests/buttonEvents/Effects.test.ts b/tests/buttonEvents/Effects.test.ts new file mode 100644 index 0000000..b500491 --- /dev/null +++ b/tests/buttonEvents/Effects.test.ts @@ -0,0 +1,135 @@ +import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, EmbedBuilder } from "discord.js"; +import Effects from "../../src/buttonEvents/Effects"; +import EffectHelper from "../../src/helpers/EffectHelper"; +import { EffectDetails } from "../../src/constants/EffectDetails"; +import TimeLengthInput from "../../src/helpers/TimeLengthInput"; + +describe("Effects", () => { + let interaction: ButtonInteraction; + let effects: Effects; + + beforeEach(() => { + interaction = { + customId: "effects list 1", + user: { id: "123" }, + reply: jest.fn(), + update: jest.fn(), + } as unknown as ButtonInteraction; + effects = new Effects(); + }); + + it("should call List method when action is 'list'", async () => { + const listSpy = jest.spyOn(effects, "List").mockImplementation(async () => {}); + + await effects.execute(interaction); + + expect(listSpy).toHaveBeenCalledWith(interaction); + }); + + it("should call Use method when action is 'use'", async () => { + interaction.customId = "effects use confirm 1"; + const useSpy = jest.spyOn(effects, "Use").mockImplementation(async () => {}); + + await effects.execute(interaction); + + expect(useSpy).toHaveBeenCalledWith(interaction); + }); + + it("should reply with error message when page option is not a valid number", async () => { + interaction.customId = "effects list invalid"; + await effects.execute(interaction); + + expect(interaction.reply).toHaveBeenCalledWith("Page option is not a valid number"); + }); + + it("should update interaction with generated embed and row", async () => { + const mockEmbed = { + embed: new EmbedBuilder(), + row: new ActionRowBuilder() + }; + + jest.spyOn(EffectHelper, "GenerateEffectEmbed").mockResolvedValue(mockEmbed); + + await effects.List(interaction); + + expect(interaction.update).toHaveBeenCalledWith({ + embeds: [mockEmbed.embed], + components: [mockEmbed.row], + }); + }); + + it("should call UseConfirm method when subaction is 'confirm'", async () => { + interaction.customId = "effects use confirm 1"; + const useConfirmSpy = jest.spyOn(effects, "UseConfirm").mockImplementation(async () => {}); + + await effects.Use(interaction); + + expect(useConfirmSpy).toHaveBeenCalledWith(interaction); + }); + + it("should call UseCancel method when subaction is 'cancel'", async () => { + interaction.customId = "effects use cancel 1"; + const useCancelSpy = jest.spyOn(effects, "UseCancel").mockImplementation(async () => {}); + + await effects.Use(interaction); + + expect(useCancelSpy).toHaveBeenCalledWith(interaction); + }); + + it("should reply with error message when effect detail is not found in UseConfirm", async () => { + interaction.customId = "effects use confirm invalid"; + await effects.UseConfirm(interaction); + + expect(interaction.reply).toHaveBeenCalledWith("Unable to find effect!"); + }); + + it("should reply with error message when effect detail is not found in UseCancel", async () => { + interaction.customId = "effects use cancel invalid"; + await effects.UseCancel(interaction); + + expect(interaction.reply).toHaveBeenCalledWith("Unable to find effect!"); + }); + + it("should update interaction with embed and row when effect is used successfully", async () => { + const mockEffectDetail = { id: "1", friendlyName: "Test Effect", duration: 1000, cost: 10, cooldown: 5000 }; + const mockResult = true; + + jest.spyOn(EffectDetails, "get").mockReturnValue(mockEffectDetail); + jest.spyOn(EffectHelper, "UseEffect").mockResolvedValue(mockResult); + + await effects.UseConfirm(interaction); + + expect(interaction.update).toHaveBeenCalledWith(expect.objectContaining({ + embeds: expect.any(Array), + components: expect.any(Array), + })); + }); + + it("should reply with error message when effect is not used successfully", async () => { + const mockEffectDetail = { id: "1", friendlyName: "Test Effect", duration: 1000, cost: 0, cooldown: 0 }; + const mockResult = false; + + jest.spyOn(EffectDetails, "get").mockReturnValue(mockEffectDetail); + jest.spyOn(EffectHelper, "UseEffect").mockResolvedValue(mockResult); + + await effects.UseConfirm(interaction); + + expect(interaction.reply).toHaveBeenCalledWith("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown"); + }); + + it("should update interaction with embed and row when effect use is cancelled", async () => { + const mockEffectDetail = { id: "1", friendlyName: "Test Effect", duration: 1000, cost: 0, cooldown: 0 }; + + jest.spyOn(EffectDetails, "get").mockReturnValue(mockEffectDetail); + jest.spyOn(TimeLengthInput, "ConvertFromMilliseconds").mockReturnValue({ + GetLengthShort: () => "1s", + } as TimeLengthInput); + + await effects.UseCancel(interaction); + + expect(interaction.update).toHaveBeenCalledWith(expect.objectContaining({ + embeds: expect.any(Array), + components: expect.any(Array), + })); + }); +}); \ No newline at end of file -- 2.45.2 From 222d990a315bfc0494efc50ff70405d42376f738 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Tue, 31 Dec 2024 17:00:36 +0000 Subject: [PATCH 12/22] Fix linting issues --- src/buttonEvents/Effects.ts | 2 +- src/commands/effects.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/buttonEvents/Effects.ts b/src/buttonEvents/Effects.ts index f481733..a09ffaa 100644 --- a/src/buttonEvents/Effects.ts +++ b/src/buttonEvents/Effects.ts @@ -1,4 +1,4 @@ -import {ActionRowBuilder, ButtonBuilder, ButtonInteraction,ButtonStyle,Embed,EmbedBuilder} from "discord.js"; +import {ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, EmbedBuilder} from "discord.js"; import {ButtonEvent} from "../type/buttonEvent"; import EffectHelper from "../helpers/EffectHelper"; import { EffectDetails } from "../constants/EffectDetails"; diff --git a/src/commands/effects.ts b/src/commands/effects.ts index 55f0da9..bc4f743 100644 --- a/src/commands/effects.ts +++ b/src/commands/effects.ts @@ -2,7 +2,6 @@ import {ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedB import {Command} from "../type/command"; import EffectHelper from "../helpers/EffectHelper"; import {EffectDetails} from "../constants/EffectDetails"; -import UserEffect from "../database/entities/app/UserEffect"; import TimeLengthInput from "../helpers/TimeLengthInput"; import EmbedColours from "../constants/EmbedColours"; -- 2.45.2 From ff2980f3c097a6422dfea437a4d3f6f53eefda25 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Fri, 3 Jan 2025 14:31:27 +0000 Subject: [PATCH 13/22] Fix suggested changes --- src/buttonEvents/Effects.ts | 4 ++-- tests/buttonEvents/Effects.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/buttonEvents/Effects.ts b/src/buttonEvents/Effects.ts index a09ffaa..8607493 100644 --- a/src/buttonEvents/Effects.ts +++ b/src/buttonEvents/Effects.ts @@ -56,7 +56,7 @@ export default class Effects extends ButtonEvent { const effectDetail = EffectDetails.get(id); if (!effectDetail) { - await interaction.reply("Unable to find effect!"); + await interaction.reply("Effect not found in system!"); return; } @@ -114,7 +114,7 @@ export default class Effects extends ButtonEvent { const effectDetail = EffectDetails.get(id); if (!effectDetail) { - await interaction.reply("Unable to find effect!"); + await interaction.reply("Effect not found in system!"); return; } diff --git a/tests/buttonEvents/Effects.test.ts b/tests/buttonEvents/Effects.test.ts index b500491..8a2f755 100644 --- a/tests/buttonEvents/Effects.test.ts +++ b/tests/buttonEvents/Effects.test.ts @@ -80,14 +80,14 @@ describe("Effects", () => { interaction.customId = "effects use confirm invalid"; await effects.UseConfirm(interaction); - expect(interaction.reply).toHaveBeenCalledWith("Unable to find effect!"); + expect(interaction.reply).toHaveBeenCalledWith("Effect not found in system!"); }); it("should reply with error message when effect detail is not found in UseCancel", async () => { interaction.customId = "effects use cancel invalid"; await effects.UseCancel(interaction); - expect(interaction.reply).toHaveBeenCalledWith("Unable to find effect!"); + expect(interaction.reply).toHaveBeenCalledWith("Effect not found in system!"); }); it("should update interaction with embed and row when effect is used successfully", async () => { -- 2.45.2 From 0092d91ee6c6ae42ebcb74fc6fe5b3f3990112db Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Fri, 3 Jan 2025 14:41:08 +0000 Subject: [PATCH 14/22] Fix undefined error if allCards variable is null --- src/helpers/CardDropHelperMetadata.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/helpers/CardDropHelperMetadata.ts b/src/helpers/CardDropHelperMetadata.ts index 80f3d6b..8ca9e51 100644 --- a/src/helpers/CardDropHelperMetadata.ts +++ b/src/helpers/CardDropHelperMetadata.ts @@ -97,6 +97,8 @@ export default class CardDropHelperMetadata { .filter(x => x.type == rarity) .filter(x => !claimedCards.find(y => y.CardNumber == x.id)); + if (!allCards) return undefined; + const randomCardIndex = Math.floor(Math.random() * allCards.length); const card = allCards[randomCardIndex]; -- 2.45.2 From 31cc9e056a372e4bed8309a0d711ec305f855bec Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Fri, 10 Jan 2025 18:23:17 +0000 Subject: [PATCH 15/22] WIP: Fix some of the suggested changes --- src/buttonEvents/Claim.ts | 9 +- src/buttonEvents/Effects.ts | 152 +---------- src/buttonEvents/Effects/List.ts | 20 ++ src/buttonEvents/Effects/Use.ts | 125 ++++++++++ src/buttonEvents/Multidrop.ts | 19 +- src/buttonEvents/Reroll.ts | 9 +- src/buttonEvents/Sacrifice.ts | 6 +- src/commands/drop.ts | 12 +- src/commands/effects.ts | 9 +- src/commands/give.ts | 4 +- src/commands/id.ts | 4 +- src/commands/multidrop.ts | 9 +- src/commands/sacrifice.ts | 4 +- src/commands/stage/dropnumber.ts | 6 +- src/commands/stage/droprarity.ts | 9 +- src/constants/CardConstants.ts | 2 +- src/helpers/CardDropHelperMetadata.ts | 235 ------------------ src/helpers/CardSearchHelper.ts | 15 +- src/helpers/DropHelpers/DropEmbedHelper.ts | 79 ++++++ src/helpers/DropHelpers/GetCardsHelper.ts | 78 ++++++ .../DropHelpers/GetUnclaimedCardsHelper.ts | 69 +++++ .../DropHelpers/MultidropEmbedHelper.ts | 28 +++ 22 files changed, 469 insertions(+), 434 deletions(-) create mode 100644 src/buttonEvents/Effects/List.ts create mode 100644 src/buttonEvents/Effects/Use.ts delete mode 100644 src/helpers/CardDropHelperMetadata.ts create mode 100644 src/helpers/DropHelpers/DropEmbedHelper.ts create mode 100644 src/helpers/DropHelpers/GetCardsHelper.ts create mode 100644 src/helpers/DropHelpers/GetUnclaimedCardsHelper.ts create mode 100644 src/helpers/DropHelpers/MultidropEmbedHelper.ts diff --git a/src/buttonEvents/Claim.ts b/src/buttonEvents/Claim.ts index 522ab40..27b82b1 100644 --- a/src/buttonEvents/Claim.ts +++ b/src/buttonEvents/Claim.ts @@ -4,9 +4,10 @@ import Inventory from "../database/entities/app/Inventory"; import { CoreClient } from "../client/client"; import { default as eClaim } from "../database/entities/app/Claim"; import AppLogger from "../client/appLogger"; -import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata"; import User from "../database/entities/app/User"; import CardConstants from "../constants/CardConstants"; +import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper"; +import DropEmbedHelper from "../helpers/DropHelpers/DropEmbedHelper"; export default class Claim extends ButtonEvent { public override async execute(interaction: ButtonInteraction) { @@ -69,7 +70,7 @@ export default class Claim extends ButtonEvent { await claim.Save(eClaim, claim); - const card = CardDropHelperMetadata.GetCardByCardNumber(cardNumber); + const card = GetCardsHelper.GetCardByCardNumber(cardNumber); if (!card) { return; @@ -77,8 +78,8 @@ export default class Claim extends ButtonEvent { const imageFileName = card.card.path.split("/").pop()!; - const embed = CardDropHelperMetadata.GenerateDropEmbed(card, inventory.Quantity, imageFileName, interaction.user.username, user.Currency); - const row = CardDropHelperMetadata.GenerateDropButtons(card, claimId, interaction.user.id, true); + const embed = DropEmbedHelper.GenerateDropEmbed(card, inventory.Quantity, imageFileName, interaction.user.username, user.Currency); + const row = DropEmbedHelper.GenerateDropButtons(card, claimId, interaction.user.id, true); await interaction.editReply({ embeds: [ embed ], diff --git a/src/buttonEvents/Effects.ts b/src/buttonEvents/Effects.ts index 8607493..5fb1bb8 100644 --- a/src/buttonEvents/Effects.ts +++ b/src/buttonEvents/Effects.ts @@ -1,9 +1,7 @@ -import {ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, EmbedBuilder} from "discord.js"; -import {ButtonEvent} from "../type/buttonEvent"; -import EffectHelper from "../helpers/EffectHelper"; -import { EffectDetails } from "../constants/EffectDetails"; -import TimeLengthInput from "../helpers/TimeLengthInput"; -import EmbedColours from "../constants/EmbedColours"; +import { ButtonInteraction } from "discord.js"; +import { ButtonEvent } from "../type/buttonEvent"; +import List from "./Effects/List"; +import Use from "./Effects/Use"; export default class Effects extends ButtonEvent { public override async execute(interaction: ButtonInteraction) { @@ -11,149 +9,11 @@ export default class Effects extends ButtonEvent { switch (action) { case "list": - await this.List(interaction); + await List(interaction); break; case "use": - await this.Use(interaction); + await Use(interaction); break; } } - - public async List(interaction: ButtonInteraction) { - const pageOption = interaction.customId.split(" ")[2]; - - const page = Number(pageOption); - - if (!page) { - await interaction.reply("Page option is not a valid number"); - return; - } - - const result = await EffectHelper.GenerateEffectEmbed(interaction.user.id, page); - - await interaction.update({ - embeds: [ result.embed ], - components: [ result.row ], - }); - } - - public async Use(interaction: ButtonInteraction) { - const subaction = interaction.customId.split(" ")[2]; - - switch (subaction) { - case "confirm": - await this.UseConfirm(interaction); - break; - case "cancel": - await this.UseCancel(interaction); - break; - } - } - - public async UseConfirm(interaction: ButtonInteraction) { - const id = interaction.customId.split(" ")[3]; - - const effectDetail = EffectDetails.get(id); - - if (!effectDetail) { - await interaction.reply("Effect not found in system!"); - return; - } - - const now = new Date(); - - const whenExpires = new Date(now.getTime() + effectDetail.duration); - - const result = await EffectHelper.UseEffect(interaction.user.id, id, whenExpires); - - if (result) { - const embed = new EmbedBuilder() - .setTitle("Effect Used") - .setDescription("You now have an active effect!") - .setColor(EmbedColours.Green) - .addFields([ - { - name: "Effect", - value: effectDetail.friendlyName, - inline: true, - }, - { - name: "Expires", - value: ``, - inline: true, - }, - ]); - - const row = new ActionRowBuilder() - .addComponents([ - new ButtonBuilder() - .setLabel("Confirm") - .setCustomId(`effects use confirm ${effectDetail.id}`) - .setStyle(ButtonStyle.Primary) - .setDisabled(true), - new ButtonBuilder() - .setLabel("Cancel") - .setCustomId(`effects use cancel ${effectDetail.id}`) - .setStyle(ButtonStyle.Danger) - .setDisabled(true), - ]); - - await interaction.update({ - embeds: [ embed ], - components: [ row ], - }); - return; - } - - await interaction.reply("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown"); - } - - public async UseCancel(interaction: ButtonInteraction) { - const id = interaction.customId.split(" ")[3]; - - const effectDetail = EffectDetails.get(id); - - if (!effectDetail) { - await interaction.reply("Effect not found in system!"); - return; - } - - const timeLengthInput = TimeLengthInput.ConvertFromMilliseconds(effectDetail.duration); - - const embed = new EmbedBuilder() - .setTitle("Effect Use Cancelled") - .setDescription("The effect from your inventory has not been used") - .setColor(EmbedColours.Grey) - .addFields([ - { - name: "Effect", - value: effectDetail.friendlyName, - inline: true, - }, - { - name: "Expires", - value: timeLengthInput.GetLengthShort(), - inline: true, - }, - ]); - - const row = new ActionRowBuilder() - .addComponents([ - new ButtonBuilder() - .setLabel("Confirm") - .setCustomId(`effects use confirm ${effectDetail.id}`) - .setStyle(ButtonStyle.Primary) - .setDisabled(true), - new ButtonBuilder() - .setLabel("Cancel") - .setCustomId(`effects use cancel ${effectDetail.id}`) - .setStyle(ButtonStyle.Danger) - .setDisabled(true), - ]); - - await interaction.update({ - embeds: [ embed ], - components: [ row ], - }); - } } diff --git a/src/buttonEvents/Effects/List.ts b/src/buttonEvents/Effects/List.ts new file mode 100644 index 0000000..059623b --- /dev/null +++ b/src/buttonEvents/Effects/List.ts @@ -0,0 +1,20 @@ +import { ButtonInteraction } from "discord.js"; +import EffectHelper from "../../helpers/EffectHelper"; + +export default async function List(interaction: ButtonInteraction) { + const pageOption = interaction.customId.split(" ")[2]; + + const page = Number(pageOption); + + if (!page) { + await interaction.reply("Page option is not a valid number"); + return; + } + + const result = await EffectHelper.GenerateEffectEmbed(interaction.user.id, page); + + await interaction.update({ + embeds: [ result.embed ], + components: [ result.row ], + }); +} \ No newline at end of file diff --git a/src/buttonEvents/Effects/Use.ts b/src/buttonEvents/Effects/Use.ts new file mode 100644 index 0000000..25c2ba2 --- /dev/null +++ b/src/buttonEvents/Effects/Use.ts @@ -0,0 +1,125 @@ +import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, EmbedBuilder } from "discord.js"; +import { EffectDetails } from "../../constants/EffectDetails"; +import EffectHelper from "../../helpers/EffectHelper"; +import EmbedColours from "../../constants/EmbedColours"; +import TimeLengthInput from "../../helpers/TimeLengthInput"; + +export default async function Use(interaction: ButtonInteraction) { + const subaction = interaction.customId.split(" ")[2]; + + switch (subaction) { + case "confirm": + await UseConfirm(interaction); + break; + case "cancel": + await UseCancel(interaction); + break; + } +} + +export async function UseConfirm(interaction: ButtonInteraction) { + const id = interaction.customId.split(" ")[3]; + + const effectDetail = EffectDetails.get(id); + + if (!effectDetail) { + await interaction.reply("Effect not found in system!"); + return; + } + + const now = new Date(); + + const whenExpires = new Date(now.getTime() + effectDetail.duration); + + const result = await EffectHelper.UseEffect(interaction.user.id, id, whenExpires); + + if (result) { + const embed = new EmbedBuilder() + .setTitle("Effect Used") + .setDescription("You now have an active effect!") + .setColor(EmbedColours.Green) + .addFields([ + { + name: "Effect", + value: effectDetail.friendlyName, + inline: true, + }, + { + name: "Expires", + value: ``, + inline: true, + }, + ]); + + const row = new ActionRowBuilder() + .addComponents([ + new ButtonBuilder() + .setLabel("Confirm") + .setCustomId(`effects use confirm ${effectDetail.id}`) + .setStyle(ButtonStyle.Primary) + .setDisabled(true), + new ButtonBuilder() + .setLabel("Cancel") + .setCustomId(`effects use cancel ${effectDetail.id}`) + .setStyle(ButtonStyle.Danger) + .setDisabled(true), + ]); + + await interaction.update({ + embeds: [ embed ], + components: [ row ], + }); + return; + } + + await interaction.reply("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown"); +} + +export async function UseCancel(interaction: ButtonInteraction) { + const id = interaction.customId.split(" ")[3]; + + const effectDetail = EffectDetails.get(id); + + if (!effectDetail) { + await interaction.reply("Effect not found in system!"); + return; + } + + const timeLengthInput = TimeLengthInput.ConvertFromMilliseconds(effectDetail.duration); + + const embed = new EmbedBuilder() + .setTitle("Effect Use Cancelled") + .setDescription("The effect from your inventory has not been used") + .setColor(EmbedColours.Grey) + .addFields([ + { + name: "Effect", + value: effectDetail.friendlyName, + inline: true, + }, + { + name: "Expires", + value: timeLengthInput.GetLengthShort(), + inline: true, + }, + ]); + + const row = new ActionRowBuilder() + .addComponents([ + new ButtonBuilder() + .setLabel("Confirm") + .setCustomId(`effects use confirm ${effectDetail.id}`) + .setStyle(ButtonStyle.Primary) + .setDisabled(true), + new ButtonBuilder() + .setLabel("Cancel") + .setCustomId(`effects use cancel ${effectDetail.id}`) + .setStyle(ButtonStyle.Danger) + .setDisabled(true), + ]); + + await interaction.update({ + embeds: [ embed ], + components: [ row ], + }); +} \ No newline at end of file diff --git a/src/buttonEvents/Multidrop.ts b/src/buttonEvents/Multidrop.ts index 604f02c..e6ea7c2 100644 --- a/src/buttonEvents/Multidrop.ts +++ b/src/buttonEvents/Multidrop.ts @@ -1,7 +1,6 @@ import { AttachmentBuilder, ButtonInteraction, EmbedBuilder } from "discord.js"; import { ButtonEvent } from "../type/buttonEvent"; import AppLogger from "../client/appLogger"; -import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata"; import Inventory from "../database/entities/app/Inventory"; import EmbedColours from "../constants/EmbedColours"; import { readFileSync } from "fs"; @@ -9,6 +8,8 @@ import path from "path"; import ErrorMessages from "../constants/ErrorMessages"; import User from "../database/entities/app/User"; import { GetSacrificeAmount } from "../constants/CardRarity"; +import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper"; +import MultidropEmbedHelper from "../helpers/DropHelpers/MultidropEmbedHelper"; export default class Multidrop extends ButtonEvent { public override async execute(interaction: ButtonInteraction) { @@ -37,7 +38,7 @@ export default class Multidrop extends ButtonEvent { return; } - const card = CardDropHelperMetadata.GetCardByCardNumber(cardNumber); + const card = GetCardsHelper.GetCardByCardNumber(cardNumber); if (!card) { await interaction.reply("Unable to find card."); @@ -85,7 +86,7 @@ export default class Multidrop extends ButtonEvent { } // Drop next card - const randomCard = CardDropHelperMetadata.GetRandomCard(); + const randomCard = GetCardsHelper.GetRandomCard(); cardsRemaining -= 1; if (!randomCard) { @@ -105,9 +106,9 @@ export default class Multidrop extends ButtonEvent { const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id); const quantityClaimed = inventory ? inventory.Quantity : 0; - const embed = CardDropHelperMetadata.GenerateMultidropEmbed(randomCard, quantityClaimed, imageFileName, cardsRemaining, undefined, user.Currency); + const embed = MultidropEmbedHelper.GenerateMultidropEmbed(randomCard, quantityClaimed, imageFileName, cardsRemaining, undefined, user.Currency); - const row = CardDropHelperMetadata.GenerateMultidropButtons(randomCard, cardsRemaining, interaction.user.id, cardsRemaining < 0); + const row = MultidropEmbedHelper.GenerateMultidropButtons(randomCard, cardsRemaining, interaction.user.id, cardsRemaining < 0); await interaction.editReply({ embeds: [ embed ], @@ -131,7 +132,7 @@ export default class Multidrop extends ButtonEvent { return; } - const card = CardDropHelperMetadata.GetCardByCardNumber(cardNumber); + const card = GetCardsHelper.GetCardByCardNumber(cardNumber); if (!card) { await interaction.reply("Unable to find card."); @@ -175,7 +176,7 @@ export default class Multidrop extends ButtonEvent { } // Drop next card - const randomCard = CardDropHelperMetadata.GetRandomCard(); + const randomCard = GetCardsHelper.GetRandomCard(); cardsRemaining -= 1; if (!randomCard) { @@ -195,9 +196,9 @@ export default class Multidrop extends ButtonEvent { const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id); const quantityClaimed = inventory ? inventory.Quantity : 0; - const embed = CardDropHelperMetadata.GenerateMultidropEmbed(randomCard, quantityClaimed, imageFileName, cardsRemaining, undefined, user.Currency); + const embed = MultidropEmbedHelper.GenerateMultidropEmbed(randomCard, quantityClaimed, imageFileName, cardsRemaining, undefined, user.Currency); - const row = CardDropHelperMetadata.GenerateMultidropButtons(randomCard, cardsRemaining, interaction.user.id, cardsRemaining < 0); + const row = MultidropEmbedHelper.GenerateMultidropButtons(randomCard, cardsRemaining, interaction.user.id, cardsRemaining < 0); await interaction.editReply({ embeds: [ embed ], diff --git a/src/buttonEvents/Reroll.ts b/src/buttonEvents/Reroll.ts index 12578db..814cccc 100644 --- a/src/buttonEvents/Reroll.ts +++ b/src/buttonEvents/Reroll.ts @@ -5,11 +5,12 @@ import { v4 } from "uuid"; import { CoreClient } from "../client/client"; import Inventory from "../database/entities/app/Inventory"; import Config from "../database/entities/app/Config"; -import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata"; import path from "path"; import AppLogger from "../client/appLogger"; import User from "../database/entities/app/User"; import CardConstants from "../constants/CardConstants"; +import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper"; +import DropEmbedHelper from "../helpers/DropHelpers/DropEmbedHelper"; export default class Reroll extends ButtonEvent { public override async execute(interaction: ButtonInteraction) { @@ -39,7 +40,7 @@ export default class Reroll extends ButtonEvent { return; } - const randomCard = CardDropHelperMetadata.GetRandomCard(); + const randomCard = GetCardsHelper.GetRandomCard(); if (!randomCard) { await interaction.reply("Unable to fetch card, please try again."); @@ -59,11 +60,11 @@ export default class Reroll extends ButtonEvent { const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id); const quantityClaimed = inventory ? inventory.Quantity : 0; - const embed = CardDropHelperMetadata.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName, undefined, user.Currency); + const embed = DropEmbedHelper.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName, undefined, user.Currency); const claimId = v4(); - const row = CardDropHelperMetadata.GenerateDropButtons(randomCard, claimId, interaction.user.id); + const row = DropEmbedHelper.GenerateDropButtons(randomCard, claimId, interaction.user.id); await interaction.editReply({ embeds: [ embed ], diff --git a/src/buttonEvents/Sacrifice.ts b/src/buttonEvents/Sacrifice.ts index 463ca1f..de0bb77 100644 --- a/src/buttonEvents/Sacrifice.ts +++ b/src/buttonEvents/Sacrifice.ts @@ -1,10 +1,10 @@ import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, EmbedBuilder } from "discord.js"; import { ButtonEvent } from "../type/buttonEvent"; import Inventory from "../database/entities/app/Inventory"; -import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata"; import { CardRarityToString, GetSacrificeAmount } from "../constants/CardRarity"; import EmbedColours from "../constants/EmbedColours"; import User from "../database/entities/app/User"; +import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper"; export default class Sacrifice extends ButtonEvent { public override async execute(interaction: ButtonInteraction) { @@ -42,7 +42,7 @@ export default class Sacrifice extends ButtonEvent { return; } - const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardNumber); + const cardData = GetCardsHelper.GetCardByCardNumber(cardNumber); if (!cardData) { await interaction.reply("Unable to find card in the database."); @@ -124,7 +124,7 @@ export default class Sacrifice extends ButtonEvent { return; } - const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardNumber); + const cardData = GetCardsHelper.GetCardByCardNumber(cardNumber); if (!cardData) { await interaction.reply("Unable to find card in the database."); diff --git a/src/commands/drop.ts b/src/commands/drop.ts index 8d82d81..bd4d3f6 100644 --- a/src/commands/drop.ts +++ b/src/commands/drop.ts @@ -5,7 +5,6 @@ import { CoreClient } from "../client/client"; import { v4 } from "uuid"; import Inventory from "../database/entities/app/Inventory"; import Config from "../database/entities/app/Config"; -import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata"; import path from "path"; import AppLogger from "../client/appLogger"; import User from "../database/entities/app/User"; @@ -13,6 +12,9 @@ import CardConstants from "../constants/CardConstants"; import ErrorMessages from "../constants/ErrorMessages"; import { DropResult } from "../contracts/SeriesMetadata"; import EffectHelper from "../helpers/EffectHelper"; +import GetUnclaimedCardsHelper from "../helpers/DropHelpers/GetUnclaimedCardsHelper"; +import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper"; +import DropEmbedHelper from "../helpers/DropHelpers/DropEmbedHelper"; export default class Drop extends Command { constructor() { @@ -54,9 +56,9 @@ export default class Drop extends Command { const hasChanceUpEffect = await EffectHelper.HasEffect(interaction.user.id, "unclaimed"); if (hasChanceUpEffect && Math.random() <= CardConstants.UnusedChanceUpChance) { - randomCard = await CardDropHelperMetadata.GetRandomCardUnclaimed(interaction.user.id); + randomCard = await GetUnclaimedCardsHelper.GetRandomCardUnclaimed(interaction.user.id); } else { - randomCard = CardDropHelperMetadata.GetRandomCard(); + randomCard = GetCardsHelper.GetRandomCard(); } if (!randomCard) { @@ -76,11 +78,11 @@ export default class Drop extends Command { const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id); const quantityClaimed = inventory ? inventory.Quantity : 0; - const embed = CardDropHelperMetadata.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName, undefined, user.Currency); + const embed = DropEmbedHelper.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName, undefined, user.Currency); const claimId = v4(); - const row = CardDropHelperMetadata.GenerateDropButtons(randomCard, claimId, interaction.user.id); + const row = DropEmbedHelper.GenerateDropButtons(randomCard, claimId, interaction.user.id); await interaction.editReply({ embeds: [ embed ], diff --git a/src/commands/effects.ts b/src/commands/effects.ts index bc4f743..98727b9 100644 --- a/src/commands/effects.ts +++ b/src/commands/effects.ts @@ -1,9 +1,10 @@ -import {ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder, SlashCommandBuilder} from "discord.js"; -import {Command} from "../type/command"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder, SlashCommandBuilder } from "discord.js"; +import { Command } from "../type/command"; import EffectHelper from "../helpers/EffectHelper"; -import {EffectDetails} from "../constants/EffectDetails"; +import { EffectDetails } from "../constants/EffectDetails"; import TimeLengthInput from "../helpers/TimeLengthInput"; import EmbedColours from "../constants/EmbedColours"; +import AppLogger from "../client/appLogger"; export default class Effects extends Command { constructor() { @@ -65,6 +66,8 @@ export default class Effects extends Command { const effectDetail = EffectDetails.get(id); if (!effectDetail) { + AppLogger.LogWarn("Commands/Effects", `Unable to find effect details for ${id}`); + await interaction.reply("Unable to find effect!"); return; } diff --git a/src/commands/give.ts b/src/commands/give.ts index 3ffbe8f..35dfa04 100644 --- a/src/commands/give.ts +++ b/src/commands/give.ts @@ -2,10 +2,10 @@ import { CacheType, CommandInteraction, PermissionsBitField, SlashCommandBuilder import { Command } from "../type/command"; import { CoreClient } from "../client/client"; import Config from "../database/entities/app/Config"; -import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata"; import Inventory from "../database/entities/app/Inventory"; import AppLogger from "../client/appLogger"; import User from "../database/entities/app/User"; +import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper"; export default class Give extends Command { constructor() { @@ -81,7 +81,7 @@ export default class Give extends Command { AppLogger.LogSilly("Commands/Give/GiveCard", `Parameters: cardNumber=${cardNumber.value}, user=${user.id}`); - const card = CardDropHelperMetadata.GetCardByCardNumber(cardNumber.value!.toString()); + const card = GetCardsHelper.GetCardByCardNumber(cardNumber.value!.toString()); if (!card) { await interaction.reply("Unable to fetch card, please try again."); diff --git a/src/commands/id.ts b/src/commands/id.ts index ae924a6..072553d 100644 --- a/src/commands/id.ts +++ b/src/commands/id.ts @@ -4,8 +4,8 @@ import { CoreClient } from "../client/client"; import { readFileSync } from "fs"; import path from "path"; import Inventory from "../database/entities/app/Inventory"; -import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata"; import AppLogger from "../client/appLogger"; +import DropEmbedHelper from "../helpers/DropHelpers/DropEmbedHelper"; export default class Id extends Command { constructor() { @@ -62,7 +62,7 @@ export default class Id extends Command { const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.id); const quantityClaimed = inventory ? inventory.Quantity : 0; - const embed = CardDropHelperMetadata.GenerateDropEmbed({ card, series }, quantityClaimed, imageFileName); + const embed = DropEmbedHelper.GenerateDropEmbed({ card, series }, quantityClaimed, imageFileName); try { await interaction.editReply({ diff --git a/src/commands/multidrop.ts b/src/commands/multidrop.ts index f35b921..aa42686 100644 --- a/src/commands/multidrop.ts +++ b/src/commands/multidrop.ts @@ -6,10 +6,11 @@ import Config from "../database/entities/app/Config"; import AppLogger from "../client/appLogger"; import User from "../database/entities/app/User"; import CardConstants from "../constants/CardConstants"; -import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata"; import { readFileSync } from "fs"; import path from "path"; import Inventory from "../database/entities/app/Inventory"; +import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper"; +import MultidropEmbedHelper from "../helpers/DropHelpers/MultidropEmbedHelper"; export default class Multidrop extends Command { constructor() { @@ -49,7 +50,7 @@ export default class Multidrop extends Command { user.RemoveCurrency(CardConstants.MultidropCost); await user.Save(User, user); - const randomCard = CardDropHelperMetadata.GetRandomCard(); + const randomCard = GetCardsHelper.GetRandomCard(); const cardsRemaining = CardConstants.MultidropQuantity - 1; if (!randomCard) { @@ -69,9 +70,9 @@ export default class Multidrop extends Command { const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id); const quantityClaimed = inventory ? inventory.Quantity : 0; - const embed = CardDropHelperMetadata.GenerateMultidropEmbed(randomCard, quantityClaimed, imageFileName, cardsRemaining, undefined, user.Currency); + const embed = MultidropEmbedHelper.GenerateMultidropEmbed(randomCard, quantityClaimed, imageFileName, cardsRemaining, undefined, user.Currency); - const row = CardDropHelperMetadata.GenerateMultidropButtons(randomCard, cardsRemaining, interaction.user.id); + const row = MultidropEmbedHelper.GenerateMultidropButtons(randomCard, cardsRemaining, interaction.user.id); await interaction.editReply({ embeds: [ embed ], diff --git a/src/commands/sacrifice.ts b/src/commands/sacrifice.ts index 9683714..a44a69e 100644 --- a/src/commands/sacrifice.ts +++ b/src/commands/sacrifice.ts @@ -2,8 +2,8 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CacheType, CommandInterac import { Command } from "../type/command"; import Inventory from "../database/entities/app/Inventory"; import { CardRarityToString, GetSacrificeAmount } from "../constants/CardRarity"; -import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata"; import EmbedColours from "../constants/EmbedColours"; +import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper"; export default class Sacrifice extends Command { constructor() { @@ -41,7 +41,7 @@ export default class Sacrifice extends Command { return; } - const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardnumber.value! as string); + const cardData = GetCardsHelper.GetCardByCardNumber(cardnumber.value! as string); if (!cardData) { await interaction.reply("Unable to find card in the database."); diff --git a/src/commands/stage/dropnumber.ts b/src/commands/stage/dropnumber.ts index 0642327..6819a5c 100644 --- a/src/commands/stage/dropnumber.ts +++ b/src/commands/stage/dropnumber.ts @@ -5,7 +5,7 @@ import Inventory from "../../database/entities/app/Inventory"; import { v4 } from "uuid"; import { CoreClient } from "../../client/client"; import path from "path"; -import CardDropHelperMetadata from "../../helpers/CardDropHelperMetadata"; +import DropEmbedHelper from "../../helpers/DropHelpers/DropEmbedHelper"; export default class Dropnumber extends Command { constructor() { @@ -60,11 +60,11 @@ export default class Dropnumber extends Command { const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.id); const quantityClaimed = inventory ? inventory.Quantity : 0; - const embed = CardDropHelperMetadata.GenerateDropEmbed({ card, series }, quantityClaimed, imageFileName); + const embed = DropEmbedHelper.GenerateDropEmbed({ card, series }, quantityClaimed, imageFileName); const claimId = v4(); - const row = CardDropHelperMetadata.GenerateDropButtons({ card, series }, claimId, interaction.user.id); + const row = DropEmbedHelper.GenerateDropButtons({ card, series }, claimId, interaction.user.id); try { await interaction.editReply({ diff --git a/src/commands/stage/droprarity.ts b/src/commands/stage/droprarity.ts index be0a62d..517a443 100644 --- a/src/commands/stage/droprarity.ts +++ b/src/commands/stage/droprarity.ts @@ -5,8 +5,9 @@ import { readFileSync } from "fs"; import Inventory from "../../database/entities/app/Inventory"; import { v4 } from "uuid"; import { CoreClient } from "../../client/client"; -import CardDropHelperMetadata from "../../helpers/CardDropHelperMetadata"; import path from "path"; +import GetCardsHelper from "../../helpers/DropHelpers/GetCardsHelper"; +import DropEmbedHelper from "../../helpers/DropHelpers/DropEmbedHelper"; export default class Droprarity extends Command { constructor() { @@ -39,7 +40,7 @@ export default class Droprarity extends Command { return; } - const card = await CardDropHelperMetadata.GetRandomCardByRarity(rarityType); + const card = await GetCardsHelper.GetRandomCardByRarity(rarityType); if (!card) { await interaction.reply("Card not found"); @@ -63,11 +64,11 @@ export default class Droprarity extends Command { const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.card.id); const quantityClaimed = inventory ? inventory.Quantity : 0; - const embed = CardDropHelperMetadata.GenerateDropEmbed(card, quantityClaimed, imageFileName); + const embed = DropEmbedHelper.GenerateDropEmbed(card, quantityClaimed, imageFileName); const claimId = v4(); - const row = CardDropHelperMetadata.GenerateDropButtons(card, claimId, interaction.user.id); + const row = DropEmbedHelper.GenerateDropButtons(card, claimId, interaction.user.id); try { await interaction.editReply({ diff --git a/src/constants/CardConstants.ts b/src/constants/CardConstants.ts index 9330111..60a7f59 100644 --- a/src/constants/CardConstants.ts +++ b/src/constants/CardConstants.ts @@ -9,5 +9,5 @@ export default class CardConstants { public static readonly MultidropQuantity = 11; // Effects - public static readonly UnusedChanceUpChance = 1; + public static readonly UnusedChanceUpChance = 0.5; } \ No newline at end of file diff --git a/src/helpers/CardDropHelperMetadata.ts b/src/helpers/CardDropHelperMetadata.ts deleted file mode 100644 index 8ca9e51..0000000 --- a/src/helpers/CardDropHelperMetadata.ts +++ /dev/null @@ -1,235 +0,0 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js"; -import { CardRarity, CardRarityToColour, CardRarityToString, GetSacrificeAmount } from "../constants/CardRarity"; -import CardRarityChances from "../constants/CardRarityChances"; -import { DropResult } from "../contracts/SeriesMetadata"; -import { CoreClient } from "../client/client"; -import AppLogger from "../client/appLogger"; -import CardConstants from "../constants/CardConstants"; -import StringTools from "./StringTools"; -import Inventory from "../database/entities/app/Inventory"; - -export default class CardDropHelperMetadata { - public static GetRandomCard(): DropResult | undefined { - const randomRarity = Math.random() * 100; - - let cardRarity: CardRarity; - - const bronzeChance = CardRarityChances.Bronze; - const silverChance = bronzeChance + CardRarityChances.Silver; - const goldChance = silverChance + CardRarityChances.Gold; - const mangaChance = goldChance + CardRarityChances.Manga; - - if (randomRarity < bronzeChance) cardRarity = CardRarity.Bronze; - else if (randomRarity < silverChance) cardRarity = CardRarity.Silver; - else if (randomRarity < goldChance) cardRarity = CardRarity.Gold; - else if (randomRarity < mangaChance) cardRarity = CardRarity.Manga; - else cardRarity = CardRarity.Legendary; - - const randomCard = this.GetRandomCardByRarity(cardRarity); - - AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCard", `Random card: ${randomCard?.card.id} ${randomCard?.card.name}`); - - return randomCard; - } - - public static GetRandomCardByRarity(rarity: CardRarity): DropResult | undefined { - AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCardByRarity", `Parameters: rarity=${rarity}`); - - const allCards = CoreClient.Cards - .flatMap(x => x.cards) - .filter(x => x.type == rarity); - - const randomCardIndex = Math.floor(Math.random() * allCards.length); - - const card = allCards[randomCardIndex]; - const series = CoreClient.Cards - .find(x => x.cards.includes(card)); - - if (!series) { - AppLogger.LogWarn("CardDropHelperMetadata/GetRandomCardByRarity", `Series not found for card ${card.id}`); - - return undefined; - } - - AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCardByRarity", `Random card: ${card.id} ${card.name}`); - - return { - series: series, - card: card, - }; - } - - public static async GetRandomCardUnclaimed(userId: string): Promise { - const randomRarity = Math.random() * 100; - - let cardRarity: CardRarity; - - const bronzeChance = CardRarityChances.Bronze; - const silverChance = bronzeChance + CardRarityChances.Silver; - const goldChance = silverChance + CardRarityChances.Gold; - const mangaChance = goldChance + CardRarityChances.Manga; - - if (randomRarity < bronzeChance) cardRarity = CardRarity.Bronze; - else if (randomRarity < silverChance) cardRarity = CardRarity.Silver; - else if (randomRarity < goldChance) cardRarity = CardRarity.Gold; - else if (randomRarity < mangaChance) cardRarity = CardRarity.Manga; - else cardRarity = CardRarity.Legendary; - - const randomCard = await this.GetRandomCardByRarityUnclaimed(cardRarity, userId); - - AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCardUnclaimed", `Random card: ${randomCard?.card.id} ${randomCard?.card.name}`); - - return randomCard; - } - - public static async GetRandomCardByRarityUnclaimed(rarity: CardRarity, userId: string): Promise { - AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCardByRarityUnclaimed", `Parameters: rarity=${rarity}, userId=${userId}`); - - const claimedCards = await Inventory.FetchAllByUserId(userId); - - if (!claimedCards) { - // They don't have any cards, so safe to get any random card - return this.GetRandomCardByRarity(rarity); - } - - const allCards = CoreClient.Cards - .flatMap(x => x.cards) - .filter(x => x.type == rarity) - .filter(x => !claimedCards.find(y => y.CardNumber == x.id)); - - if (!allCards) return undefined; - - const randomCardIndex = Math.floor(Math.random() * allCards.length); - - const card = allCards[randomCardIndex]; - const series = CoreClient.Cards - .find(x => x.cards.includes(card)); - - if (!series) { - AppLogger.LogWarn("CardDropHelperMetadata/GetRandomCardByRarityUnclaimed", `Series not found for card ${card.id}`); - - return undefined; - } - - AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCardByRarityUnclaimed", `Random card: ${card.id} ${card.name}`); - - return { - series: series, - card: card, - }; - } - - public static GetCardByCardNumber(cardNumber: string): DropResult | undefined { - AppLogger.LogSilly("CardDropHelperMetadata/GetCardByCardNumber", `Parameters: cardNumber=${cardNumber}`); - - const card = CoreClient.Cards - .flatMap(x => x.cards) - .find(x => x.id == cardNumber); - - const series = CoreClient.Cards - .find(x => x.cards.find(y => y.id == card?.id)); - - AppLogger.LogSilly("CardDropHelperMetadata/GetCardByCardNumber", `Card: ${card?.id} ${card?.name}`); - AppLogger.LogSilly("CardDropHelperMetadata/GetCardByCardNumber", `Series: ${series?.id} ${series?.name}`); - - if (!card || !series) { - AppLogger.LogVerbose("CardDropHelperMetadata/GetCardByCardNumber", `Unable to find card metadata: ${cardNumber}`); - return undefined; - } - - return { card, series }; - } - - public static GenerateDropEmbed(drop: DropResult, quantityClaimed: number, imageFileName: string, claimedBy?: string, currency?: number): EmbedBuilder { - AppLogger.LogSilly("CardDropHelperMetadata/GenerateDropEmbed", `Parameters: drop=${drop.card.id}, quantityClaimed=${quantityClaimed}, imageFileName=${imageFileName}`); - - const description = drop.card.subseries ?? drop.series.name; - let colour = CardRarityToColour(drop.card.type); - - if (drop.card.colour && StringTools.IsHexCode(drop.card.colour)) { - const hexCode = Number("0x" + drop.card.colour); - - if (hexCode) { - colour = hexCode; - } else { - AppLogger.LogWarn("CardDropHelperMetadata/GenerateDropEmbed", `Card's colour override is invalid: ${drop.card.id}, ${drop.card.colour}`); - } - } else if (drop.card.colour) { - AppLogger.LogWarn("CardDropHelperMetadata/GenerateDropEmbed", `Card's colour override is invalid: ${drop.card.id}, ${drop.card.colour}`); - } - - const embed = new EmbedBuilder() - .setTitle(drop.card.name) - .setDescription(description) - .setFooter({ text: `${CardRarityToString(drop.card.type)} · ${drop.card.id}` }) - .setColor(colour) - .setImage(`attachment://${imageFileName}`) - .addFields([ - { - name: "Claimed", - value: `${quantityClaimed}`, - inline: true, - } - ]); - - if (claimedBy != null) { - embed.addFields([ - { - name: "Claimed by", - value: claimedBy, - inline: true, - } - ]); - } - - if (currency != null) { - embed.addFields([ - { - name: "Currency", - value: `${currency}`, - inline: true, - } - ]); - } - - return embed; - } - - public static GenerateDropButtons(drop: DropResult, claimId: string, userId: string, disabled: boolean = false): ActionRowBuilder { - AppLogger.LogSilly("CardDropHelperMetadata/GenerateDropButtons", `Parameters: drop=${drop.card.id}, claimId=${claimId}, userId=${userId}`); - - return new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setCustomId(`claim ${drop.card.id} ${claimId} ${userId}`) - .setLabel(`Claim (${CardConstants.ClaimCost} 🪙)`) - .setStyle(ButtonStyle.Primary) - .setDisabled(disabled), - new ButtonBuilder() - .setCustomId("reroll") - .setLabel("Reroll") - .setStyle(ButtonStyle.Secondary)); - } - - public static GenerateMultidropEmbed(drop: DropResult, quantityClaimed: number, imageFileName: string, cardsRemaining: number, claimedBy?: string, currency?: number): EmbedBuilder { - const dropEmbed = this.GenerateDropEmbed(drop, quantityClaimed, imageFileName, claimedBy, currency); - - dropEmbed.setFooter({ text: `${dropEmbed.data.footer?.text} · ${cardsRemaining} Remaining`}); - - return dropEmbed; - } - - public static GenerateMultidropButtons(drop: DropResult, cardsRemaining: number, userId: string, disabled = false): ActionRowBuilder { - return new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setCustomId(`multidrop keep ${drop.card.id} ${cardsRemaining} ${userId}`) - .setLabel("Keep") - .setStyle(ButtonStyle.Primary) - .setDisabled(disabled), - new ButtonBuilder() - .setCustomId(`multidrop sacrifice ${drop.card.id} ${cardsRemaining} ${userId}`) - .setLabel(`Sacrifice (+${GetSacrificeAmount(drop.card.type)} 🪙)`) - .setStyle(ButtonStyle.Secondary)); - } -} diff --git a/src/helpers/CardSearchHelper.ts b/src/helpers/CardSearchHelper.ts index 29014f7..6e848f4 100644 --- a/src/helpers/CardSearchHelper.ts +++ b/src/helpers/CardSearchHelper.ts @@ -1,11 +1,12 @@ import {ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder} from "discord.js"; import Fuse from "fuse.js"; import {CoreClient} from "../client/client.js"; -import CardDropHelperMetadata from "./CardDropHelperMetadata.js"; import Inventory from "../database/entities/app/Inventory.js"; import {readFileSync} from "fs"; import path from "path"; import AppLogger from "../client/appLogger.js"; +import GetCardsHelper from "./DropHelpers/GetCardsHelper.js"; +import DropEmbedHelper from "./DropHelpers/DropEmbedHelper.js"; interface ReturnedPage { embed: EmbedBuilder, @@ -32,7 +33,7 @@ export default class CardSearchHelper { return undefined; } - const card = CardDropHelperMetadata.GetCardByCardNumber(entry.item.id); + const card = GetCardsHelper.GetCardByCardNumber(entry.item.id); if (!card) return undefined; @@ -43,16 +44,16 @@ export default class CardSearchHelper { image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path)); } catch { AppLogger.LogError("CardSearchHelper/GenerateSearchQuery", `Unable to fetch image for card ${card.card.id}.`); - + return undefined; } const attachment = new AttachmentBuilder(image, { name: imageFileName }); - + const inventory = await Inventory.FetchOneByCardNumberAndUserId(userid, card.card.id); const quantityClaimed = inventory?.Quantity ?? 0; - const embed = CardDropHelperMetadata.GenerateDropEmbed(card, quantityClaimed, imageFileName); + const embed = DropEmbedHelper.GenerateDropEmbed(card, quantityClaimed, imageFileName); const row = new ActionRowBuilder() .addComponents( @@ -73,7 +74,7 @@ export default class CardSearchHelper { public static async GenerateSearchPageFromQuery(results: string[], userid: string, page: number): Promise { const currentPageId = results[page - 1]; - const card = CardDropHelperMetadata.GetCardByCardNumber(currentPageId); + const card = GetCardsHelper.GetCardByCardNumber(currentPageId); if (!card) { AppLogger.LogError("CardSearchHelper/GenerateSearchPageFromQuery", `Unable to find card by id: ${currentPageId}.`); @@ -97,7 +98,7 @@ export default class CardSearchHelper { const inventory = await Inventory.FetchOneByCardNumberAndUserId(userid, card.card.id); const quantityClaimed = inventory?.Quantity ?? 0; - const embed = CardDropHelperMetadata.GenerateDropEmbed(card, quantityClaimed, imageFileName); + const embed = DropEmbedHelper.GenerateDropEmbed(card, quantityClaimed, imageFileName); const row = new ActionRowBuilder() .addComponents( diff --git a/src/helpers/DropHelpers/DropEmbedHelper.ts b/src/helpers/DropHelpers/DropEmbedHelper.ts new file mode 100644 index 0000000..ba1ba47 --- /dev/null +++ b/src/helpers/DropHelpers/DropEmbedHelper.ts @@ -0,0 +1,79 @@ +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js"; +import { DropResult } from "../../contracts/SeriesMetadata"; +import AppLogger from "../../client/appLogger"; +import { CardRarityToColour, CardRarityToString } from "../../constants/CardRarity"; +import StringTools from "../StringTools"; +import CardConstants from "../../constants/CardConstants"; + +export default class DropEmbedHelper { + public static GenerateDropEmbed(drop: DropResult, quantityClaimed: number, imageFileName: string, claimedBy?: string, currency?: number): EmbedBuilder { + AppLogger.LogSilly("CardDropHelperMetadata/GenerateDropEmbed", `Parameters: drop=${drop.card.id}, quantityClaimed=${quantityClaimed}, imageFileName=${imageFileName}`); + + const description = drop.card.subseries ?? drop.series.name; + let colour = CardRarityToColour(drop.card.type); + + if (drop.card.colour && StringTools.IsHexCode(drop.card.colour)) { + const hexCode = Number("0x" + drop.card.colour); + + if (hexCode) { + colour = hexCode; + } else { + AppLogger.LogWarn("CardDropHelperMetadata/GenerateDropEmbed", `Card's colour override is invalid: ${drop.card.id}, ${drop.card.colour}`); + } + } else if (drop.card.colour) { + AppLogger.LogWarn("CardDropHelperMetadata/GenerateDropEmbed", `Card's colour override is invalid: ${drop.card.id}, ${drop.card.colour}`); + } + + const embed = new EmbedBuilder() + .setTitle(drop.card.name) + .setDescription(description) + .setFooter({ text: `${CardRarityToString(drop.card.type)} · ${drop.card.id}` }) + .setColor(colour) + .setImage(`attachment://${imageFileName}`) + .addFields([ + { + name: "Claimed", + value: `${quantityClaimed}`, + inline: true, + } + ]); + + if (claimedBy != null) { + embed.addFields([ + { + name: "Claimed by", + value: claimedBy, + inline: true, + } + ]); + } + + if (currency != null) { + embed.addFields([ + { + name: "Currency", + value: `${currency}`, + inline: true, + } + ]); + } + + return embed; + } + + public static GenerateDropButtons(drop: DropResult, claimId: string, userId: string, disabled: boolean = false): ActionRowBuilder { + AppLogger.LogSilly("CardDropHelperMetadata/GenerateDropButtons", `Parameters: drop=${drop.card.id}, claimId=${claimId}, userId=${userId}`); + + return new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId(`claim ${drop.card.id} ${claimId} ${userId}`) + .setLabel(`Claim (${CardConstants.ClaimCost} 🪙)`) + .setStyle(ButtonStyle.Primary) + .setDisabled(disabled), + new ButtonBuilder() + .setCustomId("reroll") + .setLabel("Reroll") + .setStyle(ButtonStyle.Secondary)); + } +} \ No newline at end of file diff --git a/src/helpers/DropHelpers/GetCardsHelper.ts b/src/helpers/DropHelpers/GetCardsHelper.ts new file mode 100644 index 0000000..cdf4209 --- /dev/null +++ b/src/helpers/DropHelpers/GetCardsHelper.ts @@ -0,0 +1,78 @@ +import AppLogger from "../../client/appLogger"; +import { CoreClient } from "../../client/client"; +import { CardRarity } from "../../constants/CardRarity"; +import CardRarityChances from "../../constants/CardRarityChances"; +import { DropResult } from "../../contracts/SeriesMetadata"; + +export default class GetCardsHelper { + public static GetRandomCard(): DropResult | undefined { + const randomRarity = Math.random() * 100; + + let cardRarity: CardRarity; + + const bronzeChance = CardRarityChances.Bronze; + const silverChance = bronzeChance + CardRarityChances.Silver; + const goldChance = silverChance + CardRarityChances.Gold; + const mangaChance = goldChance + CardRarityChances.Manga; + + if (randomRarity < bronzeChance) cardRarity = CardRarity.Bronze; + else if (randomRarity < silverChance) cardRarity = CardRarity.Silver; + else if (randomRarity < goldChance) cardRarity = CardRarity.Gold; + else if (randomRarity < mangaChance) cardRarity = CardRarity.Manga; + else cardRarity = CardRarity.Legendary; + + const randomCard = this.GetRandomCardByRarity(cardRarity); + + AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCard", `Random card: ${randomCard?.card.id} ${randomCard?.card.name}`); + + return randomCard; + } + + public static GetRandomCardByRarity(rarity: CardRarity): DropResult | undefined { + AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCardByRarity", `Parameters: rarity=${rarity}`); + + const allCards = CoreClient.Cards + .flatMap(x => x.cards) + .filter(x => x.type == rarity); + + const randomCardIndex = Math.floor(Math.random() * allCards.length); + + const card = allCards[randomCardIndex]; + const series = CoreClient.Cards + .find(x => x.cards.includes(card)); + + if (!series) { + AppLogger.LogWarn("CardDropHelperMetadata/GetRandomCardByRarity", `Series not found for card ${card.id}`); + + return undefined; + } + + AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCardByRarity", `Random card: ${card.id} ${card.name}`); + + return { + series: series, + card: card, + }; + } + + public static GetCardByCardNumber(cardNumber: string): DropResult | undefined { + AppLogger.LogSilly("CardDropHelperMetadata/GetCardByCardNumber", `Parameters: cardNumber=${cardNumber}`); + + const card = CoreClient.Cards + .flatMap(x => x.cards) + .find(x => x.id == cardNumber); + + const series = CoreClient.Cards + .find(x => x.cards.find(y => y.id == card?.id)); + + AppLogger.LogSilly("CardDropHelperMetadata/GetCardByCardNumber", `Card: ${card?.id} ${card?.name}`); + AppLogger.LogSilly("CardDropHelperMetadata/GetCardByCardNumber", `Series: ${series?.id} ${series?.name}`); + + if (!card || !series) { + AppLogger.LogVerbose("CardDropHelperMetadata/GetCardByCardNumber", `Unable to find card metadata: ${cardNumber}`); + return undefined; + } + + return { card, series }; + } +} diff --git a/src/helpers/DropHelpers/GetUnclaimedCardsHelper.ts b/src/helpers/DropHelpers/GetUnclaimedCardsHelper.ts new file mode 100644 index 0000000..42e9cd7 --- /dev/null +++ b/src/helpers/DropHelpers/GetUnclaimedCardsHelper.ts @@ -0,0 +1,69 @@ +import AppLogger from "../../client/appLogger"; +import { CoreClient } from "../../client/client"; +import { CardRarity } from "../../constants/CardRarity"; +import CardRarityChances from "../../constants/CardRarityChances"; +import { DropResult } from "../../contracts/SeriesMetadata"; +import Inventory from "../../database/entities/app/Inventory"; +import GetCardsHelper from "./GetCardsHelper"; + +export default class GetUnclaimedCardsHelper { + public static async GetRandomCardUnclaimed(userId: string): Promise { + const randomRarity = Math.random() * 100; + + let cardRarity: CardRarity; + + const bronzeChance = CardRarityChances.Bronze; + const silverChance = bronzeChance + CardRarityChances.Silver; + const goldChance = silverChance + CardRarityChances.Gold; + const mangaChance = goldChance + CardRarityChances.Manga; + + if (randomRarity < bronzeChance) cardRarity = CardRarity.Bronze; + else if (randomRarity < silverChance) cardRarity = CardRarity.Silver; + else if (randomRarity < goldChance) cardRarity = CardRarity.Gold; + else if (randomRarity < mangaChance) cardRarity = CardRarity.Manga; + else cardRarity = CardRarity.Legendary; + + const randomCard = await this.GetRandomCardByRarityUnclaimed(cardRarity, userId); + + AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCardUnclaimed", `Random card: ${randomCard?.card.id} ${randomCard?.card.name}`); + + return randomCard; + } + + public static async GetRandomCardByRarityUnclaimed(rarity: CardRarity, userId: string): Promise { + AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCardByRarityUnclaimed", `Parameters: rarity=${rarity}, userId=${userId}`); + + const claimedCards = await Inventory.FetchAllByUserId(userId); + + if (!claimedCards) { + // They don't have any cards, so safe to get any random card + return GetCardsHelper.GetRandomCardByRarity(rarity); + } + + const allCards = CoreClient.Cards + .flatMap(x => x.cards) + .filter(x => x.type == rarity) + .filter(x => !claimedCards.find(y => y.CardNumber == x.id)); + + if (!allCards) return undefined; + + const randomCardIndex = Math.floor(Math.random() * allCards.length); + + const card = allCards[randomCardIndex]; + const series = CoreClient.Cards + .find(x => x.cards.includes(card)); + + if (!series) { + AppLogger.LogWarn("CardDropHelperMetadata/GetRandomCardByRarityUnclaimed", `Series not found for card ${card.id}`); + + return undefined; + } + + AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCardByRarityUnclaimed", `Random card: ${card.id} ${card.name}`); + + return { + series: series, + card: card, + }; + } +} diff --git a/src/helpers/DropHelpers/MultidropEmbedHelper.ts b/src/helpers/DropHelpers/MultidropEmbedHelper.ts new file mode 100644 index 0000000..526993e --- /dev/null +++ b/src/helpers/DropHelpers/MultidropEmbedHelper.ts @@ -0,0 +1,28 @@ +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js"; +import { DropResult } from "../../contracts/SeriesMetadata"; +import { GetSacrificeAmount } from "../../constants/CardRarity"; +import DropEmbedHelper from "./DropEmbedHelper"; + +export default class MultidropEmbedHelper { + public static GenerateMultidropEmbed(drop: DropResult, quantityClaimed: number, imageFileName: string, cardsRemaining: number, claimedBy?: string, currency?: number): EmbedBuilder { + const dropEmbed = DropEmbedHelper.GenerateDropEmbed(drop, quantityClaimed, imageFileName, claimedBy, currency); + + dropEmbed.setFooter({ text: `${dropEmbed.data.footer?.text} · ${cardsRemaining} Remaining`}); + + return dropEmbed; + } + + public static GenerateMultidropButtons(drop: DropResult, cardsRemaining: number, userId: string, disabled = false): ActionRowBuilder { + return new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId(`multidrop keep ${drop.card.id} ${cardsRemaining} ${userId}`) + .setLabel("Keep") + .setStyle(ButtonStyle.Primary) + .setDisabled(disabled), + new ButtonBuilder() + .setCustomId(`multidrop sacrifice ${drop.card.id} ${cardsRemaining} ${userId}`) + .setLabel(`Sacrifice (+${GetSacrificeAmount(drop.card.type)} 🪙)`) + .setStyle(ButtonStyle.Secondary)); + } +} -- 2.45.2 From d794b30bb5efb4c2f38307fef98435d844081e48 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Sat, 11 Jan 2025 18:58:35 +0000 Subject: [PATCH 16/22] WIP: Plan tests --- src/buttonEvents/Claim.ts | 2 + src/buttonEvents/Effects/Use.ts | 79 +++++++------- src/helpers/EffectHelper.ts | 10 +- tests/buttonEvents/Claim.test.ts | 23 ++++ tests/buttonEvents/Effects.test.ts | 136 +----------------------- tests/buttonEvents/Effects/List.test.ts | 3 + tests/buttonEvents/Effects/Use.test.ts | 21 ++++ tests/commands/effects.test.ts | 27 +++++ tests/helpers/EffectHelper.test.ts | 41 +++++++ tests/helpers/TimeLengthInput.test.ts | 38 +++++++ 10 files changed, 204 insertions(+), 176 deletions(-) create mode 100644 tests/buttonEvents/Claim.test.ts create mode 100644 tests/buttonEvents/Effects/List.test.ts create mode 100644 tests/buttonEvents/Effects/Use.test.ts create mode 100644 tests/commands/effects.test.ts create mode 100644 tests/helpers/EffectHelper.test.ts create mode 100644 tests/helpers/TimeLengthInput.test.ts diff --git a/src/buttonEvents/Claim.ts b/src/buttonEvents/Claim.ts index 27b82b1..97ee54d 100644 --- a/src/buttonEvents/Claim.ts +++ b/src/buttonEvents/Claim.ts @@ -73,6 +73,8 @@ export default class Claim extends ButtonEvent { const card = GetCardsHelper.GetCardByCardNumber(cardNumber); if (!card) { + AppLogger.LogError("Button/Claim", `Unable to find card, ${cardNumber}`); + return; } diff --git a/src/buttonEvents/Effects/Use.ts b/src/buttonEvents/Effects/Use.ts index 25c2ba2..2b38bd8 100644 --- a/src/buttonEvents/Effects/Use.ts +++ b/src/buttonEvents/Effects/Use.ts @@ -3,6 +3,7 @@ import { EffectDetails } from "../../constants/EffectDetails"; import EffectHelper from "../../helpers/EffectHelper"; import EmbedColours from "../../constants/EmbedColours"; import TimeLengthInput from "../../helpers/TimeLengthInput"; +import AppLogger from "../../client/appLogger"; export default async function Use(interaction: ButtonInteraction) { const subaction = interaction.customId.split(" ")[2]; @@ -23,6 +24,8 @@ export async function UseConfirm(interaction: ButtonInteraction) { const effectDetail = EffectDetails.get(id); if (!effectDetail) { + AppLogger.LogError("Button/Effects/Use", `Effect not found, ${id}`); + await interaction.reply("Effect not found in system!"); return; } @@ -33,46 +36,46 @@ export async function UseConfirm(interaction: ButtonInteraction) { const result = await EffectHelper.UseEffect(interaction.user.id, id, whenExpires); - if (result) { - const embed = new EmbedBuilder() - .setTitle("Effect Used") - .setDescription("You now have an active effect!") - .setColor(EmbedColours.Green) - .addFields([ - { - name: "Effect", - value: effectDetail.friendlyName, - inline: true, - }, - { - name: "Expires", - value: ``, - inline: true, - }, - ]); - - const row = new ActionRowBuilder() - .addComponents([ - new ButtonBuilder() - .setLabel("Confirm") - .setCustomId(`effects use confirm ${effectDetail.id}`) - .setStyle(ButtonStyle.Primary) - .setDisabled(true), - new ButtonBuilder() - .setLabel("Cancel") - .setCustomId(`effects use cancel ${effectDetail.id}`) - .setStyle(ButtonStyle.Danger) - .setDisabled(true), - ]); - - await interaction.update({ - embeds: [ embed ], - components: [ row ], - }); + if (!result) { + await interaction.reply("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown"); return; } - await interaction.reply("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown"); + const embed = new EmbedBuilder() + .setTitle("Effect Used") + .setDescription("You now have an active effect!") + .setColor(EmbedColours.Green) + .addFields([ + { + name: "Effect", + value: effectDetail.friendlyName, + inline: true, + }, + { + name: "Expires", + value: ``, + inline: true, + }, + ]); + + const row = new ActionRowBuilder() + .addComponents([ + new ButtonBuilder() + .setLabel("Confirm") + .setCustomId(`effects use confirm ${effectDetail.id}`) + .setStyle(ButtonStyle.Primary) + .setDisabled(true), + new ButtonBuilder() + .setLabel("Cancel") + .setCustomId(`effects use cancel ${effectDetail.id}`) + .setStyle(ButtonStyle.Danger) + .setDisabled(true), + ]); + + await interaction.update({ + embeds: [ embed ], + components: [ row ], + }); } export async function UseCancel(interaction: ButtonInteraction) { @@ -81,6 +84,8 @@ export async function UseCancel(interaction: ButtonInteraction) { const effectDetail = EffectDetails.get(id); if (!effectDetail) { + AppLogger.LogError("Button/Effects/Cancel", `Effect not found, ${id}`); + await interaction.reply("Effect not found in system!"); return; } diff --git a/src/helpers/EffectHelper.ts b/src/helpers/EffectHelper.ts index 4c45022..d4673f4 100644 --- a/src/helpers/EffectHelper.ts +++ b/src/helpers/EffectHelper.ts @@ -1,7 +1,7 @@ -import {ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder} from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js"; import UserEffect from "../database/entities/app/UserEffect"; import EmbedColours from "../constants/EmbedColours"; -import {EffectDetails} from "../constants/EffectDetails"; +import { EffectDetails } from "../constants/EffectDetails"; export default class EffectHelper { public static async AddEffectToUserInventory(userId: string, name: string, quantity: number = 1) { @@ -23,11 +23,9 @@ export default class EffectHelper { const effect = await UserEffect.FetchOneByUserIdAndName(userId, name); - if (!effect) return false; + effect!.UseEffect(whenExpires); - effect.UseEffect(whenExpires); - - await effect.Save(UserEffect, effect); + await effect!.Save(UserEffect, effect!); return true; } diff --git a/tests/buttonEvents/Claim.test.ts b/tests/buttonEvents/Claim.test.ts new file mode 100644 index 0000000..beab3c8 --- /dev/null +++ b/tests/buttonEvents/Claim.test.ts @@ -0,0 +1,23 @@ +test.todo("GIVEN interaction.guild is null, EXPECT nothing to happen"); + +test.todo("GIVEN interaction.guildId is null, EXPECT nothing to happen"); + +test.todo("GIVEN interaction.channel is null, EXPECT nothing to happen"); + +test.todo("GIVEN channel is not sendable, EXPECT nothing to happen"); + +test.todo("GIVEN interaction.message was created more than 5 minutes ago, EXPECT error"); + +test.todo("GIVEN user.RemoveCurrency fails, EXPECT error"); + +test.todo("GIVEN the card has already been claimed, EXPECT error"); + +test.todo("GIVEN the current drop is the latest AND the current user is NOT the one who dropped it, EXPECT error"); + +test.todo("GIVEN the user already has the card in their inventory, EXPECT the entity quantity to be +1"); + +test.todo("GIVEN user does NOT have the card in their inventory, EXPECT a new entity to be created"); + +test.todo("GIVEN card can not be found in the bot, EXPECT error logged"); + +test.todo("EXPECT message to be edited"); \ No newline at end of file diff --git a/tests/buttonEvents/Effects.test.ts b/tests/buttonEvents/Effects.test.ts index 8a2f755..18ccc31 100644 --- a/tests/buttonEvents/Effects.test.ts +++ b/tests/buttonEvents/Effects.test.ts @@ -1,135 +1,5 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, EmbedBuilder } from "discord.js"; -import Effects from "../../src/buttonEvents/Effects"; -import EffectHelper from "../../src/helpers/EffectHelper"; -import { EffectDetails } from "../../src/constants/EffectDetails"; -import TimeLengthInput from "../../src/helpers/TimeLengthInput"; +test.todo("GIVEN action is list, EXPECT list function to be called"); -describe("Effects", () => { - let interaction: ButtonInteraction; - let effects: Effects; +test.todo("GIVEN action is use, EXPECT use function to be called"); - beforeEach(() => { - interaction = { - customId: "effects list 1", - user: { id: "123" }, - reply: jest.fn(), - update: jest.fn(), - } as unknown as ButtonInteraction; - effects = new Effects(); - }); - - it("should call List method when action is 'list'", async () => { - const listSpy = jest.spyOn(effects, "List").mockImplementation(async () => {}); - - await effects.execute(interaction); - - expect(listSpy).toHaveBeenCalledWith(interaction); - }); - - it("should call Use method when action is 'use'", async () => { - interaction.customId = "effects use confirm 1"; - const useSpy = jest.spyOn(effects, "Use").mockImplementation(async () => {}); - - await effects.execute(interaction); - - expect(useSpy).toHaveBeenCalledWith(interaction); - }); - - it("should reply with error message when page option is not a valid number", async () => { - interaction.customId = "effects list invalid"; - await effects.execute(interaction); - - expect(interaction.reply).toHaveBeenCalledWith("Page option is not a valid number"); - }); - - it("should update interaction with generated embed and row", async () => { - const mockEmbed = { - embed: new EmbedBuilder(), - row: new ActionRowBuilder() - }; - - jest.spyOn(EffectHelper, "GenerateEffectEmbed").mockResolvedValue(mockEmbed); - - await effects.List(interaction); - - expect(interaction.update).toHaveBeenCalledWith({ - embeds: [mockEmbed.embed], - components: [mockEmbed.row], - }); - }); - - it("should call UseConfirm method when subaction is 'confirm'", async () => { - interaction.customId = "effects use confirm 1"; - const useConfirmSpy = jest.spyOn(effects, "UseConfirm").mockImplementation(async () => {}); - - await effects.Use(interaction); - - expect(useConfirmSpy).toHaveBeenCalledWith(interaction); - }); - - it("should call UseCancel method when subaction is 'cancel'", async () => { - interaction.customId = "effects use cancel 1"; - const useCancelSpy = jest.spyOn(effects, "UseCancel").mockImplementation(async () => {}); - - await effects.Use(interaction); - - expect(useCancelSpy).toHaveBeenCalledWith(interaction); - }); - - it("should reply with error message when effect detail is not found in UseConfirm", async () => { - interaction.customId = "effects use confirm invalid"; - await effects.UseConfirm(interaction); - - expect(interaction.reply).toHaveBeenCalledWith("Effect not found in system!"); - }); - - it("should reply with error message when effect detail is not found in UseCancel", async () => { - interaction.customId = "effects use cancel invalid"; - await effects.UseCancel(interaction); - - expect(interaction.reply).toHaveBeenCalledWith("Effect not found in system!"); - }); - - it("should update interaction with embed and row when effect is used successfully", async () => { - const mockEffectDetail = { id: "1", friendlyName: "Test Effect", duration: 1000, cost: 10, cooldown: 5000 }; - const mockResult = true; - - jest.spyOn(EffectDetails, "get").mockReturnValue(mockEffectDetail); - jest.spyOn(EffectHelper, "UseEffect").mockResolvedValue(mockResult); - - await effects.UseConfirm(interaction); - - expect(interaction.update).toHaveBeenCalledWith(expect.objectContaining({ - embeds: expect.any(Array), - components: expect.any(Array), - })); - }); - - it("should reply with error message when effect is not used successfully", async () => { - const mockEffectDetail = { id: "1", friendlyName: "Test Effect", duration: 1000, cost: 0, cooldown: 0 }; - const mockResult = false; - - jest.spyOn(EffectDetails, "get").mockReturnValue(mockEffectDetail); - jest.spyOn(EffectHelper, "UseEffect").mockResolvedValue(mockResult); - - await effects.UseConfirm(interaction); - - expect(interaction.reply).toHaveBeenCalledWith("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown"); - }); - - it("should update interaction with embed and row when effect use is cancelled", async () => { - const mockEffectDetail = { id: "1", friendlyName: "Test Effect", duration: 1000, cost: 0, cooldown: 0 }; - - jest.spyOn(EffectDetails, "get").mockReturnValue(mockEffectDetail); - jest.spyOn(TimeLengthInput, "ConvertFromMilliseconds").mockReturnValue({ - GetLengthShort: () => "1s", - } as TimeLengthInput); - - await effects.UseCancel(interaction); - - expect(interaction.update).toHaveBeenCalledWith(expect.objectContaining({ - embeds: expect.any(Array), - components: expect.any(Array), - })); - }); -}); \ No newline at end of file +test.todo("GIVEN action is unknown, EXPECT nothing to be called"); \ No newline at end of file diff --git a/tests/buttonEvents/Effects/List.test.ts b/tests/buttonEvents/Effects/List.test.ts new file mode 100644 index 0000000..af894b4 --- /dev/null +++ b/tests/buttonEvents/Effects/List.test.ts @@ -0,0 +1,3 @@ +test.todo("GIVEN pageOption is NOT a number, EXPECT error"); + +test.todo("GIVEN pageOption is a number, EXPECT interaction updated"); \ No newline at end of file diff --git a/tests/buttonEvents/Effects/Use.test.ts b/tests/buttonEvents/Effects/Use.test.ts new file mode 100644 index 0000000..5cb6912 --- /dev/null +++ b/tests/buttonEvents/Effects/Use.test.ts @@ -0,0 +1,21 @@ +describe("Use", () => { + test.todo("GIVEN subaction is confirm, EXPECT UseConfirm to be called"); + + test.todo("GIVEN subaction is cancel, EXPECT UseCancel to be called"); + + test.todo("GIVEN subaction is unknown, EXPECT nothing to be called"); +}); + +describe("UseConfirm", () => { + test.todo("GIVEN effectDetail is not found, EXPECT error"); + + test.todo("GIVEN EffectHelper.UseEffect failed, EXPECT error"); + + test.todo("GIVEN EffectHelper.UseEffect succeeded, EXPECT interaction updated"); +}); + +describe("UseCancel", () => { + test.todo("GIVEN effectDetail is not found, EXPECT error"); + + test.todo("GIVEN effectDetail is found, EXPECT interaction updated"); +}); \ No newline at end of file diff --git a/tests/commands/effects.test.ts b/tests/commands/effects.test.ts new file mode 100644 index 0000000..20c8f35 --- /dev/null +++ b/tests/commands/effects.test.ts @@ -0,0 +1,27 @@ +describe("constructor", () => { + test.todo("EXPECT CommandBuilder to be defined"); +}); + +describe("execute", () => { + test.todo("GIVEN interaction is NOT a ChatInputCommand, EXPECT nothing to happen"); + + test.todo("GIVEN subcommand is list, EXPECT list function to be called"); + + test.todo("GIVEN subcommand is use, EXPECT use function to be called"); +}); + +describe("List", () => { + test.todo("GIVEN pageOption is null, EXPECT page to default to 0"); + + test.todo("GIVEN pageOption.value is undefined, EXPECT page to default to 0"); + + test.todo("EXPECT interaction to be replied"); +}); + +describe("Use", () => { + test.todo("GIVEN effectDetail is not found, EXPECT error"); + + test.todo("GIVEN user can not use effect, EXPECT error"); + + test.todo("EXPECT interaction to be replied"); +}); \ No newline at end of file diff --git a/tests/helpers/EffectHelper.test.ts b/tests/helpers/EffectHelper.test.ts new file mode 100644 index 0000000..e41c2bc --- /dev/null +++ b/tests/helpers/EffectHelper.test.ts @@ -0,0 +1,41 @@ +describe("AddEffectToUserInventory", () => { + test.todo("GIVEN effect is found in database, EXPECT effect unused to be incremented"); + + test.todo("GIVEN effect is NOT found in database, EXPECT new effect to be created"); +}); + +describe("UseEffect", () => { + test.todo("GIVEN user can not use effect, EXPECT false returned"); + + test.todo("GIVEN user has effect, EXPECT entity to be updated AND true returned"); +}); + +describe("CanUseEffect", () => { + test.todo("GIVEN effect is not in database, EXPECT false returned"); + + test.todo("GIVEN user does not have any of the effect unused, EXPECT false returned"); + + test.todo("GIVEN effectDetail can not be found, EXPECT false returned"); + + test.todo("GIVEN effect has NOT passed the cooldown, EXPECT false returned"); + + test.todo("GIVEN effect has passed the cooldown, EXPECT true returned"); + + test.todo("GIVEN effect does not have a WhenExpires date supplied, EXPECT true returned"); +}); + +describe("HasEffect", () => { + test.todo("GIVEN effect is NOT found in database, EXPECT false returned"); + + test.todo("GIVEN effect does NOT have an expiry date, EXPECT false returned"); + + test.todo("GIVEN effect.WhenExpires is in the future, EXPECT false returned"); + + test.todo("GIVEN effect.WhenExpires is in the past, EXPECT true returned"); +}); + +describe("GenerateEffectEmbed", () => { + test.todo("GIVEN user has no effects, EXPECT embed to be generated"); + + test.todo("GIVEN user has some effects, EXPECT embed to be generated"); +}); \ No newline at end of file diff --git a/tests/helpers/TimeLengthInput.test.ts b/tests/helpers/TimeLengthInput.test.ts new file mode 100644 index 0000000..6a23d67 --- /dev/null +++ b/tests/helpers/TimeLengthInput.test.ts @@ -0,0 +1,38 @@ +import TimeLengthInput from "../../src/helpers/TimeLengthInput"; + +describe("ConvertFromMilliseconds", () => { + test("EXPECT 1000ms to be outputted as a second", () => { + const timeLength = TimeLengthInput.ConvertFromMilliseconds(1000); + expect(timeLength.GetLengthShort()).toBe("1s"); + }); + + test("EXPECT 60000ms to be outputted as a minute", () => { + const timeLength = TimeLengthInput.ConvertFromMilliseconds(60000); + expect(timeLength.GetLengthShort()).toBe("1m"); + }); + + test("EXPECT 3600000ms to be outputted as an hour", () => { + const timeLength = TimeLengthInput.ConvertFromMilliseconds(3600000); + expect(timeLength.GetLengthShort()).toBe("1h"); + }); + + test("EXPECT 86400000ms to be outputted as a day", () => { + const timeLength = TimeLengthInput.ConvertFromMilliseconds(86400000); + expect(timeLength.GetLengthShort()).toBe("1d"); + }); + + test("EXPECT a combination to be outputted correctly", () => { + const timeLength = TimeLengthInput.ConvertFromMilliseconds(90061000); + expect(timeLength.GetLengthShort()).toBe("1d 1h 1m 1s"); + }); + + test("EXPECT 0ms to be outputted as empty", () => { + const timeLength = TimeLengthInput.ConvertFromMilliseconds(0); + expect(timeLength.GetLengthShort()).toBe(""); + }); + + test("EXPECT 123456789ms to be outputted correctly", () => { + const timeLength = TimeLengthInput.ConvertFromMilliseconds(123456789); + expect(timeLength.GetLengthShort()).toBe("1d 10h 17m 36s"); + }); +}); \ No newline at end of file -- 2.45.2 From 6c0b7c6899ab701f5fe7b659cd08e58f22cc5f76 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Mon, 13 Jan 2025 17:57:20 +0000 Subject: [PATCH 17/22] WIP: EFfects List Button Event tests --- tests/buttonEvents/Effects/List.test.ts | 51 ++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/tests/buttonEvents/Effects/List.test.ts b/tests/buttonEvents/Effects/List.test.ts index af894b4..52fa550 100644 --- a/tests/buttonEvents/Effects/List.test.ts +++ b/tests/buttonEvents/Effects/List.test.ts @@ -1,3 +1,50 @@ -test.todo("GIVEN pageOption is NOT a number, EXPECT error"); +import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, EmbedBuilder } from "discord.js"; +import List from "../../../src/buttonEvents/Effects/List"; +import EffectHelper from "../../../src/helpers/EffectHelper"; +import { mock } from "jest-mock-extended"; -test.todo("GIVEN pageOption is a number, EXPECT interaction updated"); \ No newline at end of file +jest.mock("../../../src/helpers/EffectHelper"); + +let interaction: ReturnType>; + +beforeEach(() => { + jest.resetAllMocks(); + + (EffectHelper.GenerateEffectEmbed as jest.Mock).mockResolvedValue({ + embed: mock(), + row: mock>(), + }); + + interaction = mock(); + interaction.user.id = "userId"; + interaction.customId = "effects list 1"; +}); + +test("GIVEN pageOption is NOT a number, EXPECT error", async () => { + // Arrange + interaction.customId = "effects list invalid"; + + // Act + await List(interaction); + + // Assert + expect(interaction.reply).toHaveBeenCalledTimes(1); + expect(interaction.reply).toHaveBeenCalledWith("Page option is not a valid number") + + expect(EffectHelper.GenerateEffectEmbed).not.toHaveBeenCalled(); + expect(interaction.update).not.toHaveBeenCalled(); +}); + +test("GIVEN pageOption is a number, EXPECT interaction updated", async () => { + // Arrange + interaction.customId = "effects list 1"; + + // Act + await List(interaction); + + // Assert + expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledTimes(1); + expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledWith("userId", 1); + + expect(interaction.update).toHaveBeenCalledTimes(1); +}); \ No newline at end of file -- 2.45.2 From 3d665aba67974b3348e18debf9c34331a10336cd Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Thu, 16 Jan 2025 20:34:32 +0000 Subject: [PATCH 18/22] WIP: Create UseEffect tests --- src/buttonEvents/Effects.ts | 2 +- src/buttonEvents/Effects/Use.ts | 242 +++++++++--------- tests/buttonEvents/Effects/Use.test.ts | 145 ++++++++++- .../Effects/__snapshots__/Use.test.ts.snap | 95 +++++++ 4 files changed, 354 insertions(+), 130 deletions(-) create mode 100644 tests/buttonEvents/Effects/__snapshots__/Use.test.ts.snap diff --git a/src/buttonEvents/Effects.ts b/src/buttonEvents/Effects.ts index 5fb1bb8..f185009 100644 --- a/src/buttonEvents/Effects.ts +++ b/src/buttonEvents/Effects.ts @@ -12,7 +12,7 @@ export default class Effects extends ButtonEvent { await List(interaction); break; case "use": - await Use(interaction); + await Use.Execute(interaction); break; } } diff --git a/src/buttonEvents/Effects/Use.ts b/src/buttonEvents/Effects/Use.ts index 2b38bd8..5bfe2fd 100644 --- a/src/buttonEvents/Effects/Use.ts +++ b/src/buttonEvents/Effects/Use.ts @@ -5,126 +5,128 @@ import EmbedColours from "../../constants/EmbedColours"; import TimeLengthInput from "../../helpers/TimeLengthInput"; import AppLogger from "../../client/appLogger"; -export default async function Use(interaction: ButtonInteraction) { - const subaction = interaction.customId.split(" ")[2]; +export default class Use { + public static async Execute(interaction: ButtonInteraction) { + const subaction = interaction.customId.split(" ")[2]; - switch (subaction) { - case "confirm": - await UseConfirm(interaction); - break; - case "cancel": - await UseCancel(interaction); - break; + switch (subaction) { + case "confirm": + await this.UseConfirm(interaction); + break; + case "cancel": + await this.UseCancel(interaction); + break; + } + } + + private static async UseConfirm(interaction: ButtonInteraction) { + const id = interaction.customId.split(" ")[3]; + + const effectDetail = EffectDetails.get(id); + + if (!effectDetail) { + AppLogger.LogError("Button/Effects/Use", `Effect not found, ${id}`); + + await interaction.reply("Effect not found in system!"); + return; + } + + const now = new Date(); + + const whenExpires = new Date(now.getTime() + effectDetail.duration); + + const result = await EffectHelper.UseEffect(interaction.user.id, id, whenExpires); + + if (!result) { + await interaction.reply("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown"); + return; + } + + const embed = new EmbedBuilder() + .setTitle("Effect Used") + .setDescription("You now have an active effect!") + .setColor(EmbedColours.Green) + .addFields([ + { + name: "Effect", + value: effectDetail.friendlyName, + inline: true, + }, + { + name: "Expires", + value: ``, + inline: true, + }, + ]); + + const row = new ActionRowBuilder() + .addComponents([ + new ButtonBuilder() + .setLabel("Confirm") + .setCustomId(`effects use confirm ${effectDetail.id}`) + .setStyle(ButtonStyle.Primary) + .setDisabled(true), + new ButtonBuilder() + .setLabel("Cancel") + .setCustomId(`effects use cancel ${effectDetail.id}`) + .setStyle(ButtonStyle.Danger) + .setDisabled(true), + ]); + + await interaction.update({ + embeds: [ embed ], + components: [ row ], + }); + } + + private static async UseCancel(interaction: ButtonInteraction) { + const id = interaction.customId.split(" ")[3]; + + const effectDetail = EffectDetails.get(id); + + if (!effectDetail) { + AppLogger.LogError("Button/Effects/Cancel", `Effect not found, ${id}`); + + await interaction.reply("Effect not found in system!"); + return; + } + + const timeLengthInput = TimeLengthInput.ConvertFromMilliseconds(effectDetail.duration); + + const embed = new EmbedBuilder() + .setTitle("Effect Use Cancelled") + .setDescription("The effect from your inventory has not been used") + .setColor(EmbedColours.Grey) + .addFields([ + { + name: "Effect", + value: effectDetail.friendlyName, + inline: true, + }, + { + name: "Expires", + value: timeLengthInput.GetLengthShort(), + inline: true, + }, + ]); + + const row = new ActionRowBuilder() + .addComponents([ + new ButtonBuilder() + .setLabel("Confirm") + .setCustomId(`effects use confirm ${effectDetail.id}`) + .setStyle(ButtonStyle.Primary) + .setDisabled(true), + new ButtonBuilder() + .setLabel("Cancel") + .setCustomId(`effects use cancel ${effectDetail.id}`) + .setStyle(ButtonStyle.Danger) + .setDisabled(true), + ]); + + await interaction.update({ + embeds: [ embed ], + components: [ row ], + }); } } - -export async function UseConfirm(interaction: ButtonInteraction) { - const id = interaction.customId.split(" ")[3]; - - const effectDetail = EffectDetails.get(id); - - if (!effectDetail) { - AppLogger.LogError("Button/Effects/Use", `Effect not found, ${id}`); - - await interaction.reply("Effect not found in system!"); - return; - } - - const now = new Date(); - - const whenExpires = new Date(now.getTime() + effectDetail.duration); - - const result = await EffectHelper.UseEffect(interaction.user.id, id, whenExpires); - - if (!result) { - await interaction.reply("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown"); - return; - } - - const embed = new EmbedBuilder() - .setTitle("Effect Used") - .setDescription("You now have an active effect!") - .setColor(EmbedColours.Green) - .addFields([ - { - name: "Effect", - value: effectDetail.friendlyName, - inline: true, - }, - { - name: "Expires", - value: ``, - inline: true, - }, - ]); - - const row = new ActionRowBuilder() - .addComponents([ - new ButtonBuilder() - .setLabel("Confirm") - .setCustomId(`effects use confirm ${effectDetail.id}`) - .setStyle(ButtonStyle.Primary) - .setDisabled(true), - new ButtonBuilder() - .setLabel("Cancel") - .setCustomId(`effects use cancel ${effectDetail.id}`) - .setStyle(ButtonStyle.Danger) - .setDisabled(true), - ]); - - await interaction.update({ - embeds: [ embed ], - components: [ row ], - }); -} - -export async function UseCancel(interaction: ButtonInteraction) { - const id = interaction.customId.split(" ")[3]; - - const effectDetail = EffectDetails.get(id); - - if (!effectDetail) { - AppLogger.LogError("Button/Effects/Cancel", `Effect not found, ${id}`); - - await interaction.reply("Effect not found in system!"); - return; - } - - const timeLengthInput = TimeLengthInput.ConvertFromMilliseconds(effectDetail.duration); - - const embed = new EmbedBuilder() - .setTitle("Effect Use Cancelled") - .setDescription("The effect from your inventory has not been used") - .setColor(EmbedColours.Grey) - .addFields([ - { - name: "Effect", - value: effectDetail.friendlyName, - inline: true, - }, - { - name: "Expires", - value: timeLengthInput.GetLengthShort(), - inline: true, - }, - ]); - - const row = new ActionRowBuilder() - .addComponents([ - new ButtonBuilder() - .setLabel("Confirm") - .setCustomId(`effects use confirm ${effectDetail.id}`) - .setStyle(ButtonStyle.Primary) - .setDisabled(true), - new ButtonBuilder() - .setLabel("Cancel") - .setCustomId(`effects use cancel ${effectDetail.id}`) - .setStyle(ButtonStyle.Danger) - .setDisabled(true), - ]); - - await interaction.update({ - embeds: [ embed ], - components: [ row ], - }); -} \ No newline at end of file diff --git a/tests/buttonEvents/Effects/Use.test.ts b/tests/buttonEvents/Effects/Use.test.ts index 5cb6912..d5618cc 100644 --- a/tests/buttonEvents/Effects/Use.test.ts +++ b/tests/buttonEvents/Effects/Use.test.ts @@ -1,21 +1,148 @@ -describe("Use", () => { - test.todo("GIVEN subaction is confirm, EXPECT UseConfirm to be called"); +import { ButtonInteraction, EmbedBuilder, InteractionResponse } from "discord.js"; +import Use from "../../../src/buttonEvents/Effects/Use"; +import { mock } from "jest-mock-extended"; +import AppLogger from "../../../src/client/appLogger"; +import EffectHelper from "../../../src/helpers/EffectHelper"; - test.todo("GIVEN subaction is cancel, EXPECT UseCancel to be called"); +jest.mock("../../../src/client/appLogger"); +jest.mock("../../../src/helpers/EffectHelper"); - test.todo("GIVEN subaction is unknown, EXPECT nothing to be called"); +beforeEach(() => { + jest.resetAllMocks(); + + jest.useFakeTimers(); + jest.setSystemTime(0); +}); + +afterAll(() => { + jest.useRealTimers(); +}); + +describe("Execute", () => { + test("GIVEN subaction is unknown, EXPECT nothing to be called", async () => { + // Arrange + const interaction = mock(); + interaction.customId = "effects use invalud"; + + // Act + await Use.Execute(interaction); + + // Assert + expect(interaction.reply).not.toHaveBeenCalled(); + expect(interaction.update).not.toHaveBeenCalled(); + }); }); describe("UseConfirm", () => { - test.todo("GIVEN effectDetail is not found, EXPECT error"); + let interaction = mock(); - test.todo("GIVEN EffectHelper.UseEffect failed, EXPECT error"); + beforeEach(() => { + interaction = mock(); + interaction.customId = "effects use confirm"; + }); - test.todo("GIVEN EffectHelper.UseEffect succeeded, EXPECT interaction updated"); + test("GIVEN effectDetail is not found, EXPECT error", async () => { + // Arrange + interaction.customId += " invalid"; + + // Act + await Use.Execute(interaction); + + // Assert + expect(AppLogger.LogError).toHaveBeenCalledTimes(1); + expect(AppLogger.LogError).toHaveBeenCalledWith("Button/Effects/Use", "Effect not found, invalid"); + + expect(interaction.reply).toHaveBeenCalledTimes(1); + expect(interaction.reply).toHaveBeenCalledWith("Effect not found in system!"); + }); + + test("GIVEN EffectHelper.UseEffect failed, EXPECT error", async () => { + // Arrange + interaction.customId += " unclaimed"; + interaction.user.id = "userId"; + + (EffectHelper.UseEffect as jest.Mock).mockResolvedValue(false); + + const whenExpires = new Date(Date.now() + 10 * 60 * 1000); + + // Act + await Use.Execute(interaction); + + // Assert + expect(EffectHelper.UseEffect).toHaveBeenCalledTimes(1); + expect(EffectHelper.UseEffect).toHaveBeenCalledWith("userId", "unclaimed", whenExpires); + + expect(interaction.reply).toHaveBeenCalledTimes(1); + expect(interaction.reply).toHaveBeenCalledWith("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown"); + }); + + test("GIVEN EffectHelper.UseEffect succeeded, EXPECT interaction updated", async () => { + let updatedWith; + + // Arrange + interaction.customId += " unclaimed"; + interaction.user.id = "userId"; + interaction.update.mockImplementation(async (opts: any) => { + updatedWith = opts; + + return mock>(); + }); + + (EffectHelper.UseEffect as jest.Mock).mockResolvedValue(true); + + const whenExpires = new Date(Date.now() + 10 * 60 * 1000); + + // Act + await Use.Execute(interaction); + + // Assert + expect(EffectHelper.UseEffect).toHaveBeenCalledTimes(1); + expect(EffectHelper.UseEffect).toHaveBeenCalledWith("userId", "unclaimed", whenExpires); + + expect(interaction.update).toHaveBeenCalledTimes(1); + expect(updatedWith).toMatchSnapshot(); + }); }); describe("UseCancel", () => { - test.todo("GIVEN effectDetail is not found, EXPECT error"); + let interaction = mock(); - test.todo("GIVEN effectDetail is found, EXPECT interaction updated"); + beforeEach(() => { + interaction = mock(); + interaction.customId = "effects use cancel"; + }); + + test("GIVEN effectDetail is not found, EXPECT error", async () => { + // Arrange + interaction.customId += " invalid"; + + // Act + await Use.Execute(interaction); + + // Assert + expect(AppLogger.LogError).toHaveBeenCalledTimes(1); + expect(AppLogger.LogError).toHaveBeenCalledWith("Button/Effects/Cancel", "Effect not found, invalid"); + + expect(interaction.reply).toHaveBeenCalledTimes(1); + expect(interaction.reply).toHaveBeenCalledWith("Effect not found in system!"); + }); + + test("GIVEN effectDetail is found, EXPECT interaction updated", async () => { + let updatedWith; + + // Arrange + interaction.customId += " unclaimed"; + interaction.user.id = "userId"; + interaction.update.mockImplementation(async (opts: any) => { + updatedWith = opts; + + return mock>(); + }); + // Act + await Use.Execute(interaction); + + // Assert + expect(interaction.update).toHaveBeenCalledTimes(1); + expect(updatedWith).toMatchSnapshot(); + }); }); \ No newline at end of file diff --git a/tests/buttonEvents/Effects/__snapshots__/Use.test.ts.snap b/tests/buttonEvents/Effects/__snapshots__/Use.test.ts.snap new file mode 100644 index 0000000..6cec0f4 --- /dev/null +++ b/tests/buttonEvents/Effects/__snapshots__/Use.test.ts.snap @@ -0,0 +1,95 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UseCancel GIVEN effectDetail is found, EXPECT interaction updated 1`] = ` +{ + "components": [ + { + "components": [ + { + "custom_id": "effects use confirm unclaimed", + "disabled": true, + "emoji": undefined, + "label": "Confirm", + "style": 1, + "type": 2, + }, + { + "custom_id": "effects use cancel unclaimed", + "disabled": true, + "emoji": undefined, + "label": "Cancel", + "style": 4, + "type": 2, + }, + ], + "type": 1, + }, + ], + "embeds": [ + { + "color": 13882323, + "description": "The effect from your inventory has not been used", + "fields": [ + { + "inline": true, + "name": "Effect", + "value": "Unclaimed Chance Up", + }, + { + "inline": true, + "name": "Expires", + "value": "10m", + }, + ], + "title": "Effect Use Cancelled", + }, + ], +} +`; + +exports[`UseConfirm GIVEN EffectHelper.UseEffect succeeded, EXPECT interaction updated 1`] = ` +{ + "components": [ + { + "components": [ + { + "custom_id": "effects use confirm unclaimed", + "disabled": true, + "emoji": undefined, + "label": "Confirm", + "style": 1, + "type": 2, + }, + { + "custom_id": "effects use cancel unclaimed", + "disabled": true, + "emoji": undefined, + "label": "Cancel", + "style": 4, + "type": 2, + }, + ], + "type": 1, + }, + ], + "embeds": [ + { + "color": 2263842, + "description": "You now have an active effect!", + "fields": [ + { + "inline": true, + "name": "Effect", + "value": "Unclaimed Chance Up", + }, + { + "inline": true, + "name": "Expires", + "value": "", + }, + ], + "title": "Effect Used", + }, + ], +} +`; -- 2.45.2 From 6e6ad3331ab44f9ae11c644a25e2097710a790da Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Sat, 18 Jan 2025 16:49:49 +0000 Subject: [PATCH 19/22] WIP: Start of Claim tests --- tests/__types__/discord.js.ts | 17 +++++ tests/buttonEvents/Claim.test.ts | 124 +++++++++++++++++++++++++++++-- 2 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 tests/__types__/discord.js.ts diff --git a/tests/__types__/discord.js.ts b/tests/__types__/discord.js.ts new file mode 100644 index 0000000..22c3291 --- /dev/null +++ b/tests/__types__/discord.js.ts @@ -0,0 +1,17 @@ +export type ButtonInteraction = { + guild: {} | null, + guildId: string | null, + channel: { + isSendable: jest.Func, + send: jest.Func, + } | null, + deferUpdate: jest.Func, + editReply: jest.Func, + message: { + createdAt: Date, + } | null, + user: { + id: string, + } | null, + customId: string, +} \ No newline at end of file diff --git a/tests/buttonEvents/Claim.test.ts b/tests/buttonEvents/Claim.test.ts index beab3c8..226cee0 100644 --- a/tests/buttonEvents/Claim.test.ts +++ b/tests/buttonEvents/Claim.test.ts @@ -1,14 +1,126 @@ -test.todo("GIVEN interaction.guild is null, EXPECT nothing to happen"); +import { ButtonInteraction, TextChannel } from "discord.js"; +import Claim from "../../src/buttonEvents/Claim"; +import { ButtonInteraction as ButtonInteractionType } from "../__types__/discord.js"; +import User from "../../src/database/entities/app/User"; -test.todo("GIVEN interaction.guildId is null, EXPECT nothing to happen"); +jest.mock("../../src/client/appLogger"); -test.todo("GIVEN interaction.channel is null, EXPECT nothing to happen"); +let interaction: ButtonInteractionType; -test.todo("GIVEN channel is not sendable, EXPECT nothing to happen"); +beforeEach(() => { + jest.useFakeTimers(); + jest.setSystemTime(1000 * 60 * 30); -test.todo("GIVEN interaction.message was created more than 5 minutes ago, EXPECT error"); + interaction = { + guild: {}, + guildId: "guildId", + channel: { + isSendable: jest.fn().mockReturnValue(true), + send: jest.fn(), + }, + deferUpdate: jest.fn(), + editReply: jest.fn(), + message: { + createdAt: new Date(1000 * 60 * 27), + }, + user: { + id: "userId", + }, + customId: "claim cardNumber claimId droppedBy userId", + }; +}); -test.todo("GIVEN user.RemoveCurrency fails, EXPECT error"); +afterAll(() => { + jest.useRealTimers(); +}); + +test("GIVEN interaction.guild is null, EXPECT nothing to happen", async () => { + // Arrange + interaction.guild = null; + + // Act + const claim = new Claim(); + await claim.execute(interaction as unknown as ButtonInteraction); + + // Assert + expect(interaction.deferUpdate).not.toHaveBeenCalled(); + expect(interaction.editReply).not.toHaveBeenCalled(); + expect((interaction.channel as TextChannel).send).not.toHaveBeenCalled(); +}); + +test("GIVEN interaction.guildId is null, EXPECT nothing to happen", async () => { + // Arrange + interaction.guildId = null; + + // Act + const claim = new Claim(); + await claim.execute(interaction as unknown as ButtonInteraction); + + // Assert + expect(interaction.deferUpdate).not.toHaveBeenCalled(); + expect(interaction.editReply).not.toHaveBeenCalled(); + expect((interaction.channel as TextChannel).send).not.toHaveBeenCalled(); +}); + +test("GIVEN interaction.channel is null, EXPECT nothing to happen", async () => { + // Arrange + interaction.channel = null; + + // Act + const claim = new Claim(); + await claim.execute(interaction as unknown as ButtonInteraction); + + // Assert + expect(interaction.deferUpdate).not.toHaveBeenCalled(); + expect(interaction.editReply).not.toHaveBeenCalled(); +}); + +test("GIVEN channel is not sendable, EXPECT nothing to happen", async () => { + // Arrange + interaction.channel!.isSendable = jest.fn().mockReturnValue(false); + + // Act + const claim = new Claim(); + await claim.execute(interaction as unknown as ButtonInteraction); + + // Assert + expect(interaction.deferUpdate).not.toHaveBeenCalled(); + expect(interaction.editReply).not.toHaveBeenCalled(); + expect((interaction.channel as TextChannel).send).not.toHaveBeenCalled(); +}); + +test("GIVEN interaction.message was created more than 5 minutes ago, EXPECT error", async () => { + // Arrange + interaction.message!.createdAt = new Date(0); + + // Act + const claim = new Claim(); + await claim.execute(interaction as unknown as ButtonInteraction); + + // Assert + expect(interaction.channel!.send).toHaveBeenCalledTimes(1); + expect(interaction.channel!.send).toHaveBeenCalledWith("[object Object], Cards can only be claimed within 5 minutes of it being dropped!"); + + expect(interaction.editReply).not.toHaveBeenCalled(); +}); + +test("GIVEN user.RemoveCurrency fails, EXPECT error", async () => { + // Arrange + User.FetchOneById = jest.fn().mockResolvedValue({ + RemoveCurrency: jest.fn().mockReturnValue(false), + Currency: 5, + }); + + // Act + const claim = new Claim(); + await claim.execute(interaction as unknown as ButtonInteraction); + + // Assert + expect(interaction.channel!.send).toHaveBeenCalledTimes(1); + expect(interaction.channel!.send).toHaveBeenCalledWith("[object Object], Not enough currency! You need 10 currency, you have 5!"); + + expect(interaction.editReply).not.toHaveBeenCalled(); +}); test.todo("GIVEN the card has already been claimed, EXPECT error"); -- 2.45.2 From 6f42cfb8f6d511a1da295b8f19bc35734ca2e0fe Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Wed, 22 Jan 2025 17:50:15 +0000 Subject: [PATCH 20/22] Implement merge change --- src/helpers/DropHelpers/DropEmbedHelper.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/helpers/DropHelpers/DropEmbedHelper.ts b/src/helpers/DropHelpers/DropEmbedHelper.ts index ba1ba47..c739de7 100644 --- a/src/helpers/DropHelpers/DropEmbedHelper.ts +++ b/src/helpers/DropHelpers/DropEmbedHelper.ts @@ -24,12 +24,18 @@ export default class DropEmbedHelper { AppLogger.LogWarn("CardDropHelperMetadata/GenerateDropEmbed", `Card's colour override is invalid: ${drop.card.id}, ${drop.card.colour}`); } + let imageUrl = `attachment://${imageFileName}`; + + if (drop.card.path.startsWith("http://") || drop.card.path.startsWith("https://")) { + imageUrl = drop.card.path; + } + const embed = new EmbedBuilder() .setTitle(drop.card.name) .setDescription(description) .setFooter({ text: `${CardRarityToString(drop.card.type)} · ${drop.card.id}` }) .setColor(colour) - .setImage(`attachment://${imageFileName}`) + .setImage(imageUrl) .addFields([ { name: "Claimed", -- 2.45.2 From 34964af78ec628287ba63bd1d661830c0eedd557 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Wed, 22 Jan 2025 18:05:49 +0000 Subject: [PATCH 21/22] Update tests --- src/buttonEvents/Effects.ts | 3 + .../GenerateButtonInteractionMock.ts | 21 ++++++ tests/buttonEvents/Claim.test.ts | 34 ++-------- tests/buttonEvents/Effects.test.ts | 67 ++++++++++++++++++- tests/commands/effects.test.ts | 27 -------- tests/helpers/EffectHelper.test.ts | 41 ------------ 6 files changed, 92 insertions(+), 101 deletions(-) create mode 100644 tests/__functions__/discord.js/GenerateButtonInteractionMock.ts delete mode 100644 tests/commands/effects.test.ts delete mode 100644 tests/helpers/EffectHelper.test.ts diff --git a/src/buttonEvents/Effects.ts b/src/buttonEvents/Effects.ts index f185009..0f9686b 100644 --- a/src/buttonEvents/Effects.ts +++ b/src/buttonEvents/Effects.ts @@ -2,6 +2,7 @@ import { ButtonInteraction } from "discord.js"; import { ButtonEvent } from "../type/buttonEvent"; import List from "./Effects/List"; import Use from "./Effects/Use"; +import AppLogger from "../client/appLogger"; export default class Effects extends ButtonEvent { public override async execute(interaction: ButtonInteraction) { @@ -14,6 +15,8 @@ export default class Effects extends ButtonEvent { case "use": await Use.Execute(interaction); break; + default: + AppLogger.LogError("Buttons/Effects", `Unknown action, ${action}`); } } } diff --git a/tests/__functions__/discord.js/GenerateButtonInteractionMock.ts b/tests/__functions__/discord.js/GenerateButtonInteractionMock.ts new file mode 100644 index 0000000..a1024ee --- /dev/null +++ b/tests/__functions__/discord.js/GenerateButtonInteractionMock.ts @@ -0,0 +1,21 @@ +import { ButtonInteraction } from "../../__types__/discord.js"; + +export default function GenerateButtonInteractionMock(): ButtonInteraction { + return { + guild: {}, + guildId: "guildId", + channel: { + isSendable: jest.fn().mockReturnValue(true), + send: jest.fn(), + }, + deferUpdate: jest.fn(), + editReply: jest.fn(), + message: { + createdAt: new Date(1000 * 60 * 27), + }, + user: { + id: "userId", + }, + customId: "customId", + }; +} \ No newline at end of file diff --git a/tests/buttonEvents/Claim.test.ts b/tests/buttonEvents/Claim.test.ts index 226cee0..d9e7e6f 100644 --- a/tests/buttonEvents/Claim.test.ts +++ b/tests/buttonEvents/Claim.test.ts @@ -2,6 +2,7 @@ import { ButtonInteraction, TextChannel } from "discord.js"; import Claim from "../../src/buttonEvents/Claim"; import { ButtonInteraction as ButtonInteractionType } from "../__types__/discord.js"; import User from "../../src/database/entities/app/User"; +import GenerateButtonInteractionMock from "../__functions__/discord.js/GenerateButtonInteractionMock"; jest.mock("../../src/client/appLogger"); @@ -11,23 +12,8 @@ beforeEach(() => { jest.useFakeTimers(); jest.setSystemTime(1000 * 60 * 30); - interaction = { - guild: {}, - guildId: "guildId", - channel: { - isSendable: jest.fn().mockReturnValue(true), - send: jest.fn(), - }, - deferUpdate: jest.fn(), - editReply: jest.fn(), - message: { - createdAt: new Date(1000 * 60 * 27), - }, - user: { - id: "userId", - }, - customId: "claim cardNumber claimId droppedBy userId", - }; + interaction = GenerateButtonInteractionMock(); + interaction.customId = "claim cardNumber claimId droppedBy userId"; }); afterAll(() => { @@ -120,16 +106,4 @@ test("GIVEN user.RemoveCurrency fails, EXPECT error", async () => { expect(interaction.channel!.send).toHaveBeenCalledWith("[object Object], Not enough currency! You need 10 currency, you have 5!"); expect(interaction.editReply).not.toHaveBeenCalled(); -}); - -test.todo("GIVEN the card has already been claimed, EXPECT error"); - -test.todo("GIVEN the current drop is the latest AND the current user is NOT the one who dropped it, EXPECT error"); - -test.todo("GIVEN the user already has the card in their inventory, EXPECT the entity quantity to be +1"); - -test.todo("GIVEN user does NOT have the card in their inventory, EXPECT a new entity to be created"); - -test.todo("GIVEN card can not be found in the bot, EXPECT error logged"); - -test.todo("EXPECT message to be edited"); \ No newline at end of file +}); \ No newline at end of file diff --git a/tests/buttonEvents/Effects.test.ts b/tests/buttonEvents/Effects.test.ts index 18ccc31..f1f86be 100644 --- a/tests/buttonEvents/Effects.test.ts +++ b/tests/buttonEvents/Effects.test.ts @@ -1,5 +1,66 @@ -test.todo("GIVEN action is list, EXPECT list function to be called"); +import { ButtonInteraction } from "discord.js"; +import Effects from "../../src/buttonEvents/Effects"; +import GenerateButtonInteractionMock from "../__functions__/discord.js/GenerateButtonInteractionMock"; +import { ButtonInteraction as ButtonInteractionType } from "../__types__/discord.js"; +import List from "../../src/buttonEvents/Effects/List"; +import Use from "../../src/buttonEvents/Effects/Use"; +import AppLogger from "../../src/client/appLogger"; -test.todo("GIVEN action is use, EXPECT use function to be called"); +jest.mock("../../src/client/appLogger"); +jest.mock("../../src/buttonEvents/Effects/List"); +jest.mock("../../src/buttonEvents/Effects/Use"); -test.todo("GIVEN action is unknown, EXPECT nothing to be called"); \ No newline at end of file +let interaction: ButtonInteractionType; + +beforeEach(() => { + jest.resetAllMocks(); + + interaction = GenerateButtonInteractionMock(); + interaction.customId = "effects"; +}); + +test("GIVEN action is list, EXPECT list function to be called", async () => { + // Arrange + interaction.customId = "effects list"; + + // Act + const effects = new Effects(); + await effects.execute(interaction as unknown as ButtonInteraction); + + // Assert + expect(List).toHaveBeenCalledTimes(1); + expect(List).toHaveBeenCalledWith(interaction); + + expect(Use.Execute).not.toHaveBeenCalled(); +}); + +test("GIVEN action is use, EXPECT use function to be called", async () => { + // Arrange + interaction.customId = "effects use"; + + // Act + const effects = new Effects(); + await effects.execute(interaction as unknown as ButtonInteraction); + + // Assert + expect(Use.Execute).toHaveBeenCalledTimes(1); + expect(Use.Execute).toHaveBeenCalledWith(interaction); + + expect(List).not.toHaveBeenCalled(); +}); + +test("GIVEN action is invalid, EXPECT nothing to be called", async () => { + // Arrange + interaction.customId = "effects invalid"; + + // Act + const effects = new Effects(); + await effects.execute(interaction as unknown as ButtonInteraction); + + // Assert + expect(List).not.toHaveBeenCalled(); + expect(Use.Execute).not.toHaveBeenCalled(); + + expect(AppLogger.LogError).toHaveBeenCalledTimes(1); + expect(AppLogger.LogError).toHaveBeenCalledWith("Buttons/Effects", "Unknown action, invalid"); +}); \ No newline at end of file diff --git a/tests/commands/effects.test.ts b/tests/commands/effects.test.ts deleted file mode 100644 index 20c8f35..0000000 --- a/tests/commands/effects.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -describe("constructor", () => { - test.todo("EXPECT CommandBuilder to be defined"); -}); - -describe("execute", () => { - test.todo("GIVEN interaction is NOT a ChatInputCommand, EXPECT nothing to happen"); - - test.todo("GIVEN subcommand is list, EXPECT list function to be called"); - - test.todo("GIVEN subcommand is use, EXPECT use function to be called"); -}); - -describe("List", () => { - test.todo("GIVEN pageOption is null, EXPECT page to default to 0"); - - test.todo("GIVEN pageOption.value is undefined, EXPECT page to default to 0"); - - test.todo("EXPECT interaction to be replied"); -}); - -describe("Use", () => { - test.todo("GIVEN effectDetail is not found, EXPECT error"); - - test.todo("GIVEN user can not use effect, EXPECT error"); - - test.todo("EXPECT interaction to be replied"); -}); \ No newline at end of file diff --git a/tests/helpers/EffectHelper.test.ts b/tests/helpers/EffectHelper.test.ts deleted file mode 100644 index e41c2bc..0000000 --- a/tests/helpers/EffectHelper.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -describe("AddEffectToUserInventory", () => { - test.todo("GIVEN effect is found in database, EXPECT effect unused to be incremented"); - - test.todo("GIVEN effect is NOT found in database, EXPECT new effect to be created"); -}); - -describe("UseEffect", () => { - test.todo("GIVEN user can not use effect, EXPECT false returned"); - - test.todo("GIVEN user has effect, EXPECT entity to be updated AND true returned"); -}); - -describe("CanUseEffect", () => { - test.todo("GIVEN effect is not in database, EXPECT false returned"); - - test.todo("GIVEN user does not have any of the effect unused, EXPECT false returned"); - - test.todo("GIVEN effectDetail can not be found, EXPECT false returned"); - - test.todo("GIVEN effect has NOT passed the cooldown, EXPECT false returned"); - - test.todo("GIVEN effect has passed the cooldown, EXPECT true returned"); - - test.todo("GIVEN effect does not have a WhenExpires date supplied, EXPECT true returned"); -}); - -describe("HasEffect", () => { - test.todo("GIVEN effect is NOT found in database, EXPECT false returned"); - - test.todo("GIVEN effect does NOT have an expiry date, EXPECT false returned"); - - test.todo("GIVEN effect.WhenExpires is in the future, EXPECT false returned"); - - test.todo("GIVEN effect.WhenExpires is in the past, EXPECT true returned"); -}); - -describe("GenerateEffectEmbed", () => { - test.todo("GIVEN user has no effects, EXPECT embed to be generated"); - - test.todo("GIVEN user has some effects, EXPECT embed to be generated"); -}); \ No newline at end of file -- 2.45.2 From 1106585f581342eb87f34a7dcde90133838e3c0c Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Wed, 22 Jan 2025 18:08:05 +0000 Subject: [PATCH 22/22] Fix linter issues --- tests/__types__/discord.js.ts | 2 +- tests/buttonEvents/Effects/Use.test.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/__types__/discord.js.ts b/tests/__types__/discord.js.ts index 22c3291..6506b1d 100644 --- a/tests/__types__/discord.js.ts +++ b/tests/__types__/discord.js.ts @@ -1,5 +1,5 @@ export type ButtonInteraction = { - guild: {} | null, + guild: object | null, guildId: string | null, channel: { isSendable: jest.Func, diff --git a/tests/buttonEvents/Effects/Use.test.ts b/tests/buttonEvents/Effects/Use.test.ts index d5618cc..86391cc 100644 --- a/tests/buttonEvents/Effects/Use.test.ts +++ b/tests/buttonEvents/Effects/Use.test.ts @@ -1,4 +1,4 @@ -import { ButtonInteraction, EmbedBuilder, InteractionResponse } from "discord.js"; +import { ButtonInteraction, InteractionResponse, InteractionUpdateOptions, MessagePayload } from "discord.js"; import Use from "../../../src/buttonEvents/Effects/Use"; import { mock } from "jest-mock-extended"; import AppLogger from "../../../src/client/appLogger"; @@ -82,7 +82,7 @@ describe("UseConfirm", () => { // Arrange interaction.customId += " unclaimed"; interaction.user.id = "userId"; - interaction.update.mockImplementation(async (opts: any) => { + interaction.update.mockImplementation(async (opts: string | MessagePayload | InteractionUpdateOptions) => { updatedWith = opts; return mock>(); @@ -133,7 +133,7 @@ describe("UseCancel", () => { // Arrange interaction.customId += " unclaimed"; interaction.user.id = "userId"; - interaction.update.mockImplementation(async (opts: any) => { + interaction.update.mockImplementation(async (opts: string | MessagePayload | InteractionUpdateOptions) => { updatedWith = opts; return mock>(); -- 2.45.2