Compare commits
No commits in common. "main" and "v0.1.2" have entirely different histories.
|
@ -7,34 +7,23 @@
|
|||
# any secret values.
|
||||
|
||||
BOT_TOKEN=
|
||||
BOT_VER=0.8.1
|
||||
BOT_VER=0.1.2 DEV
|
||||
BOT_AUTHOR=Vylpes
|
||||
BOT_OWNERID=147392775707426816
|
||||
BOT_CLIENTID=682942374040961060
|
||||
BOT_ENV=4
|
||||
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_REPO=
|
||||
|
||||
DATA_DIR=./.temp
|
||||
DROP_RARITY=-1
|
||||
DROP_CARD=-1
|
||||
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3301
|
||||
DB_NAME=carddrop
|
||||
DB_AUTH_USER=
|
||||
DB_AUTH_PASS=
|
||||
DB_SYNC=
|
||||
DB_LOGGING=
|
||||
DB_DATA_LOCATION=./.temp/database
|
||||
DB_AUTH_USER=dev
|
||||
DB_AUTH_PASS=dev
|
||||
DB_SYNC=true
|
||||
DB_LOGGING=true
|
||||
|
||||
DB_CARD_FILE=:memory:
|
||||
|
||||
EXPRESS_PORT=3302
|
||||
|
||||
GDRIVESYNC_AUTO=true
|
86
.drone.yml
86
.drone.yml
|
@ -4,36 +4,16 @@ kind: pipeline
|
|||
name: deployment
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: node
|
||||
commands:
|
||||
- npm ci
|
||||
- npm run build
|
||||
- name: test
|
||||
image: node
|
||||
commands:
|
||||
- npm test
|
||||
- name: deploy to prod
|
||||
image: drillster/drone-rsync
|
||||
secrets: [ ssh_key, bot_token_prod ]
|
||||
- name: deploy
|
||||
image: appleboy/drone-ssh
|
||||
settings:
|
||||
hosts:
|
||||
- 192.168.68.120
|
||||
user: vylpes
|
||||
key:
|
||||
from_secret: ssh_key
|
||||
source: .
|
||||
target: ~/apps/card-drop/card-drop_prod
|
||||
recursive: true
|
||||
host: 192.168.68.121
|
||||
username: vylpes
|
||||
password:
|
||||
from_secret: ssh_password
|
||||
port: 22
|
||||
script:
|
||||
- export PATH="$HOME/.yarn/bin:$PATH"
|
||||
- export PATH="$HOME/.nodeuse/bin:$PATH"
|
||||
- export BOT_TOKEN="$BOT_TOKEN_PROD"
|
||||
- cd ~/apps/card-drop/card-drop_prod
|
||||
- docker compose --file docker-compose.prod.yml up -d
|
||||
- sleep 10
|
||||
- cp .prod.env .env
|
||||
- pm2 restart card-drop_prod || pm2 start --name card-drop_prod dist/bot.js
|
||||
- sh /home/vylpes/scripts/card-drop/deploy_prod.sh
|
||||
|
||||
trigger:
|
||||
event:
|
||||
|
@ -45,36 +25,16 @@ kind: pipeline
|
|||
name: staging
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: node
|
||||
commands:
|
||||
- npm ci
|
||||
- npm run build
|
||||
- name: test
|
||||
image: node
|
||||
commands:
|
||||
- npm test
|
||||
- name: deploy to stage
|
||||
image: drillster/drone-rsync
|
||||
secrets: [ ssh_key, bot_token_stage ]
|
||||
- name: stage
|
||||
image: appleboy/drone-ssh
|
||||
settings:
|
||||
hosts:
|
||||
- 192.168.68.120
|
||||
user: vylpes
|
||||
key:
|
||||
from_secret: ssh_key
|
||||
source: .
|
||||
target: ~/apps/card-drop/card-drop_stage
|
||||
recursive: true
|
||||
host: 192.168.68.121
|
||||
username: vylpes
|
||||
password:
|
||||
from_secret: ssh_password
|
||||
port: 22
|
||||
script:
|
||||
- export PATH="$HOME/.yarn/bin:$PATH"
|
||||
- export PATH="$HOME/.nodeuse/bin:$PATH"
|
||||
- export BOT_TOKEN="$BOT_TOKEN_STAGE"
|
||||
- cd ~/apps/card-drop/card-drop_stage
|
||||
- docker compose --file docker-compose.stage.yml up -d
|
||||
- sleep 10
|
||||
- cp .stage.env .env
|
||||
- pm2 restart card-drop_stage || pm2 start --name card-drop_stage dist/bot.js
|
||||
- sh /home/vylpes/scripts/card-drop/deploy_stage.sh
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
|
@ -91,21 +51,19 @@ steps:
|
|||
- name: build
|
||||
image: node
|
||||
commands:
|
||||
- npm ci
|
||||
- npm run build
|
||||
|
||||
- name: lint
|
||||
image: node
|
||||
commands:
|
||||
- npm run lint
|
||||
- yarn install --frozen-lockfile
|
||||
- yarn build
|
||||
|
||||
- name: test
|
||||
image: node
|
||||
commands:
|
||||
- npm test
|
||||
- yarn install --frozen-lockfile
|
||||
- yarn test
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
- develop
|
||||
- hotfix/*
|
||||
- feature/*
|
||||
- renovate/*
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
4
|
||||
],
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"quotes": [
|
||||
"error",
|
||||
"double"
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
]
|
||||
},
|
||||
"globals": {
|
||||
"jest": true,
|
||||
"require": true,
|
||||
"exports": true,
|
||||
"process": true
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"dist/**/*"
|
||||
]
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
name: Deploy To Production
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
environment: prod
|
||||
|
||||
runs-on: node
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn build
|
||||
- run: yarn test
|
||||
- run: yarn lint
|
||||
|
||||
- name: "Copy files over to location"
|
||||
run: cp -r . ${{ secrets.PROD_REPO_PATH }}
|
||||
|
||||
deploy:
|
||||
environment: prod
|
||||
needs: build
|
||||
runs-on: node
|
||||
steps:
|
||||
- uses: https://github.com/appleboy/ssh-action@v1.0.3
|
||||
env:
|
||||
DB_NAME: ${{ secrets.PROD_DB_NAME }}
|
||||
DB_AUTH_USER: ${{ secrets.PROD_DB_AUTH_USER }}
|
||||
DB_AUTH_PASS: ${{ secrets.PROD_DB_AUTH_PASS }}
|
||||
DB_HOST: ${{ secrets.PROD_DB_HOST }}
|
||||
DB_PORT: ${{ secrets.PROD_DB_PORT }}
|
||||
DB_ROOT_HOST: ${{ secrets.PROD_DB_ROOT_HOST }}
|
||||
DB_SYNC: ${{ secrets.PROD_DB_SYNC }}
|
||||
DB_LOGGING: ${{ secrets.PROD_DB_LOGGING }}
|
||||
DB_DATA_LOCATION: ${{ secrets.PROD_DB_DATA_LOCATION }}
|
||||
SERVER_PATH: ${{ secrets.PROD_SSH_SERVER_PATH }}
|
||||
BOT_TOKEN: ${{ secrets.PROD_BOT_TOKEN }}
|
||||
BOT_VER: ${{ vars.PROD_BOT_VER }}
|
||||
BOT_AUTHOR: ${{ vars.PROD_BOT_AUTHOR }}
|
||||
BOT_OWNERID: ${{ vars.PROD_BOT_OWNERID }}
|
||||
BOT_CLIENTID: ${{ vars.PROD_BOT_CLIENTID }}
|
||||
BOT_ENV: ${{ vars.PROD_BOT_ENV }}
|
||||
BOT_ADMINS: ${{ vars.PROD_BOT_ADMINS }}
|
||||
ABOUT_FUNDING: ${{ vars.PROD_ABOUT_FUNDING }}
|
||||
ABOUT_REPO: ${{ vars.PROD_ABOUT_REPO }}
|
||||
DATA_DIR: ${{ secrets.PROD_DATA_DIR }}
|
||||
GDRIVESYNC_AUTO: ${{ vars.PROD_GDRIVESYNC_AUTO }}
|
||||
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:
|
||||
host: ${{ secrets.PROD_SSH_HOST }}
|
||||
username: ${{ secrets.PROD_SSH_USER }}
|
||||
key: ${{ secrets.PROD_SSH_KEY }}
|
||||
port: ${{ secrets.PROD_SSH_PORT }}
|
||||
envs: DB_NAME,DB_AUTH_USER,DB_AUTH_PASS,DB_HOST,DB_PORT,DB_ROOT_HOST,DB_SYNC,DB_LOGGING,DB_DATA_LOCATION,BOT_TOKEN,BOT_VER,BOT_AUTHOR,BOT_OWNERID,BOT_CLIENTID,ABOUT_FUNDING,ABOUT_REPO,BOT_ENV,BOT_ADMINS,DATA_DIR,GDRIVESYNC_AUTO,SERVER_PATH,EXPRESS_PORT,BOT_LOGLEVEL,BOT_LOG_DISCORD_ENABLE,BOT_LOG_DISCORD_LEVEL,BOT_LOG_DISCORD_WEBHOOK,BOT_LOG_DISCORD_SERVICE
|
||||
script: |
|
||||
source .sshrc \
|
||||
&& cd /home/vylpes/apps/card-drop/card-drop_prod \
|
||||
&& docker compose down \
|
||||
&& (pm2 stop card-drop_prod || true) \
|
||||
&& (pm2 delete card-drop_prod || true) \
|
||||
&& docker compose up -d \
|
||||
&& sleep 10 \
|
||||
&& yarn run db:up \
|
||||
&& pm2 start --name card-drop_prod dist/bot.js
|
|
@ -1,77 +0,0 @@
|
|||
name: Deploy To Stage
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
build:
|
||||
environment: stage
|
||||
|
||||
runs-on: node
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn build
|
||||
- run: yarn test
|
||||
- run: yarn lint
|
||||
|
||||
- name: "Copy files over to location"
|
||||
run: cp -r . ${{ secrets.STAGE_REPO_PATH }}
|
||||
|
||||
deploy:
|
||||
environment: prod
|
||||
needs: build
|
||||
runs-on: node
|
||||
steps:
|
||||
- uses: https://github.com/appleboy/ssh-action@v1.0.3
|
||||
env:
|
||||
DB_NAME: ${{ secrets.STAGE_DB_NAME }}
|
||||
DB_AUTH_USER: ${{ secrets.STAGE_DB_AUTH_USER }}
|
||||
DB_AUTH_PASS: ${{ secrets.STAGE_DB_AUTH_PASS }}
|
||||
DB_HOST: ${{ secrets.STAGE_DB_HOST }}
|
||||
DB_PORT: ${{ secrets.STAGE_DB_PORT }}
|
||||
DB_ROOT_HOST: ${{ secrets.STAGE_DB_ROOT_HOST }}
|
||||
DB_SYNC: ${{ secrets.STAGE_DB_SYNC }}
|
||||
DB_LOGGING: ${{ secrets.STAGE_DB_LOGGING }}
|
||||
DB_DATA_LOCATION: ${{ secrets.STAGE_DB_DATA_LOCATION }}
|
||||
SERVER_PATH: ${{ secrets.STAGE_SSH_SERVER_PATH }}
|
||||
BOT_TOKEN: ${{ secrets.STAGE_BOT_TOKEN }}
|
||||
BOT_VER: ${{ vars.STAGE_BOT_VER }}
|
||||
BOT_AUTHOR: ${{ vars.STAGE_BOT_AUTHOR }}
|
||||
BOT_OWNERID: ${{ vars.STAGE_BOT_OWNERID }}
|
||||
BOT_CLIENTID: ${{ vars.STAGE_BOT_CLIENTID }}
|
||||
BOT_ENV: ${{ vars.STAGE_BOT_ENV }}
|
||||
BOT_ADMINS: ${{ vars.STAGE_BOT_ADMINS }}
|
||||
ABOUT_FUNDING: ${{ vars.STAGE_ABOUT_FUNDING }}
|
||||
ABOUT_REPO: ${{ vars.STAGE_ABOUT_REPO }}
|
||||
DATA_DIR: ${{ secrets.STAGE_DATA_DIR }}
|
||||
GDRIVESYNC_AUTO: ${{ vars.STAGE_GDRIVESYNC_AUTO }}
|
||||
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:
|
||||
host: ${{ secrets.STAGE_SSH_HOST }}
|
||||
username: ${{ secrets.STAGE_SSH_USER }}
|
||||
key: ${{ secrets.STAGE_SSH_KEY }}
|
||||
port: ${{ secrets.STAGE_SSH_PORT }}
|
||||
envs: DB_NAME,DB_AUTH_USER,DB_AUTH_PASS,DB_HOST,DB_PORT,DB_ROOT_HOST,DB_SYNC,DB_LOGGING,DB_DATA_LOCATION,BOT_TOKEN,BOT_VER,BOT_AUTHOR,BOT_OWNERID,BOT_CLIENTID,ABOUT_FUNDING,ABOUT_REPO,BOT_ENV,BOT_ADMINS,DATA_DIR,GDRIVESYNC_AUTO,SERVER_PATH,EXPRESS_PORT,BOT_LOGLEVEL,BOT_LOG_DISCORD_ENABLE,BOT_LOG_DISCORD_LEVEL,BOT_LOG_DISCORD_WEBHOOK,BOT_LOG_DISCORD_SERVICE
|
||||
script: |
|
||||
source .sshrc \
|
||||
&& cd /home/vylpes/apps/card-drop/card-drop_stage \
|
||||
&& docker compose down \
|
||||
&& (pm2 stop card-drop_stage || true) \
|
||||
&& (pm2 delete card-drop_stage || true) \
|
||||
&& docker compose up -d \
|
||||
&& sleep 10 \
|
||||
&& yarn run db:up \
|
||||
&& pm2 start --name card-drop_stage dist/bot.js
|
|
@ -1,25 +0,0 @@
|
|||
name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- feature/*
|
||||
- hotfix/*
|
||||
- renovate/*
|
||||
|
||||
jobs:
|
||||
build:
|
||||
environment: stage
|
||||
|
||||
runs-on: node
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn build
|
||||
- run: yarn test
|
||||
- run: yarn lint
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -107,6 +107,4 @@ config.json
|
|||
.DS_Store
|
||||
ormconfig.json
|
||||
gdrive-credentials.json
|
||||
data/
|
||||
*.db
|
||||
.temp/
|
||||
cards/
|
29
.prod.env
Normal file
29
.prod.env
Normal file
|
@ -0,0 +1,29 @@
|
|||
# Security Warning! Do not commit this file to any VCS!
|
||||
# This is a local file to speed up development process,
|
||||
# so you don't have to change your environment variables.
|
||||
#
|
||||
# This is not applied to `.env.template`!
|
||||
# Template files must be committed to the VCS, but must not contain
|
||||
# any secret values.
|
||||
|
||||
BOT_TOKEN=
|
||||
BOT_VER=0.1.2
|
||||
BOT_AUTHOR=Vylpes
|
||||
BOT_OWNERID=147392775707426816
|
||||
BOT_CLIENTID=1093810443589529631
|
||||
|
||||
ABOUT_FUNDING=
|
||||
ABOUT_REPO=
|
||||
|
||||
DROP_RARITY=-1
|
||||
DROP_CARD=-1
|
||||
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3321
|
||||
DB_NAME=carddrop
|
||||
DB_AUTH_USER=prod
|
||||
DB_AUTH_PASS=prod
|
||||
DB_SYNC=false
|
||||
DB_LOGGING=false
|
||||
|
||||
DB_CARD_FILE=:memory:
|
29
.stage.env
Normal file
29
.stage.env
Normal file
|
@ -0,0 +1,29 @@
|
|||
# Security Warning! Do not commit this file to any VCS!
|
||||
# This is a local file to speed up development process,
|
||||
# so you don't have to change your environment variables.
|
||||
#
|
||||
# This is not applied to `.env.template`!
|
||||
# Template files must be committed to the VCS, but must not contain
|
||||
# any secret values.
|
||||
|
||||
BOT_TOKEN=
|
||||
BOT_VER=0.1.2 BETA
|
||||
BOT_AUTHOR=Vylpes
|
||||
BOT_OWNERID=147392775707426816
|
||||
BOT_CLIENTID=1147976642942214235
|
||||
|
||||
ABOUT_FUNDING=
|
||||
ABOUT_REPO=
|
||||
|
||||
DROP_RARITY=-1
|
||||
DROP_CARD=-1
|
||||
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3311
|
||||
DB_NAME=carddrop
|
||||
DB_AUTH_USER=stage
|
||||
DB_AUTH_PASS=stage
|
||||
DB_SYNC=false
|
||||
DB_LOGGING=false
|
||||
|
||||
DB_CARD_FILE=:memory:
|
|
@ -1,47 +0,0 @@
|
|||
steps:
|
||||
build:
|
||||
image: node
|
||||
commands:
|
||||
- npm ci
|
||||
- npm run build
|
||||
when:
|
||||
event: push
|
||||
branch: [ hotfix/*, feature/*, renovate/*, develop, main ]
|
||||
lint:
|
||||
image: node
|
||||
commands:
|
||||
- npm run lint
|
||||
when:
|
||||
event: push
|
||||
branch: [ hotfix/*, feature/*, renovate/*, develop, main ]
|
||||
test:
|
||||
image: node
|
||||
commands:
|
||||
- npm test
|
||||
when:
|
||||
event: push
|
||||
branch: [ hotfix/*, feature/*, renovate/*, develop, main ]
|
||||
stage:
|
||||
image: alpine
|
||||
secrets: [ ssh_key, stage_bot_token ]
|
||||
commands:
|
||||
- apk add rsync openssh-client
|
||||
- eval `ssh-agent -s`
|
||||
- echo "$SSH_KEY" | tr -d '\r' | ssh-add -
|
||||
- rsync -e "ssh -o StrictHostKeyChecking=no" -r ./* vylpes@192.168.1.115:/home/vylpes/apps/card-drop/card-drop_stage
|
||||
- ssh vylpes@192.168.1.115 BOT_TOKEN='$${stage_bot_token}' 'bash -s' < ./scripts/deploy_stage.sh
|
||||
when:
|
||||
event: push
|
||||
branch: [ develop ]
|
||||
deploy:
|
||||
image: alpine
|
||||
secrets: [ ssh_key, prod_bot_token ]
|
||||
commands:
|
||||
- apk add rsync openssh-client
|
||||
- eval `ssh-agent -s`
|
||||
- echo "$SSH_KEY" | tr -d '\r' | ssh-add -
|
||||
- rsync -e "ssh -o StrictHostKeyChecking=no" -r ./* vylpes@192.168.1.115:/home/vylpes/apps/card-drop/card-drop_prod
|
||||
- ssh vylpes@192.168.1.115 BOT_TOKEN='$${prod_bot_token}' 'bash -s' < ./scripts/deploy_prod.sh
|
||||
when:
|
||||
event: push
|
||||
branch: [ main ]
|
60
README.md
60
README.md
|
@ -1,60 +1,2 @@
|
|||
# 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
|
||||
```
|
||||
# card-drop
|
||||
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
CREATE TABLE `claim` (
|
||||
`Id` varchar(255) NOT NULL,
|
||||
`WhenCreated` datetime NOT NULL,
|
||||
`WhenUpdated` datetime NOT NULL,
|
||||
`ClaimId` varchar(255) NOT NULL,
|
||||
`InventoryId` varchar(255) NOT NULL,
|
||||
PRIMARY KEY (`Id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
@ -1,14 +0,0 @@
|
|||
INSERT INTO claim (
|
||||
Id,
|
||||
WhenCreated,
|
||||
WhenUpdated,
|
||||
ClaimId,
|
||||
InventoryId
|
||||
)
|
||||
SELECT
|
||||
UUID(),
|
||||
NOW(),
|
||||
NOW(),
|
||||
ClaimId,
|
||||
Id
|
||||
FROM inventory;
|
|
@ -1,2 +0,0 @@
|
|||
ALTER TABLE inventory
|
||||
DROP ClaimId;
|
|
@ -1,8 +0,0 @@
|
|||
CREATE TABLE `config` (
|
||||
`Id` VARCHAR(255) NOT NULL,
|
||||
`WhenCreated` DATETIME NOT NULL,
|
||||
`WhenUpdated` DATETIME NOT NULL,
|
||||
`Key` VARCHAR(255) NOT NULL,
|
||||
`Value` VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (`Id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
@ -1,7 +0,0 @@
|
|||
CREATE TABLE `user` (
|
||||
`Id` varchar(255) NOT NULL,
|
||||
`WhenCreated` datetime NOT NULL,
|
||||
`WhenUpdated` datetime NOT NULL,
|
||||
`Currency` int NOT NULL,
|
||||
PRIMARY KEY (`Id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
@ -1,2 +0,0 @@
|
|||
ALTER TABLE `user`
|
||||
ADD LastUsedDaily datetime null;
|
31
docker-compose.prod.yml
Normal file
31
docker-compose.prod.yml
Normal file
|
@ -0,0 +1,31 @@
|
|||
version: "3.9"
|
||||
|
||||
volumes:
|
||||
prod_database_data:
|
||||
|
||||
services:
|
||||
# discord:
|
||||
# build: .
|
||||
|
||||
database:
|
||||
image: mysql/mysql-server
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
restart: always
|
||||
environment:
|
||||
- MYSQL_DATABASE=carddrop
|
||||
- MYSQL_USER=prod
|
||||
- MYSQL_PASSWORD=prod
|
||||
- MYSQL_ROOT_PASSWORD=root
|
||||
- MYSQL_ROOT_HOST=0.0.0.0
|
||||
ports:
|
||||
- "3321:3306"
|
||||
volumes:
|
||||
- prod_database_data:/var/lib/mysql
|
||||
|
||||
phpmyadmin:
|
||||
image: phpmyadmin
|
||||
restart: always
|
||||
ports:
|
||||
- "3322:80"
|
||||
environment:
|
||||
- PMA_ARBITRARY=1
|
31
docker-compose.stage.yml
Normal file
31
docker-compose.stage.yml
Normal file
|
@ -0,0 +1,31 @@
|
|||
version: "3.9"
|
||||
|
||||
volumes:
|
||||
stage_database_data:
|
||||
|
||||
services:
|
||||
# discord:
|
||||
# build: .
|
||||
|
||||
database:
|
||||
image: mysql/mysql-server
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
restart: always
|
||||
environment:
|
||||
- MYSQL_DATABASE=carddrop
|
||||
- MYSQL_USER=stage
|
||||
- MYSQL_PASSWORD=stage
|
||||
- MYSQL_ROOT_PASSWORD=root
|
||||
- MYSQL_ROOT_HOST=0.0.0.0
|
||||
ports:
|
||||
- "3311:3306"
|
||||
volumes:
|
||||
- stage_database_data:/var/lib/mysql
|
||||
|
||||
phpmyadmin:
|
||||
image: phpmyadmin
|
||||
restart: always
|
||||
ports:
|
||||
- "3312:80"
|
||||
environment:
|
||||
- PMA_ARBITRARY=1
|
|
@ -1,17 +1,31 @@
|
|||
version: "3.9"
|
||||
|
||||
volumes:
|
||||
dev_database_data:
|
||||
|
||||
services:
|
||||
# discord:
|
||||
# build: .
|
||||
|
||||
database:
|
||||
image: mysql/mysql-server
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
restart: always
|
||||
environment:
|
||||
- MYSQL_DATABASE=$DB_NAME
|
||||
- MYSQL_USER=$DB_AUTH_USER
|
||||
- MYSQL_PASSWORD=$DB_AUTH_PASS
|
||||
- MYSQL_ROOT_PASSWORD=$DB_AUTH_PASS
|
||||
- MYSQL_ROOT_HOST=$DB_ROOT_HOST
|
||||
- MYSQL_DATABASE=carddrop
|
||||
- MYSQL_USER=dev
|
||||
- MYSQL_PASSWORD=dev
|
||||
- MYSQL_ROOT_PASSWORD=root
|
||||
- MYSQL_ROOT_HOST=0.0.0.0
|
||||
ports:
|
||||
- "$DB_PORT:3306"
|
||||
- "3301:3306"
|
||||
volumes:
|
||||
- $DB_DATA_LOCATION:/var/lib/mysql
|
||||
- dev_database_data:/var/lib/mysql
|
||||
|
||||
phpmyadmin:
|
||||
image: phpmyadmin
|
||||
restart: always
|
||||
ports:
|
||||
- "3302:80"
|
||||
environment:
|
||||
- PMA_ARBITRARY=1
|
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>
|
39
package.json
39
package.json
|
@ -1,63 +1,48 @@
|
|||
{
|
||||
"name": "card-drop",
|
||||
"version": "0.8.1",
|
||||
"version": "0.1.2",
|
||||
"main": "./dist/bot.js",
|
||||
"typings": "./dist",
|
||||
"scripts": {
|
||||
"clean": "rm -rf node_modules/ dist/",
|
||||
"build": "tsc",
|
||||
"start": "node ./dist/bot.js",
|
||||
"test": "echo true",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"test": "jest --passWithNoTests",
|
||||
"db:up": "typeorm migration:run -d dist/database/dataSources/appDataSource.js",
|
||||
"db:down": "typeorm migration:revert -d dist/database/dataSources/appDataSource.js",
|
||||
"db:create": "typeorm migration:create ./src/database/migrations/app/new",
|
||||
"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>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https//git.vylpes.xyz/External/card-drop/issues",
|
||||
"url": "https//gitea.vylpes.xyz/External/card-drop/issues",
|
||||
"email": "helpdesk@vylpes.com"
|
||||
},
|
||||
"homepage": "https://gitea.vylpes.xyz/External/card-drop",
|
||||
"funding": "https://ko-fi.com/vylpes",
|
||||
"dependencies": {
|
||||
"@discordjs/rest": "^2.0.0",
|
||||
"@types/clone-deep": "^4.0.4",
|
||||
"@types/express": "^4.17.20",
|
||||
"@discordjs/rest": "^1.1.0",
|
||||
"@types/jest": "^29.0.0",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"canvas": "^2.11.2",
|
||||
"clone-deep": "^4.0.1",
|
||||
"cron": "^3.1.7",
|
||||
"discord.js": "^14.15.3",
|
||||
"discord.js": "^14.3.0",
|
||||
"dotenv": "^16.0.0",
|
||||
"express": "^4.18.2",
|
||||
"glob": "^10.3.10",
|
||||
"googleapis": "^126.0.0",
|
||||
"jest": "^29.0.0",
|
||||
"jest-mock-extended": "^3.0.0",
|
||||
"jimp": "^0.22.12",
|
||||
"minimatch": "9.0.5",
|
||||
"minimatch": "9.0.2",
|
||||
"mysql": "^2.18.1",
|
||||
"sqlite3": "^5.1.6",
|
||||
"ts-jest": "^29.0.0",
|
||||
"typeorm": "0.3.20",
|
||||
"winston": "^3.11.0",
|
||||
"winston-daily-rotate-file": "^5.0.0",
|
||||
"winston-discord-transport": "^1.3.0"
|
||||
"typeorm": "0.3.17"
|
||||
},
|
||||
"resolutions": {
|
||||
"**/ws": "^8.17.1"
|
||||
"**/semver": "^7.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||
"@typescript-eslint/parser": "^6.16.0",
|
||||
"eslint": "^8.56.0",
|
||||
"np": "^9.0.0",
|
||||
"np": "^8.0.4",
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"baseBranches": ["develop"],
|
||||
"labels": ["type/dependencies"]
|
||||
"baseBranches": ["develop"]
|
||||
}
|
||||
|
|
|
@ -3,8 +3,21 @@
|
|||
export PATH="$HOME/.yarn/bin:$PATH"
|
||||
export PATH="$HOME/.nodeuse/bin:$PATH"
|
||||
|
||||
cd ~/apps/card-drop/card-drop_prod
|
||||
docker compose --file docker-compose.prod.yml up -d
|
||||
sleep 10
|
||||
cp .prod.env .env
|
||||
pm2 restart card-drop_prod || pm2 start --name card-drop_prod dist/bot.js
|
||||
export BOT_TOKEN=$(cat $HOME/scripts/card-drop/prod_key.txt)
|
||||
|
||||
cd ~/apps/card-drop/card-drop_prod \
|
||||
&& git checkout main \
|
||||
&& git fetch \
|
||||
&& git pull \
|
||||
&& docker compose --file docker-compose.prod.yml down \
|
||||
&& (pm2 stop card-drop_prod || true) \
|
||||
&& (pm2 delete card-drop_prod || true) \
|
||||
&& cp .prod.env .env \
|
||||
&& yarn clean \
|
||||
&& yarn install --frozen-lockfile \
|
||||
&& yarn build \
|
||||
&& docker compose --file docker-compose.prod.yml up -d \
|
||||
&& echo "Sleeping for 10 seconds to let database load..." \
|
||||
&& sleep 10 \
|
||||
&& yarn run db:up \
|
||||
&& NODE_ENV=production pm2 start --name card-drop_prod dist/bot.js
|
|
@ -3,8 +3,21 @@
|
|||
export PATH="$HOME/.yarn/bin:$PATH"
|
||||
export PATH="$HOME/.nodeuse/bin:$PATH"
|
||||
|
||||
cd ~/apps/card-drop/card-drop_stage
|
||||
docker compose --file docker-compose.stage.yml up -d
|
||||
sleep 10
|
||||
cp .stage.env .env
|
||||
pm2 restart card-drop_stage || pm2 start --name card-drop_stage dist/bot.js
|
||||
export BOT_TOKEN=$(cat $HOME/scripts/card-drop/stage_key.txt)
|
||||
|
||||
cd ~/apps/card-drop/card-drop_stage \
|
||||
&& git checkout develop \
|
||||
&& git fetch \
|
||||
&& git pull \
|
||||
&& docker compose --file docker-compose.stage.yml down \
|
||||
&& (pm2 stop card-drop_stage || true) \
|
||||
&& (pm2 delete card-drop_stage || true) \
|
||||
&& cp .stage.env .env \
|
||||
&& yarn clean \
|
||||
&& yarn install --frozen-lockfile \
|
||||
&& yarn build \
|
||||
&& docker compose --file docker-compose.stage.yml up -d \
|
||||
&& echo "Sleeping for 10 seconds to let database load..." \
|
||||
&& sleep 10 \
|
||||
&& yarn run db:up \
|
||||
&& NODE_ENV=production pm2 start --name card-drop_stage dist/bot.js
|
|
@ -1,5 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd ~/apps/card-drop/card-drop_stage \
|
||||
&& rclone sync card-drop-gdrive: ./cards \
|
||||
&& curl -X POST http://localhost:3313/api/reload-db
|
|
@ -1,109 +0,0 @@
|
|||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
import Config from "../database/entities/app/Config";
|
||||
import { glob } from "glob";
|
||||
import { SeriesMetadata } from "../contracts/SeriesMetadata";
|
||||
import { CoreClient } from "../client/client";
|
||||
import AppLogger from "../client/appLogger";
|
||||
import {CardRarity} from "../constants/CardRarity";
|
||||
|
||||
export interface CardMetadataResult {
|
||||
IsSuccess: boolean;
|
||||
ErrorMessage?: string;
|
||||
}
|
||||
|
||||
export interface FindMetadataResult {
|
||||
IsSuccess: boolean;
|
||||
Result?: SeriesMetadata[];
|
||||
Error?: {
|
||||
File: string;
|
||||
Message: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default class CardMetadataFunction {
|
||||
public static async Execute(overrideSafeMode: boolean = false): Promise<CardMetadataResult> {
|
||||
AppLogger.LogInfo("Functions/CardMetadataFunction", "Executing");
|
||||
|
||||
if (!overrideSafeMode && await Config.GetValue("safemode") == "true") {
|
||||
AppLogger.LogWarn("Functions/CardMetadataFunction", "Safe Mode is active, refusing to resync");
|
||||
|
||||
return {
|
||||
IsSuccess: false,
|
||||
ErrorMessage: "Safe mode is on and not overridden",
|
||||
};
|
||||
}
|
||||
|
||||
const cardResult = await this.FindMetadataJSONs();
|
||||
|
||||
if (cardResult.IsSuccess) {
|
||||
CoreClient.Cards = cardResult.Result!;
|
||||
|
||||
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 {
|
||||
IsSuccess: true,
|
||||
};
|
||||
}
|
||||
|
||||
await Config.SetValue("safemode", "true");
|
||||
AppLogger.LogError("Functions/CardMetadataFunction", `Safe Mode activated due to error: ${cardResult.Error!.Message}`);
|
||||
|
||||
return {
|
||||
IsSuccess: false,
|
||||
ErrorMessage: `${cardResult.Error!.File}: ${cardResult.Error!.Message}`,
|
||||
};
|
||||
}
|
||||
|
||||
private static async FindMetadataJSONs(): Promise<FindMetadataResult> {
|
||||
const res: SeriesMetadata[] = [];
|
||||
|
||||
const seriesJSONs = await glob(path.join(process.env.DATA_DIR!, "cards", "/**/*.json"));
|
||||
|
||||
for (const jsonPath of seriesJSONs) {
|
||||
try {
|
||||
AppLogger.LogVerbose("Functions/CardMetadataFunction", `Reading file ${jsonPath}`);
|
||||
const jsonFile = readFileSync(jsonPath);
|
||||
const parsedJson: SeriesMetadata[] = JSON.parse(jsonFile.toString());
|
||||
|
||||
res.push(...parsedJson);
|
||||
} catch (e) {
|
||||
AppLogger.LogError("Functions/CardMetadataFunction", `Error reading file ${jsonPath}: ${e}`);
|
||||
|
||||
return {
|
||||
IsSuccess: false,
|
||||
Error: {
|
||||
File: jsonPath,
|
||||
Message: `${e}`,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
IsSuccess: true,
|
||||
Result: res,
|
||||
};
|
||||
}
|
||||
}
|
111
src/Functions/CardSetupFunction.ts
Normal file
111
src/Functions/CardSetupFunction.ts
Normal file
|
@ -0,0 +1,111 @@
|
|||
import { existsSync, readdirSync } from "fs";
|
||||
import CardDataSource from "../database/dataSources/cardDataSource";
|
||||
import Card from "../database/entities/card/Card";
|
||||
import Series from "../database/entities/card/Series";
|
||||
import path from "path";
|
||||
import { CardRarity } from "../constants/CardRarity";
|
||||
|
||||
export default class CardSetupFunction {
|
||||
public async Execute() {
|
||||
await this.ClearDatabase();
|
||||
await this.ReadSeries();
|
||||
await this.ReadCards();
|
||||
}
|
||||
|
||||
private async ClearDatabase() {
|
||||
const cardRepository = CardDataSource.getRepository(Card);
|
||||
await cardRepository.clear();
|
||||
|
||||
const seriesRepository = CardDataSource.getRepository(Series);
|
||||
await seriesRepository.clear();
|
||||
}
|
||||
|
||||
private async ReadSeries() {
|
||||
const seriesDir = readdirSync(path.join(process.cwd(), 'cards'));
|
||||
|
||||
const seriesRepository = CardDataSource.getRepository(Series);
|
||||
|
||||
const seriesToSave: Series[] = [];
|
||||
|
||||
for (let dir of seriesDir) {
|
||||
const dirPart = dir.split(' ');
|
||||
|
||||
const seriesId = dirPart.shift();
|
||||
const seriesName = dirPart.join(' ');
|
||||
|
||||
const series = new Series(seriesId!, seriesName, dir);
|
||||
|
||||
seriesToSave.push(series);
|
||||
}
|
||||
|
||||
await seriesRepository.save(seriesToSave);
|
||||
}
|
||||
|
||||
private async ReadCards() {
|
||||
const loadedSeries = await Series.FetchAll(Series, [ "Cards", "Cards.Series" ]);
|
||||
|
||||
const cardRepository = CardDataSource.getRepository(Card);
|
||||
|
||||
const cardsToSave: Card[] = [];
|
||||
|
||||
for (let series of loadedSeries) {
|
||||
const bronzeExists = existsSync(path.join(process.cwd(), 'cards', series.Path, 'BRONZE'));
|
||||
const goldExists = existsSync(path.join(process.cwd(), 'cards', series.Path, 'GOLD'));
|
||||
const legendaryExists = existsSync(path.join(process.cwd(), 'cards', series.Path, 'LEGENDARY'));
|
||||
const silverExists = existsSync(path.join(process.cwd(), 'cards', series.Path, 'SILVER'));
|
||||
|
||||
const cardDirBronze = bronzeExists ? readdirSync(path.join(process.cwd(), 'cards', series.Path, 'BRONZE')) : [];
|
||||
const cardDirGold = goldExists ? readdirSync(path.join(process.cwd(), 'cards', series.Path, 'GOLD')) : [];
|
||||
const cardDirLegendary = legendaryExists ? readdirSync(path.join(process.cwd(), 'cards', series.Path, 'LEGENDARY')) : [];
|
||||
const cardDirSilver = silverExists ? readdirSync(path.join(process.cwd(), 'cards', series.Path, 'SILVER')) : [];
|
||||
|
||||
for (let file of cardDirBronze) {
|
||||
const filePart = file.split('.');
|
||||
|
||||
const cardId = filePart[0];
|
||||
const cardName = filePart[0];
|
||||
|
||||
const card = new Card(cardId, cardName, CardRarity.Bronze, path.join(path.join(process.cwd(), 'cards', series.Path, 'BRONZE', file)), file, series);
|
||||
|
||||
cardsToSave.push(card);
|
||||
}
|
||||
|
||||
for (let file of cardDirGold) {
|
||||
const filePart = file.split('.');
|
||||
|
||||
const cardId = filePart[0];
|
||||
const cardName = filePart[0];
|
||||
|
||||
const card = new Card(cardId, cardName, CardRarity.Gold, path.join(path.join(process.cwd(), 'cards', series.Path, 'GOLD', file)), file, series);
|
||||
|
||||
cardsToSave.push(card);
|
||||
}
|
||||
|
||||
for (let file of cardDirLegendary) {
|
||||
const filePart = file.split('.');
|
||||
|
||||
const cardId = filePart[0];
|
||||
const cardName = filePart[0];
|
||||
|
||||
const card = new Card(cardId, cardName, CardRarity.Legendary, path.join(path.join(process.cwd(), 'cards', series.Path, 'LEGENDARY', file)), file, series);
|
||||
|
||||
cardsToSave.push(card);
|
||||
}
|
||||
|
||||
for (let file of cardDirSilver) {
|
||||
const filePart = file.split('.');
|
||||
|
||||
const cardId = filePart[0];
|
||||
const cardName = filePart[0];
|
||||
|
||||
const card = new Card(cardId, cardName, CardRarity.Silver, path.join(path.join(process.cwd(), 'cards', series.Path, 'SILVER', file)), file, series);
|
||||
|
||||
cardsToSave.push(card);
|
||||
}
|
||||
}
|
||||
|
||||
await cardRepository.save(cardsToSave);
|
||||
|
||||
console.log(`Loaded ${cardsToSave.length} cards to database`);
|
||||
}
|
||||
}
|
25
src/bot.ts
25
src/bot.ts
|
@ -2,8 +2,6 @@ import * as dotenv from "dotenv";
|
|||
import { CoreClient } from "./client/client";
|
||||
import { IntentsBitField } from "discord.js";
|
||||
import Registry from "./registry";
|
||||
import { existsSync } from "fs";
|
||||
import { ExecException, exec } from "child_process";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
|
@ -13,17 +11,13 @@ const requiredConfigs: string[] = [
|
|||
"BOT_AUTHOR",
|
||||
"BOT_OWNERID",
|
||||
"BOT_CLIENTID",
|
||||
"BOT_ENV",
|
||||
"BOT_ADMINS",
|
||||
"DATA_DIR",
|
||||
"DB_HOST",
|
||||
"DB_PORT",
|
||||
"DB_AUTH_USER",
|
||||
"DB_AUTH_PASS",
|
||||
"DB_SYNC",
|
||||
"DB_LOGGING",
|
||||
"EXPRESS_PORT",
|
||||
];
|
||||
]
|
||||
|
||||
requiredConfigs.forEach(config => {
|
||||
if (!process.env[config]) {
|
||||
|
@ -37,22 +31,7 @@ const client = new CoreClient([
|
|||
]);
|
||||
|
||||
Registry.RegisterCommands();
|
||||
Registry.RegisterEvents();
|
||||
Registry.RegisterButtonEvents();
|
||||
|
||||
if (!existsSync(`${process.env.DATA_DIR}/cards`) && process.env.GDRIVESYNC_AUTO && process.env.GDRIVESYNC_AUTO == "true") {
|
||||
console.log("Card directory not found, syncing...");
|
||||
|
||||
CoreClient.AllowDrops = false;
|
||||
|
||||
exec(`rclone sync card-drop-gdrive: ${process.cwd()}/cards`, async (error: ExecException | null) => {
|
||||
if (error) {
|
||||
console.error(error.code);
|
||||
throw `Error while running sync command. Code: ${error.code}`;
|
||||
} else {
|
||||
console.log("Synced successfully.");
|
||||
CoreClient.AllowDrops = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
client.start();
|
|
@ -2,86 +2,37 @@ import { ButtonInteraction } from "discord.js";
|
|||
import { ButtonEvent } from "../type/buttonEvent";
|
||||
import Inventory from "../database/entities/app/Inventory";
|
||||
import { CoreClient } from "../client/client";
|
||||
import { default as eClaim } from "../database/entities/app/Claim";
|
||||
import AppLogger from "../client/appLogger";
|
||||
import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
||||
import User from "../database/entities/app/User";
|
||||
import CardConstants from "../constants/CardConstants";
|
||||
|
||||
export default class Claim extends ButtonEvent {
|
||||
public override async execute(interaction: ButtonInteraction) {
|
||||
if (!interaction.guild || !interaction.guildId) return;
|
||||
if (!interaction.channel) return;
|
||||
|
||||
await interaction.deferUpdate();
|
||||
|
||||
const cardNumber = interaction.customId.split(" ")[1];
|
||||
const claimId = interaction.customId.split(" ")[2];
|
||||
const droppedBy = interaction.customId.split(" ")[3];
|
||||
const cardNumber = interaction.customId.split(' ')[1];
|
||||
const claimId = interaction.customId.split(' ')[2];
|
||||
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}`);
|
||||
|
||||
const user = await User.FetchOneById(User, userId) || new User(userId, CardConstants.StartingCurrency);
|
||||
|
||||
AppLogger.LogSilly("Button/Claim", `${user.Id} has ${user.Currency} currency`);
|
||||
|
||||
if (!user.RemoveCurrency(CardConstants.ClaimCost)) {
|
||||
await interaction.channel.send(`${interaction.user}, Not enough currency! You need ${CardConstants.ClaimCost} currency, you have ${user.Currency}!`);
|
||||
return;
|
||||
}
|
||||
|
||||
const claimed = await eClaim.FetchOneByClaimId(claimId);
|
||||
const claimed = await Inventory.FetchOneByClaimId(claimId);
|
||||
|
||||
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.channel.send(`${interaction.user}, The latest dropped card can only be claimed by the user who dropped it!`);
|
||||
if (claimId != CoreClient.ClaimId) {
|
||||
await interaction.reply('This card has expired');
|
||||
return;
|
||||
}
|
||||
|
||||
await user.Save(User, user);
|
||||
|
||||
let inventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber);
|
||||
|
||||
if (!inventory) {
|
||||
inventory = new Inventory(userId, cardNumber, 1);
|
||||
inventory = new Inventory(userId, cardNumber, 1, claimId);
|
||||
} else {
|
||||
inventory.SetQuantity(inventory.Quantity + 1);
|
||||
}
|
||||
|
||||
await inventory.Save(Inventory, inventory);
|
||||
|
||||
const claim = new eClaim(claimId);
|
||||
claim.SetInventory(inventory);
|
||||
|
||||
await claim.Save(eClaim, claim);
|
||||
|
||||
const card = CardDropHelperMetadata.GetCardByCardNumber(cardNumber);
|
||||
|
||||
if (!card) {
|
||||
return;
|
||||
}
|
||||
|
||||
const imageFileName = card.card.path.split("/").pop()!;
|
||||
|
||||
const embed = CardDropHelperMetadata.GenerateDropEmbed(card, inventory.Quantity, imageFileName, interaction.user.username, user.Currency);
|
||||
const row = CardDropHelperMetadata.GenerateDropButtons(card, claimId, interaction.user.id, true);
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [ embed ],
|
||||
components: [ row ],
|
||||
});
|
||||
await interaction.reply('Card claimed');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
import { ButtonInteraction } from "discord.js";
|
||||
import { ButtonEvent } from "../type/buttonEvent";
|
||||
import InventoryHelper from "../helpers/InventoryHelper";
|
||||
import AppLogger from "../client/appLogger";
|
||||
|
||||
export default class Inventory extends ButtonEvent {
|
||||
public override async execute(interaction: ButtonInteraction) {
|
||||
if (!interaction.guild) return;
|
||||
|
||||
const userid = interaction.customId.split(" ")[1];
|
||||
const page = interaction.customId.split(" ")[2];
|
||||
|
||||
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);
|
||||
|
||||
if (!member) {
|
||||
await interaction.reply("Unable to find user.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
AppLogger.LogVerbose("Button/Inventory", `Generating inventory page ${page} for ${member.user.username} with id ${member.user.id}`);
|
||||
|
||||
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.row ],
|
||||
});
|
||||
} catch (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.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,81 +1,75 @@
|
|||
import { AttachmentBuilder, ButtonInteraction } from "discord.js";
|
||||
import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, CacheType, DiscordAPIError, EmbedBuilder } from "discord.js";
|
||||
import { ButtonEvent } from "../type/buttonEvent";
|
||||
import CardDropHelper from "../helpers/CardDropHelper";
|
||||
import { readFileSync } from "fs";
|
||||
import { CardRarityToColour, CardRarityToString } from "../constants/CardRarity";
|
||||
import { v4 } from "uuid";
|
||||
import { CoreClient } from "../client/client";
|
||||
import Inventory from "../database/entities/app/Inventory";
|
||||
import Config from "../database/entities/app/Config";
|
||||
import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
||||
import path from "path";
|
||||
import AppLogger from "../client/appLogger";
|
||||
import User from "../database/entities/app/User";
|
||||
import CardConstants from "../constants/CardConstants";
|
||||
import Card from "../database/entities/card/Card";
|
||||
|
||||
export default class Reroll extends ButtonEvent {
|
||||
public override async execute(interaction: ButtonInteraction) {
|
||||
if (!CoreClient.AllowDrops) {
|
||||
await interaction.reply("Bot is currently syncing, please wait until its done.");
|
||||
return;
|
||||
if (!interaction.guild || !interaction.guildId) return;
|
||||
|
||||
let randomCard = await CardDropHelper.GetRandomCard();
|
||||
|
||||
if (process.env.DROP_RARITY && Number(process.env.DROP_RARITY) > 0) {
|
||||
randomCard = await CardDropHelper.GetRandomCardByRarity(Number(process.env.DROP_RARITY));
|
||||
} else if (process.env.DROP_CARD && process.env.DROP_CARD != '-1') {
|
||||
let card = await Card.FetchOneByCardNumber(process.env.DROP_CARD, [ "Series" ]);
|
||||
|
||||
if (!card) {
|
||||
await interaction.reply("Card not found");
|
||||
return;
|
||||
}
|
||||
|
||||
randomCard = card;
|
||||
}
|
||||
|
||||
if (await Config.GetValue("safemode") == "true") {
|
||||
AppLogger.LogWarn("Button/Reroll", "Safe Mode is active, refusing to send next drop.");
|
||||
|
||||
await interaction.reply("Safe Mode has been activated, please resync to continue.");
|
||||
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/Drop", `New user (${interaction.user.id}) saved to the database`);
|
||||
}
|
||||
|
||||
if (user.Currency < CardConstants.ClaimCost) {
|
||||
await interaction.reply(`Not enough currency! You need ${CardConstants.ClaimCost} currency, you have ${user.Currency}!`);
|
||||
return;
|
||||
}
|
||||
|
||||
const randomCard = CardDropHelperMetadata.GetRandomCard();
|
||||
|
||||
if (!randomCard) {
|
||||
await interaction.reply("Unable to fetch card, please try again.");
|
||||
return;
|
||||
}
|
||||
const image = readFileSync(randomCard.Path);
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
const attachment = new AttachmentBuilder(image, { name: randomCard.FileName });
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(randomCard.Name)
|
||||
.setDescription(randomCard.Series.Name)
|
||||
.setFooter({ text: CardRarityToString(randomCard.Rarity) })
|
||||
.setColor(CardRarityToColour(randomCard.Rarity))
|
||||
.setImage(`attachment://${randomCard.FileName}`);
|
||||
|
||||
const row = new ActionRowBuilder<ButtonBuilder>();
|
||||
|
||||
const claimId = v4();
|
||||
|
||||
row.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`claim ${randomCard.CardNumber} ${claimId}`)
|
||||
.setLabel("Claim")
|
||||
.setStyle(ButtonStyle.Primary),
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`reroll`)
|
||||
.setLabel("Reroll")
|
||||
.setStyle(ButtonStyle.Secondary));
|
||||
|
||||
try {
|
||||
AppLogger.LogVerbose("Button/Reroll", `Sending next drop: ${randomCard.card.id} (${randomCard.card.name})`);
|
||||
|
||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path));
|
||||
const imageFileName = randomCard.card.path.split("/").pop()!;
|
||||
|
||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||
|
||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
||||
|
||||
const embed = CardDropHelperMetadata.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName, undefined, user.Currency);
|
||||
|
||||
const claimId = v4();
|
||||
|
||||
const row = CardDropHelperMetadata.GenerateDropButtons(randomCard, claimId, interaction.user.id);
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [ embed ],
|
||||
files: [ attachment ],
|
||||
components: [ row ],
|
||||
});
|
||||
|
||||
CoreClient.ClaimId = claimId;
|
||||
} catch (e) {
|
||||
AppLogger.LogError("Button/Reroll", `Error sending next drop for card ${randomCard.card.id}: ${e}`);
|
||||
console.error(e);
|
||||
|
||||
await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. (${randomCard.card.id})`);
|
||||
|
||||
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,157 +0,0 @@
|
|||
import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, EmbedBuilder } from "discord.js";
|
||||
import { ButtonEvent } from "../type/buttonEvent";
|
||||
import Inventory from "../database/entities/app/Inventory";
|
||||
import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
||||
import { CardRarityToString, GetSacrificeAmount } from "../constants/CardRarity";
|
||||
import EmbedColours from "../constants/EmbedColours";
|
||||
import User from "../database/entities/app/User";
|
||||
|
||||
export default class Sacrifice extends ButtonEvent {
|
||||
public override async execute(interaction: ButtonInteraction) {
|
||||
const subcommand = interaction.customId.split(" ")[1];
|
||||
|
||||
switch(subcommand) {
|
||||
case "confirm":
|
||||
await this.confirm(interaction);
|
||||
break;
|
||||
case "cancel":
|
||||
await this.cancel(interaction);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async confirm(interaction: ButtonInteraction) {
|
||||
const userId = interaction.customId.split(" ")[2];
|
||||
const cardNumber = interaction.customId.split(" ")[3];
|
||||
|
||||
if (userId != interaction.user.id) {
|
||||
await interaction.reply("Only the user who created this sacrifice can confirm it.");
|
||||
return;
|
||||
}
|
||||
|
||||
const cardInInventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber);
|
||||
|
||||
if (!cardInInventory) {
|
||||
await interaction.reply("Unable to find card in inventory.");
|
||||
return;
|
||||
}
|
||||
|
||||
const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardNumber);
|
||||
|
||||
if (!cardData) {
|
||||
await interaction.reply("Unable to find card in the database.");
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await User.FetchOneById(User, userId);
|
||||
|
||||
if (!user) {
|
||||
await interaction.reply("Unable to find user in database.");
|
||||
return;
|
||||
}
|
||||
|
||||
cardInInventory.RemoveQuantity(1);
|
||||
|
||||
await cardInInventory.Save(Inventory, cardInInventory);
|
||||
|
||||
const cardValue = GetSacrificeAmount(cardData.card.type);
|
||||
const cardRarityString = CardRarityToString(cardData.card.type);
|
||||
|
||||
user.AddCurrency(cardValue);
|
||||
|
||||
await user.Save(User, user);
|
||||
|
||||
const description = [
|
||||
`Card: ${cardData.card.name}`,
|
||||
`Series: ${cardData.series.name}`,
|
||||
`Rarity: ${cardRarityString}`,
|
||||
`Quantity Owned: ${cardInInventory.Quantity}`,
|
||||
`Sacrifice Amount: ${cardValue}`,
|
||||
];
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("Card Sacrificed")
|
||||
.setDescription(description.join("\n"))
|
||||
.setColor(EmbedColours.Green)
|
||||
.setFooter({ text: `${interaction.user.username}` });
|
||||
|
||||
const row = new ActionRowBuilder<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];
|
||||
|
||||
if (userId != interaction.user.id) {
|
||||
await interaction.reply("Only the user who created this sacrifice can cancel it.");
|
||||
return;
|
||||
}
|
||||
|
||||
const cardInInventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber);
|
||||
|
||||
if (!cardInInventory) {
|
||||
await interaction.reply("Unable to find card in inventory.");
|
||||
return;
|
||||
}
|
||||
|
||||
const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardNumber);
|
||||
|
||||
if (!cardData) {
|
||||
await interaction.reply("Unable to find card in the database.");
|
||||
return;
|
||||
}
|
||||
|
||||
const cardValue = GetSacrificeAmount(cardData.card.type);
|
||||
const cardRarityString = CardRarityToString(cardData.card.type);
|
||||
|
||||
const description = [
|
||||
`Card: ${cardData.card.name}`,
|
||||
`Series: ${cardData.series.name}`,
|
||||
`Rarity: ${cardRarityString}`,
|
||||
`Quantity Owned: ${cardInInventory.Quantity}`,
|
||||
`Sacrifice Amount: ${cardValue}`,
|
||||
];
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("Sacrifice Cancelled")
|
||||
.setDescription(description.join("\n"))
|
||||
.setColor(EmbedColours.Grey)
|
||||
.setFooter({ text: `${interaction.user.username}` });
|
||||
|
||||
const row = new ActionRowBuilder<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 ],
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
import { ButtonInteraction } from "discord.js";
|
||||
import { ButtonEvent } from "../type/buttonEvent";
|
||||
import AppLogger from "../client/appLogger";
|
||||
import SeriesHelper from "../helpers/SeriesHelper";
|
||||
|
||||
export default class Series extends ButtonEvent {
|
||||
public override async execute(interaction: ButtonInteraction) {
|
||||
const subaction = interaction.customId.split(" ")[1];
|
||||
|
||||
switch(subaction) {
|
||||
case "view":
|
||||
await this.ViewSeries(interaction);
|
||||
break;
|
||||
case "list":
|
||||
await this.ListSeries(interaction);
|
||||
break;
|
||||
default:
|
||||
AppLogger.LogWarn("Commands/Series", `Subaction doesn't exist: ${subaction}`);
|
||||
interaction.reply("Subaction doesn't exist.");
|
||||
}
|
||||
}
|
||||
|
||||
private async ViewSeries(interaction: ButtonInteraction) {
|
||||
const seriesid = interaction.customId.split(" ")[2];
|
||||
const page = interaction.customId.split(" ")[3];
|
||||
|
||||
await interaction.deferUpdate();
|
||||
|
||||
const embed = await SeriesHelper.GenerateSeriesViewPage(Number(seriesid), Number(page), interaction.user.id);
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [ embed!.embed ],
|
||||
components: [ embed!.row ],
|
||||
files: [ embed!.image ],
|
||||
});
|
||||
}
|
||||
|
||||
private async ListSeries(interaction: ButtonInteraction) {
|
||||
const page = interaction.customId.split(" ")[2];
|
||||
|
||||
const embed = SeriesHelper.GenerateSeriesListPage(Number(page));
|
||||
|
||||
await interaction.update({
|
||||
embeds: [ embed!.embed ],
|
||||
components: [ embed!.row ],
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,211 +0,0 @@
|
|||
import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, EmbedBuilder } from "discord.js";
|
||||
import { ButtonEvent } from "../type/buttonEvent";
|
||||
import { CoreClient } from "../client/client";
|
||||
import Inventory from "../database/entities/app/Inventory";
|
||||
import EmbedColours from "../constants/EmbedColours";
|
||||
import AppLogger from "../client/appLogger";
|
||||
|
||||
export default class Trade extends ButtonEvent {
|
||||
public override async execute(interaction: ButtonInteraction) {
|
||||
const action = interaction.customId.split(" ")[1];
|
||||
|
||||
AppLogger.LogSilly("Button/Trade", `Parameters: action=${action}`);
|
||||
|
||||
switch (action) {
|
||||
case "accept":
|
||||
await this.AcceptTrade(interaction);
|
||||
break;
|
||||
case "decline":
|
||||
await this.DeclineTrade(interaction);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async AcceptTrade(interaction: ButtonInteraction) {
|
||||
const user1UserId = interaction.customId.split(" ")[2];
|
||||
const user2UserId = interaction.customId.split(" ")[3];
|
||||
const user1CardNumber = interaction.customId.split(" ")[4];
|
||||
const user2CardNumber = interaction.customId.split(" ")[5];
|
||||
const expiry = interaction.customId.split(" ")[6];
|
||||
const timeoutId = interaction.customId.split(" ")[7];
|
||||
|
||||
AppLogger.LogSilly("Button/Trade/AcceptTrade", `Parameters: user1UserId=${user1UserId}, user2UserId=${user2UserId}, user1CardNumber=${user1CardNumber}, user2CardNumber=${user2CardNumber}, expiry=${expiry}, timeoutId=${timeoutId}`);
|
||||
|
||||
const expiryDate = new Date(expiry);
|
||||
|
||||
if (expiryDate < new Date()) {
|
||||
await interaction.reply("Trade has expired");
|
||||
return;
|
||||
}
|
||||
|
||||
if (interaction.user.id !== user2UserId) {
|
||||
await interaction.reply("You are not the user who the trade is intended for");
|
||||
return;
|
||||
}
|
||||
|
||||
const user1Item = CoreClient.Cards
|
||||
.flatMap(x => x.cards)
|
||||
.find(x => x.id === user1CardNumber);
|
||||
|
||||
const user2Item = CoreClient.Cards
|
||||
.flatMap(x => x.cards)
|
||||
.find(x => x.id === user2CardNumber);
|
||||
|
||||
if (!user1Item || !user2Item) {
|
||||
await interaction.reply("One or more of the items you are trying to trade does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
const user1User = interaction.client.users.cache.get(user1UserId) || await interaction.client.users.fetch(user1UserId);
|
||||
const user2User = interaction.client.users.cache.get(user2UserId) || await interaction.client.users.fetch(user2UserId);
|
||||
|
||||
const user1UserInventory1 = await Inventory.FetchOneByCardNumberAndUserId(user1UserId, user1CardNumber);
|
||||
const user2UserInventory1 = await Inventory.FetchOneByCardNumberAndUserId(user2UserId, user2CardNumber);
|
||||
|
||||
if (!user1UserInventory1 || !user2UserInventory1) {
|
||||
await interaction.reply("One or more of the items you are trying to trade does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (user1UserInventory1.Quantity < 1 || user2UserInventory1.Quantity < 1) {
|
||||
await interaction.reply("One or more of the items you are trying to trade does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
user1UserInventory1.SetQuantity(user1UserInventory1.Quantity - 1);
|
||||
user2UserInventory1.SetQuantity(user2UserInventory1.Quantity - 1);
|
||||
|
||||
await user1UserInventory1.Save(Inventory, user1UserInventory1);
|
||||
await user2UserInventory1.Save(Inventory, user2UserInventory1);
|
||||
|
||||
let user1UserInventory2 = await Inventory.FetchOneByCardNumberAndUserId(user1UserId, user2CardNumber);
|
||||
let user2UserInventory2 = await Inventory.FetchOneByCardNumberAndUserId(user2UserId, user1CardNumber);
|
||||
|
||||
if (!user1UserInventory2) {
|
||||
user1UserInventory2 = new Inventory(user1UserId, user1CardNumber, 1);
|
||||
} else {
|
||||
user1UserInventory2.SetQuantity(user1UserInventory2.Quantity + 1);
|
||||
}
|
||||
|
||||
if (!user2UserInventory2) {
|
||||
user2UserInventory2 = new Inventory(user2UserId, user2CardNumber, 1);
|
||||
} else {
|
||||
user2UserInventory2.SetQuantity(user2UserInventory2.Quantity + 1);
|
||||
}
|
||||
|
||||
await user1UserInventory2.Save(Inventory, user1UserInventory2);
|
||||
await user2UserInventory2.Save(Inventory, user2UserInventory2);
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
const tradeEmbed = new EmbedBuilder()
|
||||
.setTitle("Trade Accepted")
|
||||
.setDescription(`Trade initiated between ${user1User.username} and ${user2User.username}`)
|
||||
.setColor(EmbedColours.Success)
|
||||
.setImage("https://i.imgur.com/9w5f1ls.gif")
|
||||
.addFields([
|
||||
{
|
||||
name: `${user1User.username} Receives`,
|
||||
value: `${user2Item.id}: ${user2Item.name}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: `${user2User.username} Receives`,
|
||||
value: `${user1Item.id}: ${user1Item.name}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "Complete",
|
||||
value: new Date().toLocaleString(),
|
||||
}
|
||||
]);
|
||||
|
||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||
.addComponents([
|
||||
new ButtonBuilder()
|
||||
.setCustomId("trade expired accept")
|
||||
.setLabel("Accept")
|
||||
.setStyle(ButtonStyle.Success)
|
||||
.setDisabled(true),
|
||||
new ButtonBuilder()
|
||||
.setCustomId("trade expired decline")
|
||||
.setLabel("Decline")
|
||||
.setStyle(ButtonStyle.Danger)
|
||||
.setDisabled(true),
|
||||
]);
|
||||
|
||||
await interaction.update({ embeds: [ tradeEmbed ], components: [ row ]});
|
||||
}
|
||||
|
||||
private async DeclineTrade(interaction: ButtonInteraction) {
|
||||
const user1UserId = interaction.customId.split(" ")[2];
|
||||
const user2UserId = interaction.customId.split(" ")[3];
|
||||
const user1CardNumber = interaction.customId.split(" ")[4];
|
||||
const user2CardNumber = interaction.customId.split(" ")[5];
|
||||
// No need to get expiry date
|
||||
const timeoutId = interaction.customId.split(" ")[7];
|
||||
|
||||
AppLogger.LogSilly("Button/Trade/DeclineTrade", `Parameters: user1UserId=${user1UserId}, user2UserId=${user2UserId}, user1CardNumber=${user1CardNumber}, user2CardNumber=${user2CardNumber}, timeoutId=${timeoutId}`);
|
||||
|
||||
if (interaction.user.id != user1UserId && interaction.user.id !== user2UserId) {
|
||||
await interaction.reply("You are not the user who the trade is intended for");
|
||||
return;
|
||||
}
|
||||
|
||||
const user1User = interaction.client.users.cache.get(user1UserId) || await interaction.client.users.fetch(user1UserId);
|
||||
const user2User = interaction.client.users.cache.get(user2UserId) || await interaction.client.users.fetch(user2UserId);
|
||||
|
||||
const user1Item = CoreClient.Cards
|
||||
.flatMap(x => x.cards)
|
||||
.find(x => x.id === user1CardNumber);
|
||||
|
||||
const user2Item = CoreClient.Cards
|
||||
.flatMap(x => x.cards)
|
||||
.find(x => x.id === user2CardNumber);
|
||||
|
||||
if (!user1Item || !user2Item) {
|
||||
await interaction.reply("One or more of the items you are trying to trade does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
const tradeEmbed = new EmbedBuilder()
|
||||
.setTitle("Trade Declined")
|
||||
.setDescription(`Trade initiated between ${user1User.username} and ${user2User.username}`)
|
||||
.setColor(EmbedColours.Error)
|
||||
.setImage("https://i.imgur.com/9w5f1ls.gif")
|
||||
.addFields([
|
||||
{
|
||||
name: `${user1User.username} Receives`,
|
||||
value: `${user2Item.id}: ${user2Item.name}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: `${user2User.username} Receives`,
|
||||
value: `${user1Item.id}: ${user1Item.name}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "Declined",
|
||||
value: new Date().toLocaleString(),
|
||||
}
|
||||
]);
|
||||
|
||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||
.addComponents([
|
||||
new ButtonBuilder()
|
||||
.setCustomId("trade expired accept")
|
||||
.setLabel("Accept")
|
||||
.setStyle(ButtonStyle.Success)
|
||||
.setDisabled(true),
|
||||
new ButtonBuilder()
|
||||
.setCustomId("trade expired decline")
|
||||
.setLabel("Decline")
|
||||
.setStyle(ButtonStyle.Danger)
|
||||
.setDisabled(true),
|
||||
]);
|
||||
|
||||
await interaction.update({ embeds: [ tradeEmbed ], components: [ row ]});
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
import path from "path";
|
||||
import { Logger, createLogger, format, transports } from "winston";
|
||||
import DailyRotateFile from "winston-daily-rotate-file";
|
||||
import DiscordTransport from "winston-discord-transport";
|
||||
|
||||
export default class AppLogger {
|
||||
public static Logger: Logger;
|
||||
|
||||
public static InitialiseLogger(logLevel: string, outputToConsole: boolean) {
|
||||
const customFormat = format.printf(({ level, message, timestamp, label }) => {
|
||||
return `${timestamp} [${label}] ${level}: ${message}`;
|
||||
});
|
||||
|
||||
const logger = createLogger({
|
||||
level: logLevel,
|
||||
format: format.combine(
|
||||
format.timestamp({
|
||||
format: "YYYY-MM-DD HH:mm:ss"
|
||||
}),
|
||||
format.errors({ stack: true }),
|
||||
format.splat(),
|
||||
customFormat,
|
||||
),
|
||||
defaultMeta: { service: "bot" },
|
||||
transports: [],
|
||||
});
|
||||
|
||||
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) {
|
||||
logger.add(new transports.Console({
|
||||
format: format.combine(
|
||||
format.colorize(),
|
||||
format.timestamp(),
|
||||
customFormat,
|
||||
)}));
|
||||
}
|
||||
|
||||
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.LogInfo("AppLogger", `Log Level: ${logLevel}`);
|
||||
}
|
||||
|
||||
public static LogError(label: string, message: string) {
|
||||
AppLogger.Logger.error({ label, message });
|
||||
}
|
||||
|
||||
public static LogWarn(label: string, message: string) {
|
||||
AppLogger.Logger.warn({ label, message });
|
||||
}
|
||||
|
||||
public static LogInfo(label: string, message: string) {
|
||||
AppLogger.Logger.info({ label, message });
|
||||
}
|
||||
|
||||
public static LogVerbose(label: string, message: string) {
|
||||
AppLogger.Logger.verbose({ label, message });
|
||||
}
|
||||
|
||||
public static LogDebug(label: string, message: string) {
|
||||
AppLogger.Logger.debug({ label, message });
|
||||
}
|
||||
|
||||
public static LogSilly(label: string, message: string) {
|
||||
AppLogger.Logger.silly({ label, message });
|
||||
}
|
||||
}
|
|
@ -1,44 +1,36 @@
|
|||
import { Client, DMChannel, Guild, GuildBan, GuildMember, Message, NonThreadGuildBasedChannel, PartialGuildMember, PartialMessage } from "discord.js";
|
||||
import { Client } from "discord.js";
|
||||
import * as dotenv from "dotenv";
|
||||
import { EventType } from "../constants/EventType";
|
||||
import ICommandItem from "../contracts/ICommandItem";
|
||||
import EventExecutors from "../contracts/EventExecutors";
|
||||
import IEventItem from "../contracts/IEventItem";
|
||||
import { Command } from "../type/command";
|
||||
|
||||
import { Events } from "./events";
|
||||
import { Util } from "./util";
|
||||
import IButtonEventItem from "../contracts/ButtonEventItem";
|
||||
import CardSetupFunction from "../Functions/CardSetupFunction";
|
||||
import CardDataSource from "../database/dataSources/cardDataSource";
|
||||
import CardDropHelper from "../helpers/CardDropHelper";
|
||||
import IButtonEventItem from "../contracts/IButtonEventItem";
|
||||
import { ButtonEvent } from "../type/buttonEvent";
|
||||
import AppDataSource from "../database/dataSources/appDataSource";
|
||||
import { Environment } from "../constants/Environment";
|
||||
import Webhooks from "../webhooks";
|
||||
import CardMetadataFunction from "../Functions/CardMetadataFunction";
|
||||
import { SeriesMetadata } from "../contracts/SeriesMetadata";
|
||||
import AppLogger from "./appLogger";
|
||||
import TimerHelper from "../helpers/TimerHelper";
|
||||
import GiveCurrency from "../timers/GiveCurrency";
|
||||
import PurgeClaims from "../timers/PurgeClaims";
|
||||
|
||||
export class CoreClient extends Client {
|
||||
private static _commandItems: ICommandItem[];
|
||||
private static _eventExecutors: EventExecutors;
|
||||
private static _eventItems: IEventItem[];
|
||||
private static _buttonEvents: IButtonEventItem[];
|
||||
|
||||
private _events: Events;
|
||||
private _util: Util;
|
||||
private _webhooks: Webhooks;
|
||||
private _timerHelper: TimerHelper;
|
||||
private _cardSetupFunc: CardSetupFunction;
|
||||
|
||||
public static ClaimId: string;
|
||||
public static Environment: Environment;
|
||||
public static AllowDrops: boolean;
|
||||
public static Cards: SeriesMetadata[];
|
||||
|
||||
public static get commandItems(): ICommandItem[] {
|
||||
return this._commandItems;
|
||||
}
|
||||
|
||||
public static get eventExecutors(): EventExecutors {
|
||||
return this._eventExecutors;
|
||||
public static get eventItems(): IEventItem[] {
|
||||
return this._eventItems;
|
||||
}
|
||||
|
||||
public static get buttonEvents(): IButtonEventItem[] {
|
||||
|
@ -49,363 +41,65 @@ export class CoreClient extends Client {
|
|||
super({ intents: intents });
|
||||
dotenv.config();
|
||||
|
||||
CoreClient.Environment = Number(process.env.BOT_ENV);
|
||||
|
||||
const loglevel = process.env.BOT_LOGLEVEL ?? "info";
|
||||
|
||||
AppLogger.InitialiseLogger(loglevel, CoreClient.Environment == Environment.Local);
|
||||
|
||||
AppLogger.LogInfo("Client", "Initialising Client");
|
||||
|
||||
CoreClient._commandItems = [];
|
||||
CoreClient._eventItems = [];
|
||||
CoreClient._buttonEvents = [];
|
||||
|
||||
this._events = new Events();
|
||||
this._util = new Util();
|
||||
this._webhooks = new Webhooks();
|
||||
this._timerHelper = new TimerHelper();
|
||||
|
||||
AppLogger.LogInfo("Client", `Environment: ${CoreClient.Environment}`);
|
||||
|
||||
CoreClient.AllowDrops = true;
|
||||
this._cardSetupFunc = new CardSetupFunction();
|
||||
}
|
||||
|
||||
public async start() {
|
||||
if (!process.env.BOT_TOKEN) {
|
||||
AppLogger.LogError("Client", "BOT_TOKEN is not defined in .env");
|
||||
console.error("BOT_TOKEN is not defined in .env");
|
||||
return;
|
||||
}
|
||||
|
||||
await AppDataSource.initialize()
|
||||
.then(() => {
|
||||
AppLogger.LogInfo("Client", "App Data Source Initialised");
|
||||
.then(() => console.log("App Data Source Initialised"))
|
||||
.catch(err => console.error("Error initialising App Data Source", err));
|
||||
|
||||
this._timerHelper.AddTimer("*/20 * * * *", "Europe/London", GiveCurrency, false);
|
||||
this._timerHelper.AddTimer("0 0 * * *", "Europe/London", PurgeClaims, false);
|
||||
|
||||
this._timerHelper.StartAllTimers();
|
||||
})
|
||||
.catch(err => {
|
||||
AppLogger.LogError("Client", "App Data Source Initialisation Failed");
|
||||
AppLogger.LogError("Client", err);
|
||||
throw err;
|
||||
});
|
||||
await CardDataSource.initialize()
|
||||
.then(() => console.log("Card Data Source Initialised"))
|
||||
.catch(err => console.error("Error initialising Card Data Source", err));
|
||||
|
||||
super.on("interactionCreate", this._events.onInteractionCreate);
|
||||
super.on("ready", this._events.onReady);
|
||||
|
||||
await CardMetadataFunction.Execute(true);
|
||||
|
||||
this._util.loadEvents(this, CoreClient._eventExecutors);
|
||||
this._util.loadSlashCommands(this);
|
||||
|
||||
this._webhooks.start();
|
||||
await this._cardSetupFunc.Execute();
|
||||
|
||||
await super.login(process.env.BOT_TOKEN);
|
||||
|
||||
this._util.loadEvents(this, CoreClient._eventItems);
|
||||
this._util.loadSlashCommands(this);
|
||||
}
|
||||
|
||||
public static RegisterCommand(name: string, command: Command, environment: Environment = Environment.All, serverId?: string) {
|
||||
public static RegisterCommand(name: string, command: Command, serverId?: string) {
|
||||
const item: ICommandItem = {
|
||||
Name: name,
|
||||
Environment: environment,
|
||||
Command: command,
|
||||
ServerId: serverId,
|
||||
};
|
||||
|
||||
if ((environment & CoreClient.Environment) == CoreClient.Environment) {
|
||||
CoreClient._commandItems.push(item);
|
||||
|
||||
AppLogger.LogVerbose("Client", `Registered Command: ${name}`);
|
||||
}
|
||||
CoreClient._commandItems.push(item);
|
||||
}
|
||||
|
||||
public static RegisterChannelCreateEvent(fn: (channel: NonThreadGuildBasedChannel) => void) {
|
||||
if (this._eventExecutors) {
|
||||
this._eventExecutors.ChannelCreate.push(fn);
|
||||
} else {
|
||||
this._eventExecutors = {
|
||||
ChannelCreate: [ fn ],
|
||||
ChannelDelete: [],
|
||||
ChannelUpdate: [],
|
||||
GuildBanAdd: [],
|
||||
GuildBanRemove: [],
|
||||
GuildCreate: [],
|
||||
GuildMemberAdd: [],
|
||||
GuildMemberRemove: [],
|
||||
GuildMemebrUpdate: [],
|
||||
MessageCreate: [],
|
||||
MessageDelete: [],
|
||||
MessageUpdate: [],
|
||||
};
|
||||
}
|
||||
public static RegisterEvent(eventType: EventType, func: Function) {
|
||||
const item: IEventItem = {
|
||||
EventType: eventType,
|
||||
ExecutionFunction: func,
|
||||
};
|
||||
|
||||
AppLogger.LogVerbose("Client", "Registered Channel Create Event");
|
||||
CoreClient._eventItems.push(item);
|
||||
}
|
||||
|
||||
public static RegisterChannelDeleteEvent(fn: (channel: DMChannel | NonThreadGuildBasedChannel) => void) {
|
||||
if (this._eventExecutors) {
|
||||
this._eventExecutors.ChannelDelete.push(fn);
|
||||
} else {
|
||||
this._eventExecutors = {
|
||||
ChannelCreate: [],
|
||||
ChannelDelete: [ fn ],
|
||||
ChannelUpdate: [],
|
||||
GuildBanAdd: [],
|
||||
GuildBanRemove: [],
|
||||
GuildCreate: [],
|
||||
GuildMemberAdd: [],
|
||||
GuildMemberRemove: [],
|
||||
GuildMemebrUpdate: [],
|
||||
MessageCreate: [],
|
||||
MessageDelete: [],
|
||||
MessageUpdate: [],
|
||||
};
|
||||
}
|
||||
|
||||
AppLogger.LogVerbose("Client", "Registered Channel Delete Event");
|
||||
}
|
||||
|
||||
public static RegisterChannelUpdateEvent(fn: (channel: DMChannel | NonThreadGuildBasedChannel) => void) {
|
||||
if (this._eventExecutors) {
|
||||
this._eventExecutors.ChannelCreate.push(fn);
|
||||
} else {
|
||||
this._eventExecutors = {
|
||||
ChannelCreate: [],
|
||||
ChannelDelete: [],
|
||||
ChannelUpdate: [ fn ],
|
||||
GuildBanAdd: [],
|
||||
GuildBanRemove: [],
|
||||
GuildCreate: [],
|
||||
GuildMemberAdd: [],
|
||||
GuildMemberRemove: [],
|
||||
GuildMemebrUpdate: [],
|
||||
MessageCreate: [],
|
||||
MessageDelete: [],
|
||||
MessageUpdate: [],
|
||||
};
|
||||
}
|
||||
|
||||
AppLogger.LogVerbose("Client", "Registered Channel Update Event");
|
||||
}
|
||||
|
||||
public static RegisterGuildBanAddEvent(fn: (ban: GuildBan) => void) {
|
||||
if (this._eventExecutors) {
|
||||
this._eventExecutors.GuildBanAdd.push(fn);
|
||||
} else {
|
||||
this._eventExecutors = {
|
||||
ChannelCreate: [],
|
||||
ChannelDelete: [],
|
||||
ChannelUpdate: [],
|
||||
GuildBanAdd: [ fn ],
|
||||
GuildBanRemove: [],
|
||||
GuildCreate: [],
|
||||
GuildMemberAdd: [],
|
||||
GuildMemberRemove: [],
|
||||
GuildMemebrUpdate: [],
|
||||
MessageCreate: [],
|
||||
MessageDelete: [],
|
||||
MessageUpdate: [],
|
||||
};
|
||||
}
|
||||
|
||||
AppLogger.LogVerbose("Client", "Registered Guild Ban Add Event");
|
||||
}
|
||||
|
||||
public static RegisterGuildBanRemoveEvent(fn: (channel: GuildBan) => void) {
|
||||
if (this._eventExecutors) {
|
||||
this._eventExecutors.GuildBanRemove.push(fn);
|
||||
} else {
|
||||
this._eventExecutors = {
|
||||
ChannelCreate: [],
|
||||
ChannelDelete: [],
|
||||
ChannelUpdate: [],
|
||||
GuildBanAdd: [],
|
||||
GuildBanRemove: [ fn ],
|
||||
GuildCreate: [],
|
||||
GuildMemberAdd: [],
|
||||
GuildMemberRemove: [],
|
||||
GuildMemebrUpdate: [],
|
||||
MessageCreate: [],
|
||||
MessageDelete: [],
|
||||
MessageUpdate: [],
|
||||
};
|
||||
}
|
||||
|
||||
AppLogger.LogVerbose("Client", "Registered Guild Ban Remove Event");
|
||||
}
|
||||
|
||||
public static RegisterGuildCreateEvent(fn: (guild: Guild) => void) {
|
||||
if (this._eventExecutors) {
|
||||
this._eventExecutors.GuildCreate.push(fn);
|
||||
} else {
|
||||
this._eventExecutors = {
|
||||
ChannelCreate: [],
|
||||
ChannelDelete: [],
|
||||
ChannelUpdate: [],
|
||||
GuildBanAdd: [],
|
||||
GuildBanRemove: [],
|
||||
GuildCreate: [ fn ],
|
||||
GuildMemberAdd: [],
|
||||
GuildMemberRemove: [],
|
||||
GuildMemebrUpdate: [],
|
||||
MessageCreate: [],
|
||||
MessageDelete: [],
|
||||
MessageUpdate: [],
|
||||
};
|
||||
}
|
||||
|
||||
AppLogger.LogVerbose("Client", "Registered Guild Create Event");
|
||||
}
|
||||
|
||||
public static RegisterGuildMemberAddEvent(fn: (member: GuildMember) => void) {
|
||||
if (this._eventExecutors) {
|
||||
this._eventExecutors.GuildMemberAdd.push(fn);
|
||||
} else {
|
||||
this._eventExecutors = {
|
||||
ChannelCreate: [],
|
||||
ChannelDelete: [],
|
||||
ChannelUpdate: [],
|
||||
GuildBanAdd: [],
|
||||
GuildBanRemove: [],
|
||||
GuildCreate: [],
|
||||
GuildMemberAdd: [ fn ],
|
||||
GuildMemberRemove: [],
|
||||
GuildMemebrUpdate: [],
|
||||
MessageCreate: [],
|
||||
MessageDelete: [],
|
||||
MessageUpdate: [],
|
||||
};
|
||||
}
|
||||
|
||||
AppLogger.LogVerbose("Client", "Registered Guild Member Add Event");
|
||||
}
|
||||
|
||||
public static RegisterGuildMemberRemoveEvent(fn: (member: GuildMember | PartialGuildMember) => void) {
|
||||
if (this._eventExecutors) {
|
||||
this._eventExecutors.GuildMemberRemove.push(fn);
|
||||
} else {
|
||||
this._eventExecutors = {
|
||||
ChannelCreate: [],
|
||||
ChannelDelete: [],
|
||||
ChannelUpdate: [],
|
||||
GuildBanAdd: [],
|
||||
GuildBanRemove: [],
|
||||
GuildCreate: [],
|
||||
GuildMemberAdd: [],
|
||||
GuildMemberRemove: [ fn ],
|
||||
GuildMemebrUpdate: [],
|
||||
MessageCreate: [],
|
||||
MessageDelete: [],
|
||||
MessageUpdate: [],
|
||||
};
|
||||
}
|
||||
|
||||
AppLogger.LogVerbose("Client", "Registered Guild Member Remove Event");
|
||||
}
|
||||
|
||||
public static GuildMemebrUpdate(fn: (oldMember: GuildMember | PartialGuildMember, newMember: GuildMember) => void) {
|
||||
if (this._eventExecutors) {
|
||||
this._eventExecutors.GuildMemebrUpdate.push(fn);
|
||||
} else {
|
||||
this._eventExecutors = {
|
||||
ChannelCreate: [],
|
||||
ChannelDelete: [],
|
||||
ChannelUpdate: [],
|
||||
GuildBanAdd: [],
|
||||
GuildBanRemove: [],
|
||||
GuildCreate: [],
|
||||
GuildMemberAdd: [],
|
||||
GuildMemberRemove: [],
|
||||
GuildMemebrUpdate: [ fn ],
|
||||
MessageCreate: [],
|
||||
MessageDelete: [],
|
||||
MessageUpdate: [],
|
||||
};
|
||||
}
|
||||
|
||||
AppLogger.LogVerbose("Client", "Registered Guild Member Update Event");
|
||||
}
|
||||
|
||||
public static RegisterMessageCreateEvent(fn: (message: Message<boolean>) => void) {
|
||||
if (this._eventExecutors) {
|
||||
this._eventExecutors.MessageCreate.push(fn);
|
||||
} else {
|
||||
this._eventExecutors = {
|
||||
ChannelCreate: [],
|
||||
ChannelDelete: [],
|
||||
ChannelUpdate: [],
|
||||
GuildBanAdd: [],
|
||||
GuildBanRemove: [],
|
||||
GuildCreate: [],
|
||||
GuildMemberAdd: [],
|
||||
GuildMemberRemove: [],
|
||||
GuildMemebrUpdate: [],
|
||||
MessageCreate: [ fn ],
|
||||
MessageDelete: [],
|
||||
MessageUpdate: [],
|
||||
};
|
||||
}
|
||||
|
||||
AppLogger.LogVerbose("Client", "Registered Message Create Event");
|
||||
}
|
||||
|
||||
public static RegisterMessageDeleteEvent(fn: (message: Message<boolean> | PartialMessage) => void) {
|
||||
if (this._eventExecutors) {
|
||||
this._eventExecutors.MessageDelete.push(fn);
|
||||
} else {
|
||||
this._eventExecutors = {
|
||||
ChannelCreate: [],
|
||||
ChannelDelete: [],
|
||||
ChannelUpdate: [],
|
||||
GuildBanAdd: [],
|
||||
GuildBanRemove: [],
|
||||
GuildCreate: [],
|
||||
GuildMemberAdd: [],
|
||||
GuildMemberRemove: [],
|
||||
GuildMemebrUpdate: [],
|
||||
MessageCreate: [],
|
||||
MessageDelete: [ fn ],
|
||||
MessageUpdate: [],
|
||||
};
|
||||
}
|
||||
|
||||
AppLogger.LogVerbose("Client", "Registered Message Delete Event");
|
||||
}
|
||||
|
||||
public static RegisterMessageUpdateEvent(fn: (oldMessage: Message<boolean> | PartialMessage, newMessage: Message<boolean> | PartialMessage) => void) {
|
||||
if (this._eventExecutors) {
|
||||
this._eventExecutors.MessageUpdate.push(fn);
|
||||
} else {
|
||||
this._eventExecutors = {
|
||||
ChannelCreate: [],
|
||||
ChannelDelete: [],
|
||||
ChannelUpdate: [],
|
||||
GuildBanAdd: [],
|
||||
GuildBanRemove: [],
|
||||
GuildCreate: [],
|
||||
GuildMemberAdd: [],
|
||||
GuildMemberRemove: [],
|
||||
GuildMemebrUpdate: [],
|
||||
MessageCreate: [],
|
||||
MessageDelete: [],
|
||||
MessageUpdate: [ fn ],
|
||||
};
|
||||
}
|
||||
|
||||
AppLogger.LogVerbose("Client", "Registered Message Update Event");
|
||||
}
|
||||
|
||||
public static RegisterButtonEvent(buttonId: string, event: ButtonEvent, environment: Environment = Environment.All) {
|
||||
public static RegisterButtonEvent(buttonId: string, event: ButtonEvent) {
|
||||
const item: IButtonEventItem = {
|
||||
ButtonId: buttonId,
|
||||
Event: event,
|
||||
Environment: environment,
|
||||
};
|
||||
|
||||
if ((environment & CoreClient.Environment) == CoreClient.Environment) {
|
||||
CoreClient._buttonEvents.push(item);
|
||||
|
||||
AppLogger.LogVerbose("Client", `Registered Button Event: ${buttonId}`);
|
||||
}
|
||||
CoreClient._buttonEvents.push(item);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +1,22 @@
|
|||
import { Interaction } from "discord.js";
|
||||
import ChatInputCommand from "./interactionCreate/ChatInputCommand";
|
||||
import Button from "./interactionCreate/Button";
|
||||
import AppLogger from "./appLogger";
|
||||
import NewUserDiscovery from "./interactionCreate/middleware/NewUserDiscovery";
|
||||
|
||||
export class Events {
|
||||
public async onInteractionCreate(interaction: Interaction) {
|
||||
if (!interaction.guildId) return;
|
||||
|
||||
await NewUserDiscovery(interaction);
|
||||
|
||||
if (interaction.isChatInputCommand()) {
|
||||
AppLogger.LogVerbose("Client", `ChatInputCommand: ${interaction.commandName}`);
|
||||
ChatInputCommand.onChatInput(interaction);
|
||||
}
|
||||
|
||||
if (interaction.isButton()) {
|
||||
AppLogger.LogVerbose("Client", `Button: ${interaction.customId}`);
|
||||
Button.onButtonClicked(interaction);
|
||||
}
|
||||
}
|
||||
|
||||
// Emit when bot is logged in and ready to use
|
||||
public onReady() {
|
||||
AppLogger.LogInfo("Client", "Ready");
|
||||
console.log("Ready");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,17 @@
|
|||
import { ButtonInteraction } from "discord.js";
|
||||
import { ButtonInteraction, Interaction } from "discord.js";
|
||||
import { CoreClient } from "../client";
|
||||
import AppLogger from "../appLogger";
|
||||
|
||||
export default class Button {
|
||||
public static async onButtonClicked(interaction: ButtonInteraction) {
|
||||
if (!interaction.isButton) return;
|
||||
|
||||
const item = CoreClient.buttonEvents.find(x => x.ButtonId == interaction.customId.split(" ")[0]);
|
||||
const item = CoreClient.buttonEvents.find(x => x.ButtonId == interaction.customId.split(' ')[0]);
|
||||
|
||||
if (!item) {
|
||||
AppLogger.LogVerbose("Button", `Event not found: ${interaction.customId}`);
|
||||
|
||||
await interaction.reply("Event not found");
|
||||
await interaction.reply('Event not found');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
AppLogger.LogDebug("Button", `Executing ${interaction.customId}`);
|
||||
|
||||
item.Event.execute(interaction);
|
||||
} catch (e) {
|
||||
AppLogger.LogError("Button", `Error occurred while executing event: ${interaction.customId}`);
|
||||
AppLogger.LogError("Button", e as string);
|
||||
|
||||
await interaction.reply("An error occurred while executing the event");
|
||||
}
|
||||
item.Event.execute(interaction);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import { Interaction } from "discord.js";
|
||||
import { CoreClient } from "../client";
|
||||
import ICommandItem from "../../contracts/ICommandItem";
|
||||
import AppLogger from "../appLogger";
|
||||
|
||||
export default class ChatInputCommand {
|
||||
public static async onChatInput(interaction: Interaction) {
|
||||
|
@ -14,9 +13,7 @@ export default class ChatInputCommand {
|
|||
|
||||
if (!itemForServer) {
|
||||
if (!item) {
|
||||
AppLogger.LogVerbose("ChatInputCommand", `Command not found: ${interaction.commandName}`);
|
||||
|
||||
await interaction.reply("Command not found");
|
||||
await interaction.reply('Command not found');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -25,15 +22,6 @@ export default class ChatInputCommand {
|
|||
itemToUse = itemForServer;
|
||||
}
|
||||
|
||||
try {
|
||||
AppLogger.LogDebug("Command", `Executing ${interaction.commandName}`);
|
||||
|
||||
itemToUse.Command.execute(interaction);
|
||||
} catch (e) {
|
||||
AppLogger.LogError("ChatInputCommand", `Error occurred while executing command: ${interaction.commandName}`);
|
||||
AppLogger.LogError("ChatInputCommand", e as string);
|
||||
|
||||
await interaction.reply("An error occurred while executing the command");
|
||||
}
|
||||
itemToUse.Command.execute(interaction);
|
||||
}
|
||||
}
|
|
@ -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,7 +1,7 @@
|
|||
import { Client, REST, Routes, SlashCommandBuilder } from "discord.js";
|
||||
import EventExecutors from "../contracts/EventExecutors";
|
||||
import { EventType } from "../constants/EventType";
|
||||
import IEventItem from "../contracts/IEventItem";
|
||||
import { CoreClient } from "./client";
|
||||
import AppLogger from "./appLogger";
|
||||
|
||||
export class Util {
|
||||
public loadSlashCommands(client: Client) {
|
||||
|
@ -10,27 +10,19 @@ export class Util {
|
|||
const globalCommands = registeredCommands.filter(x => !x.ServerId);
|
||||
const guildCommands = registeredCommands.filter(x => x.ServerId);
|
||||
|
||||
const globalCommandData: Omit<SlashCommandBuilder, "addSubcommand" | "addSubcommandGroup">[] = [];
|
||||
|
||||
for (const command of globalCommands) {
|
||||
if (!command.Command.CommandBuilder) continue;
|
||||
|
||||
if ((command.Environment & CoreClient.Environment) == CoreClient.Environment) {
|
||||
globalCommandData.push(command.Command.CommandBuilder);
|
||||
}
|
||||
}
|
||||
const globalCommandData: SlashCommandBuilder[] = globalCommands
|
||||
.filter(x => x.Command.CommandBuilder)
|
||||
.flatMap(x => x.Command.CommandBuilder);
|
||||
|
||||
const guildIds: string[] = [];
|
||||
|
||||
for (const command of guildCommands) {
|
||||
for (let command of guildCommands) {
|
||||
if (!guildIds.find(x => x == command.ServerId)) {
|
||||
guildIds.push(command.ServerId!);
|
||||
}
|
||||
}
|
||||
|
||||
const rest = new REST({ version: "10" }).setToken(process.env.BOT_TOKEN!);
|
||||
|
||||
AppLogger.LogVerbose("Util", `REST PUT: ${globalCommandData.flatMap(x => x.name).join(", ")}`);
|
||||
const rest = new REST({ version: '10' }).setToken(process.env.BOT_TOKEN!);
|
||||
|
||||
rest.put(
|
||||
Routes.applicationCommands(process.env.BOT_CLIENTID!),
|
||||
|
@ -39,43 +31,65 @@ export class Util {
|
|||
}
|
||||
);
|
||||
|
||||
for (const guild of guildIds) {
|
||||
const guildCommandData: Omit<SlashCommandBuilder, "addSubcommand" | "addSubcommandGroup">[] = [];
|
||||
|
||||
for (const command of guildCommands.filter(x => x.ServerId == guild)) {
|
||||
if (!command.Command.CommandBuilder) continue;
|
||||
|
||||
if ((command.Environment & CoreClient.Environment) == CoreClient.Environment) {
|
||||
guildCommandData.push(command.Command.CommandBuilder);
|
||||
}
|
||||
}
|
||||
for (let guild of guildIds) {
|
||||
const guildCommandData = guildCommands.filter(x => x.ServerId == guild)
|
||||
.filter(x => x.Command.CommandBuilder)
|
||||
.flatMap(x => x.Command.CommandBuilder);
|
||||
|
||||
if (!client.guilds.cache.has(guild)) continue;
|
||||
|
||||
AppLogger.LogVerbose("Util", `REST PUT: ${guild} - ${guildCommandData.flatMap(x => x.name).join(", ")}`);
|
||||
|
||||
|
||||
rest.put(
|
||||
Routes.applicationGuildCommands(process.env.BOT_CLIENTID!, guild),
|
||||
{
|
||||
body: guildCommandData
|
||||
}
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Load the events
|
||||
loadEvents(client: Client, events: EventExecutors) {
|
||||
client.on("channelCreate", (channel) => events.ChannelCreate.forEach((fn) => fn(channel)));
|
||||
client.on("channelDelete", (channel) => events.ChannelDelete.forEach((fn) => fn(channel)));
|
||||
client.on("channelUpdate", (channel) => events.ChannelUpdate.forEach((fn) => fn(channel)));
|
||||
client.on("guildBanAdd", (ban) => events.GuildBanAdd.forEach((fn) => fn(ban)));
|
||||
client.on("guildBanRemove", (ban) => events.GuildBanRemove.forEach((fn) => fn(ban)));
|
||||
client.on("guildCreate", (guild) => events.GuildCreate.forEach((fn) => fn(guild)));
|
||||
client.on("guildMemberAdd", (member) => events.GuildMemberAdd.forEach((fn) => fn(member)));
|
||||
client.on("guildMemberRemove", (member) => events.GuildMemberRemove.forEach((fn) => fn(member)));
|
||||
client.on("guildMemberUpdate", (oldMember, newMember) => events.GuildMemebrUpdate.forEach((fn) => fn(oldMember, newMember)));
|
||||
client.on("messageCreate", (message) => events.MessageCreate.forEach((fn) => fn(message)));
|
||||
client.on("messageDelete", (message) => events.MessageDelete.forEach((fn) => fn(message)));
|
||||
client.on("messageUpdate", (oldMessage, newMessage) => events.MessageUpdate.forEach((fn) => fn(oldMessage, newMessage)));
|
||||
loadEvents(client: Client, events: IEventItem[]) {
|
||||
events.forEach((e) => {
|
||||
switch(e.EventType) {
|
||||
case EventType.ChannelCreate:
|
||||
client.on('channelCreate', (channel) => e.ExecutionFunction(channel));
|
||||
break;
|
||||
case EventType.ChannelDelete:
|
||||
client.on('channelDelete', (channel) => e.ExecutionFunction(channel));
|
||||
break;
|
||||
case EventType.ChannelUpdate:
|
||||
client.on('channelUpdate', (channel) => e.ExecutionFunction(channel));
|
||||
break;
|
||||
case EventType.GuildBanAdd:
|
||||
client.on('guildBanAdd', (ban) => e.ExecutionFunction(ban));
|
||||
break;
|
||||
case EventType.GuildBanRemove:
|
||||
client.on('guildBanRemove', (ban) => e.ExecutionFunction(ban));
|
||||
break;
|
||||
case EventType.GuildCreate:
|
||||
client.on('guildCreate', (guild) => e.ExecutionFunction(guild));
|
||||
break;
|
||||
case EventType.GuildMemberAdd:
|
||||
client.on('guildMemberAdd', (member) => e.ExecutionFunction(member));
|
||||
break;
|
||||
case EventType.GuildMemberRemove:
|
||||
client.on('guildMemberRemove', (member) => e.ExecutionFunction(member));
|
||||
break;
|
||||
case EventType.GuildMemberUpdate:
|
||||
client.on('guildMemberUpdate', (oldMember, newMember) => e.ExecutionFunction(oldMember, newMember));
|
||||
break;
|
||||
case EventType.MessageCreate:
|
||||
client.on('messageCreate', (message) => e.ExecutionFunction(message));
|
||||
break;
|
||||
case EventType.MessageDelete:
|
||||
client.on('messageDelete', (message) => e.ExecutionFunction(message));
|
||||
break;
|
||||
case EventType.MessageUpdate:
|
||||
client.on('messageUpdate', (oldMessage, newMessage) => e.ExecutionFunction(oldMessage, newMessage));
|
||||
break;
|
||||
default:
|
||||
console.error('Event not implemented.');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@ export default class About extends Command {
|
|||
constructor() {
|
||||
super();
|
||||
|
||||
this.CommandBuilder = new SlashCommandBuilder()
|
||||
.setName("about")
|
||||
.setDescription("About Bot");
|
||||
super.CommandBuilder = new SlashCommandBuilder()
|
||||
.setName('about')
|
||||
.setDescription('About Bot');
|
||||
}
|
||||
|
||||
public override async execute(interaction: CommandInteraction) {
|
||||
|
|
|
@ -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!`);
|
||||
}
|
||||
}
|
|
@ -1,91 +1,82 @@
|
|||
import { AttachmentBuilder, CommandInteraction, SlashCommandBuilder } from "discord.js";
|
||||
import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, DiscordAPIError, EmbedBuilder, SlashCommandBuilder } from "discord.js";
|
||||
import { Command } from "../type/command";
|
||||
import CardDropHelper from "../helpers/CardDropHelper";
|
||||
import { CardRarityToColour, CardRarityToString } from "../constants/CardRarity";
|
||||
import { readFileSync } from "fs";
|
||||
import { CoreClient } from "../client/client";
|
||||
import { v4 } from "uuid";
|
||||
import Inventory from "../database/entities/app/Inventory";
|
||||
import Config from "../database/entities/app/Config";
|
||||
import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
||||
import path from "path";
|
||||
import AppLogger from "../client/appLogger";
|
||||
import User from "../database/entities/app/User";
|
||||
import CardConstants from "../constants/CardConstants";
|
||||
import Card from "../database/entities/card/Card";
|
||||
|
||||
export default class Drop extends Command {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.CommandBuilder = new SlashCommandBuilder()
|
||||
.setName("drop")
|
||||
.setDescription("Summon a new card drop");
|
||||
super.CommandBuilder = new SlashCommandBuilder()
|
||||
.setName('drop')
|
||||
.setDescription('Summon a new card drop');
|
||||
}
|
||||
|
||||
public override async execute(interaction: CommandInteraction) {
|
||||
if (!CoreClient.AllowDrops) {
|
||||
await interaction.reply("Bot is currently syncing, please wait until its done.");
|
||||
return;
|
||||
let randomCard = await CardDropHelper.GetRandomCard();
|
||||
|
||||
if (process.env.DROP_RARITY && Number(process.env.DROP_RARITY) > 0) {
|
||||
randomCard = await CardDropHelper.GetRandomCardByRarity(Number(process.env.DROP_RARITY));
|
||||
} else if (process.env.DROP_CARD && process.env.DROP_CARD != '-1') {
|
||||
let card = await Card.FetchOneByCardNumber(process.env.DROP_CARD, [ "Series" ]);
|
||||
|
||||
if (!card) {
|
||||
await interaction.reply("Card not found");
|
||||
return;
|
||||
}
|
||||
|
||||
randomCard = card;
|
||||
}
|
||||
|
||||
if (await Config.GetValue("safemode") == "true") {
|
||||
AppLogger.LogWarn("Commands/Drop", "Safe Mode is active, refusing to send next drop.");
|
||||
|
||||
await interaction.reply("Safe Mode has been activated, please resync to continue.");
|
||||
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/Drop", `New user (${interaction.user.id}) saved to the database`);
|
||||
}
|
||||
|
||||
if (user.Currency < CardConstants.ClaimCost) {
|
||||
await interaction.reply(`Not enough currency! You need ${CardConstants.ClaimCost} currency, you have ${user.Currency}!`);
|
||||
return;
|
||||
}
|
||||
|
||||
const randomCard = CardDropHelperMetadata.GetRandomCard();
|
||||
|
||||
if (!randomCard) {
|
||||
AppLogger.LogWarn("Commands/Drop", "Unable to fetch card, please try again. (randomCard is null)");
|
||||
|
||||
await interaction.reply("Unable to fetch card, please try again.");
|
||||
return;
|
||||
}
|
||||
const image = readFileSync(randomCard.Path);
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
const attachment = new AttachmentBuilder(image, { name: randomCard.FileName });
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(randomCard.Name)
|
||||
.setDescription(randomCard.Series.Name)
|
||||
.setFooter({ text: CardRarityToString(randomCard.Rarity) })
|
||||
.setColor(CardRarityToColour(randomCard.Rarity))
|
||||
.setImage(`attachment://${randomCard.FileName}`);
|
||||
|
||||
const row = new ActionRowBuilder<ButtonBuilder>();
|
||||
|
||||
const claimId = v4();
|
||||
|
||||
row.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`claim ${randomCard.CardNumber} ${claimId}`)
|
||||
.setLabel("Claim")
|
||||
.setStyle(ButtonStyle.Primary),
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`reroll`)
|
||||
.setLabel("Reroll")
|
||||
.setStyle(ButtonStyle.Secondary));
|
||||
|
||||
try {
|
||||
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path));
|
||||
const imageFileName = randomCard.card.path.split("/").pop()!;
|
||||
|
||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||
|
||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
||||
|
||||
const embed = CardDropHelperMetadata.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName, undefined, user.Currency);
|
||||
|
||||
const claimId = v4();
|
||||
|
||||
const row = CardDropHelperMetadata.GenerateDropButtons(randomCard, claimId, interaction.user.id);
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [ embed ],
|
||||
files: [ attachment ],
|
||||
components: [ row ],
|
||||
});
|
||||
|
||||
CoreClient.ClaimId = claimId;
|
||||
|
||||
} catch (e) {
|
||||
AppLogger.LogError("Commands/Drop", `Error sending next drop for card ${randomCard.card.id}: ${e}`);
|
||||
console.error(e);
|
||||
|
||||
await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. (${randomCard.card.id})`);
|
||||
|
||||
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,63 +0,0 @@
|
|||
import { CacheType, CommandInteraction, PermissionsBitField, SlashCommandBuilder } from "discord.js";
|
||||
import { Command } from "../type/command";
|
||||
import { ExecException, exec } from "child_process";
|
||||
import { CoreClient } from "../client/client";
|
||||
import Config from "../database/entities/app/Config";
|
||||
import CardMetadataFunction from "../Functions/CardMetadataFunction";
|
||||
import AppLogger from "../client/appLogger";
|
||||
|
||||
export default class Gdrivesync extends Command {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.CommandBuilder = new SlashCommandBuilder()
|
||||
.setName("gdrivesync")
|
||||
.setDescription("Sync google drive to the bot")
|
||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
await interaction.reply({
|
||||
content: "Syncing, this might take a while...",
|
||||
ephemeral: true,
|
||||
});
|
||||
|
||||
CoreClient.AllowDrops = false;
|
||||
|
||||
AppLogger.LogInfo("Commands/GDriveSync", "Syncing google drive to the bot");
|
||||
|
||||
exec(`rclone sync card-drop-gdrive: ${process.env.DATA_DIR}/cards`, async (error: ExecException | null) => {
|
||||
if (error) {
|
||||
AppLogger.LogError("Commands/GDriveSync", `Error while running sync command: ${error.code}, ${error.message}`);
|
||||
AppLogger.LogWarn("Commands/GDriveSync", "Safe mode activated");
|
||||
|
||||
await interaction.editReply(`Error while running sync command. Safe Mode has been activated. Code: ${error.code}`);
|
||||
await Config.SetValue("safemode", "true");
|
||||
} else {
|
||||
const result = await CardMetadataFunction.Execute(true);
|
||||
|
||||
if (result.IsSuccess) {
|
||||
AppLogger.LogInfo("Commands/GDriveSync", "Synced successfully");
|
||||
|
||||
await interaction.editReply("Synced successfully.");
|
||||
|
||||
CoreClient.AllowDrops = true;
|
||||
await Config.SetValue("safemode", "false");
|
||||
} else {
|
||||
AppLogger.LogError("Commands/GDriveSync", `Error while running sync command: ${result.ErrorMessage}`);
|
||||
|
||||
await interaction.editReply(`Sync failed \`\`\`${result.ErrorMessage}\`\`\``);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
import { CacheType, CommandInteraction, PermissionsBitField, SlashCommandBuilder } from "discord.js";
|
||||
import { Command } from "../type/command";
|
||||
import { CoreClient } from "../client/client";
|
||||
import Config from "../database/entities/app/Config";
|
||||
import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
||||
import Inventory from "../database/entities/app/Inventory";
|
||||
import AppLogger from "../client/appLogger";
|
||||
import User from "../database/entities/app/User";
|
||||
|
||||
export default class Give extends Command {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.CommandBuilder = new SlashCommandBuilder()
|
||||
.setName("give")
|
||||
.setDescription("Give a user a card manually, in case bot breaks")
|
||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator)
|
||||
.addSubcommand(x =>
|
||||
x
|
||||
.setName("card")
|
||||
.setDescription("Give a user a card manually")
|
||||
.addStringOption(x =>
|
||||
x
|
||||
.setName("cardnumber")
|
||||
.setDescription("The card to give")
|
||||
.setRequired(true))
|
||||
.addUserOption(x =>
|
||||
x
|
||||
.setName("user")
|
||||
.setDescription("The user to give the card to")
|
||||
.setRequired(true)))
|
||||
.addSubcommand(x =>
|
||||
x
|
||||
.setName("currency")
|
||||
.setDescription("Give a user currency manually")
|
||||
.addNumberOption(x =>
|
||||
x
|
||||
.setName("amount")
|
||||
.setDescription("The amount to give")
|
||||
.setRequired(true))
|
||||
.addUserOption(x =>
|
||||
x
|
||||
.setName("user")
|
||||
.setDescription("The user to give the currency to")
|
||||
.setRequired(true)));
|
||||
}
|
||||
|
||||
public override async execute(interaction: CommandInteraction<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) {
|
||||
await interaction.reply("Bot is currently syncing, please wait until its done.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (await Config.GetValue("safemode") == "true") {
|
||||
await interaction.reply("Safe Mode has been activated, please resync to continue.");
|
||||
return;
|
||||
}
|
||||
|
||||
const cardNumber = interaction.options.get("cardnumber", true);
|
||||
const user = interaction.options.get("user", true).user!;
|
||||
|
||||
AppLogger.LogSilly("Commands/Give/GiveCard", `Parameters: cardNumber=${cardNumber.value}, user=${user.id}`);
|
||||
|
||||
const card = CardDropHelperMetadata.GetCardByCardNumber(cardNumber.value!.toString());
|
||||
|
||||
if (!card) {
|
||||
await interaction.reply("Unable to fetch card, please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
let inventory = await Inventory.FetchOneByCardNumberAndUserId(user.id, card.card.id);
|
||||
|
||||
if (!inventory) {
|
||||
inventory = new Inventory(user.id, card.card.id, 1);
|
||||
} else {
|
||||
inventory.SetQuantity(inventory.Quantity + 1);
|
||||
}
|
||||
|
||||
await inventory.Save(Inventory, inventory);
|
||||
|
||||
await interaction.reply(`Card ${card.card.name} given to ${user.username}, they now have ${inventory.Quantity}`);
|
||||
}
|
||||
|
||||
private async GiveCurrency(interaction: CommandInteraction) {
|
||||
const amount = interaction.options.get("amount", true);
|
||||
const user = interaction.options.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,58 +0,0 @@
|
|||
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
|
||||
import { Command } from "../type/command";
|
||||
import InventoryHelper from "../helpers/InventoryHelper";
|
||||
import AppLogger from "../client/appLogger";
|
||||
|
||||
export default class Inventory extends Command {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.CommandBuilder = new SlashCommandBuilder()
|
||||
.setName("inventory")
|
||||
.setDescription("View your inventory")
|
||||
.addNumberOption(x =>
|
||||
x
|
||||
.setName("page")
|
||||
.setDescription("The page to start with"))
|
||||
.addUserOption(x =>
|
||||
x
|
||||
.setName("user")
|
||||
.setDescription("The user to view (Defaults to yourself)"));
|
||||
}
|
||||
|
||||
public override async execute(interaction: CommandInteraction) {
|
||||
const page = interaction.options.get("page");
|
||||
const userOption = interaction.options.get("user");
|
||||
|
||||
const user = userOption ? userOption.user! : interaction.user;
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
AppLogger.LogSilly("Commands/Inventory", `Parameters: page=${page?.value}, user=${user.id}`);
|
||||
|
||||
try {
|
||||
let pageNumber = 0;
|
||||
|
||||
if (page && page.value) {
|
||||
pageNumber = Number(page.value) - 1;
|
||||
}
|
||||
|
||||
const embed = await InventoryHelper.GenerateInventoryPage(user.username, user.id, pageNumber);
|
||||
|
||||
if (!embed) {
|
||||
await interaction.followUp("No page for user found.");
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.followUp({
|
||||
files: [ embed.image ],
|
||||
embeds: [ embed.embed ],
|
||||
components: [ embed.row ],
|
||||
});
|
||||
} catch (e) {
|
||||
AppLogger.LogError("Commands/Inventory", e as string);
|
||||
|
||||
await interaction.followUp("An error has occurred running this command.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
import { CacheType, CommandInteraction, PermissionsBitField, SlashCommandBuilder } from "discord.js";
|
||||
import { Command } from "../type/command";
|
||||
import Config from "../database/entities/app/Config";
|
||||
import CardMetadataFunction from "../Functions/CardMetadataFunction";
|
||||
import AppLogger from "../client/appLogger";
|
||||
|
||||
export default class Resync extends Command {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.CommandBuilder = new SlashCommandBuilder()
|
||||
.setName("resync")
|
||||
.setDescription("Resync the card database")
|
||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
AppLogger.LogInfo("Commands/Resync", "Resyncing database");
|
||||
|
||||
const result = await CardMetadataFunction.Execute(true);
|
||||
|
||||
if (result) {
|
||||
if (await Config.GetValue("safemode") == "true") {
|
||||
AppLogger.LogInfo("Commands/Resync", "Resync successful, safe mode disabled");
|
||||
|
||||
await Config.SetValue("safemode", "false");
|
||||
await interaction.reply("Resynced database and disabled safe mode.");
|
||||
|
||||
return;
|
||||
}
|
||||
await interaction.reply("Resynced database.");
|
||||
} else {
|
||||
AppLogger.LogWarn("Commands/Resync", "Resync failed, safe mode activated");
|
||||
|
||||
await interaction.reply("Resync failed, safe mode has been activated until successful resync.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,73 +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 CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
||||
import EmbedColours from "../constants/EmbedColours";
|
||||
|
||||
export default class Sacrifice extends Command {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.CommandBuilder = new SlashCommandBuilder()
|
||||
.setName("sacrifice")
|
||||
.setDescription("Sacrifices a card for currency")
|
||||
.addStringOption(x =>
|
||||
x
|
||||
.setName("cardnumber")
|
||||
.setDescription("The card to sacrifice from your inventory")
|
||||
.setRequired(true));
|
||||
}
|
||||
|
||||
public override async execute(interaction: CommandInteraction<CacheType>): Promise<void> {
|
||||
const cardnumber = interaction.options.get("cardnumber", true);
|
||||
|
||||
const cardInInventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, cardnumber.value! as string);
|
||||
|
||||
if (!cardInInventory || cardInInventory.Quantity == 0) {
|
||||
await interaction.reply("Unable to find card in your inventory.");
|
||||
return;
|
||||
}
|
||||
|
||||
const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardnumber.value! as string);
|
||||
|
||||
if (!cardData) {
|
||||
await interaction.reply("Unable to find card in the database.");
|
||||
return;
|
||||
}
|
||||
|
||||
const cardValue = GetSacrificeAmount(cardData.card.type);
|
||||
const cardRarityString = CardRarityToString(cardData.card.type);
|
||||
|
||||
const description = [
|
||||
`Card: ${cardData.card.name}`,
|
||||
`Series: ${cardData.series.name}`,
|
||||
`Rarity: ${cardRarityString}`,
|
||||
`Quantity Owned: ${cardInInventory.Quantity}`,
|
||||
`Sacrifice Amount: ${cardValue}`,
|
||||
];
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("Sacrifice")
|
||||
.setDescription(description.join("\n"))
|
||||
.setColor(EmbedColours.Error)
|
||||
.setFooter({ text: `${interaction.user.username}` });
|
||||
|
||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||
.addComponents([
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`sacrifice confirm ${interaction.user.id} ${cardnumber.value!}`)
|
||||
.setLabel("Confirm")
|
||||
.setStyle(ButtonStyle.Success),
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`sacrifice cancel ${interaction.user.id} ${cardnumber.value!}`)
|
||||
.setLabel("Cancel")
|
||||
.setStyle(ButtonStyle.Secondary),
|
||||
]);
|
||||
|
||||
await interaction.reply({
|
||||
embeds: [ embed ],
|
||||
components: [ row ],
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
|
||||
import { Command } from "../type/command";
|
||||
import { CoreClient } from "../client/client";
|
||||
import AppLogger from "../client/appLogger";
|
||||
import SeriesHelper from "../helpers/SeriesHelper";
|
||||
|
||||
export default class Series extends Command {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.CommandBuilder = new SlashCommandBuilder()
|
||||
.setName("series")
|
||||
.setDescription("View details on a series")
|
||||
.addSubcommand(x =>
|
||||
x
|
||||
.setName("view")
|
||||
.setDescription("View a specifiic series by id")
|
||||
.addStringOption(y =>
|
||||
y
|
||||
.setName("id")
|
||||
.setDescription("The series id")
|
||||
.setRequired(true)))
|
||||
.addSubcommand(x =>
|
||||
x
|
||||
.setName("list")
|
||||
.setDescription("List all series")) as SlashCommandBuilder;
|
||||
}
|
||||
|
||||
public override async execute(interaction: CommandInteraction) {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
switch (interaction.options.getSubcommand()) {
|
||||
case "view":
|
||||
await this.ViewSeries(interaction);
|
||||
break;
|
||||
case "list":
|
||||
await this.ListSeries(interaction);
|
||||
break;
|
||||
default:
|
||||
AppLogger.LogWarn("Commands/Series", `Subcommand doesn't exist: ${interaction.options.getSubcommand()}`);
|
||||
await interaction.reply("Subcommand doesn't exist.");
|
||||
}
|
||||
}
|
||||
|
||||
private async ViewSeries(interaction: CommandInteraction) {
|
||||
const id = interaction.options.get("id");
|
||||
|
||||
AppLogger.LogSilly("Commands/Series/View", `Parameters: id=${id?.value}`);
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
if (!id) return;
|
||||
|
||||
const series = CoreClient.Cards.find(x => x.id == id.value);
|
||||
|
||||
if (!series) {
|
||||
AppLogger.LogVerbose("Commands/Series/View", "Series not found.");
|
||||
|
||||
await interaction.followUp("Series not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
const embed = await SeriesHelper.GenerateSeriesViewPage(series.id, 0, interaction.user.id);
|
||||
|
||||
await interaction.followUp({
|
||||
embeds: [ embed!.embed ],
|
||||
components: [ embed!.row ],
|
||||
files: [ embed!.image ],
|
||||
});
|
||||
}
|
||||
|
||||
private async ListSeries(interaction: CommandInteraction) {
|
||||
const embed = SeriesHelper.GenerateSeriesListPage(0);
|
||||
|
||||
await interaction.reply({ embeds: [ embed!.embed ], components: [ embed!.row ]});
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
import { AttachmentBuilder, CacheType, CommandInteraction, DiscordAPIError, SlashCommandBuilder } from "discord.js";
|
||||
import { Command } from "../../type/command";
|
||||
import { readFileSync } from "fs";
|
||||
import Inventory from "../../database/entities/app/Inventory";
|
||||
import { v4 } from "uuid";
|
||||
import { CoreClient } from "../../client/client";
|
||||
import path from "path";
|
||||
import CardDropHelperMetadata from "../../helpers/CardDropHelperMetadata";
|
||||
|
||||
export default class Dropnumber extends Command {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.CommandBuilder = new SlashCommandBuilder()
|
||||
.setName("dropnumber")
|
||||
.setDescription("(TEST) Summon a specific card")
|
||||
.addStringOption(x =>
|
||||
x
|
||||
.setName("cardnumber")
|
||||
.setDescription("The card number to summon")
|
||||
.setRequired(true));
|
||||
}
|
||||
|
||||
public override async execute(interaction: CommandInteraction<CacheType>) {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const cardNumber = interaction.options.get("cardnumber");
|
||||
|
||||
if (!cardNumber || !cardNumber.value) {
|
||||
await interaction.reply("Card Number is required");
|
||||
return;
|
||||
}
|
||||
|
||||
const card = CoreClient.Cards
|
||||
.flatMap(x => x.cards)
|
||||
.find(x => x.id == cardNumber.value);
|
||||
|
||||
if (!card) {
|
||||
await interaction.reply("Card not found");
|
||||
return;
|
||||
}
|
||||
|
||||
const series = CoreClient.Cards
|
||||
.find(x => x.cards.includes(card))!;
|
||||
|
||||
let image: Buffer;
|
||||
const imageFileName = card.path.split("/").pop()!;
|
||||
|
||||
try {
|
||||
image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.path));
|
||||
} catch {
|
||||
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);
|
||||
|
||||
const claimId = v4();
|
||||
|
||||
const row = CardDropHelperMetadata.GenerateDropButtons({ card, series }, claimId, interaction.user.id);
|
||||
|
||||
try {
|
||||
await interaction.editReply({
|
||||
embeds: [ embed ],
|
||||
files: [ attachment ],
|
||||
components: [ row ],
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(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");
|
||||
}
|
||||
}
|
||||
|
||||
CoreClient.ClaimId = claimId;
|
||||
}
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
import { AttachmentBuilder, CacheType, CommandInteraction, DiscordAPIError, SlashCommandBuilder } from "discord.js";
|
||||
import { Command } from "../../type/command";
|
||||
import { CardRarity, CardRarityParse } from "../../constants/CardRarity";
|
||||
import { readFileSync } from "fs";
|
||||
import Inventory from "../../database/entities/app/Inventory";
|
||||
import { v4 } from "uuid";
|
||||
import { CoreClient } from "../../client/client";
|
||||
import CardDropHelperMetadata from "../../helpers/CardDropHelperMetadata";
|
||||
import path from "path";
|
||||
|
||||
export default class Droprarity extends Command {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.CommandBuilder = new SlashCommandBuilder()
|
||||
.setName("droprarity")
|
||||
.setDescription("(TEST) Summon a random card of a specific rarity")
|
||||
.addStringOption(x =>
|
||||
x
|
||||
.setName("rarity")
|
||||
.setDescription("The rarity you want to summon")
|
||||
.setRequired(true));
|
||||
}
|
||||
|
||||
public override async execute(interaction: CommandInteraction<CacheType>) {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const rarity = interaction.options.get("rarity");
|
||||
|
||||
if (!rarity || !rarity.value) {
|
||||
await interaction.reply("Rarity is required");
|
||||
return;
|
||||
}
|
||||
|
||||
const rarityType = CardRarityParse(rarity.value.toString());
|
||||
|
||||
if (rarityType == CardRarity.Unknown) {
|
||||
await interaction.reply("Invalid rarity");
|
||||
return;
|
||||
}
|
||||
|
||||
const card = await CardDropHelperMetadata.GetRandomCardByRarity(rarityType);
|
||||
|
||||
if (!card) {
|
||||
await interaction.reply("Card not found");
|
||||
return;
|
||||
}
|
||||
|
||||
let image: Buffer;
|
||||
const imageFileName = card.card.path.split("/").pop()!;
|
||||
|
||||
try {
|
||||
image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path));
|
||||
} catch {
|
||||
await interaction.reply(`Unable to fetch image for card ${card.card.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||
|
||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.card.id);
|
||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
||||
|
||||
const embed = CardDropHelperMetadata.GenerateDropEmbed(card, quantityClaimed, imageFileName);
|
||||
|
||||
const claimId = v4();
|
||||
|
||||
const row = CardDropHelperMetadata.GenerateDropButtons(card, claimId, interaction.user.id);
|
||||
|
||||
try {
|
||||
await interaction.editReply({
|
||||
embeds: [ embed ],
|
||||
files: [ attachment ],
|
||||
components: [ row ],
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(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");
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,153 +0,0 @@
|
|||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder, SlashCommandBuilder } from "discord.js";
|
||||
import { Command } from "../type/command";
|
||||
import Inventory from "../database/entities/app/Inventory";
|
||||
import { CoreClient } from "../client/client";
|
||||
import EmbedColours from "../constants/EmbedColours";
|
||||
import AppLogger from "../client/appLogger";
|
||||
|
||||
export default class Trade extends Command {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.CommandBuilder = new SlashCommandBuilder()
|
||||
.setName("trade")
|
||||
.setDescription("Initiate a trade with another user.")
|
||||
.addUserOption(x =>
|
||||
x
|
||||
.setName("user")
|
||||
.setDescription("User to trade with")
|
||||
.setRequired(true))
|
||||
.addStringOption(x =>
|
||||
x
|
||||
.setName("give")
|
||||
.setDescription("Item to give")
|
||||
.setRequired(true))
|
||||
.addStringOption(x =>
|
||||
x
|
||||
.setName("receive")
|
||||
.setDescription("Item to receive")
|
||||
.setRequired(true));
|
||||
}
|
||||
|
||||
public override async execute(interaction: CommandInteraction) {
|
||||
const user = interaction.options.get("user", true).user!;
|
||||
const give = interaction.options.get("give", true);
|
||||
const receive = interaction.options.get("receive", true);
|
||||
|
||||
AppLogger.LogSilly("Commands/Trade", `Parameters: user=${user.id}, give=${give.value}, receive=${receive.value}`);
|
||||
|
||||
if (interaction.user.id == user.id) {
|
||||
await interaction.reply("You can not create a trade with yourself.");
|
||||
return;
|
||||
}
|
||||
|
||||
const user1ItemEntity = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, give.value!.toString());
|
||||
const user2ItemEntity = await Inventory.FetchOneByCardNumberAndUserId(user.id, receive.value!.toString());
|
||||
|
||||
if (!user1ItemEntity) {
|
||||
await interaction.reply("You do not have the item you are trying to trade.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!user2ItemEntity) {
|
||||
await interaction.reply("The user you are trying to trade with does not have the item you are trying to trade for.");
|
||||
return;
|
||||
}
|
||||
|
||||
const user1Item = CoreClient.Cards
|
||||
.flatMap(x => x.cards)
|
||||
.find(x => x.id === give.value!.toString());
|
||||
|
||||
const user2Item = CoreClient.Cards
|
||||
.flatMap(x => x.cards)
|
||||
.find(x => x.id === receive.value!.toString());
|
||||
|
||||
if (!user1Item || !user2Item) {
|
||||
await interaction.reply("One or more of the items you are trying to trade does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const expiry = now.setMinutes(now.getMinutes() + 15);
|
||||
|
||||
const tradeEmbed = new EmbedBuilder()
|
||||
.setTitle("⚠️ Trade Offer ⚠️")
|
||||
.setDescription(`Trade initiated between ${interaction.user.username} and ${user.username}`)
|
||||
.setColor(EmbedColours.Grey)
|
||||
.setImage("https://media1.tenor.com/m/KkZwKl2AQ2QAAAAd/trade-offer.gif")
|
||||
.addFields([
|
||||
{
|
||||
name: `${interaction.user.username} Receives`,
|
||||
value: `${user2Item.id}: ${user2Item.name}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: `${user.username} Receives`,
|
||||
value: `${user1Item.id}: ${user1Item.name}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "Expires",
|
||||
value: new Date(expiry).toLocaleString(),
|
||||
}
|
||||
]);
|
||||
|
||||
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>()
|
||||
.addComponents([
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`trade accept ${interaction.user.id} ${user.id} ${user1Item.id} ${user2Item.id} ${expiry} ${timeoutId}`)
|
||||
.setLabel("Accept")
|
||||
.setStyle(ButtonStyle.Success),
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`trade decline ${interaction.user.id} ${user.id} ${user1Item.id} ${user2Item.id} ${expiry} ${timeoutId}`)
|
||||
.setLabel("Decline")
|
||||
.setStyle(ButtonStyle.Danger),
|
||||
]);
|
||||
|
||||
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) {
|
||||
AppLogger.LogSilly("Commands/Trade/AutoDecline", `Auto declining trade between ${user1Username} and ${user2Username}`);
|
||||
|
||||
const tradeEmbed = new EmbedBuilder()
|
||||
.setTitle("Trade Expired")
|
||||
.setDescription(`Trade initiated between ${user1Username} and ${user2Username}`)
|
||||
.setColor(EmbedColours.Error)
|
||||
.setImage("https://media1.tenor.com/m/KkZwKl2AQ2QAAAAd/trade-offer.gif")
|
||||
.addFields([
|
||||
{
|
||||
name: `${user1Username} Receives`,
|
||||
value: `${user2CardNumber}: ${user2CardName}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: `${user2Username} Receives`,
|
||||
value: `${user1CardNumber}: ${user1CardName}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "Expired",
|
||||
value: new Date().toLocaleString(),
|
||||
}
|
||||
]);
|
||||
|
||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||
.addComponents([
|
||||
new ButtonBuilder()
|
||||
.setCustomId("trade expired accept")
|
||||
.setLabel("Accept")
|
||||
.setStyle(ButtonStyle.Success)
|
||||
.setDisabled(true),
|
||||
new ButtonBuilder()
|
||||
.setCustomId("trade expired declined")
|
||||
.setLabel("Decline")
|
||||
.setStyle(ButtonStyle.Danger)
|
||||
.setDisabled(true),
|
||||
]);
|
||||
|
||||
await interaction.editReply({ embeds: [ tradeEmbed ], components: [ row ]});
|
||||
}
|
||||
}
|
|
@ -1,82 +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 CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
|
||||
import AppLogger from "../client/appLogger";
|
||||
|
||||
export default class View extends Command {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.CommandBuilder = new SlashCommandBuilder()
|
||||
.setName("view")
|
||||
.setDescription("View a specific command")
|
||||
.addStringOption(x =>
|
||||
x
|
||||
.setName("cardnumber")
|
||||
.setDescription("The card number to view")
|
||||
.setRequired(true));
|
||||
}
|
||||
|
||||
public override async execute(interaction: CommandInteraction) {
|
||||
const cardNumber = interaction.options.get("cardnumber");
|
||||
|
||||
AppLogger.LogSilly("Commands/View", `Parameters: cardNumber=${cardNumber?.value}`);
|
||||
|
||||
if (!cardNumber || !cardNumber.value) {
|
||||
await interaction.reply("Card number is required.");
|
||||
return;
|
||||
}
|
||||
|
||||
const card = CoreClient.Cards
|
||||
.flatMap(x => x.cards)
|
||||
.find(x => x.id == cardNumber.value);
|
||||
|
||||
if (!card) {
|
||||
await interaction.reply("Card not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
const series = CoreClient.Cards
|
||||
.find(x => x.cards.includes(card))!;
|
||||
|
||||
let image: Buffer;
|
||||
const imageFileName = card.path.split("/").pop()!;
|
||||
|
||||
try {
|
||||
image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.path));
|
||||
} catch {
|
||||
AppLogger.LogError("Commands/View", `Unable to fetch image for card ${card.id}.`);
|
||||
|
||||
await interaction.reply(`Unable to fetch image for card ${card.id}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
const attachment = new AttachmentBuilder(image, { name: imageFileName });
|
||||
|
||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.id);
|
||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
||||
|
||||
const embed = CardDropHelperMetadata.GenerateDropEmbed({ card, series }, quantityClaimed, imageFileName);
|
||||
|
||||
try {
|
||||
await interaction.editReply({
|
||||
embeds: [ embed ],
|
||||
files: [ attachment ],
|
||||
});
|
||||
} catch (e) {
|
||||
AppLogger.LogError("Commands/View", `Error sending view for card ${card.id}: ${e}`);
|
||||
|
||||
if (e instanceof DiscordAPIError) {
|
||||
await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. Code: ${e.code}.`);
|
||||
} else {
|
||||
await interaction.editReply("Unable to send next drop. Please try again, and report this if it keeps happening. Code: UNKNOWN.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +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;
|
||||
}
|
|
@ -1,78 +1,34 @@
|
|||
import EmbedColours from "./EmbedColours";
|
||||
|
||||
export enum CardRarity {
|
||||
Unknown,
|
||||
Bronze,
|
||||
Silver,
|
||||
Gold,
|
||||
Manga,
|
||||
Legendary,
|
||||
}
|
||||
|
||||
export function CardRarityToString(rarity: CardRarity): string {
|
||||
switch (rarity) {
|
||||
case CardRarity.Unknown:
|
||||
return "Unknown";
|
||||
case CardRarity.Bronze:
|
||||
return "Bronze";
|
||||
case CardRarity.Silver:
|
||||
return "Silver";
|
||||
case CardRarity.Gold:
|
||||
return "Gold";
|
||||
case CardRarity.Legendary:
|
||||
return "Legendary";
|
||||
case CardRarity.Manga:
|
||||
return "Manga";
|
||||
case CardRarity.Bronze:
|
||||
return "Bronze";
|
||||
case CardRarity.Silver:
|
||||
return "Silver";
|
||||
case CardRarity.Gold:
|
||||
return "Gold";
|
||||
case CardRarity.Legendary:
|
||||
return "Legendary";
|
||||
}
|
||||
}
|
||||
|
||||
export function CardRarityToColour(rarity: CardRarity): number {
|
||||
switch (rarity) {
|
||||
case CardRarity.Unknown:
|
||||
return EmbedColours.Grey;
|
||||
case CardRarity.Bronze:
|
||||
return EmbedColours.BronzeCard;
|
||||
case CardRarity.Silver:
|
||||
return EmbedColours.SilverCard;
|
||||
case CardRarity.Gold:
|
||||
return EmbedColours.GoldCard;
|
||||
case CardRarity.Legendary:
|
||||
return EmbedColours.LegendaryCard;
|
||||
case CardRarity.Manga:
|
||||
return EmbedColours.MangaCard;
|
||||
}
|
||||
}
|
||||
|
||||
export function CardRarityParse(rarity: string): CardRarity {
|
||||
switch (rarity.toLowerCase()) {
|
||||
case "bronze":
|
||||
return CardRarity.Bronze;
|
||||
case "silver":
|
||||
return CardRarity.Silver;
|
||||
case "gold":
|
||||
return CardRarity.Gold;
|
||||
case "legendary":
|
||||
return CardRarity.Legendary;
|
||||
case "manga":
|
||||
return CardRarity.Manga;
|
||||
default:
|
||||
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;
|
||||
case CardRarity.Bronze:
|
||||
return EmbedColours.BronzeCard;
|
||||
case CardRarity.Silver:
|
||||
return EmbedColours.SilverCard;
|
||||
case CardRarity.Gold:
|
||||
return EmbedColours.GoldCard;
|
||||
case CardRarity.Legendary:
|
||||
return EmbedColours.LegendaryCard;
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
export default class CardRarityChances {
|
||||
public static readonly Bronze = 62;
|
||||
public static readonly Silver = 31;
|
||||
public static readonly Gold = 4.4;
|
||||
public static readonly Manga = 2;
|
||||
// Legendary therefore = 0.6;
|
||||
}
|
|
@ -1,17 +1,7 @@
|
|||
export default class EmbedColours {
|
||||
// General
|
||||
public static readonly Ok = 0x3050ba;
|
||||
public static readonly Success = 0x50c878;
|
||||
public static readonly Error = 0xff0000;
|
||||
|
||||
// Colours
|
||||
public static readonly Grey = 0xd3d3d3;
|
||||
public static readonly Green = 0x228B22;
|
||||
|
||||
// Card Types
|
||||
public static readonly BronzeCard = 0xcd7f32;
|
||||
public static readonly SilverCard = 0xc0c0c0;
|
||||
public static readonly GoldCard = 0xffd700;
|
||||
public static readonly LegendaryCard = 0x50c878;
|
||||
public static readonly MangaCard = 0xffffff;
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
export enum Environment {
|
||||
None = 0,
|
||||
Production = 1 << 0,
|
||||
Stage = 1 << 1,
|
||||
Local = 1 << 2,
|
||||
|
||||
All = Production | Stage | Local,
|
||||
Test = Stage | Local,
|
||||
}
|
|
@ -11,13 +11,13 @@ export default class AppBaseEntity {
|
|||
}
|
||||
|
||||
@PrimaryColumn()
|
||||
Id: string;
|
||||
Id: string;
|
||||
|
||||
@Column()
|
||||
WhenCreated: Date;
|
||||
WhenCreated: Date;
|
||||
|
||||
@Column()
|
||||
WhenUpdated: Date;
|
||||
WhenUpdated: Date;
|
||||
|
||||
public async Save<T extends AppBaseEntity>(target: EntityTarget<T>, entity: DeepPartial<T>): Promise<void> {
|
||||
this.WhenUpdated = new Date();
|
||||
|
@ -27,24 +27,12 @@ export default class AppBaseEntity {
|
|||
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> {
|
||||
const repository = AppDataSource.getRepository<T>(target);
|
||||
|
||||
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[]> {
|
||||
const repository = AppDataSource.getRepository<T>(target);
|
||||
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
import { Environment } from "../constants/Environment";
|
||||
import { ButtonEvent } from "../type/buttonEvent";
|
||||
|
||||
interface ButtonEventItem {
|
||||
ButtonId: string,
|
||||
Event: ButtonEvent,
|
||||
Environment: Environment,
|
||||
}
|
||||
|
||||
export default ButtonEventItem;
|
60
src/contracts/CardBaseEntity.ts
Normal file
60
src/contracts/CardBaseEntity.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
import { Column, DeepPartial, EntityTarget, PrimaryColumn, ObjectLiteral, FindOptionsWhere } from "typeorm";
|
||||
import { v4 } from "uuid";
|
||||
import AppDataSource from "../database/dataSources/appDataSource";
|
||||
import CardDataSource from "../database/dataSources/cardDataSource";
|
||||
|
||||
export default class CardBaseEntity {
|
||||
constructor() {
|
||||
this.Id = v4();
|
||||
|
||||
this.WhenCreated = new Date();
|
||||
this.WhenUpdated = new Date();
|
||||
}
|
||||
|
||||
@PrimaryColumn()
|
||||
Id: string;
|
||||
|
||||
@Column()
|
||||
WhenCreated: Date;
|
||||
|
||||
@Column()
|
||||
WhenUpdated: Date;
|
||||
|
||||
public async Save<T extends CardBaseEntity>(target: EntityTarget<T>, entity: DeepPartial<T>): Promise<void> {
|
||||
this.WhenUpdated = new Date();
|
||||
|
||||
const repository = CardDataSource.getRepository<T>(target);
|
||||
|
||||
await repository.save(entity);
|
||||
}
|
||||
|
||||
public static async Remove<T extends CardBaseEntity>(target: EntityTarget<T>, entity: T): Promise<void> {
|
||||
const repository = CardDataSource.getRepository<T>(target);
|
||||
|
||||
await repository.remove(entity);
|
||||
}
|
||||
|
||||
public static async FetchAll<T extends CardBaseEntity>(target: EntityTarget<T>, relations?: string[]): Promise<T[]> {
|
||||
const repository = CardDataSource.getRepository<T>(target);
|
||||
|
||||
const all = await repository.find({ relations: relations || [] });
|
||||
|
||||
return all;
|
||||
}
|
||||
|
||||
public static async FetchOneById<T extends CardBaseEntity>(target: EntityTarget<T>, id: string, relations?: string[]): Promise<T | null> {
|
||||
const repository = CardDataSource.getRepository<T>(target);
|
||||
|
||||
const single = await repository.findOne({ where: ({ Id: id } as FindOptionsWhere<T>), relations: relations || {} });
|
||||
|
||||
return single;
|
||||
}
|
||||
|
||||
public static async Any<T extends ObjectLiteral>(target: EntityTarget<T>): Promise<boolean> {
|
||||
const repository = CardDataSource.getRepository<T>(target);
|
||||
|
||||
const any = await repository.find();
|
||||
|
||||
return any.length > 0;
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
|
||||
import { DMChannel, Guild, GuildBan, GuildMember, Message, NonThreadGuildBasedChannel, PartialGuildMember, PartialMessage } from "discord.js";
|
||||
|
||||
interface EventExecutors {
|
||||
ChannelCreate: ((channel: NonThreadGuildBasedChannel) => void)[],
|
||||
ChannelDelete: ((channel: DMChannel | NonThreadGuildBasedChannel) => void)[],
|
||||
ChannelUpdate: ((channel: DMChannel | NonThreadGuildBasedChannel) => void)[],
|
||||
GuildBanAdd: ((ban: GuildBan) => void)[],
|
||||
GuildBanRemove: ((ban: GuildBan) => void)[],
|
||||
GuildCreate: ((guild: Guild) => void)[],
|
||||
GuildMemberAdd: ((member: GuildMember) => void)[],
|
||||
GuildMemberRemove: ((member: GuildMember | PartialGuildMember) => void)[],
|
||||
GuildMemebrUpdate: ((oldMember: GuildMember | PartialGuildMember, newMember: GuildMember) => void)[],
|
||||
MessageCreate: ((message: Message<boolean>) => void)[],
|
||||
MessageDelete: ((message: Message<boolean> | PartialMessage) => void)[],
|
||||
MessageUpdate: ((oldMessage: Message<boolean> | PartialMessage, newMessage: Message<boolean> | PartialMessage) => void)[],
|
||||
}
|
||||
|
||||
export default EventExecutors;
|
6
src/contracts/IButtonEventItem.ts
Normal file
6
src/contracts/IButtonEventItem.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { ButtonEvent } from "../type/buttonEvent";
|
||||
|
||||
export default interface IButtonEventItem {
|
||||
ButtonId: string,
|
||||
Event: ButtonEvent,
|
||||
}
|
|
@ -1,11 +1,7 @@
|
|||
import { Environment } from "../constants/Environment";
|
||||
import { Command } from "../type/command";
|
||||
|
||||
interface ICommandItem {
|
||||
export default interface ICommandItem {
|
||||
Name: string,
|
||||
Command: Command,
|
||||
Environment: Environment,
|
||||
ServerId?: string,
|
||||
}
|
||||
|
||||
export default ICommandItem;
|
||||
}
|
7
src/contracts/IEventItem.ts
Normal file
7
src/contracts/IEventItem.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
|
||||
import { EventType } from "../constants/EventType";
|
||||
|
||||
export default interface IEventItem {
|
||||
EventType: EventType,
|
||||
ExecutionFunction: Function,
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
export interface IGDriveFolderListing {
|
||||
export default interface IGDriveFolderListing {
|
||||
id: string,
|
||||
name: string,
|
||||
}
|
||||
};
|
|
@ -1,21 +0,0 @@
|
|||
import { CardRarity } from "../constants/CardRarity";
|
||||
|
||||
export interface SeriesMetadata {
|
||||
id: number,
|
||||
name: string,
|
||||
cards: CardMetadata[],
|
||||
}
|
||||
|
||||
export interface CardMetadata {
|
||||
id: string,
|
||||
name: string,
|
||||
type: CardRarity,
|
||||
path: string,
|
||||
subseries?: string,
|
||||
colour?: string,
|
||||
}
|
||||
|
||||
export interface DropResult {
|
||||
series: SeriesMetadata,
|
||||
card: CardMetadata,
|
||||
}
|
22
src/database/dataSources/cardDataSource.ts
Normal file
22
src/database/dataSources/cardDataSource.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { DataSource } from "typeorm";
|
||||
import * as dotenv from "dotenv";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const CardDataSource = new DataSource({
|
||||
type: "sqlite",
|
||||
database: process.env.DB_CARD_FILE!,
|
||||
synchronize: true,
|
||||
logging: process.env.DB_LOGGING == "true",
|
||||
entities: [
|
||||
"dist/database/entities/card/**/*.js",
|
||||
],
|
||||
migrations: [
|
||||
"dist/database/migrations/card/**/*.js",
|
||||
],
|
||||
subscribers: [
|
||||
"dist/database/subscribers/card/**/*.js",
|
||||
],
|
||||
});
|
||||
|
||||
export default CardDataSource;
|
|
@ -1,31 +0,0 @@
|
|||
import { Column, Entity, ManyToOne } from "typeorm";
|
||||
import AppBaseEntity from "../../../contracts/AppBaseEntity";
|
||||
import Inventory from "./Inventory";
|
||||
import AppDataSource from "../../dataSources/appDataSource";
|
||||
|
||||
@Entity()
|
||||
export default class Claim extends AppBaseEntity {
|
||||
constructor(claimId: string) {
|
||||
super();
|
||||
|
||||
this.ClaimId = claimId;
|
||||
}
|
||||
|
||||
@Column()
|
||||
ClaimId: string;
|
||||
|
||||
@ManyToOne(() => Inventory, x => x.Claims)
|
||||
Inventory: Inventory;
|
||||
|
||||
public SetInventory(inventory: Inventory) {
|
||||
this.Inventory = inventory;
|
||||
}
|
||||
|
||||
public static async FetchOneByClaimId(claimId: string): Promise<Claim | null> {
|
||||
const repository = AppDataSource.getRepository(Claim);
|
||||
|
||||
const single = await repository.findOne({ where: { ClaimId: claimId }});
|
||||
|
||||
return single;
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
import { Column, Entity } from "typeorm";
|
||||
import AppBaseEntity from "../../../contracts/AppBaseEntity";
|
||||
import AppDataSource from "../../dataSources/appDataSource";
|
||||
|
||||
@Entity()
|
||||
export default class Config extends AppBaseEntity {
|
||||
constructor(key: string, value: string) {
|
||||
super();
|
||||
|
||||
this.Key = key;
|
||||
this.Value = value;
|
||||
}
|
||||
|
||||
@Column()
|
||||
Key: string;
|
||||
|
||||
@Column()
|
||||
Value: string;
|
||||
|
||||
public SetValue(value: string) {
|
||||
this.Value = value;
|
||||
}
|
||||
|
||||
public static async FetchOneByKey(key: string): Promise<Config | null> {
|
||||
const repository = AppDataSource.getRepository(Config);
|
||||
|
||||
const single = await repository.findOne({ where: { Key: key }});
|
||||
|
||||
return single;
|
||||
}
|
||||
|
||||
public static async GetValue(key: string): Promise<string | undefined> {
|
||||
const config = await Config.FetchOneByKey(key);
|
||||
|
||||
if (!config) return undefined;
|
||||
|
||||
return config.Value;
|
||||
}
|
||||
|
||||
public static async SetValue(key: string, value: string) {
|
||||
const config = await Config.FetchOneByKey(key);
|
||||
|
||||
if (!config) {
|
||||
const newConfig = new Config(key, value);
|
||||
|
||||
await newConfig.Save(Config, newConfig);
|
||||
} else {
|
||||
config.SetValue(value);
|
||||
|
||||
await config.Save(Config, config);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,44 +1,34 @@
|
|||
import { Column, Entity, OneToMany } from "typeorm";
|
||||
import { Column, Entity } from "typeorm";
|
||||
import AppBaseEntity from "../../../contracts/AppBaseEntity";
|
||||
import AppDataSource from "../../dataSources/appDataSource";
|
||||
import Claim from "./Claim";
|
||||
|
||||
@Entity()
|
||||
export default class Inventory extends AppBaseEntity {
|
||||
constructor(userId: string, cardNumber: string, quantity: number) {
|
||||
constructor(userId: string, cardNumber: string, quantity: number, claimId: string) {
|
||||
super();
|
||||
|
||||
this.UserId = userId;
|
||||
this.CardNumber = cardNumber;
|
||||
this.Quantity = quantity;
|
||||
this.ClaimId = claimId;
|
||||
}
|
||||
|
||||
@Column()
|
||||
UserId: string;
|
||||
UserId: string;
|
||||
|
||||
@Column()
|
||||
CardNumber: string;
|
||||
CardNumber: string;
|
||||
|
||||
@Column()
|
||||
Quantity: number;
|
||||
Quantity: number;
|
||||
|
||||
@OneToMany(() => Claim, x => x.Inventory)
|
||||
Claims: Claim[];
|
||||
@Column()
|
||||
ClaimId: string;
|
||||
|
||||
public SetQuantity(quantity: number) {
|
||||
this.Quantity = quantity;
|
||||
}
|
||||
|
||||
public RemoveQuantity(amount: number) {
|
||||
if (this.Quantity < amount) return;
|
||||
|
||||
this.Quantity -= amount;
|
||||
}
|
||||
|
||||
public AddClaim(claim: Claim) {
|
||||
this.Claims.push(claim);
|
||||
}
|
||||
|
||||
public static async FetchOneByCardNumberAndUserId(userId: string, cardNumber: string): Promise<Inventory | null> {
|
||||
const repository = AppDataSource.getRepository(Inventory);
|
||||
|
||||
|
@ -47,11 +37,11 @@ export default class Inventory extends AppBaseEntity {
|
|||
return single;
|
||||
}
|
||||
|
||||
public static async FetchAllByUserId(userId: string): Promise<Inventory[]> {
|
||||
public static async FetchOneByClaimId(claimId: string): Promise<Inventory | null> {
|
||||
const repository = AppDataSource.getRepository(Inventory);
|
||||
|
||||
const all = await repository.find({ where: { UserId: userId }});
|
||||
const single = await repository.findOne({ where: { ClaimId: claimId }});
|
||||
|
||||
return all;
|
||||
return single;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
import { Column, Entity } from "typeorm";
|
||||
import AppBaseEntity from "../../../contracts/AppBaseEntity";
|
||||
|
||||
@Entity()
|
||||
export default class User extends AppBaseEntity {
|
||||
constructor(userId: string, currency: number) {
|
||||
super();
|
||||
|
||||
this.Id = userId;
|
||||
this.Currency = currency;
|
||||
}
|
||||
|
||||
@Column()
|
||||
Currency: number;
|
||||
|
||||
@Column()
|
||||
LastUsedDaily?: Date;
|
||||
|
||||
public AddCurrency(amount: number) {
|
||||
this.Currency += amount;
|
||||
}
|
||||
|
||||
public RemoveCurrency(amount: number): boolean {
|
||||
if (this.Currency < amount) return false;
|
||||
|
||||
this.Currency -= amount;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public UpdateLastUsedDaily(lastUsedDaily: Date) {
|
||||
this.LastUsedDaily = lastUsedDaily;
|
||||
}
|
||||
}
|
53
src/database/entities/card/Card.ts
Normal file
53
src/database/entities/card/Card.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { Column, Entity, ManyToOne } from "typeorm";
|
||||
import CardBaseEntity from "../../../contracts/CardBaseEntity";
|
||||
import { CardRarity } from "../../../constants/CardRarity";
|
||||
import Series from "./Series";
|
||||
import CardDataSource from "../../dataSources/cardDataSource";
|
||||
|
||||
@Entity()
|
||||
export default class Card extends CardBaseEntity {
|
||||
constructor(cardNumber: string, name: string, rarity: CardRarity, path: string, fileName: string, series: Series) {
|
||||
super();
|
||||
|
||||
this.CardNumber = cardNumber;
|
||||
this.Name = name;
|
||||
this.Rarity = rarity;
|
||||
this.Path = path;
|
||||
this.FileName = fileName;
|
||||
this.Series = series;
|
||||
}
|
||||
|
||||
@Column()
|
||||
CardNumber: string;
|
||||
|
||||
@Column()
|
||||
Name: string;
|
||||
|
||||
@Column()
|
||||
Rarity: CardRarity;
|
||||
|
||||
@Column()
|
||||
Path: string;
|
||||
|
||||
@Column()
|
||||
FileName: string;
|
||||
|
||||
@ManyToOne(() => Series, x => x.Cards)
|
||||
Series: Series;
|
||||
|
||||
public static async FetchOneByCardNumber(cardNumber: string, relations?: string[]): Promise<Card | null> {
|
||||
const repository = CardDataSource.getRepository(Card);
|
||||
|
||||
const single = await repository.findOne({ where: { CardNumber: cardNumber }, relations: relations || [] });
|
||||
|
||||
return single;
|
||||
}
|
||||
|
||||
public static async FetchAllByRarity(rarity: CardRarity, relations?: string[]): Promise<Card[]> {
|
||||
const repository = CardDataSource.getRepository(Card);
|
||||
|
||||
const all = await repository.find({ where: { Rarity: rarity }, relations: relations || [] });
|
||||
|
||||
return all;
|
||||
}
|
||||
}
|
23
src/database/entities/card/Series.ts
Normal file
23
src/database/entities/card/Series.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { Column, Entity, OneToMany } from "typeorm";
|
||||
import CardBaseEntity from "../../../contracts/CardBaseEntity";
|
||||
import Card from "./Card";
|
||||
|
||||
@Entity()
|
||||
export default class Series extends CardBaseEntity {
|
||||
constructor(id: string, name: string, path: string) {
|
||||
super();
|
||||
|
||||
this.Id = id;
|
||||
this.Name = name;
|
||||
this.Path = path;
|
||||
}
|
||||
|
||||
@Column()
|
||||
Name: string;
|
||||
|
||||
@Column()
|
||||
Path: string;
|
||||
|
||||
@OneToMany(() => Card, x => x.Series)
|
||||
Cards: Card[];
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
import MigrationHelper from "../../../../helpers/MigrationHelper";
|
||||
|
||||
export class CreateClaim1694609771821 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
MigrationHelper.Up("1694609771821-CreateClaim", "0.1.5", [
|
||||
"01-CreateClaim",
|
||||
"02-MoveToClaim",
|
||||
"03-AlterInventory",
|
||||
], queryRunner);
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
}
|
||||
|
||||
}
|
|
@ -1,15 +1,15 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
import MigrationHelper from "../../../../helpers/MigrationHelper";
|
||||
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||
import MigrationHelper from "../../../../helpers/MigrationHelper"
|
||||
|
||||
export class CreateBase1693769942868 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
MigrationHelper.Up("1693769942868-CreateBase", "0.1", [
|
||||
MigrationHelper.Up('1693769942868-CreateBase', '0.1', [
|
||||
"01-table/Inventory",
|
||||
], queryRunner);
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
import MigrationHelper from "../../../../helpers/MigrationHelper";
|
||||
|
||||
export class CreateConfig1699814500650 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
MigrationHelper.Up("1699814500650-createConfig", "0.2", [
|
||||
"01-table/Config",
|
||||
], queryRunner);
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
}
|
||||
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
import MigrationHelper from "../../../../helpers/MigrationHelper";
|
||||
|
||||
export class User1713289062969 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
MigrationHelper.Up("1713289062969-user", "0.6", [
|
||||
"01-table/User",
|
||||
], queryRunner);
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
}
|
||||
|
||||
}
|
|
@ -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> {
|
||||
}
|
||||
|
||||
}
|
0
src/database/migrations/card/.gitkeep
Normal file
0
src/database/migrations/card/.gitkeep
Normal file
45
src/helpers/CardDropHelper.ts
Normal file
45
src/helpers/CardDropHelper.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import { CardRarity } from "../constants/CardRarity";
|
||||
import Card from "../database/entities/card/Card";
|
||||
import Series from "../database/entities/card/Series";
|
||||
|
||||
export default class CardDropHelper {
|
||||
public static async GetRandomCard(): Promise<Card> {
|
||||
const allSeries = await Series.FetchAll(Series, [ "Cards", "Cards.Series" ]);
|
||||
const allSeriesWithCards = allSeries.filter(x => x.Cards.length > 0);
|
||||
|
||||
const randomSeriesIndex = Math.floor(Math.random() * allSeriesWithCards.length);
|
||||
|
||||
const randomSeries = allSeriesWithCards[randomSeriesIndex];
|
||||
|
||||
const randomRarity = Math.random() * 100;
|
||||
|
||||
let cardRarity: CardRarity;
|
||||
|
||||
const bronzeChance = 62;
|
||||
const silverChance = bronzeChance + 31;
|
||||
const goldChance = silverChance + 6.4;
|
||||
|
||||
if (randomRarity < bronzeChance) cardRarity = CardRarity.Bronze;
|
||||
else if (randomRarity < silverChance) cardRarity = CardRarity.Silver;
|
||||
else if (randomRarity < goldChance) cardRarity = CardRarity.Gold;
|
||||
else cardRarity = CardRarity.Legendary;
|
||||
|
||||
const allCards = randomSeries.Cards.filter(x => x.Rarity == cardRarity && x.Path && x.FileName);
|
||||
|
||||
const randomCardIndex = Math.floor(Math.random() * allCards.length);
|
||||
|
||||
const randomCard = allCards[randomCardIndex];
|
||||
|
||||
return randomCard;
|
||||
}
|
||||
|
||||
public static async GetRandomCardByRarity(rarity: CardRarity): Promise<Card> {
|
||||
const allCards = await Card.FetchAllByRarity(rarity, [ "Series" ]);
|
||||
|
||||
const randomCardIndex = Math.floor(Math.random() * allCards.length);
|
||||
|
||||
const card = allCards[randomCardIndex];
|
||||
|
||||
return card;
|
||||
}
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
||||
import { CardRarity, CardRarityToColour, CardRarityToString } from "../constants/CardRarity";
|
||||
import CardRarityChances from "../constants/CardRarityChances";
|
||||
import { DropResult } from "../contracts/SeriesMetadata";
|
||||
import { CoreClient } from "../client/client";
|
||||
import AppLogger from "../client/appLogger";
|
||||
import CardConstants from "../constants/CardConstants";
|
||||
import StringTools from "./StringTools";
|
||||
|
||||
export default class CardDropHelperMetadata {
|
||||
public static GetRandomCard(): DropResult | undefined {
|
||||
const randomRarity = Math.random() * 100;
|
||||
|
||||
let cardRarity: CardRarity;
|
||||
|
||||
const bronzeChance = CardRarityChances.Bronze;
|
||||
const silverChance = bronzeChance + CardRarityChances.Silver;
|
||||
const goldChance = silverChance + CardRarityChances.Gold;
|
||||
const mangaChance = goldChance + CardRarityChances.Manga;
|
||||
|
||||
if (randomRarity < bronzeChance) cardRarity = CardRarity.Bronze;
|
||||
else if (randomRarity < silverChance) cardRarity = CardRarity.Silver;
|
||||
else if (randomRarity < goldChance) cardRarity = CardRarity.Gold;
|
||||
else if (randomRarity < mangaChance) cardRarity = CardRarity.Manga;
|
||||
else cardRarity = CardRarity.Legendary;
|
||||
|
||||
const randomCard = this.GetRandomCardByRarity(cardRarity);
|
||||
|
||||
AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCard", `Random card: ${randomCard?.card.id} ${randomCard?.card.name}`);
|
||||
|
||||
return randomCard;
|
||||
}
|
||||
|
||||
public static GetRandomCardByRarity(rarity: CardRarity): DropResult | undefined {
|
||||
AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCardByRarity", `Parameters: rarity=${rarity}`);
|
||||
|
||||
const allCards = CoreClient.Cards
|
||||
.flatMap(x => x.cards)
|
||||
.filter(x => x.type == rarity);
|
||||
|
||||
const randomCardIndex = Math.floor(Math.random() * allCards.length);
|
||||
|
||||
const card = allCards[randomCardIndex];
|
||||
const series = CoreClient.Cards
|
||||
.find(x => x.cards.includes(card));
|
||||
|
||||
if (!series) {
|
||||
AppLogger.LogWarn("CardDropHelperMetadata/GetRandomCardByRarity", `Series not found for card ${card.id}`);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
AppLogger.LogSilly("CardDropHelperMetadata/GetRandomCardByRarity", `Random card: ${card.id} ${card.name}`);
|
||||
|
||||
return {
|
||||
series: series,
|
||||
card: card,
|
||||
};
|
||||
}
|
||||
|
||||
public static GetCardByCardNumber(cardNumber: string): DropResult | undefined {
|
||||
AppLogger.LogSilly("CardDropHelperMetadata/GetCardByCardNumber", `Parameters: cardNumber=${cardNumber}`);
|
||||
|
||||
const card = CoreClient.Cards
|
||||
.flatMap(x => x.cards)
|
||||
.find(x => x.id == cardNumber);
|
||||
|
||||
const series = CoreClient.Cards
|
||||
.find(x => x.cards.find(y => y.id == card?.id));
|
||||
|
||||
AppLogger.LogSilly("CardDropHelperMetadata/GetCardByCardNumber", `Card: ${card?.id} ${card?.name}`);
|
||||
AppLogger.LogSilly("CardDropHelperMetadata/GetCardByCardNumber", `Series: ${series?.id} ${series?.name}`);
|
||||
|
||||
if (!card || !series) {
|
||||
AppLogger.LogVerbose("CardDropHelperMetadata/GetCardByCardNumber", `Unable to find card metadata: ${cardNumber}`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return { card, series };
|
||||
}
|
||||
|
||||
public static GenerateDropEmbed(drop: DropResult, quantityClaimed: number, imageFileName: string, claimedBy?: string, currency?: number): EmbedBuilder {
|
||||
AppLogger.LogSilly("CardDropHelperMetadata/GenerateDropEmbed", `Parameters: drop=${drop.card.id}, quantityClaimed=${quantityClaimed}, imageFileName=${imageFileName}`);
|
||||
|
||||
const description = drop.card.subseries ?? drop.series.name;
|
||||
let colour = CardRarityToColour(drop.card.type);
|
||||
|
||||
if (drop.card.colour && StringTools.IsHexCode(drop.card.colour)) {
|
||||
const hexCode = Number("0x" + drop.card.colour);
|
||||
|
||||
if (hexCode) {
|
||||
colour = hexCode;
|
||||
} else {
|
||||
AppLogger.LogWarn("CardDropHelperMetadata/GenerateDropEmbed", `Card's colour override is invalid: ${drop.card.id}, ${drop.card.colour}`);
|
||||
}
|
||||
} else if (drop.card.colour) {
|
||||
AppLogger.LogWarn("CardDropHelperMetadata/GenerateDropEmbed", `Card's colour override is invalid: ${drop.card.id}, ${drop.card.colour}`);
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(drop.card.name)
|
||||
.setDescription(description)
|
||||
.setFooter({ text: `${CardRarityToString(drop.card.type)} · ${drop.card.id}` })
|
||||
.setColor(colour)
|
||||
.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> {
|
||||
AppLogger.LogSilly("CardDropHelperMetadata/GenerateDropButtons", `Parameters: drop=${drop.card.id}, claimId=${claimId}, userId=${userId}`);
|
||||
|
||||
return new ActionRowBuilder<ButtonBuilder>()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`claim ${drop.card.id} ${claimId} ${userId}`)
|
||||
.setLabel(`Claim (${CardConstants.ClaimCost} 🪙)`)
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setDisabled(disabled),
|
||||
new ButtonBuilder()
|
||||
.setCustomId("reroll")
|
||||
.setLabel("Reroll")
|
||||
.setStyle(ButtonStyle.Secondary));
|
||||
}
|
||||
}
|
83
src/helpers/GoogleDriveHelper.ts
Normal file
83
src/helpers/GoogleDriveHelper.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
import { Auth, drive_v3, google } from "googleapis";
|
||||
import IGDriveFolderListing from "../contracts/IGDriveFolderListing";
|
||||
import path, { resolve } from "path";
|
||||
import os from 'os';
|
||||
import uuid, { v4 } from 'uuid';
|
||||
import { createWriteStream } from "fs";
|
||||
|
||||
export default class GoogleDriveHelper {
|
||||
private _auth: Auth.GoogleAuth;
|
||||
private _drive: drive_v3.Drive;
|
||||
|
||||
constructor() {
|
||||
this._auth = new google.auth.GoogleAuth({
|
||||
keyFile: "gdrive-credentials.json",
|
||||
scopes: [
|
||||
"https://www.googleapis.com/auth/drive.readonly",
|
||||
"https://www.googleapis.com/auth/drive.metadata.readonly",
|
||||
],
|
||||
});
|
||||
|
||||
this._drive = google.drive( { version: "v3", auth: this._auth });
|
||||
}
|
||||
|
||||
public async listFolder(folderId: string, pageSize: number): Promise<IGDriveFolderListing[]> {
|
||||
const params = {
|
||||
pageSize: pageSize,
|
||||
fields: "nextPageToken, files(id, name)",
|
||||
q: `'${folderId}' in parents and trashed=false`
|
||||
}
|
||||
|
||||
const res = await this._drive.files.list(params);
|
||||
|
||||
return res.data.files as IGDriveFolderListing[];
|
||||
}
|
||||
|
||||
public downloadFile(fileId: string) {
|
||||
const res = this._drive.files.get({
|
||||
fileId: fileId,
|
||||
alt: 'media',
|
||||
}, {
|
||||
responseType: 'stream',
|
||||
})
|
||||
.then(res => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const filePath = path.join(process.cwd(), 'temp', v4());
|
||||
const dest = createWriteStream(filePath);
|
||||
let progress = 0;
|
||||
|
||||
res.data
|
||||
.on('end', () => {
|
||||
resolve(filePath);
|
||||
})
|
||||
.on('error', err => {
|
||||
reject(err);
|
||||
})
|
||||
.on('data', d => {
|
||||
progress += d.length;
|
||||
})
|
||||
.pipe(dest);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
public async exportFile(fileId: string, mimeType: string) {
|
||||
const destPath = path.join(process.cwd(), 'temp', v4());
|
||||
const dest = createWriteStream(destPath);
|
||||
|
||||
const res = await this._drive.files.export({
|
||||
fileId: fileId,
|
||||
mimeType: mimeType
|
||||
}, {
|
||||
responseType: 'stream',
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
res.data
|
||||
.on('error', reject)
|
||||
.pipe(dest)
|
||||
.on('error', reject)
|
||||
.on('finish', resolve);
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,62 +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 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,120 +0,0 @@
|
|||
import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
||||
import Inventory from "../database/entities/app/Inventory";
|
||||
import { CoreClient } from "../client/client";
|
||||
import EmbedColours from "../constants/EmbedColours";
|
||||
import { CardRarity, CardRarityToString } from "../constants/CardRarity";
|
||||
import cloneDeep from "clone-deep";
|
||||
import AppLogger from "../client/appLogger";
|
||||
import ImageHelper from "./ImageHelper";
|
||||
|
||||
interface InventoryPage {
|
||||
id: number,
|
||||
name: string,
|
||||
cards: InventoryPageCards[],
|
||||
seriesSubpage: number,
|
||||
}
|
||||
|
||||
interface InventoryPageCards {
|
||||
id: string,
|
||||
name: string,
|
||||
type: CardRarity,
|
||||
quantity: number,
|
||||
path: string,
|
||||
}
|
||||
|
||||
interface ReturnedInventoryPage {
|
||||
embed: EmbedBuilder,
|
||||
row: ActionRowBuilder<ButtonBuilder>,
|
||||
image: AttachmentBuilder,
|
||||
}
|
||||
|
||||
|
||||
export default class InventoryHelper {
|
||||
public static async GenerateInventoryPage(username: string, userid: string, page: number): Promise<ReturnedInventoryPage | undefined> {
|
||||
AppLogger.LogSilly("Helpers/InventoryHelper", `Parameters: username=${username}, userid=${userid}, page=${page}`);
|
||||
|
||||
const cardsPerPage = 9;
|
||||
|
||||
const inventory = await Inventory.FetchAllByUserId(userid);
|
||||
|
||||
if (!inventory || inventory.length == 0) return undefined;
|
||||
|
||||
const clientCards = cloneDeep(CoreClient.Cards);
|
||||
|
||||
const allSeriesClaimed = clientCards
|
||||
.sort((a, b) => a.id - b.id)
|
||||
.filter(x => {
|
||||
x.cards = x.cards
|
||||
.sort((a, b) => b.type - a.type)
|
||||
.filter(y => inventory.find(z => z.CardNumber == y.id))
|
||||
.filter(y => inventory.find(z => z.CardNumber == y.id)!.Quantity > 0);
|
||||
|
||||
return x;
|
||||
});
|
||||
|
||||
const pages: InventoryPage[] = [];
|
||||
|
||||
for (const series of allSeriesClaimed) {
|
||||
const seriesCards = series.cards;
|
||||
|
||||
for (let i = 0; i < seriesCards.length; i+= cardsPerPage) {
|
||||
const cards = series.cards.slice(i, i + cardsPerPage);
|
||||
const pageCards: InventoryPageCards[] = [];
|
||||
|
||||
for (const card of cards) {
|
||||
const item = inventory.find(x => x.CardNumber == card.id);
|
||||
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pageCards.push({
|
||||
id: card.id,
|
||||
name: card.name,
|
||||
type: card.type,
|
||||
quantity: item.Quantity,
|
||||
path: card.path,
|
||||
});
|
||||
}
|
||||
|
||||
pages.push({
|
||||
id: series.id,
|
||||
name: series.name,
|
||||
cards: pageCards,
|
||||
seriesSubpage: i / cardsPerPage,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const currentPage = pages[page];
|
||||
|
||||
if (!currentPage) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.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")}`)
|
||||
.setFooter({ text: `Page ${page + 1} of ${pages.length}` })
|
||||
.setColor(EmbedColours.Ok)
|
||||
.setImage("attachment://page.png");
|
||||
|
||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`inventory ${userid} ${page - 1}`)
|
||||
.setLabel("Previous")
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setDisabled(page == 0),
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`inventory ${userid} ${page + 1}`)
|
||||
.setLabel("Next")
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setDisabled(page + 1 == pages.length));
|
||||
|
||||
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 };
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ import { QueryRunner } from "typeorm";
|
|||
|
||||
export default class MigrationHelper {
|
||||
public static Up(migrationName: string, version: string, queryFiles: string[], queryRunner: QueryRunner) {
|
||||
for (const path of queryFiles) {
|
||||
for (let path of queryFiles) {
|
||||
const query = readFileSync(`${process.cwd()}/database/${version}/${migrationName}/Up/${path}.sql`).toString();
|
||||
|
||||
queryRunner.query(query);
|
||||
|
@ -11,7 +11,7 @@ export default class MigrationHelper {
|
|||
}
|
||||
|
||||
public static Down(migrationName: string, version: string, queryFiles: string[], queryRunner: QueryRunner) {
|
||||
for (const path of queryFiles) {
|
||||
for (let path of queryFiles) {
|
||||
const query = readFileSync(`${process.cwd()}/database/${version}/${migrationName}/Down/${path}.sql`).toString();
|
||||
|
||||
queryRunner.query(query);
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
||||
import AppLogger from "../client/appLogger";
|
||||
import cloneDeep from "clone-deep";
|
||||
import { CoreClient } from "../client/client";
|
||||
import EmbedColours from "../constants/EmbedColours";
|
||||
import { CardRarityToString } from "../constants/CardRarity";
|
||||
import ImageHelper from "./ImageHelper";
|
||||
|
||||
export default class SeriesHelper {
|
||||
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}`);
|
||||
|
||||
const itemsPerPage = 9;
|
||||
|
||||
const series = cloneDeep(CoreClient.Cards)
|
||||
.find(x => x.id == seriesId);
|
||||
|
||||
if (!series) {
|
||||
AppLogger.LogVerbose("Helpers/SeriesHelper", `Unable to find series: ${seriesId}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const totalPages = Math.ceil(series.cards.length / itemsPerPage);
|
||||
const totalCards = series.cards.length;
|
||||
|
||||
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`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const cardsOnPage = series.cards.splice(page * itemsPerPage, itemsPerPage);
|
||||
|
||||
const description = cardsOnPage
|
||||
.map(x => `[${x.id}] ${x.name} (${CardRarityToString(x.type)})`)
|
||||
.join("\n");
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(series.name)
|
||||
.setColor(EmbedColours.Ok)
|
||||
.setDescription(description)
|
||||
.setFooter({ text: `${series.id} · ${totalCards} cards · Page ${page + 1} of ${totalPages}` })
|
||||
.setImage("attachment://page.png");
|
||||
|
||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`series view ${seriesId} ${page - 1}`)
|
||||
.setLabel("Previous")
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setDisabled(page == 0),
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`series view ${seriesId} ${page + 1}`)
|
||||
.setLabel("Next")
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setDisabled(page + 1 == totalPages));
|
||||
|
||||
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 {
|
||||
AppLogger.LogSilly("Helpers/InventoryHelper", `Parameters: page=${page}`);
|
||||
|
||||
const itemsPerPage = 15;
|
||||
|
||||
const series = cloneDeep(CoreClient.Cards)
|
||||
.sort((a, b) => a.id - b.id);
|
||||
|
||||
const totalPages = Math.ceil(series.length / itemsPerPage);
|
||||
|
||||
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`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const seriesOnPage = series.splice(page * itemsPerPage, itemsPerPage);
|
||||
|
||||
const description = seriesOnPage
|
||||
.map(x => `[${x.id}] ${x.name} (${x.cards.length} cards)`)
|
||||
.join("\n");
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("Series")
|
||||
.setColor(EmbedColours.Ok)
|
||||
.setDescription(description)
|
||||
.setFooter({ text: `${CoreClient.Cards.length} series · Page ${page + 1} of ${totalPages}` });
|
||||
|
||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`series list ${page - 1}`)
|
||||
.setLabel("Previous")
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setDisabled(page == 0),
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`series list ${page + 1}`)
|
||||
.setLabel("Next")
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setDisabled(page + 1 == totalPages));
|
||||
|
||||
return { embed, row };
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
export default class StringTools {
|
||||
public static Capitalise(str: string): string {
|
||||
const words = str.split(" ");
|
||||
const result: string[] = [];
|
||||
let result: string[] = [];
|
||||
|
||||
words.forEach(word => {
|
||||
const firstLetter = word.substring(0, 1).toUpperCase();
|
||||
|
@ -26,31 +26,17 @@ export default class StringTools {
|
|||
public static RandomString(length: number) {
|
||||
let result = "";
|
||||
|
||||
const characters = "abcdefghkmnpqrstuvwxyz23456789";
|
||||
const characters = 'abcdefghkmnpqrstuvwxyz23456789';
|
||||
const charactersLength = characters.length;
|
||||
|
||||
for ( let i = 0; i < length; i++ ) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
for ( var i = 0; i < length; i++ ) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static ReplaceAll(str: string, find: string, replace: string) {
|
||||
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;
|
||||
return str.replace(new RegExp(find, 'g'), replace);
|
||||
}
|
||||
}
|
|
@ -4,23 +4,23 @@ export default class TimeLengthInput {
|
|||
public readonly value: string;
|
||||
|
||||
constructor(input: string) {
|
||||
this.value = StringTools.ReplaceAll(input, ",", "");
|
||||
this.value = StringTools.ReplaceAll(input, ',', '');
|
||||
}
|
||||
|
||||
public GetDays(): number {
|
||||
return this.GetValue("d");
|
||||
return this.GetValue('d');
|
||||
}
|
||||
|
||||
public GetHours(): number {
|
||||
return this.GetValue("h");
|
||||
return this.GetValue('h');
|
||||
}
|
||||
|
||||
public GetMinutes(): number {
|
||||
return this.GetValue("m");
|
||||
return this.GetValue('m');
|
||||
}
|
||||
|
||||
public GetSeconds(): number {
|
||||
return this.GetValue("s");
|
||||
return this.GetValue('s');
|
||||
}
|
||||
|
||||
public GetMilliseconds(): number {
|
||||
|
@ -106,7 +106,7 @@ export default class TimeLengthInput {
|
|||
}
|
||||
|
||||
private GetValue(designation: string): number {
|
||||
const valueSplit = this.value.split(" ");
|
||||
const valueSplit = this.value.split(' ');
|
||||
|
||||
const desString = valueSplit.find(x => x.charAt(x.length - 1) == designation);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
import { Request, Response } from "express";
|
||||
import CardMetadataFunction from "../Functions/CardMetadataFunction";
|
||||
import AppLogger from "../client/appLogger";
|
||||
|
||||
export default async function ReloadDB(req: Request, res: Response) {
|
||||
AppLogger.LogInfo("Hooks/ReloadDB", "Reloading Card DB...");
|
||||
|
||||
await CardMetadataFunction.Execute();
|
||||
|
||||
res.sendStatus(200);
|
||||
}
|
|
@ -1,63 +1,23 @@
|
|||
import { CoreClient } from "./client/client";
|
||||
import { Environment } from "./constants/Environment";
|
||||
|
||||
// Global Command Imports
|
||||
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 Gdrivesync from "./commands/gdrivesync";
|
||||
import Give from "./commands/give";
|
||||
import Inventory from "./commands/inventory";
|
||||
import Resync from "./commands/resync";
|
||||
import Sacrifice from "./commands/sacrifice";
|
||||
import Series from "./commands/series";
|
||||
import Stats from "./commands/stats";
|
||||
import Trade from "./commands/trade";
|
||||
import View from "./commands/view";
|
||||
|
||||
// Test Command Imports
|
||||
import Dropnumber from "./commands/stage/dropnumber";
|
||||
import Droprarity from "./commands/stage/droprarity";
|
||||
|
||||
// Button Event Imports
|
||||
import Claim from "./buttonEvents/Claim";
|
||||
import InventoryButtonEvent from "./buttonEvents/Inventory";
|
||||
import Reroll from "./buttonEvents/Reroll";
|
||||
import SacrificeButtonEvent from "./buttonEvents/Sacrifice";
|
||||
import SeriesEvent from "./buttonEvents/Series";
|
||||
import TradeButtonEvent from "./buttonEvents/Trade";
|
||||
|
||||
export default class Registry {
|
||||
public static RegisterCommands() {
|
||||
// Global Commands
|
||||
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("gdrivesync", new Gdrivesync());
|
||||
CoreClient.RegisterCommand("give", new Give());
|
||||
CoreClient.RegisterCommand("inventory", new Inventory());
|
||||
CoreClient.RegisterCommand("resync", new Resync());
|
||||
CoreClient.RegisterCommand("sacrifice", new Sacrifice());
|
||||
CoreClient.RegisterCommand("series", new Series());
|
||||
CoreClient.RegisterCommand("stats", new Stats());
|
||||
CoreClient.RegisterCommand("trade", new Trade());
|
||||
CoreClient.RegisterCommand("view", new View());
|
||||
CoreClient.RegisterCommand('about', new About());
|
||||
CoreClient.RegisterCommand('drop', new Drop());
|
||||
}
|
||||
|
||||
public static RegisterEvents() {
|
||||
|
||||
// Test Commands
|
||||
CoreClient.RegisterCommand("dropnumber", new Dropnumber(), Environment.Test);
|
||||
CoreClient.RegisterCommand("droprarity", new Droprarity(), Environment.Test);
|
||||
}
|
||||
|
||||
public static RegisterButtonEvents() {
|
||||
CoreClient.RegisterButtonEvent("claim", new Claim());
|
||||
CoreClient.RegisterButtonEvent("inventory", new InventoryButtonEvent());
|
||||
CoreClient.RegisterButtonEvent("reroll", new Reroll());
|
||||
CoreClient.RegisterButtonEvent("sacrifice", new SacrificeButtonEvent());
|
||||
CoreClient.RegisterButtonEvent("series", new SeriesEvent());
|
||||
CoreClient.RegisterButtonEvent("trade", new TradeButtonEvent());
|
||||
CoreClient.RegisterButtonEvent('claim', new Claim());
|
||||
CoreClient.RegisterButtonEvent('reroll', new Reroll());
|
||||
}
|
||||
}
|
|
@ -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 * 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`);
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
import { ButtonInteraction } from "discord.js";
|
||||
|
||||
export abstract class ButtonEvent {
|
||||
abstract execute(interaction: ButtonInteraction): Promise<void>;
|
||||
export class ButtonEvent {
|
||||
public execute(interaction: ButtonInteraction) {
|
||||
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue