Compare commits
8 commits
cb548898ce
...
92ef0041ff
Author | SHA1 | Date | |
---|---|---|---|
92ef0041ff | |||
499ad6faa9 | |||
f8af969104 | |||
8b3fb062f0 | |||
58d1541e47 | |||
51d97bacd5 | |||
c2c2998fe8 | |||
c706737369 |
56 changed files with 7426 additions and 0 deletions
26
.dev.env
Normal file
26
.dev.env
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# 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.0 DEV
|
||||||
|
BOT_AUTHOR=Vylpes
|
||||||
|
BOT_OWNERID=147392775707426816
|
||||||
|
BOT_CLIENTID=682942374040961060
|
||||||
|
|
||||||
|
ABOUT_FUNDING=
|
||||||
|
ABOUT_REPO=
|
||||||
|
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
DB_PORT=3301
|
||||||
|
DB_NAME=carddrop
|
||||||
|
DB_AUTH_USER=dev
|
||||||
|
DB_AUTH_PASS=dev
|
||||||
|
DB_SYNC=true
|
||||||
|
DB_LOGGING=true
|
||||||
|
|
||||||
|
DB_CARD_FILE=:memory:
|
71
.drone.yml
Normal file
71
.drone.yml
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
kind: pipeline
|
||||||
|
name: deployment
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: deploy
|
||||||
|
image: appleboy/drone-ssh
|
||||||
|
settings:
|
||||||
|
host: 192.168.68.121
|
||||||
|
username: vylpes
|
||||||
|
password:
|
||||||
|
from_secret: ssh_password
|
||||||
|
port: 22
|
||||||
|
script:
|
||||||
|
- sh /home/vylpes/scripts/card-drop/deploy_prod.sh
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
event:
|
||||||
|
- tag
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
kind: pipeline
|
||||||
|
name: staging
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: stage
|
||||||
|
image: appleboy/drone-ssh
|
||||||
|
settings:
|
||||||
|
host: 192.168.68.121
|
||||||
|
username: vylpes
|
||||||
|
password:
|
||||||
|
from_secret: ssh_password
|
||||||
|
port: 22
|
||||||
|
script:
|
||||||
|
- sh /home/vylpes/scripts/card-drop/deploy_stage.sh
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
branch:
|
||||||
|
- develop
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
kind: pipeline
|
||||||
|
name: integration
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
image: node
|
||||||
|
commands:
|
||||||
|
- yarn install --frozen-lockfile
|
||||||
|
- yarn build
|
||||||
|
|
||||||
|
- name: test
|
||||||
|
image: node
|
||||||
|
commands:
|
||||||
|
- yarn install --frozen-lockfile
|
||||||
|
- yarn test
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
branch:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
- hotfix/*
|
||||||
|
- feature/*
|
||||||
|
- renovate/*
|
||||||
|
event:
|
||||||
|
- push
|
18
.gitea/ISSUE_TEMPLATE.md
Normal file
18
.gitea/ISSUE_TEMPLATE.md
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
Epic: \
|
||||||
|
Story Points:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*No description*
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
*No acceptance criteria*
|
||||||
|
|
||||||
|
## Subtasks
|
||||||
|
|
||||||
|
*No subtasks*
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
*No notes*
|
29
.gitea/PULL_REQUEST_TEMPLATE.md
Normal file
29
.gitea/PULL_REQUEST_TEMPLATE.md
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# Description
|
||||||
|
|
||||||
|
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
|
||||||
|
|
||||||
|
Fixes # (issue)
|
||||||
|
|
||||||
|
## Type of change
|
||||||
|
|
||||||
|
Please delete options that are not relevant.
|
||||||
|
|
||||||
|
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||||
|
- [ ] New feature (non-breaking change which adds functionality)
|
||||||
|
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||||
|
- [ ] This change requires a documentation update
|
||||||
|
|
||||||
|
# How Has This Been Tested?
|
||||||
|
|
||||||
|
Please describe the tests that you ran to verify the changes. Provide instructions so we can reproduce. Please also list any relevant details to your test configuration.
|
||||||
|
|
||||||
|
# Checklist
|
||||||
|
|
||||||
|
- [ ] My code follows the style guidelines of this project
|
||||||
|
- [ ] I have performed a self-review of my own code
|
||||||
|
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||||
|
- [ ] I have made corresponding changes to the documentation
|
||||||
|
- [ ] My changes generate no new warnings
|
||||||
|
- [ ] I have added tests that provde my fix is effective or that my feature works
|
||||||
|
- [ ] New and existing unit tests pass locally with my changes
|
||||||
|
- [ ] Any dependent changes have been merged and published in downstream modules
|
110
.gitignore
vendored
Normal file
110
.gitignore
vendored
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
config.json
|
||||||
|
.DS_Store
|
||||||
|
ormconfig.json
|
||||||
|
gdrive-credentials.json
|
||||||
|
cards/
|
26
.prod.env
Normal file
26
.prod.env
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# 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.0
|
||||||
|
BOT_AUTHOR=Vylpes
|
||||||
|
BOT_OWNERID=147392775707426816
|
||||||
|
BOT_CLIENTID=1093810443589529631
|
||||||
|
|
||||||
|
ABOUT_FUNDING=
|
||||||
|
ABOUT_REPO=
|
||||||
|
|
||||||
|
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:
|
26
.stage.env
Normal file
26
.stage.env
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# 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.0 BETA
|
||||||
|
BOT_AUTHOR=Vylpes
|
||||||
|
BOT_OWNERID=147392775707426816
|
||||||
|
BOT_CLIENTID=1147976642942214235
|
||||||
|
|
||||||
|
ABOUT_FUNDING=
|
||||||
|
ABOUT_REPO=
|
||||||
|
|
||||||
|
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:
|
|
@ -0,0 +1,10 @@
|
||||||
|
CREATE TABLE `inventory` (
|
||||||
|
`Id` varchar(255) NOT NULL,
|
||||||
|
`WhenCreated` datetime NOT NULL,
|
||||||
|
`WhenUpdated` datetime NOT NULL,
|
||||||
|
`UserId` varchar(255) NOT NULL,
|
||||||
|
`CardNumber` varchar(255) NOT NULL,
|
||||||
|
`Quantity` int NOT NULL,
|
||||||
|
`ClaimId` varchar(255) NOT NULL,
|
||||||
|
PRIMARY KEY (`Id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
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
|
31
docker-compose.yml
Normal file
31
docker-compose.yml
Normal file
|
@ -0,0 +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=carddrop
|
||||||
|
- MYSQL_USER=dev
|
||||||
|
- MYSQL_PASSWORD=dev
|
||||||
|
- MYSQL_ROOT_PASSWORD=root
|
||||||
|
- MYSQL_ROOT_HOST=0.0.0.0
|
||||||
|
ports:
|
||||||
|
- "3301:3306"
|
||||||
|
volumes:
|
||||||
|
- dev_database_data:/var/lib/mysql
|
||||||
|
|
||||||
|
phpmyadmin:
|
||||||
|
image: phpmyadmin
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "3302:80"
|
||||||
|
environment:
|
||||||
|
- PMA_ARBITRARY=1
|
5
jest.config.json
Normal file
5
jest.config.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"preset": "ts-jest",
|
||||||
|
"testEnvironment": "node",
|
||||||
|
"setupFiles": ["./jest.setup.js"]
|
||||||
|
}
|
3
jest.setup.js
Normal file
3
jest.setup.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
jest.setTimeout(1 * 1000); // 1 second
|
||||||
|
jest.resetModules();
|
||||||
|
jest.resetAllMocks();
|
48
package.json
Normal file
48
package.json
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
"name": "card-drop",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"main": "./dist/bot.js",
|
||||||
|
"typings": "./dist",
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rm -rf node_modules/ dist/",
|
||||||
|
"build": "tsc",
|
||||||
|
"start": "node ./dist/bot.js",
|
||||||
|
"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://gitea.vylpes.xyz/External/card-drop.git",
|
||||||
|
"author": "Ethan Lane <ethan@vylpes.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"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": "^1.1.0",
|
||||||
|
"@types/jest": "^29.0.0",
|
||||||
|
"@types/uuid": "^9.0.0",
|
||||||
|
"discord.js": "^14.3.0",
|
||||||
|
"dotenv": "^16.0.0",
|
||||||
|
"googleapis": "^126.0.0",
|
||||||
|
"jest": "^29.0.0",
|
||||||
|
"jest-mock-extended": "^3.0.0",
|
||||||
|
"minimatch": "9.0.2",
|
||||||
|
"mysql": "^2.18.1",
|
||||||
|
"sqlite3": "^5.1.6",
|
||||||
|
"ts-jest": "^29.0.0",
|
||||||
|
"typeorm": "0.3.17"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"**/semver": "^7.5.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.0.0",
|
||||||
|
"np": "^8.0.4",
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
4
renovate.json
Normal file
4
renovate.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"baseBranches": ["develop"]
|
||||||
|
}
|
23
scripts/deploy_prod.sh
Normal file
23
scripts/deploy_prod.sh
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
export PATH="$HOME/.yarn/bin:$PATH"
|
||||||
|
export PATH="$HOME/.nodeuse/bin:$PATH"
|
||||||
|
|
||||||
|
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
|
23
scripts/deploy_stage.sh
Normal file
23
scripts/deploy_stage.sh
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
export PATH="$HOME/.yarn/bin:$PATH"
|
||||||
|
export PATH="$HOME/.nodeuse/bin:$PATH"
|
||||||
|
|
||||||
|
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
|
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)), 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)), 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)), 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)), series);
|
||||||
|
|
||||||
|
cardsToSave.push(card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await cardRepository.save(cardsToSave);
|
||||||
|
|
||||||
|
console.log(`Loaded ${cardsToSave.length} cards to database`);
|
||||||
|
}
|
||||||
|
}
|
37
src/bot.ts
Normal file
37
src/bot.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import * as dotenv from "dotenv";
|
||||||
|
import { CoreClient } from "./client/client";
|
||||||
|
import { IntentsBitField } from "discord.js";
|
||||||
|
import Registry from "./registry";
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const requiredConfigs: string[] = [
|
||||||
|
"BOT_TOKEN",
|
||||||
|
"BOT_VER",
|
||||||
|
"BOT_AUTHOR",
|
||||||
|
"BOT_OWNERID",
|
||||||
|
"BOT_CLIENTID",
|
||||||
|
"DB_HOST",
|
||||||
|
"DB_PORT",
|
||||||
|
"DB_AUTH_USER",
|
||||||
|
"DB_AUTH_PASS",
|
||||||
|
"DB_SYNC",
|
||||||
|
"DB_LOGGING",
|
||||||
|
]
|
||||||
|
|
||||||
|
requiredConfigs.forEach(config => {
|
||||||
|
if (!process.env[config]) {
|
||||||
|
throw `${config} is required in .env`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const client = new CoreClient([
|
||||||
|
IntentsBitField.Flags.Guilds,
|
||||||
|
IntentsBitField.Flags.GuildMembers,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Registry.RegisterCommands();
|
||||||
|
Registry.RegisterEvents();
|
||||||
|
Registry.RegisterButtonEvents();
|
||||||
|
|
||||||
|
client.start();
|
38
src/buttonEvents/Claim.ts
Normal file
38
src/buttonEvents/Claim.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { ButtonInteraction } from "discord.js";
|
||||||
|
import { ButtonEvent } from "../type/buttonEvent";
|
||||||
|
import Inventory from "../database/entities/app/Inventory";
|
||||||
|
import { CoreClient } from "../client/client";
|
||||||
|
|
||||||
|
export default class Claim extends ButtonEvent {
|
||||||
|
public override async execute(interaction: ButtonInteraction) {
|
||||||
|
if (!interaction.guild || !interaction.guildId) return;
|
||||||
|
|
||||||
|
const cardNumber = interaction.customId.split(' ')[1];
|
||||||
|
const claimId = interaction.customId.split(' ')[2];
|
||||||
|
const userId = interaction.user.id;
|
||||||
|
|
||||||
|
const claimed = await Inventory.FetchOneByClaimId(claimId);
|
||||||
|
|
||||||
|
if (claimed) {
|
||||||
|
await interaction.reply('This card has already been claimed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (claimId != CoreClient.ClaimId) {
|
||||||
|
await interaction.reply('This card has expired');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let inventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber);
|
||||||
|
|
||||||
|
if (!inventory) {
|
||||||
|
inventory = new Inventory(userId, cardNumber, 1, claimId);
|
||||||
|
} else {
|
||||||
|
inventory.SetQuantity(inventory.Quantity + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
await inventory.Save(Inventory, inventory);
|
||||||
|
|
||||||
|
await interaction.reply('Card claimed');
|
||||||
|
}
|
||||||
|
}
|
48
src/buttonEvents/Reroll.ts
Normal file
48
src/buttonEvents/Reroll.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, CacheType, 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";
|
||||||
|
|
||||||
|
export default class Reroll extends ButtonEvent {
|
||||||
|
public override async execute(interaction: ButtonInteraction) {
|
||||||
|
if (!interaction.guild || !interaction.guildId) return;
|
||||||
|
|
||||||
|
const randomCard = await CardDropHelper.GetRandomCard();
|
||||||
|
|
||||||
|
const image = readFileSync(randomCard.Path);
|
||||||
|
|
||||||
|
const attachment = new AttachmentBuilder(image, { name: `${randomCard.Id}.png` });
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle(randomCard.Name)
|
||||||
|
.setDescription(randomCard.Series.Name)
|
||||||
|
.setFooter({ text: CardRarityToString(randomCard.Rarity) })
|
||||||
|
.setColor(CardRarityToColour(randomCard.Rarity))
|
||||||
|
.setImage(`attachment://${randomCard.Id}.png`);
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [ embed ],
|
||||||
|
files: [ attachment ],
|
||||||
|
components: [ row ],
|
||||||
|
});
|
||||||
|
|
||||||
|
CoreClient.ClaimId = claimId;
|
||||||
|
}
|
||||||
|
}
|
105
src/client/client.ts
Normal file
105
src/client/client.ts
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import { Client } from "discord.js";
|
||||||
|
import * as dotenv from "dotenv";
|
||||||
|
import { EventType } from "../constants/EventType";
|
||||||
|
import ICommandItem from "../contracts/ICommandItem";
|
||||||
|
import IEventItem from "../contracts/IEventItem";
|
||||||
|
import { Command } from "../type/command";
|
||||||
|
|
||||||
|
import { Events } from "./events";
|
||||||
|
import { Util } from "./util";
|
||||||
|
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";
|
||||||
|
|
||||||
|
export class CoreClient extends Client {
|
||||||
|
private static _commandItems: ICommandItem[];
|
||||||
|
private static _eventItems: IEventItem[];
|
||||||
|
private static _buttonEvents: IButtonEventItem[];
|
||||||
|
|
||||||
|
private _events: Events;
|
||||||
|
private _util: Util;
|
||||||
|
private _cardSetupFunc: CardSetupFunction;
|
||||||
|
|
||||||
|
public static ClaimId: string;
|
||||||
|
|
||||||
|
public static get commandItems(): ICommandItem[] {
|
||||||
|
return this._commandItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get eventItems(): IEventItem[] {
|
||||||
|
return this._eventItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get buttonEvents(): IButtonEventItem[] {
|
||||||
|
return this._buttonEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(intents: number[]) {
|
||||||
|
super({ intents: intents });
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
CoreClient._commandItems = [];
|
||||||
|
CoreClient._eventItems = [];
|
||||||
|
CoreClient._buttonEvents = [];
|
||||||
|
|
||||||
|
this._events = new Events();
|
||||||
|
this._util = new Util();
|
||||||
|
this._cardSetupFunc = new CardSetupFunction();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async start() {
|
||||||
|
if (!process.env.BOT_TOKEN) {
|
||||||
|
console.error("BOT_TOKEN is not defined in .env");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await AppDataSource.initialize()
|
||||||
|
.then(() => console.log("App Data Source Initialised"))
|
||||||
|
.catch(err => console.error("Error initialising App Data Source", 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 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, serverId?: string) {
|
||||||
|
const item: ICommandItem = {
|
||||||
|
Name: name,
|
||||||
|
Command: command,
|
||||||
|
ServerId: serverId,
|
||||||
|
};
|
||||||
|
|
||||||
|
CoreClient._commandItems.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RegisterEvent(eventType: EventType, func: Function) {
|
||||||
|
const item: IEventItem = {
|
||||||
|
EventType: eventType,
|
||||||
|
ExecutionFunction: func,
|
||||||
|
};
|
||||||
|
|
||||||
|
CoreClient._eventItems.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RegisterButtonEvent(buttonId: string, event: ButtonEvent) {
|
||||||
|
const item: IButtonEventItem = {
|
||||||
|
ButtonId: buttonId,
|
||||||
|
Event: event,
|
||||||
|
};
|
||||||
|
|
||||||
|
CoreClient._buttonEvents.push(item);
|
||||||
|
}
|
||||||
|
}
|
22
src/client/events.ts
Normal file
22
src/client/events.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { Interaction } from "discord.js";
|
||||||
|
import ChatInputCommand from "./interactionCreate/ChatInputCommand";
|
||||||
|
import Button from "./interactionCreate/Button";
|
||||||
|
|
||||||
|
export class Events {
|
||||||
|
public async onInteractionCreate(interaction: Interaction) {
|
||||||
|
if (!interaction.guildId) return;
|
||||||
|
|
||||||
|
if (interaction.isChatInputCommand()) {
|
||||||
|
ChatInputCommand.onChatInput(interaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interaction.isButton()) {
|
||||||
|
Button.onButtonClicked(interaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit when bot is logged in and ready to use
|
||||||
|
public onReady() {
|
||||||
|
console.log("Ready");
|
||||||
|
}
|
||||||
|
}
|
17
src/client/interactionCreate/Button.ts
Normal file
17
src/client/interactionCreate/Button.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { ButtonInteraction, Interaction } from "discord.js";
|
||||||
|
import { CoreClient } from "../client";
|
||||||
|
|
||||||
|
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]);
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
await interaction.reply('Event not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.Event.execute(interaction);
|
||||||
|
}
|
||||||
|
}
|
27
src/client/interactionCreate/ChatInputCommand.ts
Normal file
27
src/client/interactionCreate/ChatInputCommand.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { Interaction } from "discord.js";
|
||||||
|
import { CoreClient } from "../client";
|
||||||
|
import ICommandItem from "../../contracts/ICommandItem";
|
||||||
|
|
||||||
|
export default class ChatInputCommand {
|
||||||
|
public static async onChatInput(interaction: Interaction) {
|
||||||
|
if (!interaction.isChatInputCommand()) return;
|
||||||
|
|
||||||
|
const item = CoreClient.commandItems.find(x => x.Name == interaction.commandName && !x.ServerId);
|
||||||
|
const itemForServer = CoreClient.commandItems.find(x => x.Name == interaction.commandName && x.ServerId == interaction.guildId);
|
||||||
|
|
||||||
|
let itemToUse: ICommandItem;
|
||||||
|
|
||||||
|
if (!itemForServer) {
|
||||||
|
if (!item) {
|
||||||
|
await interaction.reply('Command not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemToUse = item;
|
||||||
|
} else {
|
||||||
|
itemToUse = itemForServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemToUse.Command.execute(interaction);
|
||||||
|
}
|
||||||
|
}
|
95
src/client/util.ts
Normal file
95
src/client/util.ts
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
import { Client, REST, Routes, SlashCommandBuilder } from "discord.js";
|
||||||
|
import { EventType } from "../constants/EventType";
|
||||||
|
import IEventItem from "../contracts/IEventItem";
|
||||||
|
import { CoreClient } from "./client";
|
||||||
|
|
||||||
|
export class Util {
|
||||||
|
public loadSlashCommands(client: Client) {
|
||||||
|
const registeredCommands = CoreClient.commandItems;
|
||||||
|
|
||||||
|
const globalCommands = registeredCommands.filter(x => !x.ServerId);
|
||||||
|
const guildCommands = registeredCommands.filter(x => x.ServerId);
|
||||||
|
|
||||||
|
const globalCommandData: SlashCommandBuilder[] = globalCommands
|
||||||
|
.filter(x => x.Command.CommandBuilder)
|
||||||
|
.flatMap(x => x.Command.CommandBuilder);
|
||||||
|
|
||||||
|
const guildIds: string[] = [];
|
||||||
|
|
||||||
|
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!);
|
||||||
|
|
||||||
|
rest.put(
|
||||||
|
Routes.applicationCommands(process.env.BOT_CLIENTID!),
|
||||||
|
{
|
||||||
|
body: globalCommandData
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
rest.put(
|
||||||
|
Routes.applicationGuildCommands(process.env.BOT_CLIENTID!, guild),
|
||||||
|
{
|
||||||
|
body: guildCommandData
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the events
|
||||||
|
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.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
56
src/commands/about.ts
Normal file
56
src/commands/about.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder, SlashCommandBuilder } from "discord.js";
|
||||||
|
import EmbedColours from "../constants/EmbedColours";
|
||||||
|
import { Command } from "../type/command";
|
||||||
|
|
||||||
|
export default class About extends Command {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
super.CommandBuilder = new SlashCommandBuilder()
|
||||||
|
.setName('about')
|
||||||
|
.setDescription('About Bot');
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async execute(interaction: CommandInteraction) {
|
||||||
|
const fundingLink = process.env.ABOUT_FUNDING;
|
||||||
|
const repoLink = process.env.ABOUT_REPO;
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setColor(EmbedColours.Ok)
|
||||||
|
.setTitle("About")
|
||||||
|
.setDescription("Discord Bot made by Vylpes");
|
||||||
|
|
||||||
|
embed.addFields([
|
||||||
|
{
|
||||||
|
name: "Version",
|
||||||
|
value: process.env.BOT_VER!,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Author",
|
||||||
|
value: process.env.BOT_AUTHOR!,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const row = new ActionRowBuilder<ButtonBuilder>();
|
||||||
|
|
||||||
|
if (repoLink) {
|
||||||
|
row.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setURL(repoLink)
|
||||||
|
.setLabel("Repo")
|
||||||
|
.setStyle(ButtonStyle.Link));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fundingLink) {
|
||||||
|
row.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setURL(fundingLink)
|
||||||
|
.setLabel("Funding")
|
||||||
|
.setStyle(ButtonStyle.Link));
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [ embed ], components: row.components.length > 0 ? [ row ] : [] });
|
||||||
|
}
|
||||||
|
}
|
54
src/commands/drop.ts
Normal file
54
src/commands/drop.ts
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, 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";
|
||||||
|
|
||||||
|
export default class Drop extends Command {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
super.CommandBuilder = new SlashCommandBuilder()
|
||||||
|
.setName('drop')
|
||||||
|
.setDescription('Summon a new card drop');
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async execute(interaction: CommandInteraction) {
|
||||||
|
const randomCard = await CardDropHelper.GetRandomCard();
|
||||||
|
|
||||||
|
const image = readFileSync(randomCard.Path);
|
||||||
|
|
||||||
|
const attachment = new AttachmentBuilder(image, { name: `${randomCard.Id}.png` });
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle(randomCard.Name)
|
||||||
|
.setDescription(randomCard.Series.Name)
|
||||||
|
.setFooter({ text: CardRarityToString(randomCard.Rarity) })
|
||||||
|
.setColor(CardRarityToColour(randomCard.Rarity))
|
||||||
|
.setImage(`attachment://${randomCard.Id}.png`);
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [ embed ],
|
||||||
|
files: [ attachment ],
|
||||||
|
components: [ row ],
|
||||||
|
});
|
||||||
|
|
||||||
|
CoreClient.ClaimId = claimId;
|
||||||
|
}
|
||||||
|
}
|
34
src/constants/CardRarity.ts
Normal file
34
src/constants/CardRarity.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import EmbedColours from "./EmbedColours";
|
||||||
|
|
||||||
|
export enum CardRarity {
|
||||||
|
Bronze,
|
||||||
|
Silver,
|
||||||
|
Gold,
|
||||||
|
Legendary,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CardRarityToString(rarity: CardRarity): string {
|
||||||
|
switch (rarity) {
|
||||||
|
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.Bronze:
|
||||||
|
return EmbedColours.BronzeCard;
|
||||||
|
case CardRarity.Silver:
|
||||||
|
return EmbedColours.SilverCard;
|
||||||
|
case CardRarity.Gold:
|
||||||
|
return EmbedColours.GoldCard;
|
||||||
|
case CardRarity.Legendary:
|
||||||
|
return EmbedColours.LegendaryCard;
|
||||||
|
}
|
||||||
|
}
|
7
src/constants/EmbedColours.ts
Normal file
7
src/constants/EmbedColours.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export default class EmbedColours {
|
||||||
|
public static readonly Ok = 0x3050ba;
|
||||||
|
public static readonly BronzeCard = 0xcd7f32;
|
||||||
|
public static readonly SilverCard = 0xc0c0c0;
|
||||||
|
public static readonly GoldCard = 0xffd700;
|
||||||
|
public static readonly LegendaryCard = 0x50c878;
|
||||||
|
}
|
15
src/constants/EventType.ts
Normal file
15
src/constants/EventType.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
export enum EventType {
|
||||||
|
ChannelCreate,
|
||||||
|
ChannelDelete,
|
||||||
|
ChannelUpdate,
|
||||||
|
GuildBanAdd,
|
||||||
|
GuildBanRemove,
|
||||||
|
GuildCreate,
|
||||||
|
GuildMemberAdd,
|
||||||
|
GuildMemberRemove,
|
||||||
|
GuildMemberUpdate,
|
||||||
|
MessageCreate,
|
||||||
|
MessageDelete,
|
||||||
|
MessageUpdate,
|
||||||
|
Ready,
|
||||||
|
}
|
59
src/contracts/AppBaseEntity.ts
Normal file
59
src/contracts/AppBaseEntity.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import { Column, DeepPartial, EntityTarget, PrimaryColumn, ObjectLiteral, FindOptionsWhere } from "typeorm";
|
||||||
|
import { v4 } from "uuid";
|
||||||
|
import AppDataSource from "../database/dataSources/appDataSource";
|
||||||
|
|
||||||
|
export default class AppBaseEntity {
|
||||||
|
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 AppBaseEntity>(target: EntityTarget<T>, entity: DeepPartial<T>): Promise<void> {
|
||||||
|
this.WhenUpdated = new Date();
|
||||||
|
|
||||||
|
const repository = AppDataSource.getRepository<T>(target);
|
||||||
|
|
||||||
|
await repository.save(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 FetchAll<T extends AppBaseEntity>(target: EntityTarget<T>, relations?: string[]): Promise<T[]> {
|
||||||
|
const repository = AppDataSource.getRepository<T>(target);
|
||||||
|
|
||||||
|
const all = await repository.find({ relations: relations || [] });
|
||||||
|
|
||||||
|
return all;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async FetchOneById<T extends AppBaseEntity>(target: EntityTarget<T>, id: string, relations?: string[]): Promise<T | null> {
|
||||||
|
const repository = AppDataSource.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 = AppDataSource.getRepository<T>(target);
|
||||||
|
|
||||||
|
const any = await repository.find();
|
||||||
|
|
||||||
|
return any.length > 0;
|
||||||
|
}
|
||||||
|
}
|
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;
|
||||||
|
}
|
||||||
|
}
|
4
src/contracts/IBaseResponse.ts
Normal file
4
src/contracts/IBaseResponse.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export interface IBaseResponse {
|
||||||
|
valid: boolean;
|
||||||
|
message?: string;
|
||||||
|
}
|
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,
|
||||||
|
}
|
7
src/contracts/ICommandItem.ts
Normal file
7
src/contracts/ICommandItem.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { Command } from "../type/command";
|
||||||
|
|
||||||
|
export default interface ICommandItem {
|
||||||
|
Name: string,
|
||||||
|
Command: Command,
|
||||||
|
ServerId?: string,
|
||||||
|
}
|
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,
|
||||||
|
}
|
4
src/contracts/IGDriveFolderListing.ts
Normal file
4
src/contracts/IGDriveFolderListing.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export default interface IGDriveFolderListing {
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
};
|
26
src/database/dataSources/appDataSource.ts
Normal file
26
src/database/dataSources/appDataSource.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { DataSource } from "typeorm";
|
||||||
|
import * as dotenv from "dotenv";
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const AppDataSource = new DataSource({
|
||||||
|
type: "mysql",
|
||||||
|
host: process.env.DB_HOST,
|
||||||
|
port: Number(process.env.DB_PORT),
|
||||||
|
username: process.env.DB_AUTH_USER,
|
||||||
|
password: process.env.DB_AUTH_PASS,
|
||||||
|
database: process.env.DB_NAME,
|
||||||
|
synchronize: process.env.DB_SYNC == "true",
|
||||||
|
logging: process.env.DB_LOGGING == "true",
|
||||||
|
entities: [
|
||||||
|
"dist/database/entities/app/**/*.js",
|
||||||
|
],
|
||||||
|
migrations: [
|
||||||
|
"dist/database/migrations/app/**/*.js",
|
||||||
|
],
|
||||||
|
subscribers: [
|
||||||
|
"dist/database/subscribers/app/**/*.js",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export default AppDataSource;
|
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;
|
47
src/database/entities/app/Inventory.ts
Normal file
47
src/database/entities/app/Inventory.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import { Column, Entity } from "typeorm";
|
||||||
|
import AppBaseEntity from "../../../contracts/AppBaseEntity";
|
||||||
|
import AppDataSource from "../../dataSources/appDataSource";
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export default class Inventory extends AppBaseEntity {
|
||||||
|
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;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
CardNumber: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
Quantity: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
ClaimId: string;
|
||||||
|
|
||||||
|
public SetQuantity(quantity: number) {
|
||||||
|
this.Quantity = quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async FetchOneByCardNumberAndUserId(userId: string, cardNumber: string): Promise<Inventory | null> {
|
||||||
|
const repository = AppDataSource.getRepository(Inventory);
|
||||||
|
|
||||||
|
const single = await repository.findOne({ where: { UserId: userId, CardNumber: cardNumber }});
|
||||||
|
|
||||||
|
return single;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async FetchOneByClaimId(claimId: string): Promise<Inventory | null> {
|
||||||
|
const repository = AppDataSource.getRepository(Inventory);
|
||||||
|
|
||||||
|
const single = await repository.findOne({ where: { ClaimId: claimId }});
|
||||||
|
|
||||||
|
return single;
|
||||||
|
}
|
||||||
|
}
|
32
src/database/entities/card/Card.ts
Normal file
32
src/database/entities/card/Card.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { Column, Entity, ManyToOne } from "typeorm";
|
||||||
|
import CardBaseEntity from "../../../contracts/CardBaseEntity";
|
||||||
|
import { CardRarity } from "../../../constants/CardRarity";
|
||||||
|
import Series from "./Series";
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export default class Card extends CardBaseEntity {
|
||||||
|
constructor(cardNumber: string, name: string, rarity: CardRarity, path: string, series: Series) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.CardNumber = cardNumber;
|
||||||
|
this.Name = name;
|
||||||
|
this.Rarity = rarity;
|
||||||
|
this.Path = path;
|
||||||
|
this.Series = series;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
CardNumber: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
Name: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
Rarity: CardRarity;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
Path: string
|
||||||
|
|
||||||
|
@ManyToOne(() => Series, x => x.Cards)
|
||||||
|
Series: Series;
|
||||||
|
}
|
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[];
|
||||||
|
}
|
15
src/database/migrations/app/0.1/1693769942868-CreateBase.ts
Normal file
15
src/database/migrations/app/0.1/1693769942868-CreateBase.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
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', [
|
||||||
|
"01-table/Inventory",
|
||||||
|
], queryRunner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
0
src/database/migrations/card/.gitkeep
Normal file
0
src/database/migrations/card/.gitkeep
Normal file
38
src/helpers/CardDropHelper.ts
Normal file
38
src/helpers/CardDropHelper.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { CardRarity } from "../constants/CardRarity";
|
||||||
|
import CardDataSource from "../database/dataSources/cardDataSource";
|
||||||
|
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 seriesRepository = CardDataSource.getRepository(Series);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
const randomCardIndex = Math.floor(Math.random() * allCards.length);
|
||||||
|
|
||||||
|
const randomCard = allCards[randomCardIndex];
|
||||||
|
|
||||||
|
return randomCard;
|
||||||
|
}
|
||||||
|
}
|
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);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
20
src/helpers/MigrationHelper.ts
Normal file
20
src/helpers/MigrationHelper.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
import { QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export default class MigrationHelper {
|
||||||
|
public static Up(migrationName: string, version: string, queryFiles: string[], queryRunner: QueryRunner) {
|
||||||
|
for (let path of queryFiles) {
|
||||||
|
const query = readFileSync(`${process.cwd()}/database/${version}/${migrationName}/Up/${path}.sql`).toString();
|
||||||
|
|
||||||
|
queryRunner.query(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Down(migrationName: string, version: string, queryFiles: string[], queryRunner: QueryRunner) {
|
||||||
|
for (let path of queryFiles) {
|
||||||
|
const query = readFileSync(`${process.cwd()}/database/${version}/${migrationName}/Down/${path}.sql`).toString();
|
||||||
|
|
||||||
|
queryRunner.query(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
src/helpers/StringTools.ts
Normal file
42
src/helpers/StringTools.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
export default class StringTools {
|
||||||
|
public static Capitalise(str: string): string {
|
||||||
|
const words = str.split(" ");
|
||||||
|
let result: string[] = [];
|
||||||
|
|
||||||
|
words.forEach(word => {
|
||||||
|
const firstLetter = word.substring(0, 1).toUpperCase();
|
||||||
|
const rest = word.substring(1);
|
||||||
|
|
||||||
|
result.push(firstLetter + rest);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CapitaliseArray(str: string[]): string[] {
|
||||||
|
const res: string[] = [];
|
||||||
|
|
||||||
|
str.forEach(s => {
|
||||||
|
res.push(StringTools.Capitalise(s));
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RandomString(length: number) {
|
||||||
|
let result = "";
|
||||||
|
|
||||||
|
const characters = 'abcdefghkmnpqrstuvwxyz23456789';
|
||||||
|
const charactersLength = characters.length;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
121
src/helpers/TimeLengthInput.ts
Normal file
121
src/helpers/TimeLengthInput.ts
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import StringTools from "./StringTools";
|
||||||
|
|
||||||
|
export default class TimeLengthInput {
|
||||||
|
public readonly value: string;
|
||||||
|
|
||||||
|
constructor(input: string) {
|
||||||
|
this.value = StringTools.ReplaceAll(input, ',', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetDays(): number {
|
||||||
|
return this.GetValue('d');
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetHours(): number {
|
||||||
|
return this.GetValue('h');
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetMinutes(): number {
|
||||||
|
return this.GetValue('m');
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetSeconds(): number {
|
||||||
|
return this.GetValue('s');
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetMilliseconds(): number {
|
||||||
|
const days = this.GetDays();
|
||||||
|
const hours = this.GetHours();
|
||||||
|
const minutes = this.GetMinutes();
|
||||||
|
const seconds = this.GetSeconds();
|
||||||
|
|
||||||
|
let milliseconds = 0;
|
||||||
|
|
||||||
|
milliseconds += seconds * 1000;
|
||||||
|
milliseconds += minutes * 60 * 1000;
|
||||||
|
milliseconds += hours * 60 * 60 * 1000;
|
||||||
|
milliseconds += days * 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
return milliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetDateFromNow(): Date {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
const dateFromNow = now
|
||||||
|
+ (1000 * this.GetSeconds())
|
||||||
|
+ (1000 * 60 * this.GetMinutes())
|
||||||
|
+ (1000 * 60 * 60 * this.GetHours())
|
||||||
|
+ (1000 * 60 * 60 * 24 * this.GetDays());
|
||||||
|
|
||||||
|
return new Date(dateFromNow);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetLength(): string {
|
||||||
|
const days = this.GetDays();
|
||||||
|
const hours = this.GetHours();
|
||||||
|
const minutes = this.GetMinutes();
|
||||||
|
const seconds = this.GetSeconds();
|
||||||
|
|
||||||
|
const value = [];
|
||||||
|
|
||||||
|
if (days) {
|
||||||
|
value.push(`${days} days`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hours) {
|
||||||
|
value.push(`${hours} hours`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minutes) {
|
||||||
|
value.push(`${minutes} minutes`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds) {
|
||||||
|
value.push(`${seconds} seconds`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetLengthShort(): string {
|
||||||
|
const days = this.GetDays();
|
||||||
|
const hours = this.GetHours();
|
||||||
|
const minutes = this.GetMinutes();
|
||||||
|
const seconds = this.GetSeconds();
|
||||||
|
|
||||||
|
const value = [];
|
||||||
|
|
||||||
|
if (days) {
|
||||||
|
value.push(`${days}d`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hours) {
|
||||||
|
value.push(`${hours}h`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minutes) {
|
||||||
|
value.push(`${minutes}m`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds) {
|
||||||
|
value.push(`${seconds}s`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
private GetValue(designation: string): number {
|
||||||
|
const valueSplit = this.value.split(' ');
|
||||||
|
|
||||||
|
const desString = valueSplit.find(x => x.charAt(x.length - 1) == designation);
|
||||||
|
|
||||||
|
if (!desString) return 0;
|
||||||
|
|
||||||
|
const desNumber = Number(desString.substring(0, desString.length - 1));
|
||||||
|
|
||||||
|
if (!desNumber) return 0;
|
||||||
|
|
||||||
|
return desNumber;
|
||||||
|
}
|
||||||
|
}
|
23
src/registry.ts
Normal file
23
src/registry.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { CoreClient } from "./client/client";
|
||||||
|
|
||||||
|
import About from "./commands/about";
|
||||||
|
import Drop from "./commands/drop";
|
||||||
|
|
||||||
|
import Claim from "./buttonEvents/Claim";
|
||||||
|
import Reroll from "./buttonEvents/Reroll";
|
||||||
|
|
||||||
|
export default class Registry {
|
||||||
|
public static RegisterCommands() {
|
||||||
|
CoreClient.RegisterCommand('about', new About());
|
||||||
|
CoreClient.RegisterCommand('drop', new Drop());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RegisterEvents() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RegisterButtonEvents() {
|
||||||
|
CoreClient.RegisterButtonEvent('claim', new Claim());
|
||||||
|
CoreClient.RegisterButtonEvent('reroll', new Reroll());
|
||||||
|
}
|
||||||
|
}
|
7
src/type/buttonEvent.ts
Normal file
7
src/type/buttonEvent.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { ButtonInteraction } from "discord.js";
|
||||||
|
|
||||||
|
export class ButtonEvent {
|
||||||
|
public execute(interaction: ButtonInteraction) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
9
src/type/command.ts
Normal file
9
src/type/command.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { CommandInteraction } from "discord.js";
|
||||||
|
|
||||||
|
export class Command {
|
||||||
|
public CommandBuilder: any;
|
||||||
|
|
||||||
|
public execute(interaction: CommandInteraction) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
0
tests/.gitkeep
Normal file
0
tests/.gitkeep
Normal file
78
tsconfig.json
Normal file
78
tsconfig.json
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||||
|
|
||||||
|
/* Basic Options */
|
||||||
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
|
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
||||||
|
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||||
|
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||||
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||||
|
"declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||||
|
"declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||||
|
"sourceMap": true, /* Generates corresponding '.map' file. */
|
||||||
|
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||||
|
"outDir": "./dist", /* Redirect output structure to the directory. */
|
||||||
|
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||||
|
// "composite": true, /* Enable project compilation */
|
||||||
|
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||||
|
// "removeComments": true, /* Do not emit comments to output. */
|
||||||
|
// "noEmit": true, /* Do not emit outputs. */
|
||||||
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||||
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||||
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||||
|
|
||||||
|
/* Strict Type-Checking Options */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||||
|
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||||
|
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||||
|
"strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */
|
||||||
|
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||||
|
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||||
|
|
||||||
|
/* Additional Checks */
|
||||||
|
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||||
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||||
|
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||||
|
|
||||||
|
/* Module Resolution Options */
|
||||||
|
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||||
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||||
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
|
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||||
|
// "types": [], /* Type declaration files to be included in compilation. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||||
|
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
|
||||||
|
/* Source Map Options */
|
||||||
|
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||||
|
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||||
|
|
||||||
|
/* Experimental Options */
|
||||||
|
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||||
|
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
|
|
||||||
|
/* Advanced Options */
|
||||||
|
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||||
|
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src",
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"./tests"
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in a new issue