Pull 2nd lot of develop changes into 0.9.0 #439
66 changed files with 3034 additions and 772 deletions
|
@ -7,7 +7,7 @@
|
|||
# any secret values.
|
||||
|
||||
BOT_TOKEN=
|
||||
BOT_VER=0.8.2
|
||||
BOT_VER=0.8.4
|
||||
BOT_AUTHOR=Vylpes
|
||||
BOT_OWNERID=147392775707426816
|
||||
BOT_CLIENTID=682942374040961060
|
||||
|
@ -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
|
||||
|
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
- run: yarn lint
|
||||
|
||||
- name: "Copy files over to location"
|
||||
run: cp -r . ${{ secrets.PROD_REPO_PATH }}
|
||||
run: rsync -rvzP . ${{ secrets.PROD_REPO_PATH }}
|
||||
|
||||
deploy:
|
||||
environment: prod
|
||||
|
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
- run: yarn lint
|
||||
|
||||
- name: "Copy files over to location"
|
||||
run: cp -r . ${{ secrets.STAGE_REPO_PATH }}
|
||||
run: rsync -rvzP . ${{ secrets.STAGE_REPO_PATH }}
|
||||
|
||||
deploy:
|
||||
environment: prod
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "card-drop",
|
||||
"version": "0.8.2",
|
||||
"version": "0.8.4",
|
||||
"main": "./dist/bot.js",
|
||||
"typings": "./dist",
|
||||
"scripts": {
|
||||
|
@ -30,6 +30,7 @@
|
|||
"@types/express": "^5.0.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"axios": "^1.8.4",
|
||||
"body-parser": "^1.20.2",
|
||||
"canvas": "^2.11.2",
|
||||
"clone-deep": "^4.0.1",
|
||||
|
@ -42,6 +43,7 @@
|
|||
"jest": "^29.0.0",
|
||||
"jest-mock-extended": "^3.0.0",
|
||||
"jimp": "^1.6.0",
|
||||
"minimatch": "9.0.5",
|
||||
"mysql": "^2.18.1",
|
||||
"ts-jest": "^29.0.0",
|
||||
"typeorm": "0.3.20",
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { ButtonInteraction } from "discord.js";
|
||||
import { ButtonEvent } from "../type/buttonEvent";
|
||||
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) {
|
||||
|
@ -22,10 +22,10 @@ export default class Claim extends ButtonEvent {
|
|||
const userId = interaction.user.id;
|
||||
|
||||
const whenDropped = interaction.message.createdAt;
|
||||
const lastClaimableDate = new Date(Date.now() - (1000 * 60 * 5)); // 5 minutes ago
|
||||
const lastClaimableDate = new Date(Date.now() - (1000 * 60 * 2)); // 2 minutes ago
|
||||
|
||||
if (whenDropped < lastClaimableDate) {
|
||||
await interaction.channel.send(`${interaction.user}, Cards can only be claimed within 5 minutes of it being dropped!`);
|
||||
await interaction.channel.send(`${interaction.user}, Cards can only be claimed within 2 minutes of it being dropped!`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -35,11 +35,6 @@ export default class Claim extends ButtonEvent {
|
|||
|
||||
AppLogger.LogSilly("Button/Claim", `${user.Id} has ${user.Currency} currency`);
|
||||
|
||||
if (!user.RemoveCurrency(CardConstants.ClaimCost)) {
|
||||
await interaction.channel.send(`${interaction.user}, Not enough currency! You need ${CardConstants.ClaimCost} currency, you have ${user.Currency}!`);
|
||||
return;
|
||||
}
|
||||
|
||||
const claimed = await eClaim.FetchOneByClaimId(claimId);
|
||||
|
||||
if (claimed) {
|
||||
|
@ -47,13 +42,6 @@ export default class Claim extends ButtonEvent {
|
|||
return;
|
||||
}
|
||||
|
||||
if (claimId == CoreClient.ClaimId && userId != droppedBy) {
|
||||
await interaction.channel.send(`${interaction.user}, The latest dropped card can only be claimed by the user who dropped it!`);
|
||||
return;
|
||||
}
|
||||
|
||||
await user.Save(User, user);
|
||||
|
||||
let inventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber);
|
||||
|
||||
if (!inventory) {
|
||||
|
@ -69,16 +57,18 @@ export default class Claim extends ButtonEvent {
|
|||
|
||||
await claim.Save(eClaim, claim);
|
||||
|
||||
const card = CardDropHelperMetadata.GetCardByCardNumber(cardNumber);
|
||||
const card = GetCardsHelper.GetCardByCardNumber(cardNumber);
|
||||
|
||||
if (!card) {
|
||||
AppLogger.LogError("Button/Claim", `Unable to find card, ${cardNumber}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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 ],
|
||||
|
|
26
src/buttonEvents/Effects.ts
Normal file
26
src/buttonEvents/Effects.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
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";
|
||||
import Buy from "./Effects/Buy";
|
||||
|
||||
export default class Effects extends ButtonEvent {
|
||||
public override async execute(interaction: ButtonInteraction) {
|
||||
const action = interaction.customId.split(" ")[1];
|
||||
|
||||
switch (action) {
|
||||
case "list":
|
||||
await List(interaction);
|
||||
break;
|
||||
case "use":
|
||||
await Use.Execute(interaction);
|
||||
break;
|
||||
case "buy":
|
||||
await Buy.Execute(interaction);
|
||||
break;
|
||||
default:
|
||||
AppLogger.LogError("Buttons/Effects", `Unknown action, ${action}`);
|
||||
}
|
||||
}
|
||||
}
|
120
src/buttonEvents/Effects/Buy.ts
Normal file
120
src/buttonEvents/Effects/Buy.ts
Normal file
|
@ -0,0 +1,120 @@
|
|||
import {ButtonInteraction} from "discord.js";
|
||||
import AppLogger from "../../client/appLogger";
|
||||
import EffectHelper from "../../helpers/EffectHelper";
|
||||
import EmbedColours from "../../constants/EmbedColours";
|
||||
import User from "../../database/entities/app/User";
|
||||
import {EffectDetails} from "../../constants/EffectDetails";
|
||||
|
||||
export default class Buy {
|
||||
public static async Execute(interaction: ButtonInteraction) {
|
||||
const subaction = interaction.customId.split(" ")[2];
|
||||
|
||||
switch (subaction) {
|
||||
case "confirm":
|
||||
await this.Confirm(interaction);
|
||||
break;
|
||||
case "cancel":
|
||||
await this.Cancel(interaction);
|
||||
break;
|
||||
default:
|
||||
AppLogger.LogError("Buy", `Unknown subaction, effects ${subaction}`);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Confirm(interaction: ButtonInteraction) {
|
||||
const id = interaction.customId.split(" ")[3];
|
||||
const quantity = interaction.customId.split(" ")[4];
|
||||
|
||||
if (!id || !quantity) {
|
||||
AppLogger.LogError("Buy Confirm", "Not enough parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
const effectDetail = EffectDetails.get(id);
|
||||
|
||||
if (!effectDetail) {
|
||||
AppLogger.LogError("Buy Confirm", "Effect detail not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
const quantityNumber = Number(quantity);
|
||||
|
||||
if (!quantityNumber || quantityNumber < 1) {
|
||||
AppLogger.LogError("Buy Confirm", "Invalid number");
|
||||
return;
|
||||
}
|
||||
|
||||
const totalCost = effectDetail.cost * quantityNumber;
|
||||
|
||||
const user = await User.FetchOneById(User, interaction.user.id);
|
||||
|
||||
if (!user) {
|
||||
AppLogger.LogError("Buy Confirm", "Unable to find user");
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.Currency < totalCost) {
|
||||
interaction.reply(`You don't have enough currency to buy this! You have \`${user.Currency} Currency\` and need \`${totalCost} Currency\`!`);
|
||||
return;
|
||||
}
|
||||
|
||||
user.RemoveCurrency(totalCost);
|
||||
await user.Save(User, user);
|
||||
|
||||
await EffectHelper.AddEffectToUserInventory(interaction.user.id, id, quantityNumber);
|
||||
|
||||
const generatedEmbed = await EffectHelper.GenerateEffectBuyEmbed(interaction.user.id, id, quantityNumber, true);
|
||||
|
||||
if (typeof generatedEmbed == "string") {
|
||||
await interaction.reply(generatedEmbed);
|
||||
return;
|
||||
}
|
||||
|
||||
generatedEmbed.embed.setColor(EmbedColours.Success);
|
||||
generatedEmbed.embed.setFooter({ text: "Purchased" });
|
||||
|
||||
await interaction.update({
|
||||
embeds: [ generatedEmbed.embed ],
|
||||
components: [ generatedEmbed.row ],
|
||||
});
|
||||
}
|
||||
|
||||
private static async Cancel(interaction: ButtonInteraction) {
|
||||
const id = interaction.customId.split(" ")[3];
|
||||
const quantity = interaction.customId.split(" ")[4];
|
||||
|
||||
if (!id || !quantity) {
|
||||
AppLogger.LogError("Buy Cancel", "Not enough parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
const effectDetail = EffectDetails.get(id);
|
||||
|
||||
if (!effectDetail) {
|
||||
AppLogger.LogError("Buy Cancel", "Effect detail not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
const quantityNumber = Number(quantity);
|
||||
|
||||
if (!quantityNumber || quantityNumber < 1) {
|
||||
AppLogger.LogError("Buy Cancel", "Invalid number");
|
||||
return;
|
||||
}
|
||||
|
||||
const generatedEmbed = await EffectHelper.GenerateEffectBuyEmbed(interaction.user.id, id, quantityNumber, true);
|
||||
|
||||
if (typeof generatedEmbed == "string") {
|
||||
await interaction.reply(generatedEmbed);
|
||||
return;
|
||||
}
|
||||
|
||||
generatedEmbed.embed.setColor(EmbedColours.Error);
|
||||
generatedEmbed.embed.setFooter({ text: "Cancelled" });
|
||||
|
||||
await interaction.update({
|
||||
embeds: [ generatedEmbed.embed ],
|
||||
components: [ generatedEmbed.row ],
|
||||
});
|
||||
}
|
||||
}
|
20
src/buttonEvents/Effects/List.ts
Normal file
20
src/buttonEvents/Effects/List.ts
Normal file
|
@ -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.GenerateEffectListEmbed(interaction.user.id, page);
|
||||
|
||||
await interaction.update({
|
||||
embeds: [ result.embed ],
|
||||
components: [ result.row ],
|
||||
});
|
||||
}
|
132
src/buttonEvents/Effects/Use.ts
Normal file
132
src/buttonEvents/Effects/Use.ts
Normal file
|
@ -0,0 +1,132 @@
|
|||
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";
|
||||
import AppLogger from "../../client/appLogger";
|
||||
|
||||
export default class Use {
|
||||
public static async Execute(interaction: ButtonInteraction) {
|
||||
const subaction = interaction.customId.split(" ")[2];
|
||||
|
||||
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: `<t:${Math.round(whenExpires.getTime() / 1000)}:f>`,
|
||||
inline: true,
|
||||
},
|
||||
]);
|
||||
|
||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||
.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<ButtonBuilder>()
|
||||
.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 ],
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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 ],
|
||||
|
|
|
@ -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) {
|
||||
|
@ -34,12 +35,14 @@ export default class Reroll extends ButtonEvent {
|
|||
AppLogger.LogInfo("Commands/Drop", `New user (${interaction.user.id}) saved to the database`);
|
||||
}
|
||||
|
||||
if (user.Currency < CardConstants.ClaimCost) {
|
||||
if (!user.RemoveCurrency(CardConstants.ClaimCost)) {
|
||||
await interaction.reply(`Not enough currency! You need ${CardConstants.ClaimCost} currency, you have ${user.Currency}!`);
|
||||
return;
|
||||
}
|
||||
|
||||
const randomCard = CardDropHelperMetadata.GetRandomCard();
|
||||
await user.Save(User, user);
|
||||
|
||||
const randomCard = await GetCardsHelper.FetchCard(interaction.user.id);
|
||||
|
||||
if (!randomCard) {
|
||||
await interaction.reply("Unable to fetch card, please try again.");
|
||||
|
@ -51,27 +54,32 @@ export default class Reroll extends ButtonEvent {
|
|||
try {
|
||||
AppLogger.LogVerbose("Button/Reroll", `Sending next drop: ${randomCard.card.id} (${randomCard.card.name})`);
|
||||
|
||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path));
|
||||
const imageFileName = randomCard.card.path.split("/").pop()!;
|
||||
const files = [];
|
||||
let imageFileName = "";
|
||||
|
||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||
if (!(randomCard.card.path.startsWith("http://") || randomCard.card.path.startsWith("https://"))) {
|
||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path));
|
||||
imageFileName = randomCard.card.path.split("/").pop()!;
|
||||
|
||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||
|
||||
files.push(attachment);
|
||||
}
|
||||
|
||||
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 ],
|
||||
files: [ attachment ],
|
||||
files: files,
|
||||
components: [ row ],
|
||||
});
|
||||
|
||||
CoreClient.ClaimId = claimId;
|
||||
} catch (e) {
|
||||
AppLogger.LogError("Button/Reroll", `Error sending next drop for card ${randomCard.card.id}: ${e}`);
|
||||
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -19,7 +19,7 @@ export default class View extends ButtonEvent {
|
|||
await interaction.editReply({
|
||||
embeds: [ searchResult.embed ],
|
||||
components: [ searchResult.row ],
|
||||
files: [ searchResult.attachment ],
|
||||
files: searchResult.attachments,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,4 +86,12 @@ export default class AppLogger {
|
|||
public static LogSilly(label: string, message: string) {
|
||||
AppLogger.Logger.silly({ label, message });
|
||||
}
|
||||
|
||||
public static CatchError(label: string, error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
AppLogger.Logger.error({ label, message: error.message });
|
||||
} else {
|
||||
AppLogger.Logger.error({ label, message: error });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@ export class CoreClient extends Client {
|
|||
private _webhooks: Webhooks;
|
||||
private _timerHelper: TimerHelper;
|
||||
|
||||
public static ClaimId: string;
|
||||
public static Environment: Environment;
|
||||
public static AllowDrops: boolean;
|
||||
public static Cards: SeriesMetadata[];
|
||||
|
|
|
@ -5,12 +5,13 @@ 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";
|
||||
import CardConstants from "../constants/CardConstants";
|
||||
import ErrorMessages from "../constants/ErrorMessages";
|
||||
import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper";
|
||||
import DropEmbedHelper from "../helpers/DropHelpers/DropEmbedHelper";
|
||||
|
||||
export default class Drop extends Command {
|
||||
constructor() {
|
||||
|
@ -42,12 +43,14 @@ export default class Drop extends Command {
|
|||
AppLogger.LogInfo("Commands/Drop", `New user (${interaction.user.id}) saved to the database`);
|
||||
}
|
||||
|
||||
if (user.Currency < CardConstants.ClaimCost) {
|
||||
if (!user.RemoveCurrency(CardConstants.ClaimCost)) {
|
||||
await interaction.reply(ErrorMessages.NotEnoughCurrency(CardConstants.ClaimCost, user.Currency));
|
||||
return;
|
||||
}
|
||||
|
||||
const randomCard = CardDropHelperMetadata.GetRandomCard();
|
||||
await user.Save(User, user);
|
||||
|
||||
const randomCard = await GetCardsHelper.FetchCard(interaction.user.id);
|
||||
|
||||
if (!randomCard) {
|
||||
AppLogger.LogWarn("Commands/Drop", ErrorMessages.UnableToFetchCard);
|
||||
|
@ -58,28 +61,33 @@ export default class Drop extends Command {
|
|||
await interaction.deferReply();
|
||||
|
||||
try {
|
||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path));
|
||||
const imageFileName = randomCard.card.path.split("/").pop()!;
|
||||
const files = [];
|
||||
let imageFileName = "";
|
||||
|
||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||
if (!(randomCard.card.path.startsWith("http://") || randomCard.card.path.startsWith("https://"))) {
|
||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path));
|
||||
imageFileName = randomCard.card.path.split("/").pop()!;
|
||||
|
||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||
|
||||
files.push(attachment);
|
||||
}
|
||||
|
||||
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 ],
|
||||
files: [ attachment ],
|
||||
files: files,
|
||||
components: [ row ],
|
||||
});
|
||||
|
||||
CoreClient.ClaimId = claimId;
|
||||
|
||||
} catch (e) {
|
||||
AppLogger.LogError("Commands/Drop", `Error sending next drop for card ${randomCard.card.id}: ${e}`);
|
||||
|
||||
|
|
64
src/commands/effects.ts
Normal file
64
src/commands/effects.ts
Normal file
|
@ -0,0 +1,64 @@
|
|||
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
|
||||
import { Command } from "../type/command";
|
||||
import { EffectChoices } from "../constants/EffectDetails";
|
||||
import AppLogger from "../client/appLogger";
|
||||
import List from "./effects/List";
|
||||
import Use from "./effects/Use";
|
||||
import Buy from "./effects/Buy";
|
||||
|
||||
export default class Effects extends Command {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.CommandBuilder = new SlashCommandBuilder()
|
||||
.setName("effects")
|
||||
.setDescription("Effects")
|
||||
.addSubcommand(x => x
|
||||
.setName("list")
|
||||
.setDescription("List all effects I have")
|
||||
.addNumberOption(x => x
|
||||
.setName("page")
|
||||
.setDescription("The page number")
|
||||
.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(EffectChoices)))
|
||||
.addSubcommand(x => x
|
||||
.setName("buy")
|
||||
.setDescription("Buy more effects")
|
||||
.addStringOption(y => y
|
||||
.setName("id")
|
||||
.setDescription("The effect id to buy")
|
||||
.setRequired(true)
|
||||
.setChoices(EffectChoices))
|
||||
.addNumberOption(y => y
|
||||
.setName("quantity")
|
||||
.setDescription("The amount to buy")
|
||||
.setMinValue(1)));
|
||||
}
|
||||
|
||||
public override async execute(interaction: CommandInteraction) {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
|
||||
switch (subcommand) {
|
||||
case "list":
|
||||
await List(interaction);
|
||||
break;
|
||||
case "use":
|
||||
await Use(interaction);
|
||||
break;
|
||||
case "buy":
|
||||
await Buy(interaction);
|
||||
break;
|
||||
default:
|
||||
AppLogger.LogError("Commands/Effects", `Invalid subcommand: ${subcommand}`);
|
||||
}
|
||||
}
|
||||
}
|
22
src/commands/effects/Buy.ts
Normal file
22
src/commands/effects/Buy.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { CommandInteraction } from "discord.js";
|
||||
import EffectHelper from "../../helpers/EffectHelper";
|
||||
|
||||
export default async function Buy(interaction: CommandInteraction) {
|
||||
const id = interaction.options.get("id", true).value!;
|
||||
const quantity = interaction.options.get("quantity")?.value ?? 1;
|
||||
|
||||
const idValue = id.toString();
|
||||
const quantityValue = Number(quantity);
|
||||
|
||||
const result = await EffectHelper.GenerateEffectBuyEmbed(interaction.user.id, idValue, quantityValue, false);
|
||||
|
||||
if (typeof result == "string") {
|
||||
await interaction.reply(result);
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.reply({
|
||||
embeds: [ result.embed ],
|
||||
components: [ result.row ],
|
||||
});
|
||||
}
|
15
src/commands/effects/List.ts
Normal file
15
src/commands/effects/List.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { CommandInteraction } from "discord.js";
|
||||
import EffectHelper from "../../helpers/EffectHelper";
|
||||
|
||||
export default async function List(interaction: CommandInteraction) {
|
||||
const pageOption = interaction.options.get("page");
|
||||
|
||||
const page = !isNaN(Number(pageOption?.value)) ? Number(pageOption?.value) : 1;
|
||||
|
||||
const result = await EffectHelper.GenerateEffectListEmbed(interaction.user.id, page);
|
||||
|
||||
await interaction.reply({
|
||||
embeds: [ result.embed ],
|
||||
components: [ result.row ],
|
||||
});
|
||||
}
|
62
src/commands/effects/Use.ts
Normal file
62
src/commands/effects/Use.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder } from "discord.js";
|
||||
import { EffectDetails } from "../../constants/EffectDetails";
|
||||
import AppLogger from "../../client/appLogger";
|
||||
import EffectHelper from "../../helpers/EffectHelper";
|
||||
import TimeLengthInput from "../../helpers/TimeLengthInput";
|
||||
import EmbedColours from "../../constants/EmbedColours";
|
||||
|
||||
export default async function Use(interaction: CommandInteraction) {
|
||||
const id = interaction.options.get("id", true).value!.toString();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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?")
|
||||
.setColor(EmbedColours.Ok)
|
||||
.addFields([
|
||||
{
|
||||
name: "Effect",
|
||||
value: effectDetail.friendlyName,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "Length",
|
||||
value: timeLengthInput.GetLengthShort(),
|
||||
inline: true,
|
||||
},
|
||||
]);
|
||||
|
||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||
.addComponents([
|
||||
new ButtonBuilder()
|
||||
.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({
|
||||
embeds: [ embed ],
|
||||
components: [ row ],
|
||||
});
|
||||
}
|
|
@ -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.");
|
||||
|
|
|
@ -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() {
|
||||
|
@ -43,31 +43,29 @@ export default class Id extends Command {
|
|||
const series = CoreClient.Cards
|
||||
.find(x => x.cards.includes(card))!;
|
||||
|
||||
let image: Buffer;
|
||||
const imageFileName = card.path.split("/").pop()!;
|
||||
const files = [];
|
||||
let imageFileName = "";
|
||||
|
||||
try {
|
||||
image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.path));
|
||||
} catch {
|
||||
AppLogger.LogError("Commands/View", `Unable to fetch image for card ${card.id}.`);
|
||||
if (!(card.path.startsWith("http://") || card.path.startsWith("https://"))) {
|
||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.path));
|
||||
imageFileName = card.path.split("/").pop()!;
|
||||
|
||||
await interaction.reply(`Unable to fetch image for card ${card.id}.`);
|
||||
return;
|
||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||
|
||||
files.push(attachment);
|
||||
}
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||
|
||||
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({
|
||||
embeds: [ embed ],
|
||||
files: [ attachment ],
|
||||
files: files,
|
||||
});
|
||||
} catch (e) {
|
||||
AppLogger.LogError("Commands/View", `Error sending view for card ${card.id}: ${e}`);
|
||||
|
|
|
@ -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 ],
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -60,13 +60,18 @@ export default class Series extends Command {
|
|||
return;
|
||||
}
|
||||
|
||||
const embed = await SeriesHelper.GenerateSeriesViewPage(series.id, 0, interaction.user.id);
|
||||
try {
|
||||
const embed = await SeriesHelper.GenerateSeriesViewPage(series.id, 0, interaction.user.id);
|
||||
|
||||
await interaction.followUp({
|
||||
embeds: [ embed!.embed ],
|
||||
components: [ embed!.row ],
|
||||
files: [ embed!.image ],
|
||||
});
|
||||
await interaction.followUp({
|
||||
embeds: [ embed!.embed ],
|
||||
components: [ embed!.row ],
|
||||
files: [ embed!.image ],
|
||||
});
|
||||
} catch (e) {
|
||||
await interaction.followUp("An error has occured generating the series grid.");
|
||||
AppLogger.CatchError("Series", e);
|
||||
}
|
||||
}
|
||||
|
||||
private async ListSeries(interaction: CommandInteraction) {
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { AttachmentBuilder, CacheType, CommandInteraction, DiscordAPIError, SlashCommandBuilder } from "discord.js";
|
||||
import { AttachmentBuilder, CacheType, CommandInteraction, SlashCommandBuilder } from "discord.js";
|
||||
import { Command } from "../../type/command";
|
||||
import { readFileSync } from "fs";
|
||||
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";
|
||||
import AppLogger from "../../client/appLogger";
|
||||
|
||||
export default class Dropnumber extends Command {
|
||||
constructor() {
|
||||
|
@ -40,48 +41,40 @@ export default class Dropnumber extends Command {
|
|||
return;
|
||||
}
|
||||
|
||||
const series = CoreClient.Cards
|
||||
.find(x => x.cards.includes(card))!;
|
||||
|
||||
let image: Buffer;
|
||||
const imageFileName = card.path.split("/").pop()!;
|
||||
|
||||
try {
|
||||
image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.path));
|
||||
} catch {
|
||||
await interaction.reply(`Unable to fetch image for card ${card.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const claimId = v4();
|
||||
await interaction.deferReply();
|
||||
|
||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||
try {
|
||||
const files = [];
|
||||
let imageFileName = "";
|
||||
|
||||
if (!(card.path.startsWith("http://") || card.path.startsWith("https://"))) {
|
||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.path));
|
||||
imageFileName = card.path.split("/").pop()!;
|
||||
|
||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||
|
||||
files.push(attachment);
|
||||
}
|
||||
|
||||
const series = CoreClient.Cards
|
||||
.find(x => x.cards.includes(card))!;
|
||||
|
||||
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 = DropEmbedHelper.GenerateDropButtons({ card, series }, claimId, interaction.user.id);
|
||||
|
||||
const row = CardDropHelperMetadata.GenerateDropButtons({ card, series }, claimId, interaction.user.id);
|
||||
|
||||
try {
|
||||
await interaction.editReply({
|
||||
embeds: [ embed ],
|
||||
files: [ attachment ],
|
||||
components: [ row ],
|
||||
});
|
||||
await interaction.editReply({
|
||||
embeds: [ embed ],
|
||||
files: files,
|
||||
components: [ row ],
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
if (e instanceof DiscordAPIError) {
|
||||
await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. Code: ${e.code}`);
|
||||
} else {
|
||||
await interaction.editReply("Unable to send next drop. Please try again, and report this if it keeps happening. Code: UNKNOWN");
|
||||
}
|
||||
AppLogger.CatchError("Dropnumber", e);
|
||||
await interaction.editReply("Unable to send next drop. Please try again, and report this if it keeps happening");
|
||||
}
|
||||
|
||||
CoreClient.ClaimId = claimId;
|
||||
}
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
import { AttachmentBuilder, CacheType, CommandInteraction, DiscordAPIError, SlashCommandBuilder } from "discord.js";
|
||||
import { AttachmentBuilder, CacheType, CommandInteraction, SlashCommandBuilder } from "discord.js";
|
||||
import { Command } from "../../type/command";
|
||||
import { CardRarity, CardRarityParse } from "../../constants/CardRarity";
|
||||
import { CardRarity, CardRarityChoices, CardRarityParse } from "../../constants/CardRarity";
|
||||
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";
|
||||
import AppLogger from "../../client/appLogger";
|
||||
|
||||
export default class Droprarity extends Command {
|
||||
constructor() {
|
||||
|
@ -19,7 +20,8 @@ export default class Droprarity extends Command {
|
|||
x
|
||||
.setName("rarity")
|
||||
.setDescription("The rarity you want to summon")
|
||||
.setRequired(true));
|
||||
.setRequired(true)
|
||||
.setChoices(CardRarityChoices));
|
||||
}
|
||||
|
||||
public override async execute(interaction: CommandInteraction<CacheType>) {
|
||||
|
@ -39,52 +41,44 @@ export default class Droprarity extends Command {
|
|||
return;
|
||||
}
|
||||
|
||||
const card = await CardDropHelperMetadata.GetRandomCardByRarity(rarityType);
|
||||
const card = GetCardsHelper.GetRandomCardByRarity(rarityType);
|
||||
|
||||
if (!card) {
|
||||
await interaction.reply("Card not found");
|
||||
return;
|
||||
}
|
||||
|
||||
let image: Buffer;
|
||||
const imageFileName = card.card.path.split("/").pop()!;
|
||||
|
||||
try {
|
||||
image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path));
|
||||
} catch {
|
||||
await interaction.reply(`Unable to fetch image for card ${card.card.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const claimId = v4();
|
||||
await interaction.deferReply();
|
||||
|
||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||
|
||||
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 claimId = v4();
|
||||
|
||||
const row = CardDropHelperMetadata.GenerateDropButtons(card, claimId, interaction.user.id);
|
||||
|
||||
try {
|
||||
const files = [];
|
||||
let imageFileName = "";
|
||||
|
||||
if (!(card.card.path.startsWith("http://") || card.card.path.startsWith("https://"))) {
|
||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path));
|
||||
imageFileName = card.card.path.split("/").pop()!;
|
||||
|
||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||
|
||||
files.push(attachment);
|
||||
}
|
||||
|
||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.card.id);
|
||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
||||
|
||||
const embed = DropEmbedHelper.GenerateDropEmbed(card, quantityClaimed, imageFileName);
|
||||
|
||||
const row = DropEmbedHelper.GenerateDropButtons(card, claimId, interaction.user.id);
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [ embed ],
|
||||
files: [ attachment ],
|
||||
files: files,
|
||||
components: [ row ],
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
if (e instanceof DiscordAPIError) {
|
||||
await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. Code: ${e.code}`);
|
||||
} else {
|
||||
await interaction.editReply("Unable to send next drop. Please try again, and report this if it keeps happening. Code: UNKNOWN");
|
||||
}
|
||||
AppLogger.CatchError("Droprarity", e);
|
||||
await interaction.editReply("Unable to send next drop. Please try again, and report this if it keeps happening");
|
||||
}
|
||||
|
||||
CoreClient.ClaimId = claimId;
|
||||
}
|
||||
}
|
|
@ -34,7 +34,7 @@ export default class View extends Command {
|
|||
await interaction.editReply({
|
||||
embeds: [ searchResult.embed ],
|
||||
components: [ searchResult.row ],
|
||||
files: [ searchResult.attachment ],
|
||||
files: searchResult.attachments,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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 = 0.5;
|
||||
}
|
|
@ -9,6 +9,29 @@ export enum CardRarity {
|
|||
Legendary,
|
||||
}
|
||||
|
||||
export const CardRarityChoices = [
|
||||
{
|
||||
name: "Bronze",
|
||||
value: "bronze",
|
||||
},
|
||||
{
|
||||
name: "Silver",
|
||||
value: "silver",
|
||||
},
|
||||
{
|
||||
name: "Gold",
|
||||
value: "gold",
|
||||
},
|
||||
{
|
||||
name: "Manga",
|
||||
value: "manga",
|
||||
},
|
||||
{
|
||||
name: "Legendary",
|
||||
value: "legendary",
|
||||
},
|
||||
];
|
||||
|
||||
export function CardRarityToString(rarity: CardRarity): string {
|
||||
switch (rarity) {
|
||||
case CardRarity.Unknown:
|
||||
|
|
23
src/constants/EffectDetails.ts
Normal file
23
src/constants/EffectDetails.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
class EffectDetail {
|
||||
public readonly id: string;
|
||||
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, cooldown: number) {
|
||||
this.id = id;
|
||||
this.friendlyName = friendlyName;
|
||||
this.duration = duration;
|
||||
this.cost = cost;
|
||||
this.cooldown = cooldown;
|
||||
}
|
||||
};
|
||||
|
||||
export const EffectDetails = new Map<string, EffectDetail>([
|
||||
[ "unclaimed", new EffectDetail("unclaimed", "Unclaimed Chance Up", 10 * 60 * 1000, 100, 3 * 60 * 60 * 1000) ],
|
||||
]);
|
||||
|
||||
export const EffectChoices = [
|
||||
{ name: "Unclaimed Chance Up", value: "unclaimed" },
|
||||
];
|
|
@ -57,4 +57,30 @@ export default class UserEffect extends AppBaseEntity {
|
|||
|
||||
return single;
|
||||
}
|
||||
|
||||
public static async FetchAllByUserIdPaginated(userId: string, page: number = 0, itemsPerPage: number = 10): Promise<[UserEffect[], number]> {
|
||||
const repository = AppDataSource.getRepository(UserEffect);
|
||||
|
||||
const query = await repository.createQueryBuilder("effect")
|
||||
.where("effect.UserId = :userId", { userId })
|
||||
.andWhere("effect.Unused > 0")
|
||||
.orderBy("effect.Name", "ASC")
|
||||
.skip(page * itemsPerPage)
|
||||
.take(itemsPerPage)
|
||||
.getManyAndCount();
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
public static async FetchActiveEffectByUserId(userId: string): Promise<UserEffect | null> {
|
||||
const repository = AppDataSource.getRepository(UserEffect);
|
||||
|
||||
const query = await repository.createQueryBuilder("effect")
|
||||
.where("effect.UserId = :userId", { userId })
|
||||
.andWhere("effect.WhenExpires IS NOT NULL")
|
||||
.andWhere("effect.WhenExpires > :now", { now: new Date() })
|
||||
.getOne();
|
||||
|
||||
return query;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,174 +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";
|
||||
|
||||
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 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<ButtonBuilder> {
|
||||
AppLogger.LogSilly("CardDropHelperMetadata/GenerateDropButtons", `Parameters: drop=${drop.card.id}, claimId=${claimId}, userId=${userId}`);
|
||||
|
||||
return new ActionRowBuilder<ButtonBuilder>()
|
||||
.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<ButtonBuilder> {
|
||||
return new ActionRowBuilder<ButtonBuilder>()
|
||||
.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));
|
||||
}
|
||||
}
|
|
@ -1,16 +1,17 @@
|
|||
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,
|
||||
row: ActionRowBuilder<ButtonBuilder>,
|
||||
attachment: AttachmentBuilder,
|
||||
attachments: AttachmentBuilder[],
|
||||
results: string[],
|
||||
}
|
||||
|
||||
|
@ -32,27 +33,26 @@ export default class CardSearchHelper {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const card = CardDropHelperMetadata.GetCardByCardNumber(entry.item.id);
|
||||
const card = GetCardsHelper.GetCardByCardNumber(entry.item.id);
|
||||
|
||||
if (!card) return undefined;
|
||||
|
||||
let image: Buffer;
|
||||
const imageFileName = card.card.path.split("/").pop()!;
|
||||
const attachments = [];
|
||||
let imageFileName = "";
|
||||
|
||||
try {
|
||||
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}.`);
|
||||
if (!(card.card.path.startsWith("http://") || card.card.path.startsWith("https://"))) {
|
||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path));
|
||||
imageFileName = card.card.path.split("/").pop()!;
|
||||
|
||||
return undefined;
|
||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||
|
||||
attachments.push(attachment);
|
||||
}
|
||||
|
||||
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<ButtonBuilder>()
|
||||
.addComponents(
|
||||
|
@ -67,13 +67,13 @@ export default class CardSearchHelper {
|
|||
.setStyle(ButtonStyle.Primary)
|
||||
.setDisabled(pages == 1));
|
||||
|
||||
return { embed, row, attachment, results };
|
||||
return { embed, row, attachments, results };
|
||||
}
|
||||
|
||||
public static async GenerateSearchPageFromQuery(results: string[], userid: string, page: number): Promise<ReturnedPage | undefined> {
|
||||
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}.`);
|
||||
|
@ -81,23 +81,22 @@ export default class CardSearchHelper {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
let image: Buffer;
|
||||
const imageFileName = card.card.path.split("/").pop()!;
|
||||
const attachments = [];
|
||||
let imageFileName = "";
|
||||
|
||||
try {
|
||||
image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path));
|
||||
} catch {
|
||||
AppLogger.LogError("CardSearchHelper/GenerateSearchPageFromQuery", `Unable to fetch image for card ${card.card.id}.`);
|
||||
if (!(card.card.path.startsWith("http://") || card.card.path.startsWith("https://"))) {
|
||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path));
|
||||
imageFileName = card.card.path.split("/").pop()!;
|
||||
|
||||
return undefined;
|
||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||
|
||||
attachments.push(attachment);
|
||||
}
|
||||
|
||||
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<ButtonBuilder>()
|
||||
.addComponents(
|
||||
|
@ -112,6 +111,6 @@ export default class CardSearchHelper {
|
|||
.setStyle(ButtonStyle.Primary)
|
||||
.setDisabled(page == results.length));
|
||||
|
||||
return { embed, row, attachment, results };
|
||||
return { embed, row, attachments, results };
|
||||
}
|
||||
}
|
||||
|
|
88
src/helpers/DropHelpers/DropEmbedHelper.ts
Normal file
88
src/helpers/DropHelpers/DropEmbedHelper.ts
Normal file
|
@ -0,0 +1,88 @@
|
|||
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";
|
||||
|
||||
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}`);
|
||||
}
|
||||
|
||||
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(imageUrl)
|
||||
.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<ButtonBuilder> {
|
||||
AppLogger.LogSilly("CardDropHelperMetadata/GenerateDropButtons", `Parameters: drop=${drop.card.id}, claimId=${claimId}, userId=${userId}`);
|
||||
|
||||
return new ActionRowBuilder<ButtonBuilder>()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`claim ${drop.card.id} ${claimId} ${userId}`)
|
||||
.setLabel("Claim")
|
||||
.setStyle(ButtonStyle.Success)
|
||||
.setDisabled(disabled),
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`sacrifice confirm ${userId} ${drop.card.id} 1`)
|
||||
.setLabel(`Sacrifice`)
|
||||
.setStyle(ButtonStyle.Danger),
|
||||
new ButtonBuilder()
|
||||
.setCustomId("reroll")
|
||||
.setEmoji("🔁")
|
||||
.setStyle(ButtonStyle.Primary),);
|
||||
}
|
||||
}
|
91
src/helpers/DropHelpers/GetCardsHelper.ts
Normal file
91
src/helpers/DropHelpers/GetCardsHelper.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
import AppLogger from "../../client/appLogger";
|
||||
import { CoreClient } from "../../client/client";
|
||||
import CardConstants from "../../constants/CardConstants";
|
||||
import { CardRarity } from "../../constants/CardRarity";
|
||||
import CardRarityChances from "../../constants/CardRarityChances";
|
||||
import { DropResult } from "../../contracts/SeriesMetadata";
|
||||
import EffectHelper from "../EffectHelper";
|
||||
import GetUnclaimedCardsHelper from "./GetUnclaimedCardsHelper";
|
||||
|
||||
export default class GetCardsHelper {
|
||||
public static async FetchCard(userId: string): Promise<DropResult | undefined> {
|
||||
const hasChanceUpEffect = await EffectHelper.HasEffect(userId, "unclaimed");
|
||||
|
||||
if (hasChanceUpEffect && Math.random() <= CardConstants.UnusedChanceUpChance) {
|
||||
return await GetUnclaimedCardsHelper.GetRandomCardUnclaimed(userId);
|
||||
}
|
||||
|
||||
return this.GetRandomCard();
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
}
|
63
src/helpers/DropHelpers/GetUnclaimedCardsHelper.ts
Normal file
63
src/helpers/DropHelpers/GetUnclaimedCardsHelper.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
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<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 = await this.GetRandomCardByRarityUnclaimed(cardRarity, userId);
|
||||
|
||||
return randomCard;
|
||||
}
|
||||
|
||||
public static async GetRandomCardByRarityUnclaimed(rarity: CardRarity, userId: string): Promise<DropResult | undefined> {
|
||||
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;
|
||||
}
|
||||
|
||||
return {
|
||||
series: series,
|
||||
card: card,
|
||||
};
|
||||
}
|
||||
}
|
28
src/helpers/DropHelpers/MultidropEmbedHelper.ts
Normal file
28
src/helpers/DropHelpers/MultidropEmbedHelper.ts
Normal file
|
@ -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<ButtonBuilder> {
|
||||
return new ActionRowBuilder<ButtonBuilder>()
|
||||
.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));
|
||||
}
|
||||
}
|
|
@ -1,4 +1,10 @@
|
|||
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 User from "../database/entities/app/User";
|
||||
import CardConstants from "../constants/CardConstants";
|
||||
import AppLogger from "../client/appLogger";
|
||||
|
||||
export default class EffectHelper {
|
||||
public static async AddEffectToUserInventory(userId: string, name: string, quantity: number = 1) {
|
||||
|
@ -14,6 +20,20 @@ export default class EffectHelper {
|
|||
}
|
||||
|
||||
public static async UseEffect(userId: string, name: string, whenExpires: Date): Promise<boolean> {
|
||||
const canUseEffect = await this.CanUseEffect(userId, name);
|
||||
|
||||
if (!canUseEffect) return false;
|
||||
|
||||
const effect = await UserEffect.FetchOneByUserIdAndName(userId, name);
|
||||
|
||||
effect!.UseEffect(whenExpires);
|
||||
|
||||
await effect!.Save(UserEffect, effect!);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static async CanUseEffect(userId: string, name: string): Promise<boolean> {
|
||||
const effect = await UserEffect.FetchOneByUserIdAndName(userId, name);
|
||||
const now = new Date();
|
||||
|
||||
|
@ -21,13 +41,15 @@ export default class EffectHelper {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (effect.WhenExpires && now < effect.WhenExpires) {
|
||||
const effectDetail = EffectDetails.get(effect.Name);
|
||||
|
||||
if (!effectDetail) {
|
||||
return false;
|
||||
}
|
||||
|
||||
effect.UseEffect(whenExpires);
|
||||
|
||||
await effect.Save(UserEffect, effect);
|
||||
if (effect.WhenExpires && now < new Date(effect.WhenExpires.getTime() + effectDetail.cooldown)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -46,4 +68,127 @@ export default class EffectHelper {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static async GenerateEffectListEmbed(userId: string, page: number): Promise<{
|
||||
embed: EmbedBuilder,
|
||||
row: ActionRowBuilder<ButtonBuilder>,
|
||||
}> {
|
||||
const itemsPerPage = 10;
|
||||
|
||||
const query = await UserEffect.FetchAllByUserIdPaginated(userId, page - 1, itemsPerPage);
|
||||
const activeEffect = await UserEffect.FetchActiveEffectByUserId(userId);
|
||||
|
||||
const effects = query[0];
|
||||
const count = query[1];
|
||||
|
||||
const totalPages = count > 0 ? Math.ceil(count / itemsPerPage) : 1;
|
||||
|
||||
let description = "*none*";
|
||||
|
||||
if (effects.length > 0) {
|
||||
description = effects.map(x => `${EffectDetails.get(x.Name)?.friendlyName} x${x.Unused}`).join("\n");
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("Effects")
|
||||
.setDescription(description)
|
||||
.setColor(EmbedColours.Ok)
|
||||
.setFooter({ text: `Page ${page} of ${totalPages}` });
|
||||
|
||||
if (activeEffect) {
|
||||
embed.addFields([
|
||||
{
|
||||
name: "Active",
|
||||
value: `${EffectDetails.get(activeEffect.Name)?.friendlyName}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "Expires",
|
||||
value: `<t:${Math.round(activeEffect.WhenExpires!.getTime() / 1000)}>`,
|
||||
inline: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`effects list ${page - 1}`)
|
||||
.setLabel("Previous")
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setDisabled(page == 1),
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`effects list ${page + 1}`)
|
||||
.setLabel("Next")
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setDisabled(page == totalPages),
|
||||
);
|
||||
|
||||
return {
|
||||
embed,
|
||||
row,
|
||||
};
|
||||
}
|
||||
|
||||
public static async GenerateEffectBuyEmbed(userId: string, id: string, quantity: number, disabled: boolean): Promise<{
|
||||
embed: EmbedBuilder,
|
||||
row: ActionRowBuilder<ButtonBuilder>,
|
||||
} | string> {
|
||||
const effectDetail = EffectDetails.get(id);
|
||||
|
||||
if (!effectDetail) {
|
||||
return "Effect detail not found!";
|
||||
}
|
||||
|
||||
const totalCost = effectDetail.cost * quantity;
|
||||
|
||||
let user = await User.FetchOneById(User, userId);
|
||||
|
||||
if (!user) {
|
||||
user = new User(userId, CardConstants.StartingCurrency);
|
||||
await user.Save(User, user);
|
||||
|
||||
AppLogger.LogInfo("EffectHelper", `Created initial user entity for : ${userId}`);
|
||||
}
|
||||
|
||||
if (user.Currency < totalCost) {
|
||||
return `You don't have enough currency to buy this! You have \`${user.Currency} Currency\` and need \`${totalCost} Currency\`!`;
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("Buy Effect")
|
||||
.setDescription(effectDetail.friendlyName)
|
||||
.setColor(EmbedColours.Ok)
|
||||
.addFields([
|
||||
{
|
||||
name: "Cost",
|
||||
value: `${totalCost}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "Quantity",
|
||||
value: `${quantity}`,
|
||||
inline: true,
|
||||
},
|
||||
]);
|
||||
|
||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||
.addComponents([
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`effects buy confirm ${id} ${quantity}`)
|
||||
.setLabel("Confirm")
|
||||
.setStyle(ButtonStyle.Success)
|
||||
.setDisabled(disabled),
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`effects buy cancel ${id} ${quantity}`)
|
||||
.setLabel("Cancel")
|
||||
.setStyle(ButtonStyle.Danger)
|
||||
.setDisabled(disabled),
|
||||
]);
|
||||
|
||||
return {
|
||||
embed,
|
||||
row,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@ import path from "path";
|
|||
import AppLogger from "../client/appLogger";
|
||||
import {existsSync} from "fs";
|
||||
import Inventory from "../database/entities/app/Inventory";
|
||||
import {Jimp} from "jimp";
|
||||
import { Bitmap, Jimp } from "jimp";
|
||||
import axios from "axios";
|
||||
|
||||
interface CardInput {
|
||||
id: string;
|
||||
|
@ -25,36 +26,52 @@ export default class ImageHelper {
|
|||
const ctx = canvas.getContext("2d");
|
||||
|
||||
for (let i = 0; i < cards.length; i++) {
|
||||
const card = cards[i];
|
||||
try {
|
||||
const card = cards[i];
|
||||
|
||||
const filePath = path.join(process.env.DATA_DIR!, "cards", card.path);
|
||||
const filePath = path.join(process.env.DATA_DIR!, "cards", card.path);
|
||||
|
||||
const exists = existsSync(filePath);
|
||||
let bitmap: Bitmap;
|
||||
|
||||
if (!exists) {
|
||||
AppLogger.LogError("ImageHelper/GenerateCardImageGrid", `Failed to load image from path ${card.path}`);
|
||||
continue;
|
||||
}
|
||||
if (existsSync(filePath)) {
|
||||
const data = await Jimp.read(filePath);
|
||||
|
||||
const imageData = await Jimp.read(filePath);
|
||||
bitmap = data.bitmap;
|
||||
} else if (card.path.startsWith("http://") || card.path.startsWith("https://")) {
|
||||
const response = await axios.get(card.path, { responseType: "arraybuffer" });
|
||||
const buffer = Buffer.from(response.data);
|
||||
const data = await Jimp.fromBuffer(buffer);
|
||||
|
||||
if (userId != null) {
|
||||
const claimed = await Inventory.FetchOneByCardNumberAndUserId(userId, card.id);
|
||||
|
||||
if (!claimed || claimed.Quantity == 0) {
|
||||
imageData.greyscale();
|
||||
bitmap = data.bitmap;
|
||||
} else {
|
||||
AppLogger.LogError("ImageHelper/GenerateCardImageGrid", `Failed to load image from path ${card.path}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const imageData = Jimp.fromBitmap(bitmap);
|
||||
|
||||
if (userId != null) {
|
||||
const claimed = await Inventory.FetchOneByCardNumberAndUserId(userId, card.id);
|
||||
|
||||
if (!claimed || claimed.Quantity == 0) {
|
||||
imageData.greyscale();
|
||||
}
|
||||
}
|
||||
|
||||
const image = await loadImage(await imageData.getBuffer("image/png"));
|
||||
|
||||
const x = i % gridWidth;
|
||||
const y = Math.floor(i / gridWidth);
|
||||
|
||||
const imageX = imageWidth * x;
|
||||
const imageY = imageHeight * y;
|
||||
|
||||
ctx.drawImage(image, imageX, imageY);
|
||||
}
|
||||
catch {
|
||||
// TODO: Enable once we've investigated a fix
|
||||
//AppLogger.CatchError("ImageHelper", e);
|
||||
}
|
||||
|
||||
const image = await loadImage(await imageData.getBuffer("image/png"));
|
||||
|
||||
const x = i % gridWidth;
|
||||
const y = Math.floor(i / gridWidth);
|
||||
|
||||
const imageX = imageWidth * x;
|
||||
const imageY = imageHeight * y;
|
||||
|
||||
ctx.drawImage(image, imageX, imageY);
|
||||
}
|
||||
|
||||
return canvas.toBuffer();
|
||||
|
|
|
@ -118,4 +118,19 @@ export default class TimeLengthInput {
|
|||
|
||||
return desNumber;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ import AllBalance from "./commands/allbalance";
|
|||
import Balance from "./commands/balance";
|
||||
import Daily from "./commands/daily";
|
||||
import Drop from "./commands/drop";
|
||||
import Effects from "./commands/effects";
|
||||
import Gdrivesync from "./commands/gdrivesync";
|
||||
import Give from "./commands/give";
|
||||
import Id from "./commands/id";
|
||||
|
@ -25,6 +26,7 @@ import Droprarity from "./commands/stage/droprarity";
|
|||
|
||||
// Button Event Imports
|
||||
import Claim from "./buttonEvents/Claim";
|
||||
import EffectsButtonEvent from "./buttonEvents/Effects";
|
||||
import InventoryButtonEvent from "./buttonEvents/Inventory";
|
||||
import MultidropButtonEvent from "./buttonEvents/Multidrop";
|
||||
import Reroll from "./buttonEvents/Reroll";
|
||||
|
@ -44,6 +46,7 @@ export default class Registry {
|
|||
CoreClient.RegisterCommand("balance", new Balance());
|
||||
CoreClient.RegisterCommand("daily", new Daily());
|
||||
CoreClient.RegisterCommand("drop", new Drop());
|
||||
CoreClient.RegisterCommand("effects", new Effects());
|
||||
CoreClient.RegisterCommand("gdrivesync", new Gdrivesync());
|
||||
CoreClient.RegisterCommand("give", new Give());
|
||||
CoreClient.RegisterCommand("id", new Id());
|
||||
|
@ -63,6 +66,7 @@ export default class Registry {
|
|||
|
||||
public static RegisterButtonEvents() {
|
||||
CoreClient.RegisterButtonEvent("claim", new Claim());
|
||||
CoreClient.RegisterButtonEvent("effects", new EffectsButtonEvent());
|
||||
CoreClient.RegisterButtonEvent("inventory", new InventoryButtonEvent());
|
||||
CoreClient.RegisterButtonEvent("multidrop", new MultidropButtonEvent());
|
||||
CoreClient.RegisterButtonEvent("reroll", new Reroll());
|
||||
|
|
|
@ -4,7 +4,7 @@ import Claim from "../database/entities/app/Claim";
|
|||
export default async function PurgeClaims() {
|
||||
const claims = await Claim.FetchAll(Claim);
|
||||
|
||||
const whenLastClaimable = new Date(Date.now() - (1000 * 60 * 5)); // 5 minutes ago
|
||||
const whenLastClaimable = new Date(Date.now() - (1000 * 60 * 2)); // 2 minutes ago
|
||||
|
||||
const expiredClaims = claims.filter(x => x.WhenCreated < whenLastClaimable);
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
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",
|
||||
update: jest.fn(),
|
||||
reply: jest.fn(),
|
||||
};
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import { CommandInteraction } from "../../__types__/discord.js";
|
||||
|
||||
export default function GenerateCommandInteractionMock(options?: {
|
||||
subcommand?: string,
|
||||
}): CommandInteraction {
|
||||
return {
|
||||
deferReply: jest.fn(),
|
||||
editReply: jest.fn(),
|
||||
isChatInputCommand: jest.fn().mockReturnValue(true),
|
||||
options: {
|
||||
getSubcommand: jest.fn().mockReturnValue(options?.subcommand),
|
||||
},
|
||||
user: {
|
||||
id: "userId",
|
||||
},
|
||||
};
|
||||
}
|
31
tests/__types__/discord.js.ts
Normal file
31
tests/__types__/discord.js.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
export type ButtonInteraction = {
|
||||
guild: object | 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,
|
||||
update: jest.Func,
|
||||
reply: jest.Func,
|
||||
}
|
||||
|
||||
export type CommandInteraction = {
|
||||
deferReply: jest.Func,
|
||||
editReply: jest.Func,
|
||||
isChatInputCommand: jest.Func,
|
||||
options: {
|
||||
getSubcommand: jest.Func,
|
||||
},
|
||||
user: {
|
||||
id: string,
|
||||
},
|
||||
}
|
90
tests/buttonEvents/Claim.test.ts
Normal file
90
tests/buttonEvents/Claim.test.ts
Normal file
|
@ -0,0 +1,90 @@
|
|||
import { ButtonInteraction, TextChannel } from "discord.js";
|
||||
import Claim from "../../src/buttonEvents/Claim";
|
||||
import { ButtonInteraction as ButtonInteractionType } from "../__types__/discord.js";
|
||||
import GenerateButtonInteractionMock from "../__functions__/discord.js/GenerateButtonInteractionMock";
|
||||
|
||||
jest.mock("../../src/client/appLogger");
|
||||
|
||||
let interaction: ButtonInteractionType;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(1000 * 60 * 30);
|
||||
|
||||
interaction = GenerateButtonInteractionMock();
|
||||
interaction.customId = "claim cardNumber claimId droppedBy userId";
|
||||
});
|
||||
|
||||
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 2 minutes of it being dropped!");
|
||||
|
||||
expect(interaction.editReply).not.toHaveBeenCalled();
|
||||
});
|
68
tests/buttonEvents/Effects.test.ts
Normal file
68
tests/buttonEvents/Effects.test.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
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";
|
||||
|
||||
jest.mock("../../src/client/appLogger");
|
||||
jest.mock("../../src/buttonEvents/Effects/List");
|
||||
jest.mock("../../src/buttonEvents/Effects/Use");
|
||||
|
||||
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.todo("GIVEN action is buy, EXPECT buy function to be called");
|
||||
|
||||
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");
|
||||
});
|
350
tests/buttonEvents/Effects/Buy.test.ts
Normal file
350
tests/buttonEvents/Effects/Buy.test.ts
Normal file
|
@ -0,0 +1,350 @@
|
|||
import {ButtonInteraction} from "discord.js";
|
||||
import Buy from "../../../src/buttonEvents/Effects/Buy";
|
||||
import GenerateButtonInteractionMock from "../../__functions__/discord.js/GenerateButtonInteractionMock";
|
||||
import { ButtonInteraction as ButtonInteractionType } from "../../__types__/discord.js";
|
||||
import AppLogger from "../../../src/client/appLogger";
|
||||
import EffectHelper from "../../../src/helpers/EffectHelper";
|
||||
import EmbedColours from "../../../src/constants/EmbedColours";
|
||||
import User from "../../../src/database/entities/app/User";
|
||||
|
||||
jest.mock("../../../src/client/appLogger");
|
||||
jest.mock("../../../src/helpers/EffectHelper");
|
||||
jest.mock("../../../src/database/entities/app/User");
|
||||
|
||||
let interaction: ButtonInteractionType;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
|
||||
interaction = GenerateButtonInteractionMock();
|
||||
interaction.customId = "effects buy";
|
||||
|
||||
});
|
||||
|
||||
describe("Execute", () => {
|
||||
test("GIVEN subaction is invalid, EXPECT error logged", async () => {
|
||||
// Arrange
|
||||
interaction.customId += " invalid";
|
||||
|
||||
// Act
|
||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
||||
|
||||
// Assert
|
||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy", "Unknown subaction, effects invalid");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Confirm", () => {
|
||||
let user: User;
|
||||
|
||||
beforeEach(() => {
|
||||
interaction.customId += " confirm";
|
||||
|
||||
user = {
|
||||
Currency: 1000,
|
||||
Save: jest.fn(),
|
||||
RemoveCurrency: jest.fn(),
|
||||
} as unknown as User;
|
||||
|
||||
(User.FetchOneById as jest.Mock).mockResolvedValue(user);
|
||||
});
|
||||
|
||||
test("EXPECT success embed generated", async () => {
|
||||
// Assert
|
||||
interaction.customId += " unclaimed 1";
|
||||
|
||||
const embed = {
|
||||
id: "embed",
|
||||
setColor: jest.fn(),
|
||||
setFooter: jest.fn(),
|
||||
};
|
||||
const row = {
|
||||
id: "row",
|
||||
};
|
||||
|
||||
(EffectHelper.GenerateEffectBuyEmbed as jest.Mock).mockResolvedValue({
|
||||
embed,
|
||||
row,
|
||||
});
|
||||
|
||||
// Act
|
||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
||||
|
||||
// Assert
|
||||
expect(interaction.update).toHaveBeenCalledTimes(1);
|
||||
expect(interaction.update).toHaveBeenCalledWith({
|
||||
embeds: [ embed ],
|
||||
components: [ row ],
|
||||
});
|
||||
|
||||
expect(EffectHelper.GenerateEffectBuyEmbed).toHaveBeenCalledTimes(1);
|
||||
expect(EffectHelper.GenerateEffectBuyEmbed).toHaveBeenCalledWith("userId", "unclaimed", 1, true);
|
||||
|
||||
expect(embed.setColor).toHaveBeenCalledTimes(1);
|
||||
expect(embed.setColor).toHaveBeenCalledWith(EmbedColours.Success);
|
||||
|
||||
expect(embed.setFooter).toHaveBeenCalledTimes(1);
|
||||
expect(embed.setFooter).toHaveBeenCalledWith({ text: "Purchased" });
|
||||
|
||||
expect(interaction.reply).not.toHaveBeenCalled();
|
||||
expect(AppLogger.LogError).not.toHaveBeenCalled();
|
||||
|
||||
expect(User.FetchOneById).toHaveBeenCalledTimes(1);
|
||||
expect(User.FetchOneById).toHaveBeenCalledWith(User, "userId");
|
||||
|
||||
expect(user.RemoveCurrency).toHaveBeenCalledTimes(1);
|
||||
expect(user.RemoveCurrency).toHaveBeenCalledWith(100);
|
||||
|
||||
expect(user.Save).toHaveBeenCalledTimes(1);
|
||||
expect(user.Save).toHaveBeenCalledWith(User, user);
|
||||
|
||||
expect(EffectHelper.AddEffectToUserInventory).toHaveBeenCalledTimes(1);
|
||||
expect(EffectHelper.AddEffectToUserInventory).toHaveBeenCalledWith("userId", "unclaimed", 1);
|
||||
});
|
||||
|
||||
test("GIVEN id is not supplied, EXPECT error", async () => {
|
||||
// Act
|
||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
||||
|
||||
// Assert
|
||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy Confirm", "Not enough parameters");
|
||||
|
||||
expect(EffectHelper.GenerateEffectBuyEmbed).not.toHaveBeenCalled();
|
||||
expect(interaction.reply).not.toHaveBeenCalled();
|
||||
expect(interaction.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("GIVEN quantity is not supplied, EXPECT error", async () => {
|
||||
// Assert
|
||||
interaction.customId += " unclaimed";
|
||||
|
||||
// Act
|
||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
||||
|
||||
// Assert
|
||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy Confirm", "Not enough parameters");
|
||||
|
||||
expect(EffectHelper.GenerateEffectBuyEmbed).not.toHaveBeenCalled();
|
||||
expect(interaction.reply).not.toHaveBeenCalled();
|
||||
expect(interaction.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("GIVEN quantity is not a number, EXPECT error", async () => {
|
||||
// Assert
|
||||
interaction.customId += " unclaimed invalid";
|
||||
|
||||
// Act
|
||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
||||
|
||||
// Assert
|
||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy Confirm", "Invalid number");
|
||||
|
||||
expect(EffectHelper.GenerateEffectBuyEmbed).not.toHaveBeenCalled();
|
||||
expect(interaction.reply).not.toHaveBeenCalled();
|
||||
expect(interaction.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("GIVEN quantity is 0, EXPECT error", async () => {
|
||||
// Assert
|
||||
interaction.customId += " unclaimed 0";
|
||||
|
||||
// Act
|
||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
||||
|
||||
// Assert
|
||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy Confirm", "Invalid number");
|
||||
|
||||
expect(EffectHelper.GenerateEffectBuyEmbed).not.toHaveBeenCalled();
|
||||
expect(interaction.reply).not.toHaveBeenCalled();
|
||||
expect(interaction.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("GIVEN user is not found, EXPECT error", async () => {
|
||||
// Assert
|
||||
interaction.customId += " unclaimed 1";
|
||||
|
||||
(User.FetchOneById as jest.Mock).mockResolvedValue(undefined);
|
||||
|
||||
// Act
|
||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
||||
|
||||
// Assert
|
||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy Confirm", "Unable to find user");
|
||||
|
||||
expect(EffectHelper.AddEffectToUserInventory).not.toHaveBeenCalled();
|
||||
|
||||
expect(interaction.update).not.toHaveBeenCalled();
|
||||
expect(interaction.reply).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("GIVEN user does not have enough currency, EXPECT error", async () => {
|
||||
// Assert
|
||||
interaction.customId += " unclaimed 1";
|
||||
|
||||
user.Currency = 0;
|
||||
|
||||
// Act
|
||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
||||
|
||||
// Assert
|
||||
expect(interaction.reply).toHaveBeenCalledTimes(1);
|
||||
expect(interaction.reply).toHaveBeenCalledWith("You don't have enough currency to buy this! You have `0 Currency` and need `100 Currency`!");
|
||||
|
||||
expect(EffectHelper.AddEffectToUserInventory).not.toHaveBeenCalled();
|
||||
|
||||
expect(interaction.update).not.toHaveBeenCalled();
|
||||
|
||||
expect(AppLogger.LogError).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("GIVEN GenerateEffectBuyEmbed returns with a string, EXPECT error replied", async () => {
|
||||
// Assert
|
||||
interaction.customId += " unclaimed 1";
|
||||
|
||||
(EffectHelper.GenerateEffectBuyEmbed as jest.Mock).mockResolvedValue("Test error");
|
||||
|
||||
// Act
|
||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
||||
|
||||
// Assert
|
||||
expect(interaction.reply).toHaveBeenCalledTimes(1);
|
||||
expect(interaction.reply).toHaveBeenCalledWith("Test error");
|
||||
|
||||
expect(EffectHelper.GenerateEffectBuyEmbed).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(interaction.update).not.toHaveBeenCalled();
|
||||
expect(AppLogger.LogError).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Cancel", () => {
|
||||
beforeEach(() => {
|
||||
interaction.customId += " cancel";
|
||||
});
|
||||
|
||||
test("EXPECT embed generated", async () => {
|
||||
// Assert
|
||||
interaction.customId += " unclaimed 1";
|
||||
|
||||
const embed = {
|
||||
id: "embed",
|
||||
setColor: jest.fn(),
|
||||
setFooter: jest.fn(),
|
||||
};
|
||||
const row = {
|
||||
id: "row",
|
||||
};
|
||||
|
||||
(EffectHelper.GenerateEffectBuyEmbed as jest.Mock).mockResolvedValue({
|
||||
embed,
|
||||
row,
|
||||
});
|
||||
|
||||
// Act
|
||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
||||
|
||||
// Assert
|
||||
expect(interaction.update).toHaveBeenCalledTimes(1);
|
||||
expect(interaction.update).toHaveBeenCalledWith({
|
||||
embeds: [ embed ],
|
||||
components: [ row ],
|
||||
});
|
||||
|
||||
expect(EffectHelper.GenerateEffectBuyEmbed).toHaveBeenCalledTimes(1);
|
||||
expect(EffectHelper.GenerateEffectBuyEmbed).toHaveBeenCalledWith("userId", "unclaimed", 1, true);
|
||||
|
||||
expect(embed.setColor).toHaveBeenCalledTimes(1);
|
||||
expect(embed.setColor).toHaveBeenCalledWith(EmbedColours.Error);
|
||||
|
||||
expect(embed.setFooter).toHaveBeenCalledTimes(1);
|
||||
expect(embed.setFooter).toHaveBeenCalledWith({ text: "Cancelled" });
|
||||
|
||||
expect(interaction.reply).not.toHaveBeenCalled();
|
||||
expect(AppLogger.LogError).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("GIVEN id is not supplied, EXPECT error", async () => {
|
||||
// Act
|
||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
||||
|
||||
// Assert
|
||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy Cancel", "Not enough parameters");
|
||||
|
||||
expect(interaction.reply).not.toHaveBeenCalled();
|
||||
expect(interaction.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("GIVEN quantity is not supplied, EXPECT error", async () => {
|
||||
// Assert
|
||||
interaction.customId += " unclaimed";
|
||||
|
||||
// Act
|
||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
||||
|
||||
// Assert
|
||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy Cancel", "Not enough parameters");
|
||||
|
||||
expect(EffectHelper.GenerateEffectBuyEmbed).not.toHaveBeenCalled();
|
||||
expect(interaction.reply).not.toHaveBeenCalled();
|
||||
expect(interaction.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("GIVEN quantity is not a number, EXPECT error", async () => {
|
||||
// Assert
|
||||
interaction.customId += " unclaimed invalid";
|
||||
|
||||
// Act
|
||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
||||
|
||||
// Assert
|
||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy Cancel", "Invalid number");
|
||||
|
||||
expect(EffectHelper.GenerateEffectBuyEmbed).not.toHaveBeenCalled();
|
||||
expect(interaction.reply).not.toHaveBeenCalled();
|
||||
expect(interaction.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("GIVEN quantity is 0, EXPECT error", async () => {
|
||||
// Assert
|
||||
interaction.customId += " unclaimed 0";
|
||||
|
||||
// Act
|
||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
||||
|
||||
// Assert
|
||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy Cancel", "Invalid number");
|
||||
|
||||
expect(EffectHelper.GenerateEffectBuyEmbed).not.toHaveBeenCalled();
|
||||
expect(interaction.reply).not.toHaveBeenCalled();
|
||||
expect(interaction.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("GIVEN GenerateEffectBuyEmbed returns with a string, EXPECT error replied", async () => {
|
||||
// Assert
|
||||
interaction.customId += " unclaimed 1";
|
||||
|
||||
(EffectHelper.GenerateEffectBuyEmbed as jest.Mock).mockResolvedValue("Test error");
|
||||
|
||||
// Act
|
||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
||||
|
||||
// Assert
|
||||
expect(interaction.reply).toHaveBeenCalledTimes(1);
|
||||
expect(interaction.reply).toHaveBeenCalledWith("Test error");
|
||||
|
||||
expect(EffectHelper.GenerateEffectBuyEmbed).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(interaction.update).not.toHaveBeenCalled();
|
||||
expect(AppLogger.LogError).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
50
tests/buttonEvents/Effects/List.test.ts
Normal file
50
tests/buttonEvents/Effects/List.test.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
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";
|
||||
|
||||
jest.mock("../../../src/helpers/EffectHelper");
|
||||
|
||||
let interaction: ReturnType<typeof mock<ButtonInteraction>>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
|
||||
(EffectHelper.GenerateEffectListEmbed as jest.Mock).mockResolvedValue({
|
||||
embed: mock<EmbedBuilder>(),
|
||||
row: mock<ActionRowBuilder<ButtonBuilder>>(),
|
||||
});
|
||||
|
||||
interaction = mock<ButtonInteraction>();
|
||||
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.GenerateEffectListEmbed).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.GenerateEffectListEmbed).toHaveBeenCalledTimes(1);
|
||||
expect(EffectHelper.GenerateEffectListEmbed).toHaveBeenCalledWith("userId", 1);
|
||||
|
||||
expect(interaction.update).toHaveBeenCalledTimes(1);
|
||||
});
|
148
tests/buttonEvents/Effects/Use.test.ts
Normal file
148
tests/buttonEvents/Effects/Use.test.ts
Normal file
|
@ -0,0 +1,148 @@
|
|||
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";
|
||||
import EffectHelper from "../../../src/helpers/EffectHelper";
|
||||
|
||||
jest.mock("../../../src/client/appLogger");
|
||||
jest.mock("../../../src/helpers/EffectHelper");
|
||||
|
||||
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<ButtonInteraction>();
|
||||
interaction.customId = "effects use invalud";
|
||||
|
||||
// Act
|
||||
await Use.Execute(interaction);
|
||||
|
||||
// Assert
|
||||
expect(interaction.reply).not.toHaveBeenCalled();
|
||||
expect(interaction.update).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("UseConfirm", () => {
|
||||
let interaction = mock<ButtonInteraction>();
|
||||
|
||||
beforeEach(() => {
|
||||
interaction = mock<ButtonInteraction>();
|
||||
interaction.customId = "effects use confirm";
|
||||
});
|
||||
|
||||
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: string | MessagePayload | InteractionUpdateOptions) => {
|
||||
updatedWith = opts;
|
||||
|
||||
return mock<InteractionResponse<boolean>>();
|
||||
});
|
||||
|
||||
(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", () => {
|
||||
let interaction = mock<ButtonInteraction>();
|
||||
|
||||
beforeEach(() => {
|
||||
interaction = mock<ButtonInteraction>();
|
||||
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: string | MessagePayload | InteractionUpdateOptions) => {
|
||||
updatedWith = opts;
|
||||
|
||||
return mock<InteractionResponse<boolean>>();
|
||||
});
|
||||
// Act
|
||||
await Use.Execute(interaction);
|
||||
|
||||
// Assert
|
||||
expect(interaction.update).toHaveBeenCalledTimes(1);
|
||||
expect(updatedWith).toMatchSnapshot();
|
||||
});
|
||||
});
|
95
tests/buttonEvents/Effects/__snapshots__/Use.test.ts.snap
Normal file
95
tests/buttonEvents/Effects/__snapshots__/Use.test.ts.snap
Normal file
|
@ -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": "<t:600:f>",
|
||||
},
|
||||
],
|
||||
"title": "Effect Used",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
7
tests/buttonEvents/Reroll.test.ts
Normal file
7
tests/buttonEvents/Reroll.test.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
describe("GIVEN valid conditions", () => {
|
||||
test.todo("EXPECT user.RemoveCurrency to be called");
|
||||
|
||||
test.todo("GIVEN user is saved");
|
||||
});
|
||||
|
||||
test.todo("GIVEN user.RemoveCurrency fails, EXPECT error replied");
|
106
tests/commands/__snapshots__/effects.test.ts.snap
Normal file
106
tests/commands/__snapshots__/effects.test.ts.snap
Normal file
|
@ -0,0 +1,106 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`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,
|
||||
},
|
||||
{
|
||||
"description": "Use an effect in your inventory",
|
||||
"description_localizations": undefined,
|
||||
"name": "use",
|
||||
"name_localizations": undefined,
|
||||
"options": [
|
||||
{
|
||||
"autocomplete": undefined,
|
||||
"choices": [
|
||||
{
|
||||
"name": "Unclaimed Chance Up",
|
||||
"name_localizations": undefined,
|
||||
"value": "unclaimed",
|
||||
},
|
||||
],
|
||||
"description": "The effect id to use",
|
||||
"description_localizations": undefined,
|
||||
"max_length": undefined,
|
||||
"min_length": undefined,
|
||||
"name": "id",
|
||||
"name_localizations": undefined,
|
||||
"required": true,
|
||||
"type": 3,
|
||||
},
|
||||
],
|
||||
"type": 1,
|
||||
},
|
||||
{
|
||||
"description": "Buy more effects",
|
||||
"description_localizations": undefined,
|
||||
"name": "buy",
|
||||
"name_localizations": undefined,
|
||||
"options": [
|
||||
{
|
||||
"autocomplete": undefined,
|
||||
"choices": [
|
||||
{
|
||||
"name": "Unclaimed Chance Up",
|
||||
"name_localizations": undefined,
|
||||
"value": "unclaimed",
|
||||
},
|
||||
],
|
||||
"description": "The effect id to buy",
|
||||
"description_localizations": undefined,
|
||||
"max_length": undefined,
|
||||
"min_length": undefined,
|
||||
"name": "id",
|
||||
"name_localizations": undefined,
|
||||
"required": true,
|
||||
"type": 3,
|
||||
},
|
||||
{
|
||||
"autocomplete": undefined,
|
||||
"choices": undefined,
|
||||
"description": "The amount to buy",
|
||||
"description_localizations": undefined,
|
||||
"max_value": undefined,
|
||||
"min_value": 1,
|
||||
"name": "quantity",
|
||||
"name_localizations": undefined,
|
||||
"required": false,
|
||||
"type": 10,
|
||||
},
|
||||
],
|
||||
"type": 1,
|
||||
},
|
||||
],
|
||||
"type": 1,
|
||||
}
|
||||
`;
|
142
tests/commands/drop.test.ts
Normal file
142
tests/commands/drop.test.ts
Normal file
|
@ -0,0 +1,142 @@
|
|||
import { CommandInteraction } from "discord.js";
|
||||
import Drop from "../../src/commands/drop";
|
||||
import GenerateCommandInteractionMock from "../__functions__/discord.js/GenerateCommandInteractionMock";
|
||||
import { CommandInteraction as CommandInteractionMock } from "../__types__/discord.js";
|
||||
import { CoreClient } from "../../src/client/client";
|
||||
import Config from "../../src/database/entities/app/Config";
|
||||
import User from "../../src/database/entities/app/User";
|
||||
import GetCardsHelper from "../../src/helpers/DropHelpers/GetCardsHelper";
|
||||
import Inventory from "../../src/database/entities/app/Inventory";
|
||||
import DropEmbedHelper from "../../src/helpers/DropHelpers/DropEmbedHelper";
|
||||
import CardConstants from "../../src/constants/CardConstants";
|
||||
import * as uuid from "uuid";
|
||||
|
||||
jest.mock("../../src/database/entities/app/Config");
|
||||
jest.mock("../../src/database/entities/app/User");
|
||||
jest.mock("../../src/helpers/DropHelpers/GetCardsHelper");
|
||||
jest.mock("../../src/database/entities/app/Inventory");
|
||||
jest.mock("../../src/helpers/DropHelpers/DropEmbedHelper");
|
||||
|
||||
jest.mock("uuid");
|
||||
|
||||
beforeEach(() => {
|
||||
(Config.GetValue as jest.Mock).mockResolvedValue("false");
|
||||
});
|
||||
|
||||
describe("execute", () => {
|
||||
describe("GIVEN user is in the database", () => {
|
||||
let interaction: CommandInteractionMock;
|
||||
let user: User;
|
||||
const randomCard = {
|
||||
card: {
|
||||
id: "cardId",
|
||||
path: "https://google.com/",
|
||||
}
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
// Arrange
|
||||
CoreClient.AllowDrops = true;
|
||||
|
||||
interaction = GenerateCommandInteractionMock();
|
||||
|
||||
user = {
|
||||
Currency: 500,
|
||||
RemoveCurrency: jest.fn().mockReturnValue(true),
|
||||
Save: jest.fn(),
|
||||
} as unknown as User;
|
||||
|
||||
(User.FetchOneById as jest.Mock).mockResolvedValue(user);
|
||||
(GetCardsHelper.FetchCard as jest.Mock).mockResolvedValue(randomCard);
|
||||
(Inventory.FetchOneByCardNumberAndUserId as jest.Mock).mockResolvedValue({
|
||||
Quantity: 1,
|
||||
});
|
||||
(DropEmbedHelper.GenerateDropEmbed as jest.Mock).mockReturnValue({
|
||||
type: "Embed",
|
||||
});
|
||||
(DropEmbedHelper.GenerateDropButtons as jest.Mock).mockReturnValue({
|
||||
type: "Button",
|
||||
});
|
||||
|
||||
(uuid.v4 as jest.Mock).mockReturnValue("uuid");
|
||||
|
||||
// Act
|
||||
const drop = new Drop();
|
||||
await drop.execute(interaction as unknown as CommandInteraction);
|
||||
});
|
||||
|
||||
test("EXPECT user to be fetched", () => {
|
||||
expect(User.FetchOneById).toHaveBeenCalledTimes(1);
|
||||
expect(User.FetchOneById).toHaveBeenCalledWith(User, "userId");
|
||||
});
|
||||
|
||||
test("EXPECT user.RemoveCurrency to be called", () => {
|
||||
expect(user.RemoveCurrency).toHaveBeenCalledTimes(1);
|
||||
expect(user.RemoveCurrency).toHaveBeenCalledWith(CardConstants.ClaimCost);
|
||||
});
|
||||
|
||||
test("EXPECT user to be saved", () => {
|
||||
expect(user.Save).toHaveBeenCalledTimes(1);
|
||||
expect(user.Save).toHaveBeenCalledWith(User, user);
|
||||
});
|
||||
|
||||
test("EXPECT random card to be fetched", () => {
|
||||
expect(GetCardsHelper.FetchCard).toHaveBeenCalledTimes(1);
|
||||
expect(GetCardsHelper.FetchCard).toHaveBeenCalledWith("userId");
|
||||
});
|
||||
|
||||
test("EXPECT interaction to be deferred", () => {
|
||||
expect(interaction.deferReply).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("EXPECT Inventory.FetchOneByCardNumberAndUserId to be called", () => {
|
||||
expect(Inventory.FetchOneByCardNumberAndUserId).toHaveBeenCalledTimes(1);
|
||||
expect(Inventory.FetchOneByCardNumberAndUserId).toHaveBeenCalledWith("userId", "cardId");
|
||||
});
|
||||
|
||||
test("EXPECT DropEmbedHelper.GenerateDropEmbed to be called", () => {
|
||||
expect(DropEmbedHelper.GenerateDropEmbed).toHaveBeenCalledTimes(1);
|
||||
expect(DropEmbedHelper.GenerateDropEmbed).toHaveBeenCalledWith(randomCard, 1, "", undefined, 500);
|
||||
});
|
||||
|
||||
test("EXPECT DropEmbedHelper.GenerateDropButtons to be called", () => {
|
||||
expect(DropEmbedHelper.GenerateDropButtons).toHaveBeenCalledTimes(1);
|
||||
expect(DropEmbedHelper.GenerateDropButtons).toHaveBeenCalledWith(randomCard, "uuid", "userId");
|
||||
});
|
||||
|
||||
test("EXPECT interaction to be edited", () => {
|
||||
expect(interaction.editReply).toHaveBeenCalledTimes(1);
|
||||
expect(interaction.editReply).toHaveBeenCalledWith({
|
||||
embeds: [ { type: "Embed" } ],
|
||||
files: [],
|
||||
components: [ { type: "Button" } ],
|
||||
});
|
||||
});
|
||||
|
||||
describe("AND randomCard path is not a url", () => {
|
||||
test.todo("EXPECT image read from file system");
|
||||
|
||||
test.todo("EXPECT files on the embed to contain the image as an attachment");
|
||||
});
|
||||
});
|
||||
|
||||
describe("GIVEN user is not in the database", () => {
|
||||
test.todo("EXPECT new user to be created");
|
||||
});
|
||||
|
||||
describe("GIVEN user.RemoveCurrency fails", () => {
|
||||
test.todo("EXPECT error replied");
|
||||
});
|
||||
|
||||
describe("GIVEN randomCard returns null", () => {
|
||||
test.todo("EXPECT error logged");
|
||||
|
||||
test.todo("EXPECT error replied");
|
||||
});
|
||||
|
||||
describe("GIVEN the code throws an error", () => {
|
||||
test.todo("EXPECT error logged");
|
||||
|
||||
test.todo("EXPECT interaction edited with error");
|
||||
});
|
||||
});
|
105
tests/commands/effects.test.ts
Normal file
105
tests/commands/effects.test.ts
Normal file
|
@ -0,0 +1,105 @@
|
|||
import Effects from "../../src/commands/effects";
|
||||
import List from "../../src/commands/effects/List";
|
||||
import Use from "../../src/commands/effects/Use";
|
||||
import Buy from "../../src/commands/effects/Buy";
|
||||
import AppLogger from "../../src/client/appLogger";
|
||||
import GenerateCommandInteractionMock from "../__functions__/discord.js/GenerateCommandInteractionMock";
|
||||
import { CommandInteraction } from "discord.js";
|
||||
|
||||
jest.mock("../../src/commands/effects/List");
|
||||
jest.mock("../../src/commands/effects/Use");
|
||||
jest.mock("../../src/commands/effects/Buy");
|
||||
jest.mock("../../src/client/appLogger");
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
test("EXPECT CommandBuilder to be defined", async () => {
|
||||
// Act
|
||||
const effects = new Effects();
|
||||
|
||||
// Assert
|
||||
expect(effects.CommandBuilder).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("execute", () => {
|
||||
test("GIVEN interaction subcommand is list, EXPECT buy function called", async () => {
|
||||
// Arrange
|
||||
const interaction = GenerateCommandInteractionMock({
|
||||
subcommand: "list",
|
||||
});
|
||||
|
||||
// Act
|
||||
const effects = new Effects();
|
||||
await effects.execute(interaction as unknown as CommandInteraction);
|
||||
|
||||
// Assert
|
||||
expect(List).toHaveBeenCalledTimes(1);
|
||||
expect(List).toHaveBeenCalledWith(interaction);
|
||||
|
||||
expect(Use).not.toHaveBeenCalled();
|
||||
expect(Buy).not.toHaveBeenCalled();
|
||||
|
||||
expect(AppLogger.LogError).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("GIVEN interaction subcommand is use, EXPECT buy function called", async () => {
|
||||
// Arrange
|
||||
const interaction = GenerateCommandInteractionMock({
|
||||
subcommand: "use",
|
||||
});
|
||||
|
||||
// Act
|
||||
const effects = new Effects();
|
||||
await effects.execute(interaction as unknown as CommandInteraction);
|
||||
|
||||
// Assert
|
||||
expect(Use).toHaveBeenCalledTimes(1);
|
||||
expect(Use).toHaveBeenCalledWith(interaction);
|
||||
|
||||
expect(List).not.toHaveBeenCalled();
|
||||
expect(Buy).not.toHaveBeenCalled();
|
||||
|
||||
expect(AppLogger.LogError).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("GIVEN interaction subcommand is buy, EXPECT buy function called", async () => {
|
||||
// Arrange
|
||||
const interaction = GenerateCommandInteractionMock({
|
||||
subcommand: "buy",
|
||||
});
|
||||
|
||||
// Act
|
||||
const effects = new Effects();
|
||||
await effects.execute(interaction as unknown as CommandInteraction);
|
||||
|
||||
// Assert
|
||||
expect(Buy).toHaveBeenCalledTimes(1);
|
||||
expect(Buy).toHaveBeenCalledWith(interaction);
|
||||
|
||||
expect(List).not.toHaveBeenCalled();
|
||||
expect(Use).not.toHaveBeenCalled();
|
||||
|
||||
expect(AppLogger.LogError).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("GIVEN interaction subcommand is invalid, EXPECT error logged", async () => {
|
||||
// Arrange
|
||||
const interaction = GenerateCommandInteractionMock({
|
||||
subcommand: "invalid",
|
||||
});
|
||||
|
||||
// Act
|
||||
const effects = new Effects();
|
||||
await effects.execute(interaction as unknown as CommandInteraction);
|
||||
|
||||
// Assert
|
||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Commands/Effects", "Invalid subcommand: invalid");
|
||||
|
||||
expect(List).not.toHaveBeenCalled();
|
||||
expect(Use).not.toHaveBeenCalled();
|
||||
expect(Buy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
9
tests/commands/effects/Buy.test.ts
Normal file
9
tests/commands/effects/Buy.test.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
jest.mock("../../../src/helpers/EffectHelper");
|
||||
|
||||
describe("Buy", () => {
|
||||
test.todo("GIVEN result returns a string, EXPECT interaction replied with string");
|
||||
|
||||
test.todo("GIVEN result returns an embed, EXPECT interaction replied with embed and row");
|
||||
|
||||
test.todo("GIVEN quantity option is not supplied, EXPECT quantity to default to 1");
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
3
tests/helpers/DropHelpers/DropEmbedHelper.test.ts
Normal file
3
tests/helpers/DropHelpers/DropEmbedHelper.test.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
describe("GenerateDropButtons", () => {
|
||||
test.todo("EXPECT row to be returned");
|
||||
});
|
68
tests/helpers/DropHelpers/GetCardsHelper.test.ts
Normal file
68
tests/helpers/DropHelpers/GetCardsHelper.test.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
import GetCardsHelper from "../../../src/helpers/DropHelpers/GetCardsHelper";
|
||||
import EffectHelper from "../../../src/helpers/EffectHelper";
|
||||
import GetUnclaimedCardsHelper from "../../../src/helpers/DropHelpers/GetUnclaimedCardsHelper";
|
||||
import CardConstants from "../../../src/constants/CardConstants";
|
||||
|
||||
jest.mock("../../../src/helpers/EffectHelper");
|
||||
jest.mock("../../../src/helpers/DropHelpers/GetUnclaimedCardsHelper");
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe("FetchCard", () => {
|
||||
test("GIVEN user has the unclaimed effect AND unused chance is within constraint, EXPECT unclaimed card returned", async () => {
|
||||
// Arrange
|
||||
(EffectHelper.HasEffect as jest.Mock).mockResolvedValue(true);
|
||||
GetCardsHelper.GetRandomCard = jest.fn();
|
||||
Math.random = jest.fn().mockReturnValue(CardConstants.UnusedChanceUpChance - 0.1);
|
||||
|
||||
// Act
|
||||
await GetCardsHelper.FetchCard("userId");
|
||||
|
||||
// Assert
|
||||
expect(EffectHelper.HasEffect).toHaveBeenCalledTimes(1);
|
||||
expect(EffectHelper.HasEffect).toHaveBeenCalledWith("userId", "unclaimed");
|
||||
|
||||
expect(GetUnclaimedCardsHelper.GetRandomCardUnclaimed).toHaveBeenCalledTimes(1);
|
||||
expect(GetUnclaimedCardsHelper.GetRandomCardUnclaimed).toHaveBeenCalledWith("userId");
|
||||
|
||||
expect(GetCardsHelper.GetRandomCard).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("GIVEN user has unclaimed effect AND unused chance is NOT within constraint, EXPECT random card returned", async () => {
|
||||
// Arrange
|
||||
(EffectHelper.HasEffect as jest.Mock).mockResolvedValue(true);
|
||||
GetCardsHelper.GetRandomCard = jest.fn();
|
||||
Math.random = jest.fn().mockReturnValue(CardConstants.UnusedChanceUpChance + 0.1);
|
||||
|
||||
// Act
|
||||
await GetCardsHelper.FetchCard("userId");
|
||||
|
||||
// Assert
|
||||
expect(EffectHelper.HasEffect).toHaveBeenCalledTimes(1);
|
||||
expect(EffectHelper.HasEffect).toHaveBeenCalledWith("userId", "unclaimed");
|
||||
|
||||
expect(GetCardsHelper.GetRandomCard).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(GetUnclaimedCardsHelper.GetRandomCardUnclaimed).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("GIVEN user does NOT have unclaimed effect, EXPECT random card returned", async () => {
|
||||
// Arrange
|
||||
(EffectHelper.HasEffect as jest.Mock).mockResolvedValue(false);
|
||||
GetCardsHelper.GetRandomCard = jest.fn();
|
||||
Math.random = jest.fn().mockReturnValue(CardConstants.UnusedChanceUpChance + 0.1);
|
||||
|
||||
// Act
|
||||
await GetCardsHelper.FetchCard("userId");
|
||||
|
||||
// Assert
|
||||
expect(EffectHelper.HasEffect).toHaveBeenCalledTimes(1);
|
||||
expect(EffectHelper.HasEffect).toHaveBeenCalledWith("userId", "unclaimed");
|
||||
|
||||
expect(GetCardsHelper.GetRandomCard).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(GetUnclaimedCardsHelper.GetRandomCardUnclaimed).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -1,281 +1,127 @@
|
|||
import UserEffect from "../../src/database/entities/app/UserEffect";
|
||||
import EffectHelper from "../../src/helpers/EffectHelper";
|
||||
import UserEffect from "../../src/database/entities/app/UserEffect";
|
||||
|
||||
describe("AddEffectToUserInventory", () => {
|
||||
describe("GIVEN effect is in database", () => {
|
||||
const effectMock = {
|
||||
AddUnused: jest.fn(),
|
||||
Save: jest.fn(),
|
||||
};
|
||||
jest.mock("../../src/database/entities/app/UserEffect");
|
||||
|
||||
beforeAll(async () => {
|
||||
UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(effectMock);
|
||||
describe("GenerateEffectListEmbed", () => {
|
||||
test("GIVEN user has an effect, EXPECT detailed embed to be returned", async () => {
|
||||
// Arrange
|
||||
(UserEffect.FetchAllByUserIdPaginated as jest.Mock).mockResolvedValue([
|
||||
[
|
||||
{
|
||||
Name: "unclaimed",
|
||||
Unused: 1,
|
||||
}
|
||||
],
|
||||
1,
|
||||
]);
|
||||
|
||||
await EffectHelper.AddEffectToUserInventory("userId", "name", 1);
|
||||
});
|
||||
// Act
|
||||
const result = await EffectHelper.GenerateEffectListEmbed("userId", 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);
|
||||
});
|
||||
// Assert
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("GIVEN effect is not in database", () => {
|
||||
beforeAll(async () => {
|
||||
UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(null);
|
||||
UserEffect.prototype.Save = jest.fn();
|
||||
test("GIVEN user has more than 1 page of effects, EXPECT pagination enabled", async () => {
|
||||
const effects: {
|
||||
Name: string,
|
||||
Unused: number,
|
||||
}[] = [];
|
||||
|
||||
await EffectHelper.AddEffectToUserInventory("userId", "name", 1);
|
||||
for (let i = 0; i < 15; i++) {
|
||||
effects.push({
|
||||
Name: "unclaimed",
|
||||
Unused: 1,
|
||||
});
|
||||
}
|
||||
|
||||
// Arrange
|
||||
(UserEffect.FetchAllByUserIdPaginated as jest.Mock).mockResolvedValue([
|
||||
effects,
|
||||
15,
|
||||
]);
|
||||
|
||||
// Act
|
||||
const result = await EffectHelper.GenerateEffectListEmbed("userId", 1);
|
||||
|
||||
// Assert
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("GIVEN user is on a page other than 1, EXPECT pagination enabled", async () => {
|
||||
const effects: {
|
||||
Name: string,
|
||||
Unused: number,
|
||||
}[] = [];
|
||||
|
||||
for (let i = 0; i < 15; i++) {
|
||||
effects.push({
|
||||
Name: "unclaimed",
|
||||
Unused: 1,
|
||||
});
|
||||
}
|
||||
|
||||
// Arrange
|
||||
(UserEffect.FetchAllByUserIdPaginated as jest.Mock).mockResolvedValue([
|
||||
effects,
|
||||
15,
|
||||
]);
|
||||
|
||||
// Act
|
||||
const result = await EffectHelper.GenerateEffectListEmbed("userId", 2);
|
||||
|
||||
// Assert
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("GIVEN user does NOT have an effect, EXPECT empty embed to be returned", async () => {
|
||||
// Arrange
|
||||
(UserEffect.FetchAllByUserIdPaginated as jest.Mock).mockResolvedValue([
|
||||
[],
|
||||
0,
|
||||
]);
|
||||
|
||||
// Act
|
||||
const result = await EffectHelper.GenerateEffectListEmbed("userId", 1);
|
||||
|
||||
// Assert
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("GIVEN there is an active effect, EXPECT field added", async () => {
|
||||
// Arrange
|
||||
(UserEffect.FetchAllByUserIdPaginated as jest.Mock).mockResolvedValue([
|
||||
[
|
||||
{
|
||||
Name: "unclaimed",
|
||||
Unused: 1,
|
||||
}
|
||||
],
|
||||
1,
|
||||
]);
|
||||
|
||||
(UserEffect.FetchActiveEffectByUserId as jest.Mock).mockResolvedValue({
|
||||
Name: "unclaimed",
|
||||
WhenExpires: new Date(1738174052),
|
||||
});
|
||||
|
||||
test("EXPECT effect to be saved", () => {
|
||||
expect(UserEffect.prototype.Save).toHaveBeenCalledTimes(1);
|
||||
expect(UserEffect.prototype.Save).toHaveBeenCalledWith(UserEffect, expect.any(UserEffect));
|
||||
});
|
||||
// Act
|
||||
const result = await EffectHelper.GenerateEffectListEmbed("userId", 1);
|
||||
|
||||
// Assert
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("UseEffect", () => {
|
||||
describe("GIVEN effect is in database", () => {
|
||||
describe("GIVEN now is before effect.WhenExpires", () => {
|
||||
let result: boolean | undefined;
|
||||
describe("GenerateEffectBuyEmbed", () => {
|
||||
test.todo("GIVEN Effect Details are not found, EXPECT error");
|
||||
|
||||
// nowMock < whenExpires
|
||||
const nowMock = new Date(2024, 11, 3, 13, 30);
|
||||
const whenExpires = new Date(2024, 11, 3, 14, 0);
|
||||
test.todo("GIVEN user is not in database, EXPECT blank user created");
|
||||
|
||||
const userEffect = {
|
||||
Unused: 1,
|
||||
WhenExpires: whenExpires,
|
||||
};
|
||||
test.todo("GIVEN user does not have enough currency, EXPECT error");
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.setSystemTime(nowMock);
|
||||
test.todo("GIVEN user does have enough currency, EXPECT embed returned");
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
test.todo("GIVEN disabled boolean is true, EXPECT buttons to be disabled");
|
||||
});
|
38
tests/helpers/TimeLengthInput.test.ts
Normal file
38
tests/helpers/TimeLengthInput.test.ts
Normal file
|
@ -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");
|
||||
});
|
||||
});
|
216
tests/helpers/__snapshots__/EffectHelper.test.ts.snap
Normal file
216
tests/helpers/__snapshots__/EffectHelper.test.ts.snap
Normal file
|
@ -0,0 +1,216 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`GenerateEffectListEmbed GIVEN there is an active effect, EXPECT field added 1`] = `
|
||||
{
|
||||
"embed": {
|
||||
"color": 3166394,
|
||||
"description": "Unclaimed Chance Up x1",
|
||||
"fields": [
|
||||
{
|
||||
"inline": true,
|
||||
"name": "Active",
|
||||
"value": "Unclaimed Chance Up",
|
||||
},
|
||||
{
|
||||
"inline": true,
|
||||
"name": "Expires",
|
||||
"value": "<t:1738174>",
|
||||
},
|
||||
],
|
||||
"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[`GenerateEffectListEmbed GIVEN user does NOT have an effect, EXPECT empty embed to be 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,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`GenerateEffectListEmbed GIVEN user has an effect, EXPECT detailed embed to be returned 1`] = `
|
||||
{
|
||||
"embed": {
|
||||
"color": 3166394,
|
||||
"description": "Unclaimed Chance Up 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[`GenerateEffectListEmbed GIVEN user has more than 1 page of effects, EXPECT pagination enabled 1`] = `
|
||||
{
|
||||
"embed": {
|
||||
"color": 3166394,
|
||||
"description": "Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1",
|
||||
"footer": {
|
||||
"icon_url": undefined,
|
||||
"text": "Page 1 of 2",
|
||||
},
|
||||
"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": false,
|
||||
"emoji": undefined,
|
||||
"label": "Next",
|
||||
"style": 1,
|
||||
"type": 2,
|
||||
},
|
||||
],
|
||||
"type": 1,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`GenerateEffectListEmbed GIVEN user is on a page other than 1, EXPECT pagination enabled 1`] = `
|
||||
{
|
||||
"embed": {
|
||||
"color": 3166394,
|
||||
"description": "Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1
|
||||
Unclaimed Chance Up x1",
|
||||
"footer": {
|
||||
"icon_url": undefined,
|
||||
"text": "Page 2 of 2",
|
||||
},
|
||||
"title": "Effects",
|
||||
},
|
||||
"row": {
|
||||
"components": [
|
||||
{
|
||||
"custom_id": "effects list 1",
|
||||
"disabled": false,
|
||||
"emoji": undefined,
|
||||
"label": "Previous",
|
||||
"style": 1,
|
||||
"type": 2,
|
||||
},
|
||||
{
|
||||
"custom_id": "effects list 3",
|
||||
"disabled": true,
|
||||
"emoji": undefined,
|
||||
"label": "Next",
|
||||
"style": 1,
|
||||
"type": 2,
|
||||
},
|
||||
],
|
||||
"type": 1,
|
||||
},
|
||||
}
|
||||
`;
|
7
tests/timers/PurgeClaims.test.ts
Normal file
7
tests/timers/PurgeClaims.test.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
describe("PurgeClaims", () => {
|
||||
test.todo("EXPECT claims to be fetched");
|
||||
|
||||
test.todo("EXPECT Claim.RemoveMany to remove the claims older than 2 minutes");
|
||||
|
||||
test.todo("EXPECT info logged");
|
||||
});
|
155
yarn.lock
155
yarn.lock
|
@ -1591,6 +1591,15 @@ await-to-js@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/await-to-js/-/await-to-js-3.0.0.tgz#70929994185616f4675a91af6167eb61cc92868f"
|
||||
integrity sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==
|
||||
|
||||
axios@^1.8.4:
|
||||
version "1.8.4"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.4.tgz#78990bb4bc63d2cae072952d374835950a82f447"
|
||||
integrity sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==
|
||||
dependencies:
|
||||
follow-redirects "^1.15.6"
|
||||
form-data "^4.0.0"
|
||||
proxy-from-env "^1.1.0"
|
||||
|
||||
babel-jest@^29.7.0:
|
||||
version "29.7.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5"
|
||||
|
@ -1794,6 +1803,14 @@ bytes@3.1.2:
|
|||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
|
||||
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
|
||||
|
||||
call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
|
||||
integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
|
||||
dependencies:
|
||||
es-errors "^1.3.0"
|
||||
function-bind "^1.1.2"
|
||||
|
||||
call-bind@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
|
||||
|
@ -2378,6 +2395,15 @@ dotenv@^16.0.0, dotenv@^16.0.3:
|
|||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
|
||||
integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
|
||||
|
||||
dunder-proto@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
|
||||
integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
|
||||
dependencies:
|
||||
call-bind-apply-helpers "^1.0.1"
|
||||
es-errors "^1.3.0"
|
||||
gopd "^1.2.0"
|
||||
|
||||
eastasianwidth@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
|
||||
|
@ -2454,12 +2480,39 @@ es-define-property@^1.0.0:
|
|||
dependencies:
|
||||
get-intrinsic "^1.2.4"
|
||||
|
||||
es-define-property@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
|
||||
integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
|
||||
|
||||
es-errors@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
|
||||
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
|
||||
|
||||
escalade@^3.1.1, escalade@^3.2.0:
|
||||
es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1"
|
||||
integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
|
||||
dependencies:
|
||||
es-errors "^1.3.0"
|
||||
|
||||
es-set-tostringtag@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d"
|
||||
integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==
|
||||
dependencies:
|
||||
es-errors "^1.3.0"
|
||||
get-intrinsic "^1.2.6"
|
||||
has-tostringtag "^1.0.2"
|
||||
hasown "^2.0.2"
|
||||
|
||||
escalade@^3.1.1:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27"
|
||||
integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==
|
||||
|
||||
escalade@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
|
||||
integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
|
||||
|
@ -2874,6 +2927,11 @@ fn.name@1.x.x:
|
|||
resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
|
||||
integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==
|
||||
|
||||
follow-redirects@^1.15.6:
|
||||
version "1.15.9"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
|
||||
integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
|
||||
|
||||
foreground-child@^3.1.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77"
|
||||
|
@ -2891,6 +2949,16 @@ form-data@^3.0.0:
|
|||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.2.tgz#35cabbdd30c3ce73deb2c42d3c8d3ed9ca51794c"
|
||||
integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
es-set-tostringtag "^2.1.0"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
formidable@^1.2.1:
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.6.tgz#d2a51d60162bbc9b4a055d8457a7c75315d1a168"
|
||||
|
@ -2974,11 +3042,35 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
|
|||
has-symbols "^1.0.3"
|
||||
hasown "^2.0.0"
|
||||
|
||||
get-intrinsic@^1.2.6:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
|
||||
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
|
||||
dependencies:
|
||||
call-bind-apply-helpers "^1.0.2"
|
||||
es-define-property "^1.0.1"
|
||||
es-errors "^1.3.0"
|
||||
es-object-atoms "^1.1.1"
|
||||
function-bind "^1.1.2"
|
||||
get-proto "^1.0.1"
|
||||
gopd "^1.2.0"
|
||||
has-symbols "^1.1.0"
|
||||
hasown "^2.0.2"
|
||||
math-intrinsics "^1.1.0"
|
||||
|
||||
get-package-type@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
|
||||
integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
|
||||
|
||||
get-proto@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
|
||||
integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
|
||||
dependencies:
|
||||
dunder-proto "^1.0.1"
|
||||
es-object-atoms "^1.0.0"
|
||||
|
||||
get-stream@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
|
||||
|
@ -3087,6 +3179,11 @@ gopd@^1.0.1:
|
|||
dependencies:
|
||||
get-intrinsic "^1.1.3"
|
||||
|
||||
gopd@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
|
||||
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
|
||||
|
||||
graceful-fs@4.2.10:
|
||||
version "4.2.10"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
|
||||
|
@ -3136,6 +3233,18 @@ has-symbols@^1.0.3:
|
|||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
|
||||
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
|
||||
|
||||
has-symbols@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
|
||||
integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
|
||||
|
||||
has-tostringtag@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc"
|
||||
integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==
|
||||
dependencies:
|
||||
has-symbols "^1.0.3"
|
||||
|
||||
has-unicode@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
|
||||
|
@ -4328,6 +4437,11 @@ makeerror@1.0.12:
|
|||
dependencies:
|
||||
tmpl "1.0.5"
|
||||
|
||||
math-intrinsics@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
|
||||
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
|
||||
|
||||
media-typer@0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||
|
@ -4418,6 +4532,13 @@ mimic-response@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43"
|
||||
integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
|
||||
|
||||
minimatch@9.0.5, minimatch@^9.0.0, minimatch@^9.0.4:
|
||||
version "9.0.5"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5"
|
||||
integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimatch@^10.0.0:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b"
|
||||
|
@ -4439,13 +4560,6 @@ minimatch@^5.0.1:
|
|||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimatch@^9.0.0, minimatch@^9.0.4:
|
||||
version "9.0.5"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5"
|
||||
integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimist@^1.2.0:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||
|
@ -5007,7 +5121,12 @@ peek-readable@^4.1.0:
|
|||
resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.1.0.tgz#4ece1111bf5c2ad8867c314c81356847e8a62e72"
|
||||
integrity sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==
|
||||
|
||||
picocolors@^1.0.0, picocolors@^1.1.0:
|
||||
picocolors@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1"
|
||||
integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==
|
||||
|
||||
picocolors@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
|
||||
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
|
||||
|
@ -5098,6 +5217,11 @@ proxy-addr@~2.0.7:
|
|||
forwarded "0.2.0"
|
||||
ipaddr.js "1.9.1"
|
||||
|
||||
proxy-from-env@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||
|
||||
punycode@^2.1.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
|
||||
|
@ -5997,7 +6121,12 @@ type-fest@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.13.1.tgz#bb744c1f0678bea7543a2d1ec24e83e68e8c8706"
|
||||
integrity sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==
|
||||
|
||||
type-fest@^4.18.2, type-fest@^4.21.0, type-fest@^4.6.0, type-fest@^4.7.1:
|
||||
type-fest@^4.18.2:
|
||||
version "4.40.0"
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.40.0.tgz#62bc09caccb99a75e1ad6b9b4653e8805e5e1eee"
|
||||
integrity sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw==
|
||||
|
||||
type-fest@^4.21.0, type-fest@^4.6.0, type-fest@^4.7.1:
|
||||
version "4.26.1"
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.26.1.tgz#a4a17fa314f976dd3e6d6675ef6c775c16d7955e"
|
||||
integrity sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==
|
||||
|
@ -6412,6 +6541,6 @@ yoctocolors-cjs@^2.1.2:
|
|||
integrity sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==
|
||||
|
||||
zod@^3.23.8:
|
||||
version "3.23.8"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"
|
||||
integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==
|
||||
version "3.24.3"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.3.tgz#1f40f750a05e477396da64438e0e1c0995dafd87"
|
||||
integrity sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue