9 Commits e635090685 ... 51177cc716

Author SHA1 Message Date
  ghorsington 51177cc716 Adjust message 3 years ago
  ghorsington fe185b4ffb Add ability to autoadd roles 3 years ago
  ghorsington c95464a72f Cleanup 3 years ago
  ghorsington 37ab09d4a5 Report (un)mute fail 3 years ago
  ghorsington 54f4ea9374 Adjust role setup 3 years ago
  ghorsington dca8ba9528 Check for mute on verify 3 years ago
  ghorsington 6dec6bc60e Automatically remove roles on mute 3 years ago
  ghorsington c196b6fb9c Add quick verify button for debug 3 years ago
  ghorsington 66d7da1491 Update packages 3 years ago

+ 1 - 0
.env.template

@@ -18,6 +18,7 @@ NOCTBOT_ADDR=
 RPC_PORT=
 WEB_COOKIE_KEY=
 WEB_AUTH_URI=
+HCAPTCHA_ENABLED=
 HCAPTCHA_SITEKEY=
 HCAPTCHA_SECRET=
 

File diff suppressed because it is too large
+ 810 - 898
bot/package-lock.json


+ 29 - 27
bot/package.json

@@ -26,60 +26,62 @@
       "src": "./build"
    },
    "dependencies": {
-      "@types/cheerio": "^0.22.22",
+      "@types/cheerio": "^0.22.28",
       "@types/humanize-duration": "^3.18.1",
-      "@types/koa": "^2.11.6",
-      "@types/koa-router": "^7.4.1",
+      "@types/koa": "^2.13.1",
+      "@types/koa-router": "^7.4.2",
       "@types/lowdb": "^1.0.9",
       "@types/node-schedule": "^1.3.1",
       "@types/sha1": "^1.1.2",
       "@types/turndown": "^5.0.0",
-      "@types/ws": "^7.4.0",
-      "@types/xml2js": "^0.4.7",
-      "cheerio": "^1.0.0-rc.3",
-      "discord.js": "^12.5.1",
+      "@types/ws": "^7.4.1",
+      "@types/xml2js": "^0.4.8",
+      "cheerio": "^1.0.0-rc.6",
+      "discord.js": "^12.5.3",
       "dotenv": "^8.2.0",
-      "emoji-regex": "^9.2.0",
+      "emoji-regex": "^9.2.2",
       "express": "^4.17.1",
-      "form-data": "^3.0.0",
-      "google-protobuf": "^3.14.0",
-      "got": "^11.8.0",
+      "form-data": "^3.0.1",
+      "fp-ts": "^2.10.4",
+      "google-protobuf": "^3.15.8",
+      "got": "^11.8.2",
       "html2bbcode": "^1.2.6",
-      "humanize-duration": "^3.24.0",
+      "humanize-duration": "^3.25.2",
       "interval-promise": "^1.4.0",
+      "io-ts": "^2.2.16",
       "jimp": "^0.16.1",
-      "koa": "^2.13.0",
+      "koa": "^2.13.1",
       "koa-body": "^4.2.0",
       "koa-router": "^10.0.0",
       "lowdb": "^1.0.0",
       "module-alias": "^2.2.2",
-      "node-schedule": "^1.3.2",
-      "nodemailer": "^6.4.16",
-      "pg": "^8.5.1",
+      "node-schedule": "^1.3.3",
+      "nodemailer": "^6.5.0",
+      "pg": "^8.6.0",
       "reflect-metadata": "^0.1.13",
       "rimraf": "^3.0.2",
       "rpc_ts": "^2.1.0",
-      "rss-parser": "^3.9.0",
+      "rss-parser": "^3.12.0",
       "sha1": "^1.1.1",
       "tsconfig-paths": "^3.9.0",
       "turndown": "^7.0.0",
       "type-zoo": "^3.4.1",
-      "typeorm": "^0.2.29",
-      "typescript": "^4.1.2",
+      "typeorm": "^0.2.32",
+      "typescript": "^4.2.4",
       "typescript-rest-rpc": "^1.0.10",
       "uws": "^200.0.0",
       "winston": "^3.3.3",
-      "winston-mongodb": "^5.0.5",
+      "winston-mongodb": "^5.0.7",
       "winston-transport": "^4.4.0",
-      "yaml": "^1.10.0"
+      "yaml": "^1.10.2"
    },
    "devDependencies": {
-      "@types/node": "^14.14.10",
-      "@types/nodemailer": "^6.4.0",
-      "@typescript-eslint/eslint-plugin": "^4.8.2",
-      "@typescript-eslint/parser": "^4.8.2",
-      "eslint": "^7.14.0",
-      "nodemon": "^2.0.6",
+      "@types/node": "^14.14.41",
+      "@types/nodemailer": "^6.4.1",
+      "@typescript-eslint/eslint-plugin": "^4.22.0",
+      "@typescript-eslint/parser": "^4.22.0",
+      "eslint": "^7.25.0",
+      "nodemon": "^2.0.7",
       "ts-node": "9.0.0"
    }
 }

+ 1 - 1
bot/src/plugin_manager.ts

