diff options
| author | TymanWasTaken <32660892+tymanwastaken@users.noreply.github.com> | 2021-04-27 21:06:22 -0600 |
|---|---|---|
| committer | TymanWasTaken <32660892+tymanwastaken@users.noreply.github.com> | 2021-04-27 21:06:22 -0600 |
| commit | 763fb7d98c3accbb21adf035a7cf0a83cb9533c9 (patch) | |
| tree | 9d333fbca2a2a8e19d79904a4e29226174925cfc | |
| download | tanzanite-763fb7d98c3accbb21adf035a7cf0a83cb9533c9.tar.gz tanzanite-763fb7d98c3accbb21adf035a7cf0a83cb9533c9.tar.bz2 tanzanite-763fb7d98c3accbb21adf035a7cf0a83cb9533c9.zip | |
legit just copy utilibot v2 code
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 + } +} @@ -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"> + +<!-- [](https://www.codefactor.io/repository/github/tymanwastaken/cascade) --> + +[](https://discord.gg/2pf4xfG) +[](https://shields.io) +[](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; + }< |
