diff --git a/.env.example b/.env.example index 4fccad3..dc6f5de 100644 --- a/.env.example +++ b/.env.example @@ -7,7 +7,7 @@ # any secret values. BOT_TOKEN= -BOT_VER=0.5.0 +BOT_VER=0.6.1 BOT_AUTHOR=Vylpes BOT_OWNERID=147392775707426816 BOT_CLIENTID=682942374040961060 diff --git a/.gitignore b/.gitignore index 9326cc8..a7d5b20 100644 --- a/.gitignore +++ b/.gitignore @@ -109,4 +109,4 @@ ormconfig.json gdrive-credentials.json data/ *.db -.temp/ \ No newline at end of file +.temp/ diff --git a/package-lock.json b/package-lock.json index 7c81d71..dca33d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "card-drop", - "version": "0.5.1", + "version": "0.6.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "card-drop", - "version": "0.5.1", + "version": "0.6.1", "license": "MIT", "dependencies": { "@discordjs/rest": "^2.0.0", @@ -732,9 +732,9 @@ } }, "node_modules/@discordjs/builders": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.8.1.tgz", - "integrity": "sha512-GkF+HM01FHy+NSoTaUPR8z44otfQgJ1AIsRxclYGUZDyUbdZEFyD/5QVv2Y1Flx6M+B0bQLzg2M9CJv5lGTqpA==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.8.2.tgz", + "integrity": "sha512-6wvG3QaCjtMu0xnle4SoOIeFB4y6fKMN6WZfy3BMKJdQQtPLik8KGzDwBVL/+wTtcE/ZlFjgEk74GublyEVZ7g==", "license": "Apache-2.0", "dependencies": { "@discordjs/formatters": "^0.4.0", @@ -815,9 +815,9 @@ } }, "node_modules/@discordjs/ws": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.1.0.tgz", - "integrity": "sha512-O97DIeSvfNTn5wz5vaER6ciyUsr7nOqSEtsLoMhhIgeFkhnxLRqSr00/Fpq2/ppLgjDGLbQCDzIK7ilGoB/M7A==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.1.1.tgz", + "integrity": "sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==", "license": "Apache-2.0", "dependencies": { "@discordjs/collection": "^2.1.0", @@ -4242,17 +4242,17 @@ "license": "MIT" }, "node_modules/discord.js": { - "version": "14.15.2", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.15.2.tgz", - "integrity": "sha512-wGD37YCaTUNprtpqMIRuNiswwsvSWXrHykBSm2SAosoTYut0VUDj9yo9t4iLtMKvuhI49zYkvKc2TNdzdvpJhg==", + "version": "14.15.3", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.15.3.tgz", + "integrity": "sha512-/UJDQO10VuU6wQPglA4kz2bw2ngeeSbogiIPx/TsnctfzV/tNf+q+i1HlgtX1OGpeOBpJH9erZQNO5oRM2uAtQ==", "license": "Apache-2.0", "dependencies": { - "@discordjs/builders": "^1.8.1", + "@discordjs/builders": "^1.8.2", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.4.0", "@discordjs/rest": "^2.3.0", "@discordjs/util": "^1.1.0", - "@discordjs/ws": "^1.1.0", + "@discordjs/ws": "^1.1.1", "@sapphire/snowflake": "3.5.3", "discord-api-types": "0.37.83", "fast-deep-equal": "3.1.3", @@ -10921,9 +10921,10 @@ } }, "node_modules/ts-jest": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", - "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", + "version": "29.1.3", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.3.tgz", + "integrity": "sha512-6L9qz3ginTd1NKhOxmkP0qU3FyKjj5CPoY+anszfVn6Pmv/RIKzhiMCsH7Yb7UvJR9I2A64rm4zQl531s2F1iw==", + "license": "MIT", "dependencies": { "bs-logger": "0.x", "fast-json-stable-stringify": "2.x", @@ -10938,10 +10939,11 @@ "ts-jest": "cli.js" }, "engines": { - "node": "^16.10.0 || ^18.0.0 || >=20.0.0" + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", "@jest/types": "^29.0.0", "babel-jest": "^29.0.0", "jest": "^29.0.0", @@ -10951,6 +10953,9 @@ "@babel/core": { "optional": true }, + "@jest/transform": { + "optional": true + }, "@jest/types": { "optional": true }, diff --git a/package.json b/package.json index 004cfb8..ec102f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "card-drop", - "version": "0.5.1", + "version": "0.6.1", "main": "./dist/bot.js", "typings": "./dist", "scripts": { diff --git a/src/Functions/CardMetadataFunction.ts b/src/Functions/CardMetadataFunction.ts index 733c50c..a2499d9 100644 --- a/src/Functions/CardMetadataFunction.ts +++ b/src/Functions/CardMetadataFunction.ts @@ -39,6 +39,13 @@ export default class CardMetadataFunction { CoreClient.Cards = cardResult.Result!; AppLogger.LogInfo("Functions/CardMetadataFunction", `Loaded ${CoreClient.Cards.flatMap(x => x.cards).length} cards to database`); + const duplicateCards = CoreClient.Cards.flatMap(x => x.cards) + .filter((card, index, self) => self.findIndex(c => c.id === card.id) !== index); + + if (duplicateCards.length > 0) { + AppLogger.LogWarn("Functions/CardMetadataFunction", `Duplicate card ids found: ${duplicateCards.flatMap(x => x.id).join(", ")}`); + } + return { IsSuccess: true, }; diff --git a/src/buttonEvents/Claim.ts b/src/buttonEvents/Claim.ts index a643905..4f7f5ae 100644 --- a/src/buttonEvents/Claim.ts +++ b/src/buttonEvents/Claim.ts @@ -1,18 +1,19 @@ -import { AttachmentBuilder, ButtonInteraction } from "discord.js"; +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 { readFileSync } from "fs"; -import path from "path"; import User from "../database/entities/app/User"; import CardConstants from "../constants/CardConstants"; export default class Claim extends ButtonEvent { public override async execute(interaction: ButtonInteraction) { if (!interaction.guild || !interaction.guildId) return; + if (!interaction.channel) return; + + await interaction.deferUpdate(); const cardNumber = interaction.customId.split(" ")[1]; const claimId = interaction.customId.split(" ")[2]; @@ -21,15 +22,26 @@ export default class Claim extends ButtonEvent { AppLogger.LogSilly("Button/Claim", `Parameters: cardNumber=${cardNumber}, claimId=${claimId}, droppedBy=${droppedBy}, userId=${userId}`); + const user = await User.FetchOneById(User, userId) || new User(userId, CardConstants.StartingCurrency); + + 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; + } + + await user.Save(User, user); + const claimed = await eClaim.FetchOneByClaimId(claimId); if (claimed) { - await interaction.reply("This card has already been claimed"); + await interaction.channel.send(`${interaction.user}, This card has already been claimed!`); return; } if (claimId == CoreClient.ClaimId && userId != droppedBy) { - await interaction.reply("The latest dropped card can only be claimed by the user who dropped it"); + await interaction.channel.send(`${interaction.user}, The latest dropped card can only be claimed by the user who dropped it!`); return; } @@ -43,17 +55,6 @@ export default class Claim extends ButtonEvent { await inventory.Save(Inventory, inventory); - const user = await User.FetchOneById(User, userId) || new User(userId, CardConstants.StartingCurrency); - - AppLogger.LogSilly("Button/Claim", `${user.Id} has ${user.Currency} currency`); - - if (!user.RemoveCurrency(CardConstants.ClaimCost)) { - await interaction.reply(`Not enough currency! You need 10 currency, you have ${user.Currency}`); - return; - } - - await user.Save(User, user); - const claim = new eClaim(claimId); claim.SetInventory(inventory); @@ -65,17 +66,13 @@ export default class Claim extends ButtonEvent { return; } - const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path)); const imageFileName = card.card.path.split("/").pop()!; - const attachment = new AttachmentBuilder(image, { name: imageFileName }); - const embed = CardDropHelperMetadata.GenerateDropEmbed(card, inventory.Quantity, imageFileName, interaction.user.username); const row = CardDropHelperMetadata.GenerateDropButtons(card, claimId, interaction.user.id, true); - await interaction.update({ + await interaction.editReply({ embeds: [ embed ], - files: [ attachment ], components: [ row ], }); } diff --git a/src/buttonEvents/Reroll.ts b/src/buttonEvents/Reroll.ts index c271a3a..c55aedf 100644 --- a/src/buttonEvents/Reroll.ts +++ b/src/buttonEvents/Reroll.ts @@ -8,6 +8,8 @@ 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"; export default class Reroll extends ButtonEvent { public override async execute(interaction: ButtonInteraction) { @@ -23,6 +25,20 @@ export default class Reroll extends ButtonEvent { return; } + let user = await User.FetchOneById(User, interaction.user.id); + + if (!user) { + 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`); + } + + if (user.Currency < CardConstants.ClaimCost) { + await interaction.reply(`Not enough currency! You need ${CardConstants.ClaimCost} currency, you have ${user.Currency}!`); + return; + } + const randomCard = CardDropHelperMetadata.GetRandomCard(); if (!randomCard) { diff --git a/src/client/client.ts b/src/client/client.ts index 2dd9a29..384843d 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -79,7 +79,7 @@ export class CoreClient extends Client { .then(() => { AppLogger.LogInfo("Client", "App Data Source Initialised"); - const timerId = this._timerHelper.AddTimer("*/30 * * * * *", "Europe/London", GiveCurrency, false); + const timerId = this._timerHelper.AddTimer("*/20 * * * *", "Europe/London", GiveCurrency, false); this._timerHelper.StartTimer(timerId); }) .catch(err => { diff --git a/src/commands/balance.ts b/src/commands/balance.ts new file mode 100644 index 0000000..2dcba50 --- /dev/null +++ b/src/commands/balance.ts @@ -0,0 +1,28 @@ +import { CommandInteraction, EmbedBuilder, SlashCommandBuilder } from "discord.js"; +import { Command } from "../type/command"; +import User from "../database/entities/app/User"; +import EmbedColours from "../constants/EmbedColours"; + +export default class Balance extends Command { + constructor() { + super(); + + this.CommandBuilder = new SlashCommandBuilder() + .setName("balance") + .setDescription("Get your currency balance"); + } + + public override async execute(interaction: CommandInteraction) { + const user = await User.FetchOneById(User, interaction.user.id); + + let userBalance = user != null ? user.Currency : 0; + + const embed = new EmbedBuilder() + .setColor(EmbedColours.Ok) + .setTitle("Balance") + .setDescription(`You currently have **${userBalance} currency**!`) + .setFooter({ text: interaction.user.username, iconURL: interaction.user.avatarURL() ?? undefined }); + + await interaction.reply({ embeds: [ embed ]}); + } +} \ No newline at end of file diff --git a/src/commands/daily.ts b/src/commands/daily.ts index 172b942..65d69fc 100644 --- a/src/commands/daily.ts +++ b/src/commands/daily.ts @@ -38,6 +38,6 @@ export default class Daily extends Command { await user.Save(User, user); - await interaction.reply(`Given ${CardConstants.DailyCurrency} currency to ${interaction.user.username}`); + await interaction.reply(`Congratulations, you have claimed your daily ${CardConstants.DailyCurrency} currency! You now have ${user.Currency} currency and can claim again in 24 hours!`); } } \ No newline at end of file diff --git a/src/commands/drop.ts b/src/commands/drop.ts index 7a91042..ab6c339 100644 --- a/src/commands/drop.ts +++ b/src/commands/drop.ts @@ -8,6 +8,8 @@ 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"; export default class Drop extends Command { constructor() { @@ -31,6 +33,20 @@ export default class Drop extends Command { return; } + let user = await User.FetchOneById(User, interaction.user.id); + + if (!user) { + 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`); + } + + if (user.Currency < CardConstants.ClaimCost) { + await interaction.reply(`Not enough currency! You need ${CardConstants.ClaimCost} currency, you have ${user.Currency}!`); + return; + } + const randomCard = CardDropHelperMetadata.GetRandomCard(); if (!randomCard) { diff --git a/src/commands/trade.ts b/src/commands/trade.ts index 60f9033..3324128 100644 --- a/src/commands/trade.ts +++ b/src/commands/trade.ts @@ -36,6 +36,11 @@ export default class Trade extends Command { AppLogger.LogSilly("Commands/Trade", `Parameters: user=${user.id}, give=${give.value}, receive=${receive.value}`); + if (interaction.user.id == user.id) { + await interaction.reply("You can not create a trade with yourself."); + return; + } + const giveItemEntity = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, give.value!.toString()); const receiveItemEntity = await Inventory.FetchOneByCardNumberAndUserId(user.id, receive.value!.toString()); diff --git a/src/constants/CardConstants.ts b/src/constants/CardConstants.ts index dfc89b8..0a87e22 100644 --- a/src/constants/CardConstants.ts +++ b/src/constants/CardConstants.ts @@ -1,5 +1,6 @@ export default class CardConstants { public static readonly ClaimCost = 10; + public static readonly TimerGiveAmount = 10; public static readonly DailyCurrency = 100; public static readonly StartingCurrency = 300; } \ No newline at end of file diff --git a/src/registry.ts b/src/registry.ts index dc2770d..182418e 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -3,6 +3,7 @@ import { Environment } from "./constants/Environment"; // Global Command Imports import About from "./commands/about"; +import Balance from "./commands/balance"; import Daily from "./commands/daily"; import Drop from "./commands/drop"; import Gdrivesync from "./commands/gdrivesync"; @@ -30,6 +31,7 @@ export default class Registry { public static RegisterCommands() { // Global Commands CoreClient.RegisterCommand("about", new About()); + CoreClient.RegisterCommand("balance", new Balance()); CoreClient.RegisterCommand("daily", new Daily()); CoreClient.RegisterCommand("drop", new Drop()); CoreClient.RegisterCommand("gdrivesync", new Gdrivesync()); diff --git a/src/timers/GiveCurrency.ts b/src/timers/GiveCurrency.ts index ad1a21a..c292025 100644 --- a/src/timers/GiveCurrency.ts +++ b/src/timers/GiveCurrency.ts @@ -1,4 +1,5 @@ import AppLogger from "../client/appLogger"; +import CardConstants from "../constants/CardConstants"; import User from "../database/entities/app/User"; export default async function GiveCurrency() { @@ -7,10 +8,10 @@ export default async function GiveCurrency() { const users = await User.FetchAll(User); for (const user of users) { - user.AddCurrency(5); + user.AddCurrency(CardConstants.TimerGiveAmount); } User.SaveAll(User, users); - AppLogger.LogInfo("Timers/GiveCurrency", `Successfully gave +5 currency to ${users.length} users`); + AppLogger.LogInfo("Timers/GiveCurrency", `Successfully gave +${CardConstants.TimerGiveAmount} currency to ${users.length} users`); } \ No newline at end of file