@@ -118,7 +118,7 @@ export class PluginManager {
             let matchData: string | RegExpMatchArray = "";
             if (typeof (c.pattern) == "string" && lowerCaseContent.startsWith(c.pattern)) {
                 match = true;
-                matchData = c.pattern;
+                matchData = content;
             }
             else if (c.pattern instanceof RegExp) {
                 const result = c.pattern.exec(content);

+ 10 - 2
bot/src/plugins/aggregators/com3d2_world.ts

@@ -8,6 +8,10 @@ import got from "got";
 const kissDiaryRoot = "https://com3d2.world/r18/notices.php";
 const FEED_NAME = "com3d2-world-notices";
 
+function isTag<T>(o: T): o is (T & {attribs: { [attr: string]: string };}) {
+    return typeof(o) === "object" && Object.keys(o).includes("tagName");
+}
+
 async function aggregate() {
     const repo = getRepository(AggroNewsItem);
     
@@ -43,10 +47,14 @@ async function aggregate() {
         let latestEntry = lastPost.newsId;
 
         for(const a of diaryEntries.toArray()) {
-            if(!a.attribs.id)
+            if (!isTag(a)) {
+                continue;
+            }
+            const idAttrVal = a.attribs.id;
+            if(!idAttrVal)
                 continue;
             
-            const id = +a.attribs.id;
+            const id = +idAttrVal;
 
             if(id <= lastPost.newsId)
                 continue;

+ 165 - 0
bot/src/plugins/give_role_for_react.ts

@@ -0,0 +1,165 @@
+import { parseYaml } from "../util";
+import { Command, ICommandData, Plugin } from "src/model/plugin";
+import { tryDo } from "@shared/common/async_utils";
+import * as t from "io-ts";
+import { logger } from "src/logging";
+import { getRepository } from "typeorm";
+import { GiveRoleMessage } from "@shared/db/entity/GiveRoleMessage";
+import { Message, MessageReaction, ReactionCollector, ReactionEmoji, TextChannel, User } from "discord.js";
+import { client } from "src/client";
+
+const ReactRoleMessageParams = t.type({
+    title: t.string,
+    message: t.string,
+    roleId: t.string
+});
+
+const REACT_EMOTE = "⭐";
+const MSG_COLOR = 9830318;
+
+@Plugin
+export class GiveRoleForReact {
+    @Command({
+        type: "mention",
+        pattern: "add role message",
+        documentation: {
+            description: "Add role giver message",
+            example: "add role message { message: \"This is a role!\", roleId: \"0237894783782\" }"
+        },
+        allowDM: false,
+        auth: true
+    })
+    async makeRoleMessage({ message, contents }: ICommandData): Promise<void> {
+        const textContent = (contents as string).substring("add role message".length).trim();
+        
+        tryDo(message.delete());
+
+        const opts = parseYaml(ReactRoleMessageParams, textContent);
+        if (!opts.ok) {
+            await this.sendDM(message.author, `Sorry, I don't understand the command! Got the following errors: ${opts.errors.join("\n")}`);
+            return;
+        }
+
+        const params = opts.result;
+
+        const role = message.guild?.roles.cache.get(params.roleId);
+
+        if (!role) {
+            await this.sendDM(message.author, "Sorry, the role ID is not a valid role on the server!");
+            return;
+        }
+
+        const msgSendResult = await tryDo(message.channel.send({
+            embed: {
+                title: params.title,
+                description: `${params.message}\n\nReact with ${REACT_EMOTE} to gain role ${role.toString()}`,
+                color: MSG_COLOR
+            }
+        }));
+
+        if (!msgSendResult.ok) {
+            logger.error(`GiveRoleForReact: failed to create message because ${msgSendResult.error}`);
+            return;
+        }
+
+        const msg = msgSendResult.result;
+        await msg.react(REACT_EMOTE);
+        const roleGiveMessageRepo = getRepository(GiveRoleMessage);
+
+        if (!msg.guild) {
+            logger.error("GiveRoleForReact: tried to set up role react for DMs (this should never happen!)");
+            return;
+        }
+        
+        await roleGiveMessageRepo.save({
+            messageId: msg.id,
+            guildId: msg.guild.id,
+            channelId: msg.channel.id,
+            roleToGive: params.roleId
+        });
+
+        this.initReactCollector(msg, params.roleId);
+    }
+
+    private initReactCollector(msg: Message, role: string) {
+        const check = (reaction: MessageReaction) => {
+            return reaction.emoji.name == REACT_EMOTE;
+        };
+        const collector = new ReactionCollector(msg, check, {
+            dispose: true
+        });
+
+        const guild = msg.guild;
+        if (!guild) {
+            throw new Error("Tried to initialize role collector for non-guild channel.");
+        }
+
+        collector.on("collect", async (_, user) => {
+            if (user.bot) {
+                return;
+            }
+            const gu = guild.member(user);
+            if (!gu) {
+                return;
+            }
+            const result = await tryDo(gu.roles.add(role));
+            if (!result.ok) {
+                logger.error("GiveRoleForReact: Can't add role %s to user %s: %s", role, gu.id, result.error);
+            }
+        });
+
+        collector.on("remove", async (_, user) => {
+            if (user.bot) {
+                return;
+            }
+            const gu = guild.member(user);
+            if (!gu) {
+                return;
+            }
+            const result = await tryDo(gu.roles.remove(role));
+            if (!result.ok) {
+                logger.error("GiveRoleForReact: Can't remove role %s to user %s: %s", role, gu.id, result.error);
+            }
+        });
+    }
+
+    private async sendDM(usr: User, messageText: string) {
+        const dmResult = await tryDo(usr.createDM());
+        if (dmResult.ok) {
+            tryDo(dmResult.result.send(messageText));
+        }
+    }
+
+    async start(): Promise<void> {
+        logger.info("Initializing role give messages");
+        const roleGiveMessageRepo = getRepository(GiveRoleMessage);
+
+        const reactMessages = await roleGiveMessageRepo.find();
+
+        const staleEntities = [];
+        for (const reactMessage of reactMessages) {
+            const guildResult = await tryDo(client.bot.guilds.fetch(reactMessage.guildId));
+            if (!guildResult.ok) {
+                staleEntities.push(reactMessage);
+                continue;
+            }
+
+            const guild = guildResult.result;
+            const channel = guild.channels.resolve(reactMessage.channelId);
+            if (!channel || !(channel instanceof TextChannel)) {
+                staleEntities.push(reactMessage);
+                continue;
+            }
+
+            const msgResult = await tryDo(channel.messages.fetch(reactMessage.messageId));
+            if (!msgResult.ok) {
+                staleEntities.push(reactMessage);
+                continue;
+            }
+            
+            this.initReactCollector(msgResult.result, reactMessage.roleToGive);
+        }
+
+        await roleGiveMessageRepo.remove(staleEntities);
+    }
+}

+ 47 - 13
bot/src/plugins/violation.ts

@@ -26,12 +26,14 @@ interface ViolationInfo {
 
 type TimedViolation = Violation & { endsAt: Date };
 
-type StartViolationFunction = (member: GuildMember | PartialGuildMember, settings: GuildViolationSettings) => Promise<void>;
-type StopViolationFunction = (guild: Guild, userId: string, settings: GuildViolationSettings) => Promise<void>;
+type ModifyViolationFunction = (member: GuildMember | PartialGuildMember, settings: GuildViolationSettings, violation: DeepPartial<TimedViolation>) => DeepPartial<TimedViolation>; 
+type StartViolationFunction = (member: GuildMember | PartialGuildMember, settings: GuildViolationSettings, violation: TimedViolation) => Promise<void>;
+type StopViolationFunction = (guild: Guild, userId: string, settings: GuildViolationSettings, violation: TimedViolation) => Promise<void>;
 interface TimedViolationStopHandler {
     type: ObjectType<TimedViolation>;
     start: StartViolationFunction;
     stop: StopViolationFunction;
+    modify?: ModifyViolationFunction;
     command: string;
 }
 
@@ -53,9 +55,15 @@ export class ViolationPlugin {
                         settings.muteRoleId);
                     return;
                 }
-                await member.roles.add(muteRoleResolve.result);
+                const result = await tryDo(member.roles.set([ muteRoleResolve.result ]));
+                if (!result.ok) {
+                    logger.error("mute: Couldn't mute/remove roles for user %s#%s (%s)",
+                        member.user?.username,
+                        member.user?.discriminator,
+                        member.user?.id);
+                }
             },
-            stop: async (guild: Guild, userId: string, settings: GuildViolationSettings): Promise<void> => {
+            stop: async (guild: Guild, userId: string, settings: GuildViolationSettings, violation: TimedViolation): Promise<void> => {
                 const muteRoleResolve = await tryDo(guild.roles.fetch(settings.muteRoleId));
 
                 if (!muteRoleResolve.ok || !muteRoleResolve.result) {
@@ -71,7 +79,26 @@ export class ViolationPlugin {
                     return;
                 }
 
-                await memberResolve.result.roles.remove(muteRole);
+                const member = memberResolve.result;
+                const unmuteResult = await tryDo(member.roles.remove(muteRole));
+                if (!unmuteResult.ok) {
+                    logger.error("mute: failed to unmute user %s#%s (%s). Maybe he's already unmuted?",
+                        member.user?.username,
+                        member.user?.discriminator,
+                        member.user?.id,);
+                }
+                const mute = violation as Mute;
+                if (mute.previousRoles) {
+                    const result = await tryDo(member.roles.set(mute.previousRoles));
+                    if (!result.ok) {
+                        logger.warn("mute: couldn't readd all roles for user %s (tried to restore role ids: %s)", member.id, mute.previousRoles.join(", "));
+                    }
+                }
+            },
+            modify: (member: GuildMember | PartialGuildMember, settings: GuildViolationSettings, violation: DeepPartial<Mute>): DeepPartial<Mute> => {
+                const originalRoles = member.roles.cache.keyArray().filter(r => r != settings.muteRoleId);
+                violation.previousRoles = originalRoles;
+                return violation;
             }
         }
     ];
@@ -130,7 +157,7 @@ export class ViolationPlugin {
                 if (violation.endsAt < new Date())
                     await repo.update({ id: violation.id }, { valid: false });
                 else
-                    await handler.start(member, settings);
+                    await handler.start(member, settings, violation);
             }
         }
     }
@@ -159,7 +186,7 @@ export class ViolationPlugin {
         if (!info.dryRun) {
             eventLogger.warn("User %s#%s muted user %s#%s for %s because: %s", message.author.username, message.author.discriminator, info.member.user.username, info.member.user.discriminator, info.duration, info.reason);
         }
-        await this.applyTimedViolation(Mute, info, "mute", handler.start, handler.stop);
+        await this.applyTimedViolation(Mute, info, "mute", handler.start, handler.stop, handler.modify);
         await this.sendViolationMessage(message, info, "User has been muted for server violation");
     }
 
@@ -232,11 +259,11 @@ export class ViolationPlugin {
         delete this.jobs[existingViolation.id];
 
         const handler = this.getViolationHandler(type);
-        await handler.stop(message.guild, user.id, settings);
+        await handler.stop(message.guild, user.id, settings, existingViolation);
         await message.reply(`removed ${command} on user!`);
     }
 
-    private async applyTimedViolation<T extends TimedViolation>(type: ObjectType<T>, info: ViolationInfo, command = "violation", apply: StartViolationFunction, remove: StopViolationFunction) {
+    private async applyTimedViolation<T extends TimedViolation>(type: ObjectType<T>, info: ViolationInfo, command = "violation", apply: StartViolationFunction, remove: StopViolationFunction, modify?: ModifyViolationFunction) {
         if (info.dryRun)
             return;
 
@@ -249,22 +276,29 @@ export class ViolationPlugin {
             }
         });
 
+        let appliedViolation: T;
         if (existingViolation) {
             logger.warn("%s: trying to reapply on user %s#%s (%s)", command, info.member.user.username, info.member.user.discriminator, info.member.id);
             await violationRepo.update({ id: existingViolation.id } as unknown as FindConditions<T>, { endsAt: info.endDate } as unknown as QueryDeepPartialEntity<T>);
             const job = this.jobs[existingViolation.id];
             rescheduleJob(job, info.endDate);
+            appliedViolation = existingViolation;
         } else {
-            const newViolation = await violationRepo.save({
+            let rawViolation: DeepPartial<TimedViolation> = {
                 guildId: info.guild.id,
                 userId: info.member.id,
                 reason: info.reason,
                 endsAt: info.endDate,
                 valid: true,
-            } as unknown as DeepPartial<T>);
+            };
+            if (modify) {
+                rawViolation = modify(info.member, info.settings, rawViolation);
+            }
+            const newViolation = await violationRepo.save(rawViolation as unknown as DeepPartial<T>);
             this.jobs[newViolation.id] = scheduleJob(info.endDate, this.scheduleRemoveViolation(type, info.guild.id, info.member.id, remove, command));
+            appliedViolation = newViolation;
         }
-        await apply(info.member, info.settings);
+        await apply(info.member, info.settings, appliedViolation);
     }
 
     private scheduleRemoveViolation<T extends TimedViolation>(type: ObjectType<T>, guildId: string, userId: string, handle: StopViolationFunction, command = "violation") {
@@ -300,7 +334,7 @@ export class ViolationPlugin {
                 return;
             }
 
-            await handle(guild, userId, settings);
+            await handle(guild, userId, settings, violation);
         };
     }
 

