Compare commits
55 commits
main
...
feature/38
Author | SHA1 | Date | |
---|---|---|---|
ff9437ba81 | |||
9a2835a0eb | |||
dd1f259170 | |||
b37c087393 | |||
d874cb7a12 | |||
57c3d603a9 | |||
f8b013a091 | |||
ed52f3e3dc | |||
1cace42983 | |||
5db7cd9f11 | |||
2b96c7c0d3 | |||
3d143e7c73 | |||
d7a5472759 | |||
6c17a67d7a | |||
e67efd4197 | |||
9e963d90cb | |||
27c0e68f7a | |||
4c322f01de | |||
3304779297 | |||
73caf9315d | |||
76af70da06 | |||
8352b377bb | |||
f4c02d3613 | |||
480e496984 | |||
a8a5e39e01 | |||
816e550c84 | |||
cd7e0945a9 | |||
768f64b5ee | |||
1762b525b2 | |||
5ebc5ff27c | |||
2263871b3b | |||
c0e9378813 | |||
52c93c7803 | |||
8683c1e58a | |||
ea0ca17044 | |||
21d11afd31 | |||
a7b03d0355 | |||
761c58fb10 | |||
711d36698b | |||
57e06be9af | |||
d605eb5d59 | |||
66243e6742 | |||
79a4d18df3 | |||
8e4597512f | |||
805dd00357 | |||
5defb682c1 | |||
8bd5f44524 | |||
9302902b17 | |||
480786a1e9 | |||
981cdbfdd7 | |||
7a99d273f6 | |||
ec0292d658 | |||
89c6dc527a | |||
73776408b5 | |||
bf9b748f4f |
55 changed files with 3672 additions and 2021 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/**/*"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -16,7 +16,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
|
||||||
|
@ -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 }}
|
||||||
|
|
|
@ -16,7 +16,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
|
||||||
|
@ -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();
|
35
package.json
35
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,36 @@
|
||||||
"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",
|
||||||
"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...");
|
||||||
|
|
|
@ -12,6 +12,7 @@ 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 +59,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);
|
||||||
|
|
158
src/buttonEvents/Effects.ts
Normal file
158
src/buttonEvents/Effects.ts
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
import {ActionRowBuilder, ButtonBuilder, ButtonInteraction,ButtonStyle,Embed,EmbedBuilder} from "discord.js";
|
||||||
|
import {ButtonEvent} from "../type/buttonEvent";
|
||||||
|
import EffectHelper from "../helpers/EffectHelper";
|
||||||
|
import { EffectDetails } from "../constants/EffectDetails";
|
||||||
|
import TimeLengthInput from "../helpers/TimeLengthInput";
|
||||||
|
import EmbedColours from "../constants/EmbedColours";
|
||||||
|
|
||||||
|
export default class Effects extends ButtonEvent {
|
||||||
|
public override async execute(interaction: ButtonInteraction) {
|
||||||
|
const action = interaction.customId.split(" ")[1];
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case "list":
|
||||||
|
await this.List(interaction);
|
||||||
|
break;
|
||||||
|
case "use":
|
||||||
|
await this.Use(interaction);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async 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.GenerateEffectEmbed(interaction.user.id, page);
|
||||||
|
|
||||||
|
await interaction.update({
|
||||||
|
embeds: [ result.embed ],
|
||||||
|
components: [ result.row ],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Use(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 async UseConfirm(interaction: ButtonInteraction) {
|
||||||
|
const id = interaction.customId.split(" ")[3];
|
||||||
|
|
||||||
|
const effectDetail = EffectDetails.get(id);
|
||||||
|
|
||||||
|
if (!effectDetail) {
|
||||||
|
await interaction.reply("Unable to find effect!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const whenExpires = new Date(now.getMilliseconds() + effectDetail.duration);
|
||||||
|
|
||||||
|
const result = await EffectHelper.UseEffect(interaction.user.id, id, whenExpires);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
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:${whenExpires.getMilliseconds()}: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 ],
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.reply("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UseCancel(interaction: ButtonInteraction) {
|
||||||
|
const id = interaction.customId.split(" ")[3];
|
||||||
|
|
||||||
|
const effectDetail = EffectDetails.get(id);
|
||||||
|
|
||||||
|
if (!effectDetail) {
|
||||||
|
await interaction.reply("Unable to find effect!");
|
||||||
|
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 ],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ export default class Inventory extends ButtonEvent {
|
||||||
const page = interaction.customId.split(" ")[2];
|
const page = interaction.customId.split(" ")[2];
|
||||||
|
|
||||||
AppLogger.LogSilly("Button/Inventory", `Parameters: userid=${userid}, page=${page}`);
|
AppLogger.LogSilly("Button/Inventory", `Parameters: userid=${userid}, page=${page}`);
|
||||||
|
|
||||||
await interaction.deferUpdate();
|
await interaction.deferUpdate();
|
||||||
|
|
||||||
const member = interaction.guild.members.cache.find(x => x.id == userid) || await interaction.guild.members.fetch(userid);
|
const member = interaction.guild.members.cache.find(x => x.id == userid) || await interaction.guild.members.fetch(userid);
|
||||||
|
@ -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}`);
|
||||||
|
|
213
src/buttonEvents/Multidrop.ts
Normal file
213
src/buttonEvents/Multidrop.ts
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
import { AttachmentBuilder, ButtonInteraction, EmbedBuilder } from "discord.js";
|
||||||
|
import { ButtonEvent } from "../type/buttonEvent";
|
||||||
|
import AppLogger from "../client/appLogger";
|
||||||
|
import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
||||||
|
import Inventory from "../database/entities/app/Inventory";
|
||||||
|
import EmbedColours from "../constants/EmbedColours";
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import ErrorMessages from "../constants/ErrorMessages";
|
||||||
|
import User from "../database/entities/app/User";
|
||||||
|
import { GetSacrificeAmount } from "../constants/CardRarity";
|
||||||
|
|
||||||
|
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 = CardDropHelperMetadata.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 = CardDropHelperMetadata.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 = CardDropHelperMetadata.GenerateMultidropEmbed(randomCard, quantityClaimed, imageFileName, cardsRemaining, undefined, user.Currency);
|
||||||
|
|
||||||
|
const row = CardDropHelperMetadata.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 = CardDropHelperMetadata.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 = CardDropHelperMetadata.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 = CardDropHelperMetadata.GenerateMultidropEmbed(randomCard, quantityClaimed, imageFileName, cardsRemaining, undefined, user.Currency);
|
||||||
|
|
||||||
|
const row = CardDropHelperMetadata.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})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,11 +32,16 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cardInInventory.Quantity < quantity) {
|
||||||
|
await interaction.reply("You can only sacrifice what you own.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardNumber);
|
const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardNumber);
|
||||||
|
|
||||||
if (!cardData) {
|
if (!cardData) {
|
||||||
|
@ -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,11 +114,16 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cardInInventory.Quantity < quantity) {
|
||||||
|
await interaction.reply("You can only sacrifice what you own.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardNumber);
|
const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardNumber);
|
||||||
|
|
||||||
if (!cardData) {
|
if (!cardData) {
|
||||||
|
@ -118,7 +131,7 @@ export default class Sacrifice extends ButtonEvent {
|
||||||
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.attachment ],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,9 @@ 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 { DropResult } from "../contracts/SeriesMetadata";
|
||||||
|
import EffectHelper from "../helpers/EffectHelper";
|
||||||
|
|
||||||
export default class Drop extends Command {
|
export default class Drop extends Command {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -22,14 +25,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 +45,23 @@ 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();
|
let randomCard: DropResult | undefined;
|
||||||
|
|
||||||
|
const hasChanceUpEffect = await EffectHelper.HasEffect(interaction.user.id, "unclaimed");
|
||||||
|
|
||||||
|
if (hasChanceUpEffect && Math.random() <= CardConstants.UnusedChanceUpChance) {
|
||||||
|
randomCard = await CardDropHelperMetadata.GetRandomCardUnclaimed(interaction.user.id);
|
||||||
|
} else {
|
||||||
|
randomCard = CardDropHelperMetadata.GetRandomCard();
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
116
src/commands/effects.ts
Normal file
116
src/commands/effects.ts
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
import {ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder, SlashCommandBuilder} from "discord.js";
|
||||||
|
import {Command} from "../type/command";
|
||||||
|
import EffectHelper from "../helpers/EffectHelper";
|
||||||
|
import {EffectDetails} from "../constants/EffectDetails";
|
||||||
|
import UserEffect from "../database/entities/app/UserEffect";
|
||||||
|
import TimeLengthInput from "../helpers/TimeLengthInput";
|
||||||
|
import EmbedColours from "../constants/EmbedColours";
|
||||||
|
|
||||||
|
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([
|
||||||
|
{ name: "Unclaimed Chance Up", value: "unclaimed" },
|
||||||
|
])));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async execute(interaction: CommandInteraction) {
|
||||||
|
if (!interaction.isChatInputCommand()) return;
|
||||||
|
|
||||||
|
const subcommand = interaction.options.getSubcommand();
|
||||||
|
|
||||||
|
switch (subcommand) {
|
||||||
|
case "list":
|
||||||
|
await this.List(interaction);
|
||||||
|
break;
|
||||||
|
case "use":
|
||||||
|
await this.Use(interaction);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async List(interaction: CommandInteraction) {
|
||||||
|
const pageOption = interaction.options.get("page");
|
||||||
|
|
||||||
|
const page = !isNaN(Number(pageOption?.value)) ? Number(pageOption?.value) : 1;
|
||||||
|
|
||||||
|
const result = await EffectHelper.GenerateEffectEmbed(interaction.user.id, page);
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [ result.embed ],
|
||||||
|
components: [ result.row ],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Use(interaction: CommandInteraction) {
|
||||||
|
const id = interaction.options.get("id", true).value!.toString();
|
||||||
|
|
||||||
|
const effectDetail = EffectDetails.get(id);
|
||||||
|
|
||||||
|
if (!effectDetail) {
|
||||||
|
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 ],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
82
src/commands/id.ts
Normal file
82
src/commands/id.ts
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
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 CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
||||||
|
import AppLogger from "../client/appLogger";
|
||||||
|
|
||||||
|
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))!;
|
||||||
|
|
||||||
|
let image: Buffer;
|
||||||
|
const imageFileName = card.path.split("/").pop()!;
|
||||||
|
|
||||||
|
try {
|
||||||
|
image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.path));
|
||||||
|
} catch {
|
||||||
|
AppLogger.LogError("Commands/View", `Unable to fetch image for card ${card.id}.`);
|
||||||
|
|
||||||
|
await interaction.reply(`Unable to fetch image for card ${card.id}.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.deferReply();
|
||||||
|
|
||||||
|
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||||
|
|
||||||
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.id);
|
||||||
|
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
||||||
|
|
||||||
|
const embed = CardDropHelperMetadata.GenerateDropEmbed({ card, series }, quantityClaimed, imageFileName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await interaction.editReply({
|
||||||
|
embeds: [ embed ],
|
||||||
|
files: [ attachment ],
|
||||||
|
});
|
||||||
|
} 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);
|
||||||
|
|
87
src/commands/multidrop.ts
Normal file
87
src/commands/multidrop.ts
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
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 CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import Inventory from "../database/entities/app/Inventory";
|
||||||
|
|
||||||
|
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 = CardDropHelperMetadata.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 = CardDropHelperMetadata.GenerateMultidropEmbed(randomCard, quantityClaimed, imageFileName, cardsRemaining, undefined, user.Currency);
|
||||||
|
|
||||||
|
const row = CardDropHelperMetadata.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})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,6 +36,11 @@ export default class Sacrifice extends Command {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cardInInventory.Quantity < quantity) {
|
||||||
|
await interaction.reply(`You can only sacrifice what you own! You have ${cardInInventory.Quantity} of this card`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardnumber.value! as string);
|
const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardnumber.value! as string);
|
||||||
|
|
||||||
if (!cardData) {
|
if (!cardData) {
|
||||||
|
@ -36,7 +48,7 @@ export default class Sacrifice extends Command {
|
||||||
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),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -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,70 +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))!;
|
|
||||||
|
|
||||||
let image: Buffer;
|
|
||||||
const imageFileName = card.path.split("/").pop()!;
|
|
||||||
|
|
||||||
try {
|
|
||||||
image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.path));
|
|
||||||
} catch {
|
|
||||||
AppLogger.LogError("Commands/View", `Unable to fetch image for card ${card.id}.`);
|
|
||||||
|
|
||||||
await interaction.reply(`Unable to fetch image for card ${card.id}.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
|
|
||||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
const searchResult = await CardSearchHelper.GenerateSearchQuery(name.value!.toString(), interaction.user.id, 7);
|
||||||
|
|
||||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.id);
|
if (!searchResult) {
|
||||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
await interaction.editReply("No results found");
|
||||||
|
return;
|
||||||
const embed = CardDropHelperMetadata.GenerateDropEmbed({ card, series }, quantityClaimed, imageFileName);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await interaction.editReply({
|
|
||||||
embeds: [ embed ],
|
|
||||||
files: [ attachment ],
|
|
||||||
});
|
|
||||||
} 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.attachment ],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 = 1;
|
||||||
}
|
}
|
19
src/constants/EffectDetails.ts
Normal file
19
src/constants/EffectDetails.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
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) ],
|
||||||
|
]);
|
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) {
|
||||||
|
|
74
src/database/entities/app/UserEffect.ts
Normal file
74
src/database/entities/app/UserEffect.ts
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
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 })
|
||||||
|
.where("effect.Unused > 0")
|
||||||
|
.orderBy("effect.Name", "ASC")
|
||||||
|
.skip(page * itemsPerPage)
|
||||||
|
.take(itemsPerPage)
|
||||||
|
.getManyAndCount();
|
||||||
|
|
||||||
|
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,11 +1,12 @@
|
||||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
||||||
import { CardRarity, CardRarityToColour, CardRarityToString } from "../constants/CardRarity";
|
import { CardRarity, CardRarityToColour, CardRarityToString, GetSacrificeAmount } from "../constants/CardRarity";
|
||||||
import CardRarityChances from "../constants/CardRarityChances";
|
import CardRarityChances from "../constants/CardRarityChances";
|
||||||
import { DropResult } from "../contracts/SeriesMetadata";
|
import { DropResult } from "../contracts/SeriesMetadata";
|
||||||
import { CoreClient } from "../client/client";
|
import { CoreClient } from "../client/client";
|
||||||
import AppLogger from "../client/appLogger";
|
import AppLogger from "../client/appLogger";
|
||||||
import CardConstants from "../constants/CardConstants";
|
import CardConstants from "../constants/CardConstants";
|
||||||
import StringTools from "./StringTools";
|
import StringTools from "./StringTools";
|
||||||
|
import Inventory from "../database/entities/app/Inventory";
|
||||||
|
|
||||||
export default class CardDropHelperMetadata {
|
export default class CardDropHelperMetadata {
|
||||||
public static GetRandomCard(): DropResult | undefined {
|
public static GetRandomCard(): DropResult | undefined {
|
||||||
|
@ -58,6 +59,64 @@ export default class CardDropHelperMetadata {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCardUnclaimed", `Random card: ${randomCard?.card.id} ${randomCard?.card.name}`);
|
||||||
|
|
||||||
|
return randomCard;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async GetRandomCardByRarityUnclaimed(rarity: CardRarity, userId: string): Promise<DropResult | undefined> {
|
||||||
|
AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCardByRarityUnclaimed", `Parameters: rarity=${rarity}, userId=${userId}`);
|
||||||
|
|
||||||
|
const claimedCards = await Inventory.FetchAllByUserId(userId);
|
||||||
|
|
||||||
|
if (!claimedCards) {
|
||||||
|
// They don't have any cards, so safe to get any random card
|
||||||
|
return this.GetRandomCardByRarity(rarity);
|
||||||
|
}
|
||||||
|
|
||||||
|
const allCards = CoreClient.Cards
|
||||||
|
.flatMap(x => x.cards)
|
||||||
|
.filter(x => x.type == rarity)
|
||||||
|
.filter(x => !claimedCards.find(y => y.CardNumber == x.id));
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCardByRarityUnclaimed", `Random card: ${card.id} ${card.name}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
series: series,
|
||||||
|
card: card,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static GetCardByCardNumber(cardNumber: string): DropResult | undefined {
|
public static GetCardByCardNumber(cardNumber: string): DropResult | undefined {
|
||||||
AppLogger.LogSilly("CardDropHelperMetadata/GetCardByCardNumber", `Parameters: cardNumber=${cardNumber}`);
|
AppLogger.LogSilly("CardDropHelperMetadata/GetCardByCardNumber", `Parameters: cardNumber=${cardNumber}`);
|
||||||
|
|
||||||
|
@ -89,7 +148,7 @@ export default class CardDropHelperMetadata {
|
||||||
const hexCode = Number("0x" + drop.card.colour);
|
const hexCode = Number("0x" + drop.card.colour);
|
||||||
|
|
||||||
if (hexCode) {
|
if (hexCode) {
|
||||||
colour = hexCode;
|
colour = hexCode;
|
||||||
} else {
|
} else {
|
||||||
AppLogger.LogWarn("CardDropHelperMetadata/GenerateDropEmbed", `Card's colour override is invalid: ${drop.card.id}, ${drop.card.colour}`);
|
AppLogger.LogWarn("CardDropHelperMetadata/GenerateDropEmbed", `Card's colour override is invalid: ${drop.card.id}, ${drop.card.colour}`);
|
||||||
}
|
}
|
||||||
|
@ -149,4 +208,26 @@ export default class CardDropHelperMetadata {
|
||||||
.setLabel("Reroll")
|
.setLabel("Reroll")
|
||||||
.setStyle(ButtonStyle.Secondary));
|
.setStyle(ButtonStyle.Secondary));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static GenerateMultidropEmbed(drop: DropResult, quantityClaimed: number, imageFileName: string, cardsRemaining: number, claimedBy?: string, currency?: number): EmbedBuilder {
|
||||||
|
const dropEmbed = this.GenerateDropEmbed(drop, quantityClaimed, imageFileName, claimedBy, currency);
|
||||||
|
|
||||||
|
dropEmbed.setFooter({ text: `${dropEmbed.data.footer?.text} · ${cardsRemaining} Remaining`});
|
||||||
|
|
||||||
|
return dropEmbed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GenerateMultidropButtons(drop: DropResult, cardsRemaining: number, userId: string, disabled = false): ActionRowBuilder<ButtonBuilder> {
|
||||||
|
return new ActionRowBuilder<ButtonBuilder>()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`multidrop keep ${drop.card.id} ${cardsRemaining} ${userId}`)
|
||||||
|
.setLabel("Keep")
|
||||||
|
.setStyle(ButtonStyle.Primary)
|
||||||
|
.setDisabled(disabled),
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`multidrop sacrifice ${drop.card.id} ${cardsRemaining} ${userId}`)
|
||||||
|
.setLabel(`Sacrifice (+${GetSacrificeAmount(drop.card.type)} 🪙)`)
|
||||||
|
.setStyle(ButtonStyle.Secondary));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
117
src/helpers/CardSearchHelper.ts
Normal file
117
src/helpers/CardSearchHelper.ts
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
import {ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder} from "discord.js";
|
||||||
|
import Fuse from "fuse.js";
|
||||||
|
import {CoreClient} from "../client/client.js";
|
||||||
|
import CardDropHelperMetadata from "./CardDropHelperMetadata.js";
|
||||||
|
import Inventory from "../database/entities/app/Inventory.js";
|
||||||
|
import {readFileSync} from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import AppLogger from "../client/appLogger.js";
|
||||||
|
|
||||||
|
interface ReturnedPage {
|
||||||
|
embed: EmbedBuilder,
|
||||||
|
row: ActionRowBuilder<ButtonBuilder>,
|
||||||
|
attachment: 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 = CardDropHelperMetadata.GetCardByCardNumber(entry.item.id);
|
||||||
|
|
||||||
|
if (!card) return undefined;
|
||||||
|
|
||||||
|
let image: Buffer;
|
||||||
|
const imageFileName = card.card.path.split("/").pop()!;
|
||||||
|
|
||||||
|
try {
|
||||||
|
image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path));
|
||||||
|
} catch {
|
||||||
|
AppLogger.LogError("CardSearchHelper/GenerateSearchQuery", `Unable to fetch image for card ${card.card.id}.`);
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||||
|
|
||||||
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(userid, card.card.id);
|
||||||
|
const quantityClaimed = inventory?.Quantity ?? 0;
|
||||||
|
|
||||||
|
const embed = CardDropHelperMetadata.GenerateDropEmbed(card, quantityClaimed, imageFileName);
|
||||||
|
|
||||||
|
const 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, attachment, results };
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async GenerateSearchPageFromQuery(results: string[], userid: string, page: number): Promise<ReturnedPage | undefined> {
|
||||||
|
const currentPageId = results[page - 1];
|
||||||
|
|
||||||
|
const card = CardDropHelperMetadata.GetCardByCardNumber(currentPageId);
|
||||||
|
|
||||||
|
if (!card) {
|
||||||
|
AppLogger.LogError("CardSearchHelper/GenerateSearchPageFromQuery", `Unable to find card by id: ${currentPageId}.`);
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let image: Buffer;
|
||||||
|
const imageFileName = card.card.path.split("/").pop()!;
|
||||||
|
|
||||||
|
try {
|
||||||
|
image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path));
|
||||||
|
} catch {
|
||||||
|
AppLogger.LogError("CardSearchHelper/GenerateSearchPageFromQuery", `Unable to fetch image for card ${card.card.id}.`);
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||||
|
|
||||||
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(userid, card.card.id);
|
||||||
|
const quantityClaimed = inventory?.Quantity ?? 0;
|
||||||
|
|
||||||
|
const embed = CardDropHelperMetadata.GenerateDropEmbed(card, quantityClaimed, imageFileName);
|
||||||
|
|
||||||
|
const 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, attachment, results };
|
||||||
|
}
|
||||||
|
}
|
115
src/helpers/EffectHelper.ts
Normal file
115
src/helpers/EffectHelper.ts
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
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";
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (!effect) return false;
|
||||||
|
|
||||||
|
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.Id);
|
||||||
|
|
||||||
|
if (!effectDetail) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effect.WhenExpires && now < new Date(effect.WhenExpires.getMilliseconds() + 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 GenerateEffectEmbed(userId: string, page: number): Promise<{
|
||||||
|
embed: EmbedBuilder,
|
||||||
|
row: ActionRowBuilder<ButtonBuilder>,
|
||||||
|
}> {
|
||||||
|
const itemsPerPage = 10;
|
||||||
|
|
||||||
|
const query = await UserEffect.FetchAllByUserIdPaginated(userId, page - 1, itemsPerPage);
|
||||||
|
|
||||||
|
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 => `${x.Name} x${x.Unused}`).join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle("Effects")
|
||||||
|
.setDescription(description)
|
||||||
|
.setColor(EmbedColours.Ok)
|
||||||
|
.setFooter({ text: `Page ${page} of ${totalPages}` });
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ 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 {Jimp} from "jimp";
|
||||||
|
|
||||||
interface CardInput {
|
interface CardInput {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -46,7 +46,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>;
|
||||||
|
}
|
127
tests/buttonEvents/Effects.test.ts
Normal file
127
tests/buttonEvents/Effects.test.ts
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
import {ButtonInteraction} from "discord.js";
|
||||||
|
import Effects from "../../src/buttonEvents/Effects";
|
||||||
|
import EffectHelper from "../../src/helpers/EffectHelper";
|
||||||
|
|
||||||
|
describe("execute", () => {
|
||||||
|
describe("GIVEN action in custom id is list", () => {
|
||||||
|
const interaction = {
|
||||||
|
customId: "effects list",
|
||||||
|
} as unknown as ButtonInteraction;
|
||||||
|
|
||||||
|
let listSpy: jest.SpyInstance;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const effects = new Effects();
|
||||||
|
|
||||||
|
listSpy = jest.spyOn(effects as unknown as {"List": () => object}, "List")
|
||||||
|
.mockImplementation();
|
||||||
|
|
||||||
|
await effects.execute(interaction);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT list function to be called", () => {
|
||||||
|
expect(listSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(listSpy).toHaveBeenCalledWith(interaction);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("List", () => {
|
||||||
|
let interaction: ButtonInteraction;
|
||||||
|
|
||||||
|
const embed = {
|
||||||
|
name: "Embed",
|
||||||
|
};
|
||||||
|
|
||||||
|
const row = {
|
||||||
|
name: "Row",
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
interaction = {
|
||||||
|
customId: "effects list",
|
||||||
|
user: {
|
||||||
|
id: "userId",
|
||||||
|
},
|
||||||
|
update: jest.fn(),
|
||||||
|
reply: jest.fn(),
|
||||||
|
} as unknown as ButtonInteraction;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GIVEN page is a valid number", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
interaction.customId += " 1";
|
||||||
|
|
||||||
|
EffectHelper.GenerateEffectEmbed = jest.fn()
|
||||||
|
.mockResolvedValue({
|
||||||
|
embed,
|
||||||
|
row,
|
||||||
|
});
|
||||||
|
|
||||||
|
const effects = new Effects();
|
||||||
|
|
||||||
|
await effects.execute(interaction);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT EffectHelper.GenerateEffectEmbed to be called", () => {
|
||||||
|
expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledTimes(1);
|
||||||
|
expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledWith("userId", 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT interaction to be updated", () => {
|
||||||
|
expect(interaction.update).toHaveBeenCalledTimes(1);
|
||||||
|
expect(interaction.update).toHaveBeenCalledWith({
|
||||||
|
embeds: [ embed ],
|
||||||
|
components: [ row ],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GIVEN page in custom id is not supplied", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
EffectHelper.GenerateEffectEmbed = jest.fn()
|
||||||
|
.mockResolvedValue({
|
||||||
|
embed,
|
||||||
|
row,
|
||||||
|
});
|
||||||
|
|
||||||
|
const effects = new Effects();
|
||||||
|
|
||||||
|
await effects.execute(interaction);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT interaction to be replied with error", () => {
|
||||||
|
expect(interaction.reply).toHaveBeenCalledTimes(1);
|
||||||
|
expect(interaction.reply).toHaveBeenCalledWith("Page option is not a valid number");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT interaction to not be updated", () => {
|
||||||
|
expect(interaction.update).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GIVEN page in custom id is not a number", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
interaction.customId += " test";
|
||||||
|
|
||||||
|
EffectHelper.GenerateEffectEmbed = jest.fn()
|
||||||
|
.mockResolvedValue({
|
||||||
|
embed,
|
||||||
|
row,
|
||||||
|
});
|
||||||
|
|
||||||
|
const effects = new Effects();
|
||||||
|
|
||||||
|
await effects.execute(interaction);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT interaction to be replied with error", () => {
|
||||||
|
expect(interaction.reply).toHaveBeenCalledTimes(1);
|
||||||
|
expect(interaction.reply).toHaveBeenCalledWith("Page option is not a valid number");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT interaction to not be updated", () => {
|
||||||
|
expect(interaction.update).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
40
tests/commands/__snapshots__/effects.test.ts.snap
Normal file
40
tests/commands/__snapshots__/effects.test.ts.snap
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`constructor 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,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"type": 1,
|
||||||
|
}
|
||||||
|
`;
|
164
tests/commands/effects.test.ts
Normal file
164
tests/commands/effects.test.ts
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
import {ChatInputCommandInteraction} from "discord.js";
|
||||||
|
import Effects from "../../src/commands/effects";
|
||||||
|
import EffectHelper from "../../src/helpers/EffectHelper";
|
||||||
|
|
||||||
|
describe("constructor", () => {
|
||||||
|
let effects: Effects;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
effects = new Effects();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT CommandBuilder to be defined", () => {
|
||||||
|
expect(effects.CommandBuilder).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("execute", () => {
|
||||||
|
describe("GIVEN interaction is not a chat input command", () => {
|
||||||
|
let interaction: ChatInputCommandInteraction;
|
||||||
|
|
||||||
|
let listSpy: jest.SpyInstance;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
interaction = {
|
||||||
|
isChatInputCommand: jest.fn().mockReturnValue(false),
|
||||||
|
} as unknown as ChatInputCommandInteraction;
|
||||||
|
|
||||||
|
const effects = new Effects();
|
||||||
|
|
||||||
|
listSpy = jest.spyOn(effects as unknown as {"List": () => object}, "List")
|
||||||
|
.mockImplementation();
|
||||||
|
|
||||||
|
await effects.execute(interaction);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT isChatInputCommand to have been called", () => {
|
||||||
|
expect(interaction.isChatInputCommand).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT nothing to happen", () => {
|
||||||
|
expect(listSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GIVEN subcommand is list", () => {
|
||||||
|
let interaction: ChatInputCommandInteraction;
|
||||||
|
|
||||||
|
let listSpy: jest.SpyInstance;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
interaction = {
|
||||||
|
isChatInputCommand: jest.fn().mockReturnValue(true),
|
||||||
|
options: {
|
||||||
|
getSubcommand: jest.fn().mockReturnValue("list"),
|
||||||
|
},
|
||||||
|
} as unknown as ChatInputCommandInteraction;
|
||||||
|
|
||||||
|
const effects = new Effects();
|
||||||
|
|
||||||
|
listSpy = jest.spyOn(effects as unknown as {"List": () => object}, "List")
|
||||||
|
.mockImplementation();
|
||||||
|
|
||||||
|
await effects.execute(interaction);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT subcommand function to be called", () => {
|
||||||
|
expect(interaction.options.getSubcommand).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT list function to be called", () => {
|
||||||
|
expect(listSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(listSpy).toHaveBeenCalledWith(interaction);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("List", () => {
|
||||||
|
const effects: Effects = new Effects();
|
||||||
|
let interaction: ChatInputCommandInteraction;
|
||||||
|
|
||||||
|
const embed = {
|
||||||
|
name: "embed",
|
||||||
|
};
|
||||||
|
|
||||||
|
const row = {
|
||||||
|
name: "row",
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
interaction = {
|
||||||
|
isChatInputCommand: jest.fn().mockReturnValue(true),
|
||||||
|
options: {
|
||||||
|
getSubcommand: jest.fn().mockReturnValue("list"),
|
||||||
|
},
|
||||||
|
reply: jest.fn(),
|
||||||
|
user: {
|
||||||
|
id: "userId",
|
||||||
|
},
|
||||||
|
} as unknown as ChatInputCommandInteraction;
|
||||||
|
|
||||||
|
const effects = new Effects();
|
||||||
|
|
||||||
|
EffectHelper.GenerateEffectEmbed = jest.fn().mockReturnValue({
|
||||||
|
embed,
|
||||||
|
row,
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.spyOn(effects as unknown as {"List": () => object}, "List")
|
||||||
|
.mockImplementation();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GIVEN page option is supplied", () => {
|
||||||
|
describe("AND page is a valid number", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
interaction.options.get = jest.fn().mockReturnValueOnce({
|
||||||
|
value: "2",
|
||||||
|
});
|
||||||
|
|
||||||
|
await effects.execute(interaction);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT EffectHelper.GenerateEffectEmbed to have been called with page", () => {
|
||||||
|
expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledTimes(1);
|
||||||
|
expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledWith("userId", 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT interaction to have been replied", () => {
|
||||||
|
expect(interaction.reply).toHaveBeenCalledTimes(1);
|
||||||
|
expect(interaction.reply).toHaveBeenCalledWith({
|
||||||
|
embeds: [ embed ],
|
||||||
|
components: [ row ],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("AND page is not a valid number", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
interaction.options.get = jest.fn().mockReturnValueOnce({
|
||||||
|
value: "test",
|
||||||
|
});
|
||||||
|
|
||||||
|
await effects.execute(interaction);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT EffectHelper.GenerateEffectEmbed to have been called with page of 1", () => {
|
||||||
|
expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledTimes(1);
|
||||||
|
expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledWith("userId", 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GIVEN page option is not supplied", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
interaction.options.get = jest.fn().mockReturnValueOnce(undefined);
|
||||||
|
|
||||||
|
await effects.execute(interaction);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT EffectHelper.GenerateEffectEmbed to have been called with page of 1", () => {
|
||||||
|
expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledTimes(1);
|
||||||
|
expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledWith("userId", 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
103
tests/database/entities/app/UserEffect.test.ts
Normal file
103
tests/database/entities/app/UserEffect.test.ts
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import UserEffect from "../../../../src/database/entities/app/UserEffect";
|
||||||
|
|
||||||
|
let userEffect: UserEffect;
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
userEffect = new UserEffect("name", "userId", 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("AddUnused", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
userEffect.AddUnused(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT unused to be the amount more", () => {
|
||||||
|
expect(userEffect.Unused).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("UseEffect", () => {
|
||||||
|
describe("GIVEN Unused is 0", () => {
|
||||||
|
let result: boolean;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
userEffect.Unused = 0;
|
||||||
|
|
||||||
|
result = userEffect.UseEffect(now);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT false returned", () => {
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT details not to be changed", () => {
|
||||||
|
expect(userEffect.Unused).toBe(0);
|
||||||
|
expect(userEffect.WhenExpires).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GIVEN Unused is greater than 0", () => {
|
||||||
|
let result: boolean;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
result = userEffect.UseEffect(now);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT true returned", () => {
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT Unused to be subtracted by 1", () => {
|
||||||
|
expect(userEffect.Unused).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT WhenExpires to be set", () => {
|
||||||
|
expect(userEffect.WhenExpires).toBe(now);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("IsEffectActive", () => {
|
||||||
|
describe("GIVEN WhenExpires is null", () => {
|
||||||
|
let result: boolean;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
result = userEffect.IsEffectActive();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT false returned", () => {
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GIVEN WhenExpires is defined", () => {
|
||||||
|
describe("AND WhenExpires is in the past", () => {
|
||||||
|
let result: boolean;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
userEffect.WhenExpires = new Date(now.getTime() - 100);
|
||||||
|
|
||||||
|
result = userEffect.IsEffectActive();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT false returned", () => {
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("AND WhenExpires is in the future", () => {
|
||||||
|
let result: boolean;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
userEffect.WhenExpires = new Date(now.getTime() + 100);
|
||||||
|
|
||||||
|
result = userEffect.IsEffectActive();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT true returned", () => {
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
380
tests/helpers/EffectHelper.test.ts
Normal file
380
tests/helpers/EffectHelper.test.ts
Normal file
|
@ -0,0 +1,380 @@
|
||||||
|
import {ActionRowBuilder, ButtonBuilder, EmbedBuilder} from "discord.js";
|
||||||
|
import UserEffect from "../../src/database/entities/app/UserEffect";
|
||||||
|
import EffectHelper from "../../src/helpers/EffectHelper";
|
||||||
|
|
||||||
|
describe("AddEffectToUserInventory", () => {
|
||||||
|
describe("GIVEN effect is in database", () => {
|
||||||
|
const effectMock = {
|
||||||
|
AddUnused: jest.fn(),
|
||||||
|
Save: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(effectMock);
|
||||||
|
|
||||||
|
await EffectHelper.AddEffectToUserInventory("userId", "name", 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT database to be fetched", () => {
|
||||||
|
expect(UserEffect.FetchOneByUserIdAndName).toHaveBeenCalledTimes(1);
|
||||||
|
expect(UserEffect.FetchOneByUserIdAndName).toHaveBeenCalledWith("userId", "name");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT effect to be updated", () => {
|
||||||
|
expect(effectMock.AddUnused).toHaveBeenCalledTimes(1);
|
||||||
|
expect(effectMock.AddUnused).toHaveBeenCalledWith(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT effect to be saved", () => {
|
||||||
|
expect(effectMock.Save).toHaveBeenCalledTimes(1);
|
||||||
|
expect(effectMock.Save).toHaveBeenCalledWith(UserEffect, effectMock);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GIVEN effect is not in database", () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(null);
|
||||||
|
UserEffect.prototype.Save = jest.fn();
|
||||||
|
|
||||||
|
await EffectHelper.AddEffectToUserInventory("userId", "name", 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT effect to be saved", () => {
|
||||||
|
expect(UserEffect.prototype.Save).toHaveBeenCalledTimes(1);
|
||||||
|
expect(UserEffect.prototype.Save).toHaveBeenCalledWith(UserEffect, expect.any(UserEffect));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("UseEffect", () => {
|
||||||
|
describe("GIVEN effect is in database", () => {
|
||||||
|
describe("GIVEN now is before effect.WhenExpires", () => {
|
||||||
|
let result: boolean | undefined;
|
||||||
|
|
||||||
|
// nowMock < whenExpires
|
||||||
|
const nowMock = new Date(2024, 11, 3, 13, 30);
|
||||||
|
const whenExpires = new Date(2024, 11, 3, 14, 0);
|
||||||
|
|
||||||
|
const userEffect = {
|
||||||
|
Unused: 1,
|
||||||
|
WhenExpires: whenExpires,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.setSystemTime(nowMock);
|
||||||
|
|
||||||
|
UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(userEffect);
|
||||||
|
|
||||||
|
result = await EffectHelper.UseEffect("userId", "name", new Date());
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT false returned", () => {
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GIVEN currently used effect is inactive", () => {
|
||||||
|
let result: boolean | undefined;
|
||||||
|
|
||||||
|
// nowMock > whenExpires
|
||||||
|
const nowMock = new Date(2024, 11, 3, 13, 30);
|
||||||
|
const whenExpires = new Date(2024, 11, 3, 13, 0);
|
||||||
|
const whenExpiresNew = new Date(2024, 11, 3, 15, 0);
|
||||||
|
|
||||||
|
const userEffect = {
|
||||||
|
Unused: 1,
|
||||||
|
WhenExpires: whenExpires,
|
||||||
|
UseEffect: jest.fn(),
|
||||||
|
Save: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.setSystemTime(nowMock);
|
||||||
|
|
||||||
|
UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(userEffect);
|
||||||
|
|
||||||
|
result = await EffectHelper.UseEffect("userId", "name", whenExpiresNew);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT UseEffect to be called", () => {
|
||||||
|
expect(userEffect.UseEffect).toHaveReturnedTimes(1);
|
||||||
|
expect(userEffect.UseEffect).toHaveBeenCalledWith(whenExpiresNew);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT effect to be saved", () => {
|
||||||
|
expect(userEffect.Save).toHaveBeenCalledTimes(1);
|
||||||
|
expect(userEffect.Save).toHaveBeenCalledWith(UserEffect, userEffect);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT true returned", () => {
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GIVEN effect.WhenExpires is null", () => {
|
||||||
|
let result: boolean | undefined;
|
||||||
|
|
||||||
|
// nowMock > whenExpires
|
||||||
|
const nowMock = new Date(2024, 11, 3, 13, 30);
|
||||||
|
const whenExpiresNew = new Date(2024, 11, 3, 15, 0);
|
||||||
|
|
||||||
|
const userEffect = {
|
||||||
|
Unused: 1,
|
||||||
|
WhenExpires: null,
|
||||||
|
UseEffect: jest.fn(),
|
||||||
|
Save: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.setSystemTime(nowMock);
|
||||||
|
|
||||||
|
UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(userEffect);
|
||||||
|
|
||||||
|
result = await EffectHelper.UseEffect("userId", "name", whenExpiresNew);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT UseEffect to be called", () => {
|
||||||
|
expect(userEffect.UseEffect).toHaveBeenCalledTimes(1);
|
||||||
|
expect(userEffect.UseEffect).toHaveBeenCalledWith(whenExpiresNew);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT effect to be saved", () => {
|
||||||
|
expect(userEffect.Save).toHaveBeenCalledTimes(1);
|
||||||
|
expect(userEffect.Save).toHaveBeenCalledWith(UserEffect, userEffect);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT true returned", () => {
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GIVEN effect is not in database", () => {
|
||||||
|
let result: boolean | undefined;
|
||||||
|
|
||||||
|
// nowMock > whenExpires
|
||||||
|
const nowMock = new Date(2024, 11, 3, 13, 30);
|
||||||
|
const whenExpiresNew = new Date(2024, 11, 3, 15, 0);
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.setSystemTime(nowMock);
|
||||||
|
|
||||||
|
UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(null);
|
||||||
|
|
||||||
|
result = await EffectHelper.UseEffect("userId", "name", whenExpiresNew);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT false returned", () => {
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GIVEN effect.Unused is 0", () => {
|
||||||
|
let result: boolean | undefined;
|
||||||
|
|
||||||
|
// nowMock > whenExpires
|
||||||
|
const nowMock = new Date(2024, 11, 3, 13, 30);
|
||||||
|
const whenExpiresNew = new Date(2024, 11, 3, 15, 0);
|
||||||
|
|
||||||
|
const userEffect = {
|
||||||
|
Unused: 0,
|
||||||
|
WhenExpires: null,
|
||||||
|
UseEffect: jest.fn(),
|
||||||
|
Save: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.setSystemTime(nowMock);
|
||||||
|
|
||||||
|
UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(userEffect);
|
||||||
|
|
||||||
|
result = await EffectHelper.UseEffect("userId", "name", whenExpiresNew);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT false returned", () => {
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("HasEffect", () => {
|
||||||
|
describe("GIVEN effect is in database", () => {
|
||||||
|
describe("GIVEN effect.WhenExpires is defined", () => {
|
||||||
|
describe("GIVEN now is before effect.WhenExpires", () => {
|
||||||
|
let result: boolean | undefined;
|
||||||
|
|
||||||
|
const nowMock = new Date(2024, 11, 3, 13, 30);
|
||||||
|
const whenExpires = new Date(2024, 11, 3, 15, 0);
|
||||||
|
|
||||||
|
const userEffect = {
|
||||||
|
WhenExpires: whenExpires,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.setSystemTime(nowMock);
|
||||||
|
|
||||||
|
UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(userEffect);
|
||||||
|
|
||||||
|
result = await EffectHelper.HasEffect("userId", "name");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT true returned", () => {
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GIVEN now is after effect.WhenExpires", () => {
|
||||||
|
let result: boolean | undefined;
|
||||||
|
|
||||||
|
const nowMock = new Date(2024, 11, 3, 16, 30);
|
||||||
|
const whenExpires = new Date(2024, 11, 3, 15, 0);
|
||||||
|
|
||||||
|
const userEffect = {
|
||||||
|
WhenExpires: whenExpires,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.setSystemTime(nowMock);
|
||||||
|
|
||||||
|
UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(userEffect);
|
||||||
|
|
||||||
|
result = await EffectHelper.HasEffect("userId", "name");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT false returned", () => {
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GIVEN effect.WhenExpires is undefined", () => {
|
||||||
|
let result: boolean | undefined;
|
||||||
|
|
||||||
|
const userEffect = {
|
||||||
|
WhenExpires: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(userEffect);
|
||||||
|
|
||||||
|
result = await EffectHelper.HasEffect("userId", "name");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT false returned", () => {
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GIVEN effect is not in database", () => {
|
||||||
|
let result: boolean | undefined;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
UserEffect.FetchOneByUserIdAndName = jest.fn().mockResolvedValue(null);
|
||||||
|
|
||||||
|
result = await EffectHelper.HasEffect("userId", "name");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT false returned", () => {
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GenerateEffectEmbed", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
UserEffect.FetchAllByUserIdPaginated = jest.fn()
|
||||||
|
.mockResolvedValue([
|
||||||
|
[],
|
||||||
|
0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
await EffectHelper.GenerateEffectEmbed("userId", 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT UserEffect.FetchAllByUserIdPaginated to be called", () => {
|
||||||
|
expect(UserEffect.FetchAllByUserIdPaginated).toHaveBeenCalledTimes(1);
|
||||||
|
expect(UserEffect.FetchAllByUserIdPaginated).toHaveBeenCalledWith("userId", 0, 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GIVEN there are no effects returned", () => {
|
||||||
|
let result: {
|
||||||
|
embed: EmbedBuilder,
|
||||||
|
row: ActionRowBuilder<ButtonBuilder>,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
UserEffect.FetchAllByUserIdPaginated = jest.fn()
|
||||||
|
.mockResolvedValue([
|
||||||
|
[],
|
||||||
|
0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
result = await EffectHelper.GenerateEffectEmbed("userId", 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT result returned", () => {
|
||||||
|
expect(result).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GIVEN there are effects returned", () => {
|
||||||
|
let result: {
|
||||||
|
embed: EmbedBuilder,
|
||||||
|
row: ActionRowBuilder<ButtonBuilder>,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
UserEffect.FetchAllByUserIdPaginated = jest.fn()
|
||||||
|
.mockResolvedValue([
|
||||||
|
[
|
||||||
|
{
|
||||||
|
Name: "name",
|
||||||
|
Unused: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
result = await EffectHelper.GenerateEffectEmbed("userId", 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT result returned", () => {
|
||||||
|
expect(result).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("AND it is the first page", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
result = await EffectHelper.GenerateEffectEmbed("userId", 1)
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT Previous button to be disabled", () => {
|
||||||
|
const button = result.row.components[0].data as unknown as {
|
||||||
|
label: string,
|
||||||
|
disabled: boolean
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(button).toBeDefined();
|
||||||
|
expect(button.label).toBe("Previous");
|
||||||
|
expect(button.disabled).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("AND it is the last page", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
result = await EffectHelper.GenerateEffectEmbed("userId", 1)
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EXPECT Next button to be disabled", () => {
|
||||||
|
const button = result.row.components[1].data as unknown as {
|
||||||
|
label: string,
|
||||||
|
disabled: boolean
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(button).toBeDefined();
|
||||||
|
expect(button.label).toBe("Next");
|
||||||
|
expect(button.disabled).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
71
tests/helpers/__snapshots__/EffectHelper.test.ts.snap
Normal file
71
tests/helpers/__snapshots__/EffectHelper.test.ts.snap
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`GenerateEffectEmbed GIVEN there are effects returned EXPECT result returned 1`] = `
|
||||||
|
{
|
||||||
|
"embed": {
|
||||||
|
"color": 3166394,
|
||||||
|
"description": "name 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[`GenerateEffectEmbed GIVEN there are no effects returned EXPECT result 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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
|
@ -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…
Reference in a new issue