diff --git a/.dev.env b/.env.example similarity index 78% rename from .dev.env rename to .env.example index 9965665..3ff53e9 100644 --- a/.dev.env +++ b/.env.example @@ -7,7 +7,7 @@ # any secret values. BOT_TOKEN= -BOT_VER=0.5.1 +BOT_VER=0.5.0 BOT_AUTHOR=Vylpes BOT_OWNERID=147392775707426816 BOT_CLIENTID=682942374040961060 @@ -19,16 +19,17 @@ ABOUT_REPO= DATA_DIR= -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_HOST= +DB_PORT= +DB_NAME= +DB_AUTH_USER= +DB_AUTH_PASS= +DB_SYNC= +DB_LOGGING= +DB_DATA_LOCATION=~/.docker DB_CARD_FILE=:memory: EXPRESS_PORT=3303 -GDRIVESYNC_AUTO=true +GDRIVESYNC_AUTO=true \ No newline at end of file diff --git a/.forgejo/workflows/production.yml b/.forgejo/workflows/production.yml new file mode 100644 index 0000000..4b53d02 --- /dev/null +++ b/.forgejo/workflows/production.yml @@ -0,0 +1,71 @@ +name: Deploy To Production + +on: + push: + branches: + - main + +jobs: + build: + environment: prod + + runs-on: node + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js + uses: actions/setup-node@v1 + with: + node-version: 18.x + - run: npm ci + - run: npm run build + - run: npm test + + - 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.0 + 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 }} + 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 + 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 \ No newline at end of file diff --git a/.forgejo/workflows/stage.yml b/.forgejo/workflows/stage.yml new file mode 100644 index 0000000..2bb68c0 --- /dev/null +++ b/.forgejo/workflows/stage.yml @@ -0,0 +1,71 @@ +name: Deploy To Stage + +on: + push: + branches: + - develop + +jobs: + build: + environment: stage + + runs-on: node + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js + uses: actions/setup-node@v1 + with: + node-version: 18.x + - run: npm ci + - run: npm run build + - run: npm test + + - 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.0 + 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 }} + 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 + 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 \ No newline at end of file diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml new file mode 100644 index 0000000..989ac4f --- /dev/null +++ b/.forgejo/workflows/test.yml @@ -0,0 +1,24 @@ +name: Test + +on: + push: + branches: + - feature/* + - hotfix/* + - renovate/* + +jobs: + build: + environment: stage + + runs-on: node + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js + uses: actions/setup-node@v1 + with: + node-version: 18.x + - run: npm ci + - run: npm run build + - run: npm test \ No newline at end of file diff --git a/.prod.env b/.prod.env deleted file mode 100644 index da1fc9c..0000000 --- a/.prod.env +++ /dev/null @@ -1,34 +0,0 @@ -# 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.5.1 -BOT_AUTHOR=Vylpes -BOT_OWNERID=147392775707426816 -BOT_CLIENTID=1093810443589529631 -BOT_ENV=1 -BOT_ADMINS=147392775707426816,887272961504071690 - -ABOUT_FUNDING= -ABOUT_REPO= - -DATA_DIR=/home/vylpes/appdata/card-drop/card-drop_prod - -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: - -EXPRESS_PORT=3323 - -GDRIVESYNC_AUTO=false diff --git a/.stage.env b/.stage.env deleted file mode 100644 index 0c9264a..0000000 --- a/.stage.env +++ /dev/null @@ -1,34 +0,0 @@ -# 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.5.1 -BOT_AUTHOR=Vylpes -BOT_OWNERID=147392775707426816 -BOT_CLIENTID=1147976642942214235 -BOT_ENV=2 -BOT_ADMINS=147392775707426816,887272961504071690 - -ABOUT_FUNDING= -ABOUT_REPO= - -DATA_DIR=/home/vylpes/appdata/card-drop/card-drop_stage - -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: - -EXPRESS_PORT=3313 - -GDRIVESYNC_AUTO=false diff --git a/.woodpecker.yml b/.woodpecker.yml index 30ac056..f42ba60 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -28,8 +28,8 @@ steps: - 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.68.120:/home/vylpes/apps/card-drop/card-drop_stage - - ssh vylpes@192.168.68.120 BOT_TOKEN='$${stage_bot_token}' 'bash -s' < ./scripts/deploy_stage.sh + - 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 ] @@ -40,8 +40,8 @@ steps: - 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.68.120:/home/vylpes/apps/card-drop/card-drop_prod - - ssh vylpes@192.168.68.120 BOT_TOKEN='$${prod_bot_token}' 'bash -s' < ./scripts/deploy_prod.sh + - 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 ] \ No newline at end of file diff --git a/database/0.6/1713289062969-user/Up/01-table/User.sql b/database/0.6/1713289062969-user/Up/01-table/User.sql new file mode 100644 index 0000000..86a5d36 --- /dev/null +++ b/database/0.6/1713289062969-user/Up/01-table/User.sql @@ -0,0 +1,7 @@ +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; \ No newline at end of file diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml deleted file mode 100644 index faac7fb..0000000 --- a/docker-compose.prod.yml +++ /dev/null @@ -1,31 +0,0 @@ -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 \ No newline at end of file diff --git a/docker-compose.stage.yml b/docker-compose.stage.yml deleted file mode 100644 index a6666a4..0000000 --- a/docker-compose.stage.yml +++ /dev/null @@ -1,31 +0,0 @@ -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 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index c0b5077..025a674 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,31 +1,17 @@ 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 + - 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 ports: - - "3301:3306" + - "$DB_PORT:3306" volumes: - - dev_database_data:/var/lib/mysql - - phpmyadmin: - image: phpmyadmin - restart: always - ports: - - "3302:80" - environment: - - PMA_ARBITRARY=1 \ No newline at end of file + - $DB_DATA_LOCATION:/var/lib/mysql \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 51c2c39..e93e59f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,10 +22,11 @@ "glob": "^10.3.10", "jest": "^29.0.0", "jest-mock-extended": "^3.0.0", - "minimatch": "9.0.3", + "minimatch": "9.0.4", "mysql": "^2.18.1", "ts-jest": "^29.0.0", - "typeorm": "0.3.20" + "typeorm": "0.3.20", + "winston": "^3.11.0" }, "devDependencies": { "@types/node": "^20.0.0", @@ -711,6 +712,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "node_modules/@discordjs/builders": { "version": "1.7.0", "license": "Apache-2.0", @@ -914,9 +933,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -937,13 +956,13 @@ "peer": true }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -1009,9 +1028,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, "node_modules/@isaacs/cliui": { @@ -1881,9 +1900,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.11.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz", - "integrity": "sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==", + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", "dependencies": { "undici-types": "~5.26.4" } @@ -1910,9 +1929,9 @@ } }, "node_modules/@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz", + "integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==", "dev": true }, "node_modules/@types/send": { @@ -1940,6 +1959,11 @@ "version": "2.0.3", "license": "MIT" }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" + }, "node_modules/@types/uuid": { "version": "9.0.8", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", @@ -1964,16 +1988,16 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.18.1.tgz", - "integrity": "sha512-nISDRYnnIpk7VCFrGcu1rnZfM1Dh9LRHnfgdkjcbi/l7g16VYRri3TjXi9Ir4lOZSw5N/gnV/3H7jIPQ8Q4daA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.18.1", - "@typescript-eslint/type-utils": "6.18.1", - "@typescript-eslint/utils": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -1998,53 +2022,6 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz", - "integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz", - "integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==", - "dev": true, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz", - "integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.18.1", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2069,15 +2046,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.16.0.tgz", - "integrity": "sha512-H2GM3eUo12HpKZU9njig3DF5zJ58ja6ahj1GoHEHOgQvYxzoFJJEvC1MQ7T2l9Ha+69ZSOn7RTxOdpC/y3ikMw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.16.0", - "@typescript-eslint/types": "6.16.0", - "@typescript-eslint/typescript-estree": "6.16.0", - "@typescript-eslint/visitor-keys": "6.16.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "engines": { @@ -2120,13 +2097,13 @@ "dev": true }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.16.0.tgz", - "integrity": "sha512-0N7Y9DSPdaBQ3sqSCwlrm9zJwkpOuc6HYm7LpzLAPqBL7dmzAUimr4M29dMkOP/tEwvOCC/Cxo//yOfJD3HUiw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.16.0", - "@typescript-eslint/visitor-keys": "6.16.0" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -2137,13 +2114,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.18.1.tgz", - "integrity": "sha512-wyOSKhuzHeU/5pcRDP2G2Ndci+4g653V43gXTpt4nbyoIOAASkGDA9JIAgbQCdCkcr1MvpSYWzxTz0olCn8+/Q==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.18.1", - "@typescript-eslint/utils": "6.18.1", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -2163,64 +2140,6 @@ } } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz", - "integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==", - "dev": true, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz", - "integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz", - "integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.18.1", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/type-utils/node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2238,26 +2157,6 @@ } } }, - "node_modules/@typescript-eslint/type-utils/node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@typescript-eslint/type-utils/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2265,9 +2164,9 @@ "dev": true }, "node_modules/@typescript-eslint/types": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.16.0.tgz", - "integrity": "sha512-hvDFpLEvTJoHutVl87+MG/c5C8I6LOgEx05zExTSJDEVU7hhR3jhV8M5zuggbdFCw98+HhZWPHZeKS97kS3JoQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -2278,13 +2177,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.16.0.tgz", - "integrity": "sha512-VTWZuixh/vr7nih6CfrdpmFNLEnoVBF1skfjdyGnNwXOH1SLeHItGdZDHhhAIzd3ACazyY2Fg76zuzOVTaknGA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.16.0", - "@typescript-eslint/visitor-keys": "6.16.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2342,6 +2241,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2349,17 +2263,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.1.tgz", - "integrity": "sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.18.1", - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/typescript-estree": "6.18.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "engines": { @@ -2373,131 +2287,13 @@ "eslint": "^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz", - "integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz", - "integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==", - "dev": true, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz", - "integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz", - "integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.18.1", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils/node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.16.0.tgz", - "integrity": "sha512-QSFQLruk7fhs91a/Ep/LqRdbJCZ1Rq03rqBdKT5Ky17Sz8zRLUksqIe9DW0pKtg/Z35/ztbLQ6qpOCN6rOC11A==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.16.0", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -2819,6 +2615,11 @@ "node": ">=8" } }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + }, "node_modules/babel-jest": { "version": "29.7.0", "license": "MIT", @@ -3825,6 +3626,15 @@ "version": "1.0.2", "license": "MIT" }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "license": "MIT", @@ -3839,6 +3649,15 @@ "version": "1.1.4", "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -3849,6 +3668,28 @@ "color-support": "bin.js" } }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, "node_modules/commander": { "version": "6.2.1", "dev": true, @@ -3940,8 +3781,9 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.5.0", - "license": "MIT", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -4410,14 +4252,14 @@ } }, "node_modules/dotenv": { - "version": "16.3.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.2.tgz", - "integrity": "sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ==", + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" + "url": "https://dotenvx.com" } }, "node_modules/eastasianwidth": { @@ -4454,6 +4296,11 @@ "version": "8.0.0", "license": "MIT" }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, "node_modules/encodeurl": { "version": "1.0.2", "license": "MIT", @@ -4546,16 +4393,16 @@ } }, "node_modules/eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -4913,15 +4760,16 @@ } }, "node_modules/express": { - "version": "4.18.2", - "license": "MIT", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -4952,41 +4800,6 @@ "node": ">= 0.10.0" } }, - "node_modules/express/node_modules/body-parser": { - "version": "1.20.1", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/express/node_modules/raw-body": { - "version": "2.5.1", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/external-editor": { "version": "3.1.0", "dev": true, @@ -5044,6 +4857,11 @@ "bser": "2.1.1" } }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, "node_modules/figures": { "version": "2.0.0", "dev": true, @@ -5136,6 +4954,11 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -5331,15 +5154,15 @@ "license": "MIT" }, "node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", + "jackspeak": "^2.3.6", "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" }, "bin": { "glob": "dist/esm/bin.mjs" @@ -6993,10 +6816,11 @@ } }, "node_modules/jest-mock-extended": { - "version": "3.0.5", - "license": "MIT", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-3.0.6.tgz", + "integrity": "sha512-DJuEoFzio0loqdX8NIwkbE9dgIXNzaj//pefOQxGkoivohpxbSQeNHCAiXkDNA/fmM4EIJDoZnSibP4w3dUJ9g==", "dependencies": { - "ts-essentials": "^7.0.3" + "ts-essentials": "^9.4.2" }, "peerDependencies": { "jest": "^24.0.0 || ^25.0.0 || ^26.0.0 || ^27.0.0 || ^28.0.0 || ^29.0.0", @@ -7332,6 +7156,11 @@ "node": ">=6" } }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, "node_modules/latest-version": { "version": "7.0.0", "dev": true, @@ -7832,6 +7661,27 @@ "node": ">=4" } }, + "node_modules/logform": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz", + "integrity": "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/lowercase-keys": { "version": "2.0.0", "dev": true, @@ -8034,8 +7884,9 @@ } }, "node_modules/minimatch": { - "version": "9.0.3", - "license": "ISC", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -8947,6 +8798,14 @@ "wrappy": "1" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/onetime": { "version": "5.1.2", "license": "MIT", @@ -9476,11 +9335,11 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { @@ -9491,9 +9350,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", - "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "engines": { "node": "14 || >=16.14" } @@ -10164,6 +10023,14 @@ ], "license": "MIT" }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "license": "MIT" @@ -10311,6 +10178,19 @@ "version": "3.0.7", "license": "ISC" }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, "node_modules/sisteransi": { "version": "1.0.5", "license": "MIT" @@ -10500,6 +10380,14 @@ "node": ">=8" } }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "engines": { + "node": "*" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "license": "MIT", @@ -10848,6 +10736,11 @@ "node": "*" } }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -10933,6 +10826,14 @@ "optional": true, "peer": true }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/ts-api-utils": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", @@ -10946,10 +10847,16 @@ } }, "node_modules/ts-essentials": { - "version": "7.0.3", - "license": "MIT", + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-9.4.2.tgz", + "integrity": "sha512-mB/cDhOvD7pg3YCLk2rOtejHjjdSi9in/IBYE13S+8WA5FBSraYf4V/ws55uvs0IvQ/l0wBOlXy5yBNZ9Bl8ZQ==", "peerDependencies": { - "typescript": ">=3.7.0" + "typescript": ">=4.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/ts-jest": { @@ -11200,9 +11107,9 @@ } }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11521,6 +11428,66 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/winston": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.0.tgz", + "integrity": "sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ==", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.7.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.0.tgz", + "integrity": "sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==", + "dependencies": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/winston/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/wrap-ansi": { "version": "8.1.0", "license": "MIT", diff --git a/package.json b/package.json index d481825..178f288 100644 --- a/package.json +++ b/package.json @@ -38,10 +38,11 @@ "glob": "^10.3.10", "jest": "^29.0.0", "jest-mock-extended": "^3.0.0", - "minimatch": "9.0.3", + "minimatch": "9.0.4", "mysql": "^2.18.1", "ts-jest": "^29.0.0", - "typeorm": "0.3.20" + "typeorm": "0.3.20", + "winston": "^3.11.0" }, "overrides": { "undici": "^5.28.3" diff --git a/src/Functions/CardMetadataFunction.ts b/src/Functions/CardMetadataFunction.ts index 3a8b97a..733c50c 100644 --- a/src/Functions/CardMetadataFunction.ts +++ b/src/Functions/CardMetadataFunction.ts @@ -4,6 +4,7 @@ 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"; export interface CardMetadataResult { IsSuccess: boolean; @@ -21,16 +22,22 @@ export interface FindMetadataResult { export default class CardMetadataFunction { public static async Execute(overrideSafeMode: boolean = false): Promise { - if (!overrideSafeMode && await Config.GetValue("safemode") == "true") return { - IsSuccess: false, - ErrorMessage: "Safe mode is on and not overridden", - }; + 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!; - console.log(`Loaded ${CoreClient.Cards.flatMap(x => x.cards).length} cards to database`); + AppLogger.LogInfo("Functions/CardMetadataFunction", `Loaded ${CoreClient.Cards.flatMap(x => x.cards).length} cards to database`); return { IsSuccess: true, @@ -38,6 +45,7 @@ export default class CardMetadataFunction { } await Config.SetValue("safemode", "true"); + AppLogger.LogError("Functions/CardMetadataFunction", `Safe Mode activated due to error: ${cardResult.Error!.Message}`); return { IsSuccess: false, @@ -52,13 +60,13 @@ export default class CardMetadataFunction { for (const jsonPath of seriesJSONs) { try { - console.log(`Reading file ${jsonPath}`); + AppLogger.LogVerbose("Functions/CardMetadataFunction", `Reading file ${jsonPath}`); const jsonFile = readFileSync(jsonPath); const parsedJson: SeriesMetadata[] = JSON.parse(jsonFile.toString()); res.push(...parsedJson); } catch (e) { - console.error(e); + AppLogger.LogError("Functions/CardMetadataFunction", `Error reading file ${jsonPath}: ${e}`); return { IsSuccess: false, diff --git a/src/buttonEvents/Claim.ts b/src/buttonEvents/Claim.ts index a9f8c86..6c45900 100644 --- a/src/buttonEvents/Claim.ts +++ b/src/buttonEvents/Claim.ts @@ -1,8 +1,12 @@ -import { ButtonInteraction } from "discord.js"; +import { AttachmentBuilder, 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 { readFileSync } from "fs"; +import path from "path"; export default class Claim extends ButtonEvent { public override async execute(interaction: ButtonInteraction) { @@ -13,17 +17,17 @@ export default class Claim extends ButtonEvent { const droppedBy = interaction.customId.split(" ")[3]; const userId = interaction.user.id; - await interaction.deferReply(); + AppLogger.LogSilly("Button/Claim", `Parameters: cardNumber=${cardNumber}, claimId=${claimId}, droppedBy=${droppedBy}, userId=${userId}`); const claimed = await eClaim.FetchOneByClaimId(claimId); if (claimed) { - await interaction.editReply("This card has already been claimed"); + await interaction.reply("This card has already been claimed"); return; } if (claimId == CoreClient.ClaimId && userId != droppedBy) { - await interaction.editReply("The latest dropped card can only be claimed by the user who dropped it"); + await interaction.reply("The latest dropped card can only be claimed by the user who dropped it"); return; } @@ -42,6 +46,24 @@ export default class Claim extends ButtonEvent { await claim.Save(eClaim, claim); - await interaction.editReply(`Card claimed by ${interaction.user}`); + const card = CardDropHelperMetadata.GetCardByCardNumber(cardNumber); + + if (!card) { + return; + } + + const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path)); + const imageFileName = card.card.path.split("/").pop()!; + + const attachment = new AttachmentBuilder(image, { name: imageFileName }); + + const embed = CardDropHelperMetadata.GenerateDropEmbed(card, inventory.Quantity, imageFileName, interaction.user.username); + const row = CardDropHelperMetadata.GenerateDropButtons(card, claimId, interaction.user.id, true); + + await interaction.update({ + embeds: [ embed ], + files: [ attachment ], + components: [ row ], + }); } } \ No newline at end of file diff --git a/src/buttonEvents/Inventory.ts b/src/buttonEvents/Inventory.ts index 4bfffc1..7dfb305 100644 --- a/src/buttonEvents/Inventory.ts +++ b/src/buttonEvents/Inventory.ts @@ -1,6 +1,7 @@ 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) { @@ -9,6 +10,8 @@ export default class Inventory extends ButtonEvent { const userid = interaction.customId.split(" ")[1]; const page = interaction.customId.split(" ")[2]; + AppLogger.LogSilly("Button/Inventory", `Parameters: userid=${userid}, page=${page}`); + const member = interaction.guild.members.cache.find(x => x.id == userid) || await interaction.guild.members.fetch(userid); if (!member) { @@ -17,6 +20,8 @@ export default class Inventory extends ButtonEvent { } 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)); await interaction.update({ @@ -24,7 +29,8 @@ export default class Inventory extends ButtonEvent { components: [ embed.row ], }); } catch (e) { - console.error(e); + AppLogger.LogError("Button/Inventory", `Error generating inventory page for ${member.user.username} with id ${member.user.id}: ${e}`); + await interaction.reply("No page for user found."); } } diff --git a/src/buttonEvents/Reroll.ts b/src/buttonEvents/Reroll.ts index a265bcb..c271a3a 100644 --- a/src/buttonEvents/Reroll.ts +++ b/src/buttonEvents/Reroll.ts @@ -7,6 +7,7 @@ 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"; export default class Reroll extends ButtonEvent { public override async execute(interaction: ButtonInteraction) { @@ -16,6 +17,8 @@ export default class Reroll extends ButtonEvent { } 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; } @@ -30,6 +33,8 @@ export default class Reroll extends ButtonEvent { await interaction.deferReply(); 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()!; @@ -51,9 +56,8 @@ export default class Reroll extends ButtonEvent { }); CoreClient.ClaimId = claimId; - } catch (e) { - console.error(e); + AppLogger.LogError("Button/Reroll", `Error sending next drop for card ${randomCard.card.id}: ${e}`); await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. (${randomCard.card.id})`); } diff --git a/src/buttonEvents/Series.ts b/src/buttonEvents/Series.ts new file mode 100644 index 0000000..0d98bfb --- /dev/null +++ b/src/buttonEvents/Series.ts @@ -0,0 +1,45 @@ +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]; + + const embed = SeriesHelper.GenerateSeriesViewPage(Number(seriesid), Number(page)); + + await interaction.update({ + embeds: [ embed!.embed ], + components: [ embed!.row ], + }); + } + + 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 ], + }); + } +} \ No newline at end of file diff --git a/src/buttonEvents/Trade.ts b/src/buttonEvents/Trade.ts new file mode 100644 index 0000000..9ce4e56 --- /dev/null +++ b/src/buttonEvents/Trade.ts @@ -0,0 +1,211 @@ +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 giveUserId = interaction.customId.split(" ")[2]; + const receiveUserId = interaction.customId.split(" ")[3]; + const giveCardNumber = interaction.customId.split(" ")[4]; + const receiveCardNumber = interaction.customId.split(" ")[5]; + const expiry = interaction.customId.split(" ")[6]; + const timeoutId = interaction.customId.split(" ")[7]; + + AppLogger.LogSilly("Button/Trade/AcceptTrade", `Parameters: giveUserId=${giveUserId}, receiveUserId=${receiveUserId}, giveCardNumber=${giveCardNumber}, receiveCardNumber=${receiveCardNumber}, expiry=${expiry}, timeoutId=${timeoutId}`); + + const expiryDate = new Date(expiry); + + if (expiryDate < new Date()) { + await interaction.reply("Trade has expired"); + return; + } + + if (interaction.user.id !== receiveUserId) { + await interaction.reply("You are not the user who the trade is intended for"); + return; + } + + const giveItem = CoreClient.Cards + .flatMap(x => x.cards) + .find(x => x.id === giveCardNumber); + + const receiveItem = CoreClient.Cards + .flatMap(x => x.cards) + .find(x => x.id === receiveCardNumber); + + if (!giveItem || !receiveItem) { + await interaction.reply("One or more of the items you are trying to trade does not exist."); + return; + } + + const giveUser = interaction.client.users.cache.get(giveUserId) || await interaction.client.users.fetch(giveUserId); + const receiveUser = interaction.client.users.cache.get(receiveUserId) || await interaction.client.users.fetch(receiveUserId); + + const giveUserInventory1 = await Inventory.FetchOneByCardNumberAndUserId(giveUserId, giveCardNumber); + const receiveUserInventory1 = await Inventory.FetchOneByCardNumberAndUserId(receiveUserId, receiveCardNumber); + + if (!giveUserInventory1 || !receiveUserInventory1) { + await interaction.reply("One or more of the items you are trying to trade does not exist."); + return; + } + + if (giveUserInventory1.Quantity < 1 || receiveUserInventory1.Quantity < 1) { + await interaction.reply("One or more of the items you are trying to trade does not exist."); + return; + } + + giveUserInventory1.SetQuantity(giveUserInventory1.Quantity - 1); + receiveUserInventory1.SetQuantity(receiveUserInventory1.Quantity - 1); + + await giveUserInventory1.Save(Inventory, giveUserInventory1); + await receiveUserInventory1.Save(Inventory, receiveUserInventory1); + + let giveUserInventory2 = await Inventory.FetchOneByCardNumberAndUserId(receiveUserId, giveCardNumber); + let receiveUserInventory2 = await Inventory.FetchOneByCardNumberAndUserId(giveUserId, receiveCardNumber); + + if (!giveUserInventory2) { + giveUserInventory2 = new Inventory(receiveUserId, giveCardNumber, 1); + } else { + giveUserInventory2.SetQuantity(giveUserInventory2.Quantity + 1); + } + + if (!receiveUserInventory2) { + receiveUserInventory2 = new Inventory(giveUserId, receiveCardNumber, 1); + } else { + receiveUserInventory2.SetQuantity(receiveUserInventory2.Quantity + 1); + } + + await giveUserInventory2.Save(Inventory, giveUserInventory2); + await receiveUserInventory2.Save(Inventory, receiveUserInventory2); + + clearTimeout(timeoutId); + + const tradeEmbed = new EmbedBuilder() + .setTitle("Trade Accepted") + .setDescription(`Trade initiated between ${receiveUser.username} and ${giveUser.username}`) + .setColor(EmbedColours.Success) + .setImage("https://i.imgur.com/9w5f1ls.gif") + .addFields([ + { + name: "I receieve", + value: `${receiveItem.id}: ${receiveItem.name}`, + inline: true, + }, + { + name: "You receieve", + value: `${giveItem.id}: ${giveItem.name}`, + inline: true, + }, + { + name: "Complete", + value: new Date().toLocaleString(), + } + ]); + + const row = new ActionRowBuilder() + .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 giveUserId = interaction.customId.split(" ")[2]; + const receiveUserId = interaction.customId.split(" ")[3]; + const giveCardNumber = interaction.customId.split(" ")[4]; + const receiveCardNumber = interaction.customId.split(" ")[5]; + // No need to get expiry date + const timeoutId = interaction.customId.split(" ")[7]; + + AppLogger.LogSilly("Button/Trade/DeclineTrade", `Parameters: giveUserId=${giveUserId}, receiveUserId=${receiveUserId}, giveCardNumber=${giveCardNumber}, receiveCardNumber=${receiveCardNumber}, timeoutId=${timeoutId}`); + + if (interaction.user.id != receiveUserId && interaction.user.id !==giveUserId) { + await interaction.reply("You are not the user who the trade is intended for"); + return; + } + + const giveUser = interaction.client.users.cache.get(giveUserId) || await interaction.client.users.fetch(giveUserId); + const receiveUser = interaction.client.users.cache.get(receiveUserId) || await interaction.client.users.fetch(receiveUserId); + + const giveItem = CoreClient.Cards + .flatMap(x => x.cards) + .find(x => x.id === giveCardNumber); + + const receiveItem = CoreClient.Cards + .flatMap(x => x.cards) + .find(x => x.id === receiveCardNumber); + + if (!giveItem || !receiveItem) { + 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 ${receiveUser.username} and ${giveUser.username}`) + .setColor(EmbedColours.Error) + .setImage("https://i.imgur.com/9w5f1ls.gif") + .addFields([ + { + name: "I Receive", + value: `${receiveItem.id}: ${receiveItem.name}`, + inline: true, + }, + { + name: "You Receive", + value: `${giveItem.id}: ${giveItem.name}`, + inline: true, + }, + { + name: "Declined", + value: new Date().toLocaleString(), + } + ]); + + const row = new ActionRowBuilder() + .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 ]}); + } +} \ No newline at end of file diff --git a/src/client/appLogger.ts b/src/client/appLogger.ts new file mode 100644 index 0000000..c5d022b --- /dev/null +++ b/src/client/appLogger.ts @@ -0,0 +1,65 @@ +import { Logger, createLogger, format, transports } from "winston"; + +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: [ + new transports.File({ filename: "error.log", level: "error" }), + new transports.File({ filename: "combined.log" }), + ], + }); + + if (outputToConsole) { + logger.add(new transports.Console({ + format: format.combine( + format.colorize(), + format.timestamp(), + customFormat, + )})); + } + + 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 }); + } +} \ No newline at end of file diff --git a/src/client/client.ts b/src/client/client.ts index ca2fba3..c694950 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -13,6 +13,7 @@ import { Environment } from "../constants/Environment"; import Webhooks from "../webhooks"; import CardMetadataFunction from "../Functions/CardMetadataFunction"; import { SeriesMetadata } from "../contracts/SeriesMetadata"; +import AppLogger from "./appLogger"; export class CoreClient extends Client { private static _commandItems: ICommandItem[]; @@ -44,6 +45,14 @@ 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._buttonEvents = []; @@ -51,21 +60,24 @@ export class CoreClient extends Client { this._util = new Util(); this._webhooks = new Webhooks(); - CoreClient.Environment = Number(process.env.BOT_ENV); - console.log(`Bot Environment: ${CoreClient.Environment}`); + AppLogger.LogInfo("Client", `Environment: ${CoreClient.Environment}`); CoreClient.AllowDrops = true; } public async start() { if (!process.env.BOT_TOKEN) { - console.error("BOT_TOKEN is not defined in .env"); + AppLogger.LogError("Client", "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)); + .then(() => AppLogger.LogInfo("Client", "App Data Source Initialised")) + .catch(err => { + AppLogger.LogError("Client", "App Data Source Initialisation Failed"); + AppLogger.LogError("Client", err); + throw err; + }); super.on("interactionCreate", this._events.onInteractionCreate); super.on("ready", this._events.onReady); @@ -90,6 +102,8 @@ export class CoreClient extends Client { if ((environment & CoreClient.Environment) == CoreClient.Environment) { CoreClient._commandItems.push(item); + + AppLogger.LogVerbose("Client", `Registered Command: ${name}`); } } @@ -112,6 +126,8 @@ export class CoreClient extends Client { MessageUpdate: [], }; } + + AppLogger.LogVerbose("Client", "Registered Channel Create Event"); } public static RegisterChannelDeleteEvent(fn: (channel: DMChannel | NonThreadGuildBasedChannel) => void) { @@ -133,6 +149,8 @@ export class CoreClient extends Client { MessageUpdate: [], }; } + + AppLogger.LogVerbose("Client", "Registered Channel Delete Event"); } public static RegisterChannelUpdateEvent(fn: (channel: DMChannel | NonThreadGuildBasedChannel) => void) { @@ -154,6 +172,8 @@ export class CoreClient extends Client { MessageUpdate: [], }; } + + AppLogger.LogVerbose("Client", "Registered Channel Update Event"); } public static RegisterGuildBanAddEvent(fn: (ban: GuildBan) => void) { @@ -175,6 +195,8 @@ export class CoreClient extends Client { MessageUpdate: [], }; } + + AppLogger.LogVerbose("Client", "Registered Guild Ban Add Event"); } public static RegisterGuildBanRemoveEvent(fn: (channel: GuildBan) => void) { @@ -196,6 +218,8 @@ export class CoreClient extends Client { MessageUpdate: [], }; } + + AppLogger.LogVerbose("Client", "Registered Guild Ban Remove Event"); } public static RegisterGuildCreateEvent(fn: (guild: Guild) => void) { @@ -217,6 +241,8 @@ export class CoreClient extends Client { MessageUpdate: [], }; } + + AppLogger.LogVerbose("Client", "Registered Guild Create Event"); } public static RegisterGuildMemberAddEvent(fn: (member: GuildMember) => void) { @@ -238,6 +264,8 @@ export class CoreClient extends Client { MessageUpdate: [], }; } + + AppLogger.LogVerbose("Client", "Registered Guild Member Add Event"); } public static RegisterGuildMemberRemoveEvent(fn: (member: GuildMember | PartialGuildMember) => void) { @@ -259,6 +287,8 @@ export class CoreClient extends Client { MessageUpdate: [], }; } + + AppLogger.LogVerbose("Client", "Registered Guild Member Remove Event"); } public static GuildMemebrUpdate(fn: (oldMember: GuildMember | PartialGuildMember, newMember: GuildMember) => void) { @@ -280,6 +310,8 @@ export class CoreClient extends Client { MessageUpdate: [], }; } + + AppLogger.LogVerbose("Client", "Registered Guild Member Update Event"); } public static RegisterMessageCreateEvent(fn: (message: Message) => void) { @@ -301,6 +333,8 @@ export class CoreClient extends Client { MessageUpdate: [], }; } + + AppLogger.LogVerbose("Client", "Registered Message Create Event"); } public static RegisterMessageDeleteEvent(fn: (message: Message | PartialMessage) => void) { @@ -322,6 +356,8 @@ export class CoreClient extends Client { MessageUpdate: [], }; } + + AppLogger.LogVerbose("Client", "Registered Message Delete Event"); } public static RegisterMessageUpdateEvent(fn: (oldMessage: Message | PartialMessage, newMessage: Message | PartialMessage) => void) { @@ -343,6 +379,8 @@ export class CoreClient extends Client { MessageUpdate: [ fn ], }; } + + AppLogger.LogVerbose("Client", "Registered Message Update Event"); } public static RegisterButtonEvent(buttonId: string, event: ButtonEvent, environment: Environment = Environment.All) { @@ -354,6 +392,8 @@ export class CoreClient extends Client { if ((environment & CoreClient.Environment) == CoreClient.Environment) { CoreClient._buttonEvents.push(item); + + AppLogger.LogVerbose("Client", `Registered Button Event: ${buttonId}`); } } } diff --git a/src/client/events.ts b/src/client/events.ts index db6cfc4..0b82cee 100644 --- a/src/client/events.ts +++ b/src/client/events.ts @@ -1,22 +1,25 @@ import { Interaction } from "discord.js"; import ChatInputCommand from "./interactionCreate/ChatInputCommand"; import Button from "./interactionCreate/Button"; +import AppLogger from "./appLogger"; export class Events { public async onInteractionCreate(interaction: Interaction) { if (!interaction.guildId) return; 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() { - console.log("Ready"); + AppLogger.LogInfo("Client", "Ready"); } } diff --git a/src/client/interactionCreate/Button.ts b/src/client/interactionCreate/Button.ts index fac2b78..b8abbfc 100644 --- a/src/client/interactionCreate/Button.ts +++ b/src/client/interactionCreate/Button.ts @@ -1,5 +1,6 @@ import { ButtonInteraction } from "discord.js"; import { CoreClient } from "../client"; +import AppLogger from "../appLogger"; export default class Button { public static async onButtonClicked(interaction: ButtonInteraction) { @@ -8,10 +9,21 @@ export default class Button { 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"); return; } - item.Event.execute(interaction); + 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"); + } } } \ No newline at end of file diff --git a/src/client/interactionCreate/ChatInputCommand.ts b/src/client/interactionCreate/ChatInputCommand.ts index 3890744..47f7b37 100644 --- a/src/client/interactionCreate/ChatInputCommand.ts +++ b/src/client/interactionCreate/ChatInputCommand.ts @@ -1,6 +1,7 @@ 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) { @@ -13,6 +14,8 @@ export default class ChatInputCommand { if (!itemForServer) { if (!item) { + AppLogger.LogVerbose("ChatInputCommand", `Command not found: ${interaction.commandName}`); + await interaction.reply("Command not found"); return; } @@ -22,6 +25,15 @@ export default class ChatInputCommand { itemToUse = itemForServer; } - itemToUse.Command.execute(interaction); + 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"); + } } } \ No newline at end of file diff --git a/src/client/util.ts b/src/client/util.ts index ddd84fc..47daa3d 100644 --- a/src/client/util.ts +++ b/src/client/util.ts @@ -1,6 +1,7 @@ import { Client, REST, Routes, SlashCommandBuilder } from "discord.js"; import EventExecutors from "../contracts/EventExecutors"; import { CoreClient } from "./client"; +import AppLogger from "./appLogger"; export class Util { public loadSlashCommands(client: Client) { @@ -29,6 +30,8 @@ export class Util { const rest = new REST({ version: "10" }).setToken(process.env.BOT_TOKEN!); + AppLogger.LogVerbose("Util", `REST PUT: ${globalCommandData.flatMap(x => x.name).join(", ")}`); + rest.put( Routes.applicationCommands(process.env.BOT_CLIENTID!), { @@ -49,6 +52,8 @@ export class Util { 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), { diff --git a/src/commands/drop.ts b/src/commands/drop.ts index e671e76..7a91042 100644 --- a/src/commands/drop.ts +++ b/src/commands/drop.ts @@ -7,6 +7,7 @@ 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"; export default class Drop extends Command { constructor() { @@ -24,6 +25,8 @@ export default class Drop extends Command { } 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; } @@ -31,6 +34,8 @@ export default class Drop extends Command { 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; } @@ -61,7 +66,7 @@ export default class Drop extends Command { CoreClient.ClaimId = claimId; } catch (e) { - console.error(e); + AppLogger.LogError("Commands/Drop", `Error sending next drop for card ${randomCard.card.id}: ${e}`); await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. (${randomCard.card.id})`); } diff --git a/src/commands/gdrivesync.ts b/src/commands/gdrivesync.ts index c8a393c..b02c873 100644 --- a/src/commands/gdrivesync.ts +++ b/src/commands/gdrivesync.ts @@ -4,6 +4,7 @@ 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() { @@ -32,19 +33,28 @@ export default class Gdrivesync extends Command { 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}\`\`\``); } } diff --git a/src/commands/give.ts b/src/commands/give.ts index ad360a9..3656dc5 100644 --- a/src/commands/give.ts +++ b/src/commands/give.ts @@ -4,6 +4,7 @@ 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"; export default class Give extends Command { constructor() { @@ -46,6 +47,8 @@ export default class Give extends Command { const cardNumber = interaction.options.get("cardnumber", true); const user = interaction.options.getUser("user", true); + AppLogger.LogSilly("Commands/Give", `Parameters: cardNumber=${cardNumber.value}, user=${user.id}`); + const card = CardDropHelperMetadata.GetCardByCardNumber(cardNumber.value!.toString()); if (!card) { @@ -53,16 +56,16 @@ export default class Give extends Command { return; } - let inventory = await Inventory.FetchOneByCardNumberAndUserId(user.id, card.id); + let inventory = await Inventory.FetchOneByCardNumberAndUserId(user.id, card.card.id); if (!inventory) { - inventory = new Inventory(user.id, card.id, 1); + inventory = new Inventory(user.id, card.card.id, 1); } else { inventory.SetQuantity(inventory.Quantity + 1); } await inventory.Save(Inventory, inventory); - await interaction.reply(`${card.name} given to ${interaction.user}, they now have ${inventory.Quantity}`); + await interaction.reply(`${card.card.name} given to ${user.username}, they now have ${inventory.Quantity}`); } } \ No newline at end of file diff --git a/src/commands/inventory.ts b/src/commands/inventory.ts index 868b14d..7d77764 100644 --- a/src/commands/inventory.ts +++ b/src/commands/inventory.ts @@ -1,6 +1,7 @@ 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() { @@ -23,6 +24,8 @@ export default class Inventory extends Command { const page = interaction.options.get("page"); const user = interaction.options.getUser("user") || interaction.user; + AppLogger.LogSilly("Commands/Inventory", `Parameters: page=${page?.value}, user=${user.id}`); + try { let pageNumber = 0; @@ -36,7 +39,9 @@ export default class Inventory extends Command { embeds: [ embed.embed ], components: [ embed.row ], }); - } catch { + } catch (e) { + AppLogger.LogError("Commands/Inventory", e as string); + await interaction.reply("No page for user found."); } } diff --git a/src/commands/resync.ts b/src/commands/resync.ts index 7040d52..f90c96c 100644 --- a/src/commands/resync.ts +++ b/src/commands/resync.ts @@ -2,6 +2,7 @@ import { CacheType, CommandInteraction, PermissionsBitField, SlashCommandBuilder 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() { @@ -23,10 +24,14 @@ export default class Resync extends 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."); @@ -34,6 +39,8 @@ export default class Resync extends Command { } 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."); } } diff --git a/src/commands/series.ts b/src/commands/series.ts new file mode 100644 index 0000000..2122355 --- /dev/null +++ b/src/commands/series.ts @@ -0,0 +1,71 @@ +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}`); + + 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.reply("Series not found."); + return; + } + + const embed = SeriesHelper.GenerateSeriesViewPage(series.id, 0); + + await interaction.reply({ embeds: [ embed!.embed ], components: [ embed!.row ]}); + } + + private async ListSeries(interaction: CommandInteraction) { + const embed = SeriesHelper.GenerateSeriesListPage(0); + + await interaction.reply({ embeds: [ embed!.embed ], components: [ embed!.row ]}); + } +} \ No newline at end of file diff --git a/src/commands/trade.ts b/src/commands/trade.ts new file mode 100644 index 0000000..60f9033 --- /dev/null +++ b/src/commands/trade.ts @@ -0,0 +1,148 @@ +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.getUser("user")!; + const give = interaction.options.get("give")!; + const receive = interaction.options.get("receive")!; + + AppLogger.LogSilly("Commands/Trade", `Parameters: user=${user.id}, give=${give.value}, receive=${receive.value}`); + + const giveItemEntity = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, give.value!.toString()); + const receiveItemEntity = await Inventory.FetchOneByCardNumberAndUserId(user.id, receive.value!.toString()); + + if (!giveItemEntity) { + await interaction.reply("You do not have the item you are trying to trade."); + return; + } + + if (!receiveItemEntity) { + await interaction.reply("The user you are trying to trade with does not have the item you are trying to trade for."); + return; + } + + const giveItem = CoreClient.Cards + .flatMap(x => x.cards) + .find(x => x.id === give.value!.toString()); + + const receiveItem = CoreClient.Cards + .flatMap(x => x.cards) + .find(x => x.id === receive.value!.toString()); + + if (!giveItem || !receiveItem) { + 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: "I Receive", + value: `${receiveItem.id}: ${receiveItem.name}`, + inline: true, + }, + { + name: "You Receive", + value: `${giveItem.id}: ${giveItem.name}`, + inline: true, + }, + { + name: "Expires", + value: new Date(expiry).toLocaleString(), + } + ]); + + const timeoutId = setTimeout(async () => this.autoDecline(interaction, interaction.user.username, user.username, giveItem.id, receiveItem.id, giveItem.name, receiveItem.name), 1000 * 60 * 15); // 15 minutes + + const row = new ActionRowBuilder() + .addComponents([ + new ButtonBuilder() + .setCustomId(`trade accept ${interaction.user.id} ${user.id} ${giveItem.id} ${receiveItem.id} ${expiry} ${timeoutId}`) + .setLabel("Accept") + .setStyle(ButtonStyle.Success), + new ButtonBuilder() + .setCustomId(`trade decline ${interaction.user.id} ${user.id} ${giveItem.id} ${receiveItem.id} ${expiry} ${timeoutId}`) + .setLabel("Decline") + .setStyle(ButtonStyle.Danger), + ]); + + await interaction.reply({ content: `${user}`, embeds: [ tradeEmbed ], components: [ row ] }); + } + + private async autoDecline(interaction: CommandInteraction, giveUsername: string, receiveUsername: string, giveCardNumber: string, receiveCardNumber: string, giveCardName: string, receiveCardName: string) { + AppLogger.LogSilly("Commands/Trade/AutoDecline", `Auto declining trade between ${giveUsername} and ${receiveUsername}`); + + const tradeEmbed = new EmbedBuilder() + .setTitle("Trade Expired") + .setDescription(`Trade initiated between ${receiveUsername} and ${giveUsername}`) + .setColor(EmbedColours.Error) + .setImage("https://media1.tenor.com/m/KkZwKl2AQ2QAAAAd/trade-offer.gif") + .addFields([ + { + name: "I Receive", + value: `${receiveCardNumber}: ${receiveCardName}`, + inline: true, + }, + { + name: "You Receive", + value: `${giveCardNumber}: ${giveCardName}`, + inline: true, + }, + { + name: "Expired", + value: new Date().toLocaleString(), + } + ]); + + const row = new ActionRowBuilder() + .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 ]}); + } +} \ No newline at end of file diff --git a/src/commands/view.ts b/src/commands/view.ts index 8f89eb4..ce6f9cb 100644 --- a/src/commands/view.ts +++ b/src/commands/view.ts @@ -5,6 +5,7 @@ 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() { @@ -23,6 +24,8 @@ export default class View extends Command { 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; @@ -46,6 +49,8 @@ export default class View extends Command { 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; } @@ -65,7 +70,7 @@ export default class View extends Command { files: [ attachment ], }); } catch (e) { - console.error(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}.`); diff --git a/src/constants/EmbedColours.ts b/src/constants/EmbedColours.ts index a54d56f..36777e3 100644 --- a/src/constants/EmbedColours.ts +++ b/src/constants/EmbedColours.ts @@ -1,5 +1,6 @@ export default class EmbedColours { public static readonly Ok = 0x3050ba; + public static readonly Success = 0x50c878; public static readonly Error = 0xff0000; public static readonly Grey = 0xd3d3d3; public static readonly BronzeCard = 0xcd7f32; diff --git a/src/database/entities/app/User.ts b/src/database/entities/app/User.ts new file mode 100644 index 0000000..c954aea --- /dev/null +++ b/src/database/entities/app/User.ts @@ -0,0 +1,19 @@ +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; + + public UpdateCurrency(currency: number) { + this.Currency = currency; + } +} \ No newline at end of file diff --git a/src/database/migrations/app/0.6/1713289062969-user.ts b/src/database/migrations/app/0.6/1713289062969-user.ts new file mode 100644 index 0000000..151db88 --- /dev/null +++ b/src/database/migrations/app/0.6/1713289062969-user.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; +import MigrationHelper from "../../../../helpers/MigrationHelper"; + +export class User1713289062969 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + MigrationHelper.Up("1713289062969-user", "0.6", [ + "01-table/User", + ], queryRunner); + } + + public async down(): Promise { + } + +} diff --git a/src/helpers/CardDropHelperMetadata.ts b/src/helpers/CardDropHelperMetadata.ts index 762648d..26a7731 100644 --- a/src/helpers/CardDropHelperMetadata.ts +++ b/src/helpers/CardDropHelperMetadata.ts @@ -1,8 +1,9 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js"; import { CardRarity, CardRarityToColour, CardRarityToString } from "../constants/CardRarity"; import CardRarityChances from "../constants/CardRarityChances"; -import { CardMetadata, DropResult } from "../contracts/SeriesMetadata"; +import { DropResult } from "../contracts/SeriesMetadata"; import { CoreClient } from "../client/client"; +import AppLogger from "../client/appLogger"; export default class CardDropHelperMetadata { public static GetRandomCard(): DropResult | undefined { @@ -23,10 +24,14 @@ export default class CardDropHelperMetadata { 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); @@ -38,28 +43,53 @@ export default class CardDropHelperMetadata { .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): CardMetadata | undefined { + 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); - return card; + 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): EmbedBuilder { + public static GenerateDropEmbed(drop: DropResult, quantityClaimed: number, imageFileName: string, claimedBy?: string): EmbedBuilder { + AppLogger.LogSilly("CardDropHelperMetadata/GenerateDropEmbed", `Parameters: drop=${drop.card.id}, quantityClaimed=${quantityClaimed}, imageFileName=${imageFileName}`); + let description = ""; description += `Series: ${drop.series.name}\n`; description += `Claimed: ${quantityClaimed}\n`; + if (claimedBy != null) { + description += `Claimed by: ${claimedBy}\n`; + } else { + description += "Claimed by: (UNCLAIMED)\n"; + } + return new EmbedBuilder() .setTitle(drop.card.name) .setDescription(description) @@ -68,13 +98,16 @@ export default class CardDropHelperMetadata { .setImage(`attachment://${imageFileName}`); } - public static GenerateDropButtons(drop: DropResult, claimId: string, userId: string): ActionRowBuilder { + public static GenerateDropButtons(drop: DropResult, claimId: string, userId: string, disabled: boolean = false): ActionRowBuilder { + AppLogger.LogSilly("CardDropHelperMetadata/GenerateDropButtons", `Parameters: drop=${drop.card.id}, claimId=${claimId}, userId=${userId}`); + return new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId(`claim ${drop.card.id} ${claimId} ${userId}`) .setLabel("Claim") - .setStyle(ButtonStyle.Primary), + .setStyle(ButtonStyle.Primary) + .setDisabled(disabled), new ButtonBuilder() .setCustomId("reroll") .setLabel("Reroll") diff --git a/src/helpers/InventoryHelper.ts b/src/helpers/InventoryHelper.ts index 1103694..3b0c40a 100644 --- a/src/helpers/InventoryHelper.ts +++ b/src/helpers/InventoryHelper.ts @@ -4,6 +4,7 @@ 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"; interface InventoryPage { id: number, @@ -21,6 +22,8 @@ interface InventoryPageCards { export default class InventoryHelper { public static async GenerateInventoryPage(username: string, userid: string, page: number): Promise<{ embed: EmbedBuilder, row: ActionRowBuilder }> { + AppLogger.LogSilly("Helpers/InventoryHelper", `Parameters: username=${username}, userid=${userid}, page=${page}`); + const cardsPerPage = 15; const inventory = await Inventory.FetchAllByUserId(userid); @@ -73,7 +76,7 @@ export default class InventoryHelper { const currentPage = pages[page]; if (!currentPage) { - console.error("Unable to find page"); + AppLogger.LogError("Helpers/InventoryHelper", "Unable to find page"); return Promise.reject("Unable to find page"); } diff --git a/src/helpers/SeriesHelper.ts b/src/helpers/SeriesHelper.ts new file mode 100644 index 0000000..32b1405 --- /dev/null +++ b/src/helpers/SeriesHelper.ts @@ -0,0 +1,99 @@ +import { ActionRowBuilder, 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"; + +export default class SeriesHelper { + public static GenerateSeriesViewPage(seriesId: number, page: number): { embed: EmbedBuilder, row: ActionRowBuilder } | null { + AppLogger.LogSilly("Helpers/SeriesHelper", `Parameters: seriesId=${seriesId}, page=${page}`); + + const itemsPerPage = 15; + + 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); + + 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).toUpperCase()}`) + .join("\n"); + + const embed = new EmbedBuilder() + .setTitle(series.name) + .setColor(EmbedColours.Ok) + .setDescription(description) + .setFooter({ text: `${series.id} · ${series.cards.length} cards · Page ${page + 1} of ${totalPages}` }); + + const row = new ActionRowBuilder() + .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)); + + return { embed, row }; + } + + public static GenerateSeriesListPage(page: number): { embed: EmbedBuilder, row: ActionRowBuilder } | 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}`) + .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() + .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 }; + } +} \ No newline at end of file diff --git a/src/hooks/ReloadDB.ts b/src/hooks/ReloadDB.ts index 0f1c026..9d99df7 100644 --- a/src/hooks/ReloadDB.ts +++ b/src/hooks/ReloadDB.ts @@ -1,8 +1,9 @@ import { Request, Response } from "express"; import CardMetadataFunction from "../Functions/CardMetadataFunction"; +import AppLogger from "../client/appLogger"; export default async function ReloadDB(req: Request, res: Response) { - console.log("Reloading Card DB..."); + AppLogger.LogInfo("Hooks/ReloadDB", "Reloading Card DB..."); await CardMetadataFunction.Execute(); diff --git a/src/registry.ts b/src/registry.ts index 2fc657d..bb68685 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -8,6 +8,8 @@ import Gdrivesync from "./commands/gdrivesync"; import Give from "./commands/give"; import Inventory from "./commands/inventory"; import Resync from "./commands/resync"; +import Series from "./commands/series"; +import Trade from "./commands/trade"; import View from "./commands/view"; // Test Command Imports @@ -18,6 +20,8 @@ import Droprarity from "./commands/stage/droprarity"; import Claim from "./buttonEvents/Claim"; import InventoryButtonEvent from "./buttonEvents/Inventory"; import Reroll from "./buttonEvents/Reroll"; +import SeriesEvent from "./buttonEvents/Series"; +import TradeButtonEvent from "./buttonEvents/Trade"; export default class Registry { public static RegisterCommands() { @@ -28,6 +32,8 @@ export default class Registry { CoreClient.RegisterCommand("give", new Give()); CoreClient.RegisterCommand("inventory", new Inventory()); CoreClient.RegisterCommand("resync", new Resync()); + CoreClient.RegisterCommand("series", new Series()); + CoreClient.RegisterCommand("trade", new Trade()); CoreClient.RegisterCommand("view", new View()); // Test Commands @@ -41,7 +47,9 @@ export default class Registry { public static RegisterButtonEvents() { CoreClient.RegisterButtonEvent("claim", new Claim()); - CoreClient.RegisterButtonEvent("inventory", new InventoryButtonEvent); + CoreClient.RegisterButtonEvent("inventory", new InventoryButtonEvent()); CoreClient.RegisterButtonEvent("reroll", new Reroll()); + CoreClient.RegisterButtonEvent("series", new SeriesEvent()); + CoreClient.RegisterButtonEvent("trade", new TradeButtonEvent()); } } \ No newline at end of file diff --git a/src/webhooks.ts b/src/webhooks.ts index cccb598..0b99816 100644 --- a/src/webhooks.ts +++ b/src/webhooks.ts @@ -1,6 +1,7 @@ import bodyParser from "body-parser"; import express, { Application } from "express"; import ReloadDB from "./hooks/ReloadDB"; +import AppLogger from "./client/appLogger"; export default class Webhooks { private app: Application; @@ -24,7 +25,7 @@ export default class Webhooks { private setupListen() { this.app.listen(this.port, () => { - console.log(`API listening on port ${this.port}`); + AppLogger.LogInfo("Webhooks", `API listening on port ${this.port}`); }); } } \ No newline at end of file