+ 16 - 1
bot/src/rpc.ts

@@ -10,6 +10,7 @@ import { getRepository } from "typeorm";
 import { GuildVerification } from "@shared/db/entity/GuildVerification";
 import { isAuthorisedAsync } from "./util";
 import { GuildMember } from "discord.js";
+import { GuildViolationSettings } from "@shared/db/entity/GuildViolationSettings";
 
 const PORT = +(process.env.RPC_PORT ?? "8181");
 
@@ -42,6 +43,15 @@ async function checkUser(action: string, userId: string, check: (user: GuildMemb
     return false;
 }
 
+async function userMuted(user: GuildMember, guild: GuildVerification): Promise<boolean> {
+    const violationSettingsRepo = await getRepository(GuildViolationSettings);
+    const settings = await violationSettingsRepo.findOne(guild.guildId);
+    if (!settings?.muteRoleId) {
+        return false;
+    }
+    return user.roles.cache.has(settings.muteRoleId);
+}
+
 const handler: ModuleRpcServer.ServiceHandlerFor<typeof NoctBotService> = {
     async getPing({ ping }): Promise<{ text: string }> {
         return { text: `pong: ${ping}` };
@@ -53,10 +63,15 @@ const handler: ModuleRpcServer.ServiceHandlerFor<typeof NoctBotService> = {
         return { authorised: await checkUser("check auth", userId, (user) => isAuthorisedAsync(user)) };
     },
     async userVerified({ userId }): Promise<{ verified: boolean }> {
-        return { verified: await checkUser("check verified", userId, async (user, guild) => user.roles.cache.has(guild.verifiedRoleId)) };
+        // Prevent muted users from gaining verified role back
+        return { verified: await checkUser("check verified", userId, async (user, guild) => user.roles.cache.has(guild.verifiedRoleId) || await userMuted(user, guild)) };
     },
     async verifyUser({ userId }): Promise<{ ok: boolean }> {
         return { ok: !(await checkUser("verify", userId, async (user, guild) => {
+            if (await userMuted(user, guild)) {
+                logger.info("Muted user %s#%s (%s) tried to re-verify in guild %s", user.user.username, user.user.discriminator, user.user.id, guild.guildId);
+                return false;
+            }
             const result = await tryDo(user.roles.add(guild.verifiedRoleId));
             if (result.ok) {
                 eventLogger.info("Verifying user %s#%s (%s)", user.user.username, user.user.discriminator, user.user.id);

+ 13 - 0
bot/src/util.ts

@@ -1,7 +1,11 @@
 import { GuildMember, User } from "discord.js";
 import { getRepository, In } from "typeorm";
 import { KnownUser } from "@shared/db/entity/KnownUser";
+import { Option, } from "@shared/common/async_utils";
 import humanizeDuration from "humanize-duration";
+import * as t from "io-ts";
+import YAML from "yaml";
+import { PathReporter } from "io-ts/PathReporter";
 
 const VALID_EXTENSIONS = new Set([
     "png",
@@ -19,6 +23,15 @@ export function isDevelopment(): boolean {
     return process.env.NODE_ENV == "dev";
 }
 
+export function parseYaml<A>(type: t.Type<A>, yaml: string): Option<{ result: A }, { errors: string[] }> {
+    const t = type.decode(YAML.parse(yaml));
+    if (t._tag == "Left"){
+        return { ok: false, errors: PathReporter.report(t) };
+    } else {
+        return { ok: true, result: t.right };
+    }
+}
+
 export function isValidImage(fileName?: string | null): boolean {
     if (!fileName)
         return false;

+ 1 - 1
docker-compose.yml

@@ -62,7 +62,7 @@ services:
       - web-data:/web_data
 
   db:
-    image: postgres
+    image: postgres:12
     restart: unless-stopped
     env_file:
       - db.env

+ 105 - 237
shared/package-lock.json

@@ -15,24 +15,29 @@
       }
     },
     "@sqltools/formatter": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.2.tgz",
-      "integrity": "sha512-/5O7Fq6Vnv8L6ucmPjaWbVG1XkP4FO+w5glqfkIsq3Xw4oyNAdJddbnYodNDAfjVUvo/rrSCTom4kAND7T1o5Q=="
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.3.tgz",
+      "integrity": "sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg=="
     },
     "@types/node": {
-      "version": "14.14.10",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.10.tgz",
-      "integrity": "sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ=="
+      "version": "14.14.41",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.41.tgz",
+      "integrity": "sha512-dueRKfaJL4RTtSa7bWeTK1M+VH+Gns73oCgzvYfHZywRCoPSd8EkXBL0mZ9unPTveBn+D9phZBaxuzpwjWkW0g=="
     },
     "@types/nodemailer": {
-      "version": "6.4.0",
-      "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.0.tgz",
-      "integrity": "sha512-KY7bFWB0MahRZvVW4CuW83qcCDny59pJJ0MQ5ifvfcjNwPlIT0vW4uARO4u1gtkYnWdhSvURegecY/tzcukJcA==",
+      "version": "6.4.1",
+      "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.1.tgz",
+      "integrity": "sha512-8081UY/0XTTDpuGqCnDc8IY+Q3DSg604wB3dBH0CaZlj4nZWHWuxtZ3NRZ9c9WUrz1Vfm6wioAUnqL3bsh49uQ==",
       "dev": true,
       "requires": {
         "@types/node": "*"
       }
     },
+    "@types/zen-observable": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.2.tgz",
+      "integrity": "sha512-HrCIVMLjE1MOozVoD86622S7aunluLb2PJdPfb3nYiEtohm8mIB/vyv0Fd37AdeMFrTUQXEunw78YloMA3Qilg=="
+    },
     "ansi-regex": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
@@ -72,12 +77,9 @@
       "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw=="
     },
     "argparse": {
-      "version": "1.0.10",
-      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
-      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
-      "requires": {
-        "sprintf-js": "~1.0.2"
-      }
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
     },
     "async": {
       "version": "3.2.0",
@@ -151,74 +153,49 @@
       }
     },
     "bson": {
-      "version": "1.1.5",
-      "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz",
-      "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg=="
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz",
+      "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg=="
     },
     "buffer": {
-      "version": "5.7.1",
-      "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
-      "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+      "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
       "requires": {
         "base64-js": "^1.3.1",
-        "ieee754": "^1.1.13"
+        "ieee754": "^1.2.1"
       }
     },
-    "camelcase": {
-      "version": "5.3.1",
-      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
-      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
-    },
     "chalk": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
-      "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
+      "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
       "requires": {
         "ansi-styles": "^4.1.0",
         "supports-color": "^7.1.0"
       }
     },
     "cli-highlight": {
-      "version": "2.1.8",
-      "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.8.tgz",
-      "integrity": "sha512-mFuTW5UOV3/S0wZE9/1b0EcAM0XOJIhoAWPhWm5voiJ6ugVBkvYBIEL7sbHo9sEtWdEmwDIWab32qpaRI3cfqQ==",
+      "version": "2.1.11",
+      "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz",
+      "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==",
       "requires": {
         "chalk": "^4.0.0",
-        "highlight.js": "^10.0.0",
+        "highlight.js": "^10.7.1",
         "mz": "^2.4.0",
         "parse5": "^5.1.1",
         "parse5-htmlparser2-tree-adapter": "^6.0.0",
-        "yargs": "^15.0.0"
-      },
-      "dependencies": {
-        "yargs": {
-          "version": "15.4.1",
-          "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
-          "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
-          "requires": {
-            "cliui": "^6.0.0",
-            "decamelize": "^1.2.0",
-            "find-up": "^4.1.0",
-            "get-caller-file": "^2.0.1",
-            "require-directory": "^2.1.1",
-            "require-main-filename": "^2.0.0",
-            "set-blocking": "^2.0.0",
-            "string-width": "^4.2.0",
-            "which-module": "^2.0.0",
-            "y18n": "^4.0.0",
-            "yargs-parser": "^18.1.2"
-          }
-        }
+        "yargs": "^16.0.0"
       }
     },
     "cliui": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
-      "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+      "version": "7.0.4",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+      "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
       "requires": {
         "string-width": "^4.2.0",
         "strip-ansi": "^6.0.0",
-        "wrap-ansi": "^6.2.0"
+        "wrap-ansi": "^7.0.0"
       }
     },
     "color": {
@@ -284,15 +261,10 @@
         "ms": "2.1.2"
       }
     },
-    "decamelize": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
-      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
-    },
     "denque": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
-      "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ=="
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz",
+      "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ=="
     },
     "dotenv": {
       "version": "8.2.0",
@@ -319,11 +291,6 @@
       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
       "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
     },
-    "esprima": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
-      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
-    },
     "fast-safe-stringify": {
       "version": "2.0.7",
       "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz",
@@ -339,15 +306,6 @@
       "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.0.tgz",
       "integrity": "sha512-ZQJM4aifMpz6H19AW1VqvZ7l4pOE9p7i/3LyxgO2kp+PO/VcDYNqIHEMtkccqIhTXMKci4kjueJr/iCQEaT/Ww=="
     },
