aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitattributes1
-rw-r--r--.github/workflows/nodejs.yml34
-rw-r--r--.gitignore252
-rw-r--r--.prettierignore2
-rw-r--r--.vscode/launch.json18
-rw-r--r--.vscode/settings.json12
-rw-r--r--LICENSE21
-rw-r--r--README.md28
-rw-r--r--SETUP.md16
-rw-r--r--package.json73
-rw-r--r--src/bot.ts7
-rw-r--r--src/commands/admin/PrefixCommand.ts30
-rw-r--r--src/commands/info/BotInfoCommand.ts58
-rw-r--r--src/commands/info/HelpCommand.ts79
-rw-r--r--src/commands/info/PingCommand.ts42
-rw-r--r--src/commands/moderation/BanCommand.ts137
-rw-r--r--src/commands/moderation/KickCommand.ts72
-rw-r--r--src/commands/moderation/ModlogCommand.ts143
-rw-r--r--src/commands/moderation/WarnCommand.ts54
-rw-r--r--src/commands/owner/EvalCommand.ts139
-rw-r--r--src/commands/owner/ReloadCommand.ts34
-rw-r--r--src/config/example-options.ts30
-rw-r--r--src/inhibitors/blacklist/BlacklistInhibitor.ts14
-rw-r--r--src/lib/extensions/BotClient.ts274
-rw-r--r--src/lib/extensions/BotCommand.ts6
-rw-r--r--src/lib/extensions/BotGuild.ts38
-rw-r--r--src/lib/extensions/BotInhibitor.ts6
-rw-r--r--src/lib/extensions/BotListener.ts6
-rw-r--r--src/lib/extensions/BotMessage.ts50
-rw-r--r--src/lib/extensions/Util.ts196
-rw-r--r--src/lib/types/BaseModel.ts6
-rw-r--r--src/lib/types/Models.ts102
-rw-r--r--src/lib/utils/TopGG.ts110
-rw-r--r--src/listeners/client/ReadyListener.ts16
-rw-r--r--src/listeners/commands/CommandBlockedListener.ts34
-rw-r--r--src/listeners/guild/Unban.ts25
-rw-r--r--src/tasks.ts38
-rw-r--r--tsconfig.json24
-rw-r--r--yarn.lock1937
39 files changed, 4164 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..94f480d
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text=auto eol=lf \ No newline at end of file
diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml
new file mode 100644
index 0000000..e76a579
--- /dev/null
+++ b/.github/workflows/nodejs.yml
@@ -0,0 +1,34 @@
+name: Node.js CI
+
+on:
+ push:
+ branches: [v2]
+ pull_request:
+ branches: [v2]
+
+ workflow_dispatch:
+
+jobs:
+ Test:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [14.x, 15.x]
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v1
+ with:
+ node-version: ${{ matrix.node-version }}
+ - name: Install dependencies
+ run: yarn
+ - name: Fix config
+ run: cp src/config/example-options.ts src/config/options.ts
+ - name: ESLint
+ run: yarn lint
+ - name: Test Build
+ run: yarn build
+ - name: Test formatting
+ run: yarn prettier --check .
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..cf44ab9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,252 @@
+
+# Created by https://www.toptal.com/developers/gitignore/api/node,yarn,vscode,webstorm
+# Edit at https://www.toptal.com/developers/gitignore?templates=node,yarn,vscode,webstorm
+
+### Node ###
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# TypeScript v1 declaration files
+typings/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+.env*.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+### vscode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+### WebStorm ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### WebStorm Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+# https://plugins.jetbrains.com/plugin/7973-sonarlint
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator-enh.xml
+.idea/**/markdown-navigator/
+
+# Cache file creation bug
+# See https://youtrack.jetbrains.com/issue/JBR-2257
+.idea/$CACHE_FILE$
+
+# CodeStream plugin
+# https://plugins.jetbrains.com/plugin/12206-codestream
+.idea/codestream.xml
+
+### yarn ###
+# https://yarnpkg.com/advanced/qa#which-files-should-be-gitignored
+
+.yarn/*
+!.yarn/releases
+!.yarn/plugins
+!.yarn/sdks
+!.yarn/versions
+
+# if you are NOT using Zero-installs, then:
+# comment the following lines
+!.yarn/cache
+
+# and uncomment the following lines
+# .pnp.*
+
+# End of https://www.toptal.com/developers/gitignore/api/node,yarn,vscode,webstorm
+
+# Options and credentials for the bot
+src/config/options.ts
+
+# Unused sqlite database, uses postgresql now but I am keeping it in here just in case
+data.db \ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..2b6411c
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,2 @@
+dist
+.git \ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..24dee80
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,18 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "pwa-node",
+ "request": "launch",
+ "name": "Launch Program",
+ "skipFiles": ["<node_internals>/**"],
+ "program": "${workspaceFolder}\\src\\bot.ts",
+ "preLaunchTask": "tsc: build - tsconfig.json",
+ "outFiles": ["${workspaceFolder}/dist/**/*.js"],
+ "args": ["-r source-map-support/register"]
+ }
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..c0a5640
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,12 @@
+{
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true,
+ "files.exclude": {
+ "**/.git": true,
+ "**/.svn": true,
+ "**/.hg": true,
+ "**/CVS": true,
+ "**/.DS_Store": true,
+ "node_modules": true
+ }
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..3c4ac48
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Tyman-productions
+
+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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..92074ce
--- /dev/null
+++ b/README.md
@@ -0,0 +1,28 @@
+<h1 align = "center">
+ <img src="https://media.discordapp.net/attachments/732377549975453697/818730315778228224/3c024ae2fdbc99065681e4821569106c.png?width=100&height=100">
+ <br>
+ Utilibot discord bot
+</h1>
+
+<div align="center">
+
+<!-- [![CodeFactor](https://img.shields.io/codefactor/grade/github/TymanWasTaken/Utilibot?style=for-the-badge)](https://www.codefactor.io/repository/github/tymanwastaken/cascade) -->
+
+[![discord badge](https://img.shields.io/badge/Join%20the-Discord-blue?style=for-the-badge)](https://discord.gg/2pf4xfG)
+[![uses badges](https://img.shields.io/badge/Uses-Badges-yellow?style=for-the-badge)](https://shields.io)
+[![made with typescript](https://img.shields.io/badge/Made%20With-Typescript-orange?style=for-the-badge)](https://www.typescriptlang.org/)
+
+</div>
+
+Utilibot is a discord bot meant to automate tasks in your discord server, and also have other assorted fun things.
+
+If you would like to set up for yourself, please see [SETUP.md](https://github.com/TymanWasTaken/Utilibot/blob/v2/SETUP.md)
+
+<h2 align="center">Contributing</h2>
+
+You are free to report bugs or contribute to this project. Just open <a href="https://github.com/TymanWasTaken/Utilibot/issues">Issues</a> or <a href="https://github.com/TymanWasTaken/Utilibot/pulls">Pull Requests</a> and the Developer team will look into them.
+
+<h2 align="center">Credits</h2>
+
+- <a href="https://discord.js.org/">discord.js</a> - The main library used to interface with discord
+- <a href="https://discord-akairo.github.io/">discord-akairo</a> - The framework the bot is built on
diff --git a/SETUP.md b/SETUP.md
new file mode 100644
index 0000000..d4cabb1
--- /dev/null
+++ b/SETUP.md
@@ -0,0 +1,16 @@
+# How to set up
+
+## Pre requisites
+
+1. Git
+2. A discord bot on the dev portal
+3. NodeJS
+4. Yarn (npm also works but I strongly encourage to use yarn instead)
+
+## Main setup
+
+1. Clone this repository
+2. Install all dependencies with `yarn`
+3. Set up config by creating `src/config/options.ts` (Use `src/config/example-options.ts` as a guide) and adding all the options
+4. Optional: Make sure everything is set with `yarn test`
+5. Start the bot with `yarn start`
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..e8b966b
--- /dev/null
+++ b/package.json
@@ -0,0 +1,73 @@
+{
+ "name": "utilibot",
+ "version": "2.0.0",
+ "description": "A utility bot for discord",
+ "main": "dist/bot.js",
+ "repository": "https://github.com/tyman-productions/utilibot",
+ "author": "TymanWasTaken <tyman@tyman.tech>",
+ "license": "MIT",
+ "scripts": {
+ "start": "yarn build && node --trace-warnings -r source-map-support/register dist/bot.js",
+ "build": "yarn rimraf dist/ && yarn tsc",
+ "test": "yarn lint && yarn build",
+ "lint": "yarn eslint .",
+ "format": "yarn prettier --write ."
+ },
+ "devDependencies": {
+ "@types/common-tags": "^1.8.0",
+ "@types/express": "^4.17.11",
+ "@types/node": "^14.14.22",
+ "@types/uuid": "^8.3.0",
+ "@typescript-eslint/eslint-plugin": "^4.14.1",
+ "@typescript-eslint/parser": "^4.14.1",
+ "eslint": "^7.18.0",
+ "eslint-config-prettier": "^8.1.0",
+ "prettier": "^2.2.1",
+ "rimraf": "^3.0.2",
+ "source-map-support": "^0.5.19",
+ "typescript": "^4.1.3"
+ },
+ "dependencies": {
+ "@top-gg/sdk": "^3.0.9",
+ "body-parser": "^1.19.0",
+ "common-tags": "^1.8.0",
+ "discord-akairo": "^8.1.0",
+ "discord.js": "^12.5.1",
+ "express": "^4.17.1",
+ "got": "^11.8.1",
+ "moment": "^2.29.1",
+ "pg": "^8.5.1",
+ "pg-hstore": "^2.3.3",
+ "sequelize": "^6.5.0",
+ "uuid": "^8.3.2"
+ },
+ "eslintConfig": {
+ "env": {
+ "es2021": true,
+ "node": true
+ },
+ "extends": [
+ "eslint:recommended",
+ "plugin:@typescript-eslint/recommended",
+ "prettier"
+ ],
+ "parser": "@typescript-eslint/parser",
+ "parserOptions": {
+ "ecmaVersion": 12,
+ "sourceType": "module"
+ },
+ "plugins": [
+ "@typescript-eslint"
+ ],
+ "ignorePatterns": [
+ "dist",
+ "node_modules"
+ ]
+ },
+ "prettier": {
+ "useTabs": true,
+ "quoteProps": "consistent",
+ "singleQuote": true,
+ "trailingComma": "none"
+ }
+}
diff --git a/src/bot.ts b/src/bot.ts
new file mode 100644
index 0000000..3d427e9
--- /dev/null
+++ b/src/bot.ts
@@ -0,0 +1,7 @@
+import { BotClient } from './lib/extensions/BotClient';
+import * as config from './config/options';
+
+const client: BotClient = new BotClient(config);
+client.start();
+
+// 🦀
diff --git a/src/commands/admin/PrefixCommand.ts b/src/commands/admin/PrefixCommand.ts
new file mode 100644
index 0000000..8fb50f8
--- /dev/null
+++ b/src/commands/admin/PrefixCommand.ts
@@ -0,0 +1,30 @@
+import { BotCommand } from '../../lib/extensions/BotCommand';
+import { BotMessage } from '../../lib/extensions/BotMessage';
+
+export default class PrefixCommand extends BotCommand {
+ constructor() {
+ super('prefix', {
+ aliases: ['prefix'],
+ args: [
+ {
+ id: 'prefix'
+ }
+ ],
+ userPermissions: ['MANAGE_GUILD']
+ });
+ }
+ async exec(
+ message: BotMessage,
+ { prefix }: { prefix?: string }
+ ): Promise<void> {
+ if (prefix) {
+ await message.settings.setPrefix(prefix);
+ await message.util.send(`Sucessfully set prefix to \`${prefix}\``);
+ } else {
+ await message.settings.setPrefix(this.client.config.prefix);
+ await message.util.send(
+ `Sucessfully reset prefix to \`${this.client.config.prefix}\``
+ );
+ }
+ }
+}
diff --git a/src/commands/info/BotInfoCommand.ts b/src/commands/info/BotInfoCommand.ts
new file mode 100644
index 0000000..27e14c4
--- /dev/null
+++ b/src/commands/info/BotInfoCommand.ts
@@ -0,0 +1,58 @@
+import { MessageEmbed } from 'discord.js';
+import { BotCommand } from '../../lib/extensions/BotCommand';
+import { duration } from 'moment';
+import { BotMessage } from '../../lib/extensions/BotMessage';
+
+export default class BotInfoCommand extends BotCommand {
+ constructor() {
+ super('botinfo', {
+ aliases: ['botinfo'],
+ description: {
+ content: 'Shows information about the bot',
+ usage: 'botinfo',
+ examples: ['botinfo']
+ }
+ });
+ }
+
+ public async exec(message: BotMessage): Promise<void> {
+ const owners = (await this.client.util.mapIDs(this.client.ownerID))
+ .map((u) => u.tag)
+ .join('\n');
+ const currentCommit = (
+ await this.client.util.shell('git rev-parse HEAD')
+ ).stdout.replace('\n', '');
+ const repoUrl = (
+ await this.client.util.shell('git remote get-url origin')
+ ).stdout.replace('\n', '');
+ const embed = new MessageEmbed()
+ .setTitle('Bot Info:')
+ .addFields([
+ {
+ name: 'Owners',
+ value: owners,
+ inline: true
+ },
+ {
+ name: 'Uptime',
+ value: this.client.util.capitalize(
+ duration(this.client.uptime, 'milliseconds').humanize()
+ )
+ },
+ {
+ name: 'User count',
+ value: this.client.users.cache.size,
+ inline: true
+ },
+ {
+ name: 'Current commit',
+ value: `[${currentCommit.substring(
+ 0,
+ 7
+ )}](${repoUrl}/commit/${currentCommit})`
+ }
+ ])
+ .setTimestamp();
+ await message.util.send(embed);
+ }
+}
diff --git a/src/commands/info/HelpCommand.ts b/src/commands/info/HelpCommand.ts
new file mode 100644
index 0000000..4aa45e0
--- /dev/null
+++ b/src/commands/info/HelpCommand.ts
@@ -0,0 +1,79 @@
+import { Message, MessageEmbed } from 'discord.js';
+import { BotCommand } from '../../lib/extensions/BotCommand';
+import { stripIndent } from 'common-tags';
+import { BotMessage } from '../../lib/extensions/BotMessage';
+
+export default class HelpCommand extends BotCommand {
+ constructor() {
+ super('help', {
+ aliases: ['help'],
+ description: {
+ content: 'Shows the commands of the bot',
+ usage: 'help',
+ examples: ['help']
+ },
+ clientPermissions: ['EMBED_LINKS'],
+ args: [
+ {
+ id: 'command',
+ type: 'commandAlias'
+ }
+ ]
+ });
+ }
+
+ public async exec(
+ message: BotMessage,
+ { command }: { command: BotCommand }
+ ): Promise<Message> {
+ const prefix = this.handler.prefix;
+ if (!command) {
+ const embed = new MessageEmbed()
+ .addField(
+ 'Commands',
+ stripIndent`A list of available commands.
+ For additional info on a command, type \`${prefix}help <command>\`
+ `
+ )
+ .setFooter(
+ `For more information about a command use "${this.client.config.prefix}help <command>"`
+ )
+ .setTimestamp();
+ for (const category of this.handler.categories.values()) {
+ embed.addField(
+ `${category.id.replace(/(\b\w)/gi, (lc): string =>
+ lc.toUpperCase()
+ )}`,
+ `${category
+ .filter((cmd): boolean => cmd.aliases.length > 0)
+ .map((cmd): string => `\`${cmd.aliases[0]}\``)
+ .join(' ')}`
+ );
+ }
+ return message.util.send(embed);
+ }
+
+ const embed = new MessageEmbed()
+ .setColor([155, 200, 200])
+ .setTitle(
+ `\`${command.description.usage ? command.description.usage : ''}\``
+ )
+ .addField(
+ 'Description',
+ `${command.description.content ? command.description.content : ''} ${
+ command.ownerOnly ? '\n__Owner Only__' : ''
+ }`
+ );
+
+ if (command.aliases.length > 1)
+ embed.addField('Aliases', `\`${command.aliases.join('` `')}\``, true);
+ if (command.description.examples && command.description.examples.length)
+ embed.addField(
+ 'Examples',
+ `\`${command.description.examples.join('`\n`')}\``,
+ true
+ );
+
+ return message.util.send(embed);
+ }
+}
diff --git a/src/commands/info/PingCommand.ts b/src/commands/info/PingCommand.ts
new file mode 100644
index 0000000..5a5b819
--- /dev/null
+++ b/src/commands/info/PingCommand.ts
@@ -0,0 +1,42 @@
+import { MessageEmbed } from 'discord.js';
+import { BotCommand } from '../../lib/extensions/BotCommand';
+import { BotMessage } from '../../lib/extensions/BotMessage';
+
+export default class PingCommand extends BotCommand {
+ constructor() {
+ super('ping', {
+ aliases: ['ping'],
+ description: {
+ content: 'Gets the latency of the bot',
+ usage: 'ping',
+ examples: ['ping']
+ }
+ });
+ }
+
+ public async exec(message: BotMessage): Promise<void> {
+ const sentMessage = await message.util.send('Pong!');
+ const timestamp: number = message.editedTimestamp
+ ? message.editedTimestamp
+ : message.createdTimestamp;
+ const botLatency = `\`\`\`\n ${Math.floor(
+ sentMessage.createdTimestamp - timestamp
+ )}ms \`\`\``;
+ const apiLatency = `\`\`\`\n ${Math.round(
+ message.client.ws.ping
+ )}ms \`\`\``;
+ const embed = new MessageEmbed()
+ .setTitle('Pong! 🏓')
+ .addField('Bot Latency', botLatency, true)
+ .addField('API Latency', apiLatency, true)
+ .setFooter(
+ message.author.username,
+ message.author.displayAvatarURL({ dynamic: true })
+ )
+ .setTimestamp();
+ await sentMessage.edit({
+ content: null,
+ embed
+ });
+ }
+}
diff --git a/src/commands/moderation/BanCommand.ts b/src/commands/moderation/BanCommand.ts
new file mode 100644
index 0000000..300101b
--- /dev/null
+++ b/src/commands/moderation/BanCommand.ts
@@ -0,0 +1,137 @@
+import { User } from 'discord.js';
+import { BotCommand } from '../../lib/extensions/BotCommand';
+import { BotMessage } from '../../lib/extensions/BotMessage';
+import { Ban, Modlog, ModlogType } from '../../lib/types/Models';
+import moment from 'moment';
+
+const durationAliases: Record<string, string[]> = {
+ weeks: ['w', 'weeks', 'week', 'wk', 'wks'],
+ days: ['d', 'days', 'day'],
+ hours: ['h', 'hours', 'hour', 'hr', 'hrs'],
+ minutes: ['m', 'min', 'mins', 'minutes', 'minute'],
+ months: ['mo', 'month', 'months']
+};
+const durationRegex = /(?:(\d+)(d(?:ays?)?|h(?:ours?|rs?)?|m(?:inutes?|ins?)?|mo(?:nths?)?|w(?:eeks?|ks?)?)(?: |$))/g;
+
+export default class PrefixCommand extends BotCommand {
+ constructor() {
+ super('ban', {
+ aliases: ['ban'],
+ args: [
+ {
+ id: 'user',
+ type: 'user',
+ prompt: {
+ start: 'What user would you like to ban?',
+ retry: 'Invalid response. What user would you like to ban?'
+ }
+ },
+ {
+ id: 'reason'
+ },
+ {
+ id: 'time',
+ match: 'option',
+ flag: '--time'
+ }
+ ],
+ clientPermissions: ['BAN_MEMBERS'],
+ userPermissions: ['BAN_MEMBERS']
+ });
+ }
+ async exec(
+ message: BotMessage,
+ { user, reason, time }: { user: User; reason?: string; time?: string }
+ ): Promise<void> {
+ const duration = moment.duration();
+ let modlogEnry: Modlog;
+ let banEntry: Ban;
+ const translatedTime: string[] = [];
+ try {
+ try {
+ if (time) {
+ const parsed = [...time.matchAll(durationRegex)];
+ if (parsed.length < 1) {
+ await message.util.send('Invalid time.');
+ return;
+ }
+ for (const part of parsed) {
+ const translated = Object.keys(durationAliases).find((k) =>
+ durationAliases[k].includes(part[2])
+ );
+ translatedTime.push(part[1] + ' ' + translated);
+ duration.add(
+ Number(part[1]),
+ translated as 'weeks' | 'days' | 'hours' | 'months' | 'minutes'
+ );
+ }
+ modlogEnry = Modlog.build({
+ user: user.id,
+ guild: message.guild.id,
+ reason,
+ type: ModlogType.TEMPBAN,
+ duration: duration.asMilliseconds(),
+ moderator: message.author.id
+ });
+ banEntry = Ban.build({
+ user: user.id,
+ guild: message.guild.id,
+ reason,
+ expires: new Date(new Date().getTime() + duration.asMilliseconds()),
+ modlog: modlogEnry.id
+ });
+ } else {
+ modlogEnry = Modlog.build({
+ user: user.id,
+ guild: message.guild.id,
+ reason,
+ type: ModlogType.BAN,
+ moderator: message.author.id
+ });
+ banEntry = Ban.build({
+ user: user.id,
+ guild: message.guild.id,
+ reason,
+ modlog: modlogEnry.id
+ });
+ }
+ await modlogEnry.save();
+ await banEntry.save();
+ } catch (e) {
+ console.error(e);
+ await message.util.send(
+ 'Error saving to database. Please report this to a developer.'
+ );
+ return;
+ }
+ try {
+ await user.send(
+ `You were banned in ${message.guild.name} ${
+ translatedTime.length >= 1
+ ? `for ${translatedTime.join(', ')}`
+ : 'permanently'
+ } with reason \`${reason || 'No reason given'}\``
+ );
+ } catch (e) {
+ await message.channel.send('Error sending message to user');
+ }
+ await message.guild.members.ban(user, {
+ reason: `Banned by ${message.author.tag} with ${
+ reason ? `reason ${reason}` : 'no reason'
+ }`
+ });
+ await message.util.send(
+ `Banned <@!${user.id}> ${
+ translatedTime.length >= 1
+ ? `for ${translatedTime.join(', ')}`
+ : 'permanently'
+ } with reason \`${reason || 'No reason given'}\``
+ );
+ } catch {
+ await message.util.send('Error banning :/');
+ await modlogEnry.destroy();
+ await banEntry.destroy();
+