Compare commits
33 commits
develop
...
v3.0-beta1
Author | SHA1 | Date | |
---|---|---|---|
c8edd1b4c5 | |||
de236dfd30 | |||
97633451ed | |||
f61c4c728a | |||
2cc12d91be | |||
45d871fbf7 | |||
783c3a013d | |||
598a0b5a44 | |||
68b9ed34e4 | |||
ba51cbb28c | |||
44571d735a | |||
24818bcb44 | |||
6c90307754 | |||
acedbffdad | |||
c62488aa63 | |||
4ff88d0694 | |||
07c7155027 | |||
90ef4317cc | |||
35f7210b6e | |||
e7169d960a | |||
0d3134bf45 | |||
019966f25f | |||
be329d709f | |||
ecf9c5e4fc | |||
bb433749f8 | |||
c7417cf7a5 | |||
6fb2da2b18 | |||
19065dc3e6 | |||
ee7fe3fd19 | |||
5b9aac22d3 | |||
38a5f6fb29 | |||
|
021c495769 | ||
|
9854f3c60f |
162 changed files with 10427 additions and 7356 deletions
72
.drone.yml
72
.drone.yml
|
@ -1,72 +0,0 @@
|
||||||
---
|
|
||||||
|
|
||||||
kind: pipeline
|
|
||||||
name: deployment
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: deploy
|
|
||||||
image: appleboy/drone-ssh
|
|
||||||
settings:
|
|
||||||
host: 192.168.1.115
|
|
||||||
username: vylpes
|
|
||||||
password:
|
|
||||||
from_secret: ssh_password
|
|
||||||
port: 22
|
|
||||||
script:
|
|
||||||
- sh /home/vylpes/scripts/vylbot/deploy_prod.sh
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
event:
|
|
||||||
- tag
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
kind: pipeline
|
|
||||||
name: staging
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: stage
|
|
||||||
image: appleboy/drone-ssh
|
|
||||||
settings:
|
|
||||||
host: 192.168.1.115
|
|
||||||
username: vylpes
|
|
||||||
password:
|
|
||||||
from_secret: ssh_password
|
|
||||||
port: 22
|
|
||||||
script:
|
|
||||||
- sh /home/vylpes/scripts/vylbot/deploy_stage.sh
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
branch:
|
|
||||||
- develop
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
kind: pipeline
|
|
||||||
name: integration
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build
|
|
||||||
image: node
|
|
||||||
commands:
|
|
||||||
- yarn install --frozen-lockfile
|
|
||||||
- yarn build
|
|
||||||
|
|
||||||
# - name: test
|
|
||||||
# image: node
|
|
||||||
# commands:
|
|
||||||
# - yarn install --frozen-lockfile
|
|
||||||
# - yarn test
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
branch:
|
|
||||||
- main
|
|
||||||
- develop
|
|
||||||
- hotfix/*
|
|
||||||
- feature/*
|
|
||||||
- renovate/*
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
- pull_request
|
|
28
.env.example
28
.env.example
|
@ -1,28 +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=3.3.0
|
|
||||||
BOT_AUTHOR=Vylpes
|
|
||||||
BOT_OWNERID=147392775707426816
|
|
||||||
BOT_CLIENTID=682942374040961060
|
|
||||||
|
|
||||||
ABOUT_FUNDING=https://ko-fi.com/vylpes
|
|
||||||
ABOUT_REPO=https://gitea.vylpes.xyz/RabbitLabs/vylbot-app
|
|
||||||
|
|
||||||
CACHE_INTERVAL=1800000 # 30 minutes
|
|
||||||
|
|
||||||
DB_HOST=127.0.0.1
|
|
||||||
DB_PORT=3101
|
|
||||||
DB_NAME=vylbot
|
|
||||||
DB_AUTH_USER=dev
|
|
||||||
DB_AUTH_PASS=dev
|
|
||||||
DB_SYNC=true
|
|
||||||
DB_LOGGING=true
|
|
||||||
DB_ROOT_HOST=0.0.0.0
|
|
||||||
DB_DATA_LOCATION=./.temp/database
|
|
34
.env.template
Normal file
34
.env.template
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# 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_PREFIX=v!
|
||||||
|
BOT_VER=3.0
|
||||||
|
BOT_AUTHOR=Vylpes
|
||||||
|
BOT_DATE=28 Nov 2021
|
||||||
|
BOT_OWNERID=147392775707426816
|
||||||
|
|
||||||
|
FOLDERS_COMMANDS=src/commands
|
||||||
|
FOLDERS_EVENTS=src/events
|
||||||
|
|
||||||
|
COMMANDS_DISABLED=
|
||||||
|
COMMANDS_DISABLED_MESSAGE=This command is disabled.
|
||||||
|
|
||||||
|
COMMANDS_ROLE_ROLES=Notify,VotePings,ProjectUpdates
|
||||||
|
|
||||||
|
COMMANDS_RULES_FILE=data/rules/rules.json
|
||||||
|
|
||||||
|
EMBED_COLOUR=0x3050ba
|
||||||
|
EMBED_COLOUR_ERROR=0xD52803
|
||||||
|
|
||||||
|
ROLES_MODERATOR=Moderator
|
||||||
|
ROLES_MUTED=Muted
|
||||||
|
|
||||||
|
CHANNELS_LOGS_MESSAGE=message-logs
|
||||||
|
CHANNELS_LOGS_MEMBER=member-logs
|
||||||
|
CHANNELS_LOGS_MOD=mod-logs
|
|
@ -1,67 +0,0 @@
|
||||||
name: Deploy To Production
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
environment: prod
|
|
||||||
|
|
||||||
runs-on: node
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Use Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 20.x
|
|
||||||
- run: yarn install --frozen-lockfile
|
|
||||||
- run: yarn build
|
|
||||||
- run: yarn 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.1.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 }}
|
|
||||||
ABOUT_FUNDING: ${{ vars.PROD_ABOUT_FUNDING }}
|
|
||||||
ABOUT_REPO: ${{ vars.PROD_ABOUT_REPO }}
|
|
||||||
CACHE_INTERVAL: ${{ vars.PROD_CACHE_INTERVAL }}
|
|
||||||
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,CACHE_INTERVAL
|
|
||||||
script: |
|
|
||||||
source .sshrc \
|
|
||||||
&& cd /home/vylpes/apps/vylbot/vylbot_prod \
|
|
||||||
&& docker compose down \
|
|
||||||
&& (pm2 stop vylbot_prod || true) \
|
|
||||||
&& (pm2 delete vylbot_prod || true) \
|
|
||||||
&& docker compose up -d \
|
|
||||||
&& sleep 10 \
|
|
||||||
&& yarn db:up \
|
|
||||||
&& pm2 start --name vylbot_prod dist/vylbot.js
|
|
|
@ -1,67 +0,0 @@
|
||||||
name: Deploy To Stage
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- develop
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
environment: prod
|
|
||||||
|
|
||||||
runs-on: node
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Use Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 20.x
|
|
||||||
- run: yarn install --frozen-lockfile
|
|
||||||
- run: yarn build
|
|
||||||
- run: yarn 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.1.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 }}
|
|
||||||
ABOUT_FUNDING: ${{ vars.STAGE_ABOUT_FUNDING }}
|
|
||||||
ABOUT_REPO: ${{ vars.STAGE_ABOUT_REPO }}
|
|
||||||
CACHE_INTERVAL: ${{ vars.STAGE_CACHE_INTERVAL }}
|
|
||||||
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,CACHE_INTERVAL
|
|
||||||
script: |
|
|
||||||
source .sshrc \
|
|
||||||
&& cd /home/vylpes/apps/vylbot/vylbot_stage \
|
|
||||||
&& docker compose down \
|
|
||||||
&& (pm2 stop vylbot_stage || true) \
|
|
||||||
&& (pm2 delete vylbot_stage || true) \
|
|
||||||
&& docker compose up -d \
|
|
||||||
&& sleep 10 \
|
|
||||||
&& yarn db:up \
|
|
||||||
&& pm2 start --name vylbot_stage dist/vylbot.js
|
|
|
@ -1,24 +0,0 @@
|
||||||
name: Test
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- feature/*
|
|
||||||
- hotfix/*
|
|
||||||
- renovate/*
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
environment: stage
|
|
||||||
|
|
||||||
runs-on: node
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Use Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 20.x
|
|
||||||
- run: yarn install --frozen-lockfile
|
|
||||||
- run: yarn build
|
|
||||||
- run: yarn test
|
|
|
@ -1,18 +0,0 @@
|
||||||
Epic: \
|
|
||||||
Story Points:
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*No description*
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
*No acceptance criteria*
|
|
||||||
|
|
||||||
## Subtasks
|
|
||||||
|
|
||||||
*No subtasks*
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
*No notes*
|
|
27
.github/workflows/testing.yml
vendored
Normal file
27
.github/workflows/testing.yml
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
name: Testing
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [12.x, 14.x, 16.x]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- run: yarn install
|
||||||
|
- run: yarn build
|
||||||
|
- run: yarn test --coverage
|
||||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -104,6 +104,4 @@ dist
|
||||||
.tern-port
|
.tern-port
|
||||||
|
|
||||||
config.json
|
config.json
|
||||||
.DS_Store
|
.DS_Store
|
||||||
ormconfig.json
|
|
||||||
.temp/
|
|
0
.gitlab/.gitkeep
Normal file
0
.gitlab/.gitkeep
Normal file
0
.gitlab/merge_request_templates/.gitkeep
Normal file
0
.gitlab/merge_request_templates/.gitkeep
Normal file
|
@ -8,14 +8,14 @@ Fixes # (issue)
|
||||||
|
|
||||||
Please delete options that are not relevant.
|
Please delete options that are not relevant.
|
||||||
|
|
||||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
- Bug fix (non-breaking change which fixes an issue)
|
||||||
- [ ] New feature (non-breaking change which adds functionality)
|
- New feature (non-breaking change which adds functionality)
|
||||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
- Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||||
- [ ] This change requires a documentation update
|
- This change requires a documentation update
|
||||||
|
|
||||||
# How Has This Been Tested?
|
# How Has This Been Tested?
|
||||||
|
|
||||||
Please describe the tests that you ran to verify the changes. Provide instructions so we can reproduce. Please also list any relevant details to your test configuration.
|
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
|
||||||
|
|
||||||
# Checklist
|
# Checklist
|
||||||
|
|
||||||
|
@ -24,6 +24,6 @@ Please describe the tests that you ran to verify the changes. Provide instructio
|
||||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||||
- [ ] I have made corresponding changes to the documentation
|
- [ ] I have made corresponding changes to the documentation
|
||||||
- [ ] My changes generate no new warnings
|
- [ ] My changes generate no new warnings
|
||||||
- [ ] I have added tests that provide my fix is effective or that my feature works
|
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||||
- [ ] New and existing unit tests pass locally with my changes
|
- [ ] New and existing unit tests pass locally with my changes
|
||||||
- [ ] Any dependent changes have been merged and published in downstream modules
|
- [ ] Any dependant changes have been merged and published in downstream modules
|
15
Dockerfile
Normal file
15
Dockerfile
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# Create app and work directory
|
||||||
|
FROM node:16
|
||||||
|
WORKDIR /vylbot
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
COPY package.json .
|
||||||
|
COPY yarn.lock .
|
||||||
|
RUN yarn install
|
||||||
|
|
||||||
|
# Bundle app source
|
||||||
|
COPY . .
|
||||||
|
RUN yarn build
|
||||||
|
|
||||||
|
# Run the app source
|
||||||
|
CMD [ "yarn", "start" ]
|
21
LICENSE
21
LICENSE
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2023 Vylpes
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
50
README.md
50
README.md
|
@ -1,6 +1,6 @@
|
||||||
# VylBot App
|
# VylBot App
|
||||||
|
|
||||||
Discord bot for Vylpes' Den Discord Server.
|
Discord bot for Vylpes' Den Discord Server. Based on [VylBot Core](https://github.com/getgravitysoft/vylbot-core).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -8,48 +8,16 @@ Download the latest version from the [releases page](https://github.com/Vylpes/v
|
||||||
|
|
||||||
Copy the config template file and fill in the strings.
|
Copy the config template file and fill in the strings.
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
- NodeJS v16
|
|
||||||
- Yarn
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Install the dependencies and build the app:
|
Implement the client using something like:
|
||||||
|
|
||||||
```bash
|
```js
|
||||||
yarn install
|
const vylbot = require('vylbot-core');
|
||||||
yarn build
|
const config = require('./config.json');
|
||||||
|
|
||||||
|
const client = new vylbot.client(config);
|
||||||
|
client.start();
|
||||||
```
|
```
|
||||||
|
|
||||||
Setup the database (Recommended to use the docker-compose file)
|
See the `docs` folder for more information on how to use vylbot-core
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Copy and edit the settings files
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp .env.template .env
|
|
||||||
# Edit the .env file
|
|
||||||
|
|
||||||
cp ormconfig.json.template ormconfig.json
|
|
||||||
# Edit the ormconfig.json file
|
|
||||||
```
|
|
||||||
|
|
||||||
> **NOTE:** Make sure you do *not* check in these files! These contain sensitive information and should be treated as private.
|
|
||||||
|
|
||||||
Start the bot
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yarn start
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, you can start the bot in development mode using:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yarn start --dev
|
|
||||||
```
|
|
||||||
|
|
||||||
> Dev mode ensures that the default prefix is different to the production mode, in case you have both running in the same server.
|
|
|
@ -1,12 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"title": "Welcome to Mankalor's Discord Server!",
|
|
||||||
"description": [
|
|
||||||
"*You must follow Discord's TOS, including the rule where", "you must be 13 years or older.",
|
|
||||||
"If moderators know you're under 13, we will have to ban you!*",
|
|
||||||
"",
|
|
||||||
"You need to input a code in *#entry* which is somewhere in this message before you can start chatting, so read the server rules and info below.",
|
|
||||||
"If you still don't see the other channels after writing this code, message a moderator. For any issues with this bot, message <@147392775707426816>."
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,70 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"title": "Welcome to Mankalor's Discord Server!",
|
|
||||||
"description": [
|
|
||||||
"*You must follow Discord's TOS, including the rule where", "you must be 13 years or older.",
|
|
||||||
"If moderators know you're under 13, we will have to ban you!*",
|
|
||||||
"",
|
|
||||||
"You need to input a code in *#entry* which is somewhere in this message before you can start chatting, so read the server rules and info below.",
|
|
||||||
"If you still don't see the other channels after writing this code, message a moderator. For any issues with this bot, message <@147392775707426816>."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Server Rules",
|
|
||||||
"description": [
|
|
||||||
"1. We allow most things in *#general-off-topic*, but if it pertains to a topic that has a channel, post it in the correct chat.",
|
|
||||||
"",
|
|
||||||
"2. No spamming, except in *#bot-craziness* and *#spam* ",
|
|
||||||
" 2a. Those 'Hacker Warning!!! Copy Paste this to all servers!' messages and other copypastas are considered spam.",
|
|
||||||
"",
|
|
||||||
"3. Do not insult or harass anyone for race, religion, gender, gaming skills, social skills, etc.",
|
|
||||||
" 3a. Some people may be new to Discord or a game. Politely educate, don't belittle.",
|
|
||||||
"",
|
|
||||||
"4. Absolutely no NSFW content on this server. (Porn, Rule 34, etc.)",
|
|
||||||
"",
|
|
||||||
"5. Swearing is allowed, but certain words such as racial/homophobic/disability slurs or sex terms will still be filtered out. Bypassing this will result in punishment.",
|
|
||||||
" 5a. Swearing past the point of typical rager is still not allowed.",
|
|
||||||
" 5b. If you're unsure if a word is allowed, then don't use it.",
|
|
||||||
"",
|
|
||||||
"6. Avoid unnecessarily & excessively @ mentioning anyone, even in *#bot-craziness* and *#spam*",
|
|
||||||
"",
|
|
||||||
"7. Advertising your own content (videos, channels, servers, etc.) should only go in *#self-promo*",
|
|
||||||
"",
|
|
||||||
"8. Do not bring up drama from other places here, keep that to DMs.",
|
|
||||||
"",
|
|
||||||
"9. Keep it serious in the venting chats, don't joke around.",
|
|
||||||
" 9a. To access the vent channels, assign yourself the role from *#self-assign-roles*",
|
|
||||||
"",
|
|
||||||
"10. Do not ask to become a moderator.",
|
|
||||||
"",
|
|
||||||
"11. Please don't join to ask about how to download hacks, where to find them, etc. Requests will be ignored and if you continue to ask you will be muted.",
|
|
||||||
" 11a. Watch Mankalor's video on it here: https://www.youtube.com/watch?v=wps_4DBlEyM"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": [
|
|
||||||
"1. You can assign yourself a game role in *#self-assign-roles*. When you want to setup a lobby type m!lobby into the game's channel.",
|
|
||||||
" 1a. Do not ping a role excessively in a short time. The command's cooldown is 20 minutes.",
|
|
||||||
" 1b. Only use the ping to set up lobbies. Not for advertising, pointless announcements, etc.",
|
|
||||||
" 1c. Only give yourself a role if you don't mind Discord pings.",
|
|
||||||
" 1d. Do not complain about the pings if they're being used correctly. Remove the role, or you will be punished by moderators.",
|
|
||||||
"",
|
|
||||||
"2. Only server staff can use @ everyone & @ here.",
|
|
||||||
"3. If you are a Youtube Sponsor or Twitch Subscriber, you can get the role if you sync your Twitch/Youtube account with your Discord account by going into User Settings > Connections > Twitch/Youtube.",
|
|
||||||
" 3a. This might not work on mobile.",
|
|
||||||
" 3b. We cannot assign SponSub roles manually.",
|
|
||||||
"",
|
|
||||||
"4. Mankalor will ping @ notificationsquad for new videos and streams in #new-videos-streams. If you want these pings, type `m!role Notification Squad` in #self-assign-roles",
|
|
||||||
"",
|
|
||||||
"5. Server link in case you want to invite someone. This link is in the description of my videos, too: https://discord.gg/DQkWVbz",
|
|
||||||
"",
|
|
||||||
"Not following these rules will result in a warning, mute, or ban, depending on the severity and number of offenses.",
|
|
||||||
"",
|
|
||||||
"If you notice anything wrong, notify the *Server Staff*!",
|
|
||||||
"",
|
|
||||||
"Once you've sent the code, go say hi in *#general-off-topic*!",
|
|
||||||
"",
|
|
||||||
"**Update 01 Oct 2021:** Added `11.` and `11a.` to rules"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -6,7 +6,7 @@
|
||||||
"title": "Vylpes' Den",
|
"title": "Vylpes' Den",
|
||||||
"description": [
|
"description": [
|
||||||
"Welcome to Vylpes' Den! Make sure to say hi!",
|
"Welcome to Vylpes' Den! Make sure to say hi!",
|
||||||
"Invite link: https://go.vylpes.xyz/A6HcA"
|
"Invite link: https://discord.gg/UyAhAVp"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -70,17 +70,19 @@
|
||||||
"description": [
|
"description": [
|
||||||
"This server uses a bot made by me, VylBot, to help moderate the server.",
|
"This server uses a bot made by me, VylBot, to help moderate the server.",
|
||||||
"For more information on it, see the GitHub repositories:",
|
"For more information on it, see the GitHub repositories:",
|
||||||
"https://gitea.vylpes.xyz/rabbitlabs/vylbot-app"
|
"https://github.com/Vylpes/vylbot-core",
|
||||||
|
"https://github.com/Vylpes/vylbot-app"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Links",
|
"title": "Links",
|
||||||
"description": [
|
"description": [
|
||||||
"YouTube: https://www.youtube.com/@vylpes",
|
"YouTube: https://www.youtube.com/channel/UCwPlzKwCmP5Q9bCX3fHk2BA",
|
||||||
"Patreon: https://www.patreon.com/vylpes",
|
"Patreon: https://www.patreon.com/vylpes",
|
||||||
"Twitch: https://www.twitch.tv/vylpes_",
|
"Twitch: https://www.twitch.tv/vylpes_",
|
||||||
"Twitter: https://twitter.com/vylpes"
|
"Twitter: https://twitter.com/vylpes",
|
||||||
|
"Blog: https://vylpes.xyz"
|
||||||
],
|
],
|
||||||
"footer": "Last updated 20/06/2023"
|
"footer": "Last updated 01/02/2022"
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -1,35 +0,0 @@
|
||||||
USAGE: <key> <set|reset> [value]
|
|
||||||
|
|
||||||
===[ KEYS ]===
|
|
||||||
bot.prefix: The bot prefix for the server (Default: "v!")
|
|
||||||
|
|
||||||
commands.disabled: Disabled commands, separated by commas (Default: "")
|
|
||||||
|
|
||||||
role.moderator: The moderator role name (Default: "Moderator")
|
|
||||||
role.administrator: The administrator role name (Default: "Administrator")
|
|
||||||
|
|
||||||
rules.file: The location of the rules file (Default: "data/rules/rules")
|
|
||||||
|
|
||||||
channels.logs.message: The channel message events will be logged to (Default: "message-logs")
|
|
||||||
channels.logs.member: The channel member events will be logged to (Default: "member-logs")
|
|
||||||
channels.logs.mod: The channel mod events will be logged to (Default: "mod-logs")
|
|
||||||
|
|
||||||
verification.enabled: Enables/Disables the verification feature (Default: "false")
|
|
||||||
verification.channel: The channel to listen to for entry codes (Default: "entry")
|
|
||||||
verification.role: The server access role (Default: "Entry")
|
|
||||||
verification.code: The entry code for the channel (Default: "")
|
|
||||||
|
|
||||||
event.message.delete.enabled: Enables/Disables the message delete log event (Default: "false")
|
|
||||||
event.message.delete.channel: Sets the channel the bot will log message delete events to (Default: "message-logs")
|
|
||||||
|
|
||||||
event.message.update.enabled: Enables/Disables the message delete log event (Default: "false")
|
|
||||||
event.message.update.channel: Sets the channel the bot will log message delete events to (Default: "message-logs")
|
|
||||||
|
|
||||||
event.member.add.enabled: Enables/Disables the member join log event (Default: "false")
|
|
||||||
event.member.add.channel: Sets the channel the bot will log member join events to (Default: "member-logs")
|
|
||||||
|
|
||||||
event.member.remove.enabled: Enables/Disables the member leave log event (Default: "false")
|
|
||||||
event.member.remove.channel: Sets the channel the bot will log member leave events to (Default: "member-logs")
|
|
||||||
|
|
||||||
event.member.update.enabled: Enables/Disables the member update log event (Default: "false")
|
|
||||||
event.member.update.channel: Sets the channel the bot will log member update events to (Default: "member-logs")
|
|
|
@ -1,8 +0,0 @@
|
||||||
USAGE: config <add|remove> <Channel ID> <Role ID> [cooldown] [Game Name]
|
|
||||||
|
|
||||||
===[ EXAMPLE ]===
|
|
||||||
To add a channel:
|
|
||||||
- config add 000000000000000000 000000000000000000 30 Game Name
|
|
||||||
|
|
||||||
To remove a channel:
|
|
||||||
- config remove 000000000000000000
|
|
|
@ -1,8 +0,0 @@
|
||||||
USAGE: config <add|remove> <Role ID>
|
|
||||||
|
|
||||||
===[ EXAMPLE ]===
|
|
||||||
To add a role:
|
|
||||||
- config add 000000000000000000
|
|
||||||
|
|
||||||
To remove a role:
|
|
||||||
- config remove 000000000000000000
|
|
|
@ -1,11 +0,0 @@
|
||||||
CREATE TABLE `audit` (
|
|
||||||
`Id` varchar(255) NOT NULL,
|
|
||||||
`WhenCreated` datetime NOT NULL,
|
|
||||||
`WhenUpdated` datetime NOT NULL,
|
|
||||||
`AuditId` varchar(255) NOT NULL,
|
|
||||||
`UserId` varchar(255) NOT NULL,
|
|
||||||
`AuditType` int NOT NULL,
|
|
||||||
`Reason` varchar(255) NOT NULL,
|
|
||||||
`ModeratorId` varchar(255) NOT NULL,
|
|
||||||
`ServerId` varchar(255) NOT NULL
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
|
@ -1,5 +0,0 @@
|
||||||
CREATE TABLE `ignored_channel` (
|
|
||||||
`Id` varchar(255) NOT NULL,
|
|
||||||
`WhenCreated` datetime NOT NULL,
|
|
||||||
`WhenUpdated` datetime NOT NULL
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
|
@ -1,10 +0,0 @@
|
||||||
CREATE TABLE `lobby` (
|
|
||||||
`Id` varchar(255) NOT NULL,
|
|
||||||
`WhenCreated` datetime NOT NULL,
|
|
||||||
`WhenUpdated` datetime NOT NULL,
|
|
||||||
`ChannelId` varchar(255) NOT NULL,
|
|
||||||
`RoleId` varchar(255) NOT NULL,
|
|
||||||
`Cooldown` int NOT NULL,
|
|
||||||
`LastUsed` datetime NOT NULL,
|
|
||||||
`Name` varchar(255) NOT NULL
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
|
@ -1,7 +0,0 @@
|
||||||
CREATE TABLE `role` (
|
|
||||||
`Id` varchar(255) NOT NULL,
|
|
||||||
`WhenCreated` datetime NOT NULL,
|
|
||||||
`WhenUpdated` datetime NOT NULL,
|
|
||||||
`RoleId` varchar(255) NOT NULL,
|
|
||||||
`serverId` varchar(255) DEFAULT NULL
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
|
@ -1,5 +0,0 @@
|
||||||
CREATE TABLE `server` (
|
|
||||||
`Id` varchar(255) NOT NULL,
|
|
||||||
`WhenCreated` datetime NOT NULL,
|
|
||||||
`WhenUpdated` datetime NOT NULL
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
|
@ -1,8 +0,0 @@
|
||||||
CREATE TABLE `setting` (
|
|
||||||
`Id` varchar(255) NOT NULL,
|
|
||||||
`WhenCreated` datetime NOT NULL,
|
|
||||||
`WhenUpdated` datetime NOT NULL,
|
|
||||||
`Key` varchar(255) NOT NULL,
|
|
||||||
`Value` varchar(255) NOT NULL,
|
|
||||||
`serverId` varchar(255) DEFAULT NULL
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
|
@ -1,2 +0,0 @@
|
||||||
ALTER TABLE `audit`
|
|
||||||
ADD PRIMARY KEY (`Id`);
|
|
|
@ -1,2 +0,0 @@
|
||||||
ALTER TABLE `ignored_channel`
|
|
||||||
ADD PRIMARY KEY (`Id`);
|
|
|
@ -1,2 +0,0 @@
|
||||||
ALTER TABLE `lobby`
|
|
||||||
ADD PRIMARY KEY (`Id`);
|
|
|
@ -1,3 +0,0 @@
|
||||||
ALTER TABLE `role`
|
|
||||||
ADD PRIMARY KEY (`Id`),
|
|
||||||
ADD KEY `FK_d9e438d88cfb64f7f8e1ae593c3` (`serverId`);
|
|
|
@ -1,2 +0,0 @@
|
||||||
ALTER TABLE `server`
|
|
||||||
ADD PRIMARY KEY (`Id`);
|
|
|
@ -1,3 +0,0 @@
|
||||||
ALTER TABLE `setting`
|
|
||||||
ADD PRIMARY KEY (`Id`),
|
|
||||||
ADD KEY `FK_a3623ec541bdb12fa0f58bdfde7` (`serverId`);
|
|
|
@ -1,2 +0,0 @@
|
||||||
ALTER TABLE `role`
|
|
||||||
ADD CONSTRAINT `FK_d9e438d88cfb64f7f8e1ae593c3` FOREIGN KEY (`serverId`) REFERENCES `server` (`Id`);
|
|
|
@ -1,2 +0,0 @@
|
||||||
ALTER TABLE `setting`
|
|
||||||
ADD CONSTRAINT `FK_a3623ec541bdb12fa0f58bdfde7` FOREIGN KEY (`serverId`) REFERENCES `server` (`Id`);
|
|
|
@ -1,2 +0,0 @@
|
||||||
ALTER TABLE server
|
|
||||||
ADD LastCached datetime NOT NULL DEFAULT '2024-03-01 18:10:04';
|
|
|
@ -1,10 +0,0 @@
|
||||||
CREATE TABLE `moon` (
|
|
||||||
`Id` varchar(255) NOT NULL,
|
|
||||||
`WhenCreated` datetime NOT NULL,
|
|
||||||
`WhenUpdated` datetime NOT NULL,
|
|
||||||
`MoonNumber` int NOT NULL,
|
|
||||||
`UserId` varchar(255) NOT NULL,
|
|
||||||
`Description` varchar(255) NOT NULL,
|
|
||||||
`WhenArchived` datetime NULL
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
||||||
|
|
|
@ -1,17 +1,4 @@
|
||||||
version: "3.9"
|
version: "3.9"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
database:
|
discord:
|
||||||
image: mysql/mysql-server
|
build: .
|
||||||
command: --default-authentication-plugin=mysql_native_password
|
|
||||||
restart: always
|
|
||||||
environment:
|
|
||||||
- MYSQL_DATABASE=$DB_NAME
|
|
||||||
- MYSQL_USER=$DB_AUTH_USER
|
|
||||||
- MYSQL_PASSWORD=$DB_AUTH_PASS
|
|
||||||
- MYSQL_ROOT_PASSWORD=$DB_AUTH_PASS
|
|
||||||
- MYSQL_ROOT_HOST=$DB_ROOT_HOST
|
|
||||||
ports:
|
|
||||||
- "$DB_PORT:3306"
|
|
||||||
volumes:
|
|
||||||
- $DB_DATA_LOCATION:/var/lib/mysql
|
|
|
@ -1,31 +0,0 @@
|
||||||
# Registry
|
|
||||||
|
|
||||||
The registry file is what is used to register the bot's commands and events. This is a script which is ran at startup and adds all the commands and events to the bot.
|
|
||||||
|
|
||||||
Although you can register these outside of the registry file, this script makes it a centralised place for it to be done at.
|
|
||||||
|
|
||||||
## Adding Commands
|
|
||||||
|
|
||||||
Commands are added in the `RegisterCommands` function.
|
|
||||||
|
|
||||||
The basic syntax is as follows:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
client.RegisterCommand("Name", new Command(), "ServerId");
|
|
||||||
```
|
|
||||||
|
|
||||||
- `"Name"`: The name of the command, will be used by the user to call the command
|
|
||||||
- `new Command()`: The command class to be executed, must inherit the Command class
|
|
||||||
- `"ServerId"` (Optional): If given, will only be usable in that specific server
|
|
||||||
|
|
||||||
## Adding Events
|
|
||||||
|
|
||||||
Events are added in the `RegisterEvents` function.
|
|
||||||
|
|
||||||
The basic syntax is as follows:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
client.RegisterEvent(new Events());
|
|
||||||
```
|
|
||||||
|
|
||||||
- `new Events()`: The event class to be executed
|
|
6
jest.config.js
Normal file
6
jest.config.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */
|
||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
setupFiles: ["./jest.setup.js"]
|
||||||
|
};
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"preset": "ts-jest",
|
|
||||||
"testEnvironment": "node",
|
|
||||||
"setupFiles": ["./jest.setup.js"]
|
|
||||||
}
|
|
45
package.json
45
package.json
|
@ -1,51 +1,34 @@
|
||||||
{
|
{
|
||||||
"name": "vylbot-app",
|
"name": "vylbot-app",
|
||||||
"version": "3.2.3",
|
"version": "3.0",
|
||||||
"description": "A discord bot made for Vylpes' Den",
|
"description": "A discord bot made for Vylpes' Den",
|
||||||
"main": "./dist/vylbot",
|
"main": "./dist/vylbot",
|
||||||
"typings": "./dist",
|
"typings": "./dist",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm -rf node_modules/ dist/",
|
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"start": "node ./dist/vylbot",
|
"start": "node ./dist/vylbot",
|
||||||
"test": "jest . --passWithNoTests",
|
"test": "jest"
|
||||||
"db:up": "typeorm migration:run -d dist/database/dataSources/appDataSource.js",
|
|
||||||
"db:down": "typeorm migration:revert -d dist/database/dataSources/appDataSource.js",
|
|
||||||
"db:create": "typeorm migration:create ./src/database/migrations",
|
|
||||||
"release": "np --no-publish"
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/Vylpes/vylbot-app"
|
"url": "git+https://github.com/Vylpes/vylbot-app.git"
|
||||||
},
|
},
|
||||||
"author": "Vylpes",
|
"author": "Vylpes",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": "https://github.com/Vylpes/vylbot-app/issues",
|
||||||
"url": "https://github.com/Vylpes/vylbot-app/issues",
|
|
||||||
"email": "helpdesk@vylpes.com"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/Vylpes/vylbot-app",
|
"homepage": "https://github.com/Vylpes/vylbot-app",
|
||||||
"funding": "https://ko-fi.com/vylpes",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/rest": "^2.0.0",
|
"@types/jest": "^27.0.3",
|
||||||
"@types/uuid": "^10.0.0",
|
"discord.js": "12.5.3",
|
||||||
"discord.js": "^14.3.0",
|
"dotenv": "^10.0.0",
|
||||||
"dotenv": "^16.0.0",
|
"emoji-regex": "^9.2.0",
|
||||||
"emoji-regex": "^10.0.0",
|
"jest": "^27.4.5",
|
||||||
"minimatch": "10.0.1",
|
"jest-mock-extended": "^2.0.4",
|
||||||
"mysql": "^2.18.1",
|
"random-bunny": "^2.0.0",
|
||||||
"random-bunny": "^2.1.6",
|
"ts-jest": "^27.1.2"
|
||||||
"typeorm": "^0.3.20"
|
|
||||||
},
|
|
||||||
"resolutions": {
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.5.12",
|
"@types/node": "^16.11.10",
|
||||||
"@types/node": "^22.0.0",
|
"typescript": "^4.5.2"
|
||||||
"jest": "^29.7.0",
|
|
||||||
"jest-mock-extended": "^3.0.7",
|
|
||||||
"np": "^10.0.0",
|
|
||||||
"ts-jest": "^29.2.4",
|
|
||||||
"typescript": "^5.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
||||||
"baseBranches": ["develop"]
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
#! /bin/bash
|
|
||||||
|
|
||||||
export PATH="$HOME/.yarn/bin:$PATH"
|
|
||||||
export PATH="$HOME/.nodeuse/bin:$PATH"
|
|
||||||
|
|
||||||
export BOT_TOKEN=$(cat $HOME/scripts/vylbot/prod_key.txt)
|
|
||||||
|
|
||||||
cd ~/apps/vylbot/vylbot_prod \
|
|
||||||
&& git checkout main \
|
|
||||||
&& git fetch \
|
|
||||||
&& git pull \
|
|
||||||
&& docker compose --file docker-compose.prod.yml down \
|
|
||||||
&& (pm2 stop vylbot_prod || true) \
|
|
||||||
&& (pm2 delete vylbot_prod || true) \
|
|
||||||
&& cp .prod.env .env \
|
|
||||||
&& yarn clean \
|
|
||||||
&& yarn install --frozen-lockfile \
|
|
||||||
&& yarn build \
|
|
||||||
&& docker compose --file docker-compose.prod.yml up -d \
|
|
||||||
&& echo "Sleeping for 10 seconds to let database load..." \
|
|
||||||
&& sleep 10 \
|
|
||||||
&& yarn run db:up \
|
|
||||||
&& NODE_ENV=production pm2 start --name vylbot_prod dist/vylbot.js
|
|
|
@ -1,23 +0,0 @@
|
||||||
#! /bin/bash
|
|
||||||
|
|
||||||
export PATH="$HOME/.yarn/bin:$PATH"
|
|
||||||
export PATH="$HOME/.nodeuse/bin:$PATH"
|
|
||||||
|
|
||||||
export BOT_TOKEN=$(cat $HOME/scripts/vylbot/stage_key.txt)
|
|
||||||
|
|
||||||
cd ~/apps/vylbot/vylbot_stage \
|
|
||||||
&& git checkout develop \
|
|
||||||
&& git fetch \
|
|
||||||
&& git pull \
|
|
||||||
&& docker compose --file docker-compose.stage.yml down \
|
|
||||||
&& (pm2 stop vylbot_stage || true) \
|
|
||||||
&& (pm2 delete vylbot_stage || true) \
|
|
||||||
&& cp .stage.env .env \
|
|
||||||
&& yarn clean \
|
|
||||||
&& yarn install --frozen-lockfile \
|
|
||||||
&& yarn build \
|
|
||||||
&& docker compose --file docker-compose.stage.yml up -d \
|
|
||||||
&& echo "Sleeping for 10 seconds to let database load..." \
|
|
||||||
&& sleep 10 \
|
|
||||||
&& yarn run db:up \
|
|
||||||
&& NODE_ENV=production pm2 start --name vylbot_stage dist/vylbot.js
|
|
37
src/Register.ts
Normal file
37
src/Register.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { CoreClient } from "./client/client";
|
||||||
|
import About from "./commands/about";
|
||||||
|
import Ban from "./commands/ban";
|
||||||
|
import Clear from "./commands/clear";
|
||||||
|
import Evaluate from "./commands/eval";
|
||||||
|
import Help from "./commands/help";
|
||||||
|
import Kick from "./commands/kick";
|
||||||
|
import Mute from "./commands/mute";
|
||||||
|
import Poll from "./commands/poll";
|
||||||
|
import Role from "./commands/role";
|
||||||
|
import Rules from "./commands/rules";
|
||||||
|
import Unmute from "./commands/unmute";
|
||||||
|
import Warn from "./commands/warn";
|
||||||
|
import MemberEvents from "./events/MemberEvents";
|
||||||
|
import MessageEvents from "./events/MessageEvents";
|
||||||
|
|
||||||
|
export default class Register {
|
||||||
|
public static RegisterCommands(client: CoreClient) {
|
||||||
|
client.RegisterCommand("about", new About());
|
||||||
|
client.RegisterCommand("ban", new Ban());
|
||||||
|
client.RegisterCommand("clear", new Clear());
|
||||||
|
client.RegisterCommand("eval", new Evaluate());
|
||||||
|
client.RegisterCommand("help", new Help());
|
||||||
|
client.RegisterCommand("kick", new Kick());
|
||||||
|
client.RegisterCommand("mute", new Mute());
|
||||||
|
client.RegisterCommand("poll", new Poll());
|
||||||
|
client.RegisterCommand("role", new Role());
|
||||||
|
client.RegisterCommand("rules", new Rules());
|
||||||
|
client.RegisterCommand("unmute", new Unmute());
|
||||||
|
client.RegisterCommand("warn", new Warn());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RegisterEvents(client: CoreClient) {
|
||||||
|
client.RegisterEvent(new MemberEvents());
|
||||||
|
client.RegisterEvent(new MessageEvents());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
import {ButtonInteraction} from "discord.js";
|
|
||||||
import {ButtonEvent} from "../type/buttonEvent";
|
|
||||||
import List from "./moons/list";
|
|
||||||
|
|
||||||
export default class Moons extends ButtonEvent {
|
|
||||||
public override async execute(interaction: ButtonInteraction): Promise<void> {
|
|
||||||
const action = interaction.customId.split(" ")[1];
|
|
||||||
|
|
||||||
switch (action) {
|
|
||||||
case "list":
|
|
||||||
await List(interaction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
import {ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, EmbedBuilder} from "discord.js";
|
|
||||||
import Moon from "../../database/entities/304276391837302787/Moon";
|
|
||||||
import EmbedColours from "../../constants/EmbedColours";
|
|
||||||
|
|
||||||
export default async function List(interaction: ButtonInteraction) {
|
|
||||||
if (!interaction.guild) return;
|
|
||||||
|
|
||||||
const userId = interaction.customId.split(" ")[2];
|
|
||||||
const page = interaction.customId.split(" ")[3];
|
|
||||||
|
|
||||||
if (!userId || !page) return;
|
|
||||||
|
|
||||||
const pageNumber = Number(page);
|
|
||||||
|
|
||||||
const member = interaction.guild.members.cache.find(x => x.user.id == userId);
|
|
||||||
|
|
||||||
const pageLength = 10;
|
|
||||||
|
|
||||||
const moons = await Moon.FetchPaginatedMoonsByUserId(userId, pageLength, pageNumber);
|
|
||||||
|
|
||||||
if (!moons || moons[0].length == 0) {
|
|
||||||
await interaction.reply(`${member?.user.username ?? "This user"} does not have any moons or page is invalid.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalPages = Math.ceil(moons[1] / pageLength);
|
|
||||||
|
|
||||||
const description = moons[0].flatMap(x => `**${x.MoonNumber} -** ${x.Description.slice(0, 15)}`);
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle(`${member?.user.username}'s Moons`)
|
|
||||||
.setColor(EmbedColours.Ok)
|
|
||||||
.setDescription(description.join("\n"))
|
|
||||||
.setFooter({ text: `Page ${page + 1} of ${totalPages} · ${moons[1]} moons` });
|
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
|
||||||
.addComponents(
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`moons list ${userId} ${pageNumber - 1}`)
|
|
||||||
.setLabel("Previous")
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setDisabled(pageNumber == 0),
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`moons list ${userId} ${pageNumber + 1}`)
|
|
||||||
.setLabel("Next")
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setDisabled(pageNumber + 1 == totalPages));
|
|
||||||
|
|
||||||
await interaction.update({
|
|
||||||
embeds: [ embed ],
|
|
||||||
components: [ row ],
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
import { ButtonInteraction, CacheType } from "discord.js";
|
|
||||||
import { ButtonEvent } from "../type/buttonEvent";
|
|
||||||
import SettingsHelper from "../helpers/SettingsHelper";
|
|
||||||
|
|
||||||
export default class Verify extends ButtonEvent {
|
|
||||||
public override async execute(interaction: ButtonInteraction<CacheType>) {
|
|
||||||
if (!interaction.guildId || !interaction.guild) return;
|
|
||||||
|
|
||||||
const roleName = await SettingsHelper.GetSetting("verification.role", interaction.guildId);
|
|
||||||
|
|
||||||
if (!roleName) return;
|
|
||||||
|
|
||||||
const role = interaction.guild.roles.cache.find(x => x.name == roleName);
|
|
||||||
|
|
||||||
if (!role) {
|
|
||||||
await interaction.reply({
|
|
||||||
content: `Unable to find the role, ${roleName}`,
|
|
||||||
ephemeral: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const member = interaction.guild.members.cache.find(x => x.id == interaction.user.id);
|
|
||||||
|
|
||||||
if (!member || !member.manageable) {
|
|
||||||
await interaction.reply({
|
|
||||||
content: "Unable to give role to user",
|
|
||||||
ephemeral: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await member.roles.add(role);
|
|
||||||
|
|
||||||
await interaction.reply({
|
|
||||||
content: "Given role",
|
|
||||||
ephemeral: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,98 +1,67 @@
|
||||||
import { Client, Partials } from "discord.js";
|
import { Client } from "discord.js";
|
||||||
import * as dotenv from "dotenv";
|
import * as dotenv from "dotenv";
|
||||||
import { createConnection } from "typeorm";
|
|
||||||
import { EventType } from "../constants/EventType";
|
|
||||||
import ICommandItem from "../contracts/ICommandItem";
|
import ICommandItem from "../contracts/ICommandItem";
|
||||||
import IEventItem from "../contracts/IEventItem";
|
import IEventItem from "../contracts/IEventItem";
|
||||||
import { Command } from "../type/command";
|
import { Command } from "../type/command";
|
||||||
|
import { Event } from "../type/event";
|
||||||
|
|
||||||
import { Events } from "./events";
|
import { Events } from "./events";
|
||||||
import { Util } from "./util";
|
import { Util } from "./util";
|
||||||
import AppDataSource from "../database/dataSources/appDataSource";
|
|
||||||
import ButtonEventItem from "../contracts/ButtonEventItem";
|
|
||||||
import { ButtonEvent } from "../type/buttonEvent";
|
|
||||||
import CacheHelper from "../helpers/CacheHelper";
|
|
||||||
|
|
||||||
export class CoreClient extends Client {
|
export class CoreClient extends Client {
|
||||||
private static _commandItems: ICommandItem[];
|
private _commandItems: ICommandItem[];
|
||||||
private static _eventItems: IEventItem[];
|
private _eventItems: IEventItem[];
|
||||||
private static _buttonEvents: ButtonEventItem[];
|
|
||||||
|
|
||||||
private _events: Events;
|
private _events: Events;
|
||||||
private _util: Util;
|
private _util: Util;
|
||||||
|
|
||||||
public static get commandItems(): ICommandItem[] {
|
public get commandItems(): ICommandItem[] {
|
||||||
return this._commandItems;
|
return this._commandItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static get eventItems(): IEventItem[] {
|
public get eventItems(): IEventItem[] {
|
||||||
return this._eventItems;
|
return this._eventItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static get buttonEvents(): ButtonEventItem[] {
|
constructor() {
|
||||||
return this._buttonEvents;
|
super();
|
||||||
}
|
|
||||||
|
|
||||||
constructor(intents: number[], partials: Partials[]) {
|
|
||||||
super({ intents: intents, partials: partials });
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
CoreClient._commandItems = [];
|
this._commandItems = [];
|
||||||
CoreClient._eventItems = [];
|
this._eventItems = [];
|
||||||
CoreClient._buttonEvents = [];
|
|
||||||
|
|
||||||
this._events = new Events();
|
this._events = new Events();
|
||||||
this._util = new Util();
|
this._util = new Util();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async start() {
|
public start() {
|
||||||
if (!process.env.BOT_TOKEN) {
|
if (!process.env.BOT_TOKEN) throw "BOT_TOKEN is not defined in .env";
|
||||||
console.error("BOT_TOKEN is not defined in .env");
|
if (!process.env.BOT_PREFIX) throw "BOT_PREFIX is not defined in .env";
|
||||||
return;
|
if (!process.env.FOLDERS_COMMANDS) throw "FOLDERS_COMMANDS is not defined in .env";
|
||||||
}
|
if (!process.env.FOLDERS_EVENTS) throw "FOLDERS_EVENTS is not defined in .env";
|
||||||
|
|
||||||
await AppDataSource.initialize()
|
super.on("message", (message) => this._events.onMessage(message, this._commandItems));
|
||||||
.then(() => console.log("Data Source Initialized"))
|
|
||||||
.catch((err) => console.error("Error Initialising Data Source", err));
|
|
||||||
|
|
||||||
super.on("interactionCreate", this._events.onInteractionCreate);
|
|
||||||
super.on("ready", this._events.onReady);
|
super.on("ready", this._events.onReady);
|
||||||
|
|
||||||
await super.login(process.env.BOT_TOKEN);
|
super.login(process.env.BOT_TOKEN);
|
||||||
|
|
||||||
this.guilds.cache.forEach(async (guild) => {
|
this._util.loadEvents(this, this._eventItems);
|
||||||
await CacheHelper.UpdateServerCache(guild);
|
|
||||||
});
|
|
||||||
|
|
||||||
this._util.loadEvents(this, CoreClient._eventItems);
|
|
||||||
this._util.loadSlashCommands(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RegisterCommand(name: string, command: Command, serverId?: string) {
|
public RegisterCommand(name: string, command: Command) {
|
||||||
const item: ICommandItem = {
|
const item: ICommandItem = {
|
||||||
Name: name,
|
Name: name,
|
||||||
Command: command,
|
Command: command,
|
||||||
ServerId: serverId,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
CoreClient._commandItems.push(item);
|
this._commandItems.push(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RegisterEvent(eventType: EventType, func: Function) {
|
public RegisterEvent(event: Event) {
|
||||||
const item: IEventItem = {
|
const item: IEventItem = {
|
||||||
EventType: eventType,
|
|
||||||
ExecutionFunction: func,
|
|
||||||
};
|
|
||||||
|
|
||||||
CoreClient._eventItems.push(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RegisterButtonEvent(buttonId: string, event: ButtonEvent) {
|
|
||||||
const item: ButtonEventItem = {
|
|
||||||
ButtonId: buttonId,
|
|
||||||
Event: event,
|
Event: event,
|
||||||
};
|
};
|
||||||
|
|
||||||
CoreClient._buttonEvents.push(item);
|
this._eventItems.push(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,71 @@
|
||||||
import { Interaction } from "discord.js";
|
import { Message } from "discord.js";
|
||||||
import ChatInputCommand from "./interactionCreate/chatInputCommand";
|
import { IBaseResponse } from "../contracts/IBaseResponse";
|
||||||
import Button from "./interactionCreate/button";
|
import ICommandItem from "../contracts/ICommandItem";
|
||||||
|
import { Util } from "./util";
|
||||||
|
|
||||||
|
export interface IEventResponse extends IBaseResponse {
|
||||||
|
context?: {
|
||||||
|
prefix: string;
|
||||||
|
name: string;
|
||||||
|
args: string[];
|
||||||
|
message: Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class Events {
|
export class Events {
|
||||||
public async onInteractionCreate(interaction: Interaction) {
|
private _util: Util;
|
||||||
if (!interaction.guildId) return;
|
|
||||||
|
|
||||||
if (interaction.isChatInputCommand()) {
|
constructor() {
|
||||||
ChatInputCommand.onChatInput(interaction);
|
this._util = new Util();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit when a message is sent
|
||||||
|
// Used to check for commands
|
||||||
|
public onMessage(message: Message, commands: ICommandItem[]): IEventResponse {
|
||||||
|
if (!message.guild) return {
|
||||||
|
valid: false,
|
||||||
|
message: "Message was not sent in a guild, ignoring.",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (message.author.bot) return {
|
||||||
|
valid: false,
|
||||||
|
message: "Message was sent by a bot, ignoring.",
|
||||||
|
};
|
||||||
|
|
||||||
|
const prefix = process.env.BOT_PREFIX as string;
|
||||||
|
|
||||||
|
if (message.content.substring(0, prefix.length).toLowerCase() == prefix.toLowerCase()) {
|
||||||
|
const args = message.content.substring(prefix.length).split(" ");
|
||||||
|
const name = args.shift();
|
||||||
|
|
||||||
|
if (!name) return {
|
||||||
|
valid: false,
|
||||||
|
message: "Command name was not found",
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = this._util.loadCommand(name, args, message, commands);
|
||||||
|
|
||||||
|
if (!res.valid) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: res.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: true,
|
||||||
|
context: {
|
||||||
|
prefix: prefix,
|
||||||
|
name: name,
|
||||||
|
args: args,
|
||||||
|
message: message,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interaction.isButton()) {
|
return {
|
||||||
Button.onButtonClicked(interaction);
|
valid: false,
|
||||||
|
message: "Message was not a command, ignoring.",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
import { ButtonInteraction } from "discord.js";
|
|
||||||
import { CoreClient } from "../client";
|
|
||||||
|
|
||||||
export default class Button {
|
|
||||||
public static async onButtonClicked(interaction: ButtonInteraction) {
|
|
||||||
if (!interaction.isButton) return;
|
|
||||||
|
|
||||||
const item = CoreClient.buttonEvents.find(x => x.ButtonId == interaction.customId.split(" ")[0]);
|
|
||||||
|
|
||||||
if (!item) {
|
|
||||||
await interaction.reply("Event not found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
item.Event.execute(interaction);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
import { Interaction } from "discord.js";
|
|
||||||
import { CoreClient } from "../client";
|
|
||||||
import ICommandItem from "../../contracts/ICommandItem";
|
|
||||||
|
|
||||||
export default class ChatInputCommand {
|
|
||||||
public static async onChatInput(interaction: Interaction) {
|
|
||||||
if (!interaction.isChatInputCommand()) return;
|
|
||||||
|
|
||||||
const item = CoreClient.commandItems.find(x => x.Name == interaction.commandName && !x.ServerId);
|
|
||||||
const itemForServer = CoreClient.commandItems.find(x => x.Name == interaction.commandName && x.ServerId == interaction.guildId);
|
|
||||||
|
|
||||||
let itemToUse: ICommandItem;
|
|
||||||
|
|
||||||
if (!itemForServer) {
|
|
||||||
if (!item) {
|
|
||||||
await interaction.reply("Command not found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
itemToUse = item;
|
|
||||||
} else {
|
|
||||||
itemToUse = itemForServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
itemToUse.Command.execute(interaction);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,95 +1,102 @@
|
||||||
import { Client, REST, Routes, SlashCommandBuilder } from "discord.js";
|
// Required Components
|
||||||
import { EventType } from "../constants/EventType";
|
import { Client, Message } from "discord.js";
|
||||||
|
import { readdirSync, existsSync } from "fs";
|
||||||
|
import { IBaseResponse } from "../contracts/IBaseResponse";
|
||||||
|
import { Command } from "../type/command";
|
||||||
|
import { Event } from "../type/event";
|
||||||
|
import { ICommandContext } from "../contracts/ICommandContext";
|
||||||
|
import ICommandItem from "../contracts/ICommandItem";
|
||||||
import IEventItem from "../contracts/IEventItem";
|
import IEventItem from "../contracts/IEventItem";
|
||||||
import { CoreClient } from "./client";
|
|
||||||
|
|
||||||
|
export interface IUtilResponse extends IBaseResponse {
|
||||||
|
context?: {
|
||||||
|
name: string;
|
||||||
|
args: string[];
|
||||||
|
message: Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Util Class
|
||||||
export class Util {
|
export class Util {
|
||||||
public loadSlashCommands(client: Client) {
|
public loadCommand(name: string, args: string[], message: Message, commands: ICommandItem[]): IUtilResponse {
|
||||||
const registeredCommands = CoreClient.commandItems;
|
if (!message.member) return {
|
||||||
|
valid: false,
|
||||||
|
message: "Member is not part of message",
|
||||||
|
};
|
||||||
|
|
||||||
const globalCommands = registeredCommands.filter(x => !x.ServerId);
|
const disabledCommands = process.env.COMMANDS_DISABLED?.split(',');
|
||||||
const guildCommands = registeredCommands.filter(x => x.ServerId);
|
|
||||||
|
|
||||||
const globalCommandData: SlashCommandBuilder[] = globalCommands
|
if (disabledCommands?.find(x => x == name)) {
|
||||||
.filter(x => x.Command.CommandBuilder)
|
message.reply(process.env.COMMANDS_DISABLED_MESSAGE || "This command is disabled.");
|
||||||
.flatMap(x => x.Command.CommandBuilder);
|
|
||||||
|
|
||||||
const guildIds: string[] = [];
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: "Command is disabled",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
for (let command of guildCommands) {
|
const folder = process.env.FOLDERS_COMMANDS;
|
||||||
if (!guildIds.find(x => x == command.ServerId)) {
|
|
||||||
guildIds.push(command.ServerId!);
|
const item = commands.find(x => x.Name == name);
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
message.reply('Command not found');
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: "Command not found"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const requiredRoles = item.Command._roles;
|
||||||
|
|
||||||
|
for (const i in requiredRoles) {
|
||||||
|
if (!message.member.roles.cache.find(role => role.name == requiredRoles[i])) {
|
||||||
|
message.reply(`You require the \`${requiredRoles[i]}\` role to run this command`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: `You require the \`${requiredRoles[i]}\` role to run this command`
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const rest = new REST({ version: '10' }).setToken(process.env.BOT_TOKEN!);
|
const context: ICommandContext = {
|
||||||
|
name: name,
|
||||||
|
args: args,
|
||||||
|
message: message
|
||||||
|
};
|
||||||
|
|
||||||
rest.put(
|
item.Command.execute(context);
|
||||||
Routes.applicationCommands(process.env.BOT_CLIENTID!),
|
|
||||||
{
|
|
||||||
body: globalCommandData
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
for (let guild of guildIds) {
|
return {
|
||||||
const guildCommandData = guildCommands.filter(x => x.ServerId == guild)
|
valid: true,
|
||||||
.filter(x => x.Command.CommandBuilder)
|
context: context
|
||||||
.flatMap(x => x.Command.CommandBuilder);
|
|
||||||
|
|
||||||
if (!client.guilds.cache.has(guild)) continue;
|
|
||||||
|
|
||||||
rest.put(
|
|
||||||
Routes.applicationGuildCommands(process.env.BOT_CLIENTID!, guild),
|
|
||||||
{
|
|
||||||
body: guildCommandData
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the events
|
// Load the events
|
||||||
loadEvents(client: Client, events: IEventItem[]) {
|
loadEvents(client: Client, events: IEventItem[]): IUtilResponse {
|
||||||
|
const folder = process.env.FOLDERS_EVENTS;
|
||||||
|
|
||||||
events.forEach((e) => {
|
events.forEach((e) => {
|
||||||
switch(e.EventType) {
|
client.on('channelCreate', e.Event.channelCreate);
|
||||||
case EventType.ChannelCreate:
|
client.on('channelDelete', e.Event.channelDelete);
|
||||||
client.on('channelCreate', (channel) => e.ExecutionFunction(channel));
|
client.on('channelUpdate', e.Event.channelUpdate);
|
||||||
break;
|
client.on('guildBanAdd', e.Event.guildBanAdd);
|
||||||
case EventType.ChannelDelete:
|
client.on('guildBanRemove', e.Event.guildBanRemove);
|
||||||
client.on('channelDelete', (channel) => e.ExecutionFunction(channel));
|
client.on('guildCreate', e.Event.guildCreate);
|
||||||
break;
|
client.on('guildMemberAdd', e.Event.guildMemberAdd);
|
||||||
case EventType.ChannelUpdate:
|
client.on('guildMemberRemove', e.Event.guildMemberRemove);
|
||||||
client.on('channelUpdate', (channel) => e.ExecutionFunction(channel));
|
client.on('guildMemberUpdate', e.Event.guildMemberUpdate);
|
||||||
break;
|
client.on('message', e.Event.message);
|
||||||
case EventType.GuildBanAdd:
|
client.on('messageDelete', e.Event.messageDelete);
|
||||||
client.on('guildBanAdd', (ban) => e.ExecutionFunction(ban));
|
client.on('messageUpdate', e.Event.messageUpdate);
|
||||||
break;
|
client.on('ready', e.Event.ready);
|
||||||
case EventType.GuildBanRemove:
|
|
||||||
client.on('guildBanRemove', (ban) => e.ExecutionFunction(ban));
|
|
||||||
break;
|
|
||||||
case EventType.GuildCreate:
|
|
||||||
client.on('guildCreate', (guild) => e.ExecutionFunction(guild));
|
|
||||||
break;
|
|
||||||
case EventType.GuildMemberAdd:
|
|
||||||
client.on('guildMemberAdd', (member) => e.ExecutionFunction(member));
|
|
||||||
break;
|
|
||||||
case EventType.GuildMemberRemove:
|
|
||||||
client.on('guildMemberRemove', (member) => e.ExecutionFunction(member));
|
|
||||||
break;
|
|
||||||
case EventType.GuildMemberUpdate:
|
|
||||||
client.on('guildMemberUpdate', (oldMember, newMember) => e.ExecutionFunction(oldMember, newMember));
|
|
||||||
break;
|
|
||||||
case EventType.MessageCreate:
|
|
||||||
client.on('messageCreate', (message) => e.ExecutionFunction(message));
|
|
||||||
break;
|
|
||||||
case EventType.MessageDelete:
|
|
||||||
client.on('messageDelete', (message) => e.ExecutionFunction(message));
|
|
||||||
break;
|
|
||||||
case EventType.MessageUpdate:
|
|
||||||
client.on('messageUpdate', (oldMessage, newMessage) => e.ExecutionFunction(oldMessage, newMessage));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error('Event not implemented.');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
import { Command } from "../../type/command";
|
|
||||||
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
|
|
||||||
import ListMoons from "./moons/list";
|
|
||||||
import AddMoon from "./moons/add";
|
|
||||||
|
|
||||||
export default class Moons extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName("moons")
|
|
||||||
.setDescription("View and create moons")
|
|
||||||
.addSubcommand(subcommand =>
|
|
||||||
subcommand
|
|
||||||
.setName('list')
|
|
||||||
.setDescription('List moons you have obtained')
|
|
||||||
.addUserOption(option =>
|
|
||||||
option
|
|
||||||
.setName("user")
|
|
||||||
.setDescription("The user to view (Defaults to yourself)"))
|
|
||||||
.addNumberOption(option =>
|
|
||||||
option
|
|
||||||
.setName("page")
|
|
||||||
.setDescription("The page to start with")))
|
|
||||||
.addSubcommand(subcommand =>
|
|
||||||
subcommand
|
|
||||||
.setName('add')
|
|
||||||
.setDescription('Add a moon to your count!')
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName("description")
|
|
||||||
.setDescription("What deserved a moon?")
|
|
||||||
.setRequired(true)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.isChatInputCommand()) return;
|
|
||||||
|
|
||||||
switch (interaction.options.getSubcommand()) {
|
|
||||||
case "list":
|
|
||||||
await ListMoons(interaction);
|
|
||||||
break;
|
|
||||||
case "add":
|
|
||||||
await AddMoon(interaction);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
import {CommandInteraction, EmbedBuilder} from "discord.js";
|
|
||||||
import Moon from "../../../database/entities/304276391837302787/Moon";
|
|
||||||
import EmbedColours from "../../../constants/EmbedColours";
|
|
||||||
|
|
||||||
export default async function AddMoon(interaction: CommandInteraction) {
|
|
||||||
const description = interaction.options.get("description", true).value?.toString();
|
|
||||||
|
|
||||||
if (!description || description.length > 255) {
|
|
||||||
await interaction.reply("Name must be less than 255 characters!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const moonCount = await Moon.FetchMoonCountByUserId(interaction.user.id);
|
|
||||||
|
|
||||||
const moon = new Moon(moonCount + 1, description, interaction.user.id);
|
|
||||||
|
|
||||||
await moon.Save(Moon, moon);
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle(`${interaction.user.globalName} Got A Moon!`)
|
|
||||||
.setColor(EmbedColours.Moon)
|
|
||||||
.setDescription(`**${moon.MoonNumber} -** ${moon.Description}`)
|
|
||||||
.setThumbnail("https://cdn.discordapp.com/emojis/374131312182689793.webp?size=96&quality=lossless");
|
|
||||||
|
|
||||||
await interaction.reply({ embeds: [ embed ] });
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
import {ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder} from "discord.js";
|
|
||||||
import Moon from "../../../database/entities/304276391837302787/Moon";
|
|
||||||
import EmbedColours from "../../../constants/EmbedColours";
|
|
||||||
|
|
||||||
export default async function ListMoons(interaction: CommandInteraction) {
|
|
||||||
const user = interaction.options.get("user")?.user ?? interaction.user;
|
|
||||||
const page = interaction.options.get("page")?.value as number ?? 0;
|
|
||||||
|
|
||||||
const pageLength = 10;
|
|
||||||
|
|
||||||
const moons = await Moon.FetchPaginatedMoonsByUserId(user.id, pageLength, page);
|
|
||||||
|
|
||||||
if (!moons || moons[0].length == 0) {
|
|
||||||
await interaction.reply(`${user.username} does not have any moons or page is invalid.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalPages = Math.ceil(moons[1] / pageLength);
|
|
||||||
|
|
||||||
const description = moons[0].flatMap(x => `**${x.MoonNumber} -** ${x.Description.slice(0, 15)}`);
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle(`${user.username}'s Moons`)
|
|
||||||
.setColor(EmbedColours.Ok)
|
|
||||||
.setDescription(description.join("\n"))
|
|
||||||
.setFooter({ text: `Page ${page + 1} of ${totalPages} · ${moons[1]} moons` });
|
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
|
||||||
.addComponents(
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`moons list ${user.id} ${page - 1}`)
|
|
||||||
.setLabel("Previous")
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setDisabled(page == 0),
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId(`moons list ${user.id} ${page + 1}`)
|
|
||||||
.setLabel("Next")
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setDisabled(page + 1 == totalPages));
|
|
||||||
|
|
||||||
await interaction.reply({
|
|
||||||
embeds: [ embed ],
|
|
||||||
components: [ row ],
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
import { CommandInteraction, PermissionsBitField, SlashCommandBuilder } from "discord.js";
|
|
||||||
import { Command } from "../../../type/command";
|
|
||||||
import { default as eLobby } from "../../../database/entities/501231711271780357/Lobby";
|
|
||||||
|
|
||||||
export default class AddRole extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName('addlobby')
|
|
||||||
.setDescription('Add lobby channel')
|
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.ModerateMembers)
|
|
||||||
.addChannelOption(option =>
|
|
||||||
option
|
|
||||||
.setName('channel')
|
|
||||||
.setDescription('The channel')
|
|
||||||
.setRequired(true))
|
|
||||||
.addRoleOption(option =>
|
|
||||||
option
|
|
||||||
.setName('role')
|
|
||||||
.setDescription('The role to ping on request')
|
|
||||||
.setRequired(true))
|
|
||||||
.addNumberOption(option =>
|
|
||||||
option
|
|
||||||
.setName('cooldown')
|
|
||||||
.setDescription('The cooldown in minutes')
|
|
||||||
.setRequired(true))
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('name')
|
|
||||||
.setDescription('The game name')
|
|
||||||
.setRequired(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
const channel = interaction.options.get('channel');
|
|
||||||
const role = interaction.options.get('role');
|
|
||||||
const cooldown = interaction.options.get('cooldown');
|
|
||||||
const gameName = interaction.options.get('name');
|
|
||||||
|
|
||||||
if (!channel || !channel.channel || !role || !role.role || !cooldown || !cooldown.value || !gameName || !gameName.value) {
|
|
||||||
await interaction.reply('Fields are required.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lobby = await eLobby.FetchOneByChannelId(channel.channel.id);
|
|
||||||
|
|
||||||
if (lobby) {
|
|
||||||
await interaction.reply('This channel has already been setup.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const entity = new eLobby(channel.channel.id, role.role.id, cooldown.value as number, gameName.value as string);
|
|
||||||
await entity.Save(eLobby, entity);
|
|
||||||
|
|
||||||
await interaction.reply(`Added \`${channel.name}\` as a new lobby channel with a cooldown of \`${cooldown.value} minutes \` and will ping \`${role.name}\` on use`);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
import { CacheType, CommandInteraction, EmbedBuilder, GuildBasedChannel, PermissionsBitField, SlashCommandBuilder } from "discord.js";
|
|
||||||
import { Command } from "../../../type/command";
|
|
||||||
import { default as eLobby } from "../../../database/entities/501231711271780357/Lobby";
|
|
||||||
import EmbedColours from "../../../constants/EmbedColours";
|
|
||||||
|
|
||||||
export default class ListLobby extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName('listlobby')
|
|
||||||
.setDescription('Lists all channels set up as lobbies')
|
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.ModerateMembers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction<CacheType>) {
|
|
||||||
if (!interaction.guild) {
|
|
||||||
await interaction.reply('Guild not found.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const channels: eLobby[] = [];
|
|
||||||
|
|
||||||
for (let channel of interaction.guild.channels.cache.map(x => x)) {
|
|
||||||
const lobby = await eLobby.FetchOneByChannelId(channel.id);
|
|
||||||
|
|
||||||
if (lobby) {
|
|
||||||
channels.push(lobby);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setColor(EmbedColours.Ok)
|
|
||||||
.setTitle("Lobbies")
|
|
||||||
.setDescription(`Channels: ${channels.length}`);
|
|
||||||
|
|
||||||
for (let lobby of channels) {
|
|
||||||
embed.addFields([
|
|
||||||
{
|
|
||||||
name: `# ${lobby.Name}`,
|
|
||||||
value: `Last Used: ${lobby.LastUsed}`
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.reply({ embeds: [ embed ]});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
|
|
||||||
import { Command } from "../../../type/command";
|
|
||||||
import { default as eLobby } from "../../../database/entities/501231711271780357/Lobby";
|
|
||||||
|
|
||||||
export default class Lobby extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName('lobby')
|
|
||||||
.setDescription('Attempt to organise a lobby');
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.channelId) return;
|
|
||||||
|
|
||||||
const lobby = await eLobby.FetchOneByChannelId(interaction.channelId);
|
|
||||||
|
|
||||||
if (!lobby) {
|
|
||||||
await interaction.reply('This channel is disabled from using the lobby command.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeNow = Date.now();
|
|
||||||
const timeLength = lobby.Cooldown * 60 * 1000; // x minutes in ms
|
|
||||||
const timeAgo = timeNow - timeLength;
|
|
||||||
|
|
||||||
// If it was less than x minutes ago
|
|
||||||
if (lobby.LastUsed.getTime() > timeAgo) {
|
|
||||||
const timeLeft = Math.ceil((timeLength - (timeNow - lobby.LastUsed.getTime())) / 1000 / 60);
|
|
||||||
|
|
||||||
await interaction.reply(`Requesting a lobby for this game is on cooldown! Please try again in **${timeLeft} minutes**.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lobby.MarkAsUsed();
|
|
||||||
await lobby.Save(eLobby, lobby);
|
|
||||||
|
|
||||||
await interaction.reply(`${interaction.user} would like to organise a lobby of **${lobby.Name}**! <@&${lobby.RoleId}>`);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
import { CommandInteraction, PermissionsBitField, SlashCommandBuilder } from "discord.js";
|
|
||||||
import { Command } from "../../../type/command";
|
|
||||||
import { default as eLobby } from "../../../database/entities/501231711271780357/Lobby";
|
|
||||||
import BaseEntity from "../../../contracts/BaseEntity";
|
|
||||||
|
|
||||||
export default class RemoveLobby extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName('removelobby')
|
|
||||||
.setDescription('Remove a lobby channel')
|
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.ModerateMembers)
|
|
||||||
.addChannelOption(option =>
|
|
||||||
option
|
|
||||||
.setName('channel')
|
|
||||||
.setDescription('The channel')
|
|
||||||
.setRequired(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
const channel = interaction.options.get('channel');
|
|
||||||
|
|
||||||
if (!channel || !channel.channel) {
|
|
||||||
await interaction.reply('Channel is required.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const entity = await eLobby.FetchOneByChannelId(channel.channel.id);
|
|
||||||
|
|
||||||
if (!entity) {
|
|
||||||
await interaction.reply('Channel not found.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await BaseEntity.Remove<eLobby>(eLobby, entity);
|
|
||||||
|
|
||||||
await interaction.reply(`Removed <#${channel.channel.id}> from the list of lobby channels`);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { CommandInteraction, EmbedBuilder, PermissionsBitField, SlashCommandBuilder, TextChannel } from "discord.js";
|
|
||||||
import EmbedColours from "../../constants/EmbedColours";
|
|
||||||
import SettingsHelper from "../../helpers/SettingsHelper";
|
|
||||||
import { Command } from "../../type/command";
|
|
||||||
|
|
||||||
export default class Entry extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName('entry')
|
|
||||||
.setDescription('Sends the entry embed')
|
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.ModerateMembers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.guildId) return;
|
|
||||||
if (!interaction.channel) return;
|
|
||||||
|
|
||||||
const rulesChannelId = await SettingsHelper.GetSetting("channels.rules", interaction.guildId) || "rules";
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setColor(EmbedColours.Ok)
|
|
||||||
.setTitle("Welcome")
|
|
||||||
.setDescription(`Welcome to the server! Please make sure to read the rules in the <#${rulesChannelId}> channel and type the code found there in here to proceed to the main part of the server.`);
|
|
||||||
|
|
||||||
const channel = interaction.channel as TextChannel;
|
|
||||||
|
|
||||||
await channel.send({ embeds: [ embed ]});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
import { CommandInteraction, PermissionsBitField, SlashCommandBuilder } from "discord.js";
|
|
||||||
import { Command } from "../../type/command";
|
|
||||||
import { default as eRole } from "../../database/entities/Role";
|
|
||||||
import Server from "../../database/entities/Server";
|
|
||||||
|
|
||||||
export default class ConfigRole extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName('configrole')
|
|
||||||
.setDescription('Toggle your roles')
|
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.ManageRoles)
|
|
||||||
.addRoleOption(option =>
|
|
||||||
option
|
|
||||||
.setName('role')
|
|
||||||
.setDescription('The role name')
|
|
||||||
.setRequired(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.guildId || !interaction.guild) return;
|
|
||||||
if (!interaction.member) return;
|
|
||||||
|
|
||||||
const role = interaction.options.get('role');
|
|
||||||
|
|
||||||
if (!role || !role.role) {
|
|
||||||
await interaction.reply('Fields are required.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingRole = await eRole.FetchOneByRoleId(role.role.id);
|
|
||||||
|
|
||||||
if (existingRole) {
|
|
||||||
await eRole.Remove(eRole, existingRole);
|
|
||||||
|
|
||||||
await interaction.reply('Removed role from configuration.');
|
|
||||||
} else {
|
|
||||||
const server = await Server.FetchOneById(Server, interaction.guildId);
|
|
||||||
|
|
||||||
if (!server) {
|
|
||||||
await interaction.reply('This server has not been setup.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newRole = new eRole(role.role.id);
|
|
||||||
newRole.SetServer(server);
|
|
||||||
|
|
||||||
await newRole.Save(eRole, newRole);
|
|
||||||
|
|
||||||
await interaction.reply('Added role to configuration.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,109 +0,0 @@
|
||||||
import { CommandInteraction, EmbedBuilder, GuildMemberRoleManager, SlashCommandBuilder } from "discord.js";
|
|
||||||
import { Command } from "../../type/command";
|
|
||||||
import { default as eRole } from "../../database/entities/Role";
|
|
||||||
import EmbedColours from "../../constants/EmbedColours";
|
|
||||||
|
|
||||||
export default class Role extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName('role')
|
|
||||||
.setDescription('Toggle your roles')
|
|
||||||
.addSubcommand(subcommand =>
|
|
||||||
subcommand
|
|
||||||
.setName('toggle')
|
|
||||||
.setDescription('Toggle your role')
|
|
||||||
.addRoleOption(option =>
|
|
||||||
option
|
|
||||||
.setName('role')
|
|
||||||
.setDescription('The role name')
|
|
||||||
.setRequired(true)))
|
|
||||||
.addSubcommand(subcommand =>
|
|
||||||
subcommand
|
|
||||||
.setName('list')
|
|
||||||
.setDescription('List togglable roles'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.isChatInputCommand()) return;
|
|
||||||
|
|
||||||
switch (interaction.options.getSubcommand()) {
|
|
||||||
case 'toggle':
|
|
||||||
await this.ToggleRole(interaction);
|
|
||||||
break;
|
|
||||||
case 'list':
|
|
||||||
await this.SendRolesList(interaction);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
await interaction.reply('Subcommand not found.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async SendRolesList(interaction: CommandInteraction) {
|
|
||||||
const roles = await this.GetRolesList(interaction);
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setColor(EmbedColours.Ok)
|
|
||||||
.setTitle("Roles")
|
|
||||||
.setDescription(`Roles: ${roles.length}\n\n${roles.join("\n")}`);
|
|
||||||
|
|
||||||
await interaction.reply({ embeds: [ embed ]});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ToggleRole(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.guild) return;
|
|
||||||
if (!interaction.member) return;
|
|
||||||
|
|
||||||
const roles = await this.GetRolesList(interaction);
|
|
||||||
const requestedRole = interaction.options.get('role');
|
|
||||||
|
|
||||||
if (!requestedRole || !requestedRole.role) {
|
|
||||||
await interaction.reply('Fields are required.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!roles.includes(requestedRole.role.name)) {
|
|
||||||
await interaction.reply('This role isn\'t marked as assignable.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const roleManager = interaction.member.roles as GuildMemberRoleManager;
|
|
||||||
|
|
||||||
const userRole = roleManager.cache.find(x => x.name == requestedRole.role!.name);
|
|
||||||
const assignRole = interaction.guild.roles.cache.find(x => x.id == requestedRole.role!.id);
|
|
||||||
|
|
||||||
if (!assignRole) return;
|
|
||||||
|
|
||||||
if (!assignRole.editable) {
|
|
||||||
await interaction.reply('Insufficient permissions. Please contact a moderator.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!userRole) {
|
|
||||||
await roleManager.add(assignRole);
|
|
||||||
await interaction.reply(`Gave role: \`${assignRole.name}\``);
|
|
||||||
} else {
|
|
||||||
await roleManager.remove(assignRole);
|
|
||||||
await interaction.reply(`Removed role: \`${assignRole.name}\``);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async GetRolesList(interaction: CommandInteraction): Promise<string[]> {
|
|
||||||
if (!interaction.guildId || !interaction.guild) return [];
|
|
||||||
|
|
||||||
const rolesArray = await eRole.FetchAllByServerId(interaction.guildId);
|
|
||||||
|
|
||||||
const roles: string[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < rolesArray.length; i++) {
|
|
||||||
const serverRole = interaction.guild.roles.cache.find(x => x.id == rolesArray[i].RoleId);
|
|
||||||
|
|
||||||
if (serverRole) {
|
|
||||||
roles.push(serverRole.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,56 +1,25 @@
|
||||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder, SlashCommandBuilder } from "discord.js";
|
import { ICommandContext } from "../contracts/ICommandContext";
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
import ICommandReturnContext from "../contracts/ICommandReturnContext";
|
||||||
|
import PublicEmbed from "../helpers/embeds/PublicEmbed";
|
||||||
import { Command } from "../type/command";
|
import { Command } from "../type/command";
|
||||||
|
|
||||||
export default class About extends Command {
|
export default class About extends Command {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
super._category = "General";
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName('about')
|
|
||||||
.setDescription('About VylBot');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
public override execute(context: ICommandContext): ICommandReturnContext {
|
||||||
const fundingLink = process.env.ABOUT_FUNDING;
|
const embed = new PublicEmbed(context, "About", "")
|
||||||
const repoLink = process.env.ABOUT_REPO;
|
.addField("Version", process.env.BOT_VER)
|
||||||
|
.addField("Author", process.env.BOT_AUTHOR)
|
||||||
|
.addField("Date", process.env.BOT_DATE);
|
||||||
|
|
||||||
|
embed.SendToCurrentChannel();
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
return {
|
||||||
.setColor(EmbedColours.Ok)
|
commandContext: context,
|
||||||
.setTitle("About")
|
embeds: [embed]
|
||||||
.setDescription("Discord Bot made by Vylpes");
|
};
|
||||||
|
|
||||||
embed.addFields([
|
|
||||||
{
|
|
||||||
name: "Version",
|
|
||||||
value: process.env.BOT_VER!,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Author",
|
|
||||||
value: process.env.BOT_AUTHOR!,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>();
|
|
||||||
|
|
||||||
if (repoLink) {
|
|
||||||
row.addComponents(
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setURL(repoLink)
|
|
||||||
.setLabel("Repo")
|
|
||||||
.setStyle(ButtonStyle.Link));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fundingLink) {
|
|
||||||
row.addComponents(
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setURL(fundingLink)
|
|
||||||
.setLabel("Funding")
|
|
||||||
.setStyle(ButtonStyle.Link));
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.reply({ embeds: [ embed ], components: row.components.length > 0 ? [ row ] : [] });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,212 +0,0 @@
|
||||||
import Audit from "../database/entities/Audit";
|
|
||||||
import AuditTools from "../helpers/AuditTools";
|
|
||||||
import { Command } from "../type/command";
|
|
||||||
import { CommandInteraction, EmbedBuilder, PermissionsBitField, SlashCommandBuilder } from "discord.js";
|
|
||||||
import { AuditType } from "../constants/AuditType";
|
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
|
||||||
|
|
||||||
export default class Audits extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName("audits")
|
|
||||||
.setDescription("View audits of a particular user in the server")
|
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.ModerateMembers)
|
|
||||||
.addSubcommand(subcommand =>
|
|
||||||
subcommand
|
|
||||||
.setName('user')
|
|
||||||
.setDescription('View all audits done against a user')
|
|
||||||
.addUserOption(option =>
|
|
||||||
option
|
|
||||||
.setName('target')
|
|
||||||
.setDescription('The user')
|
|
||||||
.setRequired(true)))
|
|
||||||
.addSubcommand(subcommand =>
|
|
||||||
subcommand
|
|
||||||
.setName('view')
|
|
||||||
.setDescription('View a particular audit')
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('auditid')
|
|
||||||
.setDescription('The audit id in caps')
|
|
||||||
.setRequired(true)))
|
|
||||||
.addSubcommand(subcommand =>
|
|
||||||
subcommand
|
|
||||||
.setName('clear')
|
|
||||||
.setDescription('Clears an audit from a user')
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('auditid')
|
|
||||||
.setDescription('The audit id in caps')
|
|
||||||
.setRequired(true)))
|
|
||||||
.addSubcommand(subcommand =>
|
|
||||||
subcommand
|
|
||||||
.setName('add')
|
|
||||||
.setDescription('Manually add an audit')
|
|
||||||
.addUserOption(option =>
|
|
||||||
option
|
|
||||||
.setName('target')
|
|
||||||
.setDescription('The user')
|
|
||||||
.setRequired(true))
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('type')
|
|
||||||
.setDescription('The type of audit')
|
|
||||||
.setRequired(true)
|
|
||||||
.addChoices(
|
|
||||||
{ name: 'General', value: AuditType.General.toString() },
|
|
||||||
{ name: 'Warn', value: AuditType.Warn.toString() },
|
|
||||||
{ name: 'Mute', value: AuditType.Mute.toString() },
|
|
||||||
{ name: 'Kick', value: AuditType.Kick.toString() },
|
|
||||||
{ name: 'Ban', value: AuditType.Ban.toString() },
|
|
||||||
)
|
|
||||||
.setRequired(true))
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('reason')
|
|
||||||
.setDescription('The reason')));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.isChatInputCommand()) return;
|
|
||||||
|
|
||||||
switch (interaction.options.getSubcommand()) {
|
|
||||||
case "user":
|
|
||||||
await this.SendAuditForUser(interaction);
|
|
||||||
break;
|
|
||||||
case "view":
|
|
||||||
await this.SendAudit(interaction);
|
|
||||||
break;
|
|
||||||
case "clear":
|
|
||||||
await this.ClearAudit(interaction);
|
|
||||||
break;
|
|
||||||
case "add":
|
|
||||||
await this.AddAudit(interaction);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
await interaction.reply("Subcommand doesn't exist.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async SendAuditForUser(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.guildId) return;
|
|
||||||
|
|
||||||
const user = interaction.options.get('target', true).user!;
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
await interaction.reply("User not found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const audits = await Audit.FetchAuditsByUserId(user.id, interaction.guildId);
|
|
||||||
|
|
||||||
if (!audits || audits.length == 0) {
|
|
||||||
await interaction.reply("There are no audits for this user.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setColor(EmbedColours.Ok)
|
|
||||||
.setTitle("Audits")
|
|
||||||
.setDescription(`Audits: ${audits.length}`);
|
|
||||||
|
|
||||||
for (let audit of audits) {
|
|
||||||
embed.addFields([
|
|
||||||
{
|
|
||||||
name: `${audit.AuditId} // ${AuditTools.TypeToFriendlyText(audit.AuditType)}`,
|
|
||||||
value: audit.WhenCreated.toString(),
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.reply({ embeds: [ embed ]});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async SendAudit(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.guildId) return;
|
|
||||||
|
|
||||||
const auditId = interaction.options.get('auditid');
|
|
||||||
|
|
||||||
if (!auditId || !auditId.value) {
|
|
||||||
await interaction.reply("AuditId not found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const audit = await Audit.FetchAuditByAuditId(auditId.value.toString().toUpperCase(), interaction.guildId);
|
|
||||||
|
|
||||||
if (!audit) {
|
|
||||||
await interaction.reply("Audit not found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setColor(EmbedColours.Ok)
|
|
||||||
.setTitle("Audit")
|
|
||||||
.setDescription(audit.AuditId.toUpperCase())
|
|
||||||
.addFields([
|
|
||||||
{
|
|
||||||
name: "Reason",
|
|
||||||
value: audit.Reason || "*none*",
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Type",
|
|
||||||
value: AuditTools.TypeToFriendlyText(audit.AuditType),
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Moderator",
|
|
||||||
value: `<@${audit.ModeratorId}>`,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
await interaction.reply({ embeds: [ embed ]});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ClearAudit(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.guildId) return;
|
|
||||||
|
|
||||||
const auditId = interaction.options.get('auditid');
|
|
||||||
|
|
||||||
if (!auditId || !auditId.value) {
|
|
||||||
await interaction.reply("AuditId not found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const audit = await Audit.FetchAuditByAuditId(auditId.value.toString().toUpperCase(), interaction.guildId);
|
|
||||||
|
|
||||||
if (!audit) {
|
|
||||||
await interaction.reply("Audit not found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Audit.Remove(Audit, audit);
|
|
||||||
|
|
||||||
await interaction.reply("Audit cleared.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async AddAudit(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.guildId) return;
|
|
||||||
|
|
||||||
const user = interaction.options.get('target', true).user!;
|
|
||||||
const auditType = interaction.options.get('type');
|
|
||||||
const reasonInput = interaction.options.get('reason');
|
|
||||||
|
|
||||||
if (!user || !auditType || !auditType.value) {
|
|
||||||
await interaction.reply("Invalid input.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const type = auditType.value as AuditType;
|
|
||||||
const reason = reasonInput && reasonInput.value ? reasonInput.value.toString() : "";
|
|
||||||
|
|
||||||
const audit = new Audit(user.id, type, reason, interaction.user.id, interaction.guildId);
|
|
||||||
|
|
||||||
await audit.Save(Audit, audit);
|
|
||||||
|
|
||||||
await interaction.reply(`Created new audit with ID \`${audit.AuditId}\``);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,101 +1,80 @@
|
||||||
|
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
|
||||||
|
import ErrorMessages from "../constants/ErrorMessages";
|
||||||
|
import LogEmbed from "../helpers/embeds/LogEmbed";
|
||||||
|
import PublicEmbed from "../helpers/embeds/PublicEmbed";
|
||||||
import { Command } from "../type/command";
|
import { Command } from "../type/command";
|
||||||
import Audit from "../database/entities/Audit";
|
import { ICommandContext } from "../contracts/ICommandContext";
|
||||||
import { AuditType } from "../constants/AuditType";
|
import ICommandReturnContext from "../contracts/ICommandReturnContext";
|
||||||
import { CommandInteraction, EmbedBuilder, GuildMember, PermissionsBitField, SlashCommandBuilder, TextChannel } from "discord.js";
|
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
|
||||||
import SettingsHelper from "../helpers/SettingsHelper";
|
|
||||||
|
|
||||||
export default class Ban extends Command {
|
export default class Ban extends Command {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
super._category = "Moderation";
|
||||||
.setName("ban")
|
super._roles = [
|
||||||
.setDescription("Ban a member from the server with an optional reason")
|
process.env.ROLES_MODERATOR!
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.BanMembers)
|
];
|
||||||
.addUserOption(option =>
|
|
||||||
option
|
|
||||||
.setName('target')
|
|
||||||
.setDescription('The user')
|
|
||||||
.setRequired(true))
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('reason')
|
|
||||||
.setDescription('The reason'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
public override async execute(context: ICommandContext): Promise<ICommandReturnContext> {
|
||||||
if (!interaction.isChatInputCommand()) return;
|
const targetUser = context.message.mentions.users.first();
|
||||||
if (!interaction.guildId) return;
|
|
||||||
if (!interaction.guild) return;
|
|
||||||
|
|
||||||
const targetUser = interaction.options.get('target');
|
if (!targetUser) {
|
||||||
const reasonInput = interaction.options.get('reason');
|
const embed = new ErrorEmbed(context, "User does not exist");
|
||||||
|
embed.SendToCurrentChannel();
|
||||||
if (!targetUser || !targetUser.user || !targetUser.member) {
|
return {
|
||||||
await interaction.reply("User not found.");
|
commandContext: context,
|
||||||
return;
|
embeds: [embed],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const member = targetUser.member as GuildMember;
|
const targetMember = context.message.guild?.member(targetUser);
|
||||||
const reason = reasonInput && reasonInput.value ? reasonInput.value.toString() : "*none*";
|
|
||||||
|
|
||||||
const logEmbed = new EmbedBuilder()
|
if (!targetMember) {
|
||||||
.setColor(EmbedColours.Ok)
|
const embed = new ErrorEmbed(context, "User is not in this server");
|
||||||
.setTitle("Member Banned")
|
embed.SendToCurrentChannel();
|
||||||
.setDescription(`<@${targetUser.user.id}> \`${targetUser.user.tag}\``)
|
return {
|
||||||
.setThumbnail(targetUser.user.avatarURL())
|
commandContext: context,
|
||||||
.addFields([
|
embeds: [embed],
|
||||||
{
|
};
|
||||||
name: "Moderator",
|
|
||||||
value: `<@${interaction.user.id}>`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Reason",
|
|
||||||
value: reason,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!member.bannable) {
|
|
||||||
await interaction.reply('Insufficient permissions. Please contact a moderator.');
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await member.ban();
|
const reasonArgs = context.args;
|
||||||
|
reasonArgs.splice(0, 1)
|
||||||
const channelName = await SettingsHelper.GetSetting('channels.logs.mod', interaction.guildId);
|
|
||||||
|
const reason = reasonArgs.join(" ");
|
||||||
if (!channelName) return;
|
|
||||||
|
if (!context.message.guild?.available) {
|
||||||
const channel = interaction.guild.channels.cache.find(x => x.name == channelName) as TextChannel;
|
return {
|
||||||
|
commandContext: context,
|
||||||
if (channel) {
|
embeds: [],
|
||||||
await channel.send({ embeds: [ logEmbed ]});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const dmEmbed = new EmbedBuilder()
|
if (!targetMember.bannable) {
|
||||||
.setColor(EmbedColours.Ok)
|
const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions);
|
||||||
.setTitle(interaction.guild.name)
|
embed.SendToCurrentChannel();
|
||||||
.setDescription("You have been banned by a moderator.")
|
return {
|
||||||
.addFields([
|
commandContext: context,
|
||||||
{
|
embeds: [embed],
|
||||||
name: "Reason",
|
};
|
||||||
value: reason,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
let replyText = "Successfully banned user.";
|
|
||||||
|
|
||||||
try {
|
|
||||||
const dmChannel = await targetUser.user!.createDM();
|
|
||||||
await dmChannel.send({ embeds: [ dmEmbed ] });
|
|
||||||
} catch {
|
|
||||||
replyText += " *Note: I was unable to DM the user the reason.*";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const audit = new Audit(targetUser.user.id, AuditType.Ban, reason, interaction.user.id, interaction.guildId);
|
const logEmbed = new LogEmbed(context, "Member Banned");
|
||||||
await audit.Save(Audit, audit);
|
logEmbed.AddUser("User", targetUser, true);
|
||||||
|
logEmbed.AddUser("Moderator", context.message.author);
|
||||||
|
logEmbed.AddReason(reason);
|
||||||
|
|
||||||
await interaction.reply(replyText);
|
const publicEmbed = new PublicEmbed(context, "", `${targetUser} has been banned`);
|
||||||
|
|
||||||
|
await targetMember.ban({ reason: `Moderator: ${context.message.author.tag}, Reason: ${reason || "*none*"}` });
|
||||||
|
|
||||||
|
logEmbed.SendToModLogsChannel();
|
||||||
|
publicEmbed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [logEmbed, publicEmbed],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,47 +0,0 @@
|
||||||
import { Command } from "../type/command";
|
|
||||||
import randomBunny from "random-bunny";
|
|
||||||
import { CommandInteraction, EmbedBuilder, SlashCommandBuilder } from "discord.js";
|
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
|
||||||
|
|
||||||
export default class Bunny extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName("bunny")
|
|
||||||
.setDescription("Get a random picture of a rabbit.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.isChatInputCommand()) return;
|
|
||||||
|
|
||||||
await interaction.deferReply();
|
|
||||||
|
|
||||||
const subreddits = [
|
|
||||||
'rabbits',
|
|
||||||
'bunnieswithhats',
|
|
||||||
'buncomfortable',
|
|
||||||
'bunnytongues',
|
|
||||||
'dutchbunnymafia',
|
|
||||||
];
|
|
||||||
|
|
||||||
const random = Math.floor(Math.random() * subreddits.length);
|
|
||||||
const selectedSubreddit = subreddits[random];
|
|
||||||
|
|
||||||
const result = await randomBunny(selectedSubreddit, 'hot');
|
|
||||||
|
|
||||||
if (result.IsSuccess) {
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setColor(EmbedColours.Ok)
|
|
||||||
.setTitle(result.Result!.Title)
|
|
||||||
.setDescription(result.Result!.Permalink)
|
|
||||||
.setImage(result.Result!.Url)
|
|
||||||
.setURL(`https://reddit.com${result.Result!.Permalink}`)
|
|
||||||
.setFooter({ text: `r/${selectedSubreddit} · ${result.Result!.Ups} upvotes`});
|
|
||||||
|
|
||||||
await interaction.editReply({ embeds: [ embed ]});
|
|
||||||
} else {
|
|
||||||
await interaction.editReply("There was an error running this command.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +1,50 @@
|
||||||
import { CommandInteraction, PermissionsBitField, SlashCommandBuilder, TextChannel } from "discord.js";
|
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
|
||||||
|
import { TextChannel } from "discord.js";
|
||||||
|
import PublicEmbed from "../helpers/embeds/PublicEmbed";
|
||||||
import { Command } from "../type/command";
|
import { Command } from "../type/command";
|
||||||
|
import { ICommandContext } from "../contracts/ICommandContext";
|
||||||
|
import ICommandReturnContext from "../contracts/ICommandReturnContext";
|
||||||
|
|
||||||
export default class Clear extends Command {
|
export default class Clear extends Command {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
super._category = "Moderation";
|
||||||
.setName("clear")
|
super._roles = [
|
||||||
.setDescription("Clears the channel of messages")
|
process.env.ROLES_MODERATOR!
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.ManageMessages)
|
];
|
||||||
.addNumberOption(option =>
|
|
||||||
option
|
|
||||||
.setName('count')
|
|
||||||
.setDescription('The amount to delete')
|
|
||||||
.setRequired(true)
|
|
||||||
.setMinValue(1)
|
|
||||||
.setMaxValue(100));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
public override async execute(context: ICommandContext): Promise<ICommandReturnContext> {
|
||||||
if (!interaction.isChatInputCommand()) return;
|
if (context.args.length == 0) {
|
||||||
if (!interaction.channel) return;
|
const errorEmbed = new ErrorEmbed(context, "Please specify an amount between 1 and 100");
|
||||||
|
errorEmbed.SendToCurrentChannel();
|
||||||
|
|
||||||
const totalToClear = interaction.options.getNumber('count');
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [errorEmbed]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalToClear = Number.parseInt(context.args[0]);
|
||||||
|
|
||||||
if (!totalToClear || totalToClear <= 0 || totalToClear > 100) {
|
if (!totalToClear || totalToClear <= 0 || totalToClear > 100) {
|
||||||
await interaction.reply('Please specify an amount between 1 and 100.');
|
const errorEmbed = new ErrorEmbed(context, "Please specify an amount between 1 and 100");
|
||||||
return;
|
errorEmbed.SendToCurrentChannel();
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [errorEmbed]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const channel = interaction.channel as TextChannel;
|
await (context.message.channel as TextChannel).bulkDelete(totalToClear);
|
||||||
|
|
||||||
if (!channel.manageable) {
|
const embed = new PublicEmbed(context, "", `${totalToClear} message(s) were removed`);
|
||||||
await interaction.reply('Insufficient permissions. Please contact a moderator.');
|
embed.SendToCurrentChannel();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await channel.bulkDelete(totalToClear);
|
return {
|
||||||
|
commandContext: context,
|
||||||
await interaction.reply(`${totalToClear} message(s) were removed.`);
|
embeds: [embed]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,66 +0,0 @@
|
||||||
import { CommandInteraction, EmbedBuilder, PermissionsBitField, SlashCommandBuilder, TextChannel } from "discord.js";
|
|
||||||
import SettingsHelper from "../helpers/SettingsHelper";
|
|
||||||
import StringTools from "../helpers/StringTools";
|
|
||||||
import { Command } from "../type/command";
|
|
||||||
|
|
||||||
export default class Code extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName('code')
|
|
||||||
.setDescription('Manage the verification code of the server')
|
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.ModerateMembers)
|
|
||||||
.addSubcommand(subcommand =>
|
|
||||||
subcommand
|
|
||||||
.setName('randomise')
|
|
||||||
.setDescription('Regenerates the verification code for this server'))
|
|
||||||
.addSubcommand(subcommand =>
|
|
||||||
subcommand
|
|
||||||
.setName('embed')
|
|
||||||
.setDescription('Sends the embed with the current code to the current channel'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.isChatInputCommand()) return;
|
|
||||||
|
|
||||||
switch (interaction.options.getSubcommand()) {
|
|
||||||
case "randomise":
|
|
||||||
await this.Randomise(interaction);
|
|
||||||
break;
|
|
||||||
case "embed":
|
|
||||||
await this.SendEmbed(interaction);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Randomise(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.guildId) return;
|
|
||||||
|
|
||||||
const randomCode = StringTools.RandomString(5);
|
|
||||||
|
|
||||||
await SettingsHelper.SetSetting("verification.code", interaction.guildId, randomCode);
|
|
||||||
|
|
||||||
await interaction.reply(`Entry code has been set to \`${randomCode}\``);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async SendEmbed(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.guildId) return;
|
|
||||||
if (!interaction.channel) return;
|
|
||||||
|
|
||||||
const code = await SettingsHelper.GetSetting("verification.code", interaction.guildId);
|
|
||||||
|
|
||||||
if (!code || code == "") {
|
|
||||||
await interaction.reply("There is no code for this server setup.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle("Entry Code")
|
|
||||||
.setDescription(code);
|
|
||||||
|
|
||||||
const channel = interaction.channel as TextChannel;
|
|
||||||
|
|
||||||
await channel.send({ embeds: [ embed ]});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,200 +0,0 @@
|
||||||
import { CommandInteraction, EmbedBuilder, PermissionsBitField, SlashCommandBuilder } from "discord.js";
|
|
||||||
import { readFileSync } from "fs";
|
|
||||||
import DefaultValues from "../constants/DefaultValues";
|
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
|
||||||
import Server from "../database/entities/Server";
|
|
||||||
import Setting from "../database/entities/Setting";
|
|
||||||
import { Command } from "../type/command";
|
|
||||||
|
|
||||||
export default class Config extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName('config')
|
|
||||||
.setDescription('Configure the current server')
|
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator)
|
|
||||||
.addSubcommand(subcommand =>
|
|
||||||
subcommand
|
|
||||||
.setName('reset')
|
|
||||||
.setDescription('Reset a setting to the default')
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('key')
|
|
||||||
.setDescription('The key')
|
|
||||||
.setRequired(true)))
|
|
||||||
.addSubcommand(subcommand =>
|
|
||||||
subcommand
|
|
||||||
.setName('get')
|
|
||||||
.setDescription('Gets a setting for the server')
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('key')
|
|
||||||
.setDescription('The key')
|
|
||||||
.setRequired(true)))
|
|
||||||
.addSubcommand(subcommand =>
|
|
||||||
subcommand
|
|
||||||
.setName('set')
|
|
||||||
.setDescription('Sets a setting to a specified value')
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('key')
|
|
||||||
.setDescription('The key')
|
|
||||||
.setRequired(true))
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('value')
|
|
||||||
.setDescription('The value')
|
|
||||||
.setRequired(true)))
|
|
||||||
.addSubcommand(subcommand =>
|
|
||||||
subcommand
|
|
||||||
.setName('list')
|
|
||||||
.setDescription('Lists all settings'))
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.isChatInputCommand()) return;
|
|
||||||
if (!interaction.guildId) return;
|
|
||||||
|
|
||||||
const server = await Server.FetchOneById<Server>(Server, interaction.guildId, [
|
|
||||||
"Settings",
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!server) {
|
|
||||||
await interaction.reply('Server not setup. Please use the setup command,');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (interaction.options.getSubcommand()) {
|
|
||||||
case 'list':
|
|
||||||
await this.SendHelpText(interaction);
|
|
||||||
break;
|
|
||||||
case 'reset':
|
|
||||||
await this.ResetValue(interaction);
|
|
||||||
break;
|
|
||||||
case 'get':
|
|
||||||
await this.GetValue(interaction);
|
|
||||||
break;
|
|
||||||
case 'set':
|
|
||||||
await this.SetValue(interaction);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
await interaction.reply('Subcommand not found.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async SendHelpText(interaction: CommandInteraction) {
|
|
||||||
const description = readFileSync(`${process.cwd()}/data/usage/config.txt`).toString();
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setColor(EmbedColours.Ok)
|
|
||||||
.setTitle("Config")
|
|
||||||
.setDescription(description);
|
|
||||||
|
|
||||||
await interaction.reply({ embeds: [ embed ]});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async GetValue(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.guildId) return;
|
|
||||||
|
|
||||||
const key = interaction.options.get('key');
|
|
||||||
|
|
||||||
if (!key || !key.value) {
|
|
||||||
await interaction.reply('Fields are required.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const server = await Server.FetchOneById<Server>(Server, interaction.guildId, [
|
|
||||||
"Settings",
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!server) {
|
|
||||||
await interaction.reply('Server not found.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const setting = server.Settings.filter(x => x.Key == key.value)[0];
|
|
||||||
|
|
||||||
if (setting) {
|
|
||||||
await interaction.reply(`\`${key.value}\`: \`${setting.Value}\``);
|
|
||||||
} else {
|
|
||||||
var defaultValue = DefaultValues.GetValue(key.value.toString());
|
|
||||||
|
|
||||||
if (defaultValue) {
|
|
||||||
await interaction.reply(`\`${key.value}\`: \`${defaultValue}\` <DEFAULT>`);
|
|
||||||
} else {
|
|
||||||
await interaction.reply(`\`${key.value}\`: <NONE>`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ResetValue(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.guildId) return;
|
|
||||||
|
|
||||||
const key = interaction.options.get('key');
|
|
||||||
|
|
||||||
if (!key || !key.value) {
|
|
||||||
await interaction.reply('Fields are required.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const server = await Server.FetchOneById<Server>(Server, interaction.guildId, [
|
|
||||||
"Settings",
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!server) {
|
|
||||||
await interaction.reply('Server not found.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const setting = server.Settings.filter(x => x.Key == key.value)[0];
|
|
||||||
|
|
||||||
if (!setting) {
|
|
||||||
await interaction.reply('Setting not found.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Setting.Remove(Setting, setting);
|
|
||||||
|
|
||||||
await interaction.reply('The setting has been reset to the default.');
|
|
||||||
}
|
|
||||||
|
|
||||||
private async SetValue(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.guildId) return;
|
|
||||||
|
|
||||||
const key = interaction.options.get('key');
|
|
||||||
const value = interaction.options.get('value');
|
|
||||||
|
|
||||||
if (!key || !key.value || !value || !value.value) {
|
|
||||||
await interaction.reply('Fields are required.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const server = await Server.FetchOneById<Server>(Server, interaction.guildId, [
|
|
||||||
"Settings",
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!server) {
|
|
||||||
await interaction.reply('Server not found.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const setting = server.Settings.filter(x => x.Key == key.value)[0];
|
|
||||||
|
|
||||||
if (setting) {
|
|
||||||
setting.UpdateBasicDetails(key.value.toString(), value.value.toString());
|
|
||||||
|
|
||||||
await setting.Save(Setting, setting);
|
|
||||||
} else {
|
|
||||||
const newSetting = new Setting(key.value.toString(), value.value.toString());
|
|
||||||
|
|
||||||
await newSetting.Save(Setting, newSetting);
|
|
||||||
|
|
||||||
server.AddSettingToServer(newSetting);
|
|
||||||
|
|
||||||
await server.Save(Server, server);
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.reply('Setting has been set.');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
import { CommandInteraction, PermissionsBitField, SlashCommandBuilder } from "discord.js";
|
|
||||||
import SettingsHelper from "../helpers/SettingsHelper";
|
|
||||||
import { Command } from "../type/command";
|
|
||||||
|
|
||||||
export default class Disable extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName('disable')
|
|
||||||
.setDescription('Disables a command')
|
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator)
|
|
||||||
.addSubcommand(subcommand =>
|
|
||||||
subcommand
|
|
||||||
.setName('add')
|
|
||||||
.setDescription('Disables a command for the server')
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('name')
|
|
||||||
.setDescription('The name of the command')
|
|
||||||
.setRequired(true)))
|
|
||||||
.addSubcommand(subcommand =>
|
|
||||||
subcommand
|
|
||||||
.setName('remove')
|
|
||||||
.setDescription('Enables a command for the server')
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('name')
|
|
||||||
.setDescription('The name of the command')
|
|
||||||
.setRequired(true)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.isChatInputCommand()) return;
|
|
||||||
|
|
||||||
switch (interaction.options.getSubcommand()) {
|
|
||||||
case "add":
|
|
||||||
await this.Add(interaction);
|
|
||||||
break;
|
|
||||||
case "remove":
|
|
||||||
await this.Remove(interaction);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
await interaction.reply('Subcommand not found.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Add(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.guildId) return;
|
|
||||||
|
|
||||||
const commandName = interaction.options.get('name');
|
|
||||||
|
|
||||||
if (!commandName || !commandName.value) {
|
|
||||||
await interaction.reply('Fields are required.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const disabledCommandsString = await SettingsHelper.GetSetting("commands.disabled", interaction.guildId);
|
|
||||||
const disabledCommands = disabledCommandsString != "" ? disabledCommandsString?.split(",") : [];
|
|
||||||
|
|
||||||
disabledCommands?.push(commandName.value.toString());
|
|
||||||
|
|
||||||
await SettingsHelper.SetSetting("commands.disabled", interaction.guildId, disabledCommands!.join(","));
|
|
||||||
|
|
||||||
await interaction.reply(`Disabled command ${commandName.value}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Remove(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.guildId) return;
|
|
||||||
|
|
||||||
const commandName = interaction.options.get('name');
|
|
||||||
|
|
||||||
if (!commandName || !commandName.value) {
|
|
||||||
await interaction.reply('Fields are required.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const disabledCommandsString = await SettingsHelper.GetSetting("commands.disabled", interaction.guildId);
|
|
||||||
const disabledCommands = disabledCommandsString != "" ? disabledCommandsString?.split(",") : [];
|
|
||||||
|
|
||||||
const disabledCommandsInstance = disabledCommands?.findIndex(x => x == commandName.value!.toString());
|
|
||||||
|
|
||||||
if (disabledCommandsInstance! > -1) {
|
|
||||||
disabledCommands?.splice(disabledCommandsInstance!, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
await SettingsHelper.SetSetting("commands.disabled", interaction.guildId, disabledCommands!.join(","));
|
|
||||||
|
|
||||||
await interaction.reply(`Enabled command ${commandName.value}`);
|
|
||||||
}
|
|
||||||
}
|
|
47
src/commands/eval.ts
Normal file
47
src/commands/eval.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import { ICommandContext } from "../contracts/ICommandContext";
|
||||||
|
import ICommandReturnContext from "../contracts/ICommandReturnContext";
|
||||||
|
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
|
||||||
|
import PublicEmbed from "../helpers/embeds/PublicEmbed";
|
||||||
|
import { Command } from "../type/command";
|
||||||
|
|
||||||
|
export default class Evaluate extends Command {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
super._category = "Owner";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override execute(context: ICommandContext): ICommandReturnContext {
|
||||||
|
if (context.message.author.id != process.env.BOT_OWNERID) {
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const stmt = context.args;
|
||||||
|
|
||||||
|
console.log(`Eval Statement: ${stmt.join(" ")}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = eval(stmt.join(" "));
|
||||||
|
|
||||||
|
const embed = new PublicEmbed(context, "", result);
|
||||||
|
embed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [embed]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (err: any) {
|
||||||
|
const errorEmbed = new ErrorEmbed(context, err);
|
||||||
|
errorEmbed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [errorEmbed]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
122
src/commands/help.ts
Normal file
122
src/commands/help.ts
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
import { existsSync, readdirSync } from "fs";
|
||||||
|
import { ICommandContext } from "../contracts/ICommandContext";
|
||||||
|
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
|
||||||
|
import PublicEmbed from "../helpers/embeds/PublicEmbed";
|
||||||
|
import StringTools from "../helpers/StringTools";
|
||||||
|
import ICommandReturnContext from "../contracts/ICommandReturnContext";
|
||||||
|
import { Command } from "../type/command";
|
||||||
|
|
||||||
|
export interface ICommandData {
|
||||||
|
Exists: boolean;
|
||||||
|
Name?: string;
|
||||||
|
Category?: string;
|
||||||
|
Roles?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Help extends Command {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
super._category = "General";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override execute(context: ICommandContext): ICommandReturnContext {
|
||||||
|
if (context.args.length == 0) {
|
||||||
|
return this.SendAll(context);
|
||||||
|
} else {
|
||||||
|
return this.SendSingle(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SendAll(context: ICommandContext): ICommandReturnContext {
|
||||||
|
const allCommands = this.GetAllCommandData();
|
||||||
|
const cateogries = [...new Set(allCommands.map(x => x.Category!))];;
|
||||||
|
|
||||||
|
const embed = new PublicEmbed(context, "Commands", "");
|
||||||
|
|
||||||
|
cateogries.forEach(category => {
|
||||||
|
let filtered = allCommands.filter(x => x.Category == category);
|
||||||
|
|
||||||
|
embed.addField(StringTools.Capitalise(category), filtered.flatMap(x => x.Name).join(", "));
|
||||||
|
});
|
||||||
|
|
||||||
|
embed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [ embed ]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public SendSingle(context: ICommandContext): ICommandReturnContext {
|
||||||
|
const command = this.GetCommandData(context.args[0]);
|
||||||
|
|
||||||
|
if (!command.Exists) {
|
||||||
|
const errorEmbed = new ErrorEmbed(context, "Command does not exist");
|
||||||
|
errorEmbed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [ errorEmbed ]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const embed = new PublicEmbed(context, StringTools.Capitalise(command.Name!), "");
|
||||||
|
embed.addField("Category", StringTools.Capitalise(command.Category!));
|
||||||
|
embed.addField("Required Roles", StringTools.Capitalise(command.Roles!.join(", ")) || "*none*");
|
||||||
|
|
||||||
|
embed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [ embed ]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetAllCommandData(): ICommandData[] {
|
||||||
|
const result: ICommandData[] = [];
|
||||||
|
|
||||||
|
const folder = process.env.FOLDERS_COMMANDS!;
|
||||||
|
|
||||||
|
const contents = readdirSync(`${process.cwd()}/${folder}`);
|
||||||
|
|
||||||
|
contents.forEach(name => {
|
||||||
|
const file = require(`${process.cwd()}/${folder}/${name}`).default;
|
||||||
|
const command = new file() as Command;
|
||||||
|
|
||||||
|
const data: ICommandData = {
|
||||||
|
Exists: true,
|
||||||
|
Name: name.replace(".ts", ""),
|
||||||
|
Category: command._category || "none",
|
||||||
|
Roles: command._roles,
|
||||||
|
};
|
||||||
|
|
||||||
|
result.push(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetCommandData(name: string): ICommandData {
|
||||||
|
const folder = process.env.FOLDERS_COMMANDS!;
|
||||||
|
const path = `${process.cwd()}/${folder}/${name}.ts`;
|
||||||
|
|
||||||
|
if (!existsSync(path)) {
|
||||||
|
return {
|
||||||
|
Exists: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = require(path).default;
|
||||||
|
const command = new file() as Command;
|
||||||
|
|
||||||
|
const data: ICommandData = {
|
||||||
|
Exists: true,
|
||||||
|
Name: name,
|
||||||
|
Category: command._category || "none",
|
||||||
|
Roles: command._roles
|
||||||
|
};
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,39 +0,0 @@
|
||||||
import { CommandInteraction, PermissionsBitField, SlashCommandBuilder } from "discord.js";
|
|
||||||
import IgnoredChannel from "../database/entities/IgnoredChannel";
|
|
||||||
import { Command } from "../type/command";
|
|
||||||
|
|
||||||
export default class Ignore extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName('ignore')
|
|
||||||
.setDescription('Ignore events in this channel')
|
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.guildId) return;
|
|
||||||
|
|
||||||
const isChannelIgnored = await IgnoredChannel.IsChannelIgnored(interaction.guildId);
|
|
||||||
|
|
||||||
if (isChannelIgnored) {
|
|
||||||
const entity = await IgnoredChannel.FetchOneById(IgnoredChannel, interaction.guildId);
|
|
||||||
|
|
||||||
if (!entity) {
|
|
||||||
await interaction.reply('Unable to find channel.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await IgnoredChannel.Remove(IgnoredChannel, entity);
|
|
||||||
|
|
||||||
await interaction.reply('This channel will start being logged again.');
|
|
||||||
} else {
|
|
||||||
const entity = new IgnoredChannel(interaction.guildId);
|
|
||||||
|
|
||||||
await entity.Save(IgnoredChannel, entity);
|
|
||||||
|
|
||||||
await interaction.reply('This channel will now be ignored from logging.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,101 +1,83 @@
|
||||||
|
import ErrorMessages from "../constants/ErrorMessages";
|
||||||
|
import { ICommandContext } from "../contracts/ICommandContext";
|
||||||
|
import ICommandReturnContext from "../contracts/ICommandReturnContext";
|
||||||
|
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
|
||||||
|
import LogEmbed from "../helpers/embeds/LogEmbed";
|
||||||
|
import PublicEmbed from "../helpers/embeds/PublicEmbed";
|
||||||
import { Command } from "../type/command";
|
import { Command } from "../type/command";
|
||||||
import Audit from "../database/entities/Audit";
|
|
||||||
import { AuditType } from "../constants/AuditType";
|
|
||||||
import { CommandInteraction, EmbedBuilder, GuildMember, PermissionsBitField, SlashCommandBuilder, TextChannel } from "discord.js";
|
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
|
||||||
import SettingsHelper from "../helpers/SettingsHelper";
|
|
||||||
|
|
||||||
export default class Kick extends Command {
|
export default class Kick extends Command {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
super._category = "Moderation";
|
||||||
.setName("kick")
|
super._roles = [
|
||||||
.setDescription("Kick a member from the server with an optional reason")
|
process.env.ROLES_MODERATOR!
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.KickMembers)
|
];
|
||||||
.addUserOption(option =>
|
|
||||||
option
|
|
||||||
.setName('target')
|
|
||||||
.setDescription('The user')
|
|
||||||
.setRequired(true))
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('reason')
|
|
||||||
.setDescription('The reason'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
public override async execute(context: ICommandContext): Promise<ICommandReturnContext> {
|
||||||
if (!interaction.isChatInputCommand()) return;
|
const targetUser = context.message.mentions.users.first();
|
||||||
if (!interaction.guildId) return;
|
|
||||||
if (!interaction.guild) return;
|
|
||||||
|
|
||||||
const targetUser = interaction.options.get('target');
|
if (!targetUser) {
|
||||||
const reasonInput = interaction.options.get('reason');
|
const embed = new ErrorEmbed(context, "User does not exist");
|
||||||
|
embed.SendToCurrentChannel();
|
||||||
|
|
||||||
if (!targetUser || !targetUser.user || !targetUser.member) {
|
return {
|
||||||
await interaction.reply("User not found.");
|
commandContext: context,
|
||||||
return;
|
embeds: [embed]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const member = targetUser.member as GuildMember;
|
const targetMember = context.message.guild?.member(targetUser);
|
||||||
const reason = reasonInput && reasonInput.value ? reasonInput.value.toString() : "*none*";
|
|
||||||
|
|
||||||
const logEmbed = new EmbedBuilder()
|
if (!targetMember) {
|
||||||
.setColor(EmbedColours.Ok)
|
const embed = new ErrorEmbed(context, "User is not in this server");
|
||||||
.setTitle("Member Kicked")
|
embed.SendToCurrentChannel();
|
||||||
.setDescription(`<@${targetUser.user.id}> \`${targetUser.user.tag}\``)
|
|
||||||
.setThumbnail(targetUser.user.avatarURL())
|
return {
|
||||||
.addFields([
|
commandContext: context,
|
||||||
{
|
embeds: [embed]
|
||||||
name: "Moderator",
|
};
|
||||||
value: `<@${interaction.user.id}>`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Reason",
|
|
||||||
value: reason,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!member.kickable) {
|
|
||||||
await interaction.reply('Insufficient permissions. Please contact a moderator.');
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await member.kick();
|
const reasonArgs = context.args;
|
||||||
|
reasonArgs.splice(0, 1)
|
||||||
const channelName = await SettingsHelper.GetSetting('channels.logs.mod', interaction.guildId);
|
|
||||||
|
const reason = reasonArgs.join(" ");
|
||||||
if (!channelName) return;
|
|
||||||
|
if (!context.message.guild?.available) {
|
||||||
const channel = interaction.guild.channels.cache.find(x => x.name == channelName) as TextChannel;
|
return {
|
||||||
|
commandContext: context,
|
||||||
if (channel) {
|
embeds: []
|
||||||
await channel.send({ embeds: [ logEmbed ]});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const dmEmbed = new EmbedBuilder()
|
if (!targetMember.kickable) {
|
||||||
.setColor(EmbedColours.Ok)
|
const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions);
|
||||||
.setTitle(interaction.guild.name)
|
embed.SendToCurrentChannel();
|
||||||
.setDescription("You have been kicked from the server.")
|
|
||||||
.addFields([
|
return {
|
||||||
{
|
commandContext: context,
|
||||||
name: "Reason",
|
embeds: [embed]
|
||||||
value: reason,
|
};
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
let replyText = "Successfully kicked user.";
|
|
||||||
|
|
||||||
try {
|
|
||||||
const dmChannel = await targetUser.user!.createDM();
|
|
||||||
await dmChannel.send({ embeds: [ dmEmbed ] });
|
|
||||||
} catch {
|
|
||||||
replyText += " *Note: I was unable to DM the user the reason.*";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const audit = new Audit(targetUser.user.id, AuditType.Kick, reason, interaction.user.id, interaction.guildId);
|
const logEmbed = new LogEmbed(context, "Member Kicked");
|
||||||
await audit.Save(Audit, audit);
|
logEmbed.AddUser("User", targetUser, true);
|
||||||
|
logEmbed.AddUser("Moderator", context.message.author);
|
||||||
|
logEmbed.AddReason(reason);
|
||||||
|
|
||||||
await interaction.reply(replyText);
|
const publicEmbed = new PublicEmbed(context, "", `${targetUser} has been kicked`);
|
||||||
|
|
||||||
|
await targetMember.kick(`Moderator: ${context.message.author.tag}, Reason: ${reason || "*none*"}`);
|
||||||
|
|
||||||
|
logEmbed.SendToModLogsChannel();
|
||||||
|
publicEmbed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [logEmbed, publicEmbed]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,30 +0,0 @@
|
||||||
import { CommandInteraction, PermissionFlagsBits, SlashCommandBuilder } from "discord.js";
|
|
||||||
import { Command } from "../type/command";
|
|
||||||
import SettingsHelper from "../helpers/SettingsHelper";
|
|
||||||
|
|
||||||
export default class Linkonly extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName("linkonly")
|
|
||||||
.setDescription("Set the link only channel, leave blank to disable")
|
|
||||||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
|
|
||||||
.addChannelOption(x => x
|
|
||||||
.setName("channel")
|
|
||||||
.setDescription("The channel"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.guild) return;
|
|
||||||
|
|
||||||
const channel = interaction.options.get("channel")?.channel;
|
|
||||||
|
|
||||||
const channelid = channel?.id ?? "";
|
|
||||||
const channelName = channel?.name ?? "<NONE>";
|
|
||||||
|
|
||||||
await SettingsHelper.SetSetting("channel.linkonly", interaction.guild.id, channelid);
|
|
||||||
|
|
||||||
await interaction.reply(`Set the link only channel to \`${channelName}\``);
|
|
||||||
}
|
|
||||||
}
|
|
96
src/commands/mute.ts
Normal file
96
src/commands/mute.ts
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
import ErrorMessages from "../constants/ErrorMessages";
|
||||||
|
import { ICommandContext } from "../contracts/ICommandContext";
|
||||||
|
import ICommandReturnContext from "../contracts/ICommandReturnContext";
|
||||||
|
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
|
||||||
|
import LogEmbed from "../helpers/embeds/LogEmbed";
|
||||||
|
import PublicEmbed from "../helpers/embeds/PublicEmbed";
|
||||||
|
import { Command } from "../type/command";
|
||||||
|
|
||||||
|
export default class Mute extends Command {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
super._category = "Moderation";
|
||||||
|
super._roles = [
|
||||||
|
process.env.ROLES_MODERATOR!
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async execute(context: ICommandContext): Promise<ICommandReturnContext> {
|
||||||
|
const targetUser = context.message.mentions.users.first();
|
||||||
|
|
||||||
|
if (!targetUser) {
|
||||||
|
const embed = new ErrorEmbed(context, "User does not exist");
|
||||||
|
embed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [embed]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetMember = context.message.guild?.member(targetUser);
|
||||||
|
|
||||||
|
if (!targetMember) {
|
||||||
|
const embed = new ErrorEmbed(context, "User is not in this server");
|
||||||
|
embed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [embed]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const reasonArgs = context.args;
|
||||||
|
reasonArgs.splice(0, 1);
|
||||||
|
|
||||||
|
const reason = reasonArgs.join(" ");
|
||||||
|
|
||||||
|
if (!context.message.guild?.available) {
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetMember.manageable) {
|
||||||
|
const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions);
|
||||||
|
embed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [embed]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const logEmbed = new LogEmbed(context, "Member Muted");
|
||||||
|
logEmbed.AddUser("User", targetUser, true)
|
||||||
|
logEmbed.AddUser("Moderator", context.message.author);
|
||||||
|
logEmbed.AddReason(reason);
|
||||||
|
|
||||||
|
const publicEmbed = new PublicEmbed(context, "", `${targetUser} has been muted`);
|
||||||
|
publicEmbed.AddReason(reason);
|
||||||
|
|
||||||
|
const mutedRole = context.message.guild.roles.cache.find(role => role.name == process.env.ROLES_MUTED);
|
||||||
|
|
||||||
|
if (!mutedRole) {
|
||||||
|
const embed = new ErrorEmbed(context, ErrorMessages.RoleNotFound);
|
||||||
|
embed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [embed]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await targetMember.roles.add(mutedRole, `Moderator: ${context.message.author.tag}, Reason: ${reason || "*none*"}`);
|
||||||
|
|
||||||
|
logEmbed.SendToModLogsChannel();
|
||||||
|
publicEmbed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [logEmbed, publicEmbed]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,62 +1,31 @@
|
||||||
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
|
import { ICommandContext } from "../contracts/ICommandContext";
|
||||||
|
import ICommandReturnContext from "../contracts/ICommandReturnContext";
|
||||||
|
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
|
||||||
|
import PublicEmbed from "../helpers/embeds/PublicEmbed";
|
||||||
import { Command } from "../type/command";
|
import { Command } from "../type/command";
|
||||||
import { EmbedBuilder } from "@discordjs/builders";
|
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
|
||||||
|
|
||||||
export default class Poll extends Command {
|
export default class Poll extends Command {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
super._category = "General";
|
||||||
.setName('poll')
|
|
||||||
.setDescription('Run a poll, automatically adding reaction emojis as options')
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('title')
|
|
||||||
.setDescription('Title of the poll')
|
|
||||||
.setRequired(true))
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('option1')
|
|
||||||
.setDescription('Option 1')
|
|
||||||
.setRequired(true))
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('option2')
|
|
||||||
.setDescription('Option 2')
|
|
||||||
.setRequired(true))
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('option3')
|
|
||||||
.setDescription('Option 3'))
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('option4')
|
|
||||||
.setDescription('Option 4'))
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('option5')
|
|
||||||
.setDescription('Option 5'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
public override async execute(context: ICommandContext): Promise<ICommandReturnContext> {
|
||||||
const title = interaction.options.get('title');
|
const argsJoined = context.args.join(" ");
|
||||||
const option1 = interaction.options.get('option1');
|
const argsSplit = argsJoined.split(";");
|
||||||
const option2 = interaction.options.get('option2');
|
|
||||||
const option3 = interaction.options.get('option3');
|
|
||||||
const option4 = interaction.options.get('option4');
|
|
||||||
const option5 = interaction.options.get('option5');
|
|
||||||
|
|
||||||
if (!title || !option1 || !option2) return;
|
if (argsSplit.length < 3 || argsSplit.length > 10) {
|
||||||
|
const errorEmbed = new ErrorEmbed(context, "Usage: <title>;<option 1>;<option 2>... (separate options with semicolons), maximum of 9 options");
|
||||||
|
errorEmbed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [errorEmbed]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const description = [
|
const title = argsSplit[0];
|
||||||
option1.value as string,
|
|
||||||
option2.value as string,
|
|
||||||
option3?.value as string,
|
|
||||||
option4?.value as string,
|
|
||||||
option5?.value as string
|
|
||||||
]
|
|
||||||
.filter(x => x != null);
|
|
||||||
|
|
||||||
const arrayOfNumbers = [
|
const arrayOfNumbers = [
|
||||||
':one:',
|
':one:',
|
||||||
|
@ -64,28 +33,35 @@ export default class Poll extends Command {
|
||||||
':three:',
|
':three:',
|
||||||
':four:',
|
':four:',
|
||||||
':five:',
|
':five:',
|
||||||
|
':six:',
|
||||||
|
':seven:',
|
||||||
|
':eight:',
|
||||||
|
':nine:'
|
||||||
];
|
];
|
||||||
|
|
||||||
const reactionEmojis = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣"];
|
const reactionEmojis = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣"];
|
||||||
|
|
||||||
|
const description = arrayOfNumbers.splice(0, argsSplit.length - 1);
|
||||||
|
|
||||||
description.forEach((value, index) => {
|
description.forEach((value, index) => {
|
||||||
description[index] = `${reactionEmojis[index]} ${description[index]}`;
|
description[index] = `${value} ${argsSplit[index + 1]}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
const embed = new PublicEmbed(context, title, description.join("\n"));
|
||||||
.setColor(EmbedColours.Ok)
|
|
||||||
.setTitle(title.value as string)
|
|
||||||
.setDescription(description.join('\n'))
|
|
||||||
.setFooter({
|
|
||||||
text: `Poll by ${interaction.user.username}`,
|
|
||||||
iconURL: interaction.user.avatarURL()!,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const message = await context.message.channel.send(embed);
|
||||||
const message = await interaction.reply({ embeds: [ embed ]});
|
|
||||||
|
|
||||||
description.forEach(async (value, index) => {
|
description.forEach(async (value, index) => {
|
||||||
await (await message.fetch()).react(reactionEmojis[index]);
|
await message.react(reactionEmojis[index]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (context.message.deletable) {
|
||||||
|
await context.message.delete({ reason: "Poll command" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [embed]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
99
src/commands/role.ts
Normal file
99
src/commands/role.ts
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
|
||||||
|
import PublicEmbed from "../helpers/embeds/PublicEmbed";
|
||||||
|
import { Role as DiscordRole } from "discord.js";
|
||||||
|
import { Command } from "../type/command";
|
||||||
|
import { ICommandContext } from "../contracts/ICommandContext";
|
||||||
|
import ICommandReturnContext from "../contracts/ICommandReturnContext";
|
||||||
|
|
||||||
|
export default class Role extends Command {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
super._category = "General";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async execute(context: ICommandContext) {
|
||||||
|
const roles = process.env.COMMANDS_ROLE_ROLES!.split(',');
|
||||||
|
|
||||||
|
if (context.args.length == 0) {
|
||||||
|
this.SendRolesList(context, roles);
|
||||||
|
} else {
|
||||||
|
await this.ToggleRole(context, roles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SendRolesList(context: ICommandContext, roles: String[]): ICommandReturnContext {
|
||||||
|
const description = `Do ${process.env.BOT_PREFIX}role <role> to get the role!\n${roles.join('\n')}`;
|
||||||
|
|
||||||
|
const embed = new PublicEmbed(context, "Roles", description);
|
||||||
|
embed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [embed]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ToggleRole(context: ICommandContext, roles: String[]): Promise<ICommandReturnContext> {
|
||||||
|
const requestedRole = context.args[0];
|
||||||
|
|
||||||
|
if (!roles.includes(requestedRole)) {
|
||||||
|
const errorEmbed = new ErrorEmbed(context, "This role isn't marked as assignable, to see a list of assignable roles, run this command without any parameters");
|
||||||
|
errorEmbed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [errorEmbed]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const assignRole = context.message.guild?.roles.cache.find(x => x.name == requestedRole);
|
||||||
|
|
||||||
|
if (!assignRole) {
|
||||||
|
const errorEmbed = new ErrorEmbed(context, "The current server doesn't have this role. Please contact the server's moderators");
|
||||||
|
errorEmbed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [errorEmbed]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const role = context.message.member?.roles.cache.find(x => x.name == requestedRole)
|
||||||
|
|
||||||
|
if (!role) {
|
||||||
|
await this.AddRole(context, assignRole);
|
||||||
|
} else {
|
||||||
|
await this.RemoveRole(context, assignRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async AddRole(context: ICommandContext, role: DiscordRole): Promise<ICommandReturnContext> {
|
||||||
|
await context.message.member?.roles.add(role, "Toggled with role command");
|
||||||
|
|
||||||
|
const embed = new PublicEmbed(context, "", `Gave role: ${role.name}`);
|
||||||
|
embed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [embed]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async RemoveRole(context: ICommandContext, role: DiscordRole): Promise<ICommandReturnContext> {
|
||||||
|
await context.message.member?.roles.remove(role, "Toggled with role command");
|
||||||
|
|
||||||
|
const embed = new PublicEmbed(context, "", `Removed role: ${role.name}`);
|
||||||
|
embed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [embed]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder, PermissionsBitField, SlashCommandBuilder, TextChannel } from "discord.js";
|
|
||||||
import { existsSync, readFileSync } from "fs";
|
import { existsSync, readFileSync } from "fs";
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
import { ICommandContext } from "../contracts/ICommandContext";
|
||||||
|
import ICommandReturnContext from "../contracts/ICommandReturnContext";
|
||||||
|
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
|
||||||
|
import PublicEmbed from "../helpers/embeds/PublicEmbed";
|
||||||
import { Command } from "../type/command";
|
import { Command } from "../type/command";
|
||||||
import SettingsHelper from "../helpers/SettingsHelper";
|
|
||||||
|
|
||||||
interface IRules {
|
interface IRules {
|
||||||
title?: string;
|
title?: string;
|
||||||
|
@ -15,109 +16,42 @@ export default class Rules extends Command {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
super._category = "Admin";
|
||||||
.setName('rules')
|
super._roles = [
|
||||||
.setDescription("Rules-related commands")
|
process.env.ROLES_MODERATOR!
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator)
|
];
|
||||||
.addSubcommand(x =>
|
|
||||||
x
|
|
||||||
.setName('embeds')
|
|
||||||
.setDescription('Send the rules embeds for this server'))
|
|
||||||
.addSubcommand(x =>
|
|
||||||
x
|
|
||||||
.setName('access')
|
|
||||||
.setDescription('Send the server verification embed button'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
public override execute(context: ICommandContext): ICommandReturnContext {
|
||||||
if (!interaction.isChatInputCommand()) return;
|
if (!existsSync(`${process.cwd()}/${process.env.COMMANDS_RULES_FILE!}`)) {
|
||||||
|
const errorEmbed = new ErrorEmbed(context, "Rules file doesn't exist");
|
||||||
|
errorEmbed.SendToCurrentChannel();
|
||||||
|
|
||||||
switch (interaction.options.getSubcommand()) {
|
return {
|
||||||
case "embeds":
|
commandContext: context,
|
||||||
await this.SendEmbeds(interaction);
|
embeds: [errorEmbed]
|
||||||
break;
|
};
|
||||||
case "access":
|
|
||||||
await this.SendAccessButton(interaction);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
await interaction.reply("Subcommand doesn't exist.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async SendEmbeds(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.guildId) return;
|
|
||||||
|
|
||||||
if (!existsSync(`${process.cwd()}/data/rules/${interaction.guildId}.json`)) {
|
|
||||||
await interaction.reply('Rules file doesn\'t exist.');
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rulesFile = readFileSync(`${process.cwd()}/data/rules/${interaction.guildId}.json`).toString();
|
const rulesFile = readFileSync(`${process.cwd()}/${process.env.COMMANDS_RULES_FILE}`).toString();
|
||||||
const rules = JSON.parse(rulesFile) as IRules[];
|
const rules = JSON.parse(rulesFile) as IRules[];
|
||||||
|
|
||||||
const embeds: EmbedBuilder[] = [];
|
const embeds: PublicEmbed[] = [];
|
||||||
|
|
||||||
if (rules.length == 0) {
|
|
||||||
await interaction.reply({ content: "No rules have been supplied within code base for this server.", ephemeral: true });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
rules.forEach(rule => {
|
rules.forEach(rule => {
|
||||||
const embed = new EmbedBuilder()
|
const embed = new PublicEmbed(context, rule.title || "", rule.description?.join("\n") || "");
|
||||||
.setColor(EmbedColours.Ok)
|
|
||||||
.setTitle(rule.title || "Rules")
|
|
||||||
.setDescription(rule.description ? rule.description.join("\n") : "*none*");
|
|
||||||
|
|
||||||
if (rule.image) {
|
embed.setImage(rule.image || "");
|
||||||
embed.setImage(rule.image);
|
embed.setFooter(rule.footer || "");
|
||||||
}
|
|
||||||
|
|
||||||
if (rule.footer) {
|
|
||||||
embed.setFooter({ text: rule.footer });
|
|
||||||
}
|
|
||||||
|
|
||||||
embeds.push(embed);
|
embeds.push(embed);
|
||||||
});
|
});
|
||||||
|
|
||||||
const channel = interaction.channel as TextChannel;
|
embeds.forEach(x => x.SendToCurrentChannel());
|
||||||
|
|
||||||
if (!channel) {
|
return {
|
||||||
await interaction.reply({ content: "Channel not found.", ephemeral: true });
|
commandContext: context,
|
||||||
return;
|
embeds: embeds
|
||||||
}
|
};
|
||||||
|
|
||||||
await channel.send({ embeds: embeds });
|
|
||||||
|
|
||||||
const successEmbed = new EmbedBuilder()
|
|
||||||
.setColor(EmbedColours.Ok)
|
|
||||||
.setTitle("Success")
|
|
||||||
.setDescription("The rules have sent to this channel successfully");
|
|
||||||
|
|
||||||
await interaction.reply({ embeds: [ successEmbed ], ephemeral: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
private async SendAccessButton(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.guildId) return;
|
|
||||||
|
|
||||||
const buttonLabel = await SettingsHelper.GetSetting("rules.access.label", interaction.guildId);
|
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
|
||||||
.addComponents([
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId('verify')
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setLabel(buttonLabel || "Verify")
|
|
||||||
]);
|
|
||||||
|
|
||||||
const channel = interaction.channel as TextChannel;
|
|
||||||
|
|
||||||
await channel.send({
|
|
||||||
components: [ row ]
|
|
||||||
});
|
|
||||||
|
|
||||||
await interaction.reply({
|
|
||||||
content: "Success",
|
|
||||||
ephemeral: true,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,24 +0,0 @@
|
||||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder, SlashCommandBuilder } from "discord.js";
|
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
|
||||||
import { Command } from "../type/command";
|
|
||||||
|
|
||||||
export default class Say extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName('say')
|
|
||||||
.setDescription('Have the bot reply with your message')
|
|
||||||
.addStringOption(x =>
|
|
||||||
x
|
|
||||||
.setName("message")
|
|
||||||
.setDescription("The message to repeat")
|
|
||||||
.setRequired(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
const message = interaction.options.get("message", true);
|
|
||||||
|
|
||||||
await interaction.reply(message.value as string);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { CommandInteraction, PermissionsBitField, SlashCommandBuilder } from "discord.js";
|
|
||||||
import Server from "../database/entities/Server";
|
|
||||||
import { Command } from "../type/command";
|
|
||||||
|
|
||||||
export default class Setup extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName('setup')
|
|
||||||
.setDescription('Makes the server ready to be configured')
|
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
|
||||||
if (!interaction.guildId) return;
|
|
||||||
|
|
||||||
const server = await Server.FetchOneById(Server, interaction.guildId);
|
|
||||||
|
|
||||||
if (server) {
|
|
||||||
await interaction.reply('This server has already been setup, please configure using the config command.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newServer = new Server(interaction.guildId);
|
|
||||||
|
|
||||||
await newServer.Save(Server, newServer);
|
|
||||||
|
|
||||||
await interaction.reply('Success, please configure using the configure command.');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,133 +0,0 @@
|
||||||
import { CacheType, CommandInteraction, EmbedBuilder, GuildMember, PermissionsBitField, SlashCommandBuilder, TextChannel } from "discord.js";
|
|
||||||
import { AuditType } from "../constants/AuditType";
|
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
|
||||||
import Audit from "../database/entities/Audit";
|
|
||||||
import SettingsHelper from "../helpers/SettingsHelper";
|
|
||||||
import TimeLengthInput from "../helpers/TimeLengthInput";
|
|
||||||
import { Command } from "../type/command";
|
|
||||||
|
|
||||||
export default class Timeout extends Command {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
|
||||||
.setName("timeout")
|
|
||||||
.setDescription("Timeouts a user out, sending them a DM with the reason if possible")
|
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.ModerateMembers)
|
|
||||||
.addUserOption(option =>
|
|
||||||
option
|
|
||||||
.setName('target')
|
|
||||||
.setDescription('The user')
|
|
||||||
.setRequired(true))
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName("length")
|
|
||||||
.setDescription("How long to timeout for? (Example: 24h, 60m)")
|
|
||||||
.setRequired(true))
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('reason')
|
|
||||||
.setDescription('The reason'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction<CacheType>) {
|
|
||||||
if (!interaction.guild || !interaction.guildId) return;
|
|
||||||
|
|
||||||
// Interaction Inputs
|
|
||||||
const targetUser = interaction.options.get('target');
|
|
||||||
const lengthInput = interaction.options.get('length');
|
|
||||||
const reasonInput = interaction.options.get('reason');
|
|
||||||
|
|
||||||
// Validation
|
|
||||||
if (!targetUser || !targetUser.user || !targetUser.member) {
|
|
||||||
await interaction.reply('Fields are required.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!lengthInput || !lengthInput.value) {
|
|
||||||
await interaction.reply('Fields are required.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetUser.user.bot) {
|
|
||||||
await interaction.reply('Cannot timeout bots.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// General Variables
|
|
||||||
const targetMember = targetUser.member as GuildMember;
|
|
||||||
const reason = reasonInput && reasonInput.value ? reasonInput.value.toString() : null;
|
|
||||||
|
|
||||||
const timeLength = new TimeLengthInput(lengthInput.value.toString());
|
|
||||||
|
|
||||||
const logEmbed = new EmbedBuilder()
|
|
||||||
.setColor(EmbedColours.Ok)
|
|
||||||
.setTitle("Member Timed Out")
|
|
||||||
.setDescription(`<@${targetUser.user.id}> \`${targetUser.user.tag}\``)
|
|
||||||
.setThumbnail(targetUser.user.avatarURL())
|
|
||||||
.addFields([
|
|
||||||
{
|
|
||||||
name: "Moderator",
|
|
||||||
value: `<@${interaction.user.id}>`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Reason",
|
|
||||||
value: reason || "*none*",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Length",
|
|
||||||
value: timeLength.GetLengthShort(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Until",
|
|
||||||
value: timeLength.GetDateFromNow().toString(),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Bot Permissions Check
|
|
||||||
if (!targetMember.manageable) {
|
|
||||||
await interaction.reply('Insufficient bot permissions. Please contact a moderator.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute Timeout
|
|
||||||
await targetMember.timeout(timeLength.GetMilliseconds(), reason || "");
|
|
||||||
|
|
||||||
// Log Embed To Channel
|
|
||||||
const channelName = await SettingsHelper.GetSetting('channels.logs.mod', interaction.guildId);
|
|
||||||
|
|
||||||
if (!channelName) return;
|
|
||||||
|
|
||||||
const channel = interaction.guild.channels.cache.find(x => x.name == channelName) as TextChannel;
|
|
||||||
|
|
||||||
if (channel) {
|
|
||||||
await channel.send({ embeds: [ logEmbed ]});
|
|
||||||
}
|
|
||||||
|
|
||||||
const dmEmbed = new EmbedBuilder()
|
|
||||||
.setColor(EmbedColours.Ok)
|
|
||||||
.setTitle(interaction.guild.name)
|
|
||||||
.setDescription("You have been given a warning by a moderator.")
|
|
||||||
.addFields([
|
|
||||||
{
|
|
||||||
name: "Reason",
|
|
||||||
value: reason || "*none*",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
let replyText = "Successfully warned user.";
|
|
||||||
|
|
||||||
try {
|
|
||||||
const dmChannel = await targetUser.user!.createDM();
|
|
||||||
await dmChannel.send({ embeds: [ dmEmbed ] });
|
|
||||||
} catch {
|
|
||||||
replyText += " *Note: I was unable to DM the user the reason.*";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Audit
|
|
||||||
const audit = new Audit(targetUser.user.id, AuditType.Timeout, reason || "*none*", interaction.user.id, interaction.guildId);
|
|
||||||
await audit.Save(Audit, audit);
|
|
||||||
|
|
||||||
await interaction.reply(replyText);
|
|
||||||
}
|
|
||||||
}
|
|
96
src/commands/unmute.ts
Normal file
96
src/commands/unmute.ts
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
import ErrorMessages from "../constants/ErrorMessages";
|
||||||
|
import { ICommandContext } from "../contracts/ICommandContext";
|
||||||
|
import ICommandReturnContext from "../contracts/ICommandReturnContext";
|
||||||
|
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
|
||||||
|
import LogEmbed from "../helpers/embeds/LogEmbed";
|
||||||
|
import PublicEmbed from "../helpers/embeds/PublicEmbed";
|
||||||
|
import { Command } from "../type/command";
|
||||||
|
|
||||||
|
export default class Unmute extends Command {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
super._category = "Moderation";
|
||||||
|
super._roles = [
|
||||||
|
process.env.ROLES_MODERATOR!
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async execute(context: ICommandContext): Promise<ICommandReturnContext> {
|
||||||
|
const targetUser = context.message.mentions.users.first();
|
||||||
|
|
||||||
|
if (!targetUser) {
|
||||||
|
const embed = new ErrorEmbed(context, "User does not exist");
|
||||||
|
embed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [embed]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetMember = context.message.guild?.member(targetUser);
|
||||||
|
|
||||||
|
if (!targetMember) {
|
||||||
|
const embed = new ErrorEmbed(context, "User is not in this server");
|
||||||
|
embed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [embed]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const reasonArgs = context.args;
|
||||||
|
reasonArgs.splice(0, 1);
|
||||||
|
|
||||||
|
const reason = reasonArgs.join(" ");
|
||||||
|
|
||||||
|
if (!context.message.guild?.available) {
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetMember.manageable) {
|
||||||
|
const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions);
|
||||||
|
embed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [embed]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const logEmbed = new LogEmbed(context, "Member Unmuted");
|
||||||
|
logEmbed.AddUser("User", targetUser, true)
|
||||||
|
logEmbed.AddUser("Moderator", context.message.author);
|
||||||
|
logEmbed.AddReason(reason);
|
||||||
|
|
||||||
|
const publicEmbed = new PublicEmbed(context, "", `${targetUser} has been unmuted`);
|
||||||
|
publicEmbed.AddReason(reason);
|
||||||
|
|
||||||
|
const mutedRole = context.message.guild.roles.cache.find(role => role.name == process.env.ROLES_MUTED);
|
||||||
|
|
||||||
|
if (!mutedRole) {
|
||||||
|
const embed = new ErrorEmbed(context, ErrorMessages.RoleNotFound);
|
||||||
|
embed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [embed]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await targetMember.roles.remove(mutedRole, `Moderator: ${context.message.author.tag}, Reason: ${reason || "*none*"}`);
|
||||||
|
|
||||||
|
logEmbed.SendToModLogsChannel();
|
||||||
|
publicEmbed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [logEmbed, publicEmbed]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,91 +1,71 @@
|
||||||
import { CommandInteraction, EmbedBuilder, PermissionsBitField, SlashCommandBuilder, TextChannel } from "discord.js";
|
import { ICommandContext } from "../contracts/ICommandContext";
|
||||||
import { AuditType } from "../constants/AuditType";
|
import ICommandReturnContext from "../contracts/ICommandReturnContext";
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
|
||||||
import Audit from "../database/entities/Audit";
|
import LogEmbed from "../helpers/embeds/LogEmbed";
|
||||||
import SettingsHelper from "../helpers/SettingsHelper";
|
import PublicEmbed from "../helpers/embeds/PublicEmbed";
|
||||||
import { Command } from "../type/command";
|
import { Command } from "../type/command";
|
||||||
|
|
||||||
export default class Warn extends Command {
|
export default class Warn extends Command {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.CommandBuilder = new SlashCommandBuilder()
|
super._category = "Moderation";
|
||||||
.setName("warn")
|
super._roles = [
|
||||||
.setDescription("Warns a member in the server with an optional reason")
|
process.env.ROLES_MODERATOR!
|
||||||
.setDefaultMemberPermissions(PermissionsBitField.Flags.ModerateMembers)
|
];
|
||||||
.addUserOption(option =>
|
|
||||||
option
|
|
||||||
.setName('target')
|
|
||||||
.setDescription('The user')
|
|
||||||
.setRequired(true))
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('reason')
|
|
||||||
.setDescription('The reason'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async execute(interaction: CommandInteraction) {
|
public override execute(context: ICommandContext): ICommandReturnContext {
|
||||||
if (!interaction.guild || !interaction.guildId) return;
|
const user = context.message.mentions.users.first();
|
||||||
|
|
||||||
const targetUser = interaction.options.get('target');
|
if (!user) {
|
||||||
const reasonInput = interaction.options.get('reason');
|
const errorEmbed = new ErrorEmbed(context, "User does not exist");
|
||||||
|
errorEmbed.SendToCurrentChannel();
|
||||||
if (!targetUser || !targetUser.user || !targetUser.member) {
|
|
||||||
await interaction.reply('Fields are required.');
|
return {
|
||||||
return;
|
commandContext: context,
|
||||||
|
embeds: [errorEmbed]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const reason = reasonInput && reasonInput.value ? reasonInput.value.toString() : "*none*";
|
const member = context.message.guild?.member(user);
|
||||||
|
|
||||||
const logEmbed = new EmbedBuilder()
|
if (!member) {
|
||||||
.setColor(EmbedColours.Ok)
|
const errorEmbed = new ErrorEmbed(context, "User is not in this server");
|
||||||
.setTitle("Member Warned")
|
errorEmbed.SendToCurrentChannel();
|
||||||
.setDescription(`<@${targetUser.user.id}> \`${targetUser.user.tag}\``)
|
|
||||||
.setThumbnail(targetUser.user.avatarURL())
|
return {
|
||||||
.addFields([
|
commandContext: context,
|
||||||
{
|
embeds: [errorEmbed]
|
||||||
name: "Moderator",
|
};
|
||||||
value: `<@${interaction.user.id}>`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Reason",
|
|
||||||
value: reason,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const channelName = await SettingsHelper.GetSetting('channels.logs.mod', interaction.guildId);
|
|
||||||
|
|
||||||
if (!channelName) return;
|
|
||||||
|
|
||||||
const channel = interaction.guild.channels.cache.find(x => x.name == channelName) as TextChannel;
|
|
||||||
|
|
||||||
if (channel) {
|
|
||||||
await channel.send({ embeds: [ logEmbed ]});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const dmEmbed = new EmbedBuilder()
|
const reasonArgs = context.args;
|
||||||
.setColor(EmbedColours.Ok)
|
reasonArgs.splice(0, 1);
|
||||||
.setTitle(interaction.guild.name)
|
|
||||||
.setDescription("You have been given a warning by a moderator.")
|
|
||||||
.addFields([
|
|
||||||
{
|
|
||||||
name: "Reason",
|
|
||||||
value: reason,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
let replyText = "Successfully warned user.";
|
const reason = reasonArgs.join(" ");
|
||||||
|
|
||||||
try {
|
if (!context.message.guild?.available) {
|
||||||
const dmChannel = await targetUser.user!.createDM();
|
return {
|
||||||
await dmChannel.send({ embeds: [ dmEmbed ] });
|
commandContext: context,
|
||||||
} catch {
|
embeds: []
|
||||||
replyText += " *Note: I was unable to DM the user the reason.*";
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const audit = new Audit(targetUser.user.id, AuditType.Warn, reason, interaction.user.id, interaction.guildId);
|
const logEmbed = new LogEmbed(context, "Member Warned");
|
||||||
await audit.Save(Audit, audit);
|
logEmbed.AddUser("User", user, true);
|
||||||
|
logEmbed.AddUser("Moderator", context.message.author);
|
||||||
|
logEmbed.AddReason(reason);
|
||||||
|
|
||||||
await interaction.reply(replyText);
|
const publicEmbed = new PublicEmbed(context, "", `${user} has been warned`);
|
||||||
|
publicEmbed.AddReason(reason);
|
||||||
|
|
||||||
|
logEmbed.SendToModLogsChannel();
|
||||||
|
publicEmbed.SendToCurrentChannel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandContext: context,
|
||||||
|
embeds: [logEmbed, publicEmbed]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,8 +0,0 @@
|
||||||
export enum AuditType {
|
|
||||||
General,
|
|
||||||
Warn,
|
|
||||||
Mute,
|
|
||||||
Kick,
|
|
||||||
Ban,
|
|
||||||
Timeout,
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
export default class DefaultValues {
|
|
||||||
public static values: ISettingValue[] = [];
|
|
||||||
public static useDevPrefix: boolean = false;
|
|
||||||
|
|
||||||
public static GetValue(key: string): string | undefined {
|
|
||||||
this.SetValues();
|
|
||||||
|
|
||||||
const res = this.values.find(x => x.Key == key);
|
|
||||||
|
|
||||||
if (!res) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SetValues() {
|
|
||||||
if (this.values.length == 0) {
|
|
||||||
// Bot
|
|
||||||
this.values.push({ Key: "bot.prefix", Value: process.env.BOT_PREFIX || "v!" })
|
|
||||||
|
|
||||||
// Commands
|
|
||||||
this.values.push({ Key: "commands.disabled", Value: "" });
|
|
||||||
this.values.push({ Key: "commands.disabled.message", Value: "This command is disabled." });
|
|
||||||
|
|
||||||
// Role (Command)
|
|
||||||
this.values.push({ Key: "role.assignable", Value: "" });
|
|
||||||
this.values.push({ Key: "role.moderator", Value: "Moderator" });
|
|
||||||
this.values.push({ Key: "role.administrator", Value: "Administrator"});
|
|
||||||
this.values.push({ Key: "role.muted", Value: "Muted" });
|
|
||||||
|
|
||||||
// Rules (Command)
|
|
||||||
this.values.push({ Key: "rules.file", Value: "data/rules/rules" });
|
|
||||||
this.values.push({ Key: "rules.access.label", Value: "Verify" });
|
|
||||||
|
|
||||||
// Channels
|
|
||||||
this.values.push({ Key: "channels.logs.message", Value: "message-logs" });
|
|
||||||
this.values.push({ Key: "channels.logs.member", Value: "member-logs" });
|
|
||||||
this.values.push({ Key: "channels.logs.mod", Value: "mod-logs" });
|
|
||||||
|
|
||||||
// Verification
|
|
||||||
this.values.push({ Key: "verification.enabled", Value: "false" });
|
|
||||||
this.values.push({ Key: "verification.channel", Value: "entry" });
|
|
||||||
this.values.push({ Key: "verification.role", Value: "Entry" });
|
|
||||||
this.values.push({ Key: "verification.code", Value: "" });
|
|
||||||
|
|
||||||
// Gif Only Mode
|
|
||||||
this.values.push({ Key: "channel.linkonly", Value: "" })
|
|
||||||
|
|
||||||
// Event
|
|
||||||
this.values.push({ Key: "event.message.delete.enabled", Value: "false" });
|
|
||||||
this.values.push({ Key: "event.message.delete.channel", Value: "message-logs" });
|
|
||||||
|
|
||||||
this.values.push({ Key: "event.message.update.enabled", Value: "false" });
|
|
||||||
this.values.push({ Key: "event.message.update.channel", Value: "message-logs" });
|
|
||||||
|
|
||||||
this.values.push({ Key: "event.member.add.enabled", Value: "false" });
|
|
||||||
this.values.push({ Key: "event.member.add.channel", Value: "member-logs" });
|
|
||||||
|
|
||||||
this.values.push({ Key: "event.member.remove.enabled", Value: "false" });
|
|
||||||
this.values.push({ Key: "event.member.remove.channel", Value: "member-logs" });
|
|
||||||
|
|
||||||
this.values.push({ Key: "event.member.update.enabled", Value: "false" });
|
|
||||||
this.values.push({ Key: "event.member.update.channel", Value: "member-logs" });
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISettingValue {
|
|
||||||
Key: string,
|
|
||||||
Value: string,
|
|
||||||
};
|
|
|
@ -1,4 +0,0 @@
|
||||||
export default class EmbedColours {
|
|
||||||
public static readonly Ok = 0x3050ba;
|
|
||||||
public static readonly Moon = 0x50C878;
|
|
||||||
}
|
|
5
src/constants/ErrorMessages.ts
Normal file
5
src/constants/ErrorMessages.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export default class ErrorMessages {
|
||||||
|
public static readonly InsufficientBotPermissions = "Unable to do this action, am I missing permissions?";
|
||||||
|
public static readonly ChannelNotFound = "Unable to find channel";
|
||||||
|
public static readonly RoleNotFound = "Unable to find role";
|
||||||
|
}
|
|
@ -1,15 +0,0 @@
|
||||||
export enum EventType {
|
|
||||||
ChannelCreate,
|
|
||||||
ChannelDelete,
|
|
||||||
ChannelUpdate,
|
|
||||||
GuildBanAdd,
|
|
||||||
GuildBanRemove,
|
|
||||||
GuildCreate,
|
|
||||||
GuildMemberAdd,
|
|
||||||
GuildMemberRemove,
|
|
||||||
GuildMemberUpdate,
|
|
||||||
MessageCreate,
|
|
||||||
MessageDelete,
|
|
||||||
MessageUpdate,
|
|
||||||
Ready,
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
import { Column, DeepPartial, EntityTarget, PrimaryColumn, ObjectLiteral, FindOptionsWhere } from "typeorm";
|
|
||||||
import { v4 } from "uuid";
|
|
||||||
import AppDataSource from "../database/dataSources/appDataSource";
|
|
||||||
|
|
||||||
export default class BaseEntity {
|
|
||||||
constructor() {
|
|
||||||
this.Id = v4();
|
|
||||||
|
|
||||||
this.WhenCreated = new Date();
|
|
||||||
this.WhenUpdated = new Date();
|
|
||||||
}
|
|
||||||
|
|
||||||
@PrimaryColumn()
|
|
||||||
Id: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
WhenCreated: Date;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
WhenUpdated: Date;
|
|
||||||
|
|
||||||
public async Save<T extends BaseEntity>(target: EntityTarget<T>, entity: DeepPartial<T>): Promise<void> {
|
|
||||||
this.WhenUpdated = new Date();
|
|
||||||
|
|
||||||
const repository = AppDataSource.getRepository<T>(target);
|
|
||||||
|
|
||||||
await repository.save(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Remove<T extends BaseEntity>(target: EntityTarget<T>, entity: T): Promise<void> {
|
|
||||||
const repository = AppDataSource.getRepository<T>(target);
|
|
||||||
|
|
||||||
await repository.remove(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async FetchAll<T extends BaseEntity>(target: EntityTarget<T>, relations?: string[]): Promise<T[]> {
|
|
||||||
const repository = AppDataSource.getRepository<T>(target);
|
|
||||||
|
|
||||||
const all = await repository.find({ relations: relations || [] });
|
|
||||||
|
|
||||||
return all;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async FetchOneById<T extends BaseEntity>(target: EntityTarget<T>, id: string, relations?: string[]): Promise<T | null> {
|
|
||||||
const repository = AppDataSource.getRepository<T>(target);
|
|
||||||
|
|
||||||
const single = await repository.findOne({ where: ({ Id: id } as FindOptionsWhere<T>), relations: relations || {} });
|
|
||||||
|
|
||||||
return single;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Any<T extends ObjectLiteral>(target: EntityTarget<T>): Promise<boolean> {
|
|
||||||
const repository = AppDataSource.getRepository<T>(target);
|
|
||||||
|
|
||||||
const any = await repository.find();
|
|
||||||
|
|
||||||
return any.length > 0;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
import { ButtonEvent } from "../type/buttonEvent";
|
|
||||||
|
|
||||||
interface ButtonEventItem {
|
|
||||||
ButtonId: string,
|
|
||||||
Event: ButtonEvent,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ButtonEventItem;
|
|
7
src/contracts/ICommandContext.ts
Normal file
7
src/contracts/ICommandContext.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { Message } from "discord.js";
|
||||||
|
|
||||||
|
export interface ICommandContext {
|
||||||
|
name: string;
|
||||||
|
args: string[];
|
||||||
|
message: Message;
|
||||||
|
}
|
|
@ -3,5 +3,4 @@ import { Command } from "../type/command";
|
||||||
export default interface ICommandItem {
|
export default interface ICommandItem {
|
||||||
Name: string,
|
Name: string,
|
||||||
Command: Command,
|
Command: Command,
|
||||||
ServerId?: string,
|
|
||||||
}
|
}
|
7
src/contracts/ICommandReturnContext.ts
Normal file
7
src/contracts/ICommandReturnContext.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { MessageEmbed } from "discord.js";
|
||||||
|
import { ICommandContext } from "./ICommandContext";
|
||||||
|
|
||||||
|
export default interface ICommandReturnContext {
|
||||||
|
commandContext: ICommandContext,
|
||||||
|
embeds: MessageEmbed[]
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
|
|
||||||
import { EventType } from "../constants/EventType";
|
import { Event } from "../type/event";
|
||||||
|
|
||||||
export default interface IEventItem {
|
export default interface IEventItem {
|
||||||
EventType: EventType,
|
Event: Event,
|
||||||
ExecutionFunction: Function,
|
|
||||||
}
|
}
|
6
src/contracts/IEventReturnContext.ts
Normal file
6
src/contracts/IEventReturnContext.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { MessageEmbed } from "discord.js";
|
||||||
|
import { ICommandContext } from "./ICommandContext";
|
||||||
|
|
||||||
|
export default interface ICommandReturnContext {
|
||||||
|
embeds: MessageEmbed[]
|
||||||
|
}
|
|
@ -1,26 +0,0 @@
|
||||||
import { DataSource } from "typeorm";
|
|
||||||
import * as dotenv from "dotenv";
|
|
||||||
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
const AppDataSource = new DataSource({
|
|
||||||
type: "mysql",
|
|
||||||
host: process.env.DB_HOST,
|
|
||||||
port: Number(process.env.DB_PORT),
|
|
||||||
username: process.env.DB_AUTH_USER,
|
|
||||||
password: process.env.DB_AUTH_PASS,
|
|
||||||
database: process.env.DB_NAME,
|
|
||||||
synchronize: process.env.DB_SYNC == "true",
|
|
||||||
logging: process.env.DB_LOGGING == "true",
|
|
||||||
entities: [
|
|
||||||
"dist/database/entities/**/*.js",
|
|
||||||
],
|
|
||||||
migrations: [
|
|
||||||
"dist/database/migrations/**/*.js",
|
|
||||||
],
|
|
||||||
subscribers: [
|
|
||||||
"dist/database/subscribers/**/*.js",
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export default AppDataSource;
|
|
|
@ -1,57 +0,0 @@
|
||||||
import { Column, Entity, IsNull } from "typeorm";
|
|
||||||
import BaseEntity from "../../../contracts/BaseEntity";
|
|
||||||
import AppDataSource from "../../dataSources/appDataSource";
|
|
||||||
|
|
||||||
@Entity()
|
|
||||||
export default class Moon extends BaseEntity {
|
|
||||||
constructor(moonNumber: number, description: string, userId: string) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.MoonNumber = moonNumber;
|
|
||||||
this.Description = description;
|
|
||||||
this.UserId = userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
MoonNumber: number;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
Description: string;
|
|
||||||
|
|
||||||
@Column({ nullable: true })
|
|
||||||
WhenArchived?: Date;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
UserId: string;
|
|
||||||
|
|
||||||
public static async FetchMoonsByUserId(userId: string): Promise<Moon[] | null> {
|
|
||||||
const repository = AppDataSource.getRepository(Moon);
|
|
||||||
|
|
||||||
const all = await repository.find({ where: { UserId: userId } });
|
|
||||||
|
|
||||||
return all;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async FetchPaginatedMoonsByUserId(userId: string, pageLength: number, page: number): Promise<[ Moon[], number ]> {
|
|
||||||
const rangeStart = page * pageLength;
|
|
||||||
|
|
||||||
const repository = AppDataSource.getRepository(Moon);
|
|
||||||
|
|
||||||
const moons = await repository.findAndCount({
|
|
||||||
where: { UserId: userId, WhenArchived: IsNull() },
|
|
||||||
order: { MoonNumber: "ASC" },
|
|
||||||
skip: rangeStart,
|
|
||||||
take: pageLength,
|
|
||||||
});
|
|
||||||
|
|
||||||
return moons;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async FetchMoonCountByUserId(userId: string): Promise<number> {
|
|
||||||
const repository = AppDataSource.getRepository(Moon);
|
|
||||||
|
|
||||||
const count = await repository.count({ where: { UserId: userId } });
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue