diff --git a/package.json b/package.json index 5a0ef42..b8c7536 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "card-drop", - "version": "0.9.0", + "version": "0.9.2", "main": "./dist/bot.js", "typings": "./dist", "scripts": { diff --git a/src/buttonEvents/Multidrop.ts b/src/buttonEvents/Multidrop.ts index e6ea7c2..e88d6ef 100644 --- a/src/buttonEvents/Multidrop.ts +++ b/src/buttonEvents/Multidrop.ts @@ -98,10 +98,18 @@ export default class Multidrop extends ButtonEvent { await interaction.deferUpdate(); 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 = ""; + + 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 attachment = new AttachmentBuilder(image, { name: imageFileName }); const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id); const quantityClaimed = inventory ? inventory.Quantity : 0; @@ -112,7 +120,7 @@ export default class Multidrop extends ButtonEvent { await interaction.editReply({ embeds: [ embed ], - files: [ attachment ], + files: files, components: [ row ], }); } catch (e) { diff --git a/src/buttonEvents/Reroll.ts b/src/buttonEvents/Reroll.ts index 6d97024..6ad855e 100644 --- a/src/buttonEvents/Reroll.ts +++ b/src/buttonEvents/Reroll.ts @@ -11,6 +11,7 @@ import User from "../database/entities/app/User"; import CardConstants from "../constants/CardConstants"; import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper"; import DropEmbedHelper from "../helpers/DropHelpers/DropEmbedHelper"; +import {DropResult} from "../contracts/SeriesMetadata"; export default class Reroll extends ButtonEvent { public override async execute(interaction: ButtonInteraction) { @@ -32,7 +33,7 @@ export default class Reroll extends ButtonEvent { user = new User(interaction.user.id, CardConstants.StartingCurrency); await user.Save(User, user); - AppLogger.LogInfo("Commands/Drop", `New user (${interaction.user.id}) saved to the database`); + AppLogger.LogInfo("Button/Reroll", `New user (${interaction.user.id}) saved to the database`); } if (!user.RemoveCurrency(CardConstants.ClaimCost)) { @@ -40,9 +41,15 @@ export default class Reroll extends ButtonEvent { return; } - await user.Save(User, user); + let randomCard: DropResult | undefined; - const randomCard = await GetCardsHelper.FetchCard(interaction.user.id); + try { + randomCard = await GetCardsHelper.FetchCard(interaction.user.id); + } catch (e) { + AppLogger.CatchError("Button/Reroll", e); + + await interaction.reply("Unable to fetch card, please try again."); + } if (!randomCard) { await interaction.reply("Unable to fetch card, please try again."); @@ -75,6 +82,8 @@ export default class Reroll extends ButtonEvent { const row = DropEmbedHelper.GenerateDropButtons(randomCard, claimId, interaction.user.id); + await user.Save(User, user); + await interaction.editReply({ embeds: [ embed ], files: files, diff --git a/src/buttonEvents/Sacrifice.ts b/src/buttonEvents/Sacrifice.ts index de0bb77..1626cf6 100644 --- a/src/buttonEvents/Sacrifice.ts +++ b/src/buttonEvents/Sacrifice.ts @@ -5,6 +5,7 @@ import { CardRarityToString, GetSacrificeAmount } from "../constants/CardRarity" import EmbedColours from "../constants/EmbedColours"; import User from "../database/entities/app/User"; import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper"; +import CardConstants from "../constants/CardConstants"; export default class Sacrifice extends ButtonEvent { public override async execute(interaction: ButtonInteraction) { @@ -17,6 +18,8 @@ export default class Sacrifice extends ButtonEvent { case "cancel": await this.cancel(interaction); break; + case "give": + await this.give(interaction); } } @@ -168,4 +171,77 @@ export default class Sacrifice extends ButtonEvent { components: [ row ], }); } + + private async give(interaction: ButtonInteraction) { + const userId = interaction.customId.split(" ")[2]; + const cardNumber = interaction.customId.split(" ")[3]; + const quantity = Number(interaction.customId.split(" ")[4]) || 1; + + if (userId != interaction.user.id) { + await interaction.reply("Only the user who created this sacrifice can confirm it."); + return; + } + + const cardData = GetCardsHelper.GetCardByCardNumber(cardNumber); + + if (!cardData) { + await interaction.reply("Unable to find card in the database."); + return; + } + + let user = await User.FetchOneById(User, userId); + + if (!user) { + user = new User(userId, CardConstants.StartingCurrency); + } + + const cardInInventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber); + let cardQuantity = 0; + + if (cardInInventory) { + cardQuantity = cardInInventory.Quantity; + } + + const cardValue = GetSacrificeAmount(cardData.card.type) * quantity; + const cardRarityString = CardRarityToString(cardData.card.type); + + user.AddCurrency(cardValue); + + await user.Save(User, user); + + const description = [ + `Card: ${cardData.card.name}`, + `Series: ${cardData.series.name}`, + `Rarity: ${cardRarityString}`, + `Quantity Owned: ${cardQuantity}`, + `Quantity To Sacrifice: ${quantity}`, + `Sacrifice Amount: ${cardValue}`, + ]; + + const embed = new EmbedBuilder() + .setTitle("Card Sacrificed") + .setDescription(description.join("\n")) + .setColor(EmbedColours.Green) + .setFooter({ text: `${interaction.user.username}` }); + + const row = new ActionRowBuilder() + .addComponents([ + new ButtonBuilder() + .setCustomId(`sacrifice confirm ${interaction.user.id} ${cardNumber}`) + .setLabel("Confirm") + .setStyle(ButtonStyle.Success) + .setDisabled(true), + new ButtonBuilder() + .setCustomId("sacrifice cancel") + .setLabel("Cancel") + .setStyle(ButtonStyle.Secondary) + .setDisabled(true), + ]); + + await interaction.update({ + embeds: [ embed ], + components: [ row ], + attachments: [], + }); + } } \ No newline at end of file diff --git a/src/commands/drop.ts b/src/commands/drop.ts index ac8008c..b6ad224 100644 --- a/src/commands/drop.ts +++ b/src/commands/drop.ts @@ -12,6 +12,7 @@ import CardConstants from "../constants/CardConstants"; import ErrorMessages from "../constants/ErrorMessages"; import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper"; import DropEmbedHelper from "../helpers/DropHelpers/DropEmbedHelper"; +import {DropResult} from "../contracts/SeriesMetadata"; export default class Drop extends Command { constructor() { @@ -48,9 +49,16 @@ export default class Drop extends Command { return; } - await user.Save(User, user); + let randomCard: DropResult | undefined; + + try { + randomCard = await GetCardsHelper.FetchCard(interaction.user.id); + } catch (e) { + AppLogger.CatchError("Commands/Drop", e); + + await interaction.reply(ErrorMessages.UnableToFetchCard); + } - const randomCard = await GetCardsHelper.FetchCard(interaction.user.id); if (!randomCard) { AppLogger.LogWarn("Commands/Drop", ErrorMessages.UnableToFetchCard); @@ -82,6 +90,9 @@ export default class Drop extends Command { const row = DropEmbedHelper.GenerateDropButtons(randomCard, claimId, interaction.user.id); + await user.Save(User, user); + + await interaction.editReply({ embeds: [ embed ], files: files, diff --git a/src/commands/multidrop.ts b/src/commands/multidrop.ts index aa42686..82d19fe 100644 --- a/src/commands/multidrop.ts +++ b/src/commands/multidrop.ts @@ -62,10 +62,17 @@ export default class Multidrop 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; @@ -76,7 +83,7 @@ export default class Multidrop extends Command { await interaction.editReply({ embeds: [ embed ], - files: [ attachment ], + files: files, components: [ row ], }); } catch (e) { diff --git a/src/helpers/DropHelpers/DropEmbedHelper.ts b/src/helpers/DropHelpers/DropEmbedHelper.ts index 08b5813..5956a27 100644 --- a/src/helpers/DropHelpers/DropEmbedHelper.ts +++ b/src/helpers/DropHelpers/DropEmbedHelper.ts @@ -77,7 +77,7 @@ export default class DropEmbedHelper { .setStyle(ButtonStyle.Success) .setDisabled(disabled), new ButtonBuilder() - .setCustomId(`sacrifice confirm ${userId} ${drop.card.id} 1`) + .setCustomId(`sacrifice give ${userId} ${drop.card.id} 1`) .setLabel(`Sacrifice`) .setStyle(ButtonStyle.Danger), new ButtonBuilder() diff --git a/src/helpers/DropHelpers/GetCardsHelper.ts b/src/helpers/DropHelpers/GetCardsHelper.ts index 8bc4fe7..4c8648f 100644 --- a/src/helpers/DropHelpers/GetCardsHelper.ts +++ b/src/helpers/DropHelpers/GetCardsHelper.ts @@ -55,7 +55,7 @@ export default class GetCardsHelper { .find(x => x.cards.includes(card)); if (!series) { - AppLogger.LogWarn("CardDropHelperMetadata/GetRandomCardByRarity", `Series not found for card ${card.id}`); + AppLogger.LogError("CardDropHelperMetadata/GetRandomCardByRarity", `Series not found for card ${card.id}`); return undefined; } diff --git a/src/helpers/DropHelpers/GetUnclaimedCardsHelper.ts b/src/helpers/DropHelpers/GetUnclaimedCardsHelper.ts index 87fafea..94ce894 100644 --- a/src/helpers/DropHelpers/GetUnclaimedCardsHelper.ts +++ b/src/helpers/DropHelpers/GetUnclaimedCardsHelper.ts @@ -31,7 +31,7 @@ export default class GetUnclaimedCardsHelper { public static async GetRandomCardByRarityUnclaimed(rarity: CardRarity, userId: string): Promise { const claimedCards = await Inventory.FetchAllByUserId(userId); - if (!claimedCards) { + if (!claimedCards || claimedCards.length == 0) { // They don't have any cards, so safe to get any random card return GetCardsHelper.GetRandomCardByRarity(rarity); } @@ -39,18 +39,28 @@ export default class GetUnclaimedCardsHelper { const allCards = CoreClient.Cards .flatMap(x => x.cards) .filter(x => x.type == rarity) - .filter(x => !claimedCards.find(y => y.CardNumber == x.id)); + .filter(x => !claimedCards.find(y => y.CardNumber == x.id && y.Quantity > 0)); - if (!allCards) return undefined; + if (!allCards || allCards.length == 0) { + // There is no card left unclaimed, fallback to any card + return GetCardsHelper.GetRandomCardByRarity(rarity); + }; const randomCardIndex = Math.floor(Math.random() * allCards.length); const card = allCards[randomCardIndex]; + + if (!card) { + AppLogger.LogError("CardDropHelperMetadata/GetRandomCardByRarityUnclaimed", `Card not found in index, ${randomCardIndex} of ${allCards.length}, User Id: ${userId}, rarity: ${rarity}`); + + return undefined; + } + const series = CoreClient.Cards .find(x => x.cards.includes(card)); if (!series) { - AppLogger.LogWarn("CardDropHelperMetadata/GetRandomCardByRarityUnclaimed", `Series not found for card ${card.id}`); + AppLogger.LogError("CardDropHelperMetadata/GetRandomCardByRarityUnclaimed", `Series not found for card ${card.id}, User Id: ${userId}, rarity: ${rarity}`); return undefined; } diff --git a/src/helpers/EffectHelper.ts b/src/helpers/EffectHelper.ts index 235ea08..09e096c 100644 --- a/src/helpers/EffectHelper.ts +++ b/src/helpers/EffectHelper.ts @@ -151,7 +151,7 @@ export default class EffectHelper { AppLogger.LogInfo("EffectHelper", `Created initial user entity for : ${userId}`); } - if (user.Currency < totalCost) { + if (!disabled && user.Currency < totalCost) { return `You don't have enough currency to buy this! You have \`${user.Currency} Currency\` and need \`${totalCost} Currency\`!`; } diff --git a/src/helpers/InventoryHelper.ts b/src/helpers/InventoryHelper.ts index 3a7fbff..15c9c92 100644 --- a/src/helpers/InventoryHelper.ts +++ b/src/helpers/InventoryHelper.ts @@ -115,17 +115,48 @@ export default class InventoryHelper { let pageNum = 0; + const maxLength = 25; // Discord API limit + + const allPageOptions = pages.map(x => + new StringSelectMenuOptionBuilder() + .setLabel(`${x.name} (${x.seriesSubpage + 1})`.substring(0, 100)) + .setDescription(`Page ${pageNum + 1}`) + .setDefault(currentPage.id == x.id) + .setValue(`${userid} ${pageNum++}`)); + + const currentPageIndex = allPageOptions.indexOf(allPageOptions.find(x => x.data.default)!); + + let pageOptions: StringSelectMenuOptionBuilder[] = []; + + if (allPageOptions.length <= maxLength) { + pageOptions = allPageOptions; + } else { + let startIndex = currentPageIndex - Math.floor((maxLength - 1) / 2); + let endIndexOffset = 0; + + if (startIndex < 0) { + endIndexOffset = 0 - startIndex; + + startIndex = 0; + } + + let endIndex = currentPageIndex + Math.floor((maxLength - 1) / 2) + endIndexOffset; + + if (endIndex + 1 > allPageOptions.length) { + endIndex = allPageOptions.length; + } + + for (let i = startIndex; i < endIndex; i++) { + pageOptions.push(allPageOptions[i]); + } + } + const row2 = new ActionRowBuilder() .addComponents( new StringSelectMenuBuilder() .setCustomId("inventory") .setPlaceholder(`${currentPage.name} (${currentPage.seriesSubpage + 1})`) - .addOptions(...pages.map(x => - new StringSelectMenuOptionBuilder() - .setLabel(`${x.name} (${x.seriesSubpage + 1})`.substring(0, 100)) - .setDescription(`Page ${pageNum + 1}`) - .setDefault(currentPage.id == x.id) - .setValue(`${userid} ${pageNum++}`)))); + .addOptions(pageOptions)); const buffer = await ImageHelper.GenerateCardImageGrid(currentPage.cards.map(x => ({ id: x.id, path: x.path }))); const image = new AttachmentBuilder(buffer, { name: "page.png" }); diff --git a/tests/buttonEvents/Multidrop.test.ts b/tests/buttonEvents/Multidrop.test.ts new file mode 100644 index 0000000..5c9da42 --- /dev/null +++ b/tests/buttonEvents/Multidrop.test.ts @@ -0,0 +1,13 @@ +describe("execute", () => { + describe("GIVEN randomCard image is hosted locally", () => { + test.todo("EXPECT image to be uploaded directly"); + }); + + describe("GIVEN randomCard image is hosted via http", () => { + test.todo("EXPECT image link to be directly added to embed"); + }); + + describe("GIVEN randomCard image is hosted via https", () => { + test.todo("EXPECT image link to be directly added to embed"); + }); +}); diff --git a/tests/commands/multidrop.test.ts b/tests/commands/multidrop.test.ts new file mode 100644 index 0000000..5c9da42 --- /dev/null +++ b/tests/commands/multidrop.test.ts @@ -0,0 +1,13 @@ +describe("execute", () => { + describe("GIVEN randomCard image is hosted locally", () => { + test.todo("EXPECT image to be uploaded directly"); + }); + + describe("GIVEN randomCard image is hosted via http", () => { + test.todo("EXPECT image link to be directly added to embed"); + }); + + describe("GIVEN randomCard image is hosted via https", () => { + test.todo("EXPECT image link to be directly added to embed"); + }); +});