Compare commits
No commits in common. "main" and "v0.5.2" have entirely different histories.
110 changed files with 12167 additions and 11739 deletions
24
.env.example
24
.env.example
|
@ -7,33 +7,29 @@
|
||||||
# any secret values.
|
# any secret values.
|
||||||
|
|
||||||
BOT_TOKEN=
|
BOT_TOKEN=
|
||||||
BOT_VER=0.8.4
|
BOT_VER=0.5.2
|
||||||
BOT_AUTHOR=Vylpes
|
BOT_AUTHOR=Vylpes
|
||||||
BOT_OWNERID=147392775707426816
|
BOT_OWNERID=147392775707426816
|
||||||
BOT_CLIENTID=682942374040961060
|
BOT_CLIENTID=682942374040961060
|
||||||
BOT_ENV=4
|
BOT_ENV=4
|
||||||
BOT_ADMINS=147392775707426816,887272961504071690
|
BOT_ADMINS=147392775707426816,887272961504071690
|
||||||
BOT_LOGLEVEL=info
|
|
||||||
BOT_LOG_DISCORD_ENABLE=false
|
|
||||||
BOT_LOG_DISCORD_LEVEL=warn
|
|
||||||
BOT_LOG_DISCORD_WEBHOOK=
|
|
||||||
BOT_LOG_DISCORD_SERVICE=
|
|
||||||
|
|
||||||
ABOUT_FUNDING=
|
ABOUT_FUNDING=
|
||||||
ABOUT_REPO=
|
ABOUT_REPO=
|
||||||
|
|
||||||
DATA_DIR=./.temp
|
DATA_DIR=
|
||||||
|
|
||||||
DB_HOST=127.0.0.1
|
DB_HOST=
|
||||||
DB_PORT=3301
|
DB_PORT=
|
||||||
DB_NAME=carddrop
|
DB_NAME=
|
||||||
DB_AUTH_USER=
|
DB_AUTH_USER=
|
||||||
DB_AUTH_PASS=
|
DB_AUTH_PASS=
|
||||||
DB_SYNC=
|
DB_SYNC=
|
||||||
DB_LOGGING=
|
DB_LOGGING=
|
||||||
DB_DATA_LOCATION=./.temp/database
|
DB_DATA_LOCATION=~/.docker
|
||||||
DB_ROOT_HOST=0.0.0.0
|
|
||||||
|
|
||||||
EXPRESS_PORT=3302
|
DB_CARD_FILE=:memory:
|
||||||
|
|
||||||
GDRIVESYNC_AUTO=false
|
EXPRESS_PORT=3303
|
||||||
|
|
||||||
|
GDRIVESYNC_AUTO=true
|
45
.eslintrc.json
Normal file
45
.eslintrc.json
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"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/**/*"
|
||||||
|
]
|
||||||
|
}
|
|
@ -12,25 +12,24 @@ jobs:
|
||||||
runs-on: node
|
runs-on: node
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- name: Use Node.js
|
- name: Use Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 18.x
|
||||||
- run: yarn install --frozen-lockfile
|
- run: npm ci
|
||||||
- run: yarn build
|
- run: npm run build
|
||||||
- run: yarn test
|
- run: npm test
|
||||||
- run: yarn lint
|
|
||||||
|
|
||||||
- name: "Copy files over to location"
|
- name: "Copy files over to location"
|
||||||
run: rsync -rvzP . ${{ secrets.PROD_REPO_PATH }}
|
run: cp -r . ${{ secrets.PROD_REPO_PATH }}
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
environment: prod
|
environment: prod
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: node
|
runs-on: node
|
||||||
steps:
|
steps:
|
||||||
- uses: https://github.com/appleboy/ssh-action@v1.1.0
|
- uses: https://github.com/appleboy/ssh-action@v1.0.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 }}
|
||||||
|
@ -54,17 +53,12 @@ jobs:
|
||||||
DATA_DIR: ${{ secrets.PROD_DATA_DIR }}
|
DATA_DIR: ${{ secrets.PROD_DATA_DIR }}
|
||||||
GDRIVESYNC_AUTO: ${{ vars.PROD_GDRIVESYNC_AUTO }}
|
GDRIVESYNC_AUTO: ${{ vars.PROD_GDRIVESYNC_AUTO }}
|
||||||
EXPRESS_PORT: ${{ secrets.PROD_EXPRESS_PORT }}
|
EXPRESS_PORT: ${{ secrets.PROD_EXPRESS_PORT }}
|
||||||
BOT_LOGLEVEL: ${{ vars.PROD_BOT_LOGLEVEL }}
|
|
||||||
BOT_LOG_DISCORD_ENABLE: ${{ vars.PROD_BOT_LOG_DISCORD_ENABLE }}
|
|
||||||
BOT_LOG_DISCORD_LEVEL: ${{ vars.PROD_BOT_LOG_DISCORD_LEVEL }}
|
|
||||||
BOT_LOG_DISCORD_WEBHOOK: ${{ secrets.PROD_BOT_LOG_DISCORD_WEBHOOK }}
|
|
||||||
BOT_LOG_DISCORD_SERVICE: ${{ vars.PROD_BOT_LOG_DISCORD_SERVICE }}
|
|
||||||
with:
|
with:
|
||||||
host: ${{ secrets.PROD_SSH_HOST }}
|
host: ${{ secrets.PROD_SSH_HOST }}
|
||||||
username: ${{ secrets.PROD_SSH_USER }}
|
username: ${{ secrets.PROD_SSH_USER }}
|
||||||
key: ${{ secrets.PROD_SSH_KEY }}
|
key: ${{ secrets.PROD_SSH_KEY }}
|
||||||
port: ${{ secrets.PROD_SSH_PORT }}
|
port: ${{ secrets.PROD_SSH_PORT }}
|
||||||
envs: DB_NAME,DB_AUTH_USER,DB_AUTH_PASS,DB_HOST,DB_PORT,DB_ROOT_HOST,DB_SYNC,DB_LOGGING,DB_DATA_LOCATION,BOT_TOKEN,BOT_VER,BOT_AUTHOR,BOT_OWNERID,BOT_CLIENTID,ABOUT_FUNDING,ABOUT_REPO,BOT_ENV,BOT_ADMINS,DATA_DIR,GDRIVESYNC_AUTO,SERVER_PATH,EXPRESS_PORT,BOT_LOGLEVEL,BOT_LOG_DISCORD_ENABLE,BOT_LOG_DISCORD_LEVEL,BOT_LOG_DISCORD_WEBHOOK,BOT_LOG_DISCORD_SERVICE
|
envs: DB_NAME,DB_AUTH_USER,DB_AUTH_PASS,DB_HOST,DB_PORT,DB_ROOT_HOST,DB_SYNC,DB_LOGGING,DB_DATA_LOCATION,BOT_TOKEN,BOT_VER,BOT_AUTHOR,BOT_OWNERID,BOT_CLIENTID,ABOUT_FUNDING,ABOUT_REPO,BOT_ENV,BOT_ADMINS,DATA_DIR,GDRIVESYNC_AUTO,SERVER_PATH,EXPRESS_PORT
|
||||||
script: |
|
script: |
|
||||||
source .sshrc \
|
source .sshrc \
|
||||||
&& cd /home/vylpes/apps/card-drop/card-drop_prod \
|
&& cd /home/vylpes/apps/card-drop/card-drop_prod \
|
||||||
|
|
|
@ -12,25 +12,24 @@ jobs:
|
||||||
runs-on: node
|
runs-on: node
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- name: Use Node.js
|
- name: Use Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 18.x
|
||||||
- run: yarn install --frozen-lockfile
|
- run: npm ci
|
||||||
- run: yarn build
|
- run: npm run build
|
||||||
- run: yarn test
|
- run: npm test
|
||||||
- run: yarn lint
|
|
||||||
|
|
||||||
- name: "Copy files over to location"
|
- name: "Copy files over to location"
|
||||||
run: rsync -rvzP . ${{ secrets.STAGE_REPO_PATH }}
|
run: cp -r . ${{ secrets.STAGE_REPO_PATH }}
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
environment: prod
|
environment: prod
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: node
|
runs-on: node
|
||||||
steps:
|
steps:
|
||||||
- uses: https://github.com/appleboy/ssh-action@v1.1.0
|
- uses: https://github.com/appleboy/ssh-action@v1.0.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 }}
|
||||||
|
@ -54,17 +53,12 @@ jobs:
|
||||||
DATA_DIR: ${{ secrets.STAGE_DATA_DIR }}
|
DATA_DIR: ${{ secrets.STAGE_DATA_DIR }}
|
||||||
GDRIVESYNC_AUTO: ${{ vars.STAGE_GDRIVESYNC_AUTO }}
|
GDRIVESYNC_AUTO: ${{ vars.STAGE_GDRIVESYNC_AUTO }}
|
||||||
EXPRESS_PORT: ${{ secrets.STAGE_EXPRESS_PORT }}
|
EXPRESS_PORT: ${{ secrets.STAGE_EXPRESS_PORT }}
|
||||||
BOT_LOGLEVEL: ${{ vars.STAGE_BOT_LOGLEVEL }}
|
|
||||||
BOT_LOG_DISCORD_ENABLE: ${{ vars.STAGE_BOT_LOG_DISCORD_ENABLE }}
|
|
||||||
BOT_LOG_DISCORD_LEVEL: ${{ vars.STAGE_BOT_LOG_DISCORD_LEVEL }}
|
|
||||||
BOT_LOG_DISCORD_WEBHOOK: ${{ secrets.STAGE_BOT_LOG_DISCORD_WEBHOOK }}
|
|
||||||
BOT_LOG_DISCORD_SERVICE: ${{ vars.STAGE_BOT_LOG_DISCORD_SERVICE }}
|
|
||||||
with:
|
with:
|
||||||
host: ${{ secrets.STAGE_SSH_HOST }}
|
host: ${{ secrets.STAGE_SSH_HOST }}
|
||||||
username: ${{ secrets.STAGE_SSH_USER }}
|
username: ${{ secrets.STAGE_SSH_USER }}
|
||||||
key: ${{ secrets.STAGE_SSH_KEY }}
|
key: ${{ secrets.STAGE_SSH_KEY }}
|
||||||
port: ${{ secrets.STAGE_SSH_PORT }}
|
port: ${{ secrets.STAGE_SSH_PORT }}
|
||||||
envs: DB_NAME,DB_AUTH_USER,DB_AUTH_PASS,DB_HOST,DB_PORT,DB_ROOT_HOST,DB_SYNC,DB_LOGGING,DB_DATA_LOCATION,BOT_TOKEN,BOT_VER,BOT_AUTHOR,BOT_OWNERID,BOT_CLIENTID,ABOUT_FUNDING,ABOUT_REPO,BOT_ENV,BOT_ADMINS,DATA_DIR,GDRIVESYNC_AUTO,SERVER_PATH,EXPRESS_PORT,BOT_LOGLEVEL,BOT_LOG_DISCORD_ENABLE,BOT_LOG_DISCORD_LEVEL,BOT_LOG_DISCORD_WEBHOOK,BOT_LOG_DISCORD_SERVICE
|
envs: DB_NAME,DB_AUTH_USER,DB_AUTH_PASS,DB_HOST,DB_PORT,DB_ROOT_HOST,DB_SYNC,DB_LOGGING,DB_DATA_LOCATION,BOT_TOKEN,BOT_VER,BOT_AUTHOR,BOT_OWNERID,BOT_CLIENTID,ABOUT_FUNDING,ABOUT_REPO,BOT_ENV,BOT_ADMINS,DATA_DIR,GDRIVESYNC_AUTO,SERVER_PATH,EXPRESS_PORT
|
||||||
script: |
|
script: |
|
||||||
source .sshrc \
|
source .sshrc \
|
||||||
&& cd /home/vylpes/apps/card-drop/card-drop_stage \
|
&& cd /home/vylpes/apps/card-drop/card-drop_stage \
|
||||||
|
|
|
@ -14,12 +14,11 @@ jobs:
|
||||||
runs-on: node
|
runs-on: node
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- name: Use Node.js
|
- name: Use Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 18.x
|
||||||
- run: yarn install --frozen-lockfile
|
- run: npm ci
|
||||||
- run: yarn build
|
- run: npm run build
|
||||||
- run: yarn test
|
- run: npm test
|
||||||
- run: yarn lint
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -108,5 +108,5 @@ config.json
|
||||||
ormconfig.json
|
ormconfig.json
|
||||||
gdrive-credentials.json
|
gdrive-credentials.json
|
||||||
data/
|
data/
|
||||||
*.db
|
|
||||||
.temp/
|
.temp/
|
||||||
|
*.db
|
60
README.md
60
README.md
|
@ -1,60 +1,2 @@
|
||||||
# Card Drop
|
# card-drop
|
||||||
|
|
||||||
Card Drop is a Discord Bot designed to allow users to "drop" random cards into
|
|
||||||
a channel and have the ability to claim them for themselves or let others if
|
|
||||||
they so choose.
|
|
||||||
|
|
||||||
The cards are randomly chosen based on weights of their card type (i.e. Bronze
|
|
||||||
is more common than Gold). The user who ran the drop command has 5 minutes to
|
|
||||||
choose if they want the card to themselves before its claimable by anyone, or
|
|
||||||
until the drop command is ran again.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Downloads of the latest version can be found from the [GitHub Releases](https://github.com/vylpes/card-drop/releases)
|
|
||||||
or [Forgejo Releases](https://git.vylpes.xyz/external/card-drop/releases) page.
|
|
||||||
|
|
||||||
Copy the config template file and fill in the strings.
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
- NodeJS
|
|
||||||
- Yarn
|
|
||||||
- Docker
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Install the dependencies and build the app:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yarn Install
|
|
||||||
yarn build
|
|
||||||
```
|
|
||||||
|
|
||||||
Setup the database (Recommended to use the docker-compose file
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Copy and edit the settings file
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp .env.template .env
|
|
||||||
```
|
|
||||||
|
|
||||||
> **NOTE:** Make sure you do *not* check in these files! These contain
|
|
||||||
sensitive information and should be treated as private.
|
|
||||||
|
|
||||||
If you're not using `DB_SYNC=true` in `.env`, make sure to migrate the database
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yarn db:up
|
|
||||||
```
|
|
||||||
|
|
||||||
Start the bot
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yarn start
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
ALTER TABLE `user`
|
|
||||||
ADD LastUsedDaily datetime null;
|
|
|
@ -1 +0,0 @@
|
||||||
DROP TABLE `user_effect`;
|
|
|
@ -1,10 +0,0 @@
|
||||||
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;
|
|
120
docs/cards.md
120
docs/cards.md
|
@ -1,120 +0,0 @@
|
||||||
# Cards
|
|
||||||
|
|
||||||
This document will describe how to add cards to the bot. This is from the
|
|
||||||
perspective of the development side and doesn't go into details of syncing
|
|
||||||
from an external place such as with the Google Drive Sync function.
|
|
||||||
|
|
||||||
The cards will be put into the `$DATA_DIR/cards` folder. `$DATA_DIR` is
|
|
||||||
configured in the `.env` file.
|
|
||||||
|
|
||||||
## Folder Structure
|
|
||||||
|
|
||||||
The general structure of the cards folder is as follows:
|
|
||||||
|
|
||||||
```
|
|
||||||
cards # The main cards folder
|
|
||||||
| Series 1 # Series folder
|
|
||||||
| | BRONZE # Type folder
|
|
||||||
| | | 1000.jpg # Card image
|
|
||||||
| | | 1001.jpg
|
|
||||||
| | 1.json # Card metadata file
|
|
||||||
| Series 2
|
|
||||||
| | SILVER
|
|
||||||
| | | 2000.jpg
|
|
||||||
| | 2.json
|
|
||||||
```
|
|
||||||
|
|
||||||
- The root of the cards folder will have a folder foor each series
|
|
||||||
- Each series will contain folders for each of the card types containing the
|
|
||||||
card images.
|
|
||||||
- The series folder will also contain a metadata JSON folder containing the
|
|
||||||
metadata of the cards within that series.
|
|
||||||
|
|
||||||
The bot when loading will search the cards folder recursively for each json,
|
|
||||||
and then read them to determine what cards should be used for the bot.
|
|
||||||
|
|
||||||
## Series Metadata
|
|
||||||
|
|
||||||
An example of what the metadata files could look like are as follows:
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"name": "Series 1",
|
|
||||||
"cards": [
|
|
||||||
{
|
|
||||||
"id": "1000",
|
|
||||||
"name": "Card 1000 of Series 1",
|
|
||||||
"type": 1,
|
|
||||||
"path": "Series 1/BRONZE/1000.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "1001",
|
|
||||||
"name": "Card 1001 of Series 1",
|
|
||||||
"type": 1,
|
|
||||||
"path": "Series 2/BRONZE?1001.jpg",
|
|
||||||
"subseries": "Custom Series Name"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
This file will load a series called "Series 1" with the id of 1, containing 2
|
|
||||||
cards:
|
|
||||||
- Card 1000, with type 1 (Bronze), with its image located at (from root)
|
|
||||||
"Series 1/BRONZE/1000.jpg"
|
|
||||||
- Card 1001 is the same, except has a custom "subseries" name which will
|
|
||||||
override the main series name if shown, helpful for an "other" category.
|
|
||||||
|
|
||||||
### Card Type
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Number</th>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Chance</th>
|
|
||||||
<th>Sacrifice Cost (Coins)</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>0</td>
|
|
||||||
<td>Unknown</td>
|
|
||||||
<td>-</td>
|
|
||||||
<td>-</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>1</td>
|
|
||||||
<td>Bronze</td>
|
|
||||||
<td>62%</td>
|
|
||||||
<td>5</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>2</td>
|
|
||||||
<td>Silver</td>
|
|
||||||
<td>31%</td>
|
|
||||||
<td>10</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>3</td>
|
|
||||||
<td>Gold</td>
|
|
||||||
<td>4.4%</td>
|
|
||||||
<td>30</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>4</td>
|
|
||||||
<td>Manga</td>
|
|
||||||
<td>2%</td>
|
|
||||||
<td>40</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>5</td>
|
|
||||||
<td>Legendary</td>
|
|
||||||
<td>0.6%</td>
|
|
||||||
<td>100</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
|
@ -1,35 +0,0 @@
|
||||||
# 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.
|
|
|
@ -1,55 +0,0 @@
|
||||||
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,4 +1,3 @@
|
||||||
jest.setTimeout(1 * 1000); // 1 second
|
jest.setTimeout(1 * 1000); // 1 second
|
||||||
jest.resetModules();
|
jest.resetModules();
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
jest.useFakeTimers();
|
|
11694
package-lock.json
generated
Normal file
11694
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
46
package.json
46
package.json
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
"name": "card-drop",
|
"name": "card-drop",
|
||||||
"version": "0.9.2",
|
"version": "0.5.2",
|
||||||
"main": "./dist/bot.js",
|
"main": "./dist/bot.js",
|
||||||
"typings": "./dist",
|
"typings": "./dist",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"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": "jest",
|
"test": "jest --passWithNoTests",
|
||||||
"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",
|
||||||
|
@ -15,11 +15,11 @@
|
||||||
"db:create": "typeorm migration:create ./src/database/migrations/app/new",
|
"db:create": "typeorm migration:create ./src/database/migrations/app/new",
|
||||||
"release": "np --no-publish"
|
"release": "np --no-publish"
|
||||||
},
|
},
|
||||||
"repository": "https://git.vylpes.xyz/External/card-drop.git",
|
"repository": "https://gitea.vylpes.xyz/External/card-drop.git",
|
||||||
"author": "Ethan Lane <ethan@vylpes.com>",
|
"author": "Ethan Lane <ethan@vylpes.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https//git.vylpes.xyz/External/card-drop/issues",
|
"url": "https//gitea.vylpes.xyz/External/card-drop/issues",
|
||||||
"email": "helpdesk@vylpes.com"
|
"email": "helpdesk@vylpes.com"
|
||||||
},
|
},
|
||||||
"homepage": "https://gitea.vylpes.xyz/External/card-drop",
|
"homepage": "https://gitea.vylpes.xyz/External/card-drop",
|
||||||
|
@ -27,38 +27,32 @@
|
||||||
"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": "^5.0.0",
|
"@types/express": "^4.17.20",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.0.0",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^9.0.0",
|
||||||
"axios": "^1.8.4",
|
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"canvas": "^2.11.2",
|
|
||||||
"clone-deep": "^4.0.1",
|
"clone-deep": "^4.0.1",
|
||||||
"cron": "^3.1.7",
|
"discord.js": "^14.3.0",
|
||||||
"discord.js": "^14.16.3",
|
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"fuse.js": "^7.0.0",
|
"glob": "^10.3.10",
|
||||||
"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": "^1.6.0",
|
"minimatch": "9.0.4",
|
||||||
"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.15.0",
|
"winston": "^3.11.0"
|
||||||
"winston-daily-rotate-file": "^5.0.0",
|
},
|
||||||
"winston-discord-transport": "^1.3.0"
|
"overrides": {
|
||||||
|
"undici": "^5.28.3"
|
||||||
},
|
},
|
||||||
"resolutions": {},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.8.1",
|
"@types/node": "^20.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.11.0",
|
"@typescript-eslint/eslint-plugin": "^6.16.0",
|
||||||
"@typescript-eslint/parser": "^8.11.0",
|
"@typescript-eslint/parser": "^6.16.0",
|
||||||
"eslint": "^9.13.0",
|
"eslint": "^8.56.0",
|
||||||
"np": "^10.0.7",
|
"np": "^9.0.0",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0"
|
||||||
"typescript-eslint": "^8.11.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { glob } from "glob";
|
||||||
import { SeriesMetadata } from "../contracts/SeriesMetadata";
|
import { SeriesMetadata } from "../contracts/SeriesMetadata";
|
||||||
import { CoreClient } from "../client/client";
|
import { CoreClient } from "../client/client";
|
||||||
import AppLogger from "../client/appLogger";
|
import AppLogger from "../client/appLogger";
|
||||||
import {CardRarity} from "../constants/CardRarity";
|
|
||||||
|
|
||||||
export interface CardMetadataResult {
|
export interface CardMetadataResult {
|
||||||
IsSuccess: boolean;
|
IsSuccess: boolean;
|
||||||
|
@ -38,29 +37,7 @@ export default class CardMetadataFunction {
|
||||||
|
|
||||||
if (cardResult.IsSuccess) {
|
if (cardResult.IsSuccess) {
|
||||||
CoreClient.Cards = cardResult.Result!;
|
CoreClient.Cards = cardResult.Result!;
|
||||||
|
AppLogger.LogInfo("Functions/CardMetadataFunction", `Loaded ${CoreClient.Cards.flatMap(x => x.cards).length} cards to database`);
|
||||||
const allCards = CoreClient.Cards.flatMap(x => x.cards);
|
|
||||||
|
|
||||||
const totalCards = allCards.length;
|
|
||||||
const bronzeCards = allCards.filter(x => x.type == CardRarity.Bronze)
|
|
||||||
.length;
|
|
||||||
const silverCards = allCards.filter(x => x.type == CardRarity.Silver)
|
|
||||||
.length;
|
|
||||||
const goldCards = allCards.filter(x => x.type == CardRarity.Gold)
|
|
||||||
.length;
|
|
||||||
const mangaCards = allCards.filter(x => x.type == CardRarity.Manga)
|
|
||||||
.length;
|
|
||||||
const legendaryCards = allCards.filter(x => x.type == CardRarity.Legendary)
|
|
||||||
.length;
|
|
||||||
|
|
||||||
AppLogger.LogInfo("Functions/CardMetadataFunction", `Loaded ${totalCards} cards to database (${bronzeCards} bronze, ${silverCards} silver, ${goldCards} gold, ${mangaCards} manga, ${legendaryCards} legendary)`);
|
|
||||||
|
|
||||||
const duplicateCards = CoreClient.Cards.flatMap(x => x.cards)
|
|
||||||
.filter((card, index, self) => self.findIndex(c => c.id === card.id) !== index);
|
|
||||||
|
|
||||||
if (duplicateCards.length > 0) {
|
|
||||||
AppLogger.LogWarn("Functions/CardMetadataFunction", `Duplicate card ids found: ${duplicateCards.flatMap(x => x.id).join(", ")}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
IsSuccess: true,
|
IsSuccess: true,
|
||||||
|
|
|
@ -37,8 +37,8 @@ const client = new CoreClient([
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Registry.RegisterCommands();
|
Registry.RegisterCommands();
|
||||||
|
Registry.RegisterEvents();
|
||||||
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...");
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
import { ButtonInteraction } from "discord.js";
|
import { ButtonInteraction } from "discord.js";
|
||||||
import { ButtonEvent } from "../type/buttonEvent";
|
import { ButtonEvent } from "../type/buttonEvent";
|
||||||
import Inventory from "../database/entities/app/Inventory";
|
import Inventory from "../database/entities/app/Inventory";
|
||||||
|
import { CoreClient } from "../client/client";
|
||||||
import { default as eClaim } from "../database/entities/app/Claim";
|
import { default as eClaim } from "../database/entities/app/Claim";
|
||||||
import AppLogger from "../client/appLogger";
|
import AppLogger from "../client/appLogger";
|
||||||
import User from "../database/entities/app/User";
|
import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
||||||
import CardConstants from "../constants/CardConstants";
|
|
||||||
import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper";
|
|
||||||
import DropEmbedHelper from "../helpers/DropHelpers/DropEmbedHelper";
|
|
||||||
|
|
||||||
export default class Claim extends ButtonEvent {
|
export default class Claim extends ButtonEvent {
|
||||||
public override async execute(interaction: ButtonInteraction) {
|
public override async execute(interaction: ButtonInteraction) {
|
||||||
if (!interaction.guild || !interaction.guildId) return;
|
if (!interaction.guild || !interaction.guildId) return;
|
||||||
if (!interaction.channel) return;
|
|
||||||
if (!interaction.channel.isSendable()) return;
|
|
||||||
|
|
||||||
await interaction.deferUpdate();
|
await interaction.deferUpdate();
|
||||||
|
|
||||||
|
@ -21,24 +17,17 @@ export default class Claim extends ButtonEvent {
|
||||||
const droppedBy = interaction.customId.split(" ")[3];
|
const droppedBy = interaction.customId.split(" ")[3];
|
||||||
const userId = interaction.user.id;
|
const userId = interaction.user.id;
|
||||||
|
|
||||||
const whenDropped = interaction.message.createdAt;
|
|
||||||
const lastClaimableDate = new Date(Date.now() - (1000 * 60 * 2)); // 2 minutes ago
|
|
||||||
|
|
||||||
if (whenDropped < lastClaimableDate) {
|
|
||||||
await interaction.channel.send(`${interaction.user}, Cards can only be claimed within 2 minutes of it being dropped!`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppLogger.LogSilly("Button/Claim", `Parameters: cardNumber=${cardNumber}, claimId=${claimId}, droppedBy=${droppedBy}, userId=${userId}`);
|
AppLogger.LogSilly("Button/Claim", `Parameters: cardNumber=${cardNumber}, claimId=${claimId}, droppedBy=${droppedBy}, userId=${userId}`);
|
||||||
|
|
||||||
const user = await User.FetchOneById(User, userId) || new User(userId, CardConstants.StartingCurrency);
|
|
||||||
|
|
||||||
AppLogger.LogSilly("Button/Claim", `${user.Id} has ${user.Currency} currency`);
|
|
||||||
|
|
||||||
const claimed = await eClaim.FetchOneByClaimId(claimId);
|
const claimed = await eClaim.FetchOneByClaimId(claimId);
|
||||||
|
|
||||||
if (claimed) {
|
if (claimed) {
|
||||||
await interaction.channel.send(`${interaction.user}, This card has already been claimed!`);
|
await interaction.reply("This card has already been claimed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (claimId == CoreClient.ClaimId && userId != droppedBy) {
|
||||||
|
await interaction.reply("The latest dropped card can only be claimed by the user who dropped it");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +36,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.AddQuantity(1);
|
inventory.SetQuantity(inventory.Quantity + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
await inventory.Save(Inventory, inventory);
|
await inventory.Save(Inventory, inventory);
|
||||||
|
@ -57,18 +46,16 @@ export default class Claim extends ButtonEvent {
|
||||||
|
|
||||||
await claim.Save(eClaim, claim);
|
await claim.Save(eClaim, claim);
|
||||||
|
|
||||||
const card = GetCardsHelper.GetCardByCardNumber(cardNumber);
|
const card = CardDropHelperMetadata.GetCardByCardNumber(cardNumber);
|
||||||
|
|
||||||
if (!card) {
|
if (!card) {
|
||||||
AppLogger.LogError("Button/Claim", `Unable to find card, ${cardNumber}`);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageFileName = card.card.path.split("/").pop()!;
|
const imageFileName = card.card.path.split("/").pop()!;
|
||||||
|
|
||||||
const embed = DropEmbedHelper.GenerateDropEmbed(card, inventory.Quantity, imageFileName, interaction.user.username, user.Currency);
|
const embed = CardDropHelperMetadata.GenerateDropEmbed(card, inventory.Quantity, imageFileName, interaction.user.username);
|
||||||
const row = DropEmbedHelper.GenerateDropButtons(card, claimId, interaction.user.id, true);
|
const row = CardDropHelperMetadata.GenerateDropButtons(card, claimId, interaction.user.id, true);
|
||||||
|
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
embeds: [ embed ],
|
embeds: [ embed ],
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
import { ButtonInteraction } from "discord.js";
|
|
||||||
import { ButtonEvent } from "../type/buttonEvent";
|
|
||||||
import List from "./Effects/List";
|
|
||||||
import Use from "./Effects/Use";
|
|
||||||
import AppLogger from "../client/appLogger";
|
|
||||||
import Buy from "./Effects/Buy";
|
|
||||||
|
|
||||||
export default class Effects extends ButtonEvent {
|
|
||||||
public override async execute(interaction: ButtonInteraction) {
|
|
||||||
const action = interaction.customId.split(" ")[1];
|
|
||||||
|
|
||||||
switch (action) {
|
|
||||||
case "list":
|
|
||||||
await List(interaction);
|
|
||||||
break;
|
|
||||||
case "use":
|
|
||||||
await Use.Execute(interaction);
|
|
||||||
break;
|
|
||||||
case "buy":
|
|
||||||
await Buy.Execute(interaction);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
AppLogger.LogError("Buttons/Effects", `Unknown action, ${action}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,120 +0,0 @@
|
||||||
import {ButtonInteraction} from "discord.js";
|
|
||||||
import AppLogger from "../../client/appLogger";
|
|
||||||
import EffectHelper from "../../helpers/EffectHelper";
|
|
||||||
import EmbedColours from "../../constants/EmbedColours";
|
|
||||||
import User from "../../database/entities/app/User";
|
|
||||||
import {EffectDetails} from "../../constants/EffectDetails";
|
|
||||||
|
|
||||||
export default class Buy {
|
|
||||||
public static async Execute(interaction: ButtonInteraction) {
|
|
||||||
const subaction = interaction.customId.split(" ")[2];
|
|
||||||
|
|
||||||
switch (subaction) {
|
|
||||||
case "confirm":
|
|
||||||
await this.Confirm(interaction);
|
|
||||||
break;
|
|
||||||
case "cancel":
|
|
||||||
await this.Cancel(interaction);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
AppLogger.LogError("Buy", `Unknown subaction, effects ${subaction}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Confirm(interaction: ButtonInteraction) {
|
|
||||||
const id = interaction.customId.split(" ")[3];
|
|
||||||
const quantity = interaction.customId.split(" ")[4];
|
|
||||||
|
|
||||||
if (!id || !quantity) {
|
|
||||||
AppLogger.LogError("Buy Confirm", "Not enough parameters");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const effectDetail = EffectDetails.get(id);
|
|
||||||
|
|
||||||
if (!effectDetail) {
|
|
||||||
AppLogger.LogError("Buy Confirm", "Effect detail not found!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const quantityNumber = Number(quantity);
|
|
||||||
|
|
||||||
if (!quantityNumber || quantityNumber < 1) {
|
|
||||||
AppLogger.LogError("Buy Confirm", "Invalid number");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalCost = effectDetail.cost * quantityNumber;
|
|
||||||
|
|
||||||
const user = await User.FetchOneById(User, interaction.user.id);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
AppLogger.LogError("Buy Confirm", "Unable to find user");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.Currency < totalCost) {
|
|
||||||
interaction.reply(`You don't have enough currency to buy this! You have \`${user.Currency} Currency\` and need \`${totalCost} Currency\`!`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
user.RemoveCurrency(totalCost);
|
|
||||||
await user.Save(User, user);
|
|
||||||
|
|
||||||
await EffectHelper.AddEffectToUserInventory(interaction.user.id, id, quantityNumber);
|
|
||||||
|
|
||||||
const generatedEmbed = await EffectHelper.GenerateEffectBuyEmbed(interaction.user.id, id, quantityNumber, true);
|
|
||||||
|
|
||||||
if (typeof generatedEmbed == "string") {
|
|
||||||
await interaction.reply(generatedEmbed);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
generatedEmbed.embed.setColor(EmbedColours.Success);
|
|
||||||
generatedEmbed.embed.setFooter({ text: "Purchased" });
|
|
||||||
|
|
||||||
await interaction.update({
|
|
||||||
embeds: [ generatedEmbed.embed ],
|
|
||||||
components: [ generatedEmbed.row ],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Cancel(interaction: ButtonInteraction) {
|
|
||||||
const id = interaction.customId.split(" ")[3];
|
|
||||||
const quantity = interaction.customId.split(" ")[4];
|
|
||||||
|
|
||||||
if (!id || !quantity) {
|
|
||||||
AppLogger.LogError("Buy Cancel", "Not enough parameters");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const effectDetail = EffectDetails.get(id);
|
|
||||||
|
|
||||||
if (!effectDetail) {
|
|
||||||
AppLogger.LogError("Buy Cancel", "Effect detail not found!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const quantityNumber = Number(quantity);
|
|
||||||
|
|
||||||
if (!quantityNumber || quantityNumber < 1) {
|
|
||||||
AppLogger.LogError("Buy Cancel", "Invalid number");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const generatedEmbed = await EffectHelper.GenerateEffectBuyEmbed(interaction.user.id, id, quantityNumber, true);
|
|
||||||
|
|
||||||
if (typeof generatedEmbed == "string") {
|
|
||||||
await interaction.reply(generatedEmbed);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
generatedEmbed.embed.setColor(EmbedColours.Error);
|
|
||||||
generatedEmbed.embed.setFooter({ text: "Cancelled" });
|
|
||||||
|
|
||||||
await interaction.update({
|
|
||||||
embeds: [ generatedEmbed.embed ],
|
|
||||||
components: [ generatedEmbed.row ],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
import { ButtonInteraction } from "discord.js";
|
|
||||||
import EffectHelper from "../../helpers/EffectHelper";
|
|
||||||
|
|
||||||
export default async function List(interaction: ButtonInteraction) {
|
|
||||||
const pageOption = interaction.customId.split(" ")[2];
|
|
||||||
|
|
||||||
const page = Number(pageOption);
|
|
||||||
|
|
||||||
if (!page) {
|
|
||||||
await interaction.reply("Page option is not a valid number");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await EffectHelper.GenerateEffectListEmbed(interaction.user.id, page);
|
|
||||||
|
|
||||||
await interaction.update({
|
|
||||||
embeds: [ result.embed ],
|
|
||||||
components: [ result.row ],
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, EmbedBuilder } from "discord.js";
|
|
||||||
import { EffectDetails } from "../../constants/EffectDetails";
|
|
||||||
import EffectHelper from "../../helpers/EffectHelper";
|
|
||||||
import EmbedColours from "../../constants/EmbedColours";
|
|
||||||
import TimeLengthInput from "../../helpers/TimeLengthInput";
|
|
||||||
import AppLogger from "../../client/appLogger";
|
|
||||||
|
|
||||||
export default class Use {
|
|
||||||
public static async Execute(interaction: ButtonInteraction) {
|
|
||||||
const subaction = interaction.customId.split(" ")[2];
|
|
||||||
|
|
||||||
switch (subaction) {
|
|
||||||
case "confirm":
|
|
||||||
await this.UseConfirm(interaction);
|
|
||||||
break;
|
|
||||||
case "cancel":
|
|
||||||
await this.UseCancel(interaction);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async UseConfirm(interaction: ButtonInteraction) {
|
|
||||||
const id = interaction.customId.split(" ")[3];
|
|
||||||
|
|
||||||
const effectDetail = EffectDetails.get(id);
|
|
||||||
|
|
||||||
if (!effectDetail) {
|
|
||||||
AppLogger.LogError("Button/Effects/Use", `Effect not found, ${id}`);
|
|
||||||
|
|
||||||
await interaction.reply("Effect not found in system!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
|
|
||||||
const whenExpires = new Date(now.getTime() + effectDetail.duration);
|
|
||||||
|
|
||||||
const result = await EffectHelper.UseEffect(interaction.user.id, id, whenExpires);
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
await interaction.reply("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle("Effect Used")
|
|
||||||
.setDescription("You now have an active effect!")
|
|
||||||
.setColor(EmbedColours.Green)
|
|
||||||
.addFields([
|
|
||||||
{
|
|
||||||
name: "Effect",
|
|
||||||
value: effectDetail.friendlyName,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Expires",
|
|
||||||
value: `<t:${Math.round(whenExpires.getTime() / 1000)}:f>`,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
|
||||||
.addComponents([
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setLabel("Confirm")
|
|
||||||
.setCustomId(`effects use confirm ${effectDetail.id}`)
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setDisabled(true),
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setLabel("Cancel")
|
|
||||||
.setCustomId(`effects use cancel ${effectDetail.id}`)
|
|
||||||
.setStyle(ButtonStyle.Danger)
|
|
||||||
.setDisabled(true),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await interaction.update({
|
|
||||||
embeds: [ embed ],
|
|
||||||
components: [ row ],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async UseCancel(interaction: ButtonInteraction) {
|
|
||||||
const id = interaction.customId.split(" ")[3];
|
|
||||||
|
|
||||||
const effectDetail = EffectDetails.get(id);
|
|
||||||
|
|
||||||
if (!effectDetail) {
|
|
||||||
AppLogger.LogError("Button/Effects/Cancel", `Effect not found, ${id}`);
|
|
||||||
|
|
||||||
await interaction.reply("Effect not found in system!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeLengthInput = TimeLengthInput.ConvertFromMilliseconds(effectDetail.duration);
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle("Effect Use Cancelled")
|
|
||||||
.setDescription("The effect from your inventory has not been used")
|
|
||||||
.setColor(EmbedColours.Grey)
|
|
||||||
.addFields([
|
|
||||||
{
|
|
||||||
name: "Effect",
|
|
||||||
value: effectDetail.friendlyName,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Expires",
|
|
||||||
value: timeLengthInput.GetLengthShort(),
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
|
||||||
.addComponents([
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setLabel("Confirm")
|
|
||||||
.setCustomId(`effects use confirm ${effectDetail.id}`)
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setDisabled(true),
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setLabel("Cancel")
|
|
||||||
.setCustomId(`effects use cancel ${effectDetail.id}`)
|
|
||||||
.setStyle(ButtonStyle.Danger)
|
|
||||||
.setDisabled(true),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await interaction.update({
|
|
||||||
embeds: [ embed ],
|
|
||||||
components: [ row ],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,8 +12,6 @@ export default class Inventory extends ButtonEvent {
|
||||||
|
|
||||||
AppLogger.LogSilly("Button/Inventory", `Parameters: userid=${userid}, page=${page}`);
|
AppLogger.LogSilly("Button/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);
|
const member = interaction.guild.members.cache.find(x => x.id == userid) || await interaction.guild.members.fetch(userid);
|
||||||
|
|
||||||
if (!member) {
|
if (!member) {
|
||||||
|
@ -26,20 +24,14 @@ export default class Inventory extends ButtonEvent {
|
||||||
|
|
||||||
const embed = await InventoryHelper.GenerateInventoryPage(member.user.username, member.user.id, Number(page));
|
const embed = await InventoryHelper.GenerateInventoryPage(member.user.username, member.user.id, Number(page));
|
||||||
|
|
||||||
if (!embed) {
|
await interaction.update({
|
||||||
await interaction.followUp("No page for user found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.editReply({
|
|
||||||
files: [ embed.image ],
|
|
||||||
embeds: [ embed.embed ],
|
embeds: [ embed.embed ],
|
||||||
components: [ embed.row1, embed.row2 ],
|
components: [ embed.row ],
|
||||||
});
|
});
|
||||||
} 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}`);
|
||||||
|
|
||||||
await interaction.followUp("An error has occurred running this command.");
|
await interaction.reply("No page for user found.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,222 +0,0 @@
|
||||||
import { AttachmentBuilder, ButtonInteraction, EmbedBuilder } from "discord.js";
|
|
||||||
import { ButtonEvent } from "../type/buttonEvent";
|
|
||||||
import AppLogger from "../client/appLogger";
|
|
||||||
import Inventory from "../database/entities/app/Inventory";
|
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
|
||||||
import { readFileSync } from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import ErrorMessages from "../constants/ErrorMessages";
|
|
||||||
import User from "../database/entities/app/User";
|
|
||||||
import { GetSacrificeAmount } from "../constants/CardRarity";
|
|
||||||
import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper";
|
|
||||||
import MultidropEmbedHelper from "../helpers/DropHelpers/MultidropEmbedHelper";
|
|
||||||
|
|
||||||
export default class Multidrop extends ButtonEvent {
|
|
||||||
public override async execute(interaction: ButtonInteraction) {
|
|
||||||
const action = interaction.customId.split(" ")[1];
|
|
||||||
|
|
||||||
switch (action) {
|
|
||||||
case "keep":
|
|
||||||
await this.Keep(interaction);
|
|
||||||
break;
|
|
||||||
case "sacrifice":
|
|
||||||
await this.Sacrifice(interaction);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
await interaction.reply("Invalid action");
|
|
||||||
AppLogger.LogError("Button/Multidrop", `Invalid action, ${action}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Keep(interaction: ButtonInteraction) {
|
|
||||||
const cardNumber = interaction.customId.split(" ")[2];
|
|
||||||
let cardsRemaining = Number(interaction.customId.split(" ")[3]) || 0;
|
|
||||||
const userId = interaction.customId.split(" ")[4];
|
|
||||||
|
|
||||||
if (interaction.user.id != userId) {
|
|
||||||
await interaction.reply("You're not the user this drop was made for!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const card = GetCardsHelper.GetCardByCardNumber(cardNumber);
|
|
||||||
|
|
||||||
if (!card) {
|
|
||||||
await interaction.reply("Unable to find card.");
|
|
||||||
AppLogger.LogWarn("Button/Multidrop/Keep", `Card not found, ${cardNumber}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cardsRemaining < 0) {
|
|
||||||
await interaction.reply("Your multidrop has ran out! Please buy a new one!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await User.FetchOneById(User, interaction.user.id);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
AppLogger.LogWarn("Button/Multidrop/Keep", ErrorMessages.UnableToFetchUser);
|
|
||||||
await interaction.reply(ErrorMessages.UnableToFetchUser);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Claim
|
|
||||||
let inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, cardNumber);
|
|
||||||
|
|
||||||
if (!inventory) {
|
|
||||||
inventory = new Inventory(interaction.user.id, cardNumber, 1);
|
|
||||||
} else {
|
|
||||||
inventory.AddQuantity(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
await inventory.Save(Inventory, inventory);
|
|
||||||
|
|
||||||
// Pack has ran out
|
|
||||||
if (cardsRemaining == 0) {
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setDescription("Your multidrop has ran out! Please buy a new one!")
|
|
||||||
.setColor(EmbedColours.Ok);
|
|
||||||
|
|
||||||
await interaction.update({
|
|
||||||
embeds: [ embed ],
|
|
||||||
attachments: [],
|
|
||||||
components: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop next card
|
|
||||||
const randomCard = GetCardsHelper.GetRandomCard();
|
|
||||||
cardsRemaining -= 1;
|
|
||||||
|
|
||||||
if (!randomCard) {
|
|
||||||
AppLogger.LogWarn("Button/Multidrop/Keep", ErrorMessages.UnableToFetchCard);
|
|
||||||
await interaction.reply(ErrorMessages.UnableToFetchCard);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.deferUpdate();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const files = [];
|
|
||||||
let imageFileName = "";
|
|
||||||
|
|
||||||
if (!(randomCard.card.path.startsWith("http://") || randomCard.card.path.startsWith("https://"))) {
|
|
||||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path));
|
|
||||||
imageFileName = randomCard.card.path.split("/").pop()!;
|
|
||||||
|
|
||||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
|
||||||
|
|
||||||
files.push(attachment);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
|
||||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
|
||||||
|
|
||||||
const embed = MultidropEmbedHelper.GenerateMultidropEmbed(randomCard, quantityClaimed, imageFileName, cardsRemaining, undefined, user.Currency);
|
|
||||||
|
|
||||||
const row = MultidropEmbedHelper.GenerateMultidropButtons(randomCard, cardsRemaining, interaction.user.id, cardsRemaining < 0);
|
|
||||||
|
|
||||||
await interaction.editReply({
|
|
||||||
embeds: [ embed ],
|
|
||||||
files: files,
|
|
||||||
components: [ row ],
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
AppLogger.LogError("Button/Multidrop/Keep", `Error sending next drop for card ${randomCard.card.id}: ${e}`);
|
|
||||||
|
|
||||||
await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. (${randomCard.card.id})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Sacrifice(interaction: ButtonInteraction) {
|
|
||||||
const cardNumber = interaction.customId.split(" ")[2];
|
|
||||||
let cardsRemaining = Number(interaction.customId.split(" ")[3]) || 0;
|
|
||||||
const userId = interaction.customId.split(" ")[4];
|
|
||||||
|
|
||||||
if (interaction.user.id != userId) {
|
|
||||||
await interaction.reply("You're not the user this drop was made for!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const card = GetCardsHelper.GetCardByCardNumber(cardNumber);
|
|
||||||
|
|
||||||
if (!card) {
|
|
||||||
await interaction.reply("Unable to find card.");
|
|
||||||
AppLogger.LogWarn("Button/Multidrop/Sacrifice", `Card not found, ${cardNumber}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cardsRemaining < 0) {
|
|
||||||
await interaction.reply("Your multidrop has ran out! Please buy a new one!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await User.FetchOneById(User, interaction.user.id);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
AppLogger.LogWarn("Button/Multidrop/Sacrifice", ErrorMessages.UnableToFetchUser);
|
|
||||||
await interaction.reply(ErrorMessages.UnableToFetchUser);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sacrifice
|
|
||||||
const sacrificeAmount = GetSacrificeAmount(card.card.type);
|
|
||||||
|
|
||||||
user.AddCurrency(sacrificeAmount);
|
|
||||||
|
|
||||||
await user.Save(User, user);
|
|
||||||
|
|
||||||
// Pack has ran out
|
|
||||||
if (cardsRemaining == 0) {
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setDescription("Your multidrop has ran out! Please buy a new one!")
|
|
||||||
.setColor(EmbedColours.Ok);
|
|
||||||
|
|
||||||
await interaction.update({
|
|
||||||
embeds: [ embed ],
|
|
||||||
attachments: [],
|
|
||||||
components: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop next card
|
|
||||||
const randomCard = GetCardsHelper.GetRandomCard();
|
|
||||||
cardsRemaining -= 1;
|
|
||||||
|
|
||||||
if (!randomCard) {
|
|
||||||
AppLogger.LogWarn("Button/Multidrop/Sacrifice", ErrorMessages.UnableToFetchCard);
|
|
||||||
await interaction.reply(ErrorMessages.UnableToFetchCard);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.deferUpdate();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path));
|
|
||||||
const imageFileName = randomCard.card.path.split("/").pop()!;
|
|
||||||
|
|
||||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
|
||||||
|
|
||||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
|
||||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
|
||||||
|
|
||||||
const embed = MultidropEmbedHelper.GenerateMultidropEmbed(randomCard, quantityClaimed, imageFileName, cardsRemaining, undefined, user.Currency);
|
|
||||||
|
|
||||||
const row = MultidropEmbedHelper.GenerateMultidropButtons(randomCard, cardsRemaining, interaction.user.id, cardsRemaining < 0);
|
|
||||||
|
|
||||||
await interaction.editReply({
|
|
||||||
embeds: [ embed ],
|
|
||||||
files: [ attachment ],
|
|
||||||
components: [ row ],
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
AppLogger.LogError("Button/Multidrop/Sacrifice", `Error sending next drop for card ${randomCard.card.id}: ${e}`);
|
|
||||||
|
|
||||||
await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. (${randomCard.card.id})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,13 +5,9 @@ import { v4 } from "uuid";
|
||||||
import { CoreClient } from "../client/client";
|
import { CoreClient } from "../client/client";
|
||||||
import Inventory from "../database/entities/app/Inventory";
|
import Inventory from "../database/entities/app/Inventory";
|
||||||
import Config from "../database/entities/app/Config";
|
import Config from "../database/entities/app/Config";
|
||||||
|
import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import AppLogger from "../client/appLogger";
|
import AppLogger from "../client/appLogger";
|
||||||
import User from "../database/entities/app/User";
|
|
||||||
import CardConstants from "../constants/CardConstants";
|
|
||||||
import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper";
|
|
||||||
import DropEmbedHelper from "../helpers/DropHelpers/DropEmbedHelper";
|
|
||||||
import {DropResult} from "../contracts/SeriesMetadata";
|
|
||||||
|
|
||||||
export default class Reroll extends ButtonEvent {
|
export default class Reroll extends ButtonEvent {
|
||||||
public override async execute(interaction: ButtonInteraction) {
|
public override async execute(interaction: ButtonInteraction) {
|
||||||
|
@ -27,29 +23,7 @@ export default class Reroll extends ButtonEvent {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = await User.FetchOneById(User, interaction.user.id);
|
const randomCard = CardDropHelperMetadata.GetRandomCard();
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
user = new User(interaction.user.id, CardConstants.StartingCurrency);
|
|
||||||
await user.Save(User, user);
|
|
||||||
|
|
||||||
AppLogger.LogInfo("Button/Reroll", `New user (${interaction.user.id}) saved to the database`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!user.RemoveCurrency(CardConstants.ClaimCost)) {
|
|
||||||
await interaction.reply(`Not enough currency! You need ${CardConstants.ClaimCost} currency, you have ${user.Currency}!`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let randomCard: DropResult | undefined;
|
|
||||||
|
|
||||||
try {
|
|
||||||
randomCard = await GetCardsHelper.FetchCard(interaction.user.id);
|
|
||||||
} catch (e) {
|
|
||||||
AppLogger.CatchError("Button/Reroll", e);
|
|
||||||
|
|
||||||
await interaction.reply("Unable to fetch card, please try again.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!randomCard) {
|
if (!randomCard) {
|
||||||
await interaction.reply("Unable to fetch card, please try again.");
|
await interaction.reply("Unable to fetch card, please try again.");
|
||||||
|
@ -61,34 +35,27 @@ export default class Reroll extends ButtonEvent {
|
||||||
try {
|
try {
|
||||||
AppLogger.LogVerbose("Button/Reroll", `Sending next drop: ${randomCard.card.id} (${randomCard.card.name})`);
|
AppLogger.LogVerbose("Button/Reroll", `Sending next drop: ${randomCard.card.id} (${randomCard.card.name})`);
|
||||||
|
|
||||||
const files = [];
|
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path));
|
||||||
let imageFileName = "";
|
const imageFileName = randomCard.card.path.split("/").pop()!;
|
||||||
|
|
||||||
if (!(randomCard.card.path.startsWith("http://") || randomCard.card.path.startsWith("https://"))) {
|
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path));
|
|
||||||
imageFileName = randomCard.card.path.split("/").pop()!;
|
|
||||||
|
|
||||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
|
||||||
|
|
||||||
files.push(attachment);
|
|
||||||
}
|
|
||||||
|
|
||||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
||||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
||||||
|
|
||||||
const embed = DropEmbedHelper.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName, undefined, user.Currency);
|
const embed = CardDropHelperMetadata.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName);
|
||||||
|
|
||||||
const claimId = v4();
|
const claimId = v4();
|
||||||
|
|
||||||
const row = DropEmbedHelper.GenerateDropButtons(randomCard, claimId, interaction.user.id);
|
const row = CardDropHelperMetadata.GenerateDropButtons(randomCard, claimId, interaction.user.id);
|
||||||
|
|
||||||
await user.Save(User, user);
|
|
||||||
|
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
embeds: [ embed ],
|
embeds: [ embed ],
|
||||||
files: files,
|
files: [ attachment ],
|
||||||
components: [ row ],
|
components: [ row ],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
CoreClient.ClaimId = claimId;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
AppLogger.LogError("Button/Reroll", `Error sending next drop for card ${randomCard.card.id}: ${e}`);
|
AppLogger.LogError("Button/Reroll", `Error sending next drop for card ${randomCard.card.id}: ${e}`);
|
||||||
|
|
||||||
|
|
|
@ -1,247 +0,0 @@
|
||||||
import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, EmbedBuilder } from "discord.js";
|
|
||||||
import { ButtonEvent } from "../type/buttonEvent";
|
|
||||||
import Inventory from "../database/entities/app/Inventory";
|
|
||||||
import { CardRarityToString, GetSacrificeAmount } from "../constants/CardRarity";
|
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
|
||||||
import User from "../database/entities/app/User";
|
|
||||||
import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper";
|
|
||||||
import CardConstants from "../constants/CardConstants";
|
|
||||||
|
|
||||||
export default class Sacrifice extends ButtonEvent {
|
|
||||||
public override async execute(interaction: ButtonInteraction) {
|
|
||||||
const subcommand = interaction.customId.split(" ")[1];
|
|
||||||
|
|
||||||
switch(subcommand) {
|
|
||||||
case "confirm":
|
|
||||||
await this.confirm(interaction);
|
|
||||||
break;
|
|
||||||
case "cancel":
|
|
||||||
await this.cancel(interaction);
|
|
||||||
break;
|
|
||||||
case "give":
|
|
||||||
await this.give(interaction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async confirm(interaction: ButtonInteraction) {
|
|
||||||
const userId = interaction.customId.split(" ")[2];
|
|
||||||
const cardNumber = interaction.customId.split(" ")[3];
|
|
||||||
const quantity = Number(interaction.customId.split(" ")[4]) || 1;
|
|
||||||
|
|
||||||
if (userId != interaction.user.id) {
|
|
||||||
await interaction.reply("Only the user who created this sacrifice can confirm it.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cardInInventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber);
|
|
||||||
|
|
||||||
if (!cardInInventory || cardInInventory.Quantity == 0) {
|
|
||||||
await interaction.reply("Unable to find card in inventory.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cardInInventory.Quantity < quantity) {
|
|
||||||
await interaction.reply("You can only sacrifice what you own.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cardData = GetCardsHelper.GetCardByCardNumber(cardNumber);
|
|
||||||
|
|
||||||
if (!cardData) {
|
|
||||||
await interaction.reply("Unable to find card in the database.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await User.FetchOneById(User, userId);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
await interaction.reply("Unable to find user in database.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cardInInventory.RemoveQuantity(quantity);
|
|
||||||
|
|
||||||
await cardInInventory.Save(Inventory, cardInInventory);
|
|
||||||
|
|
||||||
const cardValue = GetSacrificeAmount(cardData.card.type) * quantity;
|
|
||||||
const cardRarityString = CardRarityToString(cardData.card.type);
|
|
||||||
|
|
||||||
user.AddCurrency(cardValue);
|
|
||||||
|
|
||||||
await user.Save(User, user);
|
|
||||||
|
|
||||||
const description = [
|
|
||||||
`Card: ${cardData.card.name}`,
|
|
||||||
`Series: ${cardData.series.name}`,
|
|
||||||
`Rarity: ${cardRarityString}`,
|
|
||||||
`Quantity Owned: ${cardInInventory.Quantity}`,
|
|
||||||
`Quantity To Sacrifice: ${quantity}`,
|
|
||||||
`Sacrifice Amount: ${cardValue}`,
|
|
||||||
];
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle("Card Sacrificed")
|
|
||||||
.setDescription(description.join("\n"))
|
|
||||||
.setColor(EmbedColours.Green)
|
|
||||||
.setFooter({ text: `${interaction.user.username}` });
|
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
|
||||||
.addComponents([
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`sacrifice confirm ${interaction.user.id} ${cardNumber}`)
|
|
||||||
.setLabel("Confirm")
|
|
||||||
.setStyle(ButtonStyle.Success)
|
|
||||||
.setDisabled(true),
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId("sacrifice cancel")
|
|
||||||
.setLabel("Cancel")
|
|
||||||
.setStyle(ButtonStyle.Secondary)
|
|
||||||
.setDisabled(true),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await interaction.update({
|
|
||||||
embeds: [ embed ],
|
|
||||||
components: [ row ],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async cancel(interaction: ButtonInteraction) {
|
|
||||||
const userId = interaction.customId.split(" ")[2];
|
|
||||||
const cardNumber = interaction.customId.split(" ")[3];
|
|
||||||
const quantity = Number(interaction.customId.split(" ")[4]) || 1;
|
|
||||||
|
|
||||||
if (userId != interaction.user.id) {
|
|
||||||
await interaction.reply("Only the user who created this sacrifice can cancel it.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cardInInventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber);
|
|
||||||
|
|
||||||
if (!cardInInventory || cardInInventory.Quantity == 0) {
|
|
||||||
await interaction.reply("Unable to find card in inventory.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cardInInventory.Quantity < quantity) {
|
|
||||||
await interaction.reply("You can only sacrifice what you own.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cardData = GetCardsHelper.GetCardByCardNumber(cardNumber);
|
|
||||||
|
|
||||||
if (!cardData) {
|
|
||||||
await interaction.reply("Unable to find card in the database.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cardValue = GetSacrificeAmount(cardData.card.type) * quantity;
|
|
||||||
const cardRarityString = CardRarityToString(cardData.card.type);
|
|
||||||
|
|
||||||
const description = [
|
|
||||||
`Card: ${cardData.card.name}`,
|
|
||||||
`Series: ${cardData.series.name}`,
|
|
||||||
`Rarity: ${cardRarityString}`,
|
|
||||||
`Quantity Owned: ${cardInInventory.Quantity}`,
|
|
||||||
`Quantity To Sacrifice: ${quantity}`,
|
|
||||||
`Sacrifice Amount: ${cardValue}`,
|
|
||||||
];
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle("Sacrifice Cancelled")
|
|
||||||
.setDescription(description.join("\n"))
|
|
||||||
.setColor(EmbedColours.Grey)
|
|
||||||
.setFooter({ text: `${interaction.user.username}` });
|
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
|
||||||
.addComponents([
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`sacrifice confirm ${interaction.user.id} ${cardNumber}`)
|
|
||||||
.setLabel("Confirm")
|
|
||||||
.setStyle(ButtonStyle.Success)
|
|
||||||
.setDisabled(true),
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId("sacrifice cancel")
|
|
||||||
.setLabel("Cancel")
|
|
||||||
.setStyle(ButtonStyle.Secondary)
|
|
||||||
.setDisabled(true),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await interaction.update({
|
|
||||||
embeds: [ embed ],
|
|
||||||
components: [ row ],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async give(interaction: ButtonInteraction) {
|
|
||||||
const userId = interaction.customId.split(" ")[2];
|
|
||||||
const cardNumber = interaction.customId.split(" ")[3];
|
|
||||||
const quantity = Number(interaction.customId.split(" ")[4]) || 1;
|
|
||||||
|
|
||||||
if (userId != interaction.user.id) {
|
|
||||||
await interaction.reply("Only the user who created this sacrifice can confirm it.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cardData = GetCardsHelper.GetCardByCardNumber(cardNumber);
|
|
||||||
|
|
||||||
if (!cardData) {
|
|
||||||
await interaction.reply("Unable to find card in the database.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = await User.FetchOneById(User, userId);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
user = new User(userId, CardConstants.StartingCurrency);
|
|
||||||
}
|
|
||||||
|
|
||||||
const cardInInventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber);
|
|
||||||
let cardQuantity = 0;
|
|
||||||
|
|
||||||
if (cardInInventory) {
|
|
||||||
cardQuantity = cardInInventory.Quantity;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cardValue = GetSacrificeAmount(cardData.card.type) * quantity;
|
|
||||||
const cardRarityString = CardRarityToString(cardData.card.type);
|
|
||||||
|
|
||||||
user.AddCurrency(cardValue);
|
|
||||||
|
|
||||||
await user.Save(User, user);
|
|
||||||
|
|
||||||
const description = [
|
|
||||||
`Card: ${cardData.card.name}`,
|
|
||||||
`Series: ${cardData.series.name}`,
|
|
||||||
`Rarity: ${cardRarityString}`,
|
|
||||||
`Quantity Owned: ${cardQuantity}`,
|
|
||||||
`Quantity To Sacrifice: ${quantity}`,
|
|
||||||
`Sacrifice Amount: ${cardValue}`,
|
|
||||||
];
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle("Card Sacrificed")
|
|
||||||
.setDescription(description.join("\n"))
|
|
||||||
.setColor(EmbedColours.Green)
|
|
||||||
.setFooter({ text: `${interaction.user.username}` });
|
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
|
||||||
.addComponents([
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`sacrifice confirm ${interaction.user.id} ${cardNumber}`)
|
|
||||||
.setLabel("Confirm")
|
|
||||||
.setStyle(ButtonStyle.Success)
|
|
||||||
.setDisabled(true),
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId("sacrifice cancel")
|
|
||||||
.setLabel("Cancel")
|
|
||||||
.setStyle(ButtonStyle.Secondary)
|
|
||||||
.setDisabled(true),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await interaction.update({
|
|
||||||
embeds: [ embed ],
|
|
||||||
components: [ row ],
|
|
||||||
attachments: [],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -24,14 +24,11 @@ export default class Series extends ButtonEvent {
|
||||||
const seriesid = interaction.customId.split(" ")[2];
|
const seriesid = interaction.customId.split(" ")[2];
|
||||||
const page = interaction.customId.split(" ")[3];
|
const page = interaction.customId.split(" ")[3];
|
||||||
|
|
||||||
await interaction.deferUpdate();
|
const embed = SeriesHelper.GenerateSeriesViewPage(Number(seriesid), Number(page));
|
||||||
|
|
||||||
const embed = await SeriesHelper.GenerateSeriesViewPage(Number(seriesid), Number(page), interaction.user.id);
|
await interaction.update({
|
||||||
|
|
||||||
await interaction.editReply({
|
|
||||||
embeds: [ embed!.embed ],
|
embeds: [ embed!.embed ],
|
||||||
components: [ embed!.row ],
|
components: [ embed!.row ],
|
||||||
files: [ embed!.image ],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,16 +22,14 @@ export default class Trade extends ButtonEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async AcceptTrade(interaction: ButtonInteraction) {
|
private async AcceptTrade(interaction: ButtonInteraction) {
|
||||||
const user1UserId = interaction.customId.split(" ")[2];
|
const giveUserId = interaction.customId.split(" ")[2];
|
||||||
const user2UserId = interaction.customId.split(" ")[3];
|
const receiveUserId = interaction.customId.split(" ")[3];
|
||||||
const user1CardNumber = interaction.customId.split(" ")[4];
|
const giveCardNumber = interaction.customId.split(" ")[4];
|
||||||
const user2CardNumber = interaction.customId.split(" ")[5];
|
const receiveCardNumber = 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} user1Quantity=${user1Quantity} user2Quantity=${user2Quantity}`);
|
AppLogger.LogSilly("Button/Trade/AcceptTrade", `Parameters: giveUserId=${giveUserId}, receiveUserId=${receiveUserId}, giveCardNumber=${giveCardNumber}, receiveCardNumber=${receiveCardNumber}, expiry=${expiry}, timeoutId=${timeoutId}`);
|
||||||
|
|
||||||
const expiryDate = new Date(expiry);
|
const expiryDate = new Date(expiry);
|
||||||
|
|
||||||
|
@ -40,80 +38,80 @@ export default class Trade extends ButtonEvent {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interaction.user.id !== user2UserId) {
|
if (interaction.user.id !== receiveUserId) {
|
||||||
await interaction.reply("You are not the user who the trade is intended for");
|
await interaction.reply("You are not the user who the trade is intended for");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user1Item = CoreClient.Cards
|
const giveItem = CoreClient.Cards
|
||||||
.flatMap(x => x.cards)
|
.flatMap(x => x.cards)
|
||||||
.find(x => x.id === user1CardNumber);
|
.find(x => x.id === giveCardNumber);
|
||||||
|
|
||||||
const user2Item = CoreClient.Cards
|
const receiveItem = CoreClient.Cards
|
||||||
.flatMap(x => x.cards)
|
.flatMap(x => x.cards)
|
||||||
.find(x => x.id === user2CardNumber);
|
.find(x => x.id === receiveCardNumber);
|
||||||
|
|
||||||
if (!user1Item || !user2Item) {
|
if (!giveItem || !receiveItem) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user1User = interaction.client.users.cache.get(user1UserId) || await interaction.client.users.fetch(user1UserId);
|
const giveUser = interaction.client.users.cache.get(giveUserId) || await interaction.client.users.fetch(giveUserId);
|
||||||
const user2User = interaction.client.users.cache.get(user2UserId) || await interaction.client.users.fetch(user2UserId);
|
const receiveUser = interaction.client.users.cache.get(receiveUserId) || await interaction.client.users.fetch(receiveUserId);
|
||||||
|
|
||||||
const user1UserInventory1 = await Inventory.FetchOneByCardNumberAndUserId(user1UserId, user1CardNumber);
|
const giveUserInventory1 = await Inventory.FetchOneByCardNumberAndUserId(giveUserId, giveCardNumber);
|
||||||
const user2UserInventory1 = await Inventory.FetchOneByCardNumberAndUserId(user2UserId, user2CardNumber);
|
const receiveUserInventory1 = await Inventory.FetchOneByCardNumberAndUserId(receiveUserId, receiveCardNumber);
|
||||||
|
|
||||||
if (!user1UserInventory1 || !user2UserInventory1) {
|
if (!giveUserInventory1 || !receiveUserInventory1) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user1UserInventory1.Quantity < user1Quantity || user2UserInventory1.Quantity < user2Quantity) {
|
if (giveUserInventory1.Quantity < 1 || receiveUserInventory1.Quantity < 1) {
|
||||||
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(user1Quantity);
|
giveUserInventory1.SetQuantity(giveUserInventory1.Quantity - 1);
|
||||||
user2UserInventory1.RemoveQuantity(user2Quantity);
|
receiveUserInventory1.SetQuantity(receiveUserInventory1.Quantity - 1);
|
||||||
|
|
||||||
await user1UserInventory1.Save(Inventory, user1UserInventory1);
|
await giveUserInventory1.Save(Inventory, giveUserInventory1);
|
||||||
await user2UserInventory1.Save(Inventory, user2UserInventory1);
|
await receiveUserInventory1.Save(Inventory, receiveUserInventory1);
|
||||||
|
|
||||||
let user1UserInventory2 = await Inventory.FetchOneByCardNumberAndUserId(user1UserId, user2CardNumber);
|
let giveUserInventory2 = await Inventory.FetchOneByCardNumberAndUserId(receiveUserId, giveCardNumber);
|
||||||
let user2UserInventory2 = await Inventory.FetchOneByCardNumberAndUserId(user2UserId, user1CardNumber);
|
let receiveUserInventory2 = await Inventory.FetchOneByCardNumberAndUserId(giveUserId, receiveCardNumber);
|
||||||
|
|
||||||
if (!user1UserInventory2) {
|
if (!giveUserInventory2) {
|
||||||
user1UserInventory2 = new Inventory(user1UserId, user2CardNumber, user2Quantity);
|
giveUserInventory2 = new Inventory(receiveUserId, giveCardNumber, 1);
|
||||||
} else {
|
} else {
|
||||||
user1UserInventory2.AddQuantity(user2Quantity);
|
giveUserInventory2.SetQuantity(giveUserInventory2.Quantity + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user2UserInventory2) {
|
if (!receiveUserInventory2) {
|
||||||
user2UserInventory2 = new Inventory(user2UserId, user1CardNumber, user1Quantity);
|
receiveUserInventory2 = new Inventory(giveUserId, receiveCardNumber, 1);
|
||||||
} else {
|
} else {
|
||||||
user2UserInventory2.AddQuantity(user1Quantity);
|
receiveUserInventory2.SetQuantity(receiveUserInventory2.Quantity + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
await user1UserInventory2.Save(Inventory, user1UserInventory2);
|
await giveUserInventory2.Save(Inventory, giveUserInventory2);
|
||||||
await user2UserInventory2.Save(Inventory, user2UserInventory2);
|
await receiveUserInventory2.Save(Inventory, receiveUserInventory2);
|
||||||
|
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
const tradeEmbed = new EmbedBuilder()
|
const tradeEmbed = new EmbedBuilder()
|
||||||
.setTitle("Trade Accepted")
|
.setTitle("Trade Accepted")
|
||||||
.setDescription(`Trade initiated between ${user1User.username} and ${user2User.username}`)
|
.setDescription(`Trade initiated between ${receiveUser.username} and ${giveUser.username}`)
|
||||||
.setColor(EmbedColours.Success)
|
.setColor(EmbedColours.Success)
|
||||||
.setImage("https://i.imgur.com/9w5f1ls.gif")
|
.setImage("https://i.imgur.com/9w5f1ls.gif")
|
||||||
.addFields([
|
.addFields([
|
||||||
{
|
{
|
||||||
name: `${user1User.username} Receives`,
|
name: "I receieve",
|
||||||
value: `${user2Item.id}: ${user2Item.name} x${user2Quantity}`,
|
value: `${receiveItem.id}: ${receiveItem.name}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `${user2User.username} Receives`,
|
name: "You receieve",
|
||||||
value: `${user1Item.id}: ${user1Item.name} x${user1Quantity}`,
|
value: `${giveItem.id}: ${giveItem.name}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -140,34 +138,32 @@ export default class Trade extends ButtonEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async DeclineTrade(interaction: ButtonInteraction) {
|
private async DeclineTrade(interaction: ButtonInteraction) {
|
||||||
const user1UserId = interaction.customId.split(" ")[2];
|
const giveUserId = interaction.customId.split(" ")[2];
|
||||||
const user2UserId = interaction.customId.split(" ")[3];
|
const receiveUserId = interaction.customId.split(" ")[3];
|
||||||
const user1CardNumber = interaction.customId.split(" ")[4];
|
const giveCardNumber = interaction.customId.split(" ")[4];
|
||||||
const user2CardNumber = interaction.customId.split(" ")[5];
|
const receiveCardNumber = 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: giveUserId=${giveUserId}, receiveUserId=${receiveUserId}, giveCardNumber=${giveCardNumber}, receiveCardNumber=${receiveCardNumber}, timeoutId=${timeoutId}`);
|
||||||
|
|
||||||
if (interaction.user.id != user1UserId && interaction.user.id !== user2UserId) {
|
if (interaction.user.id != receiveUserId && interaction.user.id !==giveUserId) {
|
||||||
await interaction.reply("You are not the user who the trade is intended for");
|
await interaction.reply("You are not the user who the trade is intended for");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user1User = interaction.client.users.cache.get(user1UserId) || await interaction.client.users.fetch(user1UserId);
|
const giveUser = interaction.client.users.cache.get(giveUserId) || await interaction.client.users.fetch(giveUserId);
|
||||||
const user2User = interaction.client.users.cache.get(user2UserId) || await interaction.client.users.fetch(user2UserId);
|
const receiveUser = interaction.client.users.cache.get(receiveUserId) || await interaction.client.users.fetch(receiveUserId);
|
||||||
|
|
||||||
const user1Item = CoreClient.Cards
|
const giveItem = CoreClient.Cards
|
||||||
.flatMap(x => x.cards)
|
.flatMap(x => x.cards)
|
||||||
.find(x => x.id === user1CardNumber);
|
.find(x => x.id === giveCardNumber);
|
||||||
|
|
||||||
const user2Item = CoreClient.Cards
|
const receiveItem = CoreClient.Cards
|
||||||
.flatMap(x => x.cards)
|
.flatMap(x => x.cards)
|
||||||
.find(x => x.id === user2CardNumber);
|
.find(x => x.id === receiveCardNumber);
|
||||||
|
|
||||||
if (!user1Item || !user2Item) {
|
if (!giveItem || !receiveItem) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
@ -176,18 +172,18 @@ export default class Trade extends ButtonEvent {
|
||||||
|
|
||||||
const tradeEmbed = new EmbedBuilder()
|
const tradeEmbed = new EmbedBuilder()
|
||||||
.setTitle("Trade Declined")
|
.setTitle("Trade Declined")
|
||||||
.setDescription(`Trade initiated between ${user1User.username} and ${user2User.username}`)
|
.setDescription(`Trade initiated between ${receiveUser.username} and ${giveUser.username}`)
|
||||||
.setColor(EmbedColours.Error)
|
.setColor(EmbedColours.Error)
|
||||||
.setImage("https://i.imgur.com/9w5f1ls.gif")
|
.setImage("https://i.imgur.com/9w5f1ls.gif")
|
||||||
.addFields([
|
.addFields([
|
||||||
{
|
{
|
||||||
name: `${user1User.username} Receives`,
|
name: "I Receive",
|
||||||
value: `${user2Item.id}: ${user2Item.name} x${user2Quantity}`,
|
value: `${receiveItem.id}: ${receiveItem.name}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `${user2User.username} Receives`,
|
name: "You Receive",
|
||||||
value: `${user1Item.id}: ${user1Item.name} x${user1Quantity}`,
|
value: `${giveItem.id}: ${giveItem.name}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
import {ButtonInteraction} from "discord.js";
|
|
||||||
import {ButtonEvent} from "../type/buttonEvent.js";
|
|
||||||
import CardSearchHelper from "../helpers/CardSearchHelper.js";
|
|
||||||
|
|
||||||
export default class View extends ButtonEvent {
|
|
||||||
public override async execute(interaction: ButtonInteraction) {
|
|
||||||
const page = interaction.customId.split(" ")[1];
|
|
||||||
const results = interaction.customId.split(" ").splice(2);
|
|
||||||
|
|
||||||
await interaction.deferUpdate();
|
|
||||||
|
|
||||||
const searchResult = await CardSearchHelper.GenerateSearchPageFromQuery(results, interaction.user.id, Number(page));
|
|
||||||
|
|
||||||
if (!searchResult) {
|
|
||||||
await interaction.followUp("No results found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.editReply({
|
|
||||||
embeds: [ searchResult.embed ],
|
|
||||||
components: [ searchResult.row ],
|
|
||||||
files: searchResult.attachments,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,4 @@
|
||||||
import path from "path";
|
|
||||||
import { Logger, createLogger, format, transports } from "winston";
|
import { Logger, createLogger, format, transports } from "winston";
|
||||||
import DailyRotateFile from "winston-daily-rotate-file";
|
|
||||||
import DiscordTransport from "winston-discord-transport";
|
|
||||||
|
|
||||||
export default class AppLogger {
|
export default class AppLogger {
|
||||||
public static Logger: Logger;
|
public static Logger: Logger;
|
||||||
|
@ -22,21 +19,12 @@ export default class AppLogger {
|
||||||
customFormat,
|
customFormat,
|
||||||
),
|
),
|
||||||
defaultMeta: { service: "bot" },
|
defaultMeta: { service: "bot" },
|
||||||
transports: [],
|
transports: [
|
||||||
|
new transports.File({ filename: "error.log", level: "error" }),
|
||||||
|
new transports.File({ filename: "combined.log" }),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (process.env.DATA_DIR) {
|
|
||||||
const logDir = path.join(process.env.DATA_DIR, "logs");
|
|
||||||
|
|
||||||
logger.add(new DailyRotateFile({
|
|
||||||
filename: "bot-%DATE%.log",
|
|
||||||
dirname: logDir,
|
|
||||||
datePattern: "YYYY-MM-DD-HH",
|
|
||||||
maxSize: "20m",
|
|
||||||
maxFiles: "14d",
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (outputToConsole) {
|
if (outputToConsole) {
|
||||||
logger.add(new transports.Console({
|
logger.add(new transports.Console({
|
||||||
format: format.combine(
|
format: format.combine(
|
||||||
|
@ -46,18 +34,6 @@ export default class AppLogger {
|
||||||
)}));
|
)}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.BOT_LOG_DISCORD_ENABLE == "true") {
|
|
||||||
if (process.env.BOT_LOG_DISCORD_WEBHOOK) {
|
|
||||||
logger.add(new DiscordTransport({
|
|
||||||
webhook: process.env.BOT_LOG_DISCORD_WEBHOOK.toString(),
|
|
||||||
defaultMeta: { service: process.env.BOT_LOG_DISCORD_SERVICE },
|
|
||||||
level: process.env.BOT_LOG_DISCORD_LEVEL,
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
throw "BOT_LOG_DISCORD_WEBHOOK is required to enable discord logger support.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AppLogger.Logger = logger;
|
AppLogger.Logger = logger;
|
||||||
|
|
||||||
AppLogger.LogInfo("AppLogger", `Log Level: ${logLevel}`);
|
AppLogger.LogInfo("AppLogger", `Log Level: ${logLevel}`);
|
||||||
|
@ -86,12 +62,4 @@ export default class AppLogger {
|
||||||
public static LogSilly(label: string, message: string) {
|
public static LogSilly(label: string, message: string) {
|
||||||
AppLogger.Logger.silly({ label, message });
|
AppLogger.Logger.silly({ label, message });
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CatchError(label: string, error: unknown) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
AppLogger.Logger.error({ label, message: error.message });
|
|
||||||
} else {
|
|
||||||
AppLogger.Logger.error({ label, message: error });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -14,23 +14,17 @@ import Webhooks from "../webhooks";
|
||||||
import CardMetadataFunction from "../Functions/CardMetadataFunction";
|
import CardMetadataFunction from "../Functions/CardMetadataFunction";
|
||||||
import { SeriesMetadata } from "../contracts/SeriesMetadata";
|
import { SeriesMetadata } from "../contracts/SeriesMetadata";
|
||||||
import AppLogger from "./appLogger";
|
import AppLogger from "./appLogger";
|
||||||
import TimerHelper from "../helpers/TimerHelper";
|
|
||||||
import GiveCurrency from "../timers/GiveCurrency";
|
|
||||||
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;
|
||||||
private _webhooks: Webhooks;
|
private _webhooks: Webhooks;
|
||||||
private _timerHelper: TimerHelper;
|
|
||||||
|
|
||||||
|
public static ClaimId: string;
|
||||||
public static Environment: Environment;
|
public static Environment: Environment;
|
||||||
public static AllowDrops: boolean;
|
public static AllowDrops: boolean;
|
||||||
public static Cards: SeriesMetadata[];
|
public static Cards: SeriesMetadata[];
|
||||||
|
@ -47,10 +41,6 @@ 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();
|
||||||
|
@ -65,12 +55,10 @@ 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();
|
||||||
this._webhooks = new Webhooks();
|
this._webhooks = new Webhooks();
|
||||||
this._timerHelper = new TimerHelper();
|
|
||||||
|
|
||||||
AppLogger.LogInfo("Client", `Environment: ${CoreClient.Environment}`);
|
AppLogger.LogInfo("Client", `Environment: ${CoreClient.Environment}`);
|
||||||
|
|
||||||
|
@ -84,14 +72,7 @@ export class CoreClient extends Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
await AppDataSource.initialize()
|
await AppDataSource.initialize()
|
||||||
.then(() => {
|
.then(() => AppLogger.LogInfo("Client", "App Data Source Initialised"))
|
||||||
AppLogger.LogInfo("Client", "App Data Source Initialised");
|
|
||||||
|
|
||||||
this._timerHelper.AddTimer("*/20 * * * *", "Europe/London", GiveCurrency, false);
|
|
||||||
this._timerHelper.AddTimer("0 0 * * *", "Europe/London", PurgeClaims, false);
|
|
||||||
|
|
||||||
this._timerHelper.StartAllTimers();
|
|
||||||
})
|
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
AppLogger.LogError("Client", "App Data Source Initialisation Failed");
|
AppLogger.LogError("Client", "App Data Source Initialisation Failed");
|
||||||
AppLogger.LogError("Client", err);
|
AppLogger.LogError("Client", err);
|
||||||
|
@ -415,19 +396,4 @@ 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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,11 @@ import { Interaction } from "discord.js";
|
||||||
import ChatInputCommand from "./interactionCreate/ChatInputCommand";
|
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 StringDropdown from "./interactionCreate/StringDropdown";
|
|
||||||
|
|
||||||
export class Events {
|
export class Events {
|
||||||
public async onInteractionCreate(interaction: Interaction) {
|
public async onInteractionCreate(interaction: Interaction) {
|
||||||
if (!interaction.guildId) return;
|
if (!interaction.guildId) return;
|
||||||
|
|
||||||
await NewUserDiscovery(interaction);
|
|
||||||
|
|
||||||
if (interaction.isChatInputCommand()) {
|
if (interaction.isChatInputCommand()) {
|
||||||
AppLogger.LogVerbose("Client", `ChatInputCommand: ${interaction.commandName}`);
|
AppLogger.LogVerbose("Client", `ChatInputCommand: ${interaction.commandName}`);
|
||||||
ChatInputCommand.onChatInput(interaction);
|
ChatInputCommand.onChatInput(interaction);
|
||||||
|
@ -20,11 +16,6 @@ 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
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { Interaction } from "discord.js";
|
|
||||||
import User from "../../../database/entities/app/User";
|
|
||||||
import CardConstants from "../../../constants/CardConstants";
|
|
||||||
import AppLogger from "../../appLogger";
|
|
||||||
|
|
||||||
export default async function NewUserDiscovery(interaction: Interaction) {
|
|
||||||
const existingUser = await User.FetchOneById(User, interaction.user.id);
|
|
||||||
|
|
||||||
if (existingUser) return;
|
|
||||||
|
|
||||||
const newUser = new User(interaction.user.id, CardConstants.StartingCurrency);
|
|
||||||
await newUser.Save(User, newUser);
|
|
||||||
|
|
||||||
AppLogger.LogInfo("NewUserDiscovery", `Discovered new user ${interaction.user.id}`);
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { CommandInteraction, EmbedBuilder, PermissionsBitField, SlashCommandBuilder } from "discord.js";
|
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
|
||||||
import { Command } from "../type/command";
|
|
||||||
import User from "../database/entities/app/User";
|
|
||||||
|
|
||||||
export default class AllBalance extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName("allbalance")
|
|
||||||
.setDescription("Get everyone's currency balance")
|
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
const users = await User.FetchAll(User);
|
|
||||||
|
|
||||||
const filteredUsers = users.filter(x => x.Currency > 0)
|
|
||||||
.sort((a, b) => b.Currency - a.Currency);
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setColor(EmbedColours.Ok)
|
|
||||||
.setTitle("All Balances")
|
|
||||||
.setDescription(filteredUsers.map(x => `<@${x.Id}> ${x.Currency}`).join("\n"));
|
|
||||||
|
|
||||||
await interaction.reply({ embeds: [ embed ], ephemeral: true });
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { CommandInteraction, EmbedBuilder, SlashCommandBuilder } from "discord.js";
|
|
||||||
import { Command } from "../type/command";
|
|
||||||
import User from "../database/entities/app/User";
|
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
|
||||||
|
|
||||||
export default class Balance extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName("balance")
|
|
||||||
.setDescription("Get your currency balance");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
const user = await User.FetchOneById(User, interaction.user.id);
|
|
||||||
|
|
||||||
const userBalance = user != null ? user.Currency : 0;
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setColor(EmbedColours.Ok)
|
|
||||||
.setTitle("Balance")
|
|
||||||
.setDescription(`You currently have **${userBalance} currency**!`)
|
|
||||||
.setFooter({ text: interaction.user.username, iconURL: interaction.user.avatarURL() ?? undefined });
|
|
||||||
|
|
||||||
await interaction.reply({ embeds: [ embed ]});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
|
|
||||||
import { Command } from "../type/command";
|
|
||||||
import User from "../database/entities/app/User";
|
|
||||||
import CardConstants from "../constants/CardConstants";
|
|
||||||
import TimeLengthInput from "../helpers/TimeLengthInput";
|
|
||||||
|
|
||||||
export default class Daily extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName("daily")
|
|
||||||
.setDescription("Gain bonus currency, once a day");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
const user = await User.FetchOneById(User, interaction.user.id) ?? new User(interaction.user.id, CardConstants.StartingCurrency);
|
|
||||||
|
|
||||||
const dayAgo = new Date(Date.now() - (1000 * 60 * 60 * 24));
|
|
||||||
|
|
||||||
if (user.LastUsedDaily && user.LastUsedDaily > dayAgo) {
|
|
||||||
const timeNow = Date.now();
|
|
||||||
const timeLength = 24 * 60 * 60 * 1000; // 1 day
|
|
||||||
|
|
||||||
const timeLeft = Math.ceil(((timeLength - (timeNow - user.LastUsedDaily.getTime()))) / 1000 / 60);
|
|
||||||
|
|
||||||
const timeLeftHours = Math.floor(timeLeft / 60);
|
|
||||||
const timeLeftMinutes = timeLeft % 60;
|
|
||||||
|
|
||||||
const timeLeftString = new TimeLengthInput(`${timeLeftHours}h ${timeLeftMinutes}m`);
|
|
||||||
|
|
||||||
await interaction.reply(`You have already used the daily command! You can use it again in **${timeLeftString.GetLength()}**.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
user.AddCurrency(CardConstants.DailyCurrency);
|
|
||||||
user.UpdateLastUsedDaily(new Date());
|
|
||||||
|
|
||||||
await user.Save(User, user);
|
|
||||||
|
|
||||||
await interaction.reply(`Congratulations, you have claimed your daily ${CardConstants.DailyCurrency} currency! You now have ${user.Currency} currency and can claim again in 24 hours!`);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,14 +5,9 @@ import { CoreClient } from "../client/client";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
import Inventory from "../database/entities/app/Inventory";
|
import Inventory from "../database/entities/app/Inventory";
|
||||||
import Config from "../database/entities/app/Config";
|
import Config from "../database/entities/app/Config";
|
||||||
|
import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import AppLogger from "../client/appLogger";
|
import AppLogger from "../client/appLogger";
|
||||||
import User from "../database/entities/app/User";
|
|
||||||
import CardConstants from "../constants/CardConstants";
|
|
||||||
import ErrorMessages from "../constants/ErrorMessages";
|
|
||||||
import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper";
|
|
||||||
import DropEmbedHelper from "../helpers/DropHelpers/DropEmbedHelper";
|
|
||||||
import {DropResult} from "../contracts/SeriesMetadata";
|
|
||||||
|
|
||||||
export default class Drop extends Command {
|
export default class Drop extends Command {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -25,80 +20,51 @@ 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(ErrorMessages.BotSyncing);
|
await interaction.reply("Bot is currently syncing, please wait until its done.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await Config.GetValue("safemode") == "true") {
|
if (await Config.GetValue("safemode") == "true") {
|
||||||
AppLogger.LogWarn("Commands/Drop", ErrorMessages.SafeMode);
|
AppLogger.LogWarn("Commands/Drop", "Safe Mode is active, refusing to send next drop.");
|
||||||
await interaction.reply(ErrorMessages.SafeMode);
|
|
||||||
|
await interaction.reply("Safe Mode has been activated, please resync to continue.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = await User.FetchOneById(User, interaction.user.id);
|
const randomCard = CardDropHelperMetadata.GetRandomCard();
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
user = new User(interaction.user.id, CardConstants.StartingCurrency);
|
|
||||||
await user.Save(User, user);
|
|
||||||
|
|
||||||
AppLogger.LogInfo("Commands/Drop", `New user (${interaction.user.id}) saved to the database`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!user.RemoveCurrency(CardConstants.ClaimCost)) {
|
|
||||||
await interaction.reply(ErrorMessages.NotEnoughCurrency(CardConstants.ClaimCost, user.Currency));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let randomCard: DropResult | undefined;
|
|
||||||
|
|
||||||
try {
|
|
||||||
randomCard = await GetCardsHelper.FetchCard(interaction.user.id);
|
|
||||||
} catch (e) {
|
|
||||||
AppLogger.CatchError("Commands/Drop", e);
|
|
||||||
|
|
||||||
await interaction.reply(ErrorMessages.UnableToFetchCard);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!randomCard) {
|
if (!randomCard) {
|
||||||
AppLogger.LogWarn("Commands/Drop", ErrorMessages.UnableToFetchCard);
|
AppLogger.LogWarn("Commands/Drop", "Unable to fetch card, please try again. (randomCard is null)");
|
||||||
await interaction.reply(ErrorMessages.UnableToFetchCard);
|
|
||||||
|
await interaction.reply("Unable to fetch card, please try again.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const files = [];
|
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path));
|
||||||
let imageFileName = "";
|
const imageFileName = randomCard.card.path.split("/").pop()!;
|
||||||
|
|
||||||
if (!(randomCard.card.path.startsWith("http://") || randomCard.card.path.startsWith("https://"))) {
|
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path));
|
|
||||||
imageFileName = randomCard.card.path.split("/").pop()!;
|
|
||||||
|
|
||||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
|
||||||
|
|
||||||
files.push(attachment);
|
|
||||||
}
|
|
||||||
|
|
||||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
||||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
||||||
|
|
||||||
const embed = DropEmbedHelper.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName, undefined, user.Currency);
|
const embed = CardDropHelperMetadata.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName);
|
||||||
|
|
||||||
const claimId = v4();
|
const claimId = v4();
|
||||||
|
|
||||||
const row = DropEmbedHelper.GenerateDropButtons(randomCard, claimId, interaction.user.id);
|
const row = CardDropHelperMetadata.GenerateDropButtons(randomCard, claimId, interaction.user.id);
|
||||||
|
|
||||||
await user.Save(User, user);
|
|
||||||
|
|
||||||
|
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
embeds: [ embed ],
|
embeds: [ embed ],
|
||||||
files: files,
|
files: [ attachment ],
|
||||||
components: [ row ],
|
components: [ row ],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
CoreClient.ClaimId = claimId;
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
AppLogger.LogError("Commands/Drop", `Error sending next drop for card ${randomCard.card.id}: ${e}`);
|
AppLogger.LogError("Commands/Drop", `Error sending next drop for card ${randomCard.card.id}: ${e}`);
|
||||||
|
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
|
|
||||||
import { Command } from "../type/command";
|
|
||||||
import { EffectChoices } from "../constants/EffectDetails";
|
|
||||||
import AppLogger from "../client/appLogger";
|
|
||||||
import List from "./effects/List";
|
|
||||||
import Use from "./effects/Use";
|
|
||||||
import Buy from "./effects/Buy";
|
|
||||||
|
|
||||||
export default class Effects extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName("effects")
|
|
||||||
.setDescription("Effects")
|
|
||||||
.addSubcommand(x => x
|
|
||||||
.setName("list")
|
|
||||||
.setDescription("List all effects I have")
|
|
||||||
.addNumberOption(x => x
|
|
||||||
.setName("page")
|
|
||||||
.setDescription("The page number")
|
|
||||||
.setMinValue(1)))
|
|
||||||
.addSubcommand(x => x
|
|
||||||
.setName("use")
|
|
||||||
.setDescription("Use an effect in your inventory")
|
|
||||||
.addStringOption(y => y
|
|
||||||
.setName("id")
|
|
||||||
.setDescription("The effect id to use")
|
|
||||||
.setRequired(true)
|
|
||||||
.setChoices(EffectChoices)))
|
|
||||||
.addSubcommand(x => x
|
|
||||||
.setName("buy")
|
|
||||||
.setDescription("Buy more effects")
|
|
||||||
.addStringOption(y => y
|
|
||||||
.setName("id")
|
|
||||||
.setDescription("The effect id to buy")
|
|
||||||
.setRequired(true)
|
|
||||||
.setChoices(EffectChoices))
|
|
||||||
.addNumberOption(y => y
|
|
||||||
.setName("quantity")
|
|
||||||
.setDescription("The amount to buy")
|
|
||||||
.setMinValue(1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.isChatInputCommand()) return;
|
|
||||||
|
|
||||||
const subcommand = interaction.options.getSubcommand();
|
|
||||||
|
|
||||||
switch (subcommand) {
|
|
||||||
case "list":
|
|
||||||
await List(interaction);
|
|
||||||
break;
|
|
||||||
case "use":
|
|
||||||
await Use(interaction);
|
|
||||||
break;
|
|
||||||
case "buy":
|
|
||||||
await Buy(interaction);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
AppLogger.LogError("Commands/Effects", `Invalid subcommand: ${subcommand}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { CommandInteraction } from "discord.js";
|
|
||||||
import EffectHelper from "../../helpers/EffectHelper";
|
|
||||||
|
|
||||||
export default async function Buy(interaction: CommandInteraction) {
|
|
||||||
const id = interaction.options.get("id", true).value!;
|
|
||||||
const quantity = interaction.options.get("quantity")?.value ?? 1;
|
|
||||||
|
|
||||||
const idValue = id.toString();
|
|
||||||
const quantityValue = Number(quantity);
|
|
||||||
|
|
||||||
const result = await EffectHelper.GenerateEffectBuyEmbed(interaction.user.id, idValue, quantityValue, false);
|
|
||||||
|
|
||||||
if (typeof result == "string") {
|
|
||||||
await interaction.reply(result);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.reply({
|
|
||||||
embeds: [ result.embed ],
|
|
||||||
components: [ result.row ],
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { CommandInteraction } from "discord.js";
|
|
||||||
import EffectHelper from "../../helpers/EffectHelper";
|
|
||||||
|
|
||||||
export default async function List(interaction: CommandInteraction) {
|
|
||||||
const pageOption = interaction.options.get("page");
|
|
||||||
|
|
||||||
const page = !isNaN(Number(pageOption?.value)) ? Number(pageOption?.value) : 1;
|
|
||||||
|
|
||||||
const result = await EffectHelper.GenerateEffectListEmbed(interaction.user.id, page);
|
|
||||||
|
|
||||||
await interaction.reply({
|
|
||||||
embeds: [ result.embed ],
|
|
||||||
components: [ result.row ],
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder } from "discord.js";
|
|
||||||
import { EffectDetails } from "../../constants/EffectDetails";
|
|
||||||
import AppLogger from "../../client/appLogger";
|
|
||||||
import EffectHelper from "../../helpers/EffectHelper";
|
|
||||||
import TimeLengthInput from "../../helpers/TimeLengthInput";
|
|
||||||
import EmbedColours from "../../constants/EmbedColours";
|
|
||||||
|
|
||||||
export default async function Use(interaction: CommandInteraction) {
|
|
||||||
const id = interaction.options.get("id", true).value!.toString();
|
|
||||||
|
|
||||||
const effectDetail = EffectDetails.get(id);
|
|
||||||
|
|
||||||
if (!effectDetail) {
|
|
||||||
AppLogger.LogWarn("Commands/Effects", `Unable to find effect details for ${id}`);
|
|
||||||
|
|
||||||
await interaction.reply("Unable to find effect!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const canUseEffect = await EffectHelper.CanUseEffect(interaction.user.id, id);
|
|
||||||
|
|
||||||
if (!canUseEffect) {
|
|
||||||
await interaction.reply("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeLengthInput = TimeLengthInput.ConvertFromMilliseconds(effectDetail.duration);
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle("Effect Confirmation")
|
|
||||||
.setDescription("Would you like to use this effect?")
|
|
||||||
.setColor(EmbedColours.Ok)
|
|
||||||
.addFields([
|
|
||||||
{
|
|
||||||
name: "Effect",
|
|
||||||
value: effectDetail.friendlyName,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Length",
|
|
||||||
value: timeLengthInput.GetLengthShort(),
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
|
||||||
.addComponents([
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setLabel("Confirm")
|
|
||||||
.setCustomId(`effects use confirm ${effectDetail.id}`)
|
|
||||||
.setStyle(ButtonStyle.Primary),
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setLabel("Cancel")
|
|
||||||
.setCustomId(`effects use cancel ${effectDetail.id}`)
|
|
||||||
.setStyle(ButtonStyle.Danger),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await interaction.reply({
|
|
||||||
embeds: [ embed ],
|
|
||||||
components: [ row ],
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -2,10 +2,9 @@ import { CacheType, CommandInteraction, PermissionsBitField, SlashCommandBuilder
|
||||||
import { Command } from "../type/command";
|
import { Command } from "../type/command";
|
||||||
import { CoreClient } from "../client/client";
|
import { CoreClient } from "../client/client";
|
||||||
import Config from "../database/entities/app/Config";
|
import Config from "../database/entities/app/Config";
|
||||||
|
import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
||||||
import Inventory from "../database/entities/app/Inventory";
|
import Inventory from "../database/entities/app/Inventory";
|
||||||
import AppLogger from "../client/appLogger";
|
import AppLogger from "../client/appLogger";
|
||||||
import User from "../database/entities/app/User";
|
|
||||||
import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper";
|
|
||||||
|
|
||||||
export default class Give extends Command {
|
export default class Give extends Command {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -15,57 +14,19 @@ export default class Give extends Command {
|
||||||
.setName("give")
|
.setName("give")
|
||||||
.setDescription("Give a user a card manually, in case bot breaks")
|
.setDescription("Give a user a card manually, in case bot breaks")
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator)
|
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator)
|
||||||
.addSubcommand(x =>
|
.addStringOption(x =>
|
||||||
x
|
x
|
||||||
.setName("card")
|
.setName("cardnumber")
|
||||||
.setDescription("Give a user a card manually")
|
.setDescription("G")
|
||||||
.addStringOption(x =>
|
.setRequired(true))
|
||||||
x
|
.addUserOption(x =>
|
||||||
.setName("cardnumber")
|
|
||||||
.setDescription("The card to give")
|
|
||||||
.setRequired(true))
|
|
||||||
.addUserOption(x =>
|
|
||||||
x
|
|
||||||
.setName("user")
|
|
||||||
.setDescription("The user to give the card to")
|
|
||||||
.setRequired(true)))
|
|
||||||
.addSubcommand(x =>
|
|
||||||
x
|
x
|
||||||
.setName("currency")
|
.setName("user")
|
||||||
.setDescription("Give a user currency manually")
|
.setDescription("The user to give the card to")
|
||||||
.addNumberOption(x =>
|
.setRequired(true));
|
||||||
x
|
|
||||||
.setName("amount")
|
|
||||||
.setDescription("The amount to give")
|
|
||||||
.setRequired(true))
|
|
||||||
.addUserOption(x =>
|
|
||||||
x
|
|
||||||
.setName("user")
|
|
||||||
.setDescription("The user to give the currency to")
|
|
||||||
.setRequired(true)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction<CacheType>) {
|
public override async execute(interaction: CommandInteraction<CacheType>) {
|
||||||
if (!interaction.isChatInputCommand()) return;
|
|
||||||
|
|
||||||
const whitelistedUsers = process.env.BOT_ADMINS!.split(",");
|
|
||||||
|
|
||||||
if (!whitelistedUsers.find(x => x == interaction.user.id)) {
|
|
||||||
await interaction.reply("Only whitelisted users can use this command.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (interaction.options.getSubcommand()) {
|
|
||||||
case "card":
|
|
||||||
await this.GiveCard(interaction);
|
|
||||||
break;
|
|
||||||
case "currency":
|
|
||||||
await this.GiveCurrency(interaction);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async GiveCard(interaction: CommandInteraction) {
|
|
||||||
if (!CoreClient.AllowDrops) {
|
if (!CoreClient.AllowDrops) {
|
||||||
await interaction.reply("Bot is currently syncing, please wait until its done.");
|
await interaction.reply("Bot is currently syncing, please wait until its done.");
|
||||||
return;
|
return;
|
||||||
|
@ -76,12 +37,19 @@ export default class Give extends Command {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const whitelistedUsers = process.env.BOT_ADMINS!.split(",");
|
||||||
|
|
||||||
|
if (!whitelistedUsers.find(x => x == interaction.user.id)) {
|
||||||
|
await interaction.reply("Only whitelisted users can use this command.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const cardNumber = interaction.options.get("cardnumber", true);
|
const cardNumber = interaction.options.get("cardnumber", true);
|
||||||
const user = interaction.options.get("user", true).user!;
|
const user = interaction.options.getUser("user", true);
|
||||||
|
|
||||||
AppLogger.LogSilly("Commands/Give/GiveCard", `Parameters: cardNumber=${cardNumber.value}, user=${user.id}`);
|
AppLogger.LogSilly("Commands/Give", `Parameters: cardNumber=${cardNumber.value}, user=${user.id}`);
|
||||||
|
|
||||||
const card = GetCardsHelper.GetCardByCardNumber(cardNumber.value!.toString());
|
const card = CardDropHelperMetadata.GetCardByCardNumber(cardNumber.value!.toString());
|
||||||
|
|
||||||
if (!card) {
|
if (!card) {
|
||||||
await interaction.reply("Unable to fetch card, please try again.");
|
await interaction.reply("Unable to fetch card, please try again.");
|
||||||
|
@ -98,21 +66,6 @@ export default class Give extends Command {
|
||||||
|
|
||||||
await inventory.Save(Inventory, inventory);
|
await inventory.Save(Inventory, inventory);
|
||||||
|
|
||||||
await interaction.reply(`Card ${card.card.name} given to ${user.username}, they now have ${inventory.Quantity}`);
|
await interaction.reply(`${card.card.name} given to ${user.username}, they now have ${inventory.Quantity}`);
|
||||||
}
|
|
||||||
|
|
||||||
private async GiveCurrency(interaction: CommandInteraction) {
|
|
||||||
const amount = interaction.options.get("amount", true);
|
|
||||||
const user = interaction.options.get("user", true).user!;
|
|
||||||
|
|
||||||
AppLogger.LogSilly("Commands/Give/GiveCurrency", `Parameters: amount=${amount.value} user=${user.id}`);
|
|
||||||
|
|
||||||
const userEntity = await User.FetchOneById(User, user.id) || new User(user.id, 300);
|
|
||||||
|
|
||||||
userEntity.AddCurrency(amount.value! as number);
|
|
||||||
|
|
||||||
await userEntity.Save(User, userEntity);
|
|
||||||
|
|
||||||
await interaction.reply(`${amount.value} currency ${amount.value! as number >= 0 ? "given to" : "taken from"} ${user.username}, they now have ${userEntity.Currency}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,80 +0,0 @@
|
||||||
import { AttachmentBuilder, CommandInteraction, DiscordAPIError, SlashCommandBuilder } from "discord.js";
|
|
||||||
import { Command } from "../type/command";
|
|
||||||
import { CoreClient } from "../client/client";
|
|
||||||
import { readFileSync } from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import Inventory from "../database/entities/app/Inventory";
|
|
||||||
import AppLogger from "../client/appLogger";
|
|
||||||
import DropEmbedHelper from "../helpers/DropHelpers/DropEmbedHelper";
|
|
||||||
|
|
||||||
export default class Id extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName("id")
|
|
||||||
.setDescription("View a specific command by its id")
|
|
||||||
.addStringOption(x =>
|
|
||||||
x
|
|
||||||
.setName("cardnumber")
|
|
||||||
.setDescription("The card number to view")
|
|
||||||
.setRequired(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
const cardNumber = interaction.options.get("cardnumber");
|
|
||||||
|
|
||||||
AppLogger.LogSilly("Commands/View", `Parameters: cardNumber=${cardNumber?.value}`);
|
|
||||||
|
|
||||||
if (!cardNumber || !cardNumber.value) {
|
|
||||||
await interaction.reply("Card number is required.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const card = CoreClient.Cards
|
|
||||||
.flatMap(x => x.cards)
|
|
||||||
.find(x => x.id == cardNumber.value);
|
|
||||||
|
|
||||||
if (!card) {
|
|
||||||
await interaction.reply("Card not found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const series = CoreClient.Cards
|
|
||||||
.find(x => x.cards.includes(card))!;
|
|
||||||
|
|
||||||
const files = [];
|
|
||||||
let imageFileName = "";
|
|
||||||
|
|
||||||
if (!(card.path.startsWith("http://") || card.path.startsWith("https://"))) {
|
|
||||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.path));
|
|
||||||
imageFileName = card.path.split("/").pop()!;
|
|
||||||
|
|
||||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
|
||||||
|
|
||||||
files.push(attachment);
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.deferReply();
|
|
||||||
|
|
||||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.id);
|
|
||||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
|
||||||
|
|
||||||
const embed = DropEmbedHelper.GenerateDropEmbed({ card, series }, quantityClaimed, imageFileName);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await interaction.editReply({
|
|
||||||
embeds: [ embed ],
|
|
||||||
files: files,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
AppLogger.LogError("Commands/View", `Error sending view for card ${card.id}: ${e}`);
|
|
||||||
|
|
||||||
if (e instanceof DiscordAPIError) {
|
|
||||||
await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. Code: ${e.code}.`);
|
|
||||||
} else {
|
|
||||||
await interaction.editReply("Unable to send next drop. Please try again, and report this if it keeps happening. Code: UNKNOWN.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -22,11 +22,7 @@ export default class Inventory extends Command {
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
public override async execute(interaction: CommandInteraction) {
|
||||||
const page = interaction.options.get("page");
|
const page = interaction.options.get("page");
|
||||||
const userOption = interaction.options.get("user");
|
const user = interaction.options.getUser("user") || interaction.user;
|
||||||
|
|
||||||
const user = userOption ? userOption.user! : interaction.user;
|
|
||||||
|
|
||||||
await interaction.deferReply();
|
|
||||||
|
|
||||||
AppLogger.LogSilly("Commands/Inventory", `Parameters: page=${page?.value}, user=${user.id}`);
|
AppLogger.LogSilly("Commands/Inventory", `Parameters: page=${page?.value}, user=${user.id}`);
|
||||||
|
|
||||||
|
@ -39,20 +35,14 @@ export default class Inventory extends Command {
|
||||||
|
|
||||||
const embed = await InventoryHelper.GenerateInventoryPage(user.username, user.id, pageNumber);
|
const embed = await InventoryHelper.GenerateInventoryPage(user.username, user.id, pageNumber);
|
||||||
|
|
||||||
if (!embed) {
|
await interaction.reply({
|
||||||
await interaction.followUp("No page for user found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.followUp({
|
|
||||||
files: [ embed.image ],
|
|
||||||
embeds: [ embed.embed ],
|
embeds: [ embed.embed ],
|
||||||
components: [ embed.row1, embed.row2 ],
|
components: [ embed.row ],
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
AppLogger.LogError("Commands/Inventory", e as string);
|
AppLogger.LogError("Commands/Inventory", e as string);
|
||||||
|
|
||||||
await interaction.followUp("An error has occurred running this command.");
|
await interaction.reply("No page for user found.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,95 +0,0 @@
|
||||||
import { AttachmentBuilder, CommandInteraction, SlashCommandBuilder } from "discord.js";
|
|
||||||
import { Command } from "../type/command";
|
|
||||||
import { CoreClient } from "../client/client";
|
|
||||||
import ErrorMessages from "../constants/ErrorMessages";
|
|
||||||
import Config from "../database/entities/app/Config";
|
|
||||||
import AppLogger from "../client/appLogger";
|
|
||||||
import User from "../database/entities/app/User";
|
|
||||||
import CardConstants from "../constants/CardConstants";
|
|
||||||
import { readFileSync } from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import Inventory from "../database/entities/app/Inventory";
|
|
||||||
import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper";
|
|
||||||
import MultidropEmbedHelper from "../helpers/DropHelpers/MultidropEmbedHelper";
|
|
||||||
|
|
||||||
export default class Multidrop extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName("multidrop")
|
|
||||||
.setDescription("Drop 11 cards for the price of 10!");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
if (!CoreClient.AllowDrops) {
|
|
||||||
await interaction.reply(ErrorMessages.BotSyncing);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await Config.GetValue("safemode") == "true") {
|
|
||||||
AppLogger.LogWarn("Commands/Multidrop", ErrorMessages.SafeMode);
|
|
||||||
await interaction.reply(ErrorMessages.SafeMode);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = await User.FetchOneById(User, interaction.user.id);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
user = new User(interaction.user.id, CardConstants.StartingCurrency);
|
|
||||||
await user.Save(User, user);
|
|
||||||
|
|
||||||
AppLogger.LogInfo("Commands/Multidrop", `New user (${interaction.user.id}) saved to the database`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.Currency < CardConstants.MultidropCost) {
|
|
||||||
await interaction.reply(ErrorMessages.NotEnoughCurrency(CardConstants.MultidropCost, user.Currency));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
user.RemoveCurrency(CardConstants.MultidropCost);
|
|
||||||
await user.Save(User, user);
|
|
||||||
|
|
||||||
const randomCard = GetCardsHelper.GetRandomCard();
|
|
||||||
const cardsRemaining = CardConstants.MultidropQuantity - 1;
|
|
||||||
|
|
||||||
if (!randomCard) {
|
|
||||||
AppLogger.LogWarn("Commands/Multidrop", ErrorMessages.UnableToFetchCard);
|
|
||||||
await interaction.reply(ErrorMessages.UnableToFetchCard);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.deferReply();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const files = [];
|
|
||||||
let imageFileName = "";
|
|
||||||
|
|
||||||
if (!(randomCard.card.path.startsWith("http://") || randomCard.card.path.startsWith("https://"))) {
|
|
||||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path));
|
|
||||||
imageFileName = randomCard.card.path.split("/").pop()!;
|
|
||||||
|
|
||||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
|
||||||
|
|
||||||
files.push(attachment);
|
|
||||||
}
|
|
||||||
|
|
||||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
|
||||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
|
||||||
|
|
||||||
const embed = MultidropEmbedHelper.GenerateMultidropEmbed(randomCard, quantityClaimed, imageFileName, cardsRemaining, undefined, user.Currency);
|
|
||||||
|
|
||||||
const row = MultidropEmbedHelper.GenerateMultidropButtons(randomCard, cardsRemaining, interaction.user.id);
|
|
||||||
|
|
||||||
await interaction.editReply({
|
|
||||||
embeds: [ embed ],
|
|
||||||
files: files,
|
|
||||||
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})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CacheType, CommandInteraction, EmbedBuilder, SlashCommandBuilder } from "discord.js";
|
|
||||||
import { Command } from "../type/command";
|
|
||||||
import Inventory from "../database/entities/app/Inventory";
|
|
||||||
import { CardRarityToString, GetSacrificeAmount } from "../constants/CardRarity";
|
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
|
||||||
import GetCardsHelper from "../helpers/DropHelpers/GetCardsHelper";
|
|
||||||
|
|
||||||
export default class Sacrifice extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName("sacrifice")
|
|
||||||
.setDescription("Sacrifices a card for currency")
|
|
||||||
.addStringOption(x =>
|
|
||||||
x
|
|
||||||
.setName("cardnumber")
|
|
||||||
.setDescription("The card to sacrifice from your inventory")
|
|
||||||
.setRequired(true))
|
|
||||||
.addNumberOption(x =>
|
|
||||||
x
|
|
||||||
.setName("quantity")
|
|
||||||
.setDescription("The amount to sacrifice (default 1)"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction<CacheType>): Promise<void> {
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (!cardInInventory || cardInInventory.Quantity == 0) {
|
|
||||||
await interaction.reply("Unable to find card in your inventory.");
|
|
||||||
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 = GetCardsHelper.GetCardByCardNumber(cardnumber.value! as string);
|
|
||||||
|
|
||||||
if (!cardData) {
|
|
||||||
await interaction.reply("Unable to find card in the database.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cardValue = GetSacrificeAmount(cardData.card.type) * quantity;
|
|
||||||
const cardRarityString = CardRarityToString(cardData.card.type);
|
|
||||||
|
|
||||||
const description = [
|
|
||||||
`Card: ${cardData.card.name}`,
|
|
||||||
`Series: ${cardData.series.name}`,
|
|
||||||
`Rarity: ${cardRarityString}`,
|
|
||||||
`Quantity Owned: ${cardInInventory.Quantity}`,
|
|
||||||
`Quantity To Sacrifice: ${quantity}`,
|
|
||||||
`Sacrifice Amount: ${cardValue}`,
|
|
||||||
];
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle("Sacrifice")
|
|
||||||
.setDescription(description.join("\n"))
|
|
||||||
.setColor(EmbedColours.Error)
|
|
||||||
.setFooter({ text: `${interaction.user.username}` });
|
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
|
||||||
.addComponents([
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`sacrifice confirm ${interaction.user.id} ${cardnumber.value!} ${quantity}`)
|
|
||||||
.setLabel("Confirm")
|
|
||||||
.setStyle(ButtonStyle.Success),
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`sacrifice cancel ${interaction.user.id} ${cardnumber.value!} ${quantity}`)
|
|
||||||
.setLabel("Cancel")
|
|
||||||
.setStyle(ButtonStyle.Secondary),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await interaction.reply({
|
|
||||||
embeds: [ embed ],
|
|
||||||
components: [ row ],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -47,8 +47,6 @@ export default class Series extends Command {
|
||||||
|
|
||||||
AppLogger.LogSilly("Commands/Series/View", `Parameters: id=${id?.value}`);
|
AppLogger.LogSilly("Commands/Series/View", `Parameters: id=${id?.value}`);
|
||||||
|
|
||||||
await interaction.deferReply();
|
|
||||||
|
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
const series = CoreClient.Cards.find(x => x.id == id.value);
|
const series = CoreClient.Cards.find(x => x.id == id.value);
|
||||||
|
@ -56,22 +54,13 @@ export default class Series extends Command {
|
||||||
if (!series) {
|
if (!series) {
|
||||||
AppLogger.LogVerbose("Commands/Series/View", "Series not found.");
|
AppLogger.LogVerbose("Commands/Series/View", "Series not found.");
|
||||||
|
|
||||||
await interaction.followUp("Series not found.");
|
await interaction.reply("Series not found.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const embed = SeriesHelper.GenerateSeriesViewPage(series.id, 0);
|
||||||
const embed = await SeriesHelper.GenerateSeriesViewPage(series.id, 0, interaction.user.id);
|
|
||||||
|
|
||||||
await interaction.followUp({
|
await interaction.reply({ embeds: [ embed!.embed ], components: [ embed!.row ]});
|
||||||
embeds: [ embed!.embed ],
|
|
||||||
components: [ embed!.row ],
|
|
||||||
files: [ embed!.image ],
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
await interaction.followUp("An error has occured generating the series grid.");
|
|
||||||
AppLogger.CatchError("Series", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ListSeries(interaction: CommandInteraction) {
|
private async ListSeries(interaction: CommandInteraction) {
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import { AttachmentBuilder, CacheType, CommandInteraction, SlashCommandBuilder } from "discord.js";
|
import { AttachmentBuilder, CacheType, CommandInteraction, DiscordAPIError, SlashCommandBuilder } from "discord.js";
|
||||||
import { Command } from "../../type/command";
|
import { Command } from "../../type/command";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import Inventory from "../../database/entities/app/Inventory";
|
import Inventory from "../../database/entities/app/Inventory";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
import { CoreClient } from "../../client/client";
|
import { CoreClient } from "../../client/client";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import DropEmbedHelper from "../../helpers/DropHelpers/DropEmbedHelper";
|
import CardDropHelperMetadata from "../../helpers/CardDropHelperMetadata";
|
||||||
import AppLogger from "../../client/appLogger";
|
|
||||||
|
|
||||||
export default class Dropnumber extends Command {
|
export default class Dropnumber extends Command {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -41,40 +40,48 @@ export default class Dropnumber extends Command {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const claimId = v4();
|
|
||||||
await interaction.deferReply();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const files = [];
|
|
||||||
let imageFileName = "";
|
|
||||||
|
|
||||||
if (!(card.path.startsWith("http://") || card.path.startsWith("https://"))) {
|
|
||||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.path));
|
|
||||||
imageFileName = card.path.split("/").pop()!;
|
|
||||||
|
|
||||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
|
||||||
|
|
||||||
files.push(attachment);
|
|
||||||
}
|
|
||||||
|
|
||||||
const series = CoreClient.Cards
|
const series = CoreClient.Cards
|
||||||
.find(x => x.cards.includes(card))!;
|
.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 {
|
||||||
|
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 inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.id);
|
||||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
||||||
|
|
||||||
const embed = DropEmbedHelper.GenerateDropEmbed({ card, series }, quantityClaimed, imageFileName);
|
const embed = CardDropHelperMetadata.GenerateDropEmbed({ card, series }, quantityClaimed, imageFileName);
|
||||||
|
|
||||||
const row = DropEmbedHelper.GenerateDropButtons({ card, series }, claimId, interaction.user.id);
|
const claimId = v4();
|
||||||
|
|
||||||
await interaction.editReply({
|
const row = CardDropHelperMetadata.GenerateDropButtons({ card, series }, claimId, interaction.user.id);
|
||||||
embeds: [ embed ],
|
|
||||||
files: files,
|
try {
|
||||||
components: [ row ],
|
await interaction.editReply({
|
||||||
});
|
embeds: [ embed ],
|
||||||
|
files: [ attachment ],
|
||||||
|
components: [ row ],
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
AppLogger.CatchError("Dropnumber", e);
|
console.error(e);
|
||||||
await interaction.editReply("Unable to send next drop. Please try again, and report this if it keeps happening");
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CoreClient.ClaimId = claimId;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,13 +1,12 @@
|
||||||
import { AttachmentBuilder, CacheType, CommandInteraction, SlashCommandBuilder } from "discord.js";
|
import { AttachmentBuilder, CacheType, CommandInteraction, DiscordAPIError, SlashCommandBuilder } from "discord.js";
|
||||||
import { Command } from "../../type/command";
|
import { Command } from "../../type/command";
|
||||||
import { CardRarity, CardRarityChoices, CardRarityParse } from "../../constants/CardRarity";
|
import { CardRarity, CardRarityParse } from "../../constants/CardRarity";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import Inventory from "../../database/entities/app/Inventory";
|
import Inventory from "../../database/entities/app/Inventory";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
|
import { CoreClient } from "../../client/client";
|
||||||
|
import CardDropHelperMetadata from "../../helpers/CardDropHelperMetadata";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import GetCardsHelper from "../../helpers/DropHelpers/GetCardsHelper";
|
|
||||||
import DropEmbedHelper from "../../helpers/DropHelpers/DropEmbedHelper";
|
|
||||||
import AppLogger from "../../client/appLogger";
|
|
||||||
|
|
||||||
export default class Droprarity extends Command {
|
export default class Droprarity extends Command {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -20,8 +19,7 @@ export default class Droprarity extends Command {
|
||||||
x
|
x
|
||||||
.setName("rarity")
|
.setName("rarity")
|
||||||
.setDescription("The rarity you want to summon")
|
.setDescription("The rarity you want to summon")
|
||||||
.setRequired(true)
|
.setRequired(true));
|
||||||
.setChoices(CardRarityChoices));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction<CacheType>) {
|
public override async execute(interaction: CommandInteraction<CacheType>) {
|
||||||
|
@ -41,44 +39,52 @@ export default class Droprarity extends Command {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const card = GetCardsHelper.GetRandomCardByRarity(rarityType);
|
const card = await CardDropHelperMetadata.GetRandomCardByRarity(rarityType);
|
||||||
|
|
||||||
if (!card) {
|
if (!card) {
|
||||||
await interaction.reply("Card not found");
|
await interaction.reply("Card not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const claimId = v4();
|
let image: Buffer;
|
||||||
await interaction.deferReply();
|
const imageFileName = card.card.path.split("/").pop()!;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const files = [];
|
image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path));
|
||||||
let imageFileName = "";
|
} catch {
|
||||||
|
await interaction.reply(`Unable to fetch image for card ${card.card.id}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(card.card.path.startsWith("http://") || card.card.path.startsWith("https://"))) {
|
await interaction.deferReply();
|
||||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path));
|
|
||||||
imageFileName = card.card.path.split("/").pop()!;
|
|
||||||
|
|
||||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||||
|
|
||||||
files.push(attachment);
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.card.id);
|
||||||
}
|
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
||||||
|
|
||||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.card.id);
|
const embed = CardDropHelperMetadata.GenerateDropEmbed(card, quantityClaimed, imageFileName);
|
||||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
|
||||||
|
|
||||||
const embed = DropEmbedHelper.GenerateDropEmbed(card, quantityClaimed, imageFileName);
|
const claimId = v4();
|
||||||
|
|
||||||
const row = DropEmbedHelper.GenerateDropButtons(card, claimId, interaction.user.id);
|
const row = CardDropHelperMetadata.GenerateDropButtons(card, claimId, interaction.user.id);
|
||||||
|
|
||||||
|
try {
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
embeds: [ embed ],
|
embeds: [ embed ],
|
||||||
files: files,
|
files: [ attachment ],
|
||||||
components: [ row ],
|
components: [ row ],
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
AppLogger.CatchError("Droprarity", e);
|
console.error(e);
|
||||||
await interaction.editReply("Unable to send next drop. Please try again, and report this if it keeps happening");
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CoreClient.ClaimId = claimId;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,51 +0,0 @@
|
||||||
import {CommandInteraction, EmbedBuilder, PermissionsBitField, SlashCommandBuilder} from "discord.js";
|
|
||||||
import {Command} from "../type/command";
|
|
||||||
import {CoreClient} from "../client/client";
|
|
||||||
import {CardRarity} from "../constants/CardRarity";
|
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
|
||||||
|
|
||||||
export default class Stats extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName("stats")
|
|
||||||
.setDescription("Get bot stats such as card info")
|
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
const allCards = CoreClient.Cards.flatMap(x => x.cards);
|
|
||||||
|
|
||||||
const totalCards = allCards.length;
|
|
||||||
const bronzeCards = allCards.filter(x => x.type == CardRarity.Bronze)
|
|
||||||
.length;
|
|
||||||
const silverCards = allCards.filter(x => x.type == CardRarity.Silver)
|
|
||||||
.length;
|
|
||||||
const goldCards = allCards.filter(x => x.type == CardRarity.Gold)
|
|
||||||
.length;
|
|
||||||
const mangaCards = allCards.filter(x => x.type == CardRarity.Manga)
|
|
||||||
.length;
|
|
||||||
const legendaryCards = allCards.filter(x => x.type == CardRarity.Legendary)
|
|
||||||
.length;
|
|
||||||
|
|
||||||
const description = [
|
|
||||||
`${totalCards} Total`,
|
|
||||||
`${bronzeCards} Bronze`,
|
|
||||||
`${silverCards} Silver`,
|
|
||||||
`${goldCards} Gold`,
|
|
||||||
`${mangaCards} Manga`,
|
|
||||||
`${legendaryCards} Legendary`,
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle("Stats")
|
|
||||||
.setDescription(description)
|
|
||||||
.setColor(EmbedColours.Ok);
|
|
||||||
|
|
||||||
await interaction.reply({
|
|
||||||
embeds: [ embed ],
|
|
||||||
ephemeral: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -26,56 +26,38 @@ 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.getUser("user")!;
|
||||||
const give = interaction.options.get("give", true);
|
const give = interaction.options.get("give")!;
|
||||||
const receive = interaction.options.get("receive", true);
|
const receive = interaction.options.get("receive")!;
|
||||||
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}`);
|
||||||
|
|
||||||
if (interaction.user.id == user.id) {
|
const giveItemEntity = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, give.value!.toString());
|
||||||
await interaction.reply("You can not create a trade with yourself.");
|
const receiveItemEntity = await Inventory.FetchOneByCardNumberAndUserId(user.id, receive.value!.toString());
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user1ItemEntity = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, give.value!.toString());
|
if (!giveItemEntity) {
|
||||||
const user2ItemEntity = await Inventory.FetchOneByCardNumberAndUserId(user.id, receive.value!.toString());
|
|
||||||
|
|
||||||
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 || user2ItemEntity.Quantity < receivequantity) {
|
if (!receiveItemEntity) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user1Item = CoreClient.Cards
|
const giveItem = CoreClient.Cards
|
||||||
.flatMap(x => x.cards)
|
.flatMap(x => x.cards)
|
||||||
.find(x => x.id === give.value!.toString());
|
.find(x => x.id === give.value!.toString());
|
||||||
|
|
||||||
const user2Item = CoreClient.Cards
|
const receiveItem = CoreClient.Cards
|
||||||
.flatMap(x => x.cards)
|
.flatMap(x => x.cards)
|
||||||
.find(x => x.id === receive.value!.toString());
|
.find(x => x.id === receive.value!.toString());
|
||||||
|
|
||||||
if (!user1Item || !user2Item) {
|
if (!giveItem || !receiveItem) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
@ -90,13 +72,13 @@ export default class Trade extends Command {
|
||||||
.setImage("https://media1.tenor.com/m/KkZwKl2AQ2QAAAAd/trade-offer.gif")
|
.setImage("https://media1.tenor.com/m/KkZwKl2AQ2QAAAAd/trade-offer.gif")
|
||||||
.addFields([
|
.addFields([
|
||||||
{
|
{
|
||||||
name: `${interaction.user.username} Receives`,
|
name: "I Receive",
|
||||||
value: `${user2Item.id}: ${user2Item.name} x${receivequantity}`,
|
value: `${receiveItem.id}: ${receiveItem.name}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `${user.username} Receives`,
|
name: "You Receive",
|
||||||
value: `${user1Item.id}: ${user1Item.name} x${givequantity}`,
|
value: `${giveItem.id}: ${giveItem.name}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -105,16 +87,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, givequantity, receivequantity), 1000 * 60 * 15); // 15 minutes
|
const timeoutId = setTimeout(async () => this.autoDecline(interaction, interaction.user.username, user.username, giveItem.id, receiveItem.id, giveItem.name, receiveItem.name), 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} ${givequantity} ${receivequantity}`)
|
.setCustomId(`trade accept ${interaction.user.id} ${user.id} ${giveItem.id} ${receiveItem.id} ${expiry} ${timeoutId}`)
|
||||||
.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} ${givequantity} ${receivequantity}`)
|
.setCustomId(`trade decline ${interaction.user.id} ${user.id} ${giveItem.id} ${receiveItem.id} ${expiry} ${timeoutId}`)
|
||||||
.setLabel("Decline")
|
.setLabel("Decline")
|
||||||
.setStyle(ButtonStyle.Danger),
|
.setStyle(ButtonStyle.Danger),
|
||||||
]);
|
]);
|
||||||
|
@ -122,23 +104,23 @@ 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, user1Quantity: number, user2Quantity: number) {
|
private async autoDecline(interaction: CommandInteraction, giveUsername: string, receiveUsername: string, giveCardNumber: string, receiveCardNumber: string, giveCardName: string, receiveCardName: string) {
|
||||||
AppLogger.LogSilly("Commands/Trade/AutoDecline", `Auto declining trade between ${user1Username} and ${user2Username}`);
|
AppLogger.LogSilly("Commands/Trade/AutoDecline", `Auto declining trade between ${giveUsername} and ${receiveUsername}`);
|
||||||
|
|
||||||
const tradeEmbed = new EmbedBuilder()
|
const tradeEmbed = new EmbedBuilder()
|
||||||
.setTitle("Trade Expired")
|
.setTitle("Trade Expired")
|
||||||
.setDescription(`Trade initiated between ${user1Username} and ${user2Username}`)
|
.setDescription(`Trade initiated between ${receiveUsername} and ${giveUsername}`)
|
||||||
.setColor(EmbedColours.Error)
|
.setColor(EmbedColours.Error)
|
||||||
.setImage("https://media1.tenor.com/m/KkZwKl2AQ2QAAAAd/trade-offer.gif")
|
.setImage("https://media1.tenor.com/m/KkZwKl2AQ2QAAAAd/trade-offer.gif")
|
||||||
.addFields([
|
.addFields([
|
||||||
{
|
{
|
||||||
name: `${user1Username} Receives`,
|
name: "I Receive",
|
||||||
value: `${user2CardNumber}: ${user2CardName} x${user2Quantity}`,
|
value: `${receiveCardNumber}: ${receiveCardName}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `${user2Username} Receives`,
|
name: "You Receive",
|
||||||
value: `${user1CardNumber}: ${user1CardName} x${user1Quantity}`,
|
value: `${giveCardNumber}: ${giveCardName}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
|
import { AttachmentBuilder, CommandInteraction, DiscordAPIError, 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() {
|
||||||
|
@ -9,32 +13,70 @@ export default class View extends Command {
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
this.CommandBuilder = new SlashCommandBuilder()
|
||||||
.setName("view")
|
.setName("view")
|
||||||
.setDescription("Search for a card by its name")
|
.setDescription("View a specific command")
|
||||||
.addStringOption(x =>
|
.addStringOption(x =>
|
||||||
x
|
x
|
||||||
.setName("name")
|
.setName("cardnumber")
|
||||||
.setDescription("The card name to search for")
|
.setDescription("The card number to view")
|
||||||
.setRequired(true));
|
.setRequired(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
public override async execute(interaction: CommandInteraction) {
|
||||||
const name = interaction.options.get("name", true);
|
const cardNumber = interaction.options.get("cardnumber");
|
||||||
|
|
||||||
AppLogger.LogSilly("Commands/View", `Parameters: name=${name.value}`);
|
AppLogger.LogSilly("Commands/View", `Parameters: cardNumber=${cardNumber?.value}`);
|
||||||
|
|
||||||
await interaction.deferReply();
|
if (!cardNumber || !cardNumber.value) {
|
||||||
|
await interaction.reply("Card number is required.");
|
||||||
const searchResult = await CardSearchHelper.GenerateSearchQuery(name.value!.toString(), interaction.user.id, 7);
|
|
||||||
|
|
||||||
if (!searchResult) {
|
|
||||||
await interaction.editReply("No results found");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await interaction.editReply({
|
const card = CoreClient.Cards
|
||||||
embeds: [ searchResult.embed ],
|
.flatMap(x => x.cards)
|
||||||
components: [ searchResult.row ],
|
.find(x => x.id == cardNumber.value);
|
||||||
files: searchResult.attachments,
|
|
||||||
});
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,13 +0,0 @@
|
||||||
export default class CardConstants {
|
|
||||||
public static readonly ClaimCost = 10;
|
|
||||||
public static readonly TimerGiveAmount = 10;
|
|
||||||
public static readonly DailyCurrency = 100;
|
|
||||||
public static readonly StartingCurrency = 300;
|
|
||||||
|
|
||||||
// Multidrop
|
|
||||||
public static readonly MultidropCost = this.ClaimCost * 10;
|
|
||||||
public static readonly MultidropQuantity = 11;
|
|
||||||
|
|
||||||
// Effects
|
|
||||||
public static readonly UnusedChanceUpChance = 0.5;
|
|
||||||
}
|
|
|
@ -9,29 +9,6 @@ export enum CardRarity {
|
||||||
Legendary,
|
Legendary,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CardRarityChoices = [
|
|
||||||
{
|
|
||||||
name: "Bronze",
|
|
||||||
value: "bronze",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Silver",
|
|
||||||
value: "silver",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Gold",
|
|
||||||
value: "gold",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Manga",
|
|
||||||
value: "manga",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Legendary",
|
|
||||||
value: "legendary",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export function CardRarityToString(rarity: CardRarity): string {
|
export function CardRarityToString(rarity: CardRarity): string {
|
||||||
switch (rarity) {
|
switch (rarity) {
|
||||||
case CardRarity.Unknown:
|
case CardRarity.Unknown:
|
||||||
|
@ -82,20 +59,3 @@ export function CardRarityParse(rarity: string): CardRarity {
|
||||||
return CardRarity.Unknown;
|
return CardRarity.Unknown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GetSacrificeAmount(rarity: CardRarity): number {
|
|
||||||
switch (rarity) {
|
|
||||||
case CardRarity.Bronze:
|
|
||||||
return 5;
|
|
||||||
case CardRarity.Silver:
|
|
||||||
return 10;
|
|
||||||
case CardRarity.Gold:
|
|
||||||
return 30;
|
|
||||||
case CardRarity.Manga:
|
|
||||||
return 40;
|
|
||||||
case CardRarity.Legendary:
|
|
||||||
return 100;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
class EffectDetail {
|
|
||||||
public readonly id: string;
|
|
||||||
public readonly friendlyName: string;
|
|
||||||
public readonly duration: number;
|
|
||||||
public readonly cost: number;
|
|
||||||
public readonly cooldown: number;
|
|
||||||
|
|
||||||
constructor(id: string, friendlyName: string, duration: number, cost: number, cooldown: number) {
|
|
||||||
this.id = id;
|
|
||||||
this.friendlyName = friendlyName;
|
|
||||||
this.duration = duration;
|
|
||||||
this.cost = cost;
|
|
||||||
this.cooldown = cooldown;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EffectDetails = new Map<string, EffectDetail>([
|
|
||||||
[ "unclaimed", new EffectDetail("unclaimed", "Unclaimed Chance Up", 10 * 60 * 1000, 100, 3 * 60 * 60 * 1000) ],
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const EffectChoices = [
|
|
||||||
{ name: "Unclaimed Chance Up", value: "unclaimed" },
|
|
||||||
];
|
|
|
@ -1,14 +1,8 @@
|
||||||
export default class EmbedColours {
|
export default class EmbedColours {
|
||||||
// General
|
|
||||||
public static readonly Ok = 0x3050ba;
|
public static readonly Ok = 0x3050ba;
|
||||||
public static readonly Success = 0x50c878;
|
public static readonly Success = 0x50c878;
|
||||||
public static readonly Error = 0xff0000;
|
public static readonly Error = 0xff0000;
|
||||||
|
|
||||||
// Colours
|
|
||||||
public static readonly Grey = 0xd3d3d3;
|
public static readonly Grey = 0xd3d3d3;
|
||||||
public static readonly Green = 0x228B22;
|
|
||||||
|
|
||||||
// Card Types
|
|
||||||
public static readonly BronzeCard = 0xcd7f32;
|
public static readonly BronzeCard = 0xcd7f32;
|
||||||
public static readonly SilverCard = 0xc0c0c0;
|
public static readonly SilverCard = 0xc0c0c0;
|
||||||
public static readonly GoldCard = 0xffd700;
|
public static readonly GoldCard = 0xffd700;
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
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}!`;
|
|
||||||
}
|
|
|
@ -27,24 +27,12 @@ export default class AppBaseEntity {
|
||||||
await repository.save(entity);
|
await repository.save(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async SaveAll<T extends AppBaseEntity>(target: EntityTarget<T>, entities: DeepPartial<T>[]): Promise<void> {
|
|
||||||
const repository = AppDataSource.getRepository<T>(target);
|
|
||||||
|
|
||||||
await repository.save(entities);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Remove<T extends AppBaseEntity>(target: EntityTarget<T>, entity: T): Promise<void> {
|
public static async Remove<T extends AppBaseEntity>(target: EntityTarget<T>, entity: T): Promise<void> {
|
||||||
const repository = AppDataSource.getRepository<T>(target);
|
const repository = AppDataSource.getRepository<T>(target);
|
||||||
|
|
||||||
await repository.remove(entity);
|
await repository.remove(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async RemoveMany<T extends AppBaseEntity>(target: EntityTarget<T>, entity: T[]): Promise<void> {
|
|
||||||
const repository = AppDataSource.getRepository<T>(target);
|
|
||||||
|
|
||||||
await repository.remove(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async FetchAll<T extends AppBaseEntity>(target: EntityTarget<T>, relations?: string[]): Promise<T[]> {
|
public static async FetchAll<T extends AppBaseEntity>(target: EntityTarget<T>, relations?: string[]): Promise<T[]> {
|
||||||
const repository = AppDataSource.getRepository<T>(target);
|
const repository = AppDataSource.getRepository<T>(target);
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,6 @@ export interface CardMetadata {
|
||||||
name: string,
|
name: string,
|
||||||
type: CardRarity,
|
type: CardRarity,
|
||||||
path: string,
|
path: string,
|
||||||
subseries?: string,
|
|
||||||
colour?: string,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DropResult {
|
export interface DropResult {
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
import {Environment} from "../constants/Environment";
|
|
||||||
import {StringDropdownEvent} from "../type/stringDropdownEvent";
|
|
||||||
|
|
||||||
interface StringDropdownEventItem {
|
|
||||||
DropdownId: string,
|
|
||||||
Event: StringDropdownEvent,
|
|
||||||
Environment: Environment,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default StringDropdownEventItem;
|
|
|
@ -29,16 +29,6 @@ export default class Inventory extends AppBaseEntity {
|
||||||
this.Quantity = quantity;
|
this.Quantity = quantity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RemoveQuantity(amount: number) {
|
|
||||||
if (this.Quantity < amount) return;
|
|
||||||
|
|
||||||
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,22 +13,7 @@ export default class User extends AppBaseEntity {
|
||||||
@Column()
|
@Column()
|
||||||
Currency: number;
|
Currency: number;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
public UpdateCurrency(currency: number) {
|
||||||
LastUsedDaily?: Date;
|
this.Currency = currency;
|
||||||
|
|
||||||
public AddCurrency(amount: number) {
|
|
||||||
this.Currency += amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RemoveCurrency(amount: number): boolean {
|
|
||||||
if (this.Currency < amount) return false;
|
|
||||||
|
|
||||||
this.Currency -= amount;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UpdateLastUsedDaily(lastUsedDaily: Date) {
|
|
||||||
this.LastUsedDaily = lastUsedDaily;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,86 +0,0 @@
|
||||||
import {Column, Entity} from "typeorm";
|
|
||||||
import AppBaseEntity from "../../../contracts/AppBaseEntity";
|
|
||||||
import AppDataSource from "../../dataSources/appDataSource";
|
|
||||||
|
|
||||||
@Entity()
|
|
||||||
export default class UserEffect extends AppBaseEntity {
|
|
||||||
constructor(name: string, userId: string, unused: number, WhenExpires?: Date) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.Name = name;
|
|
||||||
this.UserId = userId;
|
|
||||||
this.Unused = unused;
|
|
||||||
this.WhenExpires = WhenExpires;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
Name: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
UserId: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
Unused: number;
|
|
||||||
|
|
||||||
@Column({ nullable: true })
|
|
||||||
WhenExpires?: Date;
|
|
||||||
|
|
||||||
public AddUnused(amount: number) {
|
|
||||||
this.Unused += amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UseEffect(whenExpires: Date): boolean {
|
|
||||||
if (this.Unused == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Unused -= 1;
|
|
||||||
this.WhenExpires = whenExpires;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IsEffectActive(): boolean {
|
|
||||||
const now = new Date();
|
|
||||||
|
|
||||||
if (this.WhenExpires && now < this.WhenExpires) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async FetchOneByUserIdAndName(userId: string, name: string): Promise<UserEffect | null> {
|
|
||||||
const repository = AppDataSource.getRepository(UserEffect);
|
|
||||||
|
|
||||||
const single = await repository.findOne({ where: { UserId: userId, Name: name } });
|
|
||||||
|
|
||||||
return single;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async FetchAllByUserIdPaginated(userId: string, page: number = 0, itemsPerPage: number = 10): Promise<[UserEffect[], number]> {
|
|
||||||
const repository = AppDataSource.getRepository(UserEffect);
|
|
||||||
|
|
||||||
const query = await repository.createQueryBuilder("effect")
|
|
||||||
.where("effect.UserId = :userId", { userId })
|
|
||||||
.andWhere("effect.Unused > 0")
|
|
||||||
.orderBy("effect.Name", "ASC")
|
|
||||||
.skip(page * itemsPerPage)
|
|
||||||
.take(itemsPerPage)
|
|
||||||
.getManyAndCount();
|
|
||||||
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async FetchActiveEffectByUserId(userId: string): Promise<UserEffect | null> {
|
|
||||||
const repository = AppDataSource.getRepository(UserEffect);
|
|
||||||
|
|
||||||
const query = await repository.createQueryBuilder("effect")
|
|
||||||
.where("effect.UserId = :userId", { userId })
|
|
||||||
.andWhere("effect.WhenExpires IS NOT NULL")
|
|
||||||
.andWhere("effect.WhenExpires > :now", { now: new Date() })
|
|
||||||
.getOne();
|
|
||||||
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
||||||
import MigrationHelper from "../../../../helpers/MigrationHelper";
|
|
||||||
|
|
||||||
export class Daily1715967355818 implements MigrationInterface {
|
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
MigrationHelper.Up("1715967355818-daily", "0.6", [
|
|
||||||
"01-table/User",
|
|
||||||
], queryRunner);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(): Promise<void> {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
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,23 +1,11 @@
|
||||||
import AppLogger from "../../client/appLogger";
|
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
||||||
import { CoreClient } from "../../client/client";
|
import { CardRarity, CardRarityToColour, CardRarityToString } from "../constants/CardRarity";
|
||||||
import CardConstants from "../../constants/CardConstants";
|
import CardRarityChances from "../constants/CardRarityChances";
|
||||||
import { CardRarity } from "../../constants/CardRarity";
|
import { DropResult } from "../contracts/SeriesMetadata";
|
||||||
import CardRarityChances from "../../constants/CardRarityChances";
|
import { CoreClient } from "../client/client";
|
||||||
import { DropResult } from "../../contracts/SeriesMetadata";
|
import AppLogger from "../client/appLogger";
|
||||||
import EffectHelper from "../EffectHelper";
|
|
||||||
import GetUnclaimedCardsHelper from "./GetUnclaimedCardsHelper";
|
|
||||||
|
|
||||||
export default class GetCardsHelper {
|
|
||||||
public static async FetchCard(userId: string): Promise<DropResult | undefined> {
|
|
||||||
const hasChanceUpEffect = await EffectHelper.HasEffect(userId, "unclaimed");
|
|
||||||
|
|
||||||
if (hasChanceUpEffect && Math.random() <= CardConstants.UnusedChanceUpChance) {
|
|
||||||
return await GetUnclaimedCardsHelper.GetRandomCardUnclaimed(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.GetRandomCard();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export default class CardDropHelperMetadata {
|
||||||
public static GetRandomCard(): DropResult | undefined {
|
public static GetRandomCard(): DropResult | undefined {
|
||||||
const randomRarity = Math.random() * 100;
|
const randomRarity = Math.random() * 100;
|
||||||
|
|
||||||
|
@ -55,7 +43,7 @@ export default class GetCardsHelper {
|
||||||
.find(x => x.cards.includes(card));
|
.find(x => x.cards.includes(card));
|
||||||
|
|
||||||
if (!series) {
|
if (!series) {
|
||||||
AppLogger.LogError("CardDropHelperMetadata/GetRandomCardByRarity", `Series not found for card ${card.id}`);
|
AppLogger.LogWarn("CardDropHelperMetadata/GetRandomCardByRarity", `Series not found for card ${card.id}`);
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -88,4 +76,41 @@ export default class GetCardsHelper {
|
||||||
|
|
||||||
return { card, series };
|
return { card, series };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static GenerateDropEmbed(drop: DropResult, quantityClaimed: number, imageFileName: string, claimedBy?: string): EmbedBuilder {
|
||||||
|
AppLogger.LogSilly("CardDropHelperMetadata/GenerateDropEmbed", `Parameters: drop=${drop.card.id}, quantityClaimed=${quantityClaimed}, imageFileName=${imageFileName}`);
|
||||||
|
|
||||||
|
let description = "";
|
||||||
|
description += `Series: ${drop.series.name}\n`;
|
||||||
|
description += `Claimed: ${quantityClaimed}\n`;
|
||||||
|
|
||||||
|
if (claimedBy != null) {
|
||||||
|
description += `Claimed by: ${claimedBy}\n`;
|
||||||
|
} else {
|
||||||
|
description += "Claimed by: (UNCLAIMED)\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EmbedBuilder()
|
||||||
|
.setTitle(drop.card.name)
|
||||||
|
.setDescription(description)
|
||||||
|
.setFooter({ text: `${CardRarityToString(drop.card.type)} · ${drop.card.id}` })
|
||||||
|
.setColor(CardRarityToColour(drop.card.type))
|
||||||
|
.setImage(`attachment://${imageFileName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GenerateDropButtons(drop: DropResult, claimId: string, userId: string, disabled: boolean = false): ActionRowBuilder<ButtonBuilder> {
|
||||||
|
AppLogger.LogSilly("CardDropHelperMetadata/GenerateDropButtons", `Parameters: drop=${drop.card.id}, claimId=${claimId}, userId=${userId}`);
|
||||||
|
|
||||||
|
return new ActionRowBuilder<ButtonBuilder>()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`claim ${drop.card.id} ${claimId} ${userId}`)
|
||||||
|
.setLabel("Claim")
|
||||||
|
.setStyle(ButtonStyle.Primary)
|
||||||
|
.setDisabled(disabled),
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId("reroll")
|
||||||
|
.setLabel("Reroll")
|
||||||
|
.setStyle(ButtonStyle.Secondary));
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,116 +0,0 @@
|
||||||
import {ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder} from "discord.js";
|
|
||||||
import Fuse from "fuse.js";
|
|
||||||
import {CoreClient} from "../client/client.js";
|
|
||||||
import Inventory from "../database/entities/app/Inventory.js";
|
|
||||||
import {readFileSync} from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import AppLogger from "../client/appLogger.js";
|
|
||||||
import GetCardsHelper from "./DropHelpers/GetCardsHelper.js";
|
|
||||||
import DropEmbedHelper from "./DropHelpers/DropEmbedHelper.js";
|
|
||||||
|
|
||||||
interface ReturnedPage {
|
|
||||||
embed: EmbedBuilder,
|
|
||||||
row: ActionRowBuilder<ButtonBuilder>,
|
|
||||||
attachments: AttachmentBuilder[],
|
|
||||||
results: string[],
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class CardSearchHelper {
|
|
||||||
public static async GenerateSearchQuery(query: string, userid: string, pages: number): Promise<ReturnedPage | undefined> {
|
|
||||||
AppLogger.LogSilly("CardSearchHelper/GenerateSearchQuery", `Parameters: query=${query}, userid=${userid}, pages=${pages}`);
|
|
||||||
|
|
||||||
const fzf = new Fuse(CoreClient.Cards.flatMap(x => x.cards), { keys: ["name"] });
|
|
||||||
const entries = fzf.search(query)
|
|
||||||
.splice(0, pages);
|
|
||||||
|
|
||||||
const entry = entries[0];
|
|
||||||
const results = entries
|
|
||||||
.flatMap(x => x.item.id);
|
|
||||||
|
|
||||||
if (!entry) {
|
|
||||||
AppLogger.LogVerbose("CardSearchHelper/GenerateSearchQuery", `Unable to find entry: ${query}`);
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const card = GetCardsHelper.GetCardByCardNumber(entry.item.id);
|
|
||||||
|
|
||||||
if (!card) return undefined;
|
|
||||||
|
|
||||||
const attachments = [];
|
|
||||||
let imageFileName = "";
|
|
||||||
|
|
||||||
if (!(card.card.path.startsWith("http://") || card.card.path.startsWith("https://"))) {
|
|
||||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path));
|
|
||||||
imageFileName = card.card.path.split("/").pop()!;
|
|
||||||
|
|
||||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
|
||||||
|
|
||||||
attachments.push(attachment);
|
|
||||||
}
|
|
||||||
|
|
||||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(userid, card.card.id);
|
|
||||||
const quantityClaimed = inventory?.Quantity ?? 0;
|
|
||||||
|
|
||||||
const embed = DropEmbedHelper.GenerateDropEmbed(card, quantityClaimed, imageFileName);
|
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
|
||||||
.addComponents(
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`view 0 ${results.join(" ")}`)
|
|
||||||
.setLabel("Previous")
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setDisabled(true),
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`view 2 ${results.join(" ")}`)
|
|
||||||
.setLabel("Next")
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setDisabled(pages == 1));
|
|
||||||
|
|
||||||
return { embed, row, attachments, results };
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async GenerateSearchPageFromQuery(results: string[], userid: string, page: number): Promise<ReturnedPage | undefined> {
|
|
||||||
const currentPageId = results[page - 1];
|
|
||||||
|
|
||||||
const card = GetCardsHelper.GetCardByCardNumber(currentPageId);
|
|
||||||
|
|
||||||
if (!card) {
|
|
||||||
AppLogger.LogError("CardSearchHelper/GenerateSearchPageFromQuery", `Unable to find card by id: ${currentPageId}.`);
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const attachments = [];
|
|
||||||
let imageFileName = "";
|
|
||||||
|
|
||||||
if (!(card.card.path.startsWith("http://") || card.card.path.startsWith("https://"))) {
|
|
||||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path));
|
|
||||||
imageFileName = card.card.path.split("/").pop()!;
|
|
||||||
|
|
||||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
|
||||||
|
|
||||||
attachments.push(attachment);
|
|
||||||
}
|
|
||||||
|
|
||||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(userid, card.card.id);
|
|
||||||
const quantityClaimed = inventory?.Quantity ?? 0;
|
|
||||||
|
|
||||||
const embed = DropEmbedHelper.GenerateDropEmbed(card, quantityClaimed, imageFileName);
|
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
|
||||||
.addComponents(
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`view ${page - 1} ${results.join(" ")}`)
|
|
||||||
.setLabel("Previous")
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setDisabled(page - 1 == 0),
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`view ${page + 1} ${results.join(" ")}`)
|
|
||||||
.setLabel("Next")
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setDisabled(page == results.length));
|
|
||||||
|
|
||||||
return { embed, row, attachments, results };
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
|
||||||
import { DropResult } from "../../contracts/SeriesMetadata";
|
|
||||||
import AppLogger from "../../client/appLogger";
|
|
||||||
import { CardRarityToColour, CardRarityToString } from "../../constants/CardRarity";
|
|
||||||
import StringTools from "../StringTools";
|
|
||||||
|
|
||||||
export default class DropEmbedHelper {
|
|
||||||
public static GenerateDropEmbed(drop: DropResult, quantityClaimed: number, imageFileName: string, claimedBy?: string, currency?: number): EmbedBuilder {
|
|
||||||
AppLogger.LogSilly("CardDropHelperMetadata/GenerateDropEmbed", `Parameters: drop=${drop.card.id}, quantityClaimed=${quantityClaimed}, imageFileName=${imageFileName}`);
|
|
||||||
|
|
||||||
const description = drop.card.subseries ?? drop.series.name;
|
|
||||||
let colour = CardRarityToColour(drop.card.type);
|
|
||||||
|
|
||||||
if (drop.card.colour && StringTools.IsHexCode(drop.card.colour)) {
|
|
||||||
const hexCode = Number("0x" + drop.card.colour);
|
|
||||||
|
|
||||||
if (hexCode) {
|
|
||||||
colour = hexCode;
|
|
||||||
} else {
|
|
||||||
AppLogger.LogWarn("CardDropHelperMetadata/GenerateDropEmbed", `Card's colour override is invalid: ${drop.card.id}, ${drop.card.colour}`);
|
|
||||||
}
|
|
||||||
} else if (drop.card.colour) {
|
|
||||||
AppLogger.LogWarn("CardDropHelperMetadata/GenerateDropEmbed", `Card's colour override is invalid: ${drop.card.id}, ${drop.card.colour}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
let imageUrl = `attachment://${imageFileName}`;
|
|
||||||
|
|
||||||
if (drop.card.path.startsWith("http://") || drop.card.path.startsWith("https://")) {
|
|
||||||
imageUrl = drop.card.path;
|
|
||||||
}
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle(drop.card.name)
|
|
||||||
.setDescription(description)
|
|
||||||
.setFooter({ text: `${CardRarityToString(drop.card.type)} · ${drop.card.id}` })
|
|
||||||
.setColor(colour)
|
|
||||||
.setImage(imageUrl)
|
|
||||||
.addFields([
|
|
||||||
{
|
|
||||||
name: "Claimed",
|
|
||||||
value: `${quantityClaimed}`,
|
|
||||||
inline: true,
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (claimedBy != null) {
|
|
||||||
embed.addFields([
|
|
||||||
{
|
|
||||||
name: "Claimed by",
|
|
||||||
value: claimedBy,
|
|
||||||
inline: true,
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currency != null) {
|
|
||||||
embed.addFields([
|
|
||||||
{
|
|
||||||
name: "Currency",
|
|
||||||
value: `${currency}`,
|
|
||||||
inline: true,
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return embed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GenerateDropButtons(drop: DropResult, claimId: string, userId: string, disabled: boolean = false): ActionRowBuilder<ButtonBuilder> {
|
|
||||||
AppLogger.LogSilly("CardDropHelperMetadata/GenerateDropButtons", `Parameters: drop=${drop.card.id}, claimId=${claimId}, userId=${userId}`);
|
|
||||||
|
|
||||||
return new ActionRowBuilder<ButtonBuilder>()
|
|
||||||
.addComponents(
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`claim ${drop.card.id} ${claimId} ${userId}`)
|
|
||||||
.setLabel("Claim")
|
|
||||||
.setStyle(ButtonStyle.Success)
|
|
||||||
.setDisabled(disabled),
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`sacrifice give ${userId} ${drop.card.id} 1`)
|
|
||||||
.setLabel(`Sacrifice`)
|
|
||||||
.setStyle(ButtonStyle.Danger),
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId("reroll")
|
|
||||||
.setEmoji("🔁")
|
|
||||||
.setStyle(ButtonStyle.Primary),);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
import AppLogger from "../../client/appLogger";
|
|
||||||
import { CoreClient } from "../../client/client";
|
|
||||||
import { CardRarity } from "../../constants/CardRarity";
|
|
||||||
import CardRarityChances from "../../constants/CardRarityChances";
|
|
||||||
import { DropResult } from "../../contracts/SeriesMetadata";
|
|
||||||
import Inventory from "../../database/entities/app/Inventory";
|
|
||||||
import GetCardsHelper from "./GetCardsHelper";
|
|
||||||
|
|
||||||
export default class GetUnclaimedCardsHelper {
|
|
||||||
public static async GetRandomCardUnclaimed(userId: string): Promise<DropResult | undefined> {
|
|
||||||
const randomRarity = Math.random() * 100;
|
|
||||||
|
|
||||||
let cardRarity: CardRarity;
|
|
||||||
|
|
||||||
const bronzeChance = CardRarityChances.Bronze;
|
|
||||||
const silverChance = bronzeChance + CardRarityChances.Silver;
|
|
||||||
const goldChance = silverChance + CardRarityChances.Gold;
|
|
||||||
const mangaChance = goldChance + CardRarityChances.Manga;
|
|
||||||
|
|
||||||
if (randomRarity < bronzeChance) cardRarity = CardRarity.Bronze;
|
|
||||||
else if (randomRarity < silverChance) cardRarity = CardRarity.Silver;
|
|
||||||
else if (randomRarity < goldChance) cardRarity = CardRarity.Gold;
|
|
||||||
else if (randomRarity < mangaChance) cardRarity = CardRarity.Manga;
|
|
||||||
else cardRarity = CardRarity.Legendary;
|
|
||||||
|
|
||||||
const randomCard = await this.GetRandomCardByRarityUnclaimed(cardRarity, userId);
|
|
||||||
|
|
||||||
return randomCard;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async GetRandomCardByRarityUnclaimed(rarity: CardRarity, userId: string): Promise<DropResult | undefined> {
|
|
||||||
const claimedCards = await Inventory.FetchAllByUserId(userId);
|
|
||||||
|
|
||||||
if (!claimedCards || claimedCards.length == 0) {
|
|
||||||
// They don't have any cards, so safe to get any random card
|
|
||||||
return GetCardsHelper.GetRandomCardByRarity(rarity);
|
|
||||||
}
|
|
||||||
|
|
||||||
const allCards = CoreClient.Cards
|
|
||||||
.flatMap(x => x.cards)
|
|
||||||
.filter(x => x.type == rarity)
|
|
||||||
.filter(x => !claimedCards.find(y => y.CardNumber == x.id && y.Quantity > 0));
|
|
||||||
|
|
||||||
if (!allCards || allCards.length == 0) {
|
|
||||||
// There is no card left unclaimed, fallback to any card
|
|
||||||
return GetCardsHelper.GetRandomCardByRarity(rarity);
|
|
||||||
};
|
|
||||||
|
|
||||||
const randomCardIndex = Math.floor(Math.random() * allCards.length);
|
|
||||||
|
|
||||||
const card = allCards[randomCardIndex];
|
|
||||||
|
|
||||||
if (!card) {
|
|
||||||
AppLogger.LogError("CardDropHelperMetadata/GetRandomCardByRarityUnclaimed", `Card not found in index, ${randomCardIndex} of ${allCards.length}, User Id: ${userId}, rarity: ${rarity}`);
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const series = CoreClient.Cards
|
|
||||||
.find(x => x.cards.includes(card));
|
|
||||||
|
|
||||||
if (!series) {
|
|
||||||
AppLogger.LogError("CardDropHelperMetadata/GetRandomCardByRarityUnclaimed", `Series not found for card ${card.id}, User Id: ${userId}, rarity: ${rarity}`);
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
series: series,
|
|
||||||
card: card,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
|
||||||
import { DropResult } from "../../contracts/SeriesMetadata";
|
|
||||||
import { GetSacrificeAmount } from "../../constants/CardRarity";
|
|
||||||
import DropEmbedHelper from "./DropEmbedHelper";
|
|
||||||
|
|
||||||
export default class MultidropEmbedHelper {
|
|
||||||
public static GenerateMultidropEmbed(drop: DropResult, quantityClaimed: number, imageFileName: string, cardsRemaining: number, claimedBy?: string, currency?: number): EmbedBuilder {
|
|
||||||
const dropEmbed = DropEmbedHelper.GenerateDropEmbed(drop, quantityClaimed, imageFileName, claimedBy, currency);
|
|
||||||
|
|
||||||
dropEmbed.setFooter({ text: `${dropEmbed.data.footer?.text} · ${cardsRemaining} Remaining`});
|
|
||||||
|
|
||||||
return dropEmbed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GenerateMultidropButtons(drop: DropResult, cardsRemaining: number, userId: string, disabled = false): ActionRowBuilder<ButtonBuilder> {
|
|
||||||
return new ActionRowBuilder<ButtonBuilder>()
|
|
||||||
.addComponents(
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`multidrop keep ${drop.card.id} ${cardsRemaining} ${userId}`)
|
|
||||||
.setLabel("Keep")
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setDisabled(disabled),
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`multidrop sacrifice ${drop.card.id} ${cardsRemaining} ${userId}`)
|
|
||||||
.setLabel(`Sacrifice (+${GetSacrificeAmount(drop.card.type)} 🪙)`)
|
|
||||||
.setStyle(ButtonStyle.Secondary));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,194 +0,0 @@
|
||||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
|
||||||
import UserEffect from "../database/entities/app/UserEffect";
|
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
|
||||||
import { EffectDetails } from "../constants/EffectDetails";
|
|
||||||
import User from "../database/entities/app/User";
|
|
||||||
import CardConstants from "../constants/CardConstants";
|
|
||||||
import AppLogger from "../client/appLogger";
|
|
||||||
|
|
||||||
export default class EffectHelper {
|
|
||||||
public static async AddEffectToUserInventory(userId: string, name: string, quantity: number = 1) {
|
|
||||||
let effect = await UserEffect.FetchOneByUserIdAndName(userId, name);
|
|
||||||
|
|
||||||
if (!effect) {
|
|
||||||
effect = new UserEffect(name, userId, quantity);
|
|
||||||
} else {
|
|
||||||
effect.AddUnused(quantity);
|
|
||||||
}
|
|
||||||
|
|
||||||
await effect.Save(UserEffect, effect);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async UseEffect(userId: string, name: string, whenExpires: Date): Promise<boolean> {
|
|
||||||
const canUseEffect = await this.CanUseEffect(userId, name);
|
|
||||||
|
|
||||||
if (!canUseEffect) return false;
|
|
||||||
|
|
||||||
const effect = await UserEffect.FetchOneByUserIdAndName(userId, name);
|
|
||||||
|
|
||||||
effect!.UseEffect(whenExpires);
|
|
||||||
|
|
||||||
await effect!.Save(UserEffect, effect!);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async CanUseEffect(userId: string, name: string): Promise<boolean> {
|
|
||||||
const effect = await UserEffect.FetchOneByUserIdAndName(userId, name);
|
|
||||||
const now = new Date();
|
|
||||||
|
|
||||||
if (!effect || effect.Unused == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const effectDetail = EffectDetails.get(effect.Name);
|
|
||||||
|
|
||||||
if (!effectDetail) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (effect.WhenExpires && now < new Date(effect.WhenExpires.getTime() + effectDetail.cooldown)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async HasEffect(userId: string, name: string): Promise<boolean> {
|
|
||||||
const effect = await UserEffect.FetchOneByUserIdAndName(userId, name);
|
|
||||||
const now = new Date();
|
|
||||||
|
|
||||||
if (!effect || !effect.WhenExpires) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (now > effect.WhenExpires) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async GenerateEffectListEmbed(userId: string, page: number): Promise<{
|
|
||||||
embed: EmbedBuilder,
|
|
||||||
row: ActionRowBuilder<ButtonBuilder>,
|
|
||||||
}> {
|
|
||||||
const itemsPerPage = 10;
|
|
||||||
|
|
||||||
const query = await UserEffect.FetchAllByUserIdPaginated(userId, page - 1, itemsPerPage);
|
|
||||||
const activeEffect = await UserEffect.FetchActiveEffectByUserId(userId);
|
|
||||||
|
|
||||||
const effects = query[0];
|
|
||||||
const count = query[1];
|
|
||||||
|
|
||||||
const totalPages = count > 0 ? Math.ceil(count / itemsPerPage) : 1;
|
|
||||||
|
|
||||||
let description = "*none*";
|
|
||||||
|
|
||||||
if (effects.length > 0) {
|
|
||||||
description = effects.map(x => `${EffectDetails.get(x.Name)?.friendlyName} x${x.Unused}`).join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle("Effects")
|
|
||||||
.setDescription(description)
|
|
||||||
.setColor(EmbedColours.Ok)
|
|
||||||
.setFooter({ text: `Page ${page} of ${totalPages}` });
|
|
||||||
|
|
||||||
if (activeEffect) {
|
|
||||||
embed.addFields([
|
|
||||||
{
|
|
||||||
name: "Active",
|
|
||||||
value: `${EffectDetails.get(activeEffect.Name)?.friendlyName}`,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Expires",
|
|
||||||
value: `<t:${Math.round(activeEffect.WhenExpires!.getTime() / 1000)}>`,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
|
||||||
.addComponents(
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`effects list ${page - 1}`)
|
|
||||||
.setLabel("Previous")
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setDisabled(page == 1),
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`effects list ${page + 1}`)
|
|
||||||
.setLabel("Next")
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setDisabled(page == totalPages),
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
embed,
|
|
||||||
row,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async GenerateEffectBuyEmbed(userId: string, id: string, quantity: number, disabled: boolean): Promise<{
|
|
||||||
embed: EmbedBuilder,
|
|
||||||
row: ActionRowBuilder<ButtonBuilder>,
|
|
||||||
} | string> {
|
|
||||||
const effectDetail = EffectDetails.get(id);
|
|
||||||
|
|
||||||
if (!effectDetail) {
|
|
||||||
return "Effect detail not found!";
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalCost = effectDetail.cost * quantity;
|
|
||||||
|
|
||||||
let user = await User.FetchOneById(User, userId);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
user = new User(userId, CardConstants.StartingCurrency);
|
|
||||||
await user.Save(User, user);
|
|
||||||
|
|
||||||
AppLogger.LogInfo("EffectHelper", `Created initial user entity for : ${userId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!disabled && user.Currency < totalCost) {
|
|
||||||
return `You don't have enough currency to buy this! You have \`${user.Currency} Currency\` and need \`${totalCost} Currency\`!`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle("Buy Effect")
|
|
||||||
.setDescription(effectDetail.friendlyName)
|
|
||||||
.setColor(EmbedColours.Ok)
|
|
||||||
.addFields([
|
|
||||||
{
|
|
||||||
name: "Cost",
|
|
||||||
value: `${totalCost}`,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Quantity",
|
|
||||||
value: `${quantity}`,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
|
||||||
.addComponents([
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`effects buy confirm ${id} ${quantity}`)
|
|
||||||
.setLabel("Confirm")
|
|
||||||
.setStyle(ButtonStyle.Success)
|
|
||||||
.setDisabled(disabled),
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`effects buy cancel ${id} ${quantity}`)
|
|
||||||
.setLabel("Cancel")
|
|
||||||
.setStyle(ButtonStyle.Danger)
|
|
||||||
.setDisabled(disabled),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
embed,
|
|
||||||
row,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
import {createCanvas, loadImage} from "canvas";
|
|
||||||
import path from "path";
|
|
||||||
import AppLogger from "../client/appLogger";
|
|
||||||
import {existsSync} from "fs";
|
|
||||||
import Inventory from "../database/entities/app/Inventory";
|
|
||||||
import { Bitmap, Jimp } from "jimp";
|
|
||||||
import axios from "axios";
|
|
||||||
|
|
||||||
interface CardInput {
|
|
||||||
id: string;
|
|
||||||
path: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class ImageHelper {
|
|
||||||
public static async GenerateCardImageGrid(cards: CardInput[], userId?: string): Promise<Buffer> {
|
|
||||||
const gridWidth = 3;
|
|
||||||
const gridHeight = Math.ceil(cards.length / gridWidth);
|
|
||||||
|
|
||||||
const imageWidth = 526;
|
|
||||||
const imageHeight = 712;
|
|
||||||
|
|
||||||
const canvasWidth = imageWidth * gridWidth;
|
|
||||||
const canvasHeight = imageHeight * gridHeight;
|
|
||||||
|
|
||||||
const canvas = createCanvas(canvasWidth, canvasHeight);
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
|
|
||||||
for (let i = 0; i < cards.length; i++) {
|
|
||||||
try {
|
|
||||||
const card = cards[i];
|
|
||||||
|
|
||||||
const filePath = path.join(process.env.DATA_DIR!, "cards", card.path);
|
|
||||||
|
|
||||||
let bitmap: Bitmap;
|
|
||||||
|
|
||||||
if (existsSync(filePath)) {
|
|
||||||
const data = await Jimp.read(filePath);
|
|
||||||
|
|
||||||
bitmap = data.bitmap;
|
|
||||||
} else if (card.path.startsWith("http://") || card.path.startsWith("https://")) {
|
|
||||||
const response = await axios.get(card.path, { responseType: "arraybuffer" });
|
|
||||||
const buffer = Buffer.from(response.data);
|
|
||||||
const data = await Jimp.fromBuffer(buffer);
|
|
||||||
|
|
||||||
bitmap = data.bitmap;
|
|
||||||
} else {
|
|
||||||
AppLogger.LogError("ImageHelper/GenerateCardImageGrid", `Failed to load image from path ${card.path}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageData = Jimp.fromBitmap(bitmap);
|
|
||||||
|
|
||||||
if (userId != null) {
|
|
||||||
const claimed = await Inventory.FetchOneByCardNumberAndUserId(userId, card.id);
|
|
||||||
|
|
||||||
if (!claimed || claimed.Quantity == 0) {
|
|
||||||
imageData.greyscale();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const image = await loadImage(await imageData.getBuffer("image/png"));
|
|
||||||
|
|
||||||
const x = i % gridWidth;
|
|
||||||
const y = Math.floor(i / gridWidth);
|
|
||||||
|
|
||||||
const imageX = imageWidth * x;
|
|
||||||
const imageY = imageHeight * y;
|
|
||||||
|
|
||||||
ctx.drawImage(image, imageX, imageY);
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
// TODO: Enable once we've investigated a fix
|
|
||||||
//AppLogger.CatchError("ImageHelper", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return canvas.toBuffer();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder } from "discord.js";
|
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } 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";
|
||||||
import { CardRarity, CardRarityToString } from "../constants/CardRarity";
|
import { CardRarity, CardRarityToString } from "../constants/CardRarity";
|
||||||
import cloneDeep from "clone-deep";
|
import cloneDeep from "clone-deep";
|
||||||
import AppLogger from "../client/appLogger";
|
import AppLogger from "../client/appLogger";
|
||||||
import ImageHelper from "./ImageHelper";
|
|
||||||
|
|
||||||
interface InventoryPage {
|
interface InventoryPage {
|
||||||
id: number,
|
id: number,
|
||||||
|
@ -19,27 +18,16 @@ interface InventoryPageCards {
|
||||||
name: string,
|
name: string,
|
||||||
type: CardRarity,
|
type: CardRarity,
|
||||||
quantity: number,
|
quantity: number,
|
||||||
path: string,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ReturnedInventoryPage {
|
|
||||||
embed: EmbedBuilder,
|
|
||||||
row1: ActionRowBuilder<ButtonBuilder>,
|
|
||||||
row2: ActionRowBuilder<StringSelectMenuBuilder>,
|
|
||||||
image: AttachmentBuilder,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default class InventoryHelper {
|
export default class InventoryHelper {
|
||||||
public static async GenerateInventoryPage(username: string, userid: string, page: number): Promise<ReturnedInventoryPage | undefined> {
|
public static async GenerateInventoryPage(username: string, userid: string, page: number): Promise<{ embed: EmbedBuilder, row: ActionRowBuilder<ButtonBuilder> }> {
|
||||||
AppLogger.LogSilly("Helpers/InventoryHelper", `Parameters: username=${username}, userid=${userid}, page=${page}`);
|
AppLogger.LogSilly("Helpers/InventoryHelper", `Parameters: username=${username}, userid=${userid}, page=${page}`);
|
||||||
|
|
||||||
const cardsPerPage = 9;
|
const cardsPerPage = 15;
|
||||||
|
|
||||||
const inventory = await Inventory.FetchAllByUserId(userid);
|
const inventory = await Inventory.FetchAllByUserId(userid);
|
||||||
|
|
||||||
if (!inventory || inventory.length == 0) return undefined;
|
|
||||||
|
|
||||||
const clientCards = cloneDeep(CoreClient.Cards);
|
const clientCards = cloneDeep(CoreClient.Cards);
|
||||||
|
|
||||||
const allSeriesClaimed = clientCards
|
const allSeriesClaimed = clientCards
|
||||||
|
@ -47,8 +35,7 @@ export default class InventoryHelper {
|
||||||
.filter(x => {
|
.filter(x => {
|
||||||
x.cards = x.cards
|
x.cards = x.cards
|
||||||
.sort((a, b) => b.type - a.type)
|
.sort((a, b) => b.type - a.type)
|
||||||
.filter(y => inventory.find(z => z.CardNumber == y.id))
|
.filter(y => inventory.find(z => z.CardNumber == y.id));
|
||||||
.filter(y => inventory.find(z => z.CardNumber == y.id)!.Quantity > 0);
|
|
||||||
|
|
||||||
return x;
|
return x;
|
||||||
});
|
});
|
||||||
|
@ -74,7 +61,6 @@ export default class InventoryHelper {
|
||||||
name: card.name,
|
name: card.name,
|
||||||
type: card.type,
|
type: card.type,
|
||||||
quantity: item.Quantity,
|
quantity: item.Quantity,
|
||||||
path: card.path,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,17 +76,17 @@ export default class InventoryHelper {
|
||||||
const currentPage = pages[page];
|
const currentPage = pages[page];
|
||||||
|
|
||||||
if (!currentPage) {
|
if (!currentPage) {
|
||||||
return undefined;
|
AppLogger.LogError("Helpers/InventoryHelper", "Unable to find page");
|
||||||
|
return Promise.reject("Unable to find page");
|
||||||
}
|
}
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle(username)
|
.setTitle(username)
|
||||||
.setDescription(`**${currentPage.name} (${currentPage.seriesSubpage + 1})**\n${currentPage.cards.map(x => `[${x.id}] ${x.name} (${CardRarityToString(x.type)}) x${x.quantity}`).join("\n")}`)
|
.setDescription(`**${currentPage.name} (${currentPage.seriesSubpage + 1})**\n${currentPage.cards.map(x => `[${x.id}] ${x.name} (${CardRarityToString(x.type)}) x${x.quantity}`).join("\n")}`)
|
||||||
.setFooter({ text: `Page ${page + 1} of ${pages.length}` })
|
.setFooter({ text: `Page ${page + 1} of ${pages.length}` })
|
||||||
.setColor(EmbedColours.Ok)
|
.setColor(EmbedColours.Ok);
|
||||||
.setImage("attachment://page.png");
|
|
||||||
|
|
||||||
const row1 = new ActionRowBuilder<ButtonBuilder>()
|
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`inventory ${userid} ${page - 1}`)
|
.setCustomId(`inventory ${userid} ${page - 1}`)
|
||||||
|
@ -113,54 +99,6 @@ export default class InventoryHelper {
|
||||||
.setStyle(ButtonStyle.Primary)
|
.setStyle(ButtonStyle.Primary)
|
||||||
.setDisabled(page + 1 == pages.length));
|
.setDisabled(page + 1 == pages.length));
|
||||||
|
|
||||||
let pageNum = 0;
|
return { embed, row };
|
||||||
|
|
||||||
const maxLength = 25; // Discord API limit
|
|
||||||
|
|
||||||
const allPageOptions = pages.map(x =>
|
|
||||||
new StringSelectMenuOptionBuilder()
|
|
||||||
.setLabel(`${x.name} (${x.seriesSubpage + 1})`.substring(0, 100))
|
|
||||||
.setDescription(`Page ${pageNum + 1}`)
|
|
||||||
.setDefault(currentPage.id == x.id)
|
|
||||||
.setValue(`${userid} ${pageNum++}`));
|
|
||||||
|
|
||||||
const currentPageIndex = allPageOptions.indexOf(allPageOptions.find(x => x.data.default)!);
|
|
||||||
|
|
||||||
let pageOptions: StringSelectMenuOptionBuilder[] = [];
|
|
||||||
|
|
||||||
if (allPageOptions.length <= maxLength) {
|
|
||||||
pageOptions = allPageOptions;
|
|
||||||
} else {
|
|
||||||
let startIndex = currentPageIndex - Math.floor((maxLength - 1) / 2);
|
|
||||||
let endIndexOffset = 0;
|
|
||||||
|
|
||||||
if (startIndex < 0) {
|
|
||||||
endIndexOffset = 0 - startIndex;
|
|
||||||
|
|
||||||
startIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let endIndex = currentPageIndex + Math.floor((maxLength - 1) / 2) + endIndexOffset;
|
|
||||||
|
|
||||||
if (endIndex + 1 > allPageOptions.length) {
|
|
||||||
endIndex = allPageOptions.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = startIndex; i < endIndex; i++) {
|
|
||||||
pageOptions.push(allPageOptions[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const row2 = new ActionRowBuilder<StringSelectMenuBuilder>()
|
|
||||||
.addComponents(
|
|
||||||
new StringSelectMenuBuilder()
|
|
||||||
.setCustomId("inventory")
|
|
||||||
.setPlaceholder(`${currentPage.name} (${currentPage.seriesSubpage + 1})`)
|
|
||||||
.addOptions(pageOptions));
|
|
||||||
|
|
||||||
const buffer = await ImageHelper.GenerateCardImageGrid(currentPage.cards.map(x => ({ id: x.id, path: x.path })));
|
|
||||||
const image = new AttachmentBuilder(buffer, { name: "page.png" });
|
|
||||||
|
|
||||||
return { embed, row1, row2, image };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,16 +1,15 @@
|
||||||
import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
||||||
import AppLogger from "../client/appLogger";
|
import AppLogger from "../client/appLogger";
|
||||||
import cloneDeep from "clone-deep";
|
import cloneDeep from "clone-deep";
|
||||||
import { CoreClient } from "../client/client";
|
import { CoreClient } from "../client/client";
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
import EmbedColours from "../constants/EmbedColours";
|
||||||
import { CardRarityToString } from "../constants/CardRarity";
|
import { CardRarityToString } from "../constants/CardRarity";
|
||||||
import ImageHelper from "./ImageHelper";
|
|
||||||
|
|
||||||
export default class SeriesHelper {
|
export default class SeriesHelper {
|
||||||
public static async GenerateSeriesViewPage(seriesId: number, page: number, userId: string): Promise<{ embed: EmbedBuilder, row: ActionRowBuilder<ButtonBuilder>, image: AttachmentBuilder } | null> {
|
public static GenerateSeriesViewPage(seriesId: number, page: number): { embed: EmbedBuilder, row: ActionRowBuilder<ButtonBuilder> } | null {
|
||||||
AppLogger.LogSilly("Helpers/SeriesHelper", `Parameters: seriesId=${seriesId}, page=${page}`);
|
AppLogger.LogSilly("Helpers/SeriesHelper", `Parameters: seriesId=${seriesId}, page=${page}`);
|
||||||
|
|
||||||
const itemsPerPage = 9;
|
const itemsPerPage = 15;
|
||||||
|
|
||||||
const series = cloneDeep(CoreClient.Cards)
|
const series = cloneDeep(CoreClient.Cards)
|
||||||
.find(x => x.id == seriesId);
|
.find(x => x.id == seriesId);
|
||||||
|
@ -21,7 +20,6 @@ export default class SeriesHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalPages = Math.ceil(series.cards.length / itemsPerPage);
|
const totalPages = Math.ceil(series.cards.length / itemsPerPage);
|
||||||
const totalCards = series.cards.length;
|
|
||||||
|
|
||||||
if (page > totalPages) {
|
if (page > totalPages) {
|
||||||
AppLogger.LogVerbose("Helpers/SeriesHelper", `Trying to find page greater than what exists for this series. Page: ${page} but there are only ${totalPages} pages`);
|
AppLogger.LogVerbose("Helpers/SeriesHelper", `Trying to find page greater than what exists for this series. Page: ${page} but there are only ${totalPages} pages`);
|
||||||
|
@ -31,15 +29,14 @@ export default class SeriesHelper {
|
||||||
const cardsOnPage = series.cards.splice(page * itemsPerPage, itemsPerPage);
|
const cardsOnPage = series.cards.splice(page * itemsPerPage, itemsPerPage);
|
||||||
|
|
||||||
const description = cardsOnPage
|
const description = cardsOnPage
|
||||||
.map(x => `[${x.id}] ${x.name} (${CardRarityToString(x.type)})`)
|
.map(x => `[${x.id}] ${x.name} ${CardRarityToString(x.type).toUpperCase()}`)
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle(series.name)
|
.setTitle(series.name)
|
||||||
.setColor(EmbedColours.Ok)
|
.setColor(EmbedColours.Ok)
|
||||||
.setDescription(description)
|
.setDescription(description)
|
||||||
.setFooter({ text: `${series.id} · ${totalCards} cards · Page ${page + 1} of ${totalPages}` })
|
.setFooter({ text: `${series.id} · ${series.cards.length} cards · Page ${page + 1} of ${totalPages}` });
|
||||||
.setImage("attachment://page.png");
|
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
|
@ -52,12 +49,9 @@ export default class SeriesHelper {
|
||||||
.setCustomId(`series view ${seriesId} ${page + 1}`)
|
.setCustomId(`series view ${seriesId} ${page + 1}`)
|
||||||
.setLabel("Next")
|
.setLabel("Next")
|
||||||
.setStyle(ButtonStyle.Primary)
|
.setStyle(ButtonStyle.Primary)
|
||||||
.setDisabled(page + 1 == totalPages));
|
.setDisabled(page + 1 > totalPages));
|
||||||
|
|
||||||
const buffer = await ImageHelper.GenerateCardImageGrid(cardsOnPage.map(x => ({id: x.id, path: x.path})), userId);
|
return { embed, row };
|
||||||
const image = new AttachmentBuilder(buffer, { name: "page.png" });
|
|
||||||
|
|
||||||
return { embed, row, image };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GenerateSeriesListPage(page: number): { embed: EmbedBuilder, row: ActionRowBuilder<ButtonBuilder> } | null {
|
public static GenerateSeriesListPage(page: number): { embed: EmbedBuilder, row: ActionRowBuilder<ButtonBuilder> } | null {
|
||||||
|
@ -78,7 +72,7 @@ export default class SeriesHelper {
|
||||||
const seriesOnPage = series.splice(page * itemsPerPage, itemsPerPage);
|
const seriesOnPage = series.splice(page * itemsPerPage, itemsPerPage);
|
||||||
|
|
||||||
const description = seriesOnPage
|
const description = seriesOnPage
|
||||||
.map(x => `[${x.id}] ${x.name} (${x.cards.length} cards)`)
|
.map(x => `[${x.id}] ${x.name}`)
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
|
@ -98,7 +92,7 @@ export default class SeriesHelper {
|
||||||
.setCustomId(`series list ${page + 1}`)
|
.setCustomId(`series list ${page + 1}`)
|
||||||
.setLabel("Next")
|
.setLabel("Next")
|
||||||
.setStyle(ButtonStyle.Primary)
|
.setStyle(ButtonStyle.Primary)
|
||||||
.setDisabled(page + 1 == totalPages));
|
.setDisabled(page + 1 > totalPages));
|
||||||
|
|
||||||
return { embed, row };
|
return { embed, row };
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,18 +39,4 @@ export default class StringTools {
|
||||||
public static ReplaceAll(str: string, find: string, replace: string) {
|
public static ReplaceAll(str: string, find: string, replace: string) {
|
||||||
return str.replace(new RegExp(find, "g"), replace);
|
return str.replace(new RegExp(find, "g"), replace);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IsHexCode(str: string): boolean {
|
|
||||||
if (str.length != 6) return false;
|
|
||||||
|
|
||||||
const characters = "0123456789abcdefABCDEF";
|
|
||||||
|
|
||||||
for (let i = 0; i < 6; i++) {
|
|
||||||
const char = str[i];
|
|
||||||
|
|
||||||
if (!characters.includes(char)) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -118,19 +118,4 @@ 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,81 +0,0 @@
|
||||||
import { CronJob } from "cron";
|
|
||||||
import { v4 } from "uuid";
|
|
||||||
import { Primitive } from "../type/primitive";
|
|
||||||
|
|
||||||
interface Timer {
|
|
||||||
id: string;
|
|
||||||
job: CronJob;
|
|
||||||
context: Map<string, Primitive>;
|
|
||||||
onTick: ((context: Map<string, Primitive>) => void) | ((context: Map<string, Primitive>) => Promise<void>);
|
|
||||||
runOnStart: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class TimerHelper {
|
|
||||||
private _timers: Timer[];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this._timers = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public AddTimer(
|
|
||||||
cronTime: string,
|
|
||||||
timeZone: string,
|
|
||||||
onTick: ((context: Map<string, Primitive>) => void) | ((context: Map<string, Primitive>) => Promise<void>),
|
|
||||||
runOnStart: boolean = false): string {
|
|
||||||
const context = new Map<string, Primitive>();
|
|
||||||
|
|
||||||
const job = new CronJob(
|
|
||||||
cronTime,
|
|
||||||
() => {
|
|
||||||
onTick(context);
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
timeZone,
|
|
||||||
);
|
|
||||||
|
|
||||||
const id = v4();
|
|
||||||
|
|
||||||
this._timers.push({
|
|
||||||
id,
|
|
||||||
job,
|
|
||||||
context,
|
|
||||||
onTick,
|
|
||||||
runOnStart,
|
|
||||||
});
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public StartAllTimers() {
|
|
||||||
this._timers.forEach(timer => this.StartJob(timer));
|
|
||||||
}
|
|
||||||
|
|
||||||
public StopAllTimers() {
|
|
||||||
this._timers.forEach(timer => timer.job.stop());
|
|
||||||
}
|
|
||||||
|
|
||||||
public StartTimer(id: string) {
|
|
||||||
const timer = this._timers.find(x => x.id == id);
|
|
||||||
|
|
||||||
if (!timer) return;
|
|
||||||
|
|
||||||
this.StartJob(timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public StopTimer(id: string) {
|
|
||||||
const timer = this._timers.find(x => x.id == id);
|
|
||||||
|
|
||||||
if (!timer) return;
|
|
||||||
|
|
||||||
timer.job.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private StartJob(timer: Timer) {
|
|
||||||
timer.job.start();
|
|
||||||
|
|
||||||
if (timer.runOnStart) {
|
|
||||||
timer.onTick(timer.context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,20 +3,12 @@ import { Environment } from "./constants/Environment";
|
||||||
|
|
||||||
// Global Command Imports
|
// Global Command Imports
|
||||||
import About from "./commands/about";
|
import About from "./commands/about";
|
||||||
import AllBalance from "./commands/allbalance";
|
|
||||||
import Balance from "./commands/balance";
|
|
||||||
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 Series from "./commands/series";
|
import Series from "./commands/series";
|
||||||
import Stats from "./commands/stats";
|
|
||||||
import Trade from "./commands/trade";
|
import Trade from "./commands/trade";
|
||||||
import View from "./commands/view";
|
import View from "./commands/view";
|
||||||
|
|
||||||
|
@ -26,36 +18,21 @@ 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 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() {
|
||||||
// Global Commands
|
// Global Commands
|
||||||
CoreClient.RegisterCommand("about", new About());
|
CoreClient.RegisterCommand("about", new About());
|
||||||
CoreClient.RegisterCommand("allbalance", new AllBalance());
|
|
||||||
CoreClient.RegisterCommand("balance", new Balance());
|
|
||||||
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("series", new Series());
|
CoreClient.RegisterCommand("series", new Series());
|
||||||
CoreClient.RegisterCommand("stats", new Stats());
|
|
||||||
CoreClient.RegisterCommand("trade", new Trade());
|
CoreClient.RegisterCommand("trade", new Trade());
|
||||||
CoreClient.RegisterCommand("view", new View());
|
CoreClient.RegisterCommand("view", new View());
|
||||||
|
|
||||||
|
@ -64,19 +41,15 @@ export default class Registry {
|
||||||
CoreClient.RegisterCommand("droprarity", new Droprarity(), Environment.Test);
|
CoreClient.RegisterCommand("droprarity", new Droprarity(), Environment.Test);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RegisterButtonEvents() {
|
public static RegisterEvents() {
|
||||||
CoreClient.RegisterButtonEvent("claim", new Claim());
|
|
||||||
CoreClient.RegisterButtonEvent("effects", new EffectsButtonEvent());
|
|
||||||
CoreClient.RegisterButtonEvent("inventory", new InventoryButtonEvent());
|
|
||||||
CoreClient.RegisterButtonEvent("multidrop", new MultidropButtonEvent());
|
|
||||||
CoreClient.RegisterButtonEvent("reroll", new Reroll());
|
|
||||||
CoreClient.RegisterButtonEvent("sacrifice", new SacrificeButtonEvent());
|
|
||||||
CoreClient.RegisterButtonEvent("series", new SeriesEvent());
|
|
||||||
CoreClient.RegisterButtonEvent("trade", new TradeButtonEvent());
|
|
||||||
CoreClient.RegisterButtonEvent("view", new ViewButtonEvent());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RegisterStringDropdownEvents() {
|
public static RegisterButtonEvents() {
|
||||||
CoreClient.RegisterStringDropdownEvent("inventory", new InventoryStringDropdown());
|
CoreClient.RegisterButtonEvent("claim", new Claim());
|
||||||
|
CoreClient.RegisterButtonEvent("inventory", new InventoryButtonEvent());
|
||||||
|
CoreClient.RegisterButtonEvent("reroll", new Reroll());
|
||||||
|
CoreClient.RegisterButtonEvent("series", new SeriesEvent());
|
||||||
|
CoreClient.RegisterButtonEvent("trade", new TradeButtonEvent());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,43 +0,0 @@
|
||||||
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.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
import AppLogger from "../client/appLogger";
|
|
||||||
import CardConstants from "../constants/CardConstants";
|
|
||||||
import User from "../database/entities/app/User";
|
|
||||||
|
|
||||||
export default async function GiveCurrency() {
|
|
||||||
AppLogger.LogDebug("Timers/GiveCurrency", "Giving currency to every known user");
|
|
||||||
|
|
||||||
const users = await User.FetchAll(User);
|
|
||||||
|
|
||||||
const usersFiltered = users.filter(x => x.Currency < 1000);
|
|
||||||
|
|
||||||
for (const user of usersFiltered) {
|
|
||||||
user.AddCurrency(CardConstants.TimerGiveAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
User.SaveAll(User, users);
|
|
||||||
|
|
||||||
AppLogger.LogDebug("Timers/GiveCurrency", `Successfully gave +${CardConstants.TimerGiveAmount} currency to ${usersFiltered.length} users`);
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
import AppLogger from "../client/appLogger";
|
|
||||||
import Claim from "../database/entities/app/Claim";
|
|
||||||
|
|
||||||
export default async function PurgeClaims() {
|
|
||||||
const claims = await Claim.FetchAll(Claim);
|
|
||||||
|
|
||||||
const whenLastClaimable = new Date(Date.now() - (1000 * 60 * 2)); // 2 minutes ago
|
|
||||||
|
|
||||||
const expiredClaims = claims.filter(x => x.WhenCreated < whenLastClaimable);
|
|
||||||
|
|
||||||
await Claim.RemoveMany(Claim, expiredClaims);
|
|
||||||
|
|
||||||
AppLogger.LogInfo("Timers/PurgeClaims", `Purged ${expiredClaims.length} claims from the database`);
|
|
||||||
}
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { CommandInteraction } from "discord.js";
|
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
|
||||||
|
|
||||||
export abstract class Command {
|
export abstract class Command {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- CommandBuilder type is dynamic depending on options and can't be strictly typed
|
public CommandBuilder: Omit<SlashCommandBuilder, "addSubcommand" | "addSubcommandGroup">;
|
||||||
public CommandBuilder: any;
|
|
||||||
|
|
||||||
abstract execute(interaction: CommandInteraction): Promise<void>;
|
abstract execute(interaction: CommandInteraction): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export type Primitive = string | number | boolean;
|
|
|
@ -1,5 +0,0 @@
|
||||||
import {StringSelectMenuInteraction} from "discord.js";
|
|
||||||
|
|
||||||
export abstract class StringDropdownEvent {
|
|
||||||
abstract execute(interaction: StringSelectMenuInteraction): Promise<void>;
|
|
||||||
}
|
|
0
tests/.gitkeep
Normal file
0
tests/.gitkeep
Normal file
|
@ -1,23 +0,0 @@
|
||||||
import { ButtonInteraction } from "../../__types__/discord.js";
|
|
||||||
|
|
||||||
export default function GenerateButtonInteractionMock(): ButtonInteraction {
|
|
||||||
return {
|
|
||||||
guild: {},
|
|
||||||
guildId: "guildId",
|
|
||||||
channel: {
|
|
||||||
isSendable: jest.fn().mockReturnValue(true),
|
|
||||||
send: jest.fn(),
|
|
||||||
},
|
|
||||||
deferUpdate: jest.fn(),
|
|
||||||
editReply: jest.fn(),
|
|
||||||
message: {
|
|
||||||
createdAt: new Date(1000 * 60 * 27),
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
id: "userId",
|
|
||||||
},
|
|
||||||
customId: "customId",
|
|
||||||
update: jest.fn(),
|
|
||||||
reply: jest.fn(),
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
import { CommandInteraction } from "../../__types__/discord.js";
|
|
||||||
|
|
||||||
export default function GenerateCommandInteractionMock(options?: {
|
|
||||||
subcommand?: string,
|
|
||||||
}): CommandInteraction {
|
|
||||||
return {
|
|
||||||
deferReply: jest.fn(),
|
|
||||||
editReply: jest.fn(),
|
|
||||||
isChatInputCommand: jest.fn().mockReturnValue(true),
|
|
||||||
options: {
|
|
||||||
getSubcommand: jest.fn().mockReturnValue(options?.subcommand),
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
id: "userId",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
export type ButtonInteraction = {
|
|
||||||
guild: object | null,
|
|
||||||
guildId: string | null,
|
|
||||||
channel: {
|
|
||||||
isSendable: jest.Func,
|
|
||||||
send: jest.Func,
|
|
||||||
} | null,
|
|
||||||
deferUpdate: jest.Func,
|
|
||||||
editReply: jest.Func,
|
|
||||||
message: {
|
|
||||||
createdAt: Date,
|
|
||||||
} | null,
|
|
||||||
user: {
|
|
||||||
id: string,
|
|
||||||
} | null,
|
|
||||||
customId: string,
|
|
||||||
update: jest.Func,
|
|
||||||
reply: jest.Func,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CommandInteraction = {
|
|
||||||
deferReply: jest.Func,
|
|
||||||
editReply: jest.Func,
|
|
||||||
isChatInputCommand: jest.Func,
|
|
||||||
options: {
|
|
||||||
getSubcommand: jest.Func,
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
id: string,
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
import { ButtonInteraction, TextChannel } from "discord.js";
|
|
||||||
import Claim from "../../src/buttonEvents/Claim";
|
|
||||||
import { ButtonInteraction as ButtonInteractionType } from "../__types__/discord.js";
|
|
||||||
import GenerateButtonInteractionMock from "../__functions__/discord.js/GenerateButtonInteractionMock";
|
|
||||||
|
|
||||||
jest.mock("../../src/client/appLogger");
|
|
||||||
|
|
||||||
let interaction: ButtonInteractionType;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.useFakeTimers();
|
|
||||||
jest.setSystemTime(1000 * 60 * 30);
|
|
||||||
|
|
||||||
interaction = GenerateButtonInteractionMock();
|
|
||||||
interaction.customId = "claim cardNumber claimId droppedBy userId";
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
jest.useRealTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN interaction.guild is null, EXPECT nothing to happen", async () => {
|
|
||||||
// Arrange
|
|
||||||
interaction.guild = null;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
const claim = new Claim();
|
|
||||||
await claim.execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(interaction.deferUpdate).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.editReply).not.toHaveBeenCalled();
|
|
||||||
expect((interaction.channel as TextChannel).send).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN interaction.guildId is null, EXPECT nothing to happen", async () => {
|
|
||||||
// Arrange
|
|
||||||
interaction.guildId = null;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
const claim = new Claim();
|
|
||||||
await claim.execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(interaction.deferUpdate).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.editReply).not.toHaveBeenCalled();
|
|
||||||
expect((interaction.channel as TextChannel).send).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN interaction.channel is null, EXPECT nothing to happen", async () => {
|
|
||||||
// Arrange
|
|
||||||
interaction.channel = null;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
const claim = new Claim();
|
|
||||||
await claim.execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(interaction.deferUpdate).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.editReply).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN channel is not sendable, EXPECT nothing to happen", async () => {
|
|
||||||
// Arrange
|
|
||||||
interaction.channel!.isSendable = jest.fn().mockReturnValue(false);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
const claim = new Claim();
|
|
||||||
await claim.execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(interaction.deferUpdate).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.editReply).not.toHaveBeenCalled();
|
|
||||||
expect((interaction.channel as TextChannel).send).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN interaction.message was created more than 5 minutes ago, EXPECT error", async () => {
|
|
||||||
// Arrange
|
|
||||||
interaction.message!.createdAt = new Date(0);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
const claim = new Claim();
|
|
||||||
await claim.execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(interaction.channel!.send).toHaveBeenCalledTimes(1);
|
|
||||||
expect(interaction.channel!.send).toHaveBeenCalledWith("[object Object], Cards can only be claimed within 2 minutes of it being dropped!");
|
|
||||||
|
|
||||||
expect(interaction.editReply).not.toHaveBeenCalled();
|
|
||||||
});
|
|
|
@ -1,68 +0,0 @@
|
||||||
import { ButtonInteraction } from "discord.js";
|
|
||||||
import Effects from "../../src/buttonEvents/Effects";
|
|
||||||
import GenerateButtonInteractionMock from "../__functions__/discord.js/GenerateButtonInteractionMock";
|
|
||||||
import { ButtonInteraction as ButtonInteractionType } from "../__types__/discord.js";
|
|
||||||
import List from "../../src/buttonEvents/Effects/List";
|
|
||||||
import Use from "../../src/buttonEvents/Effects/Use";
|
|
||||||
import AppLogger from "../../src/client/appLogger";
|
|
||||||
|
|
||||||
jest.mock("../../src/client/appLogger");
|
|
||||||
jest.mock("../../src/buttonEvents/Effects/List");
|
|
||||||
jest.mock("../../src/buttonEvents/Effects/Use");
|
|
||||||
|
|
||||||
let interaction: ButtonInteractionType;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
|
|
||||||
interaction = GenerateButtonInteractionMock();
|
|
||||||
interaction.customId = "effects";
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN action is list, EXPECT list function to be called", async () => {
|
|
||||||
// Arrange
|
|
||||||
interaction.customId = "effects list";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
const effects = new Effects();
|
|
||||||
await effects.execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(List).toHaveBeenCalledTimes(1);
|
|
||||||
expect(List).toHaveBeenCalledWith(interaction);
|
|
||||||
|
|
||||||
expect(Use.Execute).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN action is use, EXPECT use function to be called", async () => {
|
|
||||||
// Arrange
|
|
||||||
interaction.customId = "effects use";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
const effects = new Effects();
|
|
||||||
await effects.execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(Use.Execute).toHaveBeenCalledTimes(1);
|
|
||||||
expect(Use.Execute).toHaveBeenCalledWith(interaction);
|
|
||||||
|
|
||||||
expect(List).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test.todo("GIVEN action is buy, EXPECT buy function to be called");
|
|
||||||
|
|
||||||
test("GIVEN action is invalid, EXPECT nothing to be called", async () => {
|
|
||||||
// Arrange
|
|
||||||
interaction.customId = "effects invalid";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
const effects = new Effects();
|
|
||||||
await effects.execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(List).not.toHaveBeenCalled();
|
|
||||||
expect(Use.Execute).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buttons/Effects", "Unknown action, invalid");
|
|
||||||
});
|
|
|
@ -1,350 +0,0 @@
|
||||||
import {ButtonInteraction} from "discord.js";
|
|
||||||
import Buy from "../../../src/buttonEvents/Effects/Buy";
|
|
||||||
import GenerateButtonInteractionMock from "../../__functions__/discord.js/GenerateButtonInteractionMock";
|
|
||||||
import { ButtonInteraction as ButtonInteractionType } from "../../__types__/discord.js";
|
|
||||||
import AppLogger from "../../../src/client/appLogger";
|
|
||||||
import EffectHelper from "../../../src/helpers/EffectHelper";
|
|
||||||
import EmbedColours from "../../../src/constants/EmbedColours";
|
|
||||||
import User from "../../../src/database/entities/app/User";
|
|
||||||
|
|
||||||
jest.mock("../../../src/client/appLogger");
|
|
||||||
jest.mock("../../../src/helpers/EffectHelper");
|
|
||||||
jest.mock("../../../src/database/entities/app/User");
|
|
||||||
|
|
||||||
let interaction: ButtonInteractionType;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
|
|
||||||
interaction = GenerateButtonInteractionMock();
|
|
||||||
interaction.customId = "effects buy";
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Execute", () => {
|
|
||||||
test("GIVEN subaction is invalid, EXPECT error logged", async () => {
|
|
||||||
// Arrange
|
|
||||||
interaction.customId += " invalid";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy", "Unknown subaction, effects invalid");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Confirm", () => {
|
|
||||||
let user: User;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
interaction.customId += " confirm";
|
|
||||||
|
|
||||||
user = {
|
|
||||||
Currency: 1000,
|
|
||||||
Save: jest.fn(),
|
|
||||||
RemoveCurrency: jest.fn(),
|
|
||||||
} as unknown as User;
|
|
||||||
|
|
||||||
(User.FetchOneById as jest.Mock).mockResolvedValue(user);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("EXPECT success embed generated", async () => {
|
|
||||||
// Assert
|
|
||||||
interaction.customId += " unclaimed 1";
|
|
||||||
|
|
||||||
const embed = {
|
|
||||||
id: "embed",
|
|
||||||
setColor: jest.fn(),
|
|
||||||
setFooter: jest.fn(),
|
|
||||||
};
|
|
||||||
const row = {
|
|
||||||
id: "row",
|
|
||||||
};
|
|
||||||
|
|
||||||
(EffectHelper.GenerateEffectBuyEmbed as jest.Mock).mockResolvedValue({
|
|
||||||
embed,
|
|
||||||
row,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(interaction.update).toHaveBeenCalledTimes(1);
|
|
||||||
expect(interaction.update).toHaveBeenCalledWith({
|
|
||||||
embeds: [ embed ],
|
|
||||||
components: [ row ],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(EffectHelper.GenerateEffectBuyEmbed).toHaveBeenCalledTimes(1);
|
|
||||||
expect(EffectHelper.GenerateEffectBuyEmbed).toHaveBeenCalledWith("userId", "unclaimed", 1, true);
|
|
||||||
|
|
||||||
expect(embed.setColor).toHaveBeenCalledTimes(1);
|
|
||||||
expect(embed.setColor).toHaveBeenCalledWith(EmbedColours.Success);
|
|
||||||
|
|
||||||
expect(embed.setFooter).toHaveBeenCalledTimes(1);
|
|
||||||
expect(embed.setFooter).toHaveBeenCalledWith({ text: "Purchased" });
|
|
||||||
|
|
||||||
expect(interaction.reply).not.toHaveBeenCalled();
|
|
||||||
expect(AppLogger.LogError).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
expect(User.FetchOneById).toHaveBeenCalledTimes(1);
|
|
||||||
expect(User.FetchOneById).toHaveBeenCalledWith(User, "userId");
|
|
||||||
|
|
||||||
expect(user.RemoveCurrency).toHaveBeenCalledTimes(1);
|
|
||||||
expect(user.RemoveCurrency).toHaveBeenCalledWith(100);
|
|
||||||
|
|
||||||
expect(user.Save).toHaveBeenCalledTimes(1);
|
|
||||||
expect(user.Save).toHaveBeenCalledWith(User, user);
|
|
||||||
|
|
||||||
expect(EffectHelper.AddEffectToUserInventory).toHaveBeenCalledTimes(1);
|
|
||||||
expect(EffectHelper.AddEffectToUserInventory).toHaveBeenCalledWith("userId", "unclaimed", 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN id is not supplied, EXPECT error", async () => {
|
|
||||||
// Act
|
|
||||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy Confirm", "Not enough parameters");
|
|
||||||
|
|
||||||
expect(EffectHelper.GenerateEffectBuyEmbed).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.reply).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.update).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN quantity is not supplied, EXPECT error", async () => {
|
|
||||||
// Assert
|
|
||||||
interaction.customId += " unclaimed";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy Confirm", "Not enough parameters");
|
|
||||||
|
|
||||||
expect(EffectHelper.GenerateEffectBuyEmbed).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.reply).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.update).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN quantity is not a number, EXPECT error", async () => {
|
|
||||||
// Assert
|
|
||||||
interaction.customId += " unclaimed invalid";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy Confirm", "Invalid number");
|
|
||||||
|
|
||||||
expect(EffectHelper.GenerateEffectBuyEmbed).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.reply).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.update).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN quantity is 0, EXPECT error", async () => {
|
|
||||||
// Assert
|
|
||||||
interaction.customId += " unclaimed 0";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy Confirm", "Invalid number");
|
|
||||||
|
|
||||||
expect(EffectHelper.GenerateEffectBuyEmbed).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.reply).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.update).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN user is not found, EXPECT error", async () => {
|
|
||||||
// Assert
|
|
||||||
interaction.customId += " unclaimed 1";
|
|
||||||
|
|
||||||
(User.FetchOneById as jest.Mock).mockResolvedValue(undefined);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy Confirm", "Unable to find user");
|
|
||||||
|
|
||||||
expect(EffectHelper.AddEffectToUserInventory).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
expect(interaction.update).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.reply).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN user does not have enough currency, EXPECT error", async () => {
|
|
||||||
// Assert
|
|
||||||
interaction.customId += " unclaimed 1";
|
|
||||||
|
|
||||||
user.Currency = 0;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(interaction.reply).toHaveBeenCalledTimes(1);
|
|
||||||
expect(interaction.reply).toHaveBeenCalledWith("You don't have enough currency to buy this! You have `0 Currency` and need `100 Currency`!");
|
|
||||||
|
|
||||||
expect(EffectHelper.AddEffectToUserInventory).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
expect(interaction.update).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
expect(AppLogger.LogError).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN GenerateEffectBuyEmbed returns with a string, EXPECT error replied", async () => {
|
|
||||||
// Assert
|
|
||||||
interaction.customId += " unclaimed 1";
|
|
||||||
|
|
||||||
(EffectHelper.GenerateEffectBuyEmbed as jest.Mock).mockResolvedValue("Test error");
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(interaction.reply).toHaveBeenCalledTimes(1);
|
|
||||||
expect(interaction.reply).toHaveBeenCalledWith("Test error");
|
|
||||||
|
|
||||||
expect(EffectHelper.GenerateEffectBuyEmbed).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
expect(interaction.update).not.toHaveBeenCalled();
|
|
||||||
expect(AppLogger.LogError).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Cancel", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
interaction.customId += " cancel";
|
|
||||||
});
|
|
||||||
|
|
||||||
test("EXPECT embed generated", async () => {
|
|
||||||
// Assert
|
|
||||||
interaction.customId += " unclaimed 1";
|
|
||||||
|
|
||||||
const embed = {
|
|
||||||
id: "embed",
|
|
||||||
setColor: jest.fn(),
|
|
||||||
setFooter: jest.fn(),
|
|
||||||
};
|
|
||||||
const row = {
|
|
||||||
id: "row",
|
|
||||||
};
|
|
||||||
|
|
||||||
(EffectHelper.GenerateEffectBuyEmbed as jest.Mock).mockResolvedValue({
|
|
||||||
embed,
|
|
||||||
row,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(interaction.update).toHaveBeenCalledTimes(1);
|
|
||||||
expect(interaction.update).toHaveBeenCalledWith({
|
|
||||||
embeds: [ embed ],
|
|
||||||
components: [ row ],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(EffectHelper.GenerateEffectBuyEmbed).toHaveBeenCalledTimes(1);
|
|
||||||
expect(EffectHelper.GenerateEffectBuyEmbed).toHaveBeenCalledWith("userId", "unclaimed", 1, true);
|
|
||||||
|
|
||||||
expect(embed.setColor).toHaveBeenCalledTimes(1);
|
|
||||||
expect(embed.setColor).toHaveBeenCalledWith(EmbedColours.Error);
|
|
||||||
|
|
||||||
expect(embed.setFooter).toHaveBeenCalledTimes(1);
|
|
||||||
expect(embed.setFooter).toHaveBeenCalledWith({ text: "Cancelled" });
|
|
||||||
|
|
||||||
expect(interaction.reply).not.toHaveBeenCalled();
|
|
||||||
expect(AppLogger.LogError).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN id is not supplied, EXPECT error", async () => {
|
|
||||||
// Act
|
|
||||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy Cancel", "Not enough parameters");
|
|
||||||
|
|
||||||
expect(interaction.reply).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.update).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN quantity is not supplied, EXPECT error", async () => {
|
|
||||||
// Assert
|
|
||||||
interaction.customId += " unclaimed";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy Cancel", "Not enough parameters");
|
|
||||||
|
|
||||||
expect(EffectHelper.GenerateEffectBuyEmbed).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.reply).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.update).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN quantity is not a number, EXPECT error", async () => {
|
|
||||||
// Assert
|
|
||||||
interaction.customId += " unclaimed invalid";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy Cancel", "Invalid number");
|
|
||||||
|
|
||||||
expect(EffectHelper.GenerateEffectBuyEmbed).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.reply).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.update).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN quantity is 0, EXPECT error", async () => {
|
|
||||||
// Assert
|
|
||||||
interaction.customId += " unclaimed 0";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Buy Cancel", "Invalid number");
|
|
||||||
|
|
||||||
expect(EffectHelper.GenerateEffectBuyEmbed).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.reply).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.update).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN GenerateEffectBuyEmbed returns with a string, EXPECT error replied", async () => {
|
|
||||||
// Assert
|
|
||||||
interaction.customId += " unclaimed 1";
|
|
||||||
|
|
||||||
(EffectHelper.GenerateEffectBuyEmbed as jest.Mock).mockResolvedValue("Test error");
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await Buy.Execute(interaction as unknown as ButtonInteraction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(interaction.reply).toHaveBeenCalledTimes(1);
|
|
||||||
expect(interaction.reply).toHaveBeenCalledWith("Test error");
|
|
||||||
|
|
||||||
expect(EffectHelper.GenerateEffectBuyEmbed).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
expect(interaction.update).not.toHaveBeenCalled();
|
|
||||||
expect(AppLogger.LogError).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,50 +0,0 @@
|
||||||
import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, EmbedBuilder } from "discord.js";
|
|
||||||
import List from "../../../src/buttonEvents/Effects/List";
|
|
||||||
import EffectHelper from "../../../src/helpers/EffectHelper";
|
|
||||||
import { mock } from "jest-mock-extended";
|
|
||||||
|
|
||||||
jest.mock("../../../src/helpers/EffectHelper");
|
|
||||||
|
|
||||||
let interaction: ReturnType<typeof mock<ButtonInteraction>>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
|
|
||||||
(EffectHelper.GenerateEffectListEmbed as jest.Mock).mockResolvedValue({
|
|
||||||
embed: mock<EmbedBuilder>(),
|
|
||||||
row: mock<ActionRowBuilder<ButtonBuilder>>(),
|
|
||||||
});
|
|
||||||
|
|
||||||
interaction = mock<ButtonInteraction>();
|
|
||||||
interaction.user.id = "userId";
|
|
||||||
interaction.customId = "effects list 1";
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN pageOption is NOT a number, EXPECT error", async () => {
|
|
||||||
// Arrange
|
|
||||||
interaction.customId = "effects list invalid";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await List(interaction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(interaction.reply).toHaveBeenCalledTimes(1);
|
|
||||||
expect(interaction.reply).toHaveBeenCalledWith("Page option is not a valid number")
|
|
||||||
|
|
||||||
expect(EffectHelper.GenerateEffectListEmbed).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.update).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN pageOption is a number, EXPECT interaction updated", async () => {
|
|
||||||
// Arrange
|
|
||||||
interaction.customId = "effects list 1";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await List(interaction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(EffectHelper.GenerateEffectListEmbed).toHaveBeenCalledTimes(1);
|
|
||||||
expect(EffectHelper.GenerateEffectListEmbed).toHaveBeenCalledWith("userId", 1);
|
|
||||||
|
|
||||||
expect(interaction.update).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
|
@ -1,148 +0,0 @@
|
||||||
import { ButtonInteraction, InteractionResponse, InteractionUpdateOptions, MessagePayload } from "discord.js";
|
|
||||||
import Use from "../../../src/buttonEvents/Effects/Use";
|
|
||||||
import { mock } from "jest-mock-extended";
|
|
||||||
import AppLogger from "../../../src/client/appLogger";
|
|
||||||
import EffectHelper from "../../../src/helpers/EffectHelper";
|
|
||||||
|
|
||||||
jest.mock("../../../src/client/appLogger");
|
|
||||||
jest.mock("../../../src/helpers/EffectHelper");
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
|
|
||||||
jest.useFakeTimers();
|
|
||||||
jest.setSystemTime(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
jest.useRealTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Execute", () => {
|
|
||||||
test("GIVEN subaction is unknown, EXPECT nothing to be called", async () => {
|
|
||||||
// Arrange
|
|
||||||
const interaction = mock<ButtonInteraction>();
|
|
||||||
interaction.customId = "effects use invalud";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await Use.Execute(interaction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(interaction.reply).not.toHaveBeenCalled();
|
|
||||||
expect(interaction.update).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("UseConfirm", () => {
|
|
||||||
let interaction = mock<ButtonInteraction>();
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
interaction = mock<ButtonInteraction>();
|
|
||||||
interaction.customId = "effects use confirm";
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN effectDetail is not found, EXPECT error", async () => {
|
|
||||||
// Arrange
|
|
||||||
interaction.customId += " invalid";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await Use.Execute(interaction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Button/Effects/Use", "Effect not found, invalid");
|
|
||||||
|
|
||||||
expect(interaction.reply).toHaveBeenCalledTimes(1);
|
|
||||||
expect(interaction.reply).toHaveBeenCalledWith("Effect not found in system!");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN EffectHelper.UseEffect failed, EXPECT error", async () => {
|
|
||||||
// Arrange
|
|
||||||
interaction.customId += " unclaimed";
|
|
||||||
interaction.user.id = "userId";
|
|
||||||
|
|
||||||
(EffectHelper.UseEffect as jest.Mock).mockResolvedValue(false);
|
|
||||||
|
|
||||||
const whenExpires = new Date(Date.now() + 10 * 60 * 1000);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await Use.Execute(interaction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(EffectHelper.UseEffect).toHaveBeenCalledTimes(1);
|
|
||||||
expect(EffectHelper.UseEffect).toHaveBeenCalledWith("userId", "unclaimed", whenExpires);
|
|
||||||
|
|
||||||
expect(interaction.reply).toHaveBeenCalledTimes(1);
|
|
||||||
expect(interaction.reply).toHaveBeenCalledWith("Unable to use effect! Please make sure you have it in your inventory and is not on cooldown");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN EffectHelper.UseEffect succeeded, EXPECT interaction updated", async () => {
|
|
||||||
let updatedWith;
|
|
||||||
|
|
||||||
// Arrange
|
|
||||||
interaction.customId += " unclaimed";
|
|
||||||
interaction.user.id = "userId";
|
|
||||||
interaction.update.mockImplementation(async (opts: string | MessagePayload | InteractionUpdateOptions) => {
|
|
||||||
updatedWith = opts;
|
|
||||||
|
|
||||||
return mock<InteractionResponse<boolean>>();
|
|
||||||
});
|
|
||||||
|
|
||||||
(EffectHelper.UseEffect as jest.Mock).mockResolvedValue(true);
|
|
||||||
|
|
||||||
const whenExpires = new Date(Date.now() + 10 * 60 * 1000);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await Use.Execute(interaction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(EffectHelper.UseEffect).toHaveBeenCalledTimes(1);
|
|
||||||
expect(EffectHelper.UseEffect).toHaveBeenCalledWith("userId", "unclaimed", whenExpires);
|
|
||||||
|
|
||||||
expect(interaction.update).toHaveBeenCalledTimes(1);
|
|
||||||
expect(updatedWith).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("UseCancel", () => {
|
|
||||||
let interaction = mock<ButtonInteraction>();
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
interaction = mock<ButtonInteraction>();
|
|
||||||
interaction.customId = "effects use cancel";
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN effectDetail is not found, EXPECT error", async () => {
|
|
||||||
// Arrange
|
|
||||||
interaction.customId += " invalid";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await Use.Execute(interaction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledTimes(1);
|
|
||||||
expect(AppLogger.LogError).toHaveBeenCalledWith("Button/Effects/Cancel", "Effect not found, invalid");
|
|
||||||
|
|
||||||
expect(interaction.reply).toHaveBeenCalledTimes(1);
|
|
||||||
expect(interaction.reply).toHaveBeenCalledWith("Effect not found in system!");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("GIVEN effectDetail is found, EXPECT interaction updated", async () => {
|
|
||||||
let updatedWith;
|
|
||||||
|
|
||||||
// Arrange
|
|
||||||
interaction.customId += " unclaimed";
|
|
||||||
interaction.user.id = "userId";
|
|
||||||
interaction.update.mockImplementation(async (opts: string | MessagePayload | InteractionUpdateOptions) => {
|
|
||||||
updatedWith = opts;
|
|
||||||
|
|
||||||
return mock<InteractionResponse<boolean>>();
|
|
||||||
});
|
|
||||||
// Act
|
|
||||||
await Use.Execute(interaction);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(interaction.update).toHaveBeenCalledTimes(1);
|
|
||||||
expect(updatedWith).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,95 +0,0 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`UseCancel GIVEN effectDetail is found, EXPECT interaction updated 1`] = `
|
|
||||||
{
|
|
||||||
"components": [
|
|
||||||
{
|
|
||||||
"components": [
|
|
||||||
{
|
|
||||||
"custom_id": "effects use confirm unclaimed",
|
|
||||||
"disabled": true,
|
|
||||||
"emoji": undefined,
|
|
||||||
"label": "Confirm",
|
|
||||||
"style": 1,
|
|
||||||
"type": 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"custom_id": "effects use cancel unclaimed",
|
|
||||||
"disabled": true,
|
|
||||||
"emoji": undefined,
|
|
||||||
"label": "Cancel",
|
|
||||||
"style": 4,
|
|
||||||
"type": 2,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"type": 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"embeds": [
|
|
||||||
{
|
|
||||||
"color": 13882323,
|
|
||||||
"description": "The effect from your inventory has not been used",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"inline": true,
|
|
||||||
"name": "Effect",
|
|
||||||
"value": "Unclaimed Chance Up",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inline": true,
|
|
||||||
"name": "Expires",
|
|
||||||
"value": "10m",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"title": "Effect Use Cancelled",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`UseConfirm GIVEN EffectHelper.UseEffect succeeded, EXPECT interaction updated 1`] = `
|
|
||||||
{
|
|
||||||
"components": [
|
|
||||||
{
|
|
||||||
"components": [
|
|
||||||
{
|
|
||||||
"custom_id": "effects use confirm unclaimed",
|
|
||||||
"disabled": true,
|
|
||||||
"emoji": undefined,
|
|
||||||
"label": "Confirm",
|
|
||||||
"style": 1,
|
|
||||||
"type": 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"custom_id": "effects use cancel unclaimed",
|
|
||||||
"disabled": true,
|
|
||||||
"emoji": undefined,
|
|
||||||
"label": "Cancel",
|
|
||||||
"style": 4,
|
|
||||||
"type": 2,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"type": 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"embeds": [
|
|
||||||
{
|
|
||||||
"color": 2263842,
|
|
||||||
"description": "You now have an active effect!",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"inline": true,
|
|
||||||
"name": "Effect",
|
|
||||||
"value": "Unclaimed Chance Up",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inline": true,
|
|
||||||
"name": "Expires",
|
|
||||||
"value": "<t:600:f>",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"title": "Effect Used",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
`;
|
|
|
@ -1,13 +0,0 @@
|
||||||
describe("execute", () => {
|
|
||||||
describe("GIVEN randomCard image is hosted locally", () => {
|
|
||||||
test.todo("EXPECT image to be uploaded directly");
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("GIVEN randomCard image is hosted via http", () => {
|
|
||||||
test.todo("EXPECT image link to be directly added to embed");
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("GIVEN randomCard image is hosted via https", () => {
|
|
||||||
test.todo("EXPECT image link to be directly added to embed");
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,7 +0,0 @@
|
||||||
describe("GIVEN valid conditions", () => {
|
|
||||||
test.todo("EXPECT user.RemoveCurrency to be called");
|
|
||||||
|
|
||||||
test.todo("GIVEN user is saved");
|
|
||||||
});
|
|
||||||
|
|
||||||
test.todo("GIVEN user.RemoveCurrency fails, EXPECT error replied");
|
|
|
@ -1,106 +0,0 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`EXPECT CommandBuilder to be defined 1`] = `
|
|
||||||
{
|
|
||||||
"contexts": undefined,
|
|
||||||
"default_member_permissions": undefined,
|
|
||||||
"default_permission": undefined,
|
|
||||||
"description": "Effects",
|
|
||||||
"description_localizations": undefined,
|
|
||||||
"dm_permission": undefined,
|
|
||||||
"integration_types": undefined,
|
|
||||||
"name": "effects",
|
|
||||||
"name_localizations": undefined,
|
|
||||||
"nsfw": undefined,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"description": "List all effects I have",
|
|
||||||
"description_localizations": undefined,
|
|
||||||
"name": "list",
|
|
||||||
"name_localizations": undefined,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"autocomplete": undefined,
|
|
||||||
"choices": undefined,
|
|
||||||
"description": "The page number",
|
|
||||||
"description_localizations": undefined,
|
|
||||||
"max_value": undefined,
|
|
||||||
"min_value": 1,
|
|
||||||
"name": "page",
|
|
||||||
"name_localizations": undefined,
|
|
||||||
"required": false,
|
|
||||||
"type": 10,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"type": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Use an effect in your inventory",
|
|
||||||
"description_localizations": undefined,
|
|
||||||
"name": "use",
|
|
||||||
"name_localizations": undefined,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"autocomplete": undefined,
|
|
||||||
"choices": [
|
|
||||||
{
|
|
||||||
"name": "Unclaimed Chance Up",
|
|
||||||
"name_localizations": undefined,
|
|
||||||
"value": "unclaimed",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"description": "The effect id to use",
|
|
||||||
"description_localizations": undefined,
|
|
||||||
"max_length": undefined,
|
|
||||||
"min_length": undefined,
|
|
||||||
"name": "id",
|
|
||||||
"name_localizations": undefined,
|
|
||||||
"required": true,
|
|
||||||
"type": 3,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"type": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Buy more effects",
|
|
||||||
"description_localizations": undefined,
|
|
||||||
"name": "buy",
|
|
||||||
"name_localizations": undefined,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"autocomplete": undefined,
|
|
||||||
"choices": [
|
|
||||||
{
|
|
||||||
"name": "Unclaimed Chance Up",
|
|
||||||
"name_localizations": undefined,
|
|
||||||
"value": "unclaimed",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"description": "The effect id to buy",
|
|
||||||
"description_localizations": undefined,
|
|
||||||
"max_length": undefined,
|
|
||||||
"min_length": undefined,
|
|
||||||
"name": "id",
|
|
||||||
"name_localizations": undefined,
|
|
||||||
"required": true,
|
|
||||||
"type": 3,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"autocomplete": undefined,
|
|
||||||
"choices": undefined,
|
|
||||||
"description": "The amount to buy",
|
|
||||||
"description_localizations": undefined,
|
|
||||||
"max_value": undefined,
|
|
||||||
"min_value": 1,
|
|
||||||
"name": "quantity",
|
|
||||||
"name_localizations": undefined,
|
|
||||||
"required": false,
|
|
||||||
"type": 10,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"type": 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"type": 1,
|
|
||||||
}
|
|
||||||
`;
|
|
|
@ -1,142 +0,0 @@
|
||||||
import { CommandInteraction } from "discord.js";
|
|
||||||
import Drop from "../../src/commands/drop";
|
|
||||||
import GenerateCommandInteractionMock from "../__functions__/discord.js/GenerateCommandInteractionMock";
|
|
||||||
import { CommandInteraction as CommandInteractionMock } from "../__types__/discord.js";
|
|
||||||
import { CoreClient } from "../../src/client/client";
|
|
||||||
import Config from "../../src/database/entities/app/Config";
|
|
||||||
import User from "../../src/database/entities/app/User";
|
|
||||||
import GetCardsHelper from "../../src/helpers/DropHelpers/GetCardsHelper";
|
|
||||||
import Inventory from "../../src/database/entities/app/Inventory";
|
|
||||||
import DropEmbedHelper from "../../src/helpers/DropHelpers/DropEmbedHelper";
|
|
||||||
import CardConstants from "../../src/constants/CardConstants";
|
|
||||||
import * as uuid from "uuid";
|
|
||||||
|
|
||||||
jest.mock("../../src/database/entities/app/Config");
|
|
||||||
jest.mock("../../src/database/entities/app/User");
|
|
||||||
jest.mock("../../src/helpers/DropHelpers/GetCardsHelper");
|
|
||||||
jest.mock("../../src/database/entities/app/Inventory");
|
|
||||||
jest.mock("../../src/helpers/DropHelpers/DropEmbedHelper");
|
|
||||||
|
|
||||||
jest.mock("uuid");
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
(Config.GetValue as jest.Mock).mockResolvedValue("false");
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("execute", () => {
|
|
||||||
describe("GIVEN user is in the database", () => {
|
|
||||||
let interaction: CommandInteractionMock;
|
|
||||||
let user: User;
|
|
||||||
const randomCard = {
|
|
||||||
card: {
|
|
||||||
id: "cardId",
|
|
||||||
path: "https://google.com/",
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
// Arrange
|
|
||||||
CoreClient.AllowDrops = true;
|
|
||||||
|
|
||||||
interaction = GenerateCommandInteractionMock();
|
|
||||||
|
|
||||||
user = {
|
|
||||||
Currency: 500,
|
|
||||||
RemoveCurrency: jest.fn().mockReturnValue(true),
|
|
||||||
Save: jest.fn(),
|
|
||||||
} as unknown as User;
|
|
||||||
|
|
||||||
(User.FetchOneById as jest.Mock).mockResolvedValue(user);
|
|
||||||
(GetCardsHelper.FetchCard as jest.Mock).mockResolvedValue(randomCard);
|
|
||||||
(Inventory.FetchOneByCardNumberAndUserId as jest.Mock).mockResolvedValue({
|
|
||||||
Quantity: 1,
|
|
||||||
});
|
|
||||||
(DropEmbedHelper.GenerateDropEmbed as jest.Mock).mockReturnValue({
|
|
||||||
type: "Embed",
|
|
||||||
});
|
|
||||||
(DropEmbedHelper.GenerateDropButtons as jest.Mock).mockReturnValue({
|
|
||||||
type: "Button",
|
|
||||||
});
|
|
||||||
|
|
||||||
(uuid.v4 as jest.Mock).mockReturnValue("uuid");
|
|
||||||
|
|
||||||
// Act
|
|
||||||
const drop = new Drop();
|
|
||||||
await drop.execute(interaction as unknown as CommandInteraction);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("EXPECT user to be fetched", () => {
|
|
||||||
expect(User.FetchOneById).toHaveBeenCalledTimes(1);
|
|
||||||
expect(User.FetchOneById).toHaveBeenCalledWith(User, "userId");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("EXPECT user.RemoveCurrency to be called", () => {
|
|
||||||
expect(user.RemoveCurrency).toHaveBeenCalledTimes(1);
|
|
||||||
expect(user.RemoveCurrency).toHaveBeenCalledWith(CardConstants.ClaimCost);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("EXPECT user to be saved", () => {
|
|
||||||
expect(user.Save).toHaveBeenCalledTimes(1);
|
|
||||||
expect(user.Save).toHaveBeenCalledWith(User, user);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("EXPECT random card to be fetched", () => {
|
|
||||||
expect(GetCardsHelper.FetchCard).toHaveBeenCalledTimes(1);
|
|
||||||
expect(GetCardsHelper.FetchCard).toHaveBeenCalledWith("userId");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("EXPECT interaction to be deferred", () => {
|
|
||||||
expect(interaction.deferReply).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("EXPECT Inventory.FetchOneByCardNumberAndUserId to be called", () => {
|
|
||||||
expect(Inventory.FetchOneByCardNumberAndUserId).toHaveBeenCalledTimes(1);
|
|
||||||
expect(Inventory.FetchOneByCardNumberAndUserId).toHaveBeenCalledWith("userId", "cardId");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("EXPECT DropEmbedHelper.GenerateDropEmbed to be called", () => {
|
|
||||||
expect(DropEmbedHelper.GenerateDropEmbed).toHaveBeenCalledTimes(1);
|
|
||||||
expect(DropEmbedHelper.GenerateDropEmbed).toHaveBeenCalledWith(randomCard, 1, "", undefined, 500);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("EXPECT DropEmbedHelper.GenerateDropButtons to be called", () => {
|
|
||||||
expect(DropEmbedHelper.GenerateDropButtons).toHaveBeenCalledTimes(1);
|
|
||||||
expect(DropEmbedHelper.GenerateDropButtons).toHaveBeenCalledWith(randomCard, "uuid", "userId");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("EXPECT interaction to be edited", () => {
|
|
||||||
expect(interaction.editReply).toHaveBeenCalledTimes(1);
|
|
||||||
expect(interaction.editReply).toHaveBeenCalledWith({
|
|
||||||
embeds: [ { type: "Embed" } ],
|
|
||||||
files: [],
|
|
||||||
components: [ { type: "Button" } ],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("AND randomCard path is not a url", () => {
|
|
||||||
test.todo("EXPECT image read from file system");
|
|
||||||
|
|
||||||
test.todo("EXPECT files on the embed to contain the image as an attachment");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("GIVEN user is not in the database", () => {
|
|
||||||
test.todo("EXPECT new user to be created");
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("GIVEN user.RemoveCurrency fails", () => {
|
|
||||||
test.todo("EXPECT error replied");
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("GIVEN randomCard returns null", () => {
|
|
||||||
test.todo("EXPECT error logged");
|
|
||||||
|
|
||||||
test.todo("EXPECT error replied");
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("GIVEN the code throws an error", () => {
|
|
||||||
test.todo("EXPECT error logged");
|
|
||||||
|
|
||||||
test.todo("EXPECT interaction edited with error");
|
|
||||||
});
|
|
||||||
});
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue