Fix remote image urls not showing up in image grids #427
80 changed files with 4942 additions and 2193 deletions
|
@ -32,9 +32,8 @@ DB_AUTH_PASS=
|
||||||
DB_SYNC=
|
DB_SYNC=
|
||||||
DB_LOGGING=
|
DB_LOGGING=
|
||||||
DB_DATA_LOCATION=./.temp/database
|
DB_DATA_LOCATION=./.temp/database
|
||||||
|
DB_ROOT_HOST=0.0.0.0
|
||||||
DB_CARD_FILE=:memory:
|
|
||||||
|
|
||||||
EXPRESS_PORT=3302
|
EXPRESS_PORT=3302
|
||||||
|
|
||||||
GDRIVESYNC_AUTO=true
|
GDRIVESYNC_AUTO=false
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
{
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es2021": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended"
|
|
||||||
],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": "latest",
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"plugins": [
|
|
||||||
"@typescript-eslint"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"indent": [
|
|
||||||
"error",
|
|
||||||
4
|
|
||||||
],
|
|
||||||
"linebreak-style": [
|
|
||||||
"error",
|
|
||||||
"unix"
|
|
||||||
],
|
|
||||||
"quotes": [
|
|
||||||
"error",
|
|
||||||
"double"
|
|
||||||
],
|
|
||||||
"semi": [
|
|
||||||
"error",
|
|
||||||
"always"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"globals": {
|
|
||||||
"jest": true,
|
|
||||||
"require": true,
|
|
||||||
"exports": true,
|
|
||||||
"process": true
|
|
||||||
},
|
|
||||||
"ignorePatterns": [
|
|
||||||
"dist/**/*"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -30,7 +30,7 @@ jobs:
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: node
|
runs-on: node
|
||||||
steps:
|
steps:
|
||||||
- uses: https://github.com/appleboy/ssh-action@v1.0.3
|
- uses: https://github.com/appleboy/ssh-action@v1.1.0
|
||||||
env:
|
env:
|
||||||
DB_NAME: ${{ secrets.PROD_DB_NAME }}
|
DB_NAME: ${{ secrets.PROD_DB_NAME }}
|
||||||
DB_AUTH_USER: ${{ secrets.PROD_DB_AUTH_USER }}
|
DB_AUTH_USER: ${{ secrets.PROD_DB_AUTH_USER }}
|
||||||
|
|
|
@ -30,7 +30,7 @@ jobs:
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: node
|
runs-on: node
|
||||||
steps:
|
steps:
|
||||||
- uses: https://github.com/appleboy/ssh-action@v1.0.3
|
- uses: https://github.com/appleboy/ssh-action@v1.1.0
|
||||||
env:
|
env:
|
||||||
DB_NAME: ${{ secrets.STAGE_DB_NAME }}
|
DB_NAME: ${{ secrets.STAGE_DB_NAME }}
|
||||||
DB_AUTH_USER: ${{ secrets.STAGE_DB_AUTH_USER }}
|
DB_AUTH_USER: ${{ secrets.STAGE_DB_AUTH_USER }}
|
||||||
|
|
|
@ -18,7 +18,7 @@ jobs:
|
||||||
- name: Use Node.js
|
- name: Use Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 20.x
|
||||||
- run: yarn install --frozen-lockfile
|
- run: yarn install --frozen-lockfile
|
||||||
- run: yarn build
|
- run: yarn build
|
||||||
- run: yarn test
|
- run: yarn test
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
DROP TABLE `user_effect`;
|
|
@ -0,0 +1,10 @@
|
||||||
|
CREATE TABLE `user_effect` (
|
||||||
|
`Id` varchar(255) NOT NULL,
|
||||||
|
`WhenCreated` datetime NOT NULL,
|
||||||
|
`WhenUpdated` datetime NOT NULL,
|
||||||
|
`Name` varchar(255) NOT NULL,
|
||||||
|
`UserId` varchar(255) NOT NULL,
|
||||||
|
`Unused` int NOT NULL DEFAULT 0,
|
||||||
|
`WhenExpires` datetime NULL,
|
||||||
|
PRIMARY KEY (`Id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
35
docs/google-drive-sync.md
Normal file
35
docs/google-drive-sync.md
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# Google Drive Sync
|
||||||
|
|
||||||
|
The bot relies on an external sync between the local file system and Google
|
||||||
|
Drive in order to get newer cards to the bot. This is done using
|
||||||
|
[Rclone](https://rclone.org/).
|
||||||
|
|
||||||
|
The process for this is done by once the `/gdrivesync` command is executed by
|
||||||
|
an admin user of the bot, which calls the system shell to run rclone to the
|
||||||
|
card folder.
|
||||||
|
|
||||||
|
- The admins who can run the command is specifed in `$BOT_ADMINS`, which are
|
||||||
|
discord user ids separated by commas.
|
||||||
|
- The card folder is located at `$DATA_DIR/cards`.
|
||||||
|
- The source requires rclone's remote to be setup as `card-drop-gdrive`.
|
||||||
|
|
||||||
|
The exact command it runs is: `rclone sync card-drop-gdrive: $DATA_DIR/cards`.
|
||||||
|
|
||||||
|
Once it syncs the database will reread all the cards for updates and then load
|
||||||
|
them into the bot to be given.
|
||||||
|
|
||||||
|
## Safe Mode
|
||||||
|
Safe mode is a function of the bot which disables the `/drop` command function
|
||||||
|
and any other functions which rely on the card metadata. Safe mode is activated
|
||||||
|
upon failure to sync properly. It is disabled once errors are resolved.
|
||||||
|
|
||||||
|
The reason for safe mode is to ensure that the bot stays online for admins to
|
||||||
|
be able to resync the bot in case there's an error without it crashing.
|
||||||
|
|
||||||
|
## Google Drive
|
||||||
|
Please see the Rclone documentation on how to setup a remote using Google
|
||||||
|
Drive. You will need to make an app password for this.
|
||||||
|
|
||||||
|
- scope: `drive.readonly`
|
||||||
|
- root\_folder\_id: The folder id where the cards are located, this can be found
|
||||||
|
by looking at the url when viewing the folder in the browser in google drive.
|
55
eslint.config.mjs
Normal file
55
eslint.config.mjs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import js from "@eslint/js";
|
||||||
|
import ts from "typescript-eslint";
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"**/dist/",
|
||||||
|
"eslint.config.mjs",
|
||||||
|
"jest.config.cjs",
|
||||||
|
"jest.setup.js",
|
||||||
|
"**/.temp/**/*"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
js.configs.recommended,
|
||||||
|
...ts.configs.recommended,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
exports: "writable",
|
||||||
|
module: "writable",
|
||||||
|
require: "writable",
|
||||||
|
process: "writable",
|
||||||
|
console: "writable",
|
||||||
|
jest: "writable",
|
||||||
|
},
|
||||||
|
|
||||||
|
ecmaVersion: 6,
|
||||||
|
sourceType: "script",
|
||||||
|
},
|
||||||
|
|
||||||
|
files: [
|
||||||
|
"./src",
|
||||||
|
"./tests"
|
||||||
|
],
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
camelcase: "error",
|
||||||
|
"brace-style": ["error", "1tbs"],
|
||||||
|
"comma-dangle": ["error", "never"],
|
||||||
|
|
||||||
|
"comma-spacing": ["error", {
|
||||||
|
before: false,
|
||||||
|
after: true,
|
||||||
|
}],
|
||||||
|
|
||||||
|
"comma-style": ["error", "last"],
|
||||||
|
"arrow-body-style": ["error", "as-needed"],
|
||||||
|
"arrow-parens": ["error", "as-needed"],
|
||||||
|
"arrow-spacing": "error",
|
||||||
|
"no-var": "error",
|
||||||
|
"prefer-template": "error",
|
||||||
|
"prefer-const": "error",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
];
|
|
@ -1,3 +1,4 @@
|
||||||
jest.setTimeout(1 * 1000); // 1 second
|
jest.setTimeout(1 * 1000); // 1 second
|
||||||
jest.resetModules();
|
jest.resetModules();
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
|
jest.useFakeTimers();
|
36
package.json
36
package.json
|
@ -7,7 +7,7 @@
|
||||||
"clean": "rm -rf node_modules/ dist/",
|
"clean": "rm -rf node_modules/ dist/",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"start": "node ./dist/bot.js",
|
"start": "node ./dist/bot.js",
|
||||||
"test": "echo true",
|
"test": "jest",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "eslint . --fix",
|
"lint:fix": "eslint . --fix",
|
||||||
"db:up": "typeorm migration:run -d dist/database/dataSources/appDataSource.js",
|
"db:up": "typeorm migration:run -d dist/database/dataSources/appDataSource.js",
|
||||||
|
@ -27,37 +27,37 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/rest": "^2.0.0",
|
"@discordjs/rest": "^2.0.0",
|
||||||
"@types/clone-deep": "^4.0.4",
|
"@types/clone-deep": "^4.0.4",
|
||||||
"@types/express": "^4.17.20",
|
"@types/express": "^5.0.0",
|
||||||
"@types/jest": "^29.0.0",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/uuid": "^9.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
|
"axios": "^1.8.4",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"canvas": "^2.11.2",
|
"canvas": "^2.11.2",
|
||||||
"clone-deep": "^4.0.1",
|
"clone-deep": "^4.0.1",
|
||||||
"cron": "^3.1.7",
|
"cron": "^3.1.7",
|
||||||
"discord.js": "^14.15.3",
|
"discord.js": "^14.16.3",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"glob": "^10.3.10",
|
"fuse.js": "^7.0.0",
|
||||||
|
"glob": "^11.0.0",
|
||||||
"jest": "^29.0.0",
|
"jest": "^29.0.0",
|
||||||
"jest-mock-extended": "^3.0.0",
|
"jest-mock-extended": "^3.0.0",
|
||||||
"jimp": "^0.22.12",
|
"jimp": "^1.6.0",
|
||||||
"minimatch": "9.0.5",
|
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"ts-jest": "^29.0.0",
|
"ts-jest": "^29.0.0",
|
||||||
"typeorm": "0.3.20",
|
"typeorm": "0.3.20",
|
||||||
"winston": "^3.11.0",
|
"winston": "^3.15.0",
|
||||||
"winston-daily-rotate-file": "^5.0.0",
|
"winston-daily-rotate-file": "^5.0.0",
|
||||||
"winston-discord-transport": "^1.3.0"
|
"winston-discord-transport": "^1.3.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {},
|
||||||
"**/ws": "^8.17.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.0.0",
|
"@types/node": "^22.8.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.11.0",
|
||||||
"@typescript-eslint/parser": "^6.16.0",
|
"@typescript-eslint/parser": "^8.11.0",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^9.13.0",
|
||||||
"np": "^9.0.0",
|
"np": "^10.0.7",
|
||||||
"typescript": "^5.0.0"
|
"typescript": "^5.0.0",
|
||||||
|
"typescript-eslint": "^8.11.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ const client = new CoreClient([
|
||||||
|
|
||||||
Registry.RegisterCommands();
|
Registry.RegisterCommands();
|
||||||
Registry.RegisterButtonEvents();
|
Registry.RegisterButtonEvents();
|
||||||
|
Registry.RegisterStringDropdownEvents();
|
||||||
|
|
||||||
if (!existsSync(`${process.env.DATA_DIR}/cards`) && process.env.GDRIVESYNC_AUTO && process.env.GDRIVESYNC_AUTO == "true") {
|
if (!existsSync(`${process.env.DATA_DIR}/cards`) && process.env.GDRIVESYNC_AUTO && process.env.GDRIVESYNC_AUTO == "true") {
|
||||||
console.log("Card directory not found, syncing...");
|
console.log("Card directory not found, syncing...");
|
||||||
|
|
|
@ -4,14 +4,16 @@ import Inventory from "../database/entities/app/Inventory";
|
||||||
import { CoreClient } from "../client/client";
|
import { CoreClient } from "../client/client";
|
||||||
import { default as eClaim } from "../database/entities/app/Claim";
|
import { default as eClaim } from "../database/entities/app/Claim";
|
||||||
import AppLogger from "../client/appLogger";
|
import AppLogger from "../client/appLogger";
|
||||||
import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
|
||||||
import User from "../database/entities/app/User";
|
import User from "../database/entities/app/User";
|
||||||
import CardConstants from "../constants/CardConstants";
|
import CardConstants from "../constants/CardConstants";
|
||||||
|
import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper";
|
||||||
|
import DropEmbedHelper from "../helpers/DropHelpers/DropEmbedHelper";
|
||||||
|
|
||||||
export default class Claim extends ButtonEvent {
|
export default class Claim extends ButtonEvent {
|
||||||
public override async execute(interaction: ButtonInteraction) {
|
public override async execute(interaction: ButtonInteraction) {
|
||||||
if (!interaction.guild || !interaction.guildId) return;
|
if (!interaction.guild || !interaction.guildId) return;
|
||||||
if (!interaction.channel) return;
|
if (!interaction.channel) return;
|
||||||
|
if (!interaction.channel.isSendable()) return;
|
||||||
|
|
||||||
await interaction.deferUpdate();
|
await interaction.deferUpdate();
|
||||||
|
|
||||||
|
@ -58,7 +60,7 @@ export default class Claim extends ButtonEvent {
|
||||||
if (!inventory) {
|
if (!inventory) {
|
||||||
inventory = new Inventory(userId, cardNumber, 1);
|
inventory = new Inventory(userId, cardNumber, 1);
|
||||||
} else {
|
} else {
|
||||||
inventory.SetQuantity(inventory.Quantity + 1);
|
inventory.AddQuantity(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
await inventory.Save(Inventory, inventory);
|
await inventory.Save(Inventory, inventory);
|
||||||
|
@ -68,16 +70,18 @@ export default class Claim extends ButtonEvent {
|
||||||
|
|
||||||
await claim.Save(eClaim, claim);
|
await claim.Save(eClaim, claim);
|
||||||
|
|
||||||
const card = CardDropHelperMetadata.GetCardByCardNumber(cardNumber);
|
const card = GetCardsHelper.GetCardByCardNumber(cardNumber);
|
||||||
|
|
||||||
if (!card) {
|
if (!card) {
|
||||||
|
AppLogger.LogError("Button/Claim", `Unable to find card, ${cardNumber}`);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageFileName = card.card.path.split("/").pop()!;
|
const imageFileName = card.card.path.split("/").pop()!;
|
||||||
|
|
||||||
const embed = CardDropHelperMetadata.GenerateDropEmbed(card, inventory.Quantity, imageFileName, interaction.user.username, user.Currency);
|
const embed = DropEmbedHelper.GenerateDropEmbed(card, inventory.Quantity, imageFileName, interaction.user.username, user.Currency);
|
||||||
const row = CardDropHelperMetadata.GenerateDropButtons(card, claimId, interaction.user.id, true);
|
const row = DropEmbedHelper.GenerateDropButtons(card, claimId, interaction.user.id, true);
|
||||||
|
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
embeds: [ embed ],
|
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 ],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,7 +34,7 @@ export default class Inventory extends ButtonEvent {
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
files: [ embed.image ],
|
files: [ embed.image ],
|
||||||
embeds: [ embed.embed ],
|
embeds: [ embed.embed ],
|
||||||
components: [ embed.row ],
|
components: [ embed.row1, embed.row2 ],
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
AppLogger.LogError("Button/Inventory", `Error generating inventory page for ${member.user.username} with id ${member.user.id}: ${e}`);
|
AppLogger.LogError("Button/Inventory", `Error generating inventory page for ${member.user.username} with id ${member.user.id}: ${e}`);
|
||||||
|
|
214
src/buttonEvents/Multidrop.ts
Normal file
214
src/buttonEvents/Multidrop.ts
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
import { AttachmentBuilder, ButtonInteraction, EmbedBuilder } from "discord.js";
|
||||||
|
import { ButtonEvent } from "../type/buttonEvent";
|
||||||
|
import AppLogger from "../client/appLogger";
|
||||||
|
import Inventory from "../database/entities/app/Inventory";
|
||||||
|
import EmbedColours from "../constants/EmbedColours";
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
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) {
|
||||||
|
const action = interaction.customId.split(" ")[1];
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case "keep":
|
||||||
|
await this.Keep(interaction);
|
||||||
|
break;
|
||||||
|
case "sacrifice":
|
||||||
|
await this.Sacrifice(interaction);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
await interaction.reply("Invalid action");
|
||||||
|
AppLogger.LogError("Button/Multidrop", `Invalid action, ${action}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Keep(interaction: ButtonInteraction) {
|
||||||
|
const cardNumber = interaction.customId.split(" ")[2];
|
||||||
|
let cardsRemaining = Number(interaction.customId.split(" ")[3]) || 0;
|
||||||
|
const userId = interaction.customId.split(" ")[4];
|
||||||
|
|
||||||
|
if (interaction.user.id != userId) {
|
||||||
|
await interaction.reply("You're not the user this drop was made for!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const card = GetCardsHelper.GetCardByCardNumber(cardNumber);
|
||||||
|
|
||||||
|
if (!card) {
|
||||||
|
await interaction.reply("Unable to find card.");
|
||||||
|
AppLogger.LogWarn("Button/Multidrop/Keep", `Card not found, ${cardNumber}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cardsRemaining < 0) {
|
||||||
|
await interaction.reply("Your multidrop has ran out! Please buy a new one!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await User.FetchOneById(User, interaction.user.id);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
AppLogger.LogWarn("Button/Multidrop/Keep", ErrorMessages.UnableToFetchUser);
|
||||||
|
await interaction.reply(ErrorMessages.UnableToFetchUser);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claim
|
||||||
|
let inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, cardNumber);
|
||||||
|
|
||||||
|
if (!inventory) {
|
||||||
|
inventory = new Inventory(interaction.user.id, cardNumber, 1);
|
||||||
|
} else {
|
||||||
|
inventory.AddQuantity(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
await inventory.Save(Inventory, inventory);
|
||||||
|
|
||||||
|
// Pack has ran out
|
||||||
|
if (cardsRemaining == 0) {
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setDescription("Your multidrop has ran out! Please buy a new one!")
|
||||||
|
.setColor(EmbedColours.Ok);
|
||||||
|
|
||||||
|
await interaction.update({
|
||||||
|
embeds: [ embed ],
|
||||||
|
attachments: [],
|
||||||
|
components: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop next card
|
||||||
|
const randomCard = GetCardsHelper.GetRandomCard();
|
||||||
|
cardsRemaining -= 1;
|
||||||
|
|
||||||
|
if (!randomCard) {
|
||||||
|
AppLogger.LogWarn("Button/Multidrop/Keep", ErrorMessages.UnableToFetchCard);
|
||||||
|
await interaction.reply(ErrorMessages.UnableToFetchCard);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||||
|
|
||||||
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
||||||
|
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
||||||
|
|
||||||
|
const embed = MultidropEmbedHelper.GenerateMultidropEmbed(randomCard, quantityClaimed, imageFileName, cardsRemaining, undefined, user.Currency);
|
||||||
|
|
||||||
|
const row = MultidropEmbedHelper.GenerateMultidropButtons(randomCard, cardsRemaining, interaction.user.id, cardsRemaining < 0);
|
||||||
|
|
||||||
|
await interaction.editReply({
|
||||||
|
embeds: [ embed ],
|
||||||
|
files: [ attachment ],
|
||||||
|
components: [ row ],
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
AppLogger.LogError("Button/Multidrop/Keep", `Error sending next drop for card ${randomCard.card.id}: ${e}`);
|
||||||
|
|
||||||
|
await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. (${randomCard.card.id})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Sacrifice(interaction: ButtonInteraction) {
|
||||||
|
const cardNumber = interaction.customId.split(" ")[2];
|
||||||
|
let cardsRemaining = Number(interaction.customId.split(" ")[3]) || 0;
|
||||||
|
const userId = interaction.customId.split(" ")[4];
|
||||||
|
|
||||||
|
if (interaction.user.id != userId) {
|
||||||
|
await interaction.reply("You're not the user this drop was made for!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const card = GetCardsHelper.GetCardByCardNumber(cardNumber);
|
||||||
|
|
||||||
|
if (!card) {
|
||||||
|
await interaction.reply("Unable to find card.");
|
||||||
|
AppLogger.LogWarn("Button/Multidrop/Sacrifice", `Card not found, ${cardNumber}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cardsRemaining < 0) {
|
||||||
|
await interaction.reply("Your multidrop has ran out! Please buy a new one!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await User.FetchOneById(User, interaction.user.id);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
AppLogger.LogWarn("Button/Multidrop/Sacrifice", ErrorMessages.UnableToFetchUser);
|
||||||
|
await interaction.reply(ErrorMessages.UnableToFetchUser);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sacrifice
|
||||||
|
const sacrificeAmount = GetSacrificeAmount(card.card.type);
|
||||||
|
|
||||||
|
user.AddCurrency(sacrificeAmount);
|
||||||
|
|
||||||
|
await user.Save(User, user);
|
||||||
|
|
||||||
|
// Pack has ran out
|
||||||
|
if (cardsRemaining == 0) {
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setDescription("Your multidrop has ran out! Please buy a new one!")
|
||||||
|
.setColor(EmbedColours.Ok);
|
||||||
|
|
||||||
|
await interaction.update({
|
||||||
|
embeds: [ embed ],
|
||||||
|
attachments: [],
|
||||||
|
components: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop next card
|
||||||
|
const randomCard = GetCardsHelper.GetRandomCard();
|
||||||
|
cardsRemaining -= 1;
|
||||||
|
|
||||||
|
if (!randomCard) {
|
||||||
|
AppLogger.LogWarn("Button/Multidrop/Sacrifice", ErrorMessages.UnableToFetchCard);
|
||||||
|
await interaction.reply(ErrorMessages.UnableToFetchCard);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||||
|
|
||||||
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
||||||
|
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
||||||
|
|
||||||
|
const embed = MultidropEmbedHelper.GenerateMultidropEmbed(randomCard, quantityClaimed, imageFileName, cardsRemaining, undefined, user.Currency);
|
||||||
|
|
||||||
|
const row = MultidropEmbedHelper.GenerateMultidropButtons(randomCard, cardsRemaining, interaction.user.id, cardsRemaining < 0);
|
||||||
|
|
||||||
|
await interaction.editReply({
|
||||||
|
embeds: [ embed ],
|
||||||
|
files: [ attachment ],
|
||||||
|
components: [ row ],
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
AppLogger.LogError("Button/Multidrop/Sacrifice", `Error sending next drop for card ${randomCard.card.id}: ${e}`);
|
||||||
|
|
||||||
|
await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. (${randomCard.card.id})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,11 +5,12 @@ import { v4 } from "uuid";
|
||||||
import { CoreClient } from "../client/client";
|
import { CoreClient } from "../client/client";
|
||||||
import Inventory from "../database/entities/app/Inventory";
|
import Inventory from "../database/entities/app/Inventory";
|
||||||
import Config from "../database/entities/app/Config";
|
import Config from "../database/entities/app/Config";
|
||||||
import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import AppLogger from "../client/appLogger";
|
import AppLogger from "../client/appLogger";
|
||||||
import User from "../database/entities/app/User";
|
import User from "../database/entities/app/User";
|
||||||
import CardConstants from "../constants/CardConstants";
|
import CardConstants from "../constants/CardConstants";
|
||||||
|
import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper";
|
||||||
|
import DropEmbedHelper from "../helpers/DropHelpers/DropEmbedHelper";
|
||||||
|
|
||||||
export default class Reroll extends ButtonEvent {
|
export default class Reroll extends ButtonEvent {
|
||||||
public override async execute(interaction: ButtonInteraction) {
|
public override async execute(interaction: ButtonInteraction) {
|
||||||
|
@ -39,7 +40,7 @@ export default class Reroll extends ButtonEvent {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const randomCard = CardDropHelperMetadata.GetRandomCard();
|
const randomCard = await GetCardsHelper.FetchCard(interaction.user.id);
|
||||||
|
|
||||||
if (!randomCard) {
|
if (!randomCard) {
|
||||||
await interaction.reply("Unable to fetch card, please try again.");
|
await interaction.reply("Unable to fetch card, please try again.");
|
||||||
|
@ -66,11 +67,11 @@ export default class Reroll extends ButtonEvent {
|
||||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
||||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
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 claimId = v4();
|
||||||
|
|
||||||
const row = CardDropHelperMetadata.GenerateDropButtons(randomCard, claimId, interaction.user.id);
|
const row = DropEmbedHelper.GenerateDropButtons(randomCard, claimId, interaction.user.id);
|
||||||
|
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
embeds: [ embed ],
|
embeds: [ embed ],
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, EmbedBuilder } from "discord.js";
|
import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, EmbedBuilder } from "discord.js";
|
||||||
import { ButtonEvent } from "../type/buttonEvent";
|
import { ButtonEvent } from "../type/buttonEvent";
|
||||||
import Inventory from "../database/entities/app/Inventory";
|
import Inventory from "../database/entities/app/Inventory";
|
||||||
import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
|
||||||
import { CardRarityToString, GetSacrificeAmount } from "../constants/CardRarity";
|
import { CardRarityToString, GetSacrificeAmount } from "../constants/CardRarity";
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
import EmbedColours from "../constants/EmbedColours";
|
||||||
import User from "../database/entities/app/User";
|
import User from "../database/entities/app/User";
|
||||||
|
import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper";
|
||||||
|
|
||||||
export default class Sacrifice extends ButtonEvent {
|
export default class Sacrifice extends ButtonEvent {
|
||||||
public override async execute(interaction: ButtonInteraction) {
|
public override async execute(interaction: ButtonInteraction) {
|
||||||
|
@ -23,6 +23,7 @@ export default class Sacrifice extends ButtonEvent {
|
||||||
private async confirm(interaction: ButtonInteraction) {
|
private async confirm(interaction: ButtonInteraction) {
|
||||||
const userId = interaction.customId.split(" ")[2];
|
const userId = interaction.customId.split(" ")[2];
|
||||||
const cardNumber = interaction.customId.split(" ")[3];
|
const cardNumber = interaction.customId.split(" ")[3];
|
||||||
|
const quantity = Number(interaction.customId.split(" ")[4]) || 1;
|
||||||
|
|
||||||
if (userId != interaction.user.id) {
|
if (userId != interaction.user.id) {
|
||||||
await interaction.reply("Only the user who created this sacrifice can confirm it.");
|
await interaction.reply("Only the user who created this sacrifice can confirm it.");
|
||||||
|
@ -31,12 +32,17 @@ export default class Sacrifice extends ButtonEvent {
|
||||||
|
|
||||||
const cardInInventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber);
|
const cardInInventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber);
|
||||||
|
|
||||||
if (!cardInInventory) {
|
if (!cardInInventory || cardInInventory.Quantity == 0) {
|
||||||
await interaction.reply("Unable to find card in inventory.");
|
await interaction.reply("Unable to find card in inventory.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardNumber);
|
if (cardInInventory.Quantity < quantity) {
|
||||||
|
await interaction.reply("You can only sacrifice what you own.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cardData = GetCardsHelper.GetCardByCardNumber(cardNumber);
|
||||||
|
|
||||||
if (!cardData) {
|
if (!cardData) {
|
||||||
await interaction.reply("Unable to find card in the database.");
|
await interaction.reply("Unable to find card in the database.");
|
||||||
|
@ -50,11 +56,11 @@ export default class Sacrifice extends ButtonEvent {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cardInInventory.RemoveQuantity(1);
|
cardInInventory.RemoveQuantity(quantity);
|
||||||
|
|
||||||
await cardInInventory.Save(Inventory, cardInInventory);
|
await cardInInventory.Save(Inventory, cardInInventory);
|
||||||
|
|
||||||
const cardValue = GetSacrificeAmount(cardData.card.type);
|
const cardValue = GetSacrificeAmount(cardData.card.type) * quantity;
|
||||||
const cardRarityString = CardRarityToString(cardData.card.type);
|
const cardRarityString = CardRarityToString(cardData.card.type);
|
||||||
|
|
||||||
user.AddCurrency(cardValue);
|
user.AddCurrency(cardValue);
|
||||||
|
@ -66,6 +72,7 @@ export default class Sacrifice extends ButtonEvent {
|
||||||
`Series: ${cardData.series.name}`,
|
`Series: ${cardData.series.name}`,
|
||||||
`Rarity: ${cardRarityString}`,
|
`Rarity: ${cardRarityString}`,
|
||||||
`Quantity Owned: ${cardInInventory.Quantity}`,
|
`Quantity Owned: ${cardInInventory.Quantity}`,
|
||||||
|
`Quantity To Sacrifice: ${quantity}`,
|
||||||
`Sacrifice Amount: ${cardValue}`,
|
`Sacrifice Amount: ${cardValue}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -98,6 +105,7 @@ export default class Sacrifice extends ButtonEvent {
|
||||||
private async cancel(interaction: ButtonInteraction) {
|
private async cancel(interaction: ButtonInteraction) {
|
||||||
const userId = interaction.customId.split(" ")[2];
|
const userId = interaction.customId.split(" ")[2];
|
||||||
const cardNumber = interaction.customId.split(" ")[3];
|
const cardNumber = interaction.customId.split(" ")[3];
|
||||||
|
const quantity = Number(interaction.customId.split(" ")[4]) || 1;
|
||||||
|
|
||||||
if (userId != interaction.user.id) {
|
if (userId != interaction.user.id) {
|
||||||
await interaction.reply("Only the user who created this sacrifice can cancel it.");
|
await interaction.reply("Only the user who created this sacrifice can cancel it.");
|
||||||
|
@ -106,19 +114,24 @@ export default class Sacrifice extends ButtonEvent {
|
||||||
|
|
||||||
const cardInInventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber);
|
const cardInInventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber);
|
||||||
|
|
||||||
if (!cardInInventory) {
|
if (!cardInInventory || cardInInventory.Quantity == 0) {
|
||||||
await interaction.reply("Unable to find card in inventory.");
|
await interaction.reply("Unable to find card in inventory.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardNumber);
|
if (cardInInventory.Quantity < quantity) {
|
||||||
|
await interaction.reply("You can only sacrifice what you own.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cardData = GetCardsHelper.GetCardByCardNumber(cardNumber);
|
||||||
|
|
||||||
if (!cardData) {
|
if (!cardData) {
|
||||||
await interaction.reply("Unable to find card in the database.");
|
await interaction.reply("Unable to find card in the database.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cardValue = GetSacrificeAmount(cardData.card.type);
|
const cardValue = GetSacrificeAmount(cardData.card.type) * quantity;
|
||||||
const cardRarityString = CardRarityToString(cardData.card.type);
|
const cardRarityString = CardRarityToString(cardData.card.type);
|
||||||
|
|
||||||
const description = [
|
const description = [
|
||||||
|
@ -126,6 +139,7 @@ export default class Sacrifice extends ButtonEvent {
|
||||||
`Series: ${cardData.series.name}`,
|
`Series: ${cardData.series.name}`,
|
||||||
`Rarity: ${cardRarityString}`,
|
`Rarity: ${cardRarityString}`,
|
||||||
`Quantity Owned: ${cardInInventory.Quantity}`,
|
`Quantity Owned: ${cardInInventory.Quantity}`,
|
||||||
|
`Quantity To Sacrifice: ${quantity}`,
|
||||||
`Sacrifice Amount: ${cardValue}`,
|
`Sacrifice Amount: ${cardValue}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,10 @@ export default class Trade extends ButtonEvent {
|
||||||
const user2CardNumber = interaction.customId.split(" ")[5];
|
const user2CardNumber = interaction.customId.split(" ")[5];
|
||||||
const expiry = interaction.customId.split(" ")[6];
|
const expiry = interaction.customId.split(" ")[6];
|
||||||
const timeoutId = interaction.customId.split(" ")[7];
|
const timeoutId = interaction.customId.split(" ")[7];
|
||||||
|
const user1Quantity = Number(interaction.customId.split(" ")[8]) || 1;
|
||||||
|
const user2Quantity = Number(interaction.customId.split(" ")[9]) || 1;
|
||||||
|
|
||||||
AppLogger.LogSilly("Button/Trade/AcceptTrade", `Parameters: user1UserId=${user1UserId}, user2UserId=${user2UserId}, user1CardNumber=${user1CardNumber}, user2CardNumber=${user2CardNumber}, expiry=${expiry}, timeoutId=${timeoutId}`);
|
AppLogger.LogSilly("Button/Trade/AcceptTrade", `Parameters: user1UserId=${user1UserId}, user2UserId=${user2UserId}, user1CardNumber=${user1CardNumber}, user2CardNumber=${user2CardNumber}, expiry=${expiry}, timeoutId=${timeoutId} user1Quantity=${user1Quantity} user2Quantity=${user2Quantity}`);
|
||||||
|
|
||||||
const expiryDate = new Date(expiry);
|
const expiryDate = new Date(expiry);
|
||||||
|
|
||||||
|
@ -67,13 +69,13 @@ export default class Trade extends ButtonEvent {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user1UserInventory1.Quantity < 1 || user2UserInventory1.Quantity < 1) {
|
if (user1UserInventory1.Quantity < user1Quantity || user2UserInventory1.Quantity < user2Quantity) {
|
||||||
await interaction.reply("One or more of the items you are trying to trade does not exist.");
|
await interaction.reply("One or more of the items you are trying to trade does not exist.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
user1UserInventory1.RemoveQuantity(1);
|
user1UserInventory1.RemoveQuantity(user1Quantity);
|
||||||
user2UserInventory1.RemoveQuantity(1);
|
user2UserInventory1.RemoveQuantity(user2Quantity);
|
||||||
|
|
||||||
await user1UserInventory1.Save(Inventory, user1UserInventory1);
|
await user1UserInventory1.Save(Inventory, user1UserInventory1);
|
||||||
await user2UserInventory1.Save(Inventory, user2UserInventory1);
|
await user2UserInventory1.Save(Inventory, user2UserInventory1);
|
||||||
|
@ -82,15 +84,15 @@ export default class Trade extends ButtonEvent {
|
||||||
let user2UserInventory2 = await Inventory.FetchOneByCardNumberAndUserId(user2UserId, user1CardNumber);
|
let user2UserInventory2 = await Inventory.FetchOneByCardNumberAndUserId(user2UserId, user1CardNumber);
|
||||||
|
|
||||||
if (!user1UserInventory2) {
|
if (!user1UserInventory2) {
|
||||||
user1UserInventory2 = new Inventory(user1UserId, user2CardNumber, 1);
|
user1UserInventory2 = new Inventory(user1UserId, user2CardNumber, user2Quantity);
|
||||||
} else {
|
} else {
|
||||||
user1UserInventory2.AddQuantity(1);
|
user1UserInventory2.AddQuantity(user2Quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user2UserInventory2) {
|
if (!user2UserInventory2) {
|
||||||
user2UserInventory2 = new Inventory(user2UserId, user1CardNumber, 1);
|
user2UserInventory2 = new Inventory(user2UserId, user1CardNumber, user1Quantity);
|
||||||
} else {
|
} else {
|
||||||
user2UserInventory2.AddQuantity(1);
|
user2UserInventory2.AddQuantity(user1Quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
await user1UserInventory2.Save(Inventory, user1UserInventory2);
|
await user1UserInventory2.Save(Inventory, user1UserInventory2);
|
||||||
|
@ -106,12 +108,12 @@ export default class Trade extends ButtonEvent {
|
||||||
.addFields([
|
.addFields([
|
||||||
{
|
{
|
||||||
name: `${user1User.username} Receives`,
|
name: `${user1User.username} Receives`,
|
||||||
value: `${user2Item.id}: ${user2Item.name}`,
|
value: `${user2Item.id}: ${user2Item.name} x${user2Quantity}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `${user2User.username} Receives`,
|
name: `${user2User.username} Receives`,
|
||||||
value: `${user1Item.id}: ${user1Item.name}`,
|
value: `${user1Item.id}: ${user1Item.name} x${user1Quantity}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -144,6 +146,8 @@ export default class Trade extends ButtonEvent {
|
||||||
const user2CardNumber = interaction.customId.split(" ")[5];
|
const user2CardNumber = interaction.customId.split(" ")[5];
|
||||||
// No need to get expiry date
|
// No need to get expiry date
|
||||||
const timeoutId = interaction.customId.split(" ")[7];
|
const timeoutId = interaction.customId.split(" ")[7];
|
||||||
|
const user1Quantity = Number(interaction.customId.split(" ")[8]) || 1;
|
||||||
|
const user2Quantity = Number(interaction.customId.split(" ")[9]) || 1;
|
||||||
|
|
||||||
AppLogger.LogSilly("Button/Trade/DeclineTrade", `Parameters: user1UserId=${user1UserId}, user2UserId=${user2UserId}, user1CardNumber=${user1CardNumber}, user2CardNumber=${user2CardNumber}, timeoutId=${timeoutId}`);
|
AppLogger.LogSilly("Button/Trade/DeclineTrade", `Parameters: user1UserId=${user1UserId}, user2UserId=${user2UserId}, user1CardNumber=${user1CardNumber}, user2CardNumber=${user2CardNumber}, timeoutId=${timeoutId}`);
|
||||||
|
|
||||||
|
@ -178,12 +182,12 @@ export default class Trade extends ButtonEvent {
|
||||||
.addFields([
|
.addFields([
|
||||||
{
|
{
|
||||||
name: `${user1User.username} Receives`,
|
name: `${user1User.username} Receives`,
|
||||||
value: `${user2Item.id}: ${user2Item.name}`,
|
value: `${user2Item.id}: ${user2Item.name} x${user2Quantity}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `${user2User.username} Receives`,
|
name: `${user2User.username} Receives`,
|
||||||
value: `${user1Item.id}: ${user1Item.name}`,
|
value: `${user1Item.id}: ${user1Item.name} x${user1Quantity}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
25
src/buttonEvents/View.ts
Normal file
25
src/buttonEvents/View.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import {ButtonInteraction} from "discord.js";
|
||||||
|
import {ButtonEvent} from "../type/buttonEvent.js";
|
||||||
|
import CardSearchHelper from "../helpers/CardSearchHelper.js";
|
||||||
|
|
||||||
|
export default class View extends ButtonEvent {
|
||||||
|
public override async execute(interaction: ButtonInteraction) {
|
||||||
|
const page = interaction.customId.split(" ")[1];
|
||||||
|
const results = interaction.customId.split(" ").splice(2);
|
||||||
|
|
||||||
|
await interaction.deferUpdate();
|
||||||
|
|
||||||
|
const searchResult = await CardSearchHelper.GenerateSearchPageFromQuery(results, interaction.user.id, Number(page));
|
||||||
|
|
||||||
|
if (!searchResult) {
|
||||||
|
await interaction.followUp("No results found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.editReply({
|
||||||
|
embeds: [ searchResult.embed ],
|
||||||
|
components: [ searchResult.row ],
|
||||||
|
files: searchResult.attachments,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,11 +17,14 @@ import AppLogger from "./appLogger";
|
||||||
import TimerHelper from "../helpers/TimerHelper";
|
import TimerHelper from "../helpers/TimerHelper";
|
||||||
import GiveCurrency from "../timers/GiveCurrency";
|
import GiveCurrency from "../timers/GiveCurrency";
|
||||||
import PurgeClaims from "../timers/PurgeClaims";
|
import PurgeClaims from "../timers/PurgeClaims";
|
||||||
|
import StringDropdownEventItem from "../contracts/StringDropdownEventItem";
|
||||||
|
import {StringDropdownEvent} from "../type/stringDropdownEvent";
|
||||||
|
|
||||||
export class CoreClient extends Client {
|
export class CoreClient extends Client {
|
||||||
private static _commandItems: ICommandItem[];
|
private static _commandItems: ICommandItem[];
|
||||||
private static _eventExecutors: EventExecutors;
|
private static _eventExecutors: EventExecutors;
|
||||||
private static _buttonEvents: IButtonEventItem[];
|
private static _buttonEvents: IButtonEventItem[];
|
||||||
|
private static _stringDropdowns: StringDropdownEventItem[];
|
||||||
|
|
||||||
private _events: Events;
|
private _events: Events;
|
||||||
private _util: Util;
|
private _util: Util;
|
||||||
|
@ -45,6 +48,10 @@ export class CoreClient extends Client {
|
||||||
return this._buttonEvents;
|
return this._buttonEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static get stringDropdowns(): StringDropdownEventItem[] {
|
||||||
|
return this._stringDropdowns;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(intents: number[]) {
|
constructor(intents: number[]) {
|
||||||
super({ intents: intents });
|
super({ intents: intents });
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
@ -59,6 +66,7 @@ export class CoreClient extends Client {
|
||||||
|
|
||||||
CoreClient._commandItems = [];
|
CoreClient._commandItems = [];
|
||||||
CoreClient._buttonEvents = [];
|
CoreClient._buttonEvents = [];
|
||||||
|
CoreClient._stringDropdowns = [];
|
||||||
|
|
||||||
this._events = new Events();
|
this._events = new Events();
|
||||||
this._util = new Util();
|
this._util = new Util();
|
||||||
|
@ -408,4 +416,19 @@ export class CoreClient extends Client {
|
||||||
AppLogger.LogVerbose("Client", `Registered Button Event: ${buttonId}`);
|
AppLogger.LogVerbose("Client", `Registered Button Event: ${buttonId}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static RegisterStringDropdownEvent(dropdownId: string, event: StringDropdownEvent, environment: Environment = Environment.All) {
|
||||||
|
const item: StringDropdownEventItem = {
|
||||||
|
DropdownId: dropdownId,
|
||||||
|
Event: event,
|
||||||
|
Environment: environment,
|
||||||
|
};
|
||||||
|
|
||||||
|
if ((environment & CoreClient.Environment) == CoreClient.Environment) {
|
||||||
|
CoreClient._stringDropdowns.push(item);
|
||||||
|
|
||||||
|
AppLogger.LogVerbose("Client", `Registered String Dropdown Event: ${dropdownId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import ChatInputCommand from "./interactionCreate/ChatInputCommand";
|
||||||
import Button from "./interactionCreate/Button";
|
import Button from "./interactionCreate/Button";
|
||||||
import AppLogger from "./appLogger";
|
import AppLogger from "./appLogger";
|
||||||
import NewUserDiscovery from "./interactionCreate/middleware/NewUserDiscovery";
|
import NewUserDiscovery from "./interactionCreate/middleware/NewUserDiscovery";
|
||||||
|
import StringDropdown from "./interactionCreate/StringDropdown";
|
||||||
|
|
||||||
export class Events {
|
export class Events {
|
||||||
public async onInteractionCreate(interaction: Interaction) {
|
public async onInteractionCreate(interaction: Interaction) {
|
||||||
|
@ -19,6 +20,11 @@ export class Events {
|
||||||
AppLogger.LogVerbose("Client", `Button: ${interaction.customId}`);
|
AppLogger.LogVerbose("Client", `Button: ${interaction.customId}`);
|
||||||
Button.onButtonClicked(interaction);
|
Button.onButtonClicked(interaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (interaction.isStringSelectMenu()) {
|
||||||
|
AppLogger.LogVerbose("Client", `StringDropdown: ${interaction.customId}`);
|
||||||
|
StringDropdown.onStringDropdownSelected(interaction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit when bot is logged in and ready to use
|
// Emit when bot is logged in and ready to use
|
||||||
|
|
29
src/client/interactionCreate/StringDropdown.ts
Normal file
29
src/client/interactionCreate/StringDropdown.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import {StringSelectMenuInteraction} from "discord.js";
|
||||||
|
import {CoreClient} from "../client";
|
||||||
|
import AppLogger from "../appLogger";
|
||||||
|
|
||||||
|
export default class StringDropdown {
|
||||||
|
public static async onStringDropdownSelected(interaction: StringSelectMenuInteraction) {
|
||||||
|
if (!interaction.isStringSelectMenu()) return;
|
||||||
|
|
||||||
|
const item = CoreClient.stringDropdowns.find(x => x.DropdownId == interaction.customId.split(" ")[0]);
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
AppLogger.LogVerbose("StringDropdown", `Event not found: ${interaction.customId}`);
|
||||||
|
|
||||||
|
await interaction.reply("Event not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
AppLogger.LogDebug("StringDropdown", `Executing ${interaction.customId}`);
|
||||||
|
|
||||||
|
item.Event.execute(interaction);
|
||||||
|
} catch (e) {
|
||||||
|
AppLogger.LogError("StringDropdown", `Error occurred while executing event: ${interaction.customId}`);
|
||||||
|
AppLogger.LogError("StringDropdown", e as string);
|
||||||
|
|
||||||
|
await interaction.reply("An error occurred while executing the event");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,11 +5,13 @@ import { CoreClient } from "../client/client";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
import Inventory from "../database/entities/app/Inventory";
|
import Inventory from "../database/entities/app/Inventory";
|
||||||
import Config from "../database/entities/app/Config";
|
import Config from "../database/entities/app/Config";
|
||||||
import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import AppLogger from "../client/appLogger";
|
import AppLogger from "../client/appLogger";
|
||||||
import User from "../database/entities/app/User";
|
import User from "../database/entities/app/User";
|
||||||
import CardConstants from "../constants/CardConstants";
|
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 {
|
export default class Drop extends Command {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -22,14 +24,13 @@ export default class Drop extends Command {
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
public override async execute(interaction: CommandInteraction) {
|
||||||
if (!CoreClient.AllowDrops) {
|
if (!CoreClient.AllowDrops) {
|
||||||
await interaction.reply("Bot is currently syncing, please wait until its done.");
|
await interaction.reply(ErrorMessages.BotSyncing);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await Config.GetValue("safemode") == "true") {
|
if (await Config.GetValue("safemode") == "true") {
|
||||||
AppLogger.LogWarn("Commands/Drop", "Safe Mode is active, refusing to send next drop.");
|
AppLogger.LogWarn("Commands/Drop", ErrorMessages.SafeMode);
|
||||||
|
await interaction.reply(ErrorMessages.SafeMode);
|
||||||
await interaction.reply("Safe Mode has been activated, please resync to continue.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,16 +44,15 @@ export default class Drop extends Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.Currency < CardConstants.ClaimCost) {
|
if (user.Currency < CardConstants.ClaimCost) {
|
||||||
await interaction.reply(`Not enough currency! You need ${CardConstants.ClaimCost} currency, you have ${user.Currency}!`);
|
await interaction.reply(ErrorMessages.NotEnoughCurrency(CardConstants.ClaimCost, user.Currency));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const randomCard = CardDropHelperMetadata.GetRandomCard();
|
const randomCard = await GetCardsHelper.FetchCard(interaction.user.id);
|
||||||
|
|
||||||
if (!randomCard) {
|
if (!randomCard) {
|
||||||
AppLogger.LogWarn("Commands/Drop", "Unable to fetch card, please try again. (randomCard is null)");
|
AppLogger.LogWarn("Commands/Drop", ErrorMessages.UnableToFetchCard);
|
||||||
|
await interaction.reply(ErrorMessages.UnableToFetchCard);
|
||||||
await interaction.reply("Unable to fetch card, please try again.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,11 +74,11 @@ export default class Drop extends Command {
|
||||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
||||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
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 claimId = v4();
|
||||||
|
|
||||||
const row = CardDropHelperMetadata.GenerateDropButtons(randomCard, claimId, interaction.user.id);
|
const row = DropEmbedHelper.GenerateDropButtons(randomCard, claimId, interaction.user.id);
|
||||||
|
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
embeds: [ embed ],
|
embeds: [ embed ],
|
||||||
|
|
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 { Command } from "../type/command";
|
||||||
import { CoreClient } from "../client/client";
|
import { CoreClient } from "../client/client";
|
||||||
import Config from "../database/entities/app/Config";
|
import Config from "../database/entities/app/Config";
|
||||||
import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
|
||||||
import Inventory from "../database/entities/app/Inventory";
|
import Inventory from "../database/entities/app/Inventory";
|
||||||
import AppLogger from "../client/appLogger";
|
import AppLogger from "../client/appLogger";
|
||||||
import User from "../database/entities/app/User";
|
import User from "../database/entities/app/User";
|
||||||
|
import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper";
|
||||||
|
|
||||||
export default class Give extends Command {
|
export default class Give extends Command {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -81,7 +81,7 @@ export default class Give extends Command {
|
||||||
|
|
||||||
AppLogger.LogSilly("Commands/Give/GiveCard", `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());
|
const card = GetCardsHelper.GetCardByCardNumber(cardNumber.value!.toString());
|
||||||
|
|
||||||
if (!card) {
|
if (!card) {
|
||||||
await interaction.reply("Unable to fetch card, please try again.");
|
await interaction.reply("Unable to fetch card, please try again.");
|
||||||
|
|
80
src/commands/id.ts
Normal file
80
src/commands/id.ts
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import { AttachmentBuilder, CommandInteraction, DiscordAPIError, SlashCommandBuilder } from "discord.js";
|
||||||
|
import { Command } from "../type/command";
|
||||||
|
import { CoreClient } from "../client/client";
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import Inventory from "../database/entities/app/Inventory";
|
||||||
|
import AppLogger from "../client/appLogger";
|
||||||
|
import DropEmbedHelper from "../helpers/DropHelpers/DropEmbedHelper";
|
||||||
|
|
||||||
|
export default class Id extends Command {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.CommandBuilder = new SlashCommandBuilder()
|
||||||
|
.setName("id")
|
||||||
|
.setDescription("View a specific command by its id")
|
||||||
|
.addStringOption(x =>
|
||||||
|
x
|
||||||
|
.setName("cardnumber")
|
||||||
|
.setDescription("The card number to view")
|
||||||
|
.setRequired(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async execute(interaction: CommandInteraction) {
|
||||||
|
const cardNumber = interaction.options.get("cardnumber");
|
||||||
|
|
||||||
|
AppLogger.LogSilly("Commands/View", `Parameters: cardNumber=${cardNumber?.value}`);
|
||||||
|
|
||||||
|
if (!cardNumber || !cardNumber.value) {
|
||||||
|
await interaction.reply("Card number is required.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const card = CoreClient.Cards
|
||||||
|
.flatMap(x => x.cards)
|
||||||
|
.find(x => x.id == cardNumber.value);
|
||||||
|
|
||||||
|
if (!card) {
|
||||||
|
await interaction.reply("Card not found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const series = CoreClient.Cards
|
||||||
|
.find(x => x.cards.includes(card))!;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.deferReply();
|
||||||
|
|
||||||
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.id);
|
||||||
|
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
||||||
|
|
||||||
|
const embed = DropEmbedHelper.GenerateDropEmbed({ card, series }, quantityClaimed, imageFileName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await interaction.editReply({
|
||||||
|
embeds: [ embed ],
|
||||||
|
files: files,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
AppLogger.LogError("Commands/View", `Error sending view for card ${card.id}: ${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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,7 +47,7 @@ export default class Inventory extends Command {
|
||||||
await interaction.followUp({
|
await interaction.followUp({
|
||||||
files: [ embed.image ],
|
files: [ embed.image ],
|
||||||
embeds: [ embed.embed ],
|
embeds: [ embed.embed ],
|
||||||
components: [ embed.row ],
|
components: [ embed.row1, embed.row2 ],
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
AppLogger.LogError("Commands/Inventory", e as string);
|
AppLogger.LogError("Commands/Inventory", e as string);
|
||||||
|
|
88
src/commands/multidrop.ts
Normal file
88
src/commands/multidrop.ts
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import { AttachmentBuilder, CommandInteraction, SlashCommandBuilder } from "discord.js";
|
||||||
|
import { Command } from "../type/command";
|
||||||
|
import { CoreClient } from "../client/client";
|
||||||
|
import ErrorMessages from "../constants/ErrorMessages";
|
||||||
|
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 { 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() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.CommandBuilder = new SlashCommandBuilder()
|
||||||
|
.setName("multidrop")
|
||||||
|
.setDescription("Drop 11 cards for the price of 10!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async execute(interaction: CommandInteraction) {
|
||||||
|
if (!CoreClient.AllowDrops) {
|
||||||
|
await interaction.reply(ErrorMessages.BotSyncing);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await Config.GetValue("safemode") == "true") {
|
||||||
|
AppLogger.LogWarn("Commands/Multidrop", ErrorMessages.SafeMode);
|
||||||
|
await interaction.reply(ErrorMessages.SafeMode);
|
||||||
|
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/Multidrop", `New user (${interaction.user.id}) saved to the database`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.Currency < CardConstants.MultidropCost) {
|
||||||
|
await interaction.reply(ErrorMessages.NotEnoughCurrency(CardConstants.MultidropCost, user.Currency));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
user.RemoveCurrency(CardConstants.MultidropCost);
|
||||||
|
await user.Save(User, user);
|
||||||
|
|
||||||
|
const randomCard = GetCardsHelper.GetRandomCard();
|
||||||
|
const cardsRemaining = CardConstants.MultidropQuantity - 1;
|
||||||
|
|
||||||
|
if (!randomCard) {
|
||||||
|
AppLogger.LogWarn("Commands/Multidrop", ErrorMessages.UnableToFetchCard);
|
||||||
|
await interaction.reply(ErrorMessages.UnableToFetchCard);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||||
|
|
||||||
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
||||||
|
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
||||||
|
|
||||||
|
const embed = MultidropEmbedHelper.GenerateMultidropEmbed(randomCard, quantityClaimed, imageFileName, cardsRemaining, undefined, user.Currency);
|
||||||
|
|
||||||
|
const row = MultidropEmbedHelper.GenerateMultidropButtons(randomCard, cardsRemaining, interaction.user.id);
|
||||||
|
|
||||||
|
await interaction.editReply({
|
||||||
|
embeds: [ embed ],
|
||||||
|
files: [ attachment ],
|
||||||
|
components: [ row ],
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
AppLogger.LogError("Commands/Multidrop", `Error sending next drop for card ${randomCard.card.id}: ${e}`);
|
||||||
|
|
||||||
|
await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. (${randomCard.card.id})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,8 +2,8 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CacheType, CommandInterac
|
||||||
import { Command } from "../type/command";
|
import { Command } from "../type/command";
|
||||||
import Inventory from "../database/entities/app/Inventory";
|
import Inventory from "../database/entities/app/Inventory";
|
||||||
import { CardRarityToString, GetSacrificeAmount } from "../constants/CardRarity";
|
import { CardRarityToString, GetSacrificeAmount } from "../constants/CardRarity";
|
||||||
import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
import EmbedColours from "../constants/EmbedColours";
|
||||||
|
import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper";
|
||||||
|
|
||||||
export default class Sacrifice extends Command {
|
export default class Sacrifice extends Command {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -16,11 +16,18 @@ export default class Sacrifice extends Command {
|
||||||
x
|
x
|
||||||
.setName("cardnumber")
|
.setName("cardnumber")
|
||||||
.setDescription("The card to sacrifice from your inventory")
|
.setDescription("The card to sacrifice from your inventory")
|
||||||
.setRequired(true));
|
.setRequired(true))
|
||||||
|
.addNumberOption(x =>
|
||||||
|
x
|
||||||
|
.setName("quantity")
|
||||||
|
.setDescription("The amount to sacrifice (default 1)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction<CacheType>): Promise<void> {
|
public override async execute(interaction: CommandInteraction<CacheType>): Promise<void> {
|
||||||
const cardnumber = interaction.options.get("cardnumber", true);
|
const cardnumber = interaction.options.get("cardnumber", true);
|
||||||
|
const quantityInput = interaction.options.get("quantity")?.value ?? 1;
|
||||||
|
|
||||||
|
const quantity = Number(quantityInput) || 1;
|
||||||
|
|
||||||
const cardInInventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, cardnumber.value! as string);
|
const cardInInventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, cardnumber.value! as string);
|
||||||
|
|
||||||
|
@ -29,14 +36,19 @@ export default class Sacrifice extends Command {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardnumber.value! as string);
|
if (cardInInventory.Quantity < quantity) {
|
||||||
|
await interaction.reply(`You can only sacrifice what you own! You have ${cardInInventory.Quantity} of this card`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cardData = GetCardsHelper.GetCardByCardNumber(cardnumber.value! as string);
|
||||||
|
|
||||||
if (!cardData) {
|
if (!cardData) {
|
||||||
await interaction.reply("Unable to find card in the database.");
|
await interaction.reply("Unable to find card in the database.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cardValue = GetSacrificeAmount(cardData.card.type);
|
const cardValue = GetSacrificeAmount(cardData.card.type) * quantity;
|
||||||
const cardRarityString = CardRarityToString(cardData.card.type);
|
const cardRarityString = CardRarityToString(cardData.card.type);
|
||||||
|
|
||||||
const description = [
|
const description = [
|
||||||
|
@ -44,6 +56,7 @@ export default class Sacrifice extends Command {
|
||||||
`Series: ${cardData.series.name}`,
|
`Series: ${cardData.series.name}`,
|
||||||
`Rarity: ${cardRarityString}`,
|
`Rarity: ${cardRarityString}`,
|
||||||
`Quantity Owned: ${cardInInventory.Quantity}`,
|
`Quantity Owned: ${cardInInventory.Quantity}`,
|
||||||
|
`Quantity To Sacrifice: ${quantity}`,
|
||||||
`Sacrifice Amount: ${cardValue}`,
|
`Sacrifice Amount: ${cardValue}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -56,11 +69,11 @@ export default class Sacrifice extends Command {
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||||
.addComponents([
|
.addComponents([
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`sacrifice confirm ${interaction.user.id} ${cardnumber.value!}`)
|
.setCustomId(`sacrifice confirm ${interaction.user.id} ${cardnumber.value!} ${quantity}`)
|
||||||
.setLabel("Confirm")
|
.setLabel("Confirm")
|
||||||
.setStyle(ButtonStyle.Success),
|
.setStyle(ButtonStyle.Success),
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`sacrifice cancel ${interaction.user.id} ${cardnumber.value!}`)
|
.setCustomId(`sacrifice cancel ${interaction.user.id} ${cardnumber.value!} ${quantity}`)
|
||||||
.setLabel("Cancel")
|
.setLabel("Cancel")
|
||||||
.setStyle(ButtonStyle.Secondary),
|
.setStyle(ButtonStyle.Secondary),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Inventory from "../../database/entities/app/Inventory";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
import { CoreClient } from "../../client/client";
|
import { CoreClient } from "../../client/client";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import CardDropHelperMetadata from "../../helpers/CardDropHelperMetadata";
|
import DropEmbedHelper from "../../helpers/DropHelpers/DropEmbedHelper";
|
||||||
|
|
||||||
export default class Dropnumber extends Command {
|
export default class Dropnumber extends Command {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -60,11 +60,11 @@ export default class Dropnumber extends Command {
|
||||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.id);
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.id);
|
||||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
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 claimId = v4();
|
||||||
|
|
||||||
const row = CardDropHelperMetadata.GenerateDropButtons({ card, series }, claimId, interaction.user.id);
|
const row = DropEmbedHelper.GenerateDropButtons({ card, series }, claimId, interaction.user.id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
|
|
|
@ -5,8 +5,9 @@ import { readFileSync } from "fs";
|
||||||
import Inventory from "../../database/entities/app/Inventory";
|
import Inventory from "../../database/entities/app/Inventory";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
import { CoreClient } from "../../client/client";
|
import { CoreClient } from "../../client/client";
|
||||||
import CardDropHelperMetadata from "../../helpers/CardDropHelperMetadata";
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import GetCardsHelper from "../../helpers/DropHelpers/GetCardsHelper";
|
||||||
|
import DropEmbedHelper from "../../helpers/DropHelpers/DropEmbedHelper";
|
||||||
|
|
||||||
export default class Droprarity extends Command {
|
export default class Droprarity extends Command {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -39,7 +40,7 @@ export default class Droprarity extends Command {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const card = await CardDropHelperMetadata.GetRandomCardByRarity(rarityType);
|
const card = await GetCardsHelper.GetRandomCardByRarity(rarityType);
|
||||||
|
|
||||||
if (!card) {
|
if (!card) {
|
||||||
await interaction.reply("Card not found");
|
await interaction.reply("Card not found");
|
||||||
|
@ -61,11 +62,11 @@ export default class Droprarity extends Command {
|
||||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.card.id);
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.card.id);
|
||||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
||||||
|
|
||||||
const embed = CardDropHelperMetadata.GenerateDropEmbed(card, quantityClaimed, imageFileName);
|
const embed = DropEmbedHelper.GenerateDropEmbed(card, quantityClaimed, imageFileName);
|
||||||
|
|
||||||
const claimId = v4();
|
const claimId = v4();
|
||||||
|
|
||||||
const row = CardDropHelperMetadata.GenerateDropButtons(card, claimId, interaction.user.id);
|
const row = DropEmbedHelper.GenerateDropButtons(card, claimId, interaction.user.id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
|
|
|
@ -26,13 +26,26 @@ export default class Trade extends Command {
|
||||||
x
|
x
|
||||||
.setName("receive")
|
.setName("receive")
|
||||||
.setDescription("Item to receive")
|
.setDescription("Item to receive")
|
||||||
.setRequired(true));
|
.setRequired(true))
|
||||||
|
.addNumberOption(x =>
|
||||||
|
x
|
||||||
|
.setName("givequantity")
|
||||||
|
.setDescription("Amount to give"))
|
||||||
|
.addNumberOption(x =>
|
||||||
|
x
|
||||||
|
.setName("receivequantity")
|
||||||
|
.setDescription("Amount to receive"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
public override async execute(interaction: CommandInteraction) {
|
||||||
const user = interaction.options.get("user", true).user!;
|
const user = interaction.options.get("user", true).user!;
|
||||||
const give = interaction.options.get("give", true);
|
const give = interaction.options.get("give", true);
|
||||||
const receive = interaction.options.get("receive", true);
|
const receive = interaction.options.get("receive", true);
|
||||||
|
const givequantityInput = interaction.options.get("givequantity")?.value ?? 1;
|
||||||
|
const receivequantityInput = interaction.options.get("receivequantity")?.value ?? 1;
|
||||||
|
|
||||||
|
const givequantity = Number(givequantityInput) || 1;
|
||||||
|
const receivequantity = Number(receivequantityInput) || 1;
|
||||||
|
|
||||||
AppLogger.LogSilly("Commands/Trade", `Parameters: user=${user.id}, give=${give.value}, receive=${receive.value}`);
|
AppLogger.LogSilly("Commands/Trade", `Parameters: user=${user.id}, give=${give.value}, receive=${receive.value}`);
|
||||||
|
|
||||||
|
@ -44,12 +57,12 @@ export default class Trade extends Command {
|
||||||
const user1ItemEntity = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, give.value!.toString());
|
const user1ItemEntity = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, give.value!.toString());
|
||||||
const user2ItemEntity = await Inventory.FetchOneByCardNumberAndUserId(user.id, receive.value!.toString());
|
const user2ItemEntity = await Inventory.FetchOneByCardNumberAndUserId(user.id, receive.value!.toString());
|
||||||
|
|
||||||
if (!user1ItemEntity) {
|
if (!user1ItemEntity || user1ItemEntity.Quantity < givequantity) {
|
||||||
await interaction.reply("You do not have the item you are trying to trade.");
|
await interaction.reply("You do not have the item you are trying to trade.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user2ItemEntity) {
|
if (!user2ItemEntity || user2ItemEntity.Quantity < receivequantity) {
|
||||||
await interaction.reply("The user you are trying to trade with does not have the item you are trying to trade for.");
|
await interaction.reply("The user you are trying to trade with does not have the item you are trying to trade for.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -78,12 +91,12 @@ export default class Trade extends Command {
|
||||||
.addFields([
|
.addFields([
|
||||||
{
|
{
|
||||||
name: `${interaction.user.username} Receives`,
|
name: `${interaction.user.username} Receives`,
|
||||||
value: `${user2Item.id}: ${user2Item.name}`,
|
value: `${user2Item.id}: ${user2Item.name} x${receivequantity}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `${user.username} Receives`,
|
name: `${user.username} Receives`,
|
||||||
value: `${user1Item.id}: ${user1Item.name}`,
|
value: `${user1Item.id}: ${user1Item.name} x${givequantity}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -92,16 +105,16 @@ export default class Trade extends Command {
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const timeoutId = setTimeout(async () => this.autoDecline(interaction, interaction.user.username, user.username, user1Item.id, user2Item.id, user1Item.name, user2Item.name), 1000 * 60 * 15); // 15 minutes
|
const timeoutId = setTimeout(async () => this.autoDecline(interaction, interaction.user.username, user.username, user1Item.id, user2Item.id, user1Item.name, user2Item.name, givequantity, receivequantity), 1000 * 60 * 15); // 15 minutes
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||||
.addComponents([
|
.addComponents([
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`trade accept ${interaction.user.id} ${user.id} ${user1Item.id} ${user2Item.id} ${expiry} ${timeoutId}`)
|
.setCustomId(`trade accept ${interaction.user.id} ${user.id} ${user1Item.id} ${user2Item.id} ${expiry} ${timeoutId} ${givequantity} ${receivequantity}`)
|
||||||
.setLabel("Accept")
|
.setLabel("Accept")
|
||||||
.setStyle(ButtonStyle.Success),
|
.setStyle(ButtonStyle.Success),
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`trade decline ${interaction.user.id} ${user.id} ${user1Item.id} ${user2Item.id} ${expiry} ${timeoutId}`)
|
.setCustomId(`trade decline ${interaction.user.id} ${user.id} ${user1Item.id} ${user2Item.id} ${expiry} ${timeoutId} ${givequantity} ${receivequantity}`)
|
||||||
.setLabel("Decline")
|
.setLabel("Decline")
|
||||||
.setStyle(ButtonStyle.Danger),
|
.setStyle(ButtonStyle.Danger),
|
||||||
]);
|
]);
|
||||||
|
@ -109,7 +122,7 @@ export default class Trade extends Command {
|
||||||
await interaction.reply({ content: `${user}`, embeds: [ tradeEmbed ], components: [ row ] });
|
await interaction.reply({ content: `${user}`, embeds: [ tradeEmbed ], components: [ row ] });
|
||||||
}
|
}
|
||||||
|
|
||||||
private async autoDecline(interaction: CommandInteraction, user1Username: string, user2Username: string, user1CardNumber: string, user2CardNumber: string, user1CardName: string, user2CardName: string) {
|
private async autoDecline(interaction: CommandInteraction, user1Username: string, user2Username: string, user1CardNumber: string, user2CardNumber: string, user1CardName: string, user2CardName: string, user1Quantity: number, user2Quantity: number) {
|
||||||
AppLogger.LogSilly("Commands/Trade/AutoDecline", `Auto declining trade between ${user1Username} and ${user2Username}`);
|
AppLogger.LogSilly("Commands/Trade/AutoDecline", `Auto declining trade between ${user1Username} and ${user2Username}`);
|
||||||
|
|
||||||
const tradeEmbed = new EmbedBuilder()
|
const tradeEmbed = new EmbedBuilder()
|
||||||
|
@ -120,12 +133,12 @@ export default class Trade extends Command {
|
||||||
.addFields([
|
.addFields([
|
||||||
{
|
{
|
||||||
name: `${user1Username} Receives`,
|
name: `${user1Username} Receives`,
|
||||||
value: `${user2CardNumber}: ${user2CardName}`,
|
value: `${user2CardNumber}: ${user2CardName} x${user2Quantity}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `${user2Username} Receives`,
|
name: `${user2Username} Receives`,
|
||||||
value: `${user1CardNumber}: ${user1CardName}`,
|
value: `${user1CardNumber}: ${user1CardName} x${user1Quantity}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
import { AttachmentBuilder, CommandInteraction, DiscordAPIError, SlashCommandBuilder } from "discord.js";
|
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
|
||||||
import { Command } from "../type/command";
|
import { Command } from "../type/command";
|
||||||
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 AppLogger from "../client/appLogger";
|
||||||
|
import CardSearchHelper from "../helpers/CardSearchHelper";
|
||||||
|
|
||||||
export default class View extends Command {
|
export default class View extends Command {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -13,68 +9,32 @@ export default class View extends Command {
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
this.CommandBuilder = new SlashCommandBuilder()
|
||||||
.setName("view")
|
.setName("view")
|
||||||
.setDescription("View a specific command")
|
.setDescription("Search for a card by its name")
|
||||||
.addStringOption(x =>
|
.addStringOption(x =>
|
||||||
x
|
x
|
||||||
.setName("cardnumber")
|
.setName("name")
|
||||||
.setDescription("The card number to view")
|
.setDescription("The card name to search for")
|
||||||
.setRequired(true));
|
.setRequired(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
public override async execute(interaction: CommandInteraction) {
|
||||||
const cardNumber = interaction.options.get("cardnumber");
|
const name = interaction.options.get("name", true);
|
||||||
|
|
||||||
AppLogger.LogSilly("Commands/View", `Parameters: cardNumber=${cardNumber?.value}`);
|
AppLogger.LogSilly("Commands/View", `Parameters: name=${name.value}`);
|
||||||
|
|
||||||
if (!cardNumber || !cardNumber.value) {
|
|
||||||
await interaction.reply("Card number is required.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const card = CoreClient.Cards
|
|
||||||
.flatMap(x => x.cards)
|
|
||||||
.find(x => x.id == cardNumber.value);
|
|
||||||
|
|
||||||
if (!card) {
|
|
||||||
await interaction.reply("Card not found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const series = CoreClient.Cards
|
|
||||||
.find(x => x.cards.includes(card))!;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
|
|
||||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.id);
|
const searchResult = await CardSearchHelper.GenerateSearchQuery(name.value!.toString(), interaction.user.id, 7);
|
||||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
|
||||||
|
|
||||||
const embed = CardDropHelperMetadata.GenerateDropEmbed({ card, series }, quantityClaimed, imageFileName);
|
if (!searchResult) {
|
||||||
|
await interaction.editReply("No results found");
|
||||||
try {
|
return;
|
||||||
await interaction.editReply({
|
|
||||||
embeds: [ embed ],
|
|
||||||
files: files,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
AppLogger.LogError("Commands/View", `Error sending view for card ${card.id}: ${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.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await interaction.editReply({
|
||||||
|
embeds: [ searchResult.embed ],
|
||||||
|
components: [ searchResult.row ],
|
||||||
|
files: searchResult.attachments,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,4 +3,11 @@ export default class CardConstants {
|
||||||
public static readonly TimerGiveAmount = 10;
|
public static readonly TimerGiveAmount = 10;
|
||||||
public static readonly DailyCurrency = 100;
|
public static readonly DailyCurrency = 100;
|
||||||
public static readonly StartingCurrency = 300;
|
public static readonly StartingCurrency = 300;
|
||||||
|
|
||||||
|
// Multidrop
|
||||||
|
public static readonly MultidropCost = this.ClaimCost * 10;
|
||||||
|
public static readonly MultidropQuantity = 11;
|
||||||
|
|
||||||
|
// Effects
|
||||||
|
public static readonly UnusedChanceUpChance = 0.5;
|
||||||
}
|
}
|
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" },
|
||||||
|
];
|
8
src/constants/ErrorMessages.ts
Normal file
8
src/constants/ErrorMessages.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export default class ErrorMessages {
|
||||||
|
public static readonly BotSyncing = "Bot is currently syncing, please wait until its done.";
|
||||||
|
public static readonly SafeMode = "Safe Mode has been activated, please resync to continue.";
|
||||||
|
public static readonly UnableToFetchCard = "Unable to fetch card, please try again.";
|
||||||
|
public static readonly UnableToFetchUser = "Unable to fetch user, please try again.";
|
||||||
|
|
||||||
|
public static readonly NotEnoughCurrency = (need: number, have: number) => `Not enough currency! You need ${need} currency, you have ${have}!`;
|
||||||
|
}
|
10
src/contracts/StringDropdownEventItem.ts
Normal file
10
src/contracts/StringDropdownEventItem.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import {Environment} from "../constants/Environment";
|
||||||
|
import {StringDropdownEvent} from "../type/stringDropdownEvent";
|
||||||
|
|
||||||
|
interface StringDropdownEventItem {
|
||||||
|
DropdownId: string,
|
||||||
|
Event: StringDropdownEvent,
|
||||||
|
Environment: Environment,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StringDropdownEventItem;
|
|
@ -29,16 +29,16 @@ export default class Inventory extends AppBaseEntity {
|
||||||
this.Quantity = quantity;
|
this.Quantity = quantity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AddQuantity(amount: number) {
|
|
||||||
this.Quantity += amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RemoveQuantity(amount: number) {
|
public RemoveQuantity(amount: number) {
|
||||||
if (this.Quantity < amount) return;
|
if (this.Quantity < amount) return;
|
||||||
|
|
||||||
this.Quantity -= amount;
|
this.Quantity -= amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AddQuantity(amount: number) {
|
||||||
|
this.Quantity += amount;
|
||||||
|
}
|
||||||
|
|
||||||
public AddClaim(claim: Claim) {
|
public AddClaim(claim: Claim) {
|
||||||
this.Claims.push(claim);
|
this.Claims.push(claim);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ export default class User extends AppBaseEntity {
|
||||||
@Column()
|
@Column()
|
||||||
Currency: number;
|
Currency: number;
|
||||||
|
|
||||||
@Column()
|
@Column({ nullable: true })
|
||||||
LastUsedDaily?: Date;
|
LastUsedDaily?: Date;
|
||||||
|
|
||||||
public AddCurrency(amount: number) {
|
public AddCurrency(amount: number) {
|
||||||
|
|
86
src/database/entities/app/UserEffect.ts
Normal file
86
src/database/entities/app/UserEffect.ts
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import {Column, Entity} from "typeorm";
|
||||||
|
import AppBaseEntity from "../../../contracts/AppBaseEntity";
|
||||||
|
import AppDataSource from "../../dataSources/appDataSource";
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export default class UserEffect extends AppBaseEntity {
|
||||||
|
constructor(name: string, userId: string, unused: number, WhenExpires?: Date) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.Name = name;
|
||||||
|
this.UserId = userId;
|
||||||
|
this.Unused = unused;
|
||||||
|
this.WhenExpires = WhenExpires;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
Name: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
UserId: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
Unused: number;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
WhenExpires?: Date;
|
||||||
|
|
||||||
|
public AddUnused(amount: number) {
|
||||||
|
this.Unused += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UseEffect(whenExpires: Date): boolean {
|
||||||
|
if (this.Unused == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Unused -= 1;
|
||||||
|
this.WhenExpires = whenExpires;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IsEffectActive(): boolean {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
if (this.WhenExpires && now < this.WhenExpires) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async FetchOneByUserIdAndName(userId: string, name: string): Promise<UserEffect | null> {
|
||||||
|
const repository = AppDataSource.getRepository(UserEffect);
|
||||||
|
|
||||||
|
const single = await repository.findOne({ where: { UserId: userId, Name: name } });
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
import MigrationHelper from "../../../../helpers/MigrationHelper";
|
||||||
|
|
||||||
|
export class CreateUserEffect1729962056556 implements MigrationInterface {
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
MigrationHelper.Up("1729962056556-createUserEffect", "0.9", [
|
||||||
|
"01-table-userEffect",
|
||||||
|
], queryRunner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
MigrationHelper.Down("1729962056556-createUserEffect", "0.9", [
|
||||||
|
"01-table-userEffect",
|
||||||
|
], queryRunner);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,158 +0,0 @@
|
||||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
|
||||||
import { CardRarity, CardRarityToColour, CardRarityToString } 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}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (${CardConstants.ClaimCost} 🪙)`)
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setDisabled(disabled),
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId("reroll")
|
|
||||||
.setLabel("Reroll")
|
|
||||||
.setStyle(ButtonStyle.Secondary));
|
|
||||||
}
|
|
||||||
}
|
|
116
src/helpers/CardSearchHelper.ts
Normal file
116
src/helpers/CardSearchHelper.ts
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
import {ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder} from "discord.js";
|
||||||
|
import Fuse from "fuse.js";
|
||||||
|
import {CoreClient} from "../client/client.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>,
|
||||||
|
attachments: AttachmentBuilder[],
|
||||||
|
results: string[],
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class CardSearchHelper {
|
||||||
|
public static async GenerateSearchQuery(query: string, userid: string, pages: number): Promise<ReturnedPage | undefined> {
|
||||||
|
AppLogger.LogSilly("CardSearchHelper/GenerateSearchQuery", `Parameters: query=${query}, userid=${userid}, pages=${pages}`);
|
||||||
|
|
||||||
|
const fzf = new Fuse(CoreClient.Cards.flatMap(x => x.cards), { keys: ["name"] });
|
||||||
|
const entries = fzf.search(query)
|
||||||
|
.splice(0, pages);
|
||||||
|
|
||||||
|
const entry = entries[0];
|
||||||
|
const results = entries
|
||||||
|
.flatMap(x => x.item.id);
|
||||||
|
|
||||||
|
if (!entry) {
|
||||||
|
AppLogger.LogVerbose("CardSearchHelper/GenerateSearchQuery", `Unable to find entry: ${query}`);
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const card = GetCardsHelper.GetCardByCardNumber(entry.item.id);
|
||||||
|
|
||||||
|
if (!card) return undefined;
|
||||||
|
|
||||||
|
const attachments = [];
|
||||||
|
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 });
|
||||||
|
|
||||||
|
attachments.push(attachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(userid, card.card.id);
|
||||||
|
const quantityClaimed = inventory?.Quantity ?? 0;
|
||||||
|
|
||||||
|
const embed = DropEmbedHelper.GenerateDropEmbed(card, quantityClaimed, imageFileName);
|
||||||
|
|
||||||
|
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`view 0 ${results.join(" ")}`)
|
||||||
|
.setLabel("Previous")
|
||||||
|
.setStyle(ButtonStyle.Primary)
|
||||||
|
.setDisabled(true),
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`view 2 ${results.join(" ")}`)
|
||||||
|
.setLabel("Next")
|
||||||
|
.setStyle(ButtonStyle.Primary)
|
||||||
|
.setDisabled(pages == 1));
|
||||||
|
|
||||||
|
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 = GetCardsHelper.GetCardByCardNumber(currentPageId);
|
||||||
|
|
||||||
|
if (!card) {
|
||||||
|
AppLogger.LogError("CardSearchHelper/GenerateSearchPageFromQuery", `Unable to find card by id: ${currentPageId}.`);
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachments = [];
|
||||||
|
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 });
|
||||||
|
|
||||||
|
attachments.push(attachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(userid, card.card.id);
|
||||||
|
const quantityClaimed = inventory?.Quantity ?? 0;
|
||||||
|
|
||||||
|
const embed = DropEmbedHelper.GenerateDropEmbed(card, quantityClaimed, imageFileName);
|
||||||
|
|
||||||
|
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`view ${page - 1} ${results.join(" ")}`)
|
||||||
|
.setLabel("Previous")
|
||||||
|
.setStyle(ButtonStyle.Primary)
|
||||||
|
.setDisabled(page - 1 == 0),
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`view ${page + 1} ${results.join(" ")}`)
|
||||||
|
.setLabel("Next")
|
||||||
|
.setStyle(ButtonStyle.Primary)
|
||||||
|
.setDisabled(page == results.length));
|
||||||
|
|
||||||
|
return { embed, row, attachments, results };
|
||||||
|
}
|
||||||
|
}
|
85
src/helpers/DropHelpers/DropEmbedHelper.ts
Normal file
85
src/helpers/DropHelpers/DropEmbedHelper.ts
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
||||||
|
import { DropResult } from "../../contracts/SeriesMetadata";
|
||||||
|
import AppLogger from "../../client/appLogger";
|
||||||
|
import { CardRarityToColour, CardRarityToString } from "../../constants/CardRarity";
|
||||||
|
import StringTools from "../StringTools";
|
||||||
|
import CardConstants from "../../constants/CardConstants";
|
||||||
|
|
||||||
|
export default class DropEmbedHelper {
|
||||||
|
public static GenerateDropEmbed(drop: DropResult, quantityClaimed: number, imageFileName: string, claimedBy?: string, currency?: number): EmbedBuilder {
|
||||||
|
AppLogger.LogSilly("CardDropHelperMetadata/GenerateDropEmbed", `Parameters: drop=${drop.card.id}, quantityClaimed=${quantityClaimed}, imageFileName=${imageFileName}`);
|
||||||
|
|
||||||
|
const description = drop.card.subseries ?? drop.series.name;
|
||||||
|
let colour = CardRarityToColour(drop.card.type);
|
||||||
|
|
||||||
|
if (drop.card.colour && StringTools.IsHexCode(drop.card.colour)) {
|
||||||
|
const hexCode = Number("0x" + drop.card.colour);
|
||||||
|
|
||||||
|
if (hexCode) {
|
||||||
|
colour = hexCode;
|
||||||
|
} else {
|
||||||
|
AppLogger.LogWarn("CardDropHelperMetadata/GenerateDropEmbed", `Card's colour override is invalid: ${drop.card.id}, ${drop.card.colour}`);
|
||||||
|
}
|
||||||
|
} else if (drop.card.colour) {
|
||||||
|
AppLogger.LogWarn("CardDropHelperMetadata/GenerateDropEmbed", `Card's colour override is invalid: ${drop.card.id}, ${drop.card.colour}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (${CardConstants.ClaimCost} 🪙)`)
|
||||||
|
.setStyle(ButtonStyle.Primary)
|
||||||
|
.setDisabled(disabled),
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId("reroll")
|
||||||
|
.setLabel("Reroll")
|
||||||
|
.setStyle(ButtonStyle.Secondary));
|
||||||
|
}
|
||||||
|
}
|
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));
|
||||||
|
}
|
||||||
|
}
|
194
src/helpers/EffectHelper.ts
Normal file
194
src/helpers/EffectHelper.ts
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
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) {
|
||||||
|
let effect = await UserEffect.FetchOneByUserIdAndName(userId, name);
|
||||||
|
|
||||||
|
if (!effect) {
|
||||||
|
effect = new UserEffect(name, userId, quantity);
|
||||||
|
} else {
|
||||||
|
effect.AddUnused(quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
await effect.Save(UserEffect, effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
if (!effect || effect.Unused == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const effectDetail = EffectDetails.get(effect.Name);
|
||||||
|
|
||||||
|
if (!effectDetail) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effect.WhenExpires && now < new Date(effect.WhenExpires.getTime() + effectDetail.cooldown)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async HasEffect(userId: string, name: string): Promise<boolean> {
|
||||||
|
const effect = await UserEffect.FetchOneByUserIdAndName(userId, name);
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
if (!effect || !effect.WhenExpires) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (now > effect.WhenExpires) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 AppLogger from "../client/appLogger";
|
||||||
import {existsSync} from "fs";
|
import {existsSync} from "fs";
|
||||||
import Inventory from "../database/entities/app/Inventory";
|
import Inventory from "../database/entities/app/Inventory";
|
||||||
import Jimp from "jimp";
|
import { Bitmap, Jimp } from "jimp";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
interface CardInput {
|
interface CardInput {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -29,14 +30,24 @@ export default class ImageHelper {
|
||||||
|
|
||||||
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) {
|
if (existsSync(filePath)) {
|
||||||
VylpesTester
commented
This could also then be put into an if else chain in the if below This could also then be put into an if else chain in the if below
|
|||||||
|
const data = 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);
|
||||||
|
|
||||||
|
bitmap = data.bitmap;
|
||||||
|
} else {
|
||||||
AppLogger.LogError("ImageHelper/GenerateCardImageGrid", `Failed to load image from path ${card.path}`);
|
AppLogger.LogError("ImageHelper/GenerateCardImageGrid", `Failed to load image from path ${card.path}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageData = await Jimp.read(filePath);
|
const imageData = Jimp.fromBitmap(bitmap);
|
||||||
|
|
||||||
if (userId != null) {
|
if (userId != null) {
|
||||||
const claimed = await Inventory.FetchOneByCardNumberAndUserId(userId, card.id);
|
const claimed = await Inventory.FetchOneByCardNumberAndUserId(userId, card.id);
|
||||||
|
@ -46,7 +57,7 @@ export default class ImageHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const image = await loadImage(await imageData.getBufferAsync("image/png"));
|
const image = await loadImage(await imageData.getBuffer("image/png"));
|
||||||
|
|
||||||
const x = i % gridWidth;
|
const x = i % gridWidth;
|
||||||
const y = Math.floor(i / gridWidth);
|
const y = Math.floor(i / gridWidth);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder } from "discord.js";
|
||||||
import Inventory from "../database/entities/app/Inventory";
|
import Inventory from "../database/entities/app/Inventory";
|
||||||
import { CoreClient } from "../client/client";
|
import { CoreClient } from "../client/client";
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
import EmbedColours from "../constants/EmbedColours";
|
||||||
|
@ -24,7 +24,8 @@ interface InventoryPageCards {
|
||||||
|
|
||||||
interface ReturnedInventoryPage {
|
interface ReturnedInventoryPage {
|
||||||
embed: EmbedBuilder,
|
embed: EmbedBuilder,
|
||||||
row: ActionRowBuilder<ButtonBuilder>,
|
row1: ActionRowBuilder<ButtonBuilder>,
|
||||||
|
row2: ActionRowBuilder<StringSelectMenuBuilder>,
|
||||||
image: AttachmentBuilder,
|
image: AttachmentBuilder,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +100,7 @@ export default class InventoryHelper {
|
||||||
.setColor(EmbedColours.Ok)
|
.setColor(EmbedColours.Ok)
|
||||||
.setImage("attachment://page.png");
|
.setImage("attachment://page.png");
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
const row1 = new ActionRowBuilder<ButtonBuilder>()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`inventory ${userid} ${page - 1}`)
|
.setCustomId(`inventory ${userid} ${page - 1}`)
|
||||||
|
@ -112,9 +113,23 @@ export default class InventoryHelper {
|
||||||
.setStyle(ButtonStyle.Primary)
|
.setStyle(ButtonStyle.Primary)
|
||||||
.setDisabled(page + 1 == pages.length));
|
.setDisabled(page + 1 == pages.length));
|
||||||
|
|
||||||
|
let pageNum = 0;
|
||||||
|
|
||||||
|
const row2 = new ActionRowBuilder<StringSelectMenuBuilder>()
|
||||||
|
.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++}`))));
|
||||||
|
|
||||||
const buffer = await ImageHelper.GenerateCardImageGrid(currentPage.cards.map(x => ({ id: x.id, path: x.path })));
|
const buffer = await ImageHelper.GenerateCardImageGrid(currentPage.cards.map(x => ({ id: x.id, path: x.path })));
|
||||||
const image = new AttachmentBuilder(buffer, { name: "page.png" });
|
const image = new AttachmentBuilder(buffer, { name: "page.png" });
|
||||||
|
|
||||||
return { embed, row, image };
|
return { embed, row1, row2, image };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,4 +118,19 @@ export default class TimeLengthInput {
|
||||||
|
|
||||||
return desNumber;
|
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,9 +7,12 @@ import AllBalance from "./commands/allbalance";
|
||||||
import Balance from "./commands/balance";
|
import Balance from "./commands/balance";
|
||||||
import Daily from "./commands/daily";
|
import Daily from "./commands/daily";
|
||||||
import Drop from "./commands/drop";
|
import Drop from "./commands/drop";
|
||||||
|
import Effects from "./commands/effects";
|
||||||
import Gdrivesync from "./commands/gdrivesync";
|
import Gdrivesync from "./commands/gdrivesync";
|
||||||
import Give from "./commands/give";
|
import Give from "./commands/give";
|
||||||
|
import Id from "./commands/id";
|
||||||
import Inventory from "./commands/inventory";
|
import Inventory from "./commands/inventory";
|
||||||
|
import Multidrop from "./commands/multidrop";
|
||||||
import Resync from "./commands/resync";
|
import Resync from "./commands/resync";
|
||||||
import Sacrifice from "./commands/sacrifice";
|
import Sacrifice from "./commands/sacrifice";
|
||||||
import Series from "./commands/series";
|
import Series from "./commands/series";
|
||||||
|
@ -23,11 +26,17 @@ import Droprarity from "./commands/stage/droprarity";
|
||||||
|
|
||||||
// Button Event Imports
|
// Button Event Imports
|
||||||
import Claim from "./buttonEvents/Claim";
|
import Claim from "./buttonEvents/Claim";
|
||||||
|
import EffectsButtonEvent from "./buttonEvents/Effects";
|
||||||
import InventoryButtonEvent from "./buttonEvents/Inventory";
|
import InventoryButtonEvent from "./buttonEvents/Inventory";
|
||||||
|
import MultidropButtonEvent from "./buttonEvents/Multidrop";
|
||||||
import Reroll from "./buttonEvents/Reroll";
|
import Reroll from "./buttonEvents/Reroll";
|
||||||
import SacrificeButtonEvent from "./buttonEvents/Sacrifice";
|
import SacrificeButtonEvent from "./buttonEvents/Sacrifice";
|
||||||
import SeriesEvent from "./buttonEvents/Series";
|
import SeriesEvent from "./buttonEvents/Series";
|
||||||
import TradeButtonEvent from "./buttonEvents/Trade";
|
import TradeButtonEvent from "./buttonEvents/Trade";
|
||||||
|
import ViewButtonEvent from "./buttonEvents/View";
|
||||||
|
|
||||||
|
// String Dropdown Event Imports
|
||||||
|
import InventoryStringDropdown from "./stringDropdowns/Inventory";
|
||||||
|
|
||||||
export default class Registry {
|
export default class Registry {
|
||||||
public static RegisterCommands() {
|
public static RegisterCommands() {
|
||||||
|
@ -37,9 +46,12 @@ export default class Registry {
|
||||||
CoreClient.RegisterCommand("balance", new Balance());
|
CoreClient.RegisterCommand("balance", new Balance());
|
||||||
CoreClient.RegisterCommand("daily", new Daily());
|
CoreClient.RegisterCommand("daily", new Daily());
|
||||||
CoreClient.RegisterCommand("drop", new Drop());
|
CoreClient.RegisterCommand("drop", new Drop());
|
||||||
|
CoreClient.RegisterCommand("effects", new Effects());
|
||||||
CoreClient.RegisterCommand("gdrivesync", new Gdrivesync());
|
CoreClient.RegisterCommand("gdrivesync", new Gdrivesync());
|
||||||
CoreClient.RegisterCommand("give", new Give());
|
CoreClient.RegisterCommand("give", new Give());
|
||||||
|
CoreClient.RegisterCommand("id", new Id());
|
||||||
CoreClient.RegisterCommand("inventory", new Inventory());
|
CoreClient.RegisterCommand("inventory", new Inventory());
|
||||||
|
CoreClient.RegisterCommand("multidrop", new Multidrop());
|
||||||
CoreClient.RegisterCommand("resync", new Resync());
|
CoreClient.RegisterCommand("resync", new Resync());
|
||||||
CoreClient.RegisterCommand("sacrifice", new Sacrifice());
|
CoreClient.RegisterCommand("sacrifice", new Sacrifice());
|
||||||
CoreClient.RegisterCommand("series", new Series());
|
CoreClient.RegisterCommand("series", new Series());
|
||||||
|
@ -54,10 +66,17 @@ export default class Registry {
|
||||||
|
|
||||||
public static RegisterButtonEvents() {
|
public static RegisterButtonEvents() {
|
||||||
CoreClient.RegisterButtonEvent("claim", new Claim());
|
CoreClient.RegisterButtonEvent("claim", new Claim());
|
||||||
|
CoreClient.RegisterButtonEvent("effects", new EffectsButtonEvent());
|
||||||
CoreClient.RegisterButtonEvent("inventory", new InventoryButtonEvent());
|
CoreClient.RegisterButtonEvent("inventory", new InventoryButtonEvent());
|
||||||
|
CoreClient.RegisterButtonEvent("multidrop", new MultidropButtonEvent());
|
||||||
CoreClient.RegisterButtonEvent("reroll", new Reroll());
|
CoreClient.RegisterButtonEvent("reroll", new Reroll());
|
||||||
CoreClient.RegisterButtonEvent("sacrifice", new SacrificeButtonEvent());
|
CoreClient.RegisterButtonEvent("sacrifice", new SacrificeButtonEvent());
|
||||||
CoreClient.RegisterButtonEvent("series", new SeriesEvent());
|
CoreClient.RegisterButtonEvent("series", new SeriesEvent());
|
||||||
CoreClient.RegisterButtonEvent("trade", new TradeButtonEvent());
|
CoreClient.RegisterButtonEvent("trade", new TradeButtonEvent());
|
||||||
|
CoreClient.RegisterButtonEvent("view", new ViewButtonEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RegisterStringDropdownEvents() {
|
||||||
|
CoreClient.RegisterStringDropdownEvent("inventory", new InventoryStringDropdown());
|
||||||
}
|
}
|
||||||
}
|
}
|
43
src/stringDropdowns/Inventory.ts
Normal file
43
src/stringDropdowns/Inventory.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import {StringSelectMenuInteraction} from "discord.js";
|
||||||
|
import {StringDropdownEvent} from "../type/stringDropdownEvent";
|
||||||
|
import AppLogger from "../client/appLogger";
|
||||||
|
import InventoryHelper from "../helpers/InventoryHelper";
|
||||||
|
|
||||||
|
export default class Inventory extends StringDropdownEvent {
|
||||||
|
public override async execute(interaction: StringSelectMenuInteraction) {
|
||||||
|
if (!interaction.guild) return;
|
||||||
|
|
||||||
|
const userid = interaction.values[0].split(" ")[0];
|
||||||
|
const page = interaction.values[0].split(" ")[1];
|
||||||
|
|
||||||
|
AppLogger.LogDebug("StringDropdown/Inventory", `Parameters: userid=${userid}, page=${page}`);
|
||||||
|
|
||||||
|
await interaction.deferUpdate();
|
||||||
|
|
||||||
|
const member = interaction.guild.members.cache.find(x => x.id == userid) || await interaction.guild.members.fetch(userid);
|
||||||
|
|
||||||
|
if (!member) {
|
||||||
|
await interaction.reply("Unable to find user.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const embed = await InventoryHelper.GenerateInventoryPage(member.user.username, member.user.id, Number(page));
|
||||||
|
|
||||||
|
if (!embed) {
|
||||||
|
await interaction.followUp("No page for user found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.editReply({
|
||||||
|
files: [ embed.image ],
|
||||||
|
embeds: [ embed.embed ],
|
||||||
|
components: [ embed.row1, embed.row2 ],
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
AppLogger.LogError("StringDropdown/Inventory", `Error generating inventory page for ${member.user.username} with id ${member.user.id}: ${e}`);
|
||||||
|
|
||||||
|
await interaction.followUp("An error has occurred running this command.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
src/type/stringDropdownEvent.ts
Normal file
5
src/type/stringDropdownEvent.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import {StringSelectMenuInteraction} from "discord.js";
|
||||||
|
|
||||||
|
export abstract class StringDropdownEvent {
|
||||||
|
abstract execute(interaction: StringSelectMenuInteraction): Promise<void>;
|
||||||
|
}
|
|
@ -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,12 @@
|
||||||
|
import { CommandInteraction } from "../../__types__/discord.js";
|
||||||
|
|
||||||
|
export default function GenerateCommandInteractionMock(options?: {
|
||||||
|
subcommand?: string,
|
||||||
|
}): CommandInteraction {
|
||||||
|
return {
|
||||||
|
isChatInputCommand: jest.fn().mockReturnValue(true),
|
||||||
|
options: {
|
||||||
|
getSubcommand: jest.fn().mockReturnValue(options?.subcommand),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
26
tests/__types__/discord.js.ts
Normal file
26
tests/__types__/discord.js.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
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 = {
|
||||||
|
isChatInputCommand: jest.Func,
|
||||||
|
options: {
|
||||||
|
getSubcommand: jest.Func,
|
||||||
|
},
|
||||||
|
}
|
109
tests/buttonEvents/Claim.test.ts
Normal file
109
tests/buttonEvents/Claim.test.ts
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import { ButtonInteraction, TextChannel } from "discord.js";
|
||||||
|
import Claim from "../../src/buttonEvents/Claim";
|
||||||
|
import { ButtonInteraction as ButtonInteractionType } from "../__types__/discord.js";
|
||||||
|
import User from "../../src/database/entities/app/User";
|
||||||
|
import GenerateButtonInteractionMock from "../__functions__/discord.js/GenerateButtonInteractionMock";
|
||||||
|
|
||||||
|
jest.mock("../../src/client/appLogger");
|
||||||
|
|
||||||
|
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 5 minutes of it being dropped!");
|
||||||
|
|
||||||
|
expect(interaction.editReply).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("GIVEN user.RemoveCurrency fails, EXPECT error", async () => {
|
||||||
|
// Arrange
|
||||||
|
User.FetchOneById = jest.fn().mockResolvedValue({
|
||||||
|
RemoveCurrency: jest.fn().mockReturnValue(false),
|
||||||
|
Currency: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const claim = new Claim();
|
||||||
|
await claim.execute(interaction as unknown as ButtonInteraction);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(interaction.channel!.send).toHaveBeenCalledTimes(1);
|
||||||
|
expect(interaction.channel!.send).toHaveBeenCalledWith("[object Object], Not enough currency! You need 10 currency, you have 5!");
|
||||||
|
|
||||||
|
expect(interaction.editReply).not.toHaveBeenCalled();
|
||||||
|
});
|
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",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`;
|
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,
|
||||||
|
}
|
||||||
|
`;
|
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");
|
||||||
|
});
|
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();
|
||||||
|
});
|
||||||
|
});
|
127
tests/helpers/EffectHelper.test.ts
Normal file
127
tests/helpers/EffectHelper.test.ts
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
import EffectHelper from "../../src/helpers/EffectHelper";
|
||||||
|
import UserEffect from "../../src/database/entities/app/UserEffect";
|
||||||
|
|
||||||
|
jest.mock("../../src/database/entities/app/UserEffect");
|
||||||
|
|
||||||
|
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,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = await EffectHelper.GenerateEffectListEmbed("userId", 1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("GIVEN user has more than 1 page of effects, 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", 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),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = await EffectHelper.GenerateEffectListEmbed("userId", 1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GenerateEffectBuyEmbed", () => {
|
||||||
|
test.todo("GIVEN Effect Details are not found, EXPECT error");
|
||||||
|
|
||||||
|
test.todo("GIVEN user is not in database, EXPECT blank user created");
|
||||||
|
|
||||||
|
test.todo("GIVEN user does not have enough currency, EXPECT error");
|
||||||
|
|
||||||
|
test.todo("GIVEN user does have enough currency, EXPECT embed returned");
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
|
@ -1,66 +0,0 @@
|
||||||
import {CoreClient} from "../src/client/client";
|
|
||||||
import Registry from "../src/registry";
|
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
describe("RegisterCommands", () => {
|
|
||||||
test("EXPECT every command in the commands folder to be registered", () => {
|
|
||||||
const registeredCommands: string[] = [];
|
|
||||||
|
|
||||||
CoreClient.RegisterCommand = jest.fn().mockImplementation((name: string) => {
|
|
||||||
registeredCommands.push(name);
|
|
||||||
});
|
|
||||||
|
|
||||||
Registry.RegisterCommands();
|
|
||||||
|
|
||||||
const commandFiles = getFilesInDirectory(path.join(process.cwd(), "src", "commands"))
|
|
||||||
.filter(x => x.endsWith(".ts"));
|
|
||||||
|
|
||||||
for (const file of commandFiles) {
|
|
||||||
expect(registeredCommands).toContain(file.split("/").pop()!.split(".")[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(commandFiles.length).toBe(registeredCommands.length);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("RegisterButtonEvents", () => {
|
|
||||||
test("EXEPCT every button event in the button events folder to be registered", () => {
|
|
||||||
const registeredButtonEvents: string[] = [];
|
|
||||||
|
|
||||||
CoreClient.RegisterButtonEvent = jest.fn().mockImplementation((name: string) => {
|
|
||||||
registeredButtonEvents.push(name);
|
|
||||||
});
|
|
||||||
|
|
||||||
Registry.RegisterButtonEvents();
|
|
||||||
|
|
||||||
const eventFiles = getFilesInDirectory(path.join(process.cwd(), "src", "buttonEvents"))
|
|
||||||
.filter(x => x.endsWith(".ts"));
|
|
||||||
|
|
||||||
for (const file of eventFiles) {
|
|
||||||
expect(registeredButtonEvents).toContain(file.split("/").pop()!.split(".")[0].toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(eventFiles.length).toBe(registeredButtonEvents.length);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function getFilesInDirectory(dir: string): string[] {
|
|
||||||
let results: string[] = [];
|
|
||||||
const list = fs.readdirSync(dir);
|
|
||||||
|
|
||||||
list.forEach(file => {
|
|
||||||
file = path.join(dir, file);
|
|
||||||
const stat = fs.statSync(file);
|
|
||||||
|
|
||||||
if (stat && stat.isDirectory()) {
|
|
||||||
/* recurse into a subdirectory */
|
|
||||||
results = results.concat(getFilesInDirectory(file));
|
|
||||||
} else {
|
|
||||||
/* is a file */
|
|
||||||
results.push(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue
Do we want to instead make the
startsWith
part its own variable? Might be a bit more readable and such IMO