-    "find-up": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
-      "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
-      "requires": {
-        "locate-path": "^5.0.0",
-        "path-exists": "^4.0.0"
-      }
-    },
     "fn.name": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
@@ -397,9 +355,9 @@
       "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
     },
     "highlight.js": {
-      "version": "10.4.0",
-      "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.4.0.tgz",
-      "integrity": "sha512-EfrUGcQ63oLJbj0J0RI9ebX6TAITbsDBLbsjr881L/X5fMO9+oadKzEF21C7R3ULKG6Gv3uoab2HiqVJa/4+oA=="
+      "version": "10.7.2",
+      "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.2.tgz",
+      "integrity": "sha512-oFLl873u4usRM9K63j4ME9u3etNF0PLiJhSQ8rdfuL51Wn3zkD6drf9ZW0dOzjnZI22YYG24z30JcmfCZjMgYg=="
     },
     "ieee754": {
       "version": "1.2.1",
@@ -441,12 +399,11 @@
       "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
     },
     "js-yaml": {
-      "version": "3.14.0",
-      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
-      "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
       "requires": {
-        "argparse": "^1.0.7",
-        "esprima": "^4.0.0"
+        "argparse": "^2.0.1"
       }
     },
     "kuler": {
@@ -454,14 +411,6 @@
       "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
       "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="
     },
-    "locate-path": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
-      "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
-      "requires": {
-        "p-locate": "^4.1.0"
-      }
-    },
     "logform": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz",
@@ -494,14 +443,14 @@
       "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
     },
     "mongodb": {
-      "version": "3.6.2",
-      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.2.tgz",
-      "integrity": "sha512-sSZOb04w3HcnrrXC82NEh/YGCmBuRgR+C1hZgmmv4L6dBz4BkRse6Y8/q/neXer9i95fKUBbFi4KgeceXmbsOA==",
+      "version": "3.6.6",
+      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.6.tgz",
+      "integrity": "sha512-WlirMiuV1UPbej5JeCMqE93JRfZ/ZzqE7nJTwP85XzjAF4rRSeq2bGCb1cjfoHLOF06+HxADaPGqT0g3SbVT1w==",
       "requires": {
         "bl": "^2.2.1",
         "bson": "^1.1.4",
         "denque": "^1.4.1",
-        "require_optional": "^1.0.1",
+        "optional-require": "^1.0.2",
         "safe-buffer": "^5.1.2",
         "saslprep": "^1.0.0"
       }
@@ -522,9 +471,9 @@
       }
     },
     "nodemailer": {
-      "version": "6.4.16",
-      "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.16.tgz",
-      "integrity": "sha512-68K0LgZ6hmZ7PVmwL78gzNdjpj5viqBdFqKrTtr9bZbJYj6BRj5W6WGkxXrEnUl3Co3CBXi3CZBUlpV/foGnOQ=="
+      "version": "6.5.0",
+      "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.5.0.tgz",
+      "integrity": "sha512-Tm4RPrrIZbnqDKAvX+/4M+zovEReiKlEXWDzG4iwtpL9X34MJY+D5LnQPH/+eghe8DLlAVshHAJZAZWBGhkguw=="
     },
     "object-assign": {
       "version": "4.1.1",
@@ -547,26 +496,10 @@
         "fn.name": "1.x.x"
       }
     },
-    "p-limit": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
-      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
-      "requires": {
-        "p-try": "^2.0.0"
-      }
-    },
-    "p-locate": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
-      "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
-      "requires": {
-        "p-limit": "^2.2.0"
-      }
-    },
-    "p-try": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
-      "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
+    "optional-require": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz",
+      "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA=="
     },
     "parent-require": {
       "version": "1.0.0",
@@ -593,11 +526,6 @@
         }
       }
     },
-    "path-exists": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
-      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
-    },
     "path-is-absolute": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -628,25 +556,6 @@
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
       "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
     },
-    "require-main-filename": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
-      "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
-    },
-    "require_optional": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
-      "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
-      "requires": {
-        "resolve-from": "^2.0.0",
-        "semver": "^5.1.0"
-      }
-    },
-    "resolve-from": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
-      "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c="
-    },
     "rimraf": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -674,16 +583,6 @@
       "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
       "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
     },
-    "semver": {
-      "version": "5.7.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
-      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
-    },
-    "set-blocking": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
-      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
-    },
     "sha.js": {
       "version": "2.4.11",
       "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
@@ -710,20 +609,15 @@
         "memory-pager": "^1.0.2"
       }
     },
-    "sprintf-js": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
-      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
-    },
     "stack-trace": {
       "version": "0.0.10",
       "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
       "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA="
     },
     "string-width": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
