From c7b55b1aae482403b5bfc2fe780674644fedfc24 Mon Sep 17 00:00:00 2001 From: Rauf Date: Sat, 1 Feb 2020 18:23:36 -0500 Subject: [PATCH] push time --- package-lock.json | 157 +++++++++++++++++++--- package.json | 23 +++- src/PluginClient.ts | 6 +- src/assertions/userLevel.ts | 2 +- src/events/channelCreate.ts | 35 +++++ src/events/eventLoader.ts | 4 +- src/events/guildCreate.ts | 27 ++-- src/events/lifeguardCommandUsed.ts | 21 ++- src/events/message.ts | 42 ++++-- src/events/messageReactionAdd.ts | 20 +++ src/events/ready.ts | 2 +- src/events/starboardReactionAdd.ts | 64 +++++++++ src/index.ts | 13 +- src/models/Guild.ts | 52 +++++++ src/models/User.ts | 60 ++++++++- src/plugins/Command.ts | 6 +- src/plugins/Plugin.ts | 2 +- src/plugins/admin/config.ts | 46 +++++++ src/plugins/{moderation => admin}/role.ts | 4 +- src/plugins/admin/roles.ts | 31 +++++ src/plugins/admin/slowmode.ts | 33 +++++ src/plugins/debug/ping.ts | 4 +- src/plugins/dev/eval.ts | 4 +- src/plugins/global/blacklist.ts | 4 +- src/plugins/info/about.ts | 10 +- src/plugins/info/help.ts | 12 +- src/plugins/moderation/ban.ts | 55 ++++++++ src/plugins/moderation/forceban.ts | 60 +++++++++ src/plugins/moderation/infractions.ts | 54 ++++++++ src/plugins/moderation/kick.ts | 55 ++++++++ src/plugins/moderation/mban.ts | 20 +++ src/plugins/moderation/mkick.ts | 20 +++ src/plugins/moderation/mute.ts | 59 ++++++++ src/plugins/moderation/softban.ts | 57 ++++++++ src/plugins/moderation/unmute.ts | 39 ++++++ src/plugins/moderation/warn.ts | 47 +++++++ src/structures/GuildMember.ts | 29 ++++ src/structures/GuildStructure.ts | 29 ++++ src/structures/structureLoader.ts | 11 ++ src/util/Database.ts | 8 +- tsconfig.json | 19 ++- 41 files changed, 1150 insertions(+), 96 deletions(-) create mode 100644 src/events/channelCreate.ts create mode 100644 src/events/messageReactionAdd.ts create mode 100644 src/events/starboardReactionAdd.ts create mode 100644 src/models/Guild.ts create mode 100644 src/plugins/admin/config.ts rename src/plugins/{moderation => admin}/role.ts (90%) create mode 100644 src/plugins/admin/roles.ts create mode 100644 src/plugins/admin/slowmode.ts create mode 100644 src/plugins/moderation/ban.ts create mode 100644 src/plugins/moderation/forceban.ts create mode 100644 src/plugins/moderation/infractions.ts create mode 100644 src/plugins/moderation/kick.ts create mode 100644 src/plugins/moderation/mban.ts create mode 100644 src/plugins/moderation/mkick.ts create mode 100644 src/plugins/moderation/mute.ts create mode 100644 src/plugins/moderation/softban.ts create mode 100644 src/plugins/moderation/unmute.ts create mode 100644 src/plugins/moderation/warn.ts create mode 100644 src/structures/GuildMember.ts create mode 100644 src/structures/GuildStructure.ts create mode 100644 src/structures/structureLoader.ts diff --git a/package-lock.json b/package-lock.json index c9e2511..f3ecb8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -232,6 +232,21 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true + }, + "bl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz", + "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==", + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, "boxen": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-3.2.0.tgz", @@ -346,9 +361,24 @@ } }, "bson": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.3.tgz", - "integrity": "sha512-TdiJxMVnodVS7r0BdL42y/pqC9cL2iKynVwA0Ho3qbsQYr428veL3l7BQyuqiw+Q5SqqoT0m4srSY/BlZ9AxXg==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.0.3.tgz", + "integrity": "sha512-7uBjjxwOSuGLmoqGI1UXWpDGc0K2WjR7dC6iaOg4iriNZo6M2EEBb8co4dEPJ5ArYCebPMie0ecgX0TWF+ZUrQ==", + "dev": true, + "requires": { + "buffer": "^5.1.0", + "long": "^4.0.0" + } + }, + "buffer": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz", + "integrity": "sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } }, "builtin-modules": { "version": "1.1.1", @@ -519,6 +549,11 @@ } } }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, "cross-spawn": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", @@ -595,6 +630,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "denque": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", + "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" + }, "diff": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", @@ -602,7 +642,7 @@ "dev": true }, "discord.js": { - "version": "github:discordjs/discord.js#ee0b7c155a1767ed42e2756e53d56368e8b69929", + "version": "github:discordjs/discord.js#030d263a9e018a766c5f399624fe4f8ec2b7349c", "from": "github:discordjs/discord.js", "requires": { "@discordjs/collection": "^0.1.1", @@ -854,6 +894,12 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, "import-lazy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", @@ -885,8 +931,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.5", @@ -1049,6 +1094,11 @@ "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", "dev": true }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1129,6 +1179,12 @@ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "dev": true + }, "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", @@ -1252,15 +1308,29 @@ "minimist": "0.0.8" } }, + "module-alias": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", + "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" + }, "mongodb": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.4.1.tgz", - "integrity": "sha512-juqt5/Z42J4DcE7tG7UdVaTKmUC6zinF4yioPfpeOSNBieWSK6qCY+0tfGQcHLKrauWPDdMZVROHJOa8q2pWsA==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.2.tgz", + "integrity": "sha512-Lxt4th2tK2MxmkDBR5cMik+xEnkvhwg0BC5kGcHm9RBwaNEsrIryvV5istGXOHbnif5KslMpY1FbX6YbGJ/Trg==", "requires": { + "bl": "^2.2.0", "bson": "^1.1.1", + "denque": "^1.4.1", "require_optional": "^1.0.1", "safe-buffer": "^5.1.2", "saslprep": "^1.0.0" + }, + "dependencies": { + "bson": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.3.tgz", + "integrity": "sha512-TdiJxMVnodVS7r0BdL42y/pqC9cL2iKynVwA0Ho3qbsQYr428veL3l7BQyuqiw+Q5SqqoT0m4srSY/BlZ9AxXg==" + } } }, "mute-stream": { @@ -1455,9 +1525,14 @@ "dev": true }, "prism-media": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.1.0.tgz", - "integrity": "sha512-W+oxjRyjtd7hw3pefNZuc7YEZ6VICORJvVNfCPs0+7CsJ43CqMjGAYGjPL3hQ82vw03EVra+CiX4zisqOBUUGw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.0.tgz", + "integrity": "sha512-zjcO/BLVlfxWqFpEUlDyL1R9XXMquasNP4xpeYDPPZi/Zcz0i6OXoqcvxOLgbRVPsJXVd29vlYmRx2bts+hzEw==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "pseudomap": { "version": "1.0.2", @@ -1522,6 +1597,27 @@ "read-pkg": "^3.0.0" } }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, "redent": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", @@ -1749,6 +1845,21 @@ } } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -1930,9 +2041,9 @@ } }, "tweetnacl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.1.tgz", - "integrity": "sha512-kcoMoKTPYnoeS50tzoqjPY3Uv9axeuuFAZY9M/9zFnhoVvRfxz9K29IMPD7jGmt2c8SW7i3gT9WqDl2+nV7p4A==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.2.tgz", + "integrity": "sha512-+8aPRjmXgf1VqvyxSlBUzKzeYqVS9Ai8vZ28g+mL7dNQl1jlUTCMDZnvNQdAS1xTywMkIXwJsfipsR/6s2+syw==" }, "type-fest": { "version": "0.8.1", @@ -1950,11 +2061,16 @@ } }, "typescript": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.4.tgz", - "integrity": "sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw==", + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", + "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", "dev": true }, + "typy": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/typy/-/typy-3.3.0.tgz", + "integrity": "sha512-Du53deMF9X9pSM3gVXDjLBq14BUfZWSGKfmmR1kTlg953RaIZehfc8fQuoAiW+SRO6bJsP+59mv1tsH8vwKghg==" + }, "unique-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", @@ -2045,6 +2161,11 @@ "prepend-http": "^2.0.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", diff --git a/package.json b/package.json index d4119d1..0cefe1c 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,12 @@ "author": "Rauf", "license": "MIT", "devDependencies": { + "@types/bson": "^4.0.1", + "@types/node": "^10.17.13", + "@types/ws": "^6.0.4", + "bson": "^4.0.3", "gts": "^1.1.2", - "typescript": "~3.7.0", - "@types/node": "^10.0.3", - "@types/ws": "^6.0.4" + "typescript": "^3.7.5" }, "scripts": { "check": "gts check", @@ -25,6 +27,19 @@ "dependencies": { "@types/mongodb": "^3.3.14", "discord.js": "github:discordjs/discord.js", - "mongodb": "^3.4.1" + "module-alias": "^2.2.2", + "mongodb": "^3.5.2", + "typy": "^3.3.0" + }, + "_moduleAliases": { + "@lifeguard": "./build/src", + "@lifeguard/base": "./", + "@assertions": "./build/src/assertions", + "@config": "./build/src/config", + "@events": "./build/src/events", + "@models": "./build/src/models", + "@plugins": "./build/src/plugins", + "@structures": "./build/src/structures", + "@util": "./build/src/util" } } diff --git a/src/PluginClient.ts b/src/PluginClient.ts index 1fee445..42fd069 100644 --- a/src/PluginClient.ts +++ b/src/PluginClient.ts @@ -1,7 +1,7 @@ +import { name, url } from '@config/mongodb'; +import { Plugin } from '@plugins/Plugin'; +import { Database } from '@util/Database'; import { Client, ClientOptions, Collection } from 'discord.js'; -import { Plugin } from './plugins/Plugin'; -import { Database } from './util/Database'; -import { name, url } from './config/mongodb'; export class PluginClient extends Client { plugins!: Collection; diff --git a/src/assertions/userLevel.ts b/src/assertions/userLevel.ts index 90efd12..9b3b7e6 100644 --- a/src/assertions/userLevel.ts +++ b/src/assertions/userLevel.ts @@ -1,5 +1,5 @@ import { GuildMember, Guild } from 'discord.js'; -import { developers } from '../config/bot'; +import { developers } from '@config/bot'; export function calcUserLevel(user: GuildMember, guild: Guild) { if (developers.includes(user.id)) { diff --git a/src/events/channelCreate.ts b/src/events/channelCreate.ts new file mode 100644 index 0000000..f269e4a --- /dev/null +++ b/src/events/channelCreate.ts @@ -0,0 +1,35 @@ +import { Event } from '@events/Event'; +import { GuildStructure } from '@structures/GuildStructure'; +import { defaultEmbed } from '@util/DefaultEmbed'; +import { GuildChannel, TextChannel } from 'discord.js'; + +export const event = new Event( + 'channelCreate', + async (lifeguard, channel: GuildChannel) => { + const dbGuild = await (channel.guild as GuildStructure).db; + if (dbGuild?.config.channels?.logging) { + const modlog = channel.guild.channels.get( + dbGuild.config.channels.logging + ) as TextChannel; + + const auditLog = await channel.guild.fetchAuditLogs({ + type: 'CHANNEL_CREATE', + }); + const auditLogEntry = auditLog.entries.last(); + + // const embed = defaultEmbed() + // .setDescription( + // `:pencil: Channel ${channel.toString()} (${channel.id}) was created` + // ) + // .setTitle('Channel Create'); + + // if (auditLogEntry) { + // embed.setAuthor(`${auditLogEntry.executor.tag}`); + // } + + const embed = defaultEmbed(); + + modlog.send(embed); + } + } +); diff --git a/src/events/eventLoader.ts b/src/events/eventLoader.ts index 97f3edd..cbd7bef 100644 --- a/src/events/eventLoader.ts +++ b/src/events/eventLoader.ts @@ -1,7 +1,7 @@ +import { Event } from '@events/Event'; +import { PluginClient } from '@lifeguard/PluginClient'; import { readdir } from 'fs'; import { promisify } from 'util'; -import { Event } from './Event'; -import { PluginClient } from '../PluginClient'; const readDir = promisify(readdir); diff --git a/src/events/guildCreate.ts b/src/events/guildCreate.ts index ccf097f..8b4aaf7 100644 --- a/src/events/guildCreate.ts +++ b/src/events/guildCreate.ts @@ -1,13 +1,18 @@ -import { Event } from './Event'; +import { Event } from '@events/Event'; +import { Guild } from 'discord.js'; -export const event = new Event('guildCreate', lifeguard => { - if (lifeguard.user) { - lifeguard.user.setPresence({ - activity: { - name: `${lifeguard.users.size} people in the pool`, - type: 'WATCHING', - }, - status: 'online', - }); +export const event = new Event( + 'guildCreate', + async (lifeguard, guild: Guild) => { + await lifeguard.db.guilds.insertOne({ id: guild.id, config: {} }); + if (lifeguard.user) { + lifeguard.user.setPresence({ + activity: { + name: `${lifeguard.users.size} people in the pool`, + type: 'WATCHING', + }, + status: 'online', + }); + } } -}); +); diff --git a/src/events/lifeguardCommandUsed.ts b/src/events/lifeguardCommandUsed.ts index ad2a420..131aa68 100644 --- a/src/events/lifeguardCommandUsed.ts +++ b/src/events/lifeguardCommandUsed.ts @@ -1,9 +1,9 @@ -import { Event } from './Event'; +import { calcUserLevel } from '@assertions/userLevel'; +import { prefix } from '@config/bot'; +import { Event } from '@events/Event'; +import { PluginClient } from '@lifeguard/PluginClient'; +import { UserDoc } from '@models/User'; import { Message } from 'discord.js'; -import { prefix } from '../config/bot'; -import { PluginClient } from '../PluginClient'; -import { calcUserLevel } from '../assertions/userLevel'; -import { UserDoc } from '../models/User'; function parseContent(content: string) { const split = content.split(' '); @@ -14,8 +14,15 @@ function parseContent(content: string) { function getCommandFromPlugin(lifeguard: PluginClient, cmdName: string) { const plugin = lifeguard.plugins.find(p => p.has(cmdName)); - const command = plugin?.get(cmdName); - return command; + if (plugin) { + return plugin?.get(cmdName); + } else { + const plugins = [...lifeguard.plugins.values()]; + const cmds = plugins + .map(p => [...p.values()]) + .reduce((acc, val) => acc.concat(val), []); + return cmds.find(cmd => cmd.options.alias?.includes(cmdName)); + } } export const event = new Event( diff --git a/src/events/message.ts b/src/events/message.ts index 5cfc58c..359a322 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -1,20 +1,42 @@ -import { Event } from './Event'; +import { prefix } from '@config/bot'; +import { Event } from '@events/Event'; +import { Guild } from '@models/Guild'; +import { User } from '@models/User'; +import { GuildStructure } from '@structures/GuildStructure'; import { Message } from 'discord.js'; -import { prefix } from '../config/bot'; -import { User, UserDoc } from '../models/User'; export const event = new Event('message', async (lifeguard, msg: Message) => { - let dbUser = (await lifeguard.db.users.findOne({ + let dbUser = await lifeguard.db.users.findOne({ id: msg.author.id, - })) as UserDoc; + }); if (!dbUser) { - await lifeguard.db.users.insertOne( - new User({ id: msg.author.id, infractions: [] }) - ); - dbUser = (await lifeguard.db.users.findOne({ + await lifeguard.db.users.insertOne(new User({ id: msg.author.id })); + dbUser = await lifeguard.db.users.findOne({ id: msg.author.id, - })) as UserDoc; + }); } + + if (msg.guild) { + const dbGuild = await (msg.guild as GuildStructure).db; + if (!dbGuild) { + await lifeguard.db.guilds.insertOne(new Guild({ id: msg.guild.id })); + } + } + + if (dbUser) { + dbUser.stats.totalSentMessages++; + dbUser.stats.totalSentCharacters += msg.content.length; + dbUser.stats.totalCustomEmojisUsed += + msg.content.match(/<.[^ ]*>/)?.length ?? 0; + dbUser.stats.totalTimesMentionedAUser += msg.mentions.users.size; + dbUser.stats.totalSentAttachments += msg.attachments.size; + + await lifeguard.db.users.updateOne( + { id: dbUser.id }, + { $set: { stats: dbUser.stats } } + ); + } + if (msg.content.startsWith(prefix)) { lifeguard.emit('lifeguardCommandUsed', msg, dbUser); } diff --git a/src/events/messageReactionAdd.ts b/src/events/messageReactionAdd.ts new file mode 100644 index 0000000..40d142f --- /dev/null +++ b/src/events/messageReactionAdd.ts @@ -0,0 +1,20 @@ +import { Event } from '@events/Event'; +import { GuildStructure } from '@structures/GuildStructure'; +import { MessageReaction, User } from 'discord.js'; + +export const event = new Event( + 'messageReactionAdd', + async (lifeguard, reaction: MessageReaction, user: User) => { + await lifeguard.db.users.updateOne( + { id: user.id }, + { $inc: { 'stats.totalTimesReacted': 1 } } + ); + const dbGuild = await (reaction.message.guild as GuildStructure).db; + if ( + dbGuild?.config.channels?.starboard && + reaction.emoji.name === dbGuild?.config.starboard?.emoji + ) { + lifeguard.emit('starboardReactionAdd', reaction); + } + } +); diff --git a/src/events/ready.ts b/src/events/ready.ts index dd6a506..69ac7f6 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,4 +1,4 @@ -import { Event } from './Event'; +import { Event } from '@events/Event'; export const event = new Event('ready', lifeguard => { console.log('Connected to Discord'); diff --git a/src/events/starboardReactionAdd.ts b/src/events/starboardReactionAdd.ts new file mode 100644 index 0000000..ecc6043 --- /dev/null +++ b/src/events/starboardReactionAdd.ts @@ -0,0 +1,64 @@ +import { Event } from '@events/Event'; +import { GuildStructure } from '@structures/GuildStructure'; +import { defaultEmbed } from '@util/DefaultEmbed'; +import { TextChannel, MessageReaction } from 'discord.js'; + +export const event = new Event( + 'starboardReactionAdd', + async (lifeguard, reaction: MessageReaction) => { + const dbGuild = await (reaction.message.guild as GuildStructure).db; + if (dbGuild?.config.starboard && dbGuild.config.channels?.starboard) { + const starboardChannel = reaction.message.guild?.channels.get( + dbGuild.config.channels.starboard + ) as TextChannel; + const starboard = dbGuild.config.starboard; + if ( + starboardChannel && + !starboard.ignoredChannels.includes(reaction.message.channel.id) + ) { + if (reaction.count ?? 0 >= starboard.minCount) { + const starboardMessage = starboard.messages.find( + m => m.id === reaction.message.id + ); + if (starboardMessage) { + const starboardMessageInChannel = starboardChannel.messages.get( + starboardMessage.starboardID + ); + starboardMessage.count = reaction.count ?? starboardMessage.count; + const embed = defaultEmbed() + .setAuthor( + starboardMessageInChannel?.author.tag, + starboardMessageInChannel?.author.avatarURL() ?? '' + ) + .setDescription(reaction.message.content); + starboardMessageInChannel?.edit( + `${starboard.emoji} ${reaction.count} ${reaction.message.channel} (${reaction.message.id})`, + embed + ); + } else { + const embed = defaultEmbed() + .setAuthor( + reaction.message.author.tag, + reaction.message.author.avatarURL() ?? '' + ) + .setDescription(reaction.message.content); + const starboardMessage = await starboardChannel.send( + `${starboard.emoji} ${reaction.count} ${reaction.message.channel} (${reaction.message.id})`, + embed + ); + starboard.messages.push({ + id: reaction.message.id, + starboardID: starboardMessage.id, + content: reaction.message.content, + count: reaction.count ?? 0, + }); + } + await lifeguard.db.guilds.updateOne( + { id: dbGuild.id }, + { $set: { 'config.starboard': starboard } } + ); + } + } + } + } +); diff --git a/src/index.ts b/src/index.ts index f77e02a..03c1d85 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,12 @@ -import { token } from './config/bot'; -import { EventLoader } from './events/eventLoader'; -import { PluginLoader } from './plugins/pluginLoader'; -import { PluginClient } from './PluginClient'; +import 'module-alias/register'; + +import { token } from '@config/bot'; +import { EventLoader } from '@events/eventLoader'; +import { PluginClient } from '@lifeguard/PluginClient'; +import { PluginLoader } from '@plugins/pluginLoader'; +import { StructureLoader } from '@structures/structureLoader'; + +StructureLoader(); const lifeguard = new PluginClient(); diff --git a/src/models/Guild.ts b/src/models/Guild.ts new file mode 100644 index 0000000..4b5ac0e --- /dev/null +++ b/src/models/Guild.ts @@ -0,0 +1,52 @@ +import { ObjectId } from 'bson'; + +export interface GuildChannels { + logging?: string; + starboard?: string; +} + +export interface GuildRoles { + [key: string]: string | string[] | undefined; + muted?: string; + moderator?: string; + groupRoles?: string[]; + lockedRoles?: string[]; +} + +export interface GuildStarboardMessage { + id: string; + starboardID: string; + count: number; + content: string; +} + +export interface GuildStarboard { + emoji: string; + minCount: number; + ignoredChannels: string[]; + messages: GuildStarboardMessage[]; +} + +export interface GuildConfig { + blacklisted?: boolean; + channels?: GuildChannels; + roles?: GuildRoles; + starboard?: GuildStarboard; +} + +export interface GuildDoc { + id: string; + config?: GuildConfig; +} + +export class Guild implements GuildDoc { + _id: ObjectId; + id: string; + config: GuildConfig; + constructor(data: GuildDoc) { + this._id = new ObjectId(); + this.id = data.id; + this.config = data.config ?? {}; + this.config.blacklisted = data.config?.blacklisted ?? false; + } +} diff --git a/src/models/User.ts b/src/models/User.ts index fa98595..eb1e377 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -1,5 +1,8 @@ -interface UserInfraction { - action: 'Warn' | 'Mute' | 'Tempban' | 'Ban'; +import { ObjectId } from 'bson'; +import { PermissionOverwriteOption } from 'discord.js'; + +export interface UserInfraction { + action: 'Warn' | 'Kick' | 'Mute' | 'Ban'; active: boolean; guild: string; id: number; @@ -8,19 +11,68 @@ interface UserInfraction { time: Date; } +export interface UserBackup { + guild: string; + id: number; + roles: string[]; + nickname: string; + channelOverrides: UserBackupChannelOverride[]; + deafened: boolean; + muted: boolean; + createdAt: Date; +} + +export interface UserBackupChannelOverride { + channelId: number; + overrides: PermissionOverwriteOption; +} + +export interface UserStats { + totalSentMessages: number; + totalSentCharacters: number; + totalDeletedMessages: number; + totalCustomEmojisUsed: number; + totalTimesMentionedAUser: number; + totalSentAttachments: number; + totalTimesReacted: number; + mostUsedReaction: string; +} + +export interface UserReactions { + [name: string]: number; +} + export interface UserDoc { blacklisted?: boolean; id: string; - infractions: UserInfraction[]; + infractions?: UserInfraction[]; + backups?: UserBackup[]; + stats?: UserStats; + reactions?: UserReactions; } export class User implements UserDoc { + _id: ObjectId; blacklisted: boolean; id: string; infractions: UserInfraction[]; + backups: UserBackup[]; + stats: UserStats; constructor(data: UserDoc) { + this._id = new ObjectId(); this.blacklisted = data.blacklisted ?? false; this.id = data.id; - this.infractions = data.infractions; + this.infractions = data.infractions ?? []; + this.backups = data.backups ?? []; + this.stats = data.stats ?? { + totalSentMessages: 0, + totalSentCharacters: 0, + totalDeletedMessages: 0, + totalCustomEmojisUsed: 0, + totalTimesMentionedAUser: 0, + totalSentAttachments: 0, + totalTimesReacted: 0, + mostUsedReaction: '', + }; } } diff --git a/src/plugins/Command.ts b/src/plugins/Command.ts index d9ce75b..235018a 100644 --- a/src/plugins/Command.ts +++ b/src/plugins/Command.ts @@ -1,12 +1,12 @@ import { Message, PermissionString } from 'discord.js'; -import { PluginClient } from '../PluginClient'; -import { UserDoc } from '../models/User'; +import { PluginClient } from '@lifeguard/PluginClient'; +import { UserDoc } from '@models/User'; type CommandFunction = ( lifeguard: PluginClient, msg: Message, args: string[], - dbUser: UserDoc + dbUser?: UserDoc ) => void; interface CommandOptions { diff --git a/src/plugins/Plugin.ts b/src/plugins/Plugin.ts index 861611d..5ea5b89 100644 --- a/src/plugins/Plugin.ts +++ b/src/plugins/Plugin.ts @@ -1,5 +1,5 @@ import { Collection } from 'discord.js'; -import { Command } from './Command'; +import { Command } from '@plugins/Command'; export class Plugin extends Collection { constructor() { diff --git a/src/plugins/admin/config.ts b/src/plugins/admin/config.ts new file mode 100644 index 0000000..62d5152 --- /dev/null +++ b/src/plugins/admin/config.ts @@ -0,0 +1,46 @@ +import { Command } from '@plugins/Command'; +import { t as typy } from 'typy'; + +export const command = new Command( + 'config', + async (lifeguard, msg, [cmd, ...args]) => { + const path = args[0]; + switch (cmd) { + case 'get': + const guild = await lifeguard.db.guilds.findOne({ id: msg.guild?.id }); + if (guild) { + const config = guild['config']; + if (path) { + msg.channel.send(`${path} - ${typy(config, path).safeObject}`); + } else { + msg.channel.send( + `\`\`\`json\n${JSON.stringify(config, null, 2)}\`\`\`` + ); + } + } + break; + + case 'set': + const res = await lifeguard.db.guilds.findOneAndUpdate( + { + id: msg.guild?.id, + }, + { + $set: { [`config.${path}`]: JSON.parse(args[1]) }, + } + ); + if (res.ok) { + msg.channel.send('Value has been set successfully'); + } + break; + + default: + break; + } + }, + { + level: 3, + usage: ['config get', 'config get {key}', 'config set {key} {value}'], + hidden: true, + } +); diff --git a/src/plugins/moderation/role.ts b/src/plugins/admin/role.ts similarity index 90% rename from src/plugins/moderation/role.ts rename to src/plugins/admin/role.ts index 3ef1e80..d1929e5 100644 --- a/src/plugins/moderation/role.ts +++ b/src/plugins/admin/role.ts @@ -1,5 +1,5 @@ -import { Command } from '../Command'; -import { parseUser } from '../../util/parseUser'; +import { Command } from '@plugins/Command'; +import { parseUser } from '@util/parseUser'; export const command = new Command( 'role', diff --git a/src/plugins/admin/roles.ts b/src/plugins/admin/roles.ts new file mode 100644 index 0000000..ce1fc66 --- /dev/null +++ b/src/plugins/admin/roles.ts @@ -0,0 +1,31 @@ +import { Command } from '@plugins/Command'; + +export const command = new Command( + 'roles', + async (lifeguard, msg) => { + const roleList = msg.guild?.roles + ?.sort((ra, rb) => rb.position - ra.position) + .map(r => `${r.id} - ${r.name} (${r.members.size} members)`); + const blocks: string[] = ['']; + + roleList?.forEach(r => { + let currentBlockIndex: number = blocks.length - 1; + + if ( + blocks[currentBlockIndex].length > 1990 || + blocks[currentBlockIndex].concat(`\n${r}`).length > 1990 + ) { + blocks.push(''); + currentBlockIndex++; + } + + blocks[currentBlockIndex] += `\n${r}`; + }); + + blocks.forEach(b => msg.channel.send(`\`\`\`dns${b}\`\`\``)); + }, + { + level: 1, + usage: ['roles'], + } +); diff --git a/src/plugins/admin/slowmode.ts b/src/plugins/admin/slowmode.ts new file mode 100644 index 0000000..f93dc38 --- /dev/null +++ b/src/plugins/admin/slowmode.ts @@ -0,0 +1,33 @@ +import { Command } from '@plugins/Command'; +import { TextChannel } from 'discord.js'; + +export const command = new Command( + 'slowmode', + async (lifeguard, msg, args) => { + const [cmd, time] = args; + switch (cmd) { + case 'set': + if (msg.guild) { + (msg.channel as TextChannel).setRateLimitPerUser(+time); + msg.channel.send( + `Slowmode has been set to 1 message every ${time} seconds` + ); + } + break; + + case 'off': + if (msg.guild) { + (msg.channel as TextChannel).setRateLimitPerUser(0); + msg.channel.send(`Slowmode has been turned off`); + } + break; + + default: + break; + } + }, + { + level: 1, + usage: ['slowmode set {time}', 'slowmode off'], + } +); diff --git a/src/plugins/debug/ping.ts b/src/plugins/debug/ping.ts index 51aee23..b6c308f 100644 --- a/src/plugins/debug/ping.ts +++ b/src/plugins/debug/ping.ts @@ -1,5 +1,5 @@ -import { Command } from '../Command'; -import { defaultEmbed } from '../../util/DefaultEmbed'; +import { Command } from '@plugins/Command'; +import { defaultEmbed } from '@util/DefaultEmbed'; export const command = new Command( 'ping', diff --git a/src/plugins/dev/eval.ts b/src/plugins/dev/eval.ts index e592c74..9f1c987 100644 --- a/src/plugins/dev/eval.ts +++ b/src/plugins/dev/eval.ts @@ -1,7 +1,7 @@ -import { Command } from '../Command'; +import { Command } from '@plugins/Command'; +import { defaultEmbed } from '@util/DefaultEmbed'; import { inspect } from 'util'; import { runInNewContext } from 'vm'; -import { defaultEmbed } from '../../util/DefaultEmbed'; function parseBlock(script: string) { const cbr = /^(([ \t]*`{3,4})([^\n]*)([\s\S]+?)(^[ \t]*\2))/gm; diff --git a/src/plugins/global/blacklist.ts b/src/plugins/global/blacklist.ts index 972f4ad..6f3ec84 100644 --- a/src/plugins/global/blacklist.ts +++ b/src/plugins/global/blacklist.ts @@ -1,5 +1,5 @@ -import { Command } from '../Command'; -import { parseUser } from '../../util/parseUser'; +import { Command } from '@plugins/Command'; +import { parseUser } from '@util/parseUser'; export const command = new Command( 'blacklist', diff --git a/src/plugins/info/about.ts b/src/plugins/info/about.ts index 2eab56f..2b6bade 100644 --- a/src/plugins/info/about.ts +++ b/src/plugins/info/about.ts @@ -1,8 +1,8 @@ -import { Command } from '../Command'; -import { promisify } from 'util'; +import { Command } from '@plugins/Command'; import { exec } from 'child_process'; -import { resolve } from 'path'; import { MessageEmbed } from 'discord.js'; +import { resolve } from 'path'; +import { promisify } from 'util'; export const command = new Command( 'about', @@ -23,7 +23,9 @@ export const command = new Command( const { version: discordjsVersion } = require('discord.js/package.json'); // Get Lifeguard Version - const { version: lifeguardVersion } = require('../../../../package.json'); + const { + version: lifeguardVersion, + } = require('@lifeguard/base/package.json'); const embed = new MessageEmbed() .setTitle('About Lifeguard') diff --git a/src/plugins/info/help.ts b/src/plugins/info/help.ts index d9f5052..9ec9972 100644 --- a/src/plugins/info/help.ts +++ b/src/plugins/info/help.ts @@ -1,8 +1,8 @@ -import { Command } from '../Command'; -import { MessageEmbed, Collection, GuildMember, Guild } from 'discord.js'; -import { Plugin } from '../Plugin'; -import { calcUserLevel } from '../../assertions/userLevel'; -import { defaultEmbed } from '../../util/DefaultEmbed'; +import { calcUserLevel } from '@assertions/userLevel'; +import { Command } from '@plugins/Command'; +import { Plugin } from '@plugins/Plugin'; +import { defaultEmbed } from '@util/DefaultEmbed'; +import { Collection, Guild, GuildMember } from 'discord.js'; function convertPlugins( plugins: Collection, @@ -39,8 +39,6 @@ export const command = new Command( ); for (const plugin of plugins) { - // console.log(plugin) - // embed.addField(plugin.name, plugin.cmds.join('\n')); if (plugin.cmds.length > 0) { embed.addField(plugin.name, plugin.cmds.join('\n')); } diff --git a/src/plugins/moderation/ban.ts b/src/plugins/moderation/ban.ts new file mode 100644 index 0000000..f757f32 --- /dev/null +++ b/src/plugins/moderation/ban.ts @@ -0,0 +1,55 @@ +import { UserInfraction } from '@models/User'; +import { Command } from '@plugins/Command'; +import { parseUser } from '@util/parseUser'; + +export const command = new Command( + 'ban', + async (lifeguard, msg, [uid, ...reason]) => { + // Parse user id from mention + const u = parseUser(uid); + try { + // Create Infraction + const inf: UserInfraction = { + action: 'Ban', + active: true, + guild: msg.guild?.id as string, + id: (await lifeguard.db.users.findOne({ id: u }))?.infractions + .length as number, + moderator: msg.author.id, + reason: reason.join(' '), + time: new Date(), + }; + + // Update User in Database + await lifeguard.db.users.findOneAndUpdate( + { id: u }, + { $push: { infractions: inf } }, + { returnOriginal: false } + ); + + // Get User + const member = await msg.guild?.members.get(u); + // Notify User about Action + member?.send( + `You have been banned from **${msg.guild?.name}** for \`${ + reason.length > 0 ? reason.join(' ') : 'No Reason Specified' + }\`` + ); + // Ban User + member?.ban({ reason: reason.join(' ') }); + + // Tell moderator action was successful + msg.channel.send( + `${member?.user.tag} was banned by ${msg.author.tag} for \`${ + reason.length > 0 ? reason.join(' ') : 'No Reason Specified' + }\`` + ); + } catch (err) { + msg.channel.send(err.message); + } + }, + { + level: 1, + usage: ['ban {user} [reason]'], + } +); diff --git a/src/plugins/moderation/forceban.ts b/src/plugins/moderation/forceban.ts new file mode 100644 index 0000000..8befde6 --- /dev/null +++ b/src/plugins/moderation/forceban.ts @@ -0,0 +1,60 @@ +import { UserInfraction } from '@models/User'; +import { Command } from '@plugins/Command'; +import { parseUser } from '@util/parseUser'; +import { GuildMember, User } from 'discord.js'; + +export const command = new Command( + 'forceban', + async (lifeguard, msg, [uid, ...reason]) => { + // Parse user id from mention + const u = parseUser(uid); + try { + // Create Infraction + const inf: UserInfraction = { + action: 'Ban', + active: true, + guild: msg.guild?.id as string, + id: (await lifeguard.db.users.findOne({ id: u }))?.infractions + .length as number, + moderator: msg.author.id, + reason: reason.join(' '), + time: new Date(), + }; + + // Update User in Database + await lifeguard.db.users.findOneAndUpdate( + { id: u }, + { $push: { infractions: inf } }, + { returnOriginal: false } + ); + + // Ban user from guild + const member = await msg.guild?.members.ban(u, { + reason: reason.join(' '), + }); + + // Retreive user tag + let tag; + if (member instanceof GuildMember) { + tag = member.user.tag; + } else if (member instanceof User) { + tag = member.tag; + } else { + tag = member; + } + + // Tell moderator ban was successful + msg.channel.send( + `${tag} was force-banned by ${msg.author.toString()} for \`${ + reason.length > 0 ? reason.join(' ') : 'No Reason Specified' + }\`` + ); + } catch (err) { + msg.channel.send(err.message); + } + }, + { + level: 1, + usage: ['forceban {user} [reason]'], + } +); diff --git a/src/plugins/moderation/infractions.ts b/src/plugins/moderation/infractions.ts new file mode 100644 index 0000000..d1432e7 --- /dev/null +++ b/src/plugins/moderation/infractions.ts @@ -0,0 +1,54 @@ +import { Command } from '@plugins/Command'; +import { parseUser } from '@util/parseUser'; +import { MessageAttachment } from 'discord.js'; + +export const command = new Command( + 'infractions', + async (lifeguard, msg, [cmd, ...args], dbUser) => { + switch (cmd) { + case 'archive': + // Get users from Database that have infractions + const dbUsers = lifeguard.db.users.find({ + infractions: { $elemMatch: { guild: msg.guild?.id } }, + }); + + const guildInfractions = ( + await dbUsers + // Filter out infractions not from current guild + .map(u => u.infractions.filter(inf => inf.guild === msg.guild?.id)) + .toArray() + ) + // Flatten Array + .reduce((acc, val) => acc.concat(val), []); + + // Send archive as JSON file + msg.channel.send( + new MessageAttachment( + Buffer.from(JSON.stringify(guildInfractions, null, 2)), + `${msg.guild?.id}.infractions.json` + ) + ); + break; + + case 'info': + const [user, infID] = args; + const u = parseUser(user); + const dbUser = await lifeguard.db.users.findOne({ id: u }); + const inf = dbUser?.infractions.find(inf => inf.id === +infID); + msg.channel.send(`\`\`\`json\n${JSON.stringify(inf, null, 2)}\n\`\`\``); + break; + + default: + break; + } + }, + { + level: 1, + usage: [ + 'infractions archive', + 'infractions search {user}', + 'infractions info {user} {id}', + ], + alias: ['inf'], + } +); diff --git a/src/plugins/moderation/kick.ts b/src/plugins/moderation/kick.ts new file mode 100644 index 0000000..5c66e9f --- /dev/null +++ b/src/plugins/moderation/kick.ts @@ -0,0 +1,55 @@ +import { UserInfraction } from '@models/User'; +import { Command } from '@plugins/Command'; +import { parseUser } from '@util/parseUser'; + +export const command = new Command( + 'kick', + async (lifeguard, msg, [uid, ...reason]) => { + // Parse user id from mention + const u = parseUser(uid); + try { + // Create Infraction + const inf: UserInfraction = { + action: 'Kick', + active: true, + guild: msg.guild?.id as string, + id: (await lifeguard.db.users.findOne({ id: u }))?.infractions + .length as number, + moderator: msg.author.id, + reason: reason.join(' '), + time: new Date(), + }; + + // Update User in Database + await lifeguard.db.users.findOneAndUpdate( + { id: u }, + { $push: { infractions: inf } }, + { returnOriginal: false } + ); + + // Get User + const member = await msg.guild?.members.get(u); + // Notify User about Action + member?.send( + `You have been kicked from **${msg.guild?.name}** for \`${reason.join( + ' ' + )}\`` + ); + // Ban the User + member?.kick(reason.join(' ')); + + // Tell moderator action was successful + msg.channel.send( + `${member?.user.tag} was kicked by ${msg.author.tag} for \`${ + reason.length > 0 ? reason.join(' ') : 'No Reason Specified' + }\`` + ); + } catch (err) { + msg.channel.send(err.message); + } + }, + { + level: 1, + usage: ['kick {user} [reason]'], + } +); diff --git a/src/plugins/moderation/mban.ts b/src/plugins/moderation/mban.ts new file mode 100644 index 0000000..097c5e9 --- /dev/null +++ b/src/plugins/moderation/mban.ts @@ -0,0 +1,20 @@ +import { Command } from '@plugins/Command'; +import { command as ban } from '@plugins/moderation/ban'; + +export const command = new Command( + 'mban', + async (lifeguard, msg, args) => { + // Find where '-r' is in the args + const reasonFlagIndex = args.indexOf('-r') || args.length; + // Get users from args + const users = args.slice(0, reasonFlagIndex); + // Get reason from args + const reason = args.slice(reasonFlagIndex + 1).join(' '); + // Run ban command for each user + users.forEach(user => ban.func(lifeguard, msg, [user, reason])); + }, + { + level: 1, + usage: ['mban {users} -r [reason]'], + } +); diff --git a/src/plugins/moderation/mkick.ts b/src/plugins/moderation/mkick.ts new file mode 100644 index 0000000..a319c1e --- /dev/null +++ b/src/plugins/moderation/mkick.ts @@ -0,0 +1,20 @@ +import { Command } from '@plugins/Command'; +import { command as kick } from '@plugins/moderation/kick'; + +export const command = new Command( + 'mkick', + async (lifeguard, msg, args) => { + // Find where '-r' is in the args + const reasonFlagIndex = args.indexOf('-r'); + // Get users from args + const uids = args.slice(0, reasonFlagIndex); + // Get reason from args + const reason = args.slice(reasonFlagIndex + 1).join(' '); + // Run ban command for each user + uids.forEach(uid => kick.func(lifeguard, msg, [uid, reason])); + }, + { + level: 1, + usage: ['mkick {users} -r [reason]'], + } +); diff --git a/src/plugins/moderation/mute.ts b/src/plugins/moderation/mute.ts new file mode 100644 index 0000000..5a54a28 --- /dev/null +++ b/src/plugins/moderation/mute.ts @@ -0,0 +1,59 @@ +import { UserInfraction } from '@models/User'; +import { Command } from '@plugins/Command'; +import { defaultEmbed } from '@util/DefaultEmbed'; +import { parseUser } from '@util/parseUser'; + +export const command = new Command( + 'mute', + async (lifeguard, msg, [uid, ...reason]) => { + // Get guild from db + const guild = await lifeguard.db.guilds.findOne({ id: msg.guild?.id }); + // Check if muted role exists + if (guild?.config.roles?.muted) { + // Parse user id from mention + const u = parseUser(uid); + try { + // Create Infracrion + const inf: UserInfraction = { + action: 'Mute', + active: true, + guild: msg.guild?.id as string, + id: (await lifeguard.db.users.findOne({ id: u }))?.infractions + .length as number, + moderator: msg.author.id, + reason: reason.join(' '), + time: new Date(), + }; + // Update User in Database + await lifeguard.db.users.findOneAndUpdate( + { id: u }, + { $push: { infractions: inf } }, + { returnOriginal: false } + ); + + // Get User + const member = msg.guild?.members.get(u); + // Add role to user + await member?.roles.add(guild.config.roles.muted); + + // Tell moderator action was successfull + msg.channel.send( + `${member?.user.tag} was muted by ${msg.author.tag} for \`${ + reason.length > 0 ? reason.join(' ') : 'No Reason Specified' + }\`` + ); + } catch (err) { + msg.channel.send(err.message); + } + } else { + const embed = defaultEmbed() + .setTitle(':rotating_light: Error! :rotating_light:') + .setDescription('No mute role configured!'); + msg.channel.send(embed); + } + }, + { + level: 1, + usage: ['mute {user} [reason]'], + } +); diff --git a/src/plugins/moderation/softban.ts b/src/plugins/moderation/softban.ts new file mode 100644 index 0000000..1791cd8 --- /dev/null +++ b/src/plugins/moderation/softban.ts @@ -0,0 +1,57 @@ +import { UserInfraction } from '@models/User'; +import { Command } from '@plugins/Command'; +import { parseUser } from '@util/parseUser'; + +export const command = new Command( + 'softban', + async (lifeguard, msg, [uid, ...reason]) => { + // Parse user id from mention + const u = parseUser(uid); + try { + // Create Infraction + const inf: UserInfraction = { + action: 'Ban', + active: true, + guild: msg.guild?.id as string, + id: (await lifeguard.db.users.findOne({ id: u }))?.infractions + .length as number, + moderator: msg.author.id, + reason: reason.join(' '), + time: new Date(), + }; + + // Update User in Database + await lifeguard.db.users.findOneAndUpdate( + { id: u }, + { $push: { infractions: inf } }, + { returnOriginal: false } + ); + + // Get User + const member = await msg.guild?.members.get(u); + // Notify user of action + member?.send( + `You have been soft-banned from **${msg.guild?.name}** for \`${ + reason.length > 0 ? reason.join(' ') : 'No Reason Specified' + }` + ); + // Ban User + member?.ban({ reason: reason.join(' '), days: 7 }); + // Unban User + await msg.guild?.members.unban(u, reason.join(' ')); + + // Tell moderator action was successfull + msg.channel.send( + `${member?.user.tag} was soft-banned by ${msg.author.tag} for \`${ + reason.length > 0 ? reason.join(' ') : 'No Reason Specified' + }` + ); + } catch (err) { + msg.channel.send(err.message); + } + }, + { + level: 1, + usage: ['softban {user} [reason]'], + } +); diff --git a/src/plugins/moderation/unmute.ts b/src/plugins/moderation/unmute.ts new file mode 100644 index 0000000..fefe0da --- /dev/null +++ b/src/plugins/moderation/unmute.ts @@ -0,0 +1,39 @@ +import { Command } from '@plugins/Command'; +import { defaultEmbed } from '@util/DefaultEmbed'; +import { parseUser } from '@util/parseUser'; + +export const command = new Command( + 'unmute', + async (lifeguard, msg, [uid, ...reason]) => { + // Get guild from database + const guild = await lifeguard.db.guilds.findOne({ id: msg.guild?.id }); + // Check if muted role exists + if (guild?.config.roles?.muted) { + // Parse user id from mention + const u = parseUser(uid); + try { + // Get User + const member = msg.guild?.members.get(u); + // Remove Role + await member?.roles.remove(guild.config.roles.muted); + // Tell moderator action was successfull + msg.channel.send( + `${member?.user.tag} was unmuted by ${msg.author.tag} for \`${ + reason.length > 0 ? reason.join(' ') : 'No Reason Specified' + }\`` + ); + } catch (err) { + msg.channel.send(err.message); + } + } else { + const embed = defaultEmbed() + .setTitle(':rotating_light: Error! :rotating_light:') + .setDescription('No mute role configured!'); + msg.channel.send(embed); + } + }, + { + level: 1, + usage: ['unmute {user} [reason]'], + } +); diff --git a/src/plugins/moderation/warn.ts b/src/plugins/moderation/warn.ts new file mode 100644 index 0000000..24fe26d --- /dev/null +++ b/src/plugins/moderation/warn.ts @@ -0,0 +1,47 @@ +import { UserInfraction } from '@models/User'; +import { Command } from '@plugins/Command'; +import { parseUser } from '@util/parseUser'; + +export const command = new Command( + 'warn', + async (lifeguard, msg, [uid, ...reason]) => { + // Parse user id from mention + const u = parseUser(uid); + try { + // Create Infracion + const inf: UserInfraction = { + action: 'Warn', + active: true, + guild: msg.guild?.id as string, + id: (await lifeguard.db.users.findOne({ id: u }))?.infractions + .length as number, + moderator: msg.author.id, + reason: reason.join(' '), + time: new Date(), + }; + + // Update User in Database + await lifeguard.db.users.findOneAndUpdate( + { id: u }, + { $push: { infractions: inf } }, + { returnOriginal: false } + ); + + // Get User + const member = msg.guild?.members.get(u); + + // Tell moderator action was sucessfull + msg.channel.send( + `${member?.user.tag} was warned by ${msg.author.tag} for \`${ + reason.length > 0 ? reason.join(' ') : 'No Reason Specified' + }\`` + ); + } catch (err) { + msg.channel.send(err.message); + } + }, + { + level: 1, + usage: ['warn {user} [reason]'], + } +); diff --git a/src/structures/GuildMember.ts b/src/structures/GuildMember.ts new file mode 100644 index 0000000..294aeef --- /dev/null +++ b/src/structures/GuildMember.ts @@ -0,0 +1,29 @@ +import { Structures, Guild, GuildMember } from 'discord.js'; +import { PluginClient } from 'PluginClient'; +import { inspect } from 'util'; + +Structures.extend('GuildMember', guildMember => { + return class extends guildMember { + _client: PluginClient; + constructor(client: PluginClient, data: object, guild: Guild) { + super(client, data, guild); + this._client = client; + } + + get db() { + return this._client.db.users.findOne({ id: this.user.id }); + } + }; +}); + +export class GuildMemberStructure extends GuildMember { + _client: PluginClient; + constructor(client: PluginClient, data: object, guild: Guild) { + super(client, data, guild); + this._client = client; + } + + get db() { + return this._client.db.users.findOne({ id: this.user.id }); + } +} diff --git a/src/structures/GuildStructure.ts b/src/structures/GuildStructure.ts new file mode 100644 index 0000000..968c3a8 --- /dev/null +++ b/src/structures/GuildStructure.ts @@ -0,0 +1,29 @@ +import { Structures, Guild } from 'discord.js'; +import { PluginClient } from 'PluginClient'; +import { inspect } from 'util'; + +Structures.extend('Guild', guildClass => { + return class extends guildClass { + _client: PluginClient; + constructor(client: PluginClient, data: object) { + super(client, data); + this._client = client; + } + + get db() { + return this._client.db.guilds.findOne({ id: this.id }); + } + }; +}); + +export class GuildStructure extends Guild { + _client: PluginClient; + constructor(client: PluginClient, data: object) { + super(client, data); + this._client = client; + } + + get db() { + return this._client.db.guilds.findOne({ id: this.id }); + } +} diff --git a/src/structures/structureLoader.ts b/src/structures/structureLoader.ts new file mode 100644 index 0000000..63cb177 --- /dev/null +++ b/src/structures/structureLoader.ts @@ -0,0 +1,11 @@ +import { readdirSync } from 'fs'; + +export function StructureLoader() { + const structureFiles = readdirSync('./build/src/structures'); + + for (const file of structureFiles) { + if (file !== 'structureLoader.js' && file.endsWith('js')) { + require(`./${file}`); + } + } +} diff --git a/src/util/Database.ts b/src/util/Database.ts index 1f57bc5..a31035e 100644 --- a/src/util/Database.ts +++ b/src/util/Database.ts @@ -1,4 +1,6 @@ -import { connect, Db, MongoClientOptions } from 'mongodb'; +import { connect, Db, MongoClientOptions, Collection } from 'mongodb'; +import { User } from '../models/User'; +import { Guild } from '../models/Guild'; interface DatabaseConfig { url: string; @@ -20,11 +22,11 @@ export class Database { this.db = client.db(this.config.name); } - get guilds() { + get guilds(): Collection { return this.db.collection('guilds'); } - get users() { + get users(): Collection { return this.db.collection('users'); } } diff --git a/tsconfig.json b/tsconfig.json index d1646f0..a03da9c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,10 +2,19 @@ "extends": "./node_modules/gts/tsconfig-google.json", "compilerOptions": { "rootDir": ".", - "outDir": "build" + "outDir": "build", + "baseUrl": "./src", + "paths": { + "@lifeguard/*": ["./*"], + "@lifegaurd/base/*": ["../*"], + "@assertions/*": ["./assertions/*"], + "@config/*": ["./config/*"], + "@events/*": ["./events/*"], + "@models/*": ["./models/*"], + "@plugins/*": ["./plugins/*"], + "@structures/*": ["./structures/*"], + "@util/*": ["./util/*"] + } }, - "include": [ - "src/**/*.ts", - "test/**/*.ts" - ] + "include": ["src/**/*.ts", "test/**/*.ts"] }