diff --git a/.env.example b/.env.example index ef2c218..ce664a9 100644 --- a/.env.example +++ b/.env.example @@ -7,29 +7,30 @@ # any secret values. BOT_TOKEN= -BOT_VER=0.5.2 +BOT_VER=0.6.0 BOT_AUTHOR=Vylpes BOT_OWNERID=147392775707426816 BOT_CLIENTID=682942374040961060 BOT_ENV=4 BOT_ADMINS=147392775707426816,887272961504071690 +BOT_LOGLEVEL=info ABOUT_FUNDING= ABOUT_REPO= -DATA_DIR= +DATA_DIR=./.temp -DB_HOST= -DB_PORT= -DB_NAME= +DB_HOST=127.0.0.1 +DB_PORT=3301 +DB_NAME=carddrop DB_AUTH_USER= DB_AUTH_PASS= DB_SYNC= DB_LOGGING= -DB_DATA_LOCATION=~/.docker +DB_DATA_LOCATION=./.temp/database DB_CARD_FILE=:memory: -EXPRESS_PORT=3303 +EXPRESS_PORT=3302 GDRIVESYNC_AUTO=true \ No newline at end of file diff --git a/.forgejo/workflows/production.yml b/.forgejo/workflows/production.yml index 4b53d02..79906d1 100644 --- a/.forgejo/workflows/production.yml +++ b/.forgejo/workflows/production.yml @@ -12,9 +12,9 @@ jobs: runs-on: node steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: 18.x - run: npm ci @@ -53,12 +53,13 @@ jobs: DATA_DIR: ${{ secrets.PROD_DATA_DIR }} GDRIVESYNC_AUTO: ${{ vars.PROD_GDRIVESYNC_AUTO }} EXPRESS_PORT: ${{ secrets.PROD_EXPRESS_PORT }} + BOT_LOGLEVEL: ${{ vars.PROD_BOT_LOGLEVEL }} with: host: ${{ secrets.PROD_SSH_HOST }} username: ${{ secrets.PROD_SSH_USER }} key: ${{ secrets.PROD_SSH_KEY }} port: ${{ secrets.PROD_SSH_PORT }} - envs: DB_NAME,DB_AUTH_USER,DB_AUTH_PASS,DB_HOST,DB_PORT,DB_ROOT_HOST,DB_SYNC,DB_LOGGING,DB_DATA_LOCATION,BOT_TOKEN,BOT_VER,BOT_AUTHOR,BOT_OWNERID,BOT_CLIENTID,ABOUT_FUNDING,ABOUT_REPO,BOT_ENV,BOT_ADMINS,DATA_DIR,GDRIVESYNC_AUTO,SERVER_PATH,EXPRESS_PORT + envs: DB_NAME,DB_AUTH_USER,DB_AUTH_PASS,DB_HOST,DB_PORT,DB_ROOT_HOST,DB_SYNC,DB_LOGGING,DB_DATA_LOCATION,BOT_TOKEN,BOT_VER,BOT_AUTHOR,BOT_OWNERID,BOT_CLIENTID,ABOUT_FUNDING,ABOUT_REPO,BOT_ENV,BOT_ADMINS,DATA_DIR,GDRIVESYNC_AUTO,SERVER_PATH,EXPRESS_PORT,BOT_LOGLEVEL script: | source .sshrc \ && cd /home/vylpes/apps/card-drop/card-drop_prod \ diff --git a/.forgejo/workflows/stage.yml b/.forgejo/workflows/stage.yml index 2bb68c0..ce9b78f 100644 --- a/.forgejo/workflows/stage.yml +++ b/.forgejo/workflows/stage.yml @@ -12,9 +12,9 @@ jobs: runs-on: node steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: 18.x - run: npm ci @@ -53,12 +53,13 @@ jobs: DATA_DIR: ${{ secrets.STAGE_DATA_DIR }} GDRIVESYNC_AUTO: ${{ vars.STAGE_GDRIVESYNC_AUTO }} EXPRESS_PORT: ${{ secrets.STAGE_EXPRESS_PORT }} + BOT_LOGLEVEL: ${{ vars.STAGE_BOT_LOGLEVEL }} with: host: ${{ secrets.STAGE_SSH_HOST }} username: ${{ secrets.STAGE_SSH_USER }} key: ${{ secrets.STAGE_SSH_KEY }} port: ${{ secrets.STAGE_SSH_PORT }} - envs: DB_NAME,DB_AUTH_USER,DB_AUTH_PASS,DB_HOST,DB_PORT,DB_ROOT_HOST,DB_SYNC,DB_LOGGING,DB_DATA_LOCATION,BOT_TOKEN,BOT_VER,BOT_AUTHOR,BOT_OWNERID,BOT_CLIENTID,ABOUT_FUNDING,ABOUT_REPO,BOT_ENV,BOT_ADMINS,DATA_DIR,GDRIVESYNC_AUTO,SERVER_PATH,EXPRESS_PORT + envs: DB_NAME,DB_AUTH_USER,DB_AUTH_PASS,DB_HOST,DB_PORT,DB_ROOT_HOST,DB_SYNC,DB_LOGGING,DB_DATA_LOCATION,BOT_TOKEN,BOT_VER,BOT_AUTHOR,BOT_OWNERID,BOT_CLIENTID,ABOUT_FUNDING,ABOUT_REPO,BOT_ENV,BOT_ADMINS,DATA_DIR,GDRIVESYNC_AUTO,SERVER_PATH,EXPRESS_PORT,BOT_LOGLEVEL script: | source .sshrc \ && cd /home/vylpes/apps/card-drop/card-drop_stage \ diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index 989ac4f..c5b6551 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -14,9 +14,9 @@ jobs: runs-on: node steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: 18.x - run: npm ci diff --git a/.gitignore b/.gitignore index a9ed469..a7d5b20 100644 --- a/.gitignore +++ b/.gitignore @@ -108,5 +108,5 @@ config.json ormconfig.json gdrive-credentials.json data/ +*.db .temp/ -*.db \ No newline at end of file diff --git a/database/0.6/1715967355818-daily/Up/01-table/User.sql b/database/0.6/1715967355818-daily/Up/01-table/User.sql new file mode 100644 index 0000000..8699650 --- /dev/null +++ b/database/0.6/1715967355818-daily/Up/01-table/User.sql @@ -0,0 +1,2 @@ +ALTER TABLE `user` +ADD LastUsedDaily datetime null; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1b9c8e7..bf650eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "card-drop", - "version": "0.5.2", + "version": "0.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "card-drop", - "version": "0.5.2", + "version": "0.6.0", "license": "MIT", "dependencies": { "@discordjs/rest": "^2.0.0", @@ -16,6 +16,7 @@ "@types/uuid": "^9.0.0", "body-parser": "^1.20.2", "clone-deep": "^4.0.1", + "cron": "^3.1.7", "discord.js": "^14.3.0", "dotenv": "^16.0.0", "express": "^4.18.2", @@ -747,10 +748,15 @@ } }, "node_modules/@discordjs/collection": { - "version": "2.0.0", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.0.tgz", + "integrity": "sha512-mLcTACtXUuVgutoznkh6hS3UFqYirDYAg5Dc1m8xn6OvPjetnUlf/xjtqnnc47OwWdaoCQnHmHh9KofhD6uRqw==", "license": "Apache-2.0", "engines": { "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, "node_modules/@discordjs/formatters": { @@ -764,28 +770,54 @@ } }, "node_modules/@discordjs/rest": { - "version": "2.2.0", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.3.0.tgz", + "integrity": "sha512-C1kAJK8aSYRv3ZwMG8cvrrW4GN0g5eMdP8AuN8ODH5DyOCbHgJspze1my3xHOAgwLJdKUbWNVyAeJ9cEdduqIg==", "license": "Apache-2.0", "dependencies": { - "@discordjs/collection": "^2.0.0", - "@discordjs/util": "^1.0.2", - "@sapphire/async-queue": "^1.5.0", - "@sapphire/snowflake": "^3.5.1", - "@vladfrangu/async_event_emitter": "^2.2.2", - "discord-api-types": "0.37.61", - "magic-bytes.js": "^1.5.0", + "@discordjs/collection": "^2.1.0", + "@discordjs/util": "^1.1.0", + "@sapphire/async-queue": "^1.5.2", + "@sapphire/snowflake": "^3.5.3", + "@vladfrangu/async_event_emitter": "^2.2.4", + "discord-api-types": "0.37.83", + "magic-bytes.js": "^1.10.0", "tslib": "^2.6.2", - "undici": "5.27.2" + "undici": "6.13.0" }, "engines": { "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, + "node_modules/@discordjs/rest/node_modules/@sapphire/snowflake": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", + "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@discordjs/rest/node_modules/discord-api-types": { + "version": "0.37.83", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.83.tgz", + "integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==", + "license": "MIT" + }, "node_modules/@discordjs/util": { - "version": "1.0.2", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.0.tgz", + "integrity": "sha512-IndcI5hzlNZ7GS96RV3Xw1R2kaDuXEp7tRIy/KlhidpN/BQ1qh1NZt3377dMLTa44xDUNKT7hnXkA/oUAzD/lg==", "license": "Apache-2.0", "engines": { "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, "node_modules/@discordjs/ws": { @@ -1673,7 +1705,9 @@ } }, "node_modules/@sapphire/async-queue": { - "version": "1.5.0", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.2.tgz", + "integrity": "sha512-7X7FFAA4DngXUl95+hYbUF19bp1LGiffjJtu7ygrZrbdCSsdDDBaSjB7Akw0ZbOu6k0xpXyljnJ6/RZUvLfRdg==", "license": "MIT", "engines": { "node": ">=v14.0.0", @@ -1895,14 +1929,20 @@ "@types/node": "*" } }, + "node_modules/@types/luxon": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", + "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==" + }, "node_modules/@types/mime": { "version": "3.0.4", "license": "MIT" }, "node_modules/@types/node": { - "version": "20.12.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", - "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", + "version": "20.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } @@ -3841,6 +3881,15 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/cron": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/cron/-/cron-3.1.7.tgz", + "integrity": "sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw==", + "dependencies": { + "@types/luxon": "~3.4.0", + "luxon": "~3.4.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "license": "MIT", @@ -5154,21 +5203,21 @@ "license": "MIT" }, "node_modules/glob": { - "version": "10.3.12", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", - "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "version": "10.3.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz", + "integrity": "sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^2.3.6", "minimatch": "^9.0.1", "minipass": "^7.0.4", - "path-scurry": "^1.10.2" + "path-scurry": "^1.11.0" }, "bin": { "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -6816,11 +6865,11 @@ } }, "node_modules/jest-mock-extended": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-3.0.6.tgz", - "integrity": "sha512-DJuEoFzio0loqdX8NIwkbE9dgIXNzaj//pefOQxGkoivohpxbSQeNHCAiXkDNA/fmM4EIJDoZnSibP4w3dUJ9g==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-3.0.7.tgz", + "integrity": "sha512-7lsKdLFcW9B9l5NzZ66S/yTQ9k8rFtnwYdCNuRU/81fqDWicNDVhitTSPnrGmNeNm0xyw0JHexEOShrIKRCIRQ==", "dependencies": { - "ts-essentials": "^9.4.2" + "ts-essentials": "^10.0.0" }, "peerDependencies": { "jest": "^24.0.0 || ^25.0.0 || ^26.0.0 || ^27.0.0 || ^28.0.0 || ^29.0.0", @@ -7700,8 +7749,18 @@ "node": ">=10" } }, + "node_modules/luxon": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "engines": { + "node": ">=12" + } + }, "node_modules/magic-bytes.js": { - "version": "1.6.0", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz", + "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==", "license": "MIT" }, "node_modules/make-dir": { @@ -9335,15 +9394,15 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", - "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -10847,11 +10906,11 @@ } }, "node_modules/ts-essentials": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-9.4.2.tgz", - "integrity": "sha512-mB/cDhOvD7pg3YCLk2rOtejHjjdSi9in/IBYE13S+8WA5FBSraYf4V/ws55uvs0IvQ/l0wBOlXy5yBNZ9Bl8ZQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-10.0.0.tgz", + "integrity": "sha512-77FHNJEyysF9+1s4G6eejuA1lxw7uMchT3ZPy3CIbh7GIunffpshtM8pTe5G6N5dpOzNevqRHew859ceLWVBfw==", "peerDependencies": { - "typescript": ">=4.1.0" + "typescript": ">=4.5.0" }, "peerDependenciesMeta": { "typescript": { @@ -10860,9 +10919,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", @@ -10877,10 +10937,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", @@ -10890,6 +10951,9 @@ "@babel/core": { "optional": true }, + "@jest/transform": { + "optional": true + }, "@jest/types": { "optional": true }, diff --git a/package.json b/package.json index b64eec3..25a7c33 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "card-drop", - "version": "0.5.2", + "version": "0.6.0", "main": "./dist/bot.js", "typings": "./dist", "scripts": { @@ -32,6 +32,7 @@ "@types/uuid": "^9.0.0", "body-parser": "^1.20.2", "clone-deep": "^4.0.1", + "cron": "^3.1.7", "discord.js": "^14.3.0", "dotenv": "^16.0.0", "express": "^4.18.2", 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/bot.ts b/src/bot.ts index 87008c5..e5e25a3 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -37,7 +37,6 @@ const client = new CoreClient([ ]); Registry.RegisterCommands(); -Registry.RegisterEvents(); Registry.RegisterButtonEvents(); if (!existsSync(`${process.env.DATA_DIR}/cards`) && process.env.GDRIVESYNC_AUTO && process.env.GDRIVESYNC_AUTO == "true") { diff --git a/src/buttonEvents/Claim.ts b/src/buttonEvents/Claim.ts index 3450dc4..e94f7cc 100644 --- a/src/buttonEvents/Claim.ts +++ b/src/buttonEvents/Claim.ts @@ -5,6 +5,8 @@ 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"; export default class Claim extends ButtonEvent { public override async execute(interaction: ButtonInteraction) { @@ -41,6 +43,17 @@ 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); diff --git a/src/buttonEvents/Sacrifice.ts b/src/buttonEvents/Sacrifice.ts new file mode 100644 index 0000000..a82ba81 --- /dev/null +++ b/src/buttonEvents/Sacrifice.ts @@ -0,0 +1,157 @@ +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"; + +export default class Sacrifice extends ButtonEvent { + public override async execute(interaction: ButtonInteraction) { + const subcommand = interaction.customId.split(" ")[1]; + + switch(subcommand) { + case "confirm": + await this.confirm(interaction); + break; + case "cancel": + await this.cancel(interaction); + break; + } + } + + private async confirm(interaction: ButtonInteraction) { + const userId = interaction.customId.split(" ")[2]; + const cardNumber = interaction.customId.split(" ")[3]; + + if (userId != interaction.user.id) { + await interaction.reply("Only the user who created this sacrifice can confirm it."); + return; + } + + const cardInInventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber); + + if (!cardInInventory) { + await interaction.reply("Unable to find card in inventory."); + return; + } + + const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardNumber); + + if (!cardData) { + await interaction.reply("Unable to find card in the database."); + return; + } + + const user = await User.FetchOneById(User, userId); + + if (!user) { + await interaction.reply("Unable to find user in database."); + return; + } + + cardInInventory.RemoveQuantity(1); + + await cardInInventory.Save(Inventory, cardInInventory); + + const cardValue = GetSacrificeAmount(cardData.card.type); + 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: ${cardInInventory.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 ], + }); + } + + private async cancel(interaction: ButtonInteraction) { + const userId = interaction.customId.split(" ")[2]; + const cardNumber = interaction.customId.split(" ")[3]; + + if (userId != interaction.user.id) { + await interaction.reply("Only the user who created this sacrifice can cancel it."); + return; + } + + const cardInInventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber); + + if (!cardInInventory) { + await interaction.reply("Unable to find card in inventory."); + return; + } + + const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardNumber); + + if (!cardData) { + await interaction.reply("Unable to find card in the database."); + return; + } + + const cardValue = GetSacrificeAmount(cardData.card.type); + const cardRarityString = CardRarityToString(cardData.card.type); + + const description = [ + `Card: ${cardData.card.name}`, + `Series: ${cardData.series.name}`, + `Rarity: ${cardRarityString}`, + `Quantity Owned: ${cardInInventory.Quantity}`, + `Sacrifice Amount: ${cardValue}`, + ]; + + const embed = new EmbedBuilder() + .setTitle("Sacrifice Cancelled") + .setDescription(description.join("\n")) + .setColor(EmbedColours.Grey) + .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 ], + }); + } +} \ No newline at end of file diff --git a/src/client/client.ts b/src/client/client.ts index c694950..384843d 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -14,6 +14,8 @@ import Webhooks from "../webhooks"; import CardMetadataFunction from "../Functions/CardMetadataFunction"; import { SeriesMetadata } from "../contracts/SeriesMetadata"; import AppLogger from "./appLogger"; +import TimerHelper from "../helpers/TimerHelper"; +import GiveCurrency from "../timers/GiveCurrency"; export class CoreClient extends Client { private static _commandItems: ICommandItem[]; @@ -23,6 +25,7 @@ export class CoreClient extends Client { private _events: Events; private _util: Util; private _webhooks: Webhooks; + private _timerHelper: TimerHelper; public static ClaimId: string; public static Environment: Environment; @@ -59,6 +62,7 @@ export class CoreClient extends Client { this._events = new Events(); this._util = new Util(); this._webhooks = new Webhooks(); + this._timerHelper = new TimerHelper(); AppLogger.LogInfo("Client", `Environment: ${CoreClient.Environment}`); @@ -72,7 +76,12 @@ export class CoreClient extends Client { } await AppDataSource.initialize() - .then(() => AppLogger.LogInfo("Client", "App Data Source Initialised")) + .then(() => { + AppLogger.LogInfo("Client", "App Data Source Initialised"); + + const timerId = this._timerHelper.AddTimer("*/20 * * * *", "Europe/London", GiveCurrency, false); + this._timerHelper.StartTimer(timerId); + }) .catch(err => { AppLogger.LogError("Client", "App Data Source Initialisation Failed"); AppLogger.LogError("Client", 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 new file mode 100644 index 0000000..65d69fc --- /dev/null +++ b/src/commands/daily.ts @@ -0,0 +1,43 @@ +import { CommandInteraction, SlashCommandBuilder } from "discord.js"; +import { Command } from "../type/command"; +import User from "../database/entities/app/User"; +import CardConstants from "../constants/CardConstants"; +import TimeLengthInput from "../helpers/TimeLengthInput"; + +export default class Daily extends Command { + constructor() { + super(); + + this.CommandBuilder = new SlashCommandBuilder() + .setName("daily") + .setDescription("Gain bonus currency, once a day"); + } + + public override async execute(interaction: CommandInteraction) { + const user = await User.FetchOneById(User, interaction.user.id) ?? new User(interaction.user.id, CardConstants.StartingCurrency); + + const dayAgo = new Date(Date.now() - (1000 * 60 * 60 * 24)); + + if (user.LastUsedDaily && user.LastUsedDaily > dayAgo) { + const timeNow = Date.now(); + const timeLength = 24 * 60 * 60 * 1000; // 1 day + + const timeLeft = Math.ceil(((timeLength - (timeNow - user.LastUsedDaily.getTime()))) / 1000 / 60); + + const timeLeftHours = Math.floor(timeLeft / 60); + const timeLeftMinutes = timeLeft % 60; + + const timeLeftString = new TimeLengthInput(`${timeLeftHours}h ${timeLeftMinutes}m`); + + await interaction.reply(`You have already used the daily command! You can use it again in **${timeLeftString.GetLength()}**.`); + return; + } + + user.AddCurrency(CardConstants.DailyCurrency); + user.UpdateLastUsedDaily(new Date()); + + await user.Save(User, user); + + 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/give.ts b/src/commands/give.ts index 3656dc5..ebdebe4 100644 --- a/src/commands/give.ts +++ b/src/commands/give.ts @@ -5,6 +5,7 @@ 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"; export default class Give extends Command { constructor() { @@ -14,19 +15,57 @@ export default class Give extends Command { .setName("give") .setDescription("Give a user a card manually, in case bot breaks") .setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator) - .addStringOption(x => + .addSubcommand(x => x - .setName("cardnumber") - .setDescription("G") - .setRequired(true)) - .addUserOption(x => + .setName("card") + .setDescription("Give a user a card manually") + .addStringOption(x => + x + .setName("cardnumber") + .setDescription("The card to give") + .setRequired(true)) + .addUserOption(x => + x + .setName("user") + .setDescription("The user to give the card to") + .setRequired(true))) + .addSubcommand(x => x - .setName("user") - .setDescription("The user to give the card to") - .setRequired(true)); + .setName("currency") + .setDescription("Give a user currency manually") + .addNumberOption(x => + x + .setName("amount") + .setDescription("The amount to give") + .setRequired(true)) + .addUserOption(x => + x + .setName("user") + .setDescription("The user to give the currency to") + .setRequired(true))); } public override async execute(interaction: CommandInteraction) { + if (!interaction.isChatInputCommand()) return; + + const whitelistedUsers = process.env.BOT_ADMINS!.split(","); + + if (!whitelistedUsers.find(x => x == interaction.user.id)) { + await interaction.reply("Only whitelisted users can use this command."); + return; + } + + switch (interaction.options.getSubcommand()) { + case "card": + await this.GiveCard(interaction); + break; + case "currency": + await this.GiveCurrency(interaction); + break; + } + } + + private async GiveCard(interaction: CommandInteraction) { if (!CoreClient.AllowDrops) { await interaction.reply("Bot is currently syncing, please wait until its done."); return; @@ -37,17 +76,10 @@ export default class Give extends Command { return; } - const whitelistedUsers = process.env.BOT_ADMINS!.split(","); - - if (!whitelistedUsers.find(x => x == interaction.user.id)) { - await interaction.reply("Only whitelisted users can use this command."); - return; - } - const cardNumber = interaction.options.get("cardnumber", true); const user = interaction.options.getUser("user", true); - AppLogger.LogSilly("Commands/Give", `Parameters: cardNumber=${cardNumber.value}, user=${user.id}`); + AppLogger.LogSilly("Commands/Give/GiveCard", `Parameters: cardNumber=${cardNumber.value}, user=${user.id}`); const card = CardDropHelperMetadata.GetCardByCardNumber(cardNumber.value!.toString()); @@ -66,6 +98,21 @@ export default class Give extends Command { await inventory.Save(Inventory, inventory); - await interaction.reply(`${card.card.name} given to ${user.username}, they now have ${inventory.Quantity}`); + await interaction.reply(`Card ${card.card.name} given to ${user.username}, they now have ${inventory.Quantity}`); + } + + private async GiveCurrency(interaction: CommandInteraction) { + const amount = interaction.options.get("amount", true); + const user = interaction.options.getUser("user", true); + + AppLogger.LogSilly("Commands/Give/GiveCurrency", `Parameters: amount=${amount.value} user=${user.id}`); + + const userEntity = await User.FetchOneById(User, user.id) || new User(user.id, 300); + + userEntity.AddCurrency(amount.value! as number); + + await userEntity.Save(User, userEntity); + + await interaction.reply(`${amount.value} currency ${amount.value! as number >= 0 ? "given to" : "taken from"} ${user.username}, they now have ${userEntity.Currency}`); } } \ No newline at end of file diff --git a/src/commands/sacrifice.ts b/src/commands/sacrifice.ts new file mode 100644 index 0000000..4d1c51a --- /dev/null +++ b/src/commands/sacrifice.ts @@ -0,0 +1,73 @@ +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CacheType, CommandInteraction, EmbedBuilder, SlashCommandBuilder } from "discord.js"; +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"; + +export default class Sacrifice extends Command { + constructor() { + super(); + + this.CommandBuilder = new SlashCommandBuilder() + .setName("sacrifice") + .setDescription("Sacrifices a card for currency") + .addStringOption(x => + x + .setName("cardnumber") + .setDescription("The card to sacrifice from your inventory") + .setRequired(true)); + } + + public override async execute(interaction: CommandInteraction): Promise { + const cardnumber = interaction.options.get("cardnumber", true); + + const cardInInventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, cardnumber.value! as string); + + if (!cardInInventory || cardInInventory.Quantity == 0) { + await interaction.reply("Unable to find card in your inventory."); + return; + } + + const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardnumber.value! as string); + + if (!cardData) { + await interaction.reply("Unable to find card in the database."); + return; + } + + const cardValue = GetSacrificeAmount(cardData.card.type); + const cardRarityString = CardRarityToString(cardData.card.type); + + const description = [ + `Card: ${cardData.card.name}`, + `Series: ${cardData.series.name}`, + `Rarity: ${cardRarityString}`, + `Quantity Owned: ${cardInInventory.Quantity}`, + `Sacrifice Amount: ${cardValue}`, + ]; + + const embed = new EmbedBuilder() + .setTitle("Sacrifice") + .setDescription(description.join("\n")) + .setColor(EmbedColours.Error) + .setFooter({ text: `${interaction.user.username}` }); + + const row = new ActionRowBuilder() + .addComponents([ + new ButtonBuilder() + .setCustomId(`sacrifice confirm ${interaction.user.id} ${cardnumber.value!}`) + .setLabel("Confirm") + .setStyle(ButtonStyle.Success), + new ButtonBuilder() + .setCustomId(`sacrifice cancel ${interaction.user.id} ${cardnumber.value!}`) + .setLabel("Cancel") + .setStyle(ButtonStyle.Secondary), + ]); + + await interaction.reply({ + embeds: [ embed ], + components: [ row ], + }); + } +} \ No newline at end of file diff --git a/src/constants/CardConstants.ts b/src/constants/CardConstants.ts new file mode 100644 index 0000000..0a87e22 --- /dev/null +++ b/src/constants/CardConstants.ts @@ -0,0 +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/constants/CardRarity.ts b/src/constants/CardRarity.ts index 8202a12..82ecee1 100644 --- a/src/constants/CardRarity.ts +++ b/src/constants/CardRarity.ts @@ -58,4 +58,21 @@ export function CardRarityParse(rarity: string): CardRarity { default: return CardRarity.Unknown; } +} + +export function GetSacrificeAmount(rarity: CardRarity): number { + switch (rarity) { + case CardRarity.Bronze: + return 5; + case CardRarity.Silver: + return 15; + case CardRarity.Gold: + return 30; + case CardRarity.Manga: + return 50; + case CardRarity.Legendary: + return 100; + default: + return 0; + } } \ No newline at end of file diff --git a/src/constants/EmbedColours.ts b/src/constants/EmbedColours.ts index 36777e3..f270eef 100644 --- a/src/constants/EmbedColours.ts +++ b/src/constants/EmbedColours.ts @@ -1,8 +1,14 @@ export default class EmbedColours { + // General public static readonly Ok = 0x3050ba; public static readonly Success = 0x50c878; public static readonly Error = 0xff0000; + + // Colours public static readonly Grey = 0xd3d3d3; + public static readonly Green = 0x228B22; + + // Card Types public static readonly BronzeCard = 0xcd7f32; public static readonly SilverCard = 0xc0c0c0; public static readonly GoldCard = 0xffd700; diff --git a/src/contracts/AppBaseEntity.ts b/src/contracts/AppBaseEntity.ts index b9ca565..3eb9684 100644 --- a/src/contracts/AppBaseEntity.ts +++ b/src/contracts/AppBaseEntity.ts @@ -27,6 +27,12 @@ export default class AppBaseEntity { await repository.save(entity); } + public static async SaveAll(target: EntityTarget, entities: DeepPartial[]): Promise { + const repository = AppDataSource.getRepository(target); + + await repository.save(entities); + } + public static async Remove(target: EntityTarget, entity: T): Promise { const repository = AppDataSource.getRepository(target); diff --git a/src/database/entities/app/Inventory.ts b/src/database/entities/app/Inventory.ts index bde4450..a5d0026 100644 --- a/src/database/entities/app/Inventory.ts +++ b/src/database/entities/app/Inventory.ts @@ -29,6 +29,12 @@ export default class Inventory extends AppBaseEntity { this.Quantity = quantity; } + public RemoveQuantity(amount: number) { + if (this.Quantity < amount) return; + + this.Quantity -= amount; + } + public AddClaim(claim: Claim) { this.Claims.push(claim); } diff --git a/src/database/entities/app/User.ts b/src/database/entities/app/User.ts index c954aea..c3d8437 100644 --- a/src/database/entities/app/User.ts +++ b/src/database/entities/app/User.ts @@ -13,7 +13,22 @@ export default class User extends AppBaseEntity { @Column() Currency: number; - public UpdateCurrency(currency: number) { - this.Currency = currency; + @Column() + LastUsedDaily?: Date; + + public AddCurrency(amount: number) { + this.Currency += amount; + } + + public RemoveCurrency(amount: number): boolean { + if (this.Currency < amount) return false; + + this.Currency -= amount; + + return true; + } + + public UpdateLastUsedDaily(lastUsedDaily: Date) { + this.LastUsedDaily = lastUsedDaily; } } \ No newline at end of file diff --git a/src/database/migrations/app/0.6/1715967355818-daily.ts b/src/database/migrations/app/0.6/1715967355818-daily.ts new file mode 100644 index 0000000..1c81d18 --- /dev/null +++ b/src/database/migrations/app/0.6/1715967355818-daily.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; +import MigrationHelper from "../../../../helpers/MigrationHelper"; + +export class Daily1715967355818 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + MigrationHelper.Up("1715967355818-daily", "0.6", [ + "01-table/User", + ], queryRunner); + } + + public async down(): Promise { + } + +} diff --git a/src/helpers/InventoryHelper.ts b/src/helpers/InventoryHelper.ts index 3b0c40a..8792ea6 100644 --- a/src/helpers/InventoryHelper.ts +++ b/src/helpers/InventoryHelper.ts @@ -35,7 +35,8 @@ export default class InventoryHelper { .filter(x => { x.cards = x.cards .sort((a, b) => b.type - a.type) - .filter(y => inventory.find(z => z.CardNumber == y.id)); + .filter(y => inventory.find(z => z.CardNumber == y.id)) + .filter(y => inventory.find(z => z.CardNumber == y.id)!.Quantity > 0); return x; }); diff --git a/src/helpers/TimerHelper.ts b/src/helpers/TimerHelper.ts new file mode 100644 index 0000000..535a6a7 --- /dev/null +++ b/src/helpers/TimerHelper.ts @@ -0,0 +1,81 @@ +import { CronJob } from "cron"; +import { v4 } from "uuid"; +import { Primitive } from "../type/primitive"; + +interface Timer { + id: string; + job: CronJob; + context: Map; + onTick: ((context: Map) => void) | ((context: Map) => Promise); + runOnStart: boolean; +} + +export default class TimerHelper { + private _timers: Timer[]; + + constructor() { + this._timers = []; + } + + public AddTimer( + cronTime: string, + timeZone: string, + onTick: ((context: Map) => void) | ((context: Map) => Promise), + runOnStart: boolean = false): string { + const context = new Map(); + + const job = new CronJob( + cronTime, + () => { + onTick(context); + }, + null, + false, + timeZone, + ); + + const id = v4(); + + this._timers.push({ + id, + job, + context, + onTick, + runOnStart, + }); + + return id; + } + + public StartAllTimers() { + this._timers.forEach(timer => this.StartJob(timer)); + } + + public StopAllTimers() { + this._timers.forEach(timer => timer.job.stop()); + } + + public StartTimer(id: string) { + const timer = this._timers.find(x => x.id == id); + + if (!timer) return; + + this.StartJob(timer); + } + + public StopTimer(id: string) { + const timer = this._timers.find(x => x.id == id); + + if (!timer) return; + + timer.job.stop(); + } + + private StartJob(timer: Timer) { + timer.job.start(); + + if (timer.runOnStart) { + timer.onTick(timer.context); + } + } +} \ No newline at end of file diff --git a/src/registry.ts b/src/registry.ts index bb68685..182418e 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -3,11 +3,14 @@ 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"; import Give from "./commands/give"; import Inventory from "./commands/inventory"; import Resync from "./commands/resync"; +import Sacrifice from "./commands/sacrifice"; import Series from "./commands/series"; import Trade from "./commands/trade"; import View from "./commands/view"; @@ -20,6 +23,7 @@ import Droprarity from "./commands/stage/droprarity"; import Claim from "./buttonEvents/Claim"; import InventoryButtonEvent from "./buttonEvents/Inventory"; import Reroll from "./buttonEvents/Reroll"; +import SacrificeButtonEvent from "./buttonEvents/Sacrifice"; import SeriesEvent from "./buttonEvents/Series"; import TradeButtonEvent from "./buttonEvents/Trade"; @@ -27,11 +31,14 @@ 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()); CoreClient.RegisterCommand("give", new Give()); CoreClient.RegisterCommand("inventory", new Inventory()); CoreClient.RegisterCommand("resync", new Resync()); + CoreClient.RegisterCommand("sacrifice", new Sacrifice()); CoreClient.RegisterCommand("series", new Series()); CoreClient.RegisterCommand("trade", new Trade()); CoreClient.RegisterCommand("view", new View()); @@ -41,14 +48,11 @@ export default class Registry { CoreClient.RegisterCommand("droprarity", new Droprarity(), Environment.Test); } - public static RegisterEvents() { - - } - public static RegisterButtonEvents() { CoreClient.RegisterButtonEvent("claim", new Claim()); CoreClient.RegisterButtonEvent("inventory", new InventoryButtonEvent()); CoreClient.RegisterButtonEvent("reroll", new Reroll()); + CoreClient.RegisterButtonEvent("sacrifice", new SacrificeButtonEvent()); CoreClient.RegisterButtonEvent("series", new SeriesEvent()); CoreClient.RegisterButtonEvent("trade", new TradeButtonEvent()); } diff --git a/src/timers/GiveCurrency.ts b/src/timers/GiveCurrency.ts new file mode 100644 index 0000000..c292025 --- /dev/null +++ b/src/timers/GiveCurrency.ts @@ -0,0 +1,17 @@ +import AppLogger from "../client/appLogger"; +import CardConstants from "../constants/CardConstants"; +import User from "../database/entities/app/User"; + +export default async function GiveCurrency() { + AppLogger.LogInfo("Timers/GiveCurrency", "Giving currency to every known user"); + + const users = await User.FetchAll(User); + + for (const user of users) { + user.AddCurrency(CardConstants.TimerGiveAmount); + } + + User.SaveAll(User, users); + + AppLogger.LogInfo("Timers/GiveCurrency", `Successfully gave +${CardConstants.TimerGiveAmount} currency to ${users.length} users`); +} \ No newline at end of file diff --git a/src/type/command.ts b/src/type/command.ts index 20f5e3a..458a81e 100644 --- a/src/type/command.ts +++ b/src/type/command.ts @@ -1,7 +1,8 @@ -import { CommandInteraction, SlashCommandBuilder } from "discord.js"; +import { CommandInteraction } from "discord.js"; export abstract class Command { - public CommandBuilder: Omit; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- CommandBuilder type is dynamic depending on options and can't be strictly typed + public CommandBuilder: any; abstract execute(interaction: CommandInteraction): Promise; } diff --git a/src/type/primitive.ts b/src/type/primitive.ts new file mode 100644 index 0000000..b1e143b --- /dev/null +++ b/src/type/primitive.ts @@ -0,0 +1 @@ +export type Primitive = string | number | boolean; \ No newline at end of file