-      "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
+      "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
       "requires": {
         "emoji-regex": "^8.0.0",
         "is-fullwidth-code-point": "^3.0.0",
@@ -781,48 +675,44 @@
       "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
     },
     "tslib": {
-      "version": "1.14.1",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
+      "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w=="
     },
     "typeorm": {
-      "version": "0.2.29",
-      "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.29.tgz",
-      "integrity": "sha512-ih1vrTe3gEAGKRcWlcsTRxTL7gNjacQE498wVGuJ3ZRujtMqPZlbAWuC7xDzWCRjQnkZYNwZQeG9UgKfxSHB5g==",
+      "version": "0.2.32",
+      "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.32.tgz",
+      "integrity": "sha512-LOBZKZ9As3f8KRMPCUT2H0JZbZfWfkcUnO3w/1BFAbL/X9+cADTF6bczDGGaKVENJ3P8SaKheKmBgpt5h1x+EQ==",
       "requires": {
-        "@sqltools/formatter": "1.2.2",
+        "@sqltools/formatter": "^1.2.2",
         "app-root-path": "^3.0.0",
-        "buffer": "^5.5.0",
+        "buffer": "^6.0.3",
         "chalk": "^4.1.0",
-        "cli-highlight": "^2.1.4",
-        "debug": "^4.1.1",
+        "cli-highlight": "^2.1.10",
+        "debug": "^4.3.1",
         "dotenv": "^8.2.0",
         "glob": "^7.1.6",
-        "js-yaml": "^3.14.0",
+        "js-yaml": "^4.0.0",
         "mkdirp": "^1.0.4",
         "reflect-metadata": "^0.1.13",
         "sha.js": "^2.4.11",
-        "tslib": "^1.13.0",
+        "tslib": "^2.1.0",
         "xml2js": "^0.4.23",
-        "yargonaut": "^1.1.2",
-        "yargs": "^16.0.3"
+        "yargonaut": "^1.1.4",
+        "yargs": "^16.2.0",
+        "zen-observable-ts": "^1.0.0"
       }
     },
     "typescript": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.2.tgz",
-      "integrity": "sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ=="
+      "version": "4.2.4",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
+      "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg=="
     },
     "util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
       "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
     },
-    "which-module": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
-      "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
-    },
     "winston": {
       "version": "3.3.3",
       "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz",
@@ -840,9 +730,9 @@
       }
     },
     "winston-mongodb": {
-      "version": "5.0.5",
-      "resolved": "https://registry.npmjs.org/winston-mongodb/-/winston-mongodb-5.0.5.tgz",
-      "integrity": "sha512-hUb5DStkzdLjJ7h4+ZoBbL/NJsEphy/uY9mw2F5of4iAI1Bx1INrAFzlKZx+Ww0w9IOevrg2cPKSGlDawn6SNQ==",
+      "version": "5.0.7",
+      "resolved": "https://registry.npmjs.org/winston-mongodb/-/winston-mongodb-5.0.7.tgz",
+      "integrity": "sha512-6uUdd7IPfzKMth1vbO6zPyLQzn57CTNXbyjZmdD9UgBlY19BliFnYcK7+Aopo0GHIsWDo7xlerpj3x/ZULDIpg==",
       "requires": {
         "mongodb": "^3.6.2",
         "winston-transport": "^4.4.0"
@@ -887,9 +777,9 @@
       }
     },
     "wrap-ansi": {
-      "version": "6.2.0",
-      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
-      "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
       "requires": {
         "ansi-styles": "^4.0.0",
         "string-width": "^4.1.0",
@@ -916,9 +806,9 @@
       "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
     },
     "y18n": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
-      "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w=="
+      "version": "5.0.8",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
     },
     "yargonaut": {
       "version": "1.1.4",
@@ -968,9 +858,9 @@
       }
     },
     "yargs": {
-      "version": "16.1.1",
-      "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.1.1.tgz",
-      "integrity": "sha512-hAD1RcFP/wfgfxgMVswPE+z3tlPFtxG8/yWUrG2i17sTWGCGqWnxKcLTF4cUKDUK8fzokwsmO9H0TDkRbMHy8w==",
+      "version": "16.2.0",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+      "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
       "requires": {
         "cliui": "^7.0.2",
         "escalade": "^3.1.1",
@@ -979,47 +869,25 @@
         "string-width": "^4.2.0",
         "y18n": "^5.0.5",
         "yargs-parser": "^20.2.2"
-      },
-      "dependencies": {
-        "cliui": {
-          "version": "7.0.4",
-          "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
-          "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
-          "requires": {
-            "string-width": "^4.2.0",
-            "strip-ansi": "^6.0.0",
-            "wrap-ansi": "^7.0.0"
-          }
-        },
-        "wrap-ansi": {
-          "version": "7.0.0",
-          "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
-          "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
-          "requires": {
-            "ansi-styles": "^4.0.0",
-            "string-width": "^4.1.0",
-            "strip-ansi": "^6.0.0"
-          }
-        },
-        "y18n": {
-          "version": "5.0.5",
-          "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz",
-          "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg=="
-        },
-        "yargs-parser": {
-          "version": "20.2.4",
-          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
-          "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA=="
-        }
       }
     },
     "yargs-parser": {
-      "version": "18.1.3",
-      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
-      "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+      "version": "20.2.7",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz",
+      "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw=="
+    },
+    "zen-observable": {
+      "version": "0.8.15",
+      "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz",
+      "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ=="
+    },
+    "zen-observable-ts": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.0.0.tgz",
+      "integrity": "sha512-KmWcbz+9kKUeAQ8btY8m1SsEFgBcp7h/Uf3V5quhan7ZWdjGsf0JcGLULQiwOZibbFWnHkYq8Nn2AZbJabovQg==",
       "requires": {
-        "camelcase": "^5.0.0",
-        "decamelize": "^1.2.0"
+        "@types/zen-observable": "^0.8.2",
+        "zen-observable": "^0.8.15"
       }
     }
   }

+ 6 - 6
shared/package.json

@@ -10,16 +10,16 @@
   "author": "ghorsington",
   "license": "ISC",
   "dependencies": {
-    "@types/node": "^14.14.10",
-    "nodemailer": "^6.4.16",
+    "@types/node": "^14.14.41",
+    "nodemailer": "^6.5.0",
     "rimraf": "^3.0.2",
-    "typeorm": "^0.2.29",
-    "typescript": "^4.1.2",
+    "typeorm": "^0.2.32",
+    "typescript": "^4.2.4",
     "winston": "^3.3.3",
-    "winston-mongodb": "^5.0.5",
+    "winston-mongodb": "^5.0.7",
     "winston-transport": "^4.4.0"
   },
   "devDependencies": {
-    "@types/nodemailer": "^6.4.0"
+    "@types/nodemailer": "^6.4.1"
   }
 }

