Compare commits
59 commits
Author | SHA1 | Date | |
---|---|---|---|
Ethan Lane | 65a55895e7 | ||
Ethan Lane | 93b12bbbf6 | ||
Ethan Lane | e8b20004bd | ||
Ethan Lane | 5deb6fcc14 | ||
Ethan Lane | a10eaf7d75 | ||
Ethan Lane | 6025e2b269 | ||
Ethan Lane | f7a7a3781a | ||
Ethan Lane | a290eb945a | ||
Ethan Lane | 55e3f5e5dd | ||
Ethan Lane | ff9f3e458e | ||
Ethan Lane | 097b7284e6 | ||
Ethan Lane | fef80709ee | ||
RenovateBot | a55a5cf5da | ||
RenovateBot | 341f3d2bd8 | ||
Ethan Lane | b8cd73c570 | ||
Ethan Lane | f28254e407 | ||
RenovateBot | b0b478e120 | ||
RenovateBot | 47991395ef | ||
Ethan Lane | 9ea3bbe29d | ||
Ethan Lane | a1879839d9 | ||
Ethan Lane | c0bc71c304 | ||
Ethan Lane | 42e7bda1ce | ||
Ethan Lane | b6f814f895 | ||
Ethan Lane | 29bb22a819 | ||
Ethan Lane | acfdcb17f2 | ||
Ethan Lane | 1b9857dfe5 | ||
Ethan Lane | 1360452ffd | ||
RenovateBot | eb3b04f51c | ||
RenovateBot | eda11f74ae | ||
RenovateBot | bab48a11b9 | ||
Ethan Lane | b06fe11871 | ||
Ethan Lane | 5c317be4d5 | ||
Ethan Lane | 6a18f34949 | ||
RenovateBot | edf6c99bad | ||
RenovateBot | f888f9dd37 | ||
Ethan Lane | 2945638b16 | ||
Ethan Lane | 5751694018 | ||
Ethan Lane | 1a4993b091 | ||
Ethan Lane | 5d44c46222 | ||
Ethan Lane | a0a864ef44 | ||
Ethan Lane | 9ce4d49b6a | ||
RenovateBot | d7b56a72b9 | ||
RenovateBot | 1841b49da6 | ||
RenovateBot | 146f0dbf5a | ||
Ethan Lane | 599328a3c1 | ||
Ethan Lane | 90a7dbee39 | ||
Ethan Lane | 53656ba0da | ||
Ethan Lane | 27b6224b5e | ||
Ethan Lane | e584c1291b | ||
Ethan Lane | 9e366eab5d | ||
Ethan Lane | 7385a1bdb4 | ||
Ethan Lane | 9d8107d318 | ||
Ethan Lane | 976445fa0d | ||
Ethan Lane | 27a4019f00 | ||
Ethan Lane | f6c744cdcf | ||
Ethan Lane | a03a62277d | ||
Ethan Lane | c0ac18515f | ||
RenovateBot | 6561a1c998 | ||
RenovateBot | e9876570d2 |
|
@ -7,13 +7,17 @@
|
||||||
# any secret values.
|
# any secret values.
|
||||||
|
|
||||||
BOT_TOKEN=
|
BOT_TOKEN=
|
||||||
BOT_VER=0.6.2
|
BOT_VER=0.8.1
|
||||||
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_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=
|
||||||
|
|
|
@ -30,7 +30,7 @@ jobs:
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: node
|
runs-on: node
|
||||||
steps:
|
steps:
|
||||||
- uses: https://github.com/appleboy/ssh-action@v1.0.0
|
- uses: https://github.com/appleboy/ssh-action@v1.0.3
|
||||||
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 }}
|
||||||
|
@ -55,12 +55,16 @@ jobs:
|
||||||
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_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
|
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
|
||||||
script: |
|
script: |
|
||||||
source .sshrc \
|
source .sshrc \
|
||||||
&& cd /home/vylpes/apps/card-drop/card-drop_prod \
|
&& cd /home/vylpes/apps/card-drop/card-drop_prod \
|
||||||
|
|
|
@ -30,7 +30,7 @@ jobs:
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: node
|
runs-on: node
|
||||||
steps:
|
steps:
|
||||||
- uses: https://github.com/appleboy/ssh-action@v1.0.0
|
- uses: https://github.com/appleboy/ssh-action@v1.0.3
|
||||||
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 }}
|
||||||
|
@ -55,12 +55,16 @@ jobs:
|
||||||
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_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
|
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
|
||||||
script: |
|
script: |
|
||||||
source .sshrc \
|
source .sshrc \
|
||||||
&& cd /home/vylpes/apps/card-drop/card-drop_stage \
|
&& cd /home/vylpes/apps/card-drop/card-drop_stage \
|
||||||
|
|
60
README.md
60
README.md
|
@ -1,2 +1,60 @@
|
||||||
# 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
|
||||||
|
```
|
||||||
|
|
||||||
|
|
120
docs/cards.md
Normal file
120
docs/cards.md
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
# 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>
|
24
package.json
24
package.json
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
"name": "card-drop",
|
"name": "card-drop",
|
||||||
"version": "0.6.2",
|
"version": "0.8.1",
|
||||||
"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 --passWithNoTests",
|
"test": "echo true",
|
||||||
"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://gitea.vylpes.xyz/External/card-drop.git",
|
"repository": "https://git.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//gitea.vylpes.xyz/External/card-drop/issues",
|
"url": "https//git.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",
|
||||||
|
@ -31,26 +31,30 @@
|
||||||
"@types/jest": "^29.0.0",
|
"@types/jest": "^29.0.0",
|
||||||
"@types/uuid": "^9.0.0",
|
"@types/uuid": "^9.0.0",
|
||||||
"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",
|
"cron": "^3.1.7",
|
||||||
"discord.js": "^14.3.0",
|
"discord.js": "^14.15.3",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"glob": "^10.3.10",
|
"glob": "^10.3.10",
|
||||||
"jest": "^29.0.0",
|
"jest": "^29.0.0",
|
||||||
"jest-mock-extended": "^3.0.0",
|
"jest-mock-extended": "^3.0.0",
|
||||||
"minimatch": "9.0.4",
|
"jimp": "^0.22.12",
|
||||||
|
"minimatch": "9.0.5",
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"ts-jest": "^29.0.0",
|
"ts-jest": "^29.0.0",
|
||||||
"typeorm": "0.3.20",
|
"typeorm": "0.3.20",
|
||||||
"winston": "^3.11.0"
|
"winston": "^3.11.0",
|
||||||
|
"winston-daily-rotate-file": "^5.0.0",
|
||||||
|
"winston-discord-transport": "^1.3.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"resolutions": {
|
||||||
"undici": "^5.28.3"
|
"**/ws": "^8.17.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.0.0",
|
"@types/node": "^20.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.16.0",
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||||
"@typescript-eslint/parser": "^6.16.0",
|
"@typescript-eslint/parser": "^6.16.0",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
"np": "^9.0.0",
|
"np": "^9.0.0",
|
||||||
|
|
|
@ -5,6 +5,7 @@ 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;
|
||||||
|
@ -37,7 +38,22 @@ 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)
|
const duplicateCards = CoreClient.Cards.flatMap(x => x.cards)
|
||||||
.filter((card, index, self) => self.findIndex(c => c.id === card.id) !== index);
|
.filter((card, index, self) => self.findIndex(c => c.id === card.id) !== index);
|
||||||
|
|
|
@ -20,6 +20,14 @@ 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 * 5)); // 5 minutes ago
|
||||||
|
|
||||||
|
if (whenDropped < lastClaimableDate) {
|
||||||
|
await interaction.channel.send(`${interaction.user}, Cards can only be claimed within 5 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);
|
const user = await User.FetchOneById(User, userId) || new User(userId, CardConstants.StartingCurrency);
|
||||||
|
@ -31,8 +39,6 @@ export default class Claim extends ButtonEvent {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await user.Save(User, user);
|
|
||||||
|
|
||||||
const claimed = await eClaim.FetchOneByClaimId(claimId);
|
const claimed = await eClaim.FetchOneByClaimId(claimId);
|
||||||
|
|
||||||
if (claimed) {
|
if (claimed) {
|
||||||
|
@ -45,6 +51,8 @@ export default class Claim extends ButtonEvent {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await user.Save(User, user);
|
||||||
|
|
||||||
let inventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber);
|
let inventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber);
|
||||||
|
|
||||||
if (!inventory) {
|
if (!inventory) {
|
||||||
|
@ -68,7 +76,7 @@ export default class Claim extends ButtonEvent {
|
||||||
|
|
||||||
const imageFileName = card.card.path.split("/").pop()!;
|
const imageFileName = card.card.path.split("/").pop()!;
|
||||||
|
|
||||||
const embed = CardDropHelperMetadata.GenerateDropEmbed(card, inventory.Quantity, imageFileName, interaction.user.username);
|
const embed = CardDropHelperMetadata.GenerateDropEmbed(card, inventory.Quantity, imageFileName, interaction.user.username, user.Currency);
|
||||||
const row = CardDropHelperMetadata.GenerateDropButtons(card, claimId, interaction.user.id, true);
|
const row = CardDropHelperMetadata.GenerateDropButtons(card, claimId, interaction.user.id, true);
|
||||||
|
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
|
|
|
@ -12,6 +12,8 @@ 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) {
|
||||||
|
@ -24,14 +26,20 @@ 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));
|
||||||
|
|
||||||
await interaction.update({
|
if (!embed) {
|
||||||
|
await interaction.followUp("No page for user found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.editReply({
|
||||||
|
files: [ embed.image ],
|
||||||
embeds: [ embed.embed ],
|
embeds: [ embed.embed ],
|
||||||
components: [ embed.row ],
|
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.reply("No page for user found.");
|
await interaction.followUp("An error has occurred running this command.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -59,7 +59,7 @@ export default class Reroll extends ButtonEvent {
|
||||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
||||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
||||||
|
|
||||||
const embed = CardDropHelperMetadata.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName);
|
const embed = CardDropHelperMetadata.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName, undefined, user.Currency);
|
||||||
|
|
||||||
const claimId = v4();
|
const claimId = v4();
|
||||||
|
|
||||||
|
|
|
@ -24,11 +24,14 @@ 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];
|
||||||
|
|
||||||
const embed = SeriesHelper.GenerateSeriesViewPage(Number(seriesid), Number(page));
|
await interaction.deferUpdate();
|
||||||
|
|
||||||
await interaction.update({
|
const embed = await SeriesHelper.GenerateSeriesViewPage(Number(seriesid), Number(page), interaction.user.id);
|
||||||
|
|
||||||
|
await interaction.editReply({
|
||||||
embeds: [ embed!.embed ],
|
embeds: [ embed!.embed ],
|
||||||
components: [ embed!.row ],
|
components: [ embed!.row ],
|
||||||
|
files: [ embed!.image ],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,14 +22,14 @@ export default class Trade extends ButtonEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async AcceptTrade(interaction: ButtonInteraction) {
|
private async AcceptTrade(interaction: ButtonInteraction) {
|
||||||
const giveUserId = interaction.customId.split(" ")[2];
|
const user1UserId = interaction.customId.split(" ")[2];
|
||||||
const receiveUserId = interaction.customId.split(" ")[3];
|
const user2UserId = interaction.customId.split(" ")[3];
|
||||||
const giveCardNumber = interaction.customId.split(" ")[4];
|
const user1CardNumber = interaction.customId.split(" ")[4];
|
||||||
const receiveCardNumber = interaction.customId.split(" ")[5];
|
const user2CardNumber = interaction.customId.split(" ")[5];
|
||||||
const expiry = interaction.customId.split(" ")[6];
|
const expiry = interaction.customId.split(" ")[6];
|
||||||
const timeoutId = interaction.customId.split(" ")[7];
|
const timeoutId = interaction.customId.split(" ")[7];
|
||||||
|
|
||||||
AppLogger.LogSilly("Button/Trade/AcceptTrade", `Parameters: giveUserId=${giveUserId}, receiveUserId=${receiveUserId}, giveCardNumber=${giveCardNumber}, receiveCardNumber=${receiveCardNumber}, expiry=${expiry}, timeoutId=${timeoutId}`);
|
AppLogger.LogSilly("Button/Trade/AcceptTrade", `Parameters: user1UserId=${user1UserId}, user2UserId=${user2UserId}, user1CardNumber=${user1CardNumber}, user2CardNumber=${user2CardNumber}, expiry=${expiry}, timeoutId=${timeoutId}`);
|
||||||
|
|
||||||
const expiryDate = new Date(expiry);
|
const expiryDate = new Date(expiry);
|
||||||
|
|
||||||
|
@ -38,80 +38,80 @@ export default class Trade extends ButtonEvent {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interaction.user.id !== receiveUserId) {
|
if (interaction.user.id !== user2UserId) {
|
||||||
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 giveItem = CoreClient.Cards
|
const user1Item = CoreClient.Cards
|
||||||
.flatMap(x => x.cards)
|
.flatMap(x => x.cards)
|
||||||
.find(x => x.id === giveCardNumber);
|
.find(x => x.id === user1CardNumber);
|
||||||
|
|
||||||
const receiveItem = CoreClient.Cards
|
const user2Item = CoreClient.Cards
|
||||||
.flatMap(x => x.cards)
|
.flatMap(x => x.cards)
|
||||||
.find(x => x.id === receiveCardNumber);
|
.find(x => x.id === user2CardNumber);
|
||||||
|
|
||||||
if (!giveItem || !receiveItem) {
|
if (!user1Item || !user2Item) {
|
||||||
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 giveUser = interaction.client.users.cache.get(giveUserId) || await interaction.client.users.fetch(giveUserId);
|
const user1User = interaction.client.users.cache.get(user1UserId) || await interaction.client.users.fetch(user1UserId);
|
||||||
const receiveUser = interaction.client.users.cache.get(receiveUserId) || await interaction.client.users.fetch(receiveUserId);
|
const user2User = interaction.client.users.cache.get(user2UserId) || await interaction.client.users.fetch(user2UserId);
|
||||||
|
|
||||||
const giveUserInventory1 = await Inventory.FetchOneByCardNumberAndUserId(giveUserId, giveCardNumber);
|
const user1UserInventory1 = await Inventory.FetchOneByCardNumberAndUserId(user1UserId, user1CardNumber);
|
||||||
const receiveUserInventory1 = await Inventory.FetchOneByCardNumberAndUserId(receiveUserId, receiveCardNumber);
|
const user2UserInventory1 = await Inventory.FetchOneByCardNumberAndUserId(user2UserId, user2CardNumber);
|
||||||
|
|
||||||
if (!giveUserInventory1 || !receiveUserInventory1) {
|
if (!user1UserInventory1 || !user2UserInventory1) {
|
||||||
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 (giveUserInventory1.Quantity < 1 || receiveUserInventory1.Quantity < 1) {
|
if (user1UserInventory1.Quantity < 1 || user2UserInventory1.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
giveUserInventory1.SetQuantity(giveUserInventory1.Quantity - 1);
|
user1UserInventory1.SetQuantity(user1UserInventory1.Quantity - 1);
|
||||||
receiveUserInventory1.SetQuantity(receiveUserInventory1.Quantity - 1);
|
user2UserInventory1.SetQuantity(user2UserInventory1.Quantity - 1);
|
||||||
|
|
||||||
await giveUserInventory1.Save(Inventory, giveUserInventory1);
|
await user1UserInventory1.Save(Inventory, user1UserInventory1);
|
||||||
await receiveUserInventory1.Save(Inventory, receiveUserInventory1);
|
await user2UserInventory1.Save(Inventory, user2UserInventory1);
|
||||||
|
|
||||||
let giveUserInventory2 = await Inventory.FetchOneByCardNumberAndUserId(receiveUserId, giveCardNumber);
|
let user1UserInventory2 = await Inventory.FetchOneByCardNumberAndUserId(user1UserId, user2CardNumber);
|
||||||
let receiveUserInventory2 = await Inventory.FetchOneByCardNumberAndUserId(giveUserId, receiveCardNumber);
|
let user2UserInventory2 = await Inventory.FetchOneByCardNumberAndUserId(user2UserId, user1CardNumber);
|
||||||
|
|
||||||
if (!giveUserInventory2) {
|
if (!user1UserInventory2) {
|
||||||
giveUserInventory2 = new Inventory(receiveUserId, giveCardNumber, 1);
|
user1UserInventory2 = new Inventory(user1UserId, user1CardNumber, 1);
|
||||||
} else {
|
} else {
|
||||||
giveUserInventory2.SetQuantity(giveUserInventory2.Quantity + 1);
|
user1UserInventory2.SetQuantity(user1UserInventory2.Quantity + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!receiveUserInventory2) {
|
if (!user2UserInventory2) {
|
||||||
receiveUserInventory2 = new Inventory(giveUserId, receiveCardNumber, 1);
|
user2UserInventory2 = new Inventory(user2UserId, user2CardNumber, 1);
|
||||||
} else {
|
} else {
|
||||||
receiveUserInventory2.SetQuantity(receiveUserInventory2.Quantity + 1);
|
user2UserInventory2.SetQuantity(user2UserInventory2.Quantity + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
await giveUserInventory2.Save(Inventory, giveUserInventory2);
|
await user1UserInventory2.Save(Inventory, user1UserInventory2);
|
||||||
await receiveUserInventory2.Save(Inventory, receiveUserInventory2);
|
await user2UserInventory2.Save(Inventory, user2UserInventory2);
|
||||||
|
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
const tradeEmbed = new EmbedBuilder()
|
const tradeEmbed = new EmbedBuilder()
|
||||||
.setTitle("Trade Accepted")
|
.setTitle("Trade Accepted")
|
||||||
.setDescription(`Trade initiated between ${receiveUser.username} and ${giveUser.username}`)
|
.setDescription(`Trade initiated between ${user1User.username} and ${user2User.username}`)
|
||||||
.setColor(EmbedColours.Success)
|
.setColor(EmbedColours.Success)
|
||||||
.setImage("https://i.imgur.com/9w5f1ls.gif")
|
.setImage("https://i.imgur.com/9w5f1ls.gif")
|
||||||
.addFields([
|
.addFields([
|
||||||
{
|
{
|
||||||
name: "I receieve",
|
name: `${user1User.username} Receives`,
|
||||||
value: `${receiveItem.id}: ${receiveItem.name}`,
|
value: `${user2Item.id}: ${user2Item.name}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "You receieve",
|
name: `${user2User.username} Receives`,
|
||||||
value: `${giveItem.id}: ${giveItem.name}`,
|
value: `${user1Item.id}: ${user1Item.name}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -138,32 +138,32 @@ export default class Trade extends ButtonEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async DeclineTrade(interaction: ButtonInteraction) {
|
private async DeclineTrade(interaction: ButtonInteraction) {
|
||||||
const giveUserId = interaction.customId.split(" ")[2];
|
const user1UserId = interaction.customId.split(" ")[2];
|
||||||
const receiveUserId = interaction.customId.split(" ")[3];
|
const user2UserId = interaction.customId.split(" ")[3];
|
||||||
const giveCardNumber = interaction.customId.split(" ")[4];
|
const user1CardNumber = interaction.customId.split(" ")[4];
|
||||||
const receiveCardNumber = interaction.customId.split(" ")[5];
|
const user2CardNumber = interaction.customId.split(" ")[5];
|
||||||
// No need to get expiry date
|
// No need to get expiry date
|
||||||
const timeoutId = interaction.customId.split(" ")[7];
|
const timeoutId = interaction.customId.split(" ")[7];
|
||||||
|
|
||||||
AppLogger.LogSilly("Button/Trade/DeclineTrade", `Parameters: giveUserId=${giveUserId}, receiveUserId=${receiveUserId}, giveCardNumber=${giveCardNumber}, receiveCardNumber=${receiveCardNumber}, timeoutId=${timeoutId}`);
|
AppLogger.LogSilly("Button/Trade/DeclineTrade", `Parameters: user1UserId=${user1UserId}, user2UserId=${user2UserId}, user1CardNumber=${user1CardNumber}, user2CardNumber=${user2CardNumber}, timeoutId=${timeoutId}`);
|
||||||
|
|
||||||
if (interaction.user.id != receiveUserId && interaction.user.id !==giveUserId) {
|
if (interaction.user.id != user1UserId && interaction.user.id !== user2UserId) {
|
||||||
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 giveUser = interaction.client.users.cache.get(giveUserId) || await interaction.client.users.fetch(giveUserId);
|
const user1User = interaction.client.users.cache.get(user1UserId) || await interaction.client.users.fetch(user1UserId);
|
||||||
const receiveUser = interaction.client.users.cache.get(receiveUserId) || await interaction.client.users.fetch(receiveUserId);
|
const user2User = interaction.client.users.cache.get(user2UserId) || await interaction.client.users.fetch(user2UserId);
|
||||||
|
|
||||||
const giveItem = CoreClient.Cards
|
const user1Item = CoreClient.Cards
|
||||||
.flatMap(x => x.cards)
|
.flatMap(x => x.cards)
|
||||||
.find(x => x.id === giveCardNumber);
|
.find(x => x.id === user1CardNumber);
|
||||||
|
|
||||||
const receiveItem = CoreClient.Cards
|
const user2Item = CoreClient.Cards
|
||||||
.flatMap(x => x.cards)
|
.flatMap(x => x.cards)
|
||||||
.find(x => x.id === receiveCardNumber);
|
.find(x => x.id === user2CardNumber);
|
||||||
|
|
||||||
if (!giveItem || !receiveItem) {
|
if (!user1Item || !user2Item) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
@ -172,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 ${receiveUser.username} and ${giveUser.username}`)
|
.setDescription(`Trade initiated between ${user1User.username} and ${user2User.username}`)
|
||||||
.setColor(EmbedColours.Error)
|
.setColor(EmbedColours.Error)
|
||||||
.setImage("https://i.imgur.com/9w5f1ls.gif")
|
.setImage("https://i.imgur.com/9w5f1ls.gif")
|
||||||
.addFields([
|
.addFields([
|
||||||
{
|
{
|
||||||
name: "I Receive",
|
name: `${user1User.username} Receives`,
|
||||||
value: `${receiveItem.id}: ${receiveItem.name}`,
|
value: `${user2Item.id}: ${user2Item.name}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "You Receive",
|
name: `${user2User.username} Receives`,
|
||||||
value: `${giveItem.id}: ${giveItem.name}`,
|
value: `${user1Item.id}: ${user1Item.name}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
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;
|
||||||
|
@ -19,12 +22,21 @@ 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(
|
||||||
|
@ -34,6 +46,18 @@ 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}`);
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { SeriesMetadata } from "../contracts/SeriesMetadata";
|
||||||
import AppLogger from "./appLogger";
|
import AppLogger from "./appLogger";
|
||||||
import TimerHelper from "../helpers/TimerHelper";
|
import TimerHelper from "../helpers/TimerHelper";
|
||||||
import GiveCurrency from "../timers/GiveCurrency";
|
import GiveCurrency from "../timers/GiveCurrency";
|
||||||
|
import PurgeClaims from "../timers/PurgeClaims";
|
||||||
|
|
||||||
export class CoreClient extends Client {
|
export class CoreClient extends Client {
|
||||||
private static _commandItems: ICommandItem[];
|
private static _commandItems: ICommandItem[];
|
||||||
|
@ -79,8 +80,10 @@ export class CoreClient extends Client {
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppLogger.LogInfo("Client", "App Data Source Initialised");
|
AppLogger.LogInfo("Client", "App Data Source Initialised");
|
||||||
|
|
||||||
const timerId = this._timerHelper.AddTimer("*/20 * * * *", "Europe/London", GiveCurrency, false);
|
this._timerHelper.AddTimer("*/20 * * * *", "Europe/London", GiveCurrency, false);
|
||||||
this._timerHelper.StartTimer(timerId);
|
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");
|
||||||
|
|
|
@ -2,11 +2,14 @@ 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";
|
||||||
|
|
||||||
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);
|
||||||
|
|
15
src/client/interactionCreate/middleware/NewUserDiscovery.ts
Normal file
15
src/client/interactionCreate/middleware/NewUserDiscovery.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
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}`);
|
||||||
|
}
|
29
src/commands/allbalance.ts
Normal file
29
src/commands/allbalance.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
}
|
|
@ -67,7 +67,7 @@ export default class Drop extends Command {
|
||||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
||||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
||||||
|
|
||||||
const embed = CardDropHelperMetadata.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName);
|
const embed = CardDropHelperMetadata.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName, undefined, user.Currency);
|
||||||
|
|
||||||
const claimId = v4();
|
const claimId = v4();
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,8 @@ export default class Inventory extends Command {
|
||||||
|
|
||||||
const user = userOption ? userOption.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}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -37,14 +39,20 @@ 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);
|
||||||
|
|
||||||
await interaction.reply({
|
if (!embed) {
|
||||||
|
await interaction.followUp("No page for user found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.followUp({
|
||||||
|
files: [ embed.image ],
|
||||||
embeds: [ embed.embed ],
|
embeds: [ embed.embed ],
|
||||||
components: [ embed.row ],
|
components: [ embed.row ],
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
AppLogger.LogError("Commands/Inventory", e as string);
|
AppLogger.LogError("Commands/Inventory", e as string);
|
||||||
|
|
||||||
await interaction.reply("No page for user found.");
|
await interaction.followUp("An error has occurred running this command.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -47,6 +47,8 @@ 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);
|
||||||
|
@ -54,13 +56,17 @@ 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.reply("Series not found.");
|
await interaction.followUp("Series not found.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const embed = SeriesHelper.GenerateSeriesViewPage(series.id, 0);
|
const embed = await SeriesHelper.GenerateSeriesViewPage(series.id, 0, interaction.user.id);
|
||||||
|
|
||||||
await interaction.reply({ embeds: [ embed!.embed ], components: [ embed!.row ]});
|
await interaction.followUp({
|
||||||
|
embeds: [ embed!.embed ],
|
||||||
|
components: [ embed!.row ],
|
||||||
|
files: [ embed!.image ],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ListSeries(interaction: CommandInteraction) {
|
private async ListSeries(interaction: CommandInteraction) {
|
||||||
|
|
51
src/commands/stats.ts
Normal file
51
src/commands/stats.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,28 +41,28 @@ export default class Trade extends Command {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const giveItemEntity = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, give.value!.toString());
|
const user1ItemEntity = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, give.value!.toString());
|
||||||
const receiveItemEntity = await Inventory.FetchOneByCardNumberAndUserId(user.id, receive.value!.toString());
|
const user2ItemEntity = await Inventory.FetchOneByCardNumberAndUserId(user.id, receive.value!.toString());
|
||||||
|
|
||||||
if (!giveItemEntity) {
|
if (!user1ItemEntity) {
|
||||||
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 (!receiveItemEntity) {
|
if (!user2ItemEntity) {
|
||||||
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 giveItem = CoreClient.Cards
|
const user1Item = 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 receiveItem = CoreClient.Cards
|
const user2Item = 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 (!giveItem || !receiveItem) {
|
if (!user1Item || !user2Item) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
@ -77,13 +77,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: "I Receive",
|
name: `${interaction.user.username} Receives`,
|
||||||
value: `${receiveItem.id}: ${receiveItem.name}`,
|
value: `${user2Item.id}: ${user2Item.name}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "You Receive",
|
name: `${user.username} Receives`,
|
||||||
value: `${giveItem.id}: ${giveItem.name}`,
|
value: `${user1Item.id}: ${user1Item.name}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -92,16 +92,16 @@ export default class Trade extends Command {
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
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 timeoutId = setTimeout(async () => this.autoDecline(interaction, interaction.user.username, user.username, user1Item.id, user2Item.id, user1Item.name, user2Item.name), 1000 * 60 * 15); // 15 minutes
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||||
.addComponents([
|
.addComponents([
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`trade accept ${interaction.user.id} ${user.id} ${giveItem.id} ${receiveItem.id} ${expiry} ${timeoutId}`)
|
.setCustomId(`trade accept ${interaction.user.id} ${user.id} ${user1Item.id} ${user2Item.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} ${giveItem.id} ${receiveItem.id} ${expiry} ${timeoutId}`)
|
.setCustomId(`trade decline ${interaction.user.id} ${user.id} ${user1Item.id} ${user2Item.id} ${expiry} ${timeoutId}`)
|
||||||
.setLabel("Decline")
|
.setLabel("Decline")
|
||||||
.setStyle(ButtonStyle.Danger),
|
.setStyle(ButtonStyle.Danger),
|
||||||
]);
|
]);
|
||||||
|
@ -109,23 +109,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, giveUsername: string, receiveUsername: string, giveCardNumber: string, receiveCardNumber: string, giveCardName: string, receiveCardName: string) {
|
private async autoDecline(interaction: CommandInteraction, user1Username: string, user2Username: string, user1CardNumber: string, user2CardNumber: string, user1CardName: string, user2CardName: string) {
|
||||||
AppLogger.LogSilly("Commands/Trade/AutoDecline", `Auto declining trade between ${giveUsername} and ${receiveUsername}`);
|
AppLogger.LogSilly("Commands/Trade/AutoDecline", `Auto declining trade between ${user1Username} and ${user2Username}`);
|
||||||
|
|
||||||
const tradeEmbed = new EmbedBuilder()
|
const tradeEmbed = new EmbedBuilder()
|
||||||
.setTitle("Trade Expired")
|
.setTitle("Trade Expired")
|
||||||
.setDescription(`Trade initiated between ${receiveUsername} and ${giveUsername}`)
|
.setDescription(`Trade initiated between ${user1Username} and ${user2Username}`)
|
||||||
.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: "I Receive",
|
name: `${user1Username} Receives`,
|
||||||
value: `${receiveCardNumber}: ${receiveCardName}`,
|
value: `${user2CardNumber}: ${user2CardName}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "You Receive",
|
name: `${user2Username} Receives`,
|
||||||
value: `${giveCardNumber}: ${giveCardName}`,
|
value: `${user1CardNumber}: ${user1CardName}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -39,6 +39,12 @@ export default class AppBaseEntity {
|
||||||
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,6 +11,8 @@ 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 {
|
||||||
|
|
|
@ -4,6 +4,8 @@ import CardRarityChances from "../constants/CardRarityChances";
|
||||||
import { DropResult } from "../contracts/SeriesMetadata";
|
import { DropResult } from "../contracts/SeriesMetadata";
|
||||||
import { CoreClient } from "../client/client";
|
import { CoreClient } from "../client/client";
|
||||||
import AppLogger from "../client/appLogger";
|
import AppLogger from "../client/appLogger";
|
||||||
|
import CardConstants from "../constants/CardConstants";
|
||||||
|
import StringTools from "./StringTools";
|
||||||
|
|
||||||
export default class CardDropHelperMetadata {
|
export default class CardDropHelperMetadata {
|
||||||
public static GetRandomCard(): DropResult | undefined {
|
public static GetRandomCard(): DropResult | undefined {
|
||||||
|
@ -77,25 +79,59 @@ export default class CardDropHelperMetadata {
|
||||||
return { card, series };
|
return { card, series };
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GenerateDropEmbed(drop: DropResult, quantityClaimed: number, imageFileName: string, claimedBy?: string): EmbedBuilder {
|
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}`);
|
AppLogger.LogSilly("CardDropHelperMetadata/GenerateDropEmbed", `Parameters: drop=${drop.card.id}, quantityClaimed=${quantityClaimed}, imageFileName=${imageFileName}`);
|
||||||
|
|
||||||
let description = "";
|
const description = drop.card.subseries ?? drop.series.name;
|
||||||
description += `Series: ${drop.series.name}\n`;
|
let colour = CardRarityToColour(drop.card.type);
|
||||||
description += `Claimed: ${quantityClaimed}\n`;
|
|
||||||
|
|
||||||
if (claimedBy != null) {
|
if (drop.card.colour && StringTools.IsHexCode(drop.card.colour)) {
|
||||||
description += `Claimed by: ${claimedBy}\n`;
|
const hexCode = Number("0x" + drop.card.colour);
|
||||||
|
|
||||||
|
if (hexCode) {
|
||||||
|
colour = hexCode;
|
||||||
} else {
|
} else {
|
||||||
description += "Claimed by: (UNCLAIMED)\n";
|
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}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle(drop.card.name)
|
.setTitle(drop.card.name)
|
||||||
.setDescription(description)
|
.setDescription(description)
|
||||||
.setFooter({ text: `${CardRarityToString(drop.card.type)} · ${drop.card.id}` })
|
.setFooter({ text: `${CardRarityToString(drop.card.type)} · ${drop.card.id}` })
|
||||||
.setColor(CardRarityToColour(drop.card.type))
|
.setColor(colour)
|
||||||
.setImage(`attachment://${imageFileName}`);
|
.setImage(`attachment://${imageFileName}`)
|
||||||
|
.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> {
|
public static GenerateDropButtons(drop: DropResult, claimId: string, userId: string, disabled: boolean = false): ActionRowBuilder<ButtonBuilder> {
|
||||||
|
@ -105,7 +141,7 @@ export default class CardDropHelperMetadata {
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`claim ${drop.card.id} ${claimId} ${userId}`)
|
.setCustomId(`claim ${drop.card.id} ${claimId} ${userId}`)
|
||||||
.setLabel("Claim")
|
.setLabel(`Claim (${CardConstants.ClaimCost} 🪙)`)
|
||||||
.setStyle(ButtonStyle.Primary)
|
.setStyle(ButtonStyle.Primary)
|
||||||
.setDisabled(disabled),
|
.setDisabled(disabled),
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
|
|
62
src/helpers/ImageHelper.ts
Normal file
62
src/helpers/ImageHelper.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
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 Jimp from "jimp";
|
||||||
|
|
||||||
|
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++) {
|
||||||
|
const card = cards[i];
|
||||||
|
|
||||||
|
const filePath = path.join(process.env.DATA_DIR!, "cards", card.path);
|
||||||
|
|
||||||
|
const exists = existsSync(filePath);
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
AppLogger.LogError("ImageHelper/GenerateCardImageGrid", `Failed to load image from path ${card.path}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageData = await Jimp.read(filePath);
|
||||||
|
|
||||||
|
if (userId != null) {
|
||||||
|
const claimed = await Inventory.FetchOneByCardNumberAndUserId(userId, card.id);
|
||||||
|
|
||||||
|
if (!claimed || claimed.Quantity == 0) {
|
||||||
|
imageData.greyscale();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const image = await loadImage(await imageData.getBufferAsync("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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return canvas.toBuffer();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,11 @@
|
||||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
import { ActionRowBuilder, AttachmentBuilder, 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,
|
||||||
|
@ -18,16 +19,26 @@ interface InventoryPageCards {
|
||||||
name: string,
|
name: string,
|
||||||
type: CardRarity,
|
type: CardRarity,
|
||||||
quantity: number,
|
quantity: number,
|
||||||
|
path: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ReturnedInventoryPage {
|
||||||
|
embed: EmbedBuilder,
|
||||||
|
row: ActionRowBuilder<ButtonBuilder>,
|
||||||
|
image: AttachmentBuilder,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export default class InventoryHelper {
|
export default class InventoryHelper {
|
||||||
public static async GenerateInventoryPage(username: string, userid: string, page: number): Promise<{ embed: EmbedBuilder, row: ActionRowBuilder<ButtonBuilder> }> {
|
public static async GenerateInventoryPage(username: string, userid: string, page: number): Promise<ReturnedInventoryPage | undefined> {
|
||||||
AppLogger.LogSilly("Helpers/InventoryHelper", `Parameters: username=${username}, userid=${userid}, page=${page}`);
|
AppLogger.LogSilly("Helpers/InventoryHelper", `Parameters: username=${username}, userid=${userid}, page=${page}`);
|
||||||
|
|
||||||
const cardsPerPage = 15;
|
const cardsPerPage = 9;
|
||||||
|
|
||||||
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
|
||||||
|
@ -62,6 +73,7 @@ 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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,15 +89,15 @@ export default class InventoryHelper {
|
||||||
const currentPage = pages[page];
|
const currentPage = pages[page];
|
||||||
|
|
||||||
if (!currentPage) {
|
if (!currentPage) {
|
||||||
AppLogger.LogError("Helpers/InventoryHelper", "Unable to find page");
|
return undefined;
|
||||||
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 row = new ActionRowBuilder<ButtonBuilder>()
|
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
|
@ -100,6 +112,9 @@ export default class InventoryHelper {
|
||||||
.setStyle(ButtonStyle.Primary)
|
.setStyle(ButtonStyle.Primary)
|
||||||
.setDisabled(page + 1 == pages.length));
|
.setDisabled(page + 1 == pages.length));
|
||||||
|
|
||||||
return { embed, row };
|
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, row, image };
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,15 +1,16 @@
|
||||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
import { ActionRowBuilder, AttachmentBuilder, 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 GenerateSeriesViewPage(seriesId: number, page: number): { embed: EmbedBuilder, row: ActionRowBuilder<ButtonBuilder> } | null {
|
public static async GenerateSeriesViewPage(seriesId: number, page: number, userId: string): Promise<{ embed: EmbedBuilder, row: ActionRowBuilder<ButtonBuilder>, image: AttachmentBuilder } | null> {
|
||||||
AppLogger.LogSilly("Helpers/SeriesHelper", `Parameters: seriesId=${seriesId}, page=${page}`);
|
AppLogger.LogSilly("Helpers/SeriesHelper", `Parameters: seriesId=${seriesId}, page=${page}`);
|
||||||
|
|
||||||
const itemsPerPage = 15;
|
const itemsPerPage = 9;
|
||||||
|
|
||||||
const series = cloneDeep(CoreClient.Cards)
|
const series = cloneDeep(CoreClient.Cards)
|
||||||
.find(x => x.id == seriesId);
|
.find(x => x.id == seriesId);
|
||||||
|
@ -20,6 +21,7 @@ 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`);
|
||||||
|
@ -29,14 +31,15 @@ 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).toUpperCase()}`)
|
.map(x => `[${x.id}] ${x.name} (${CardRarityToString(x.type)})`)
|
||||||
.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} · ${series.cards.length} cards · Page ${page + 1} of ${totalPages}` });
|
.setFooter({ text: `${series.id} · ${totalCards} cards · Page ${page + 1} of ${totalPages}` })
|
||||||
|
.setImage("attachment://page.png");
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
|
@ -49,9 +52,12 @@ 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));
|
||||||
|
|
||||||
return { embed, row };
|
const buffer = await ImageHelper.GenerateCardImageGrid(cardsOnPage.map(x => ({id: x.id, path: x.path})), userId);
|
||||||
|
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 {
|
||||||
|
@ -72,7 +78,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}`)
|
.map(x => `[${x.id}] ${x.name} (${x.cards.length} cards)`)
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
|
@ -92,7 +98,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,4 +39,18 @@ 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;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -3,6 +3,7 @@ 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 Balance from "./commands/balance";
|
||||||
import Daily from "./commands/daily";
|
import Daily from "./commands/daily";
|
||||||
import Drop from "./commands/drop";
|
import Drop from "./commands/drop";
|
||||||
|
@ -12,6 +13,7 @@ import Inventory from "./commands/inventory";
|
||||||
import Resync from "./commands/resync";
|
import Resync from "./commands/resync";
|
||||||
import Sacrifice from "./commands/sacrifice";
|
import Sacrifice from "./commands/sacrifice";
|
||||||
import Series from "./commands/series";
|
import Series from "./commands/series";
|
||||||
|
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";
|
||||||
|
|
||||||
|
@ -31,6 +33,7 @@ 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("balance", new Balance());
|
||||||
CoreClient.RegisterCommand("daily", new Daily());
|
CoreClient.RegisterCommand("daily", new Daily());
|
||||||
CoreClient.RegisterCommand("drop", new Drop());
|
CoreClient.RegisterCommand("drop", new Drop());
|
||||||
|
@ -40,6 +43,7 @@ export default class Registry {
|
||||||
CoreClient.RegisterCommand("resync", new Resync());
|
CoreClient.RegisterCommand("resync", new Resync());
|
||||||
CoreClient.RegisterCommand("sacrifice", new Sacrifice());
|
CoreClient.RegisterCommand("sacrifice", new Sacrifice());
|
||||||
CoreClient.RegisterCommand("series", new Series());
|
CoreClient.RegisterCommand("series", new Series());
|
||||||
|
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());
|
||||||
|
|
||||||
|
|
|
@ -3,15 +3,17 @@ import CardConstants from "../constants/CardConstants";
|
||||||
import User from "../database/entities/app/User";
|
import User from "../database/entities/app/User";
|
||||||
|
|
||||||
export default async function GiveCurrency() {
|
export default async function GiveCurrency() {
|
||||||
AppLogger.LogInfo("Timers/GiveCurrency", "Giving currency to every known user");
|
AppLogger.LogDebug("Timers/GiveCurrency", "Giving currency to every known user");
|
||||||
|
|
||||||
const users = await User.FetchAll(User);
|
const users = await User.FetchAll(User);
|
||||||
|
|
||||||
for (const user of users) {
|
const usersFiltered = users.filter(x => x.Currency < 1000);
|
||||||
|
|
||||||
|
for (const user of usersFiltered) {
|
||||||
user.AddCurrency(CardConstants.TimerGiveAmount);
|
user.AddCurrency(CardConstants.TimerGiveAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
User.SaveAll(User, users);
|
User.SaveAll(User, users);
|
||||||
|
|
||||||
AppLogger.LogInfo("Timers/GiveCurrency", `Successfully gave +${CardConstants.TimerGiveAmount} currency to ${users.length} users`);
|
AppLogger.LogDebug("Timers/GiveCurrency", `Successfully gave +${CardConstants.TimerGiveAmount} currency to ${usersFiltered.length} users`);
|
||||||
}
|
}
|
14
src/timers/PurgeClaims.ts
Normal file
14
src/timers/PurgeClaims.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
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 * 5)); // 5 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`);
|
||||||
|
}
|
66
tests/registry.test.ts
Normal file
66
tests/registry.test.ts
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import {CoreClient} from "../src/client/client";
|
||||||
|
import Registry from "../src/registry";
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
describe("RegisterCommands", () => {
|
||||||
|
test("EXPECT every command in the commands folder to be registered", () => {
|
||||||
|
const registeredCommands: string[] = [];
|
||||||
|
|
||||||
|
CoreClient.RegisterCommand = jest.fn().mockImplementation((name: string) => {
|
||||||
|
registeredCommands.push(name);
|
||||||
|
});
|
||||||
|
|
||||||
|
Registry.RegisterCommands();
|
||||||
|
|
||||||
|
const commandFiles = getFilesInDirectory(path.join(process.cwd(), "src", "commands"))
|
||||||
|
.filter(x => x.endsWith(".ts"));
|
||||||
|
|
||||||
|
for (const file of commandFiles) {
|
||||||
|
expect(registeredCommands).toContain(file.split("/").pop()!.split(".")[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(commandFiles.length).toBe(registeredCommands.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("RegisterButtonEvents", () => {
|
||||||
|
test("EXEPCT every button event in the button events folder to be registered", () => {
|
||||||
|
const registeredButtonEvents: string[] = [];
|
||||||
|
|
||||||
|
CoreClient.RegisterButtonEvent = jest.fn().mockImplementation((name: string) => {
|
||||||
|
registeredButtonEvents.push(name);
|
||||||
|
});
|
||||||
|
|
||||||
|
Registry.RegisterButtonEvents();
|
||||||
|
|
||||||
|
const eventFiles = getFilesInDirectory(path.join(process.cwd(), "src", "buttonEvents"))
|
||||||
|
.filter(x => x.endsWith(".ts"));
|
||||||
|
|
||||||
|
for (const file of eventFiles) {
|
||||||
|
expect(registeredButtonEvents).toContain(file.split("/").pop()!.split(".")[0].toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(eventFiles.length).toBe(registeredButtonEvents.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function getFilesInDirectory(dir: string): string[] {
|
||||||
|
let results: string[] = [];
|
||||||
|
const list = fs.readdirSync(dir);
|
||||||
|
|
||||||
|
list.forEach(file => {
|
||||||
|
file = path.join(dir, file);
|
||||||
|
const stat = fs.statSync(file);
|
||||||
|
|
||||||
|
if (stat && stat.isDirectory()) {
|
||||||
|
/* recurse into a subdirectory */
|
||||||
|
results = results.concat(getFilesInDirectory(file));
|
||||||
|
} else {
|
||||||
|
/* is a file */
|
||||||
|
results.push(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
Loading…
Reference in a new issue