+ 2 - 0
shared/src/db/entities.ts

@@ -18,6 +18,7 @@ import { GuildGreeting } from "./entity/GuildGreeting";
 import { Violation, Mute, Kick, Ban } from "./entity/Violation";
 import { GuildViolationSettings } from "./entity/GuildViolationSettings";
 import { GuildVerification } from "./entity/GuildVerification";
+import { GiveRoleMessage } from "./entity/GiveRoleMessage";
 
 export const DB_ENTITIES = [
     AggroNewsItem,
@@ -25,6 +26,7 @@ export const DB_ENTITIES = [
     FaceCaptionMessage,
     Guide,
     GuideKeyword,
+    GiveRoleMessage,
     KnownChannel,
     KnownUser,
     User,

+ 16 - 0
shared/src/db/entity/GiveRoleMessage.ts

@@ -0,0 +1,16 @@
+import {Entity, PrimaryColumn, Column} from "typeorm";
+
+@Entity()
+export class GiveRoleMessage {
+    @PrimaryColumn()
+    messageId: string;
+
+    @PrimaryColumn()
+    guildId: string;
+
+    @PrimaryColumn()
+    channelId: string;
+
+    @Column()
+    roleToGive: string;
+}

+ 3 - 0
shared/src/db/entity/Violation.ts

@@ -23,6 +23,9 @@ export abstract class Violation {
 export class Mute extends Violation {
     @Column()
     endsAt: Date;
+
+    @Column("text", { array: true })
+    previousRoles?: string[];
 }
 
 @ChildEntity()

File diff suppressed because it is too large
+ 2033 - 1512
web/package-lock.json


+ 42 - 42
web/package.json

@@ -15,72 +15,72 @@
 		"compression": "^1.7.4",
 		"cookie-session": "^1.4.0",
 		"dotenv": "^8.2.0",
-		"easymde": "^2.13.0",
+		"easymde": "^2.15.0",
 		"express": "^4.17.1",
-		"google-protobuf": "^3.14.0",
-		"got": "^11.8.0",
+		"google-protobuf": "^3.15.8",
+		"got": "^11.8.2",
 		"make-error": "^1.3.6",
 		"node-fetch": "^2.6.1",
-		"nodemailer": "^6.4.16",
-		"pg": "^8.5.1",
-		"postcss-nested": "^5.0.1",
+		"nodemailer": "^6.5.0",
+		"pg": "^8.6.0",
+		"postcss-nested": "^5.0.5",
 		"rpc_ts": "^2.1.0",
 		"showdown": "^1.9.1",
-		"sirv": "^1.0.7",
-		"svelte-awesome": "^2.3.0",
-		"typeorm": "^0.2.29",
+		"sirv": "^1.0.11",
+		"svelte-awesome": "^2.3.1",
+		"typeorm": "^0.2.32",
 		"vanilla-hcaptcha": "0.0.5",
 		"winston": "^3.3.3",
-		"winston-mongodb": "^5.0.5",
+		"winston-mongodb": "^5.0.7",
 		"winston-transport": "^4.4.0"
 	},
 	"devDependencies": {
-		"@babel/core": "^7.12.9",
+		"@babel/core": "^7.13.16",
 		"@babel/plugin-syntax-dynamic-import": "^7.8.3",
-		"@babel/plugin-transform-async-to-generator": "^7.12.1",
-		"@babel/plugin-transform-runtime": "^7.12.1",
-		"@babel/preset-env": "^7.12.7",
-		"@babel/runtime": "^7.12.5",
-		"@fortawesome/free-brands-svg-icons": "^5.15.1",
-		"@fortawesome/free-solid-svg-icons": "^5.15.1",
-		"@rollup/plugin-alias": "^3.1.1",
-		"@rollup/plugin-babel": "^5.2.1",
+		"@babel/plugin-transform-async-to-generator": "^7.13.0",
+		"@babel/plugin-transform-runtime": "^7.13.15",
+		"@babel/preset-env": "^7.13.15",
+		"@babel/runtime": "^7.13.17",
+		"@fortawesome/free-brands-svg-icons": "^5.15.3",
+		"@fortawesome/free-solid-svg-icons": "^5.15.3",
+		"@rollup/plugin-alias": "^3.1.2",
+		"@rollup/plugin-babel": "^5.3.0",
 		"@rollup/plugin-commonjs": "^16.0.0",
 		"@rollup/plugin-json": "^4.1.0",
 		"@rollup/plugin-node-resolve": "^10.0.0",
-		"@rollup/plugin-replace": "^2.3.4",
+		"@rollup/plugin-replace": "^2.4.2",
 		"@rollup/plugin-typescript": "^6.1.0",
 		"@types/compression": "^1.7.0",
-		"@types/cookie-session": "^2.0.41",
-		"@types/express": "^4.17.9",
-		"@types/node-fetch": "^2.5.7",
-		"@types/nodemailer": "^6.4.0",
+		"@types/cookie-session": "^2.0.42",
+		"@types/express": "^4.17.11",
+		"@types/node-fetch": "^2.5.10",
+		"@types/nodemailer": "^6.4.1",
 		"@types/showdown": "^1.9.3",
-		"@typescript-eslint/eslint-plugin": "^4.8.2",
-		"@typescript-eslint/parser": "^4.8.2",
-		"autoprefixer": "^10.0.4",
-		"bufferutil": "^4.0.2",
+		"@typescript-eslint/eslint-plugin": "^4.22.0",
+		"@typescript-eslint/parser": "^4.22.0",
+		"autoprefixer": "^10.2.5",
+		"bufferutil": "^4.0.3",
 		"class-validator": "^0.12.2",
-		"cross-env": "^7.0.2",
-		"eslint": "^7.14.0",
+		"cross-env": "^7.0.3",
+		"eslint": "^7.25.0",
 		"eslint-config-airbnb-base": "^14.2.1",
-		"eslint-import-resolver-typescript": "^2.3.0",
+		"eslint-import-resolver-typescript": "^2.4.0",
 		"eslint-plugin-import": "^2.22.1",
 		"eslint-plugin-svelte3": "^2.7.3",
-		"postcss": "^8.1.10",
+		"postcss": "^8.2.12",
 		"postcss-import": "^13.0.0",
-		"postcss-load-config": "^3.0.0",
+		"postcss-load-config": "^3.0.1",
 		"postcss-preset-env": "^6.7.0",
 		"reflect-metadata": "^0.1.13",
-		"rollup": "^2.34.0",
-		"rollup-plugin-svelte": "^7.0.0",
+		"rollup": "^2.45.2",
+		"rollup-plugin-svelte": "^7.1.0",
 		"rollup-plugin-terser": "^7.0.2",
-		"sapper": "^0.28.10",
-		"svelte": "~3.29.7",
-		"svelte-preprocess": "^4.6.1",
-		"tailwindcss": "^2.0.1",
-		"tslib": "^2.0.3",
-		"typescript": "^4.1.2",
-		"utf-8-validate": "^5.0.3"
+		"sapper": "^0.29.1",
+		"svelte": "~3.37.0",
+		"svelte-preprocess": "^4.7.2",
+		"tailwindcss": "^2.1.2",
+		"tslib": "^2.2.0",
+		"typescript": "^4.2.4",
+		"utf-8-validate": "^5.0.4"
 	}
 }

+ 11 - 2
web/src/routes/rules/index.svelte

@@ -18,6 +18,7 @@
       rulesText: md.ok ? md.text : "",
       sitekey: verify.captchaSitekey,
       verified: verify.userVerified,
+      needsCaptcha: verify.needsCaptcha
     };
   }
 </script>
@@ -32,6 +33,7 @@
   export let rulesText: string = "";
   export let sitekey: string = "";
   export let verified!: boolean;
+  export let needsCaptcha!: boolean;
   let showVerify: boolean = false;
   const spinner = (faSpinner as unknown) as undefined;
   let htmlContent = new Converter().makeHtml(rulesText);
@@ -87,6 +89,10 @@
   .error {
     @apply text-red-600;
   }
+
+  .verify-button {
+    @apply text-gray-200 font-bold;
+  }
 </style>
 
 <svelte:head>
@@ -105,8 +111,11 @@
           them</label>
       </form>
       {#if showVerify}
-        <!-- <a href="asd" on:click|preventDefault={() => onVerified({ key: 'wew' })}>Verify test</a> -->
-        <h-captcha site-key={sitekey} dark on:verified={onVerified} on:expired={onExpired} on:error={onError} />  
+        {#if needsCaptcha}
+          <h-captcha site-key={sitekey} dark on:verified={onVerified} on:expired={onExpired} on:error={onError} />  
+        {:else}
+          <button class="verify-button" on:click={() => void onVerified({ key: "" })}>Verify</button>
+        {/if}
       {/if}
       <div class="py-2 pointer-events-none select-none">
         {#if state == State.Verify}

+ 38 - 32
web/src/routes/rules/verify.ts

@@ -8,6 +8,7 @@ import { ENV } from "src/utils/environment";
 export interface VerifyInfo {
     captchaSitekey: string;
     userVerified: boolean;
+    needsCaptcha: boolean;
 }
 
 export interface VerifyRequest {
@@ -40,20 +41,13 @@ export const get = async (req: ExpressRequest, res: ExpressResponse): GetResult
     return res.json({
         captchaSitekey: ENV.HCAPTCHA_SITEKEY,
         userVerified: verified,
+        needsCaptcha: ENV.HCAPTCHA_ENABLED === "TRUE",
     });
 };
 
 type PostResult = Promise<ExpressResponse<Option<unknown, { error: string }>>>;
 export const post = async (req: ExpressRequest, res: ExpressResponse): PostResult => {
-    const hasToken = (body: unknown):
-        body is VerifyRequest => body instanceof Object
-                                    && (body as VerifyRequest).captchaResponse !== undefined;
-    if (!hasToken(req.body)) {
-        return res.json({
-            ok: false,
-            error: "No user token provided, please try again",
-        });
-    }
+    const needVerify = ENV.HCAPTCHA_ENABLED === "TRUE";
 
     if (!req.session?.userId) {
         return res.json({
@@ -62,32 +56,44 @@ export const post = async (req: ExpressRequest, res: ExpressResponse): PostResul
         });
     }
 
-    const response = await tryDo(got<HCaptchaResponse>(VERIFY_URL, {
-        method: "post",
-        responseType: "json",
-        form: {
-            secret: ENV.HCAPTCHA_SECRET,
-            response: req.body.captchaResponse,
-        },
-    }));
+    if (needVerify) {
+        const hasToken = (body: unknown):
+        body is VerifyRequest => body instanceof Object
+                                    && (body as VerifyRequest).captchaResponse !== undefined;
+        if (needVerify && !hasToken(req.body)) {
+            return res.json({
+                ok: false,
+                error: "No user token provided, please try again",
+            });
+        }
+
+        const response = await tryDo(got<HCaptchaResponse>(VERIFY_URL, {
+            method: "post",
+            responseType: "json",
+            form: {
+                secret: ENV.HCAPTCHA_SECRET,
+                response: req.body.captchaResponse,
+            },
+        }));
 
-    if (!response.ok) {
-        logger.error("Failed to hCaptcha user %s: %s", req.session.userId, response.error);
-        return res.json({
-            ok: false,
-            error: "Failed to verify hCaptcha response. Please try again.",
-        });
-    }
+        if (!response.ok) {
+            logger.error("Failed to hCaptcha user %s: %s", req.session.userId, response.error);
+            return res.json({
+                ok: false,
+                error: "Failed to verify hCaptcha response. Please try again.",
+            });
+        }
 
-    const captchaResponse = response.result.body;
+        const captchaResponse = response.result.body;
 
-    if (!captchaResponse.success) {
-        const errors = captchaResponse["error-codes"] ?? [];
-        logger.error("Failed hCaptcha verify on user %s. Got errors: %s", req.session.userId, errors.join(";"));
-        return res.json({
-            ok: false,
-            error: "Failed to verify hCaptcha response. Please try again.",
-        });
+        if (!captchaResponse.success) {
+            const errors = captchaResponse["error-codes"] ?? [];
+            logger.error("Failed hCaptcha verify on user %s. Got errors: %s", req.session.userId, errors.join(";"));
+            return res.json({
+                ok: false,
+                error: "Failed to verify hCaptcha response. Please try again.",
+            });
+        }
     }
 
     const verifyResponse = await rpcClient.verifyUser({ userId: req.session.userId });

+ 1 - 0
web/src/utils/environment.ts

@@ -25,6 +25,7 @@ export const ENV = {
     WEB_DATA_PATH: "",
     HCAPTCHA_SITEKEY: "",
     HCAPTCHA_SECRET: "",
+    HCAPTCHA_ENABLED: "",
 };
 
 function isValid(): boolean {