From 6a00c49ef332e2fcd77fee7824d76d0a5a4d1327 Mon Sep 17 00:00:00 2001 From: Vylpes Date: Tue, 29 Mar 2022 18:19:54 +0100 Subject: [PATCH] Feature/48 database (#114) * Add database and default values * Add ability to save a setting to the database * Get commands and events to use database * Setup and config command * Update commands to check roles per server * Different rules per server Signed-off-by: Ethan Lane * Different prefix per server Signed-off-by: Ethan Lane * Add verification system Signed-off-by: Ethan Lane * Disabled commands per server * Add devmode for default prefix * Update embeds * Fix broken tests --- .env.template | 23 +- .gitignore | 3 +- data/config.txt | 22 ++ .../{rules.json => 290149509969739776.json} | 0 data/rules/442730357897429002.json | 88 ++++++ data/rules/501231711271780357.json | 70 ++++ docker-compose.yml | 24 +- jest.config.js | 6 - jest.config.json | 5 + ormconfig.json.template | 24 ++ package.json | 6 +- src/client/client.ts | 21 +- src/client/events.ts | 14 +- src/client/util.ts | 60 +++- src/commands/ban.ts | 4 +- src/commands/clear.ts | 2 +- src/commands/code.ts | 94 ++++++ src/commands/config.ts | 136 ++++++++ src/commands/disable.ts | 96 ++++++ src/commands/kick.ts | 4 +- src/commands/mute.ts | 4 +- src/commands/rules.ts | 6 +- src/commands/setup.ts | 37 +++ src/commands/unmute.ts | 4 +- src/commands/warn.ts | 6 +- src/constants/CommandResponse.ts | 7 + src/constants/DefaultValues.ts | 56 ++++ src/constants/ErrorMessages.ts | 22 ++ src/contracts/BaseEntity.ts | 68 ++++ src/entity/Server.ts | 19 ++ src/entity/Setting.ts | 37 +++ src/events/MemberEvents.ts | 12 +- src/events/MemberEvents/GuildMemberUpdate.ts | 4 +- src/events/MessageEvents.ts | 21 +- src/events/MessageEvents/OnMessage.ts | 59 ++++ src/helpers/SettingsHelper.ts | 50 +++ src/helpers/StringTools.ts | 13 + src/helpers/embeds/ErrorEmbed.ts | 2 +- src/helpers/embeds/EventEmbed.ts | 34 +- src/helpers/embeds/LogEmbed.ts | 33 +- src/helpers/embeds/PublicEmbed.ts | 2 +- src/{Register.ts => registry.ts} | 10 +- src/type/command.ts | 9 + src/vylbot.ts | 26 +- tests/client/client.test.ts | 190 ++++++----- tests/client/util.test.ts | 32 +- tests/events/MemberEvents.test.ts | 16 +- .../MemberEvents/GuildMemberUpdate.test.ts | 12 +- tests/events/MessageEvents.test.ts | 40 +-- tests/helpers/embeds/EventEmbed.test.ts | 160 +++++++--- tests/helpers/embeds/LogEmbed.test.ts | 192 +++++++---- tsconfig.json | 6 +- yarn.lock | 298 +++++++++++++++++- 53 files changed, 1816 insertions(+), 373 deletions(-) create mode 100644 data/config.txt rename data/rules/{rules.json => 290149509969739776.json} (100%) create mode 100644 data/rules/442730357897429002.json create mode 100644 data/rules/501231711271780357.json delete mode 100644 jest.config.js create mode 100644 jest.config.json create mode 100644 ormconfig.json.template create mode 100644 src/commands/code.ts create mode 100644 src/commands/config.ts create mode 100644 src/commands/disable.ts create mode 100644 src/commands/setup.ts create mode 100644 src/constants/CommandResponse.ts create mode 100644 src/constants/DefaultValues.ts create mode 100644 src/contracts/BaseEntity.ts create mode 100644 src/entity/Server.ts create mode 100644 src/entity/Setting.ts create mode 100644 src/events/MessageEvents/OnMessage.ts create mode 100644 src/helpers/SettingsHelper.ts rename src/{Register.ts => registry.ts} (78%) diff --git a/.env.template b/.env.template index 22c5724..d512aac 100644 --- a/.env.template +++ b/.env.template @@ -7,28 +7,7 @@ # any secret values. BOT_TOKEN= -BOT_PREFIX=v! BOT_VER=3.0 BOT_AUTHOR=Vylpes BOT_DATE=28 Nov 2021 -BOT_OWNERID=147392775707426816 - -FOLDERS_COMMANDS=src/commands -FOLDERS_EVENTS=src/events - -COMMANDS_DISABLED= -COMMANDS_DISABLED_MESSAGE=This command is disabled. - -COMMANDS_ROLE_ROLES=Notify,VotePings,ProjectUpdates - -COMMANDS_RULES_FILE=data/rules/rules.json - -EMBED_COLOUR=0x3050ba -EMBED_COLOUR_ERROR=0xD52803 - -ROLES_MODERATOR=Moderator -ROLES_MUTED=Muted - -CHANNELS_LOGS_MESSAGE=message-logs -CHANNELS_LOGS_MEMBER=member-logs -CHANNELS_LOGS_MOD=mod-logs \ No newline at end of file +BOT_OWNERID=147392775707426816 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 707ff92..1707d85 100644 --- a/.gitignore +++ b/.gitignore @@ -104,4 +104,5 @@ dist .tern-port config.json -.DS_Store \ No newline at end of file +.DS_Store +ormconfig.json \ No newline at end of file diff --git a/data/config.txt b/data/config.txt new file mode 100644 index 0000000..3156fb2 --- /dev/null +++ b/data/config.txt @@ -0,0 +1,22 @@ +USAGE: [value] + +===[ KEYS ]=== +bot.prefix: The bot prefix for the server (Default: "v!") + +commands.disabled: Disabled commands, separated by commas (Default: "") + +role.assignable: List of roles assignable to user (Default: []) +role.moderator: The moderator role name (Default: "Moderator") +role.administrator: The administrator role name (Default: "Administrator") +role.muted: The muted role name (Default: "Muted") + +rules.file: The location of the rules file (Default: "data/rules/rules") + +channels.logs.message: The channel message events will be logged to (Default: "message-logs") +channels.logs.member: The channel member events will be logged to (Default: "member-logs") +channels.logs.mod: The channel mod events will be logged to (Default: "mod-logs") + +verification.enabled: Enables/Disables the verification feature (Default: "false") +verification.channel: The channel to listen to for entry codes (Default: "entry") +verification.role: The server access role (Default: "Entry") +verification.code: The entry code for the channel (Default: "") \ No newline at end of file diff --git a/data/rules/rules.json b/data/rules/290149509969739776.json similarity index 100% rename from data/rules/rules.json rename to data/rules/290149509969739776.json diff --git a/data/rules/442730357897429002.json b/data/rules/442730357897429002.json new file mode 100644 index 0000000..6fef8cd --- /dev/null +++ b/data/rules/442730357897429002.json @@ -0,0 +1,88 @@ +[ + { + "image": "https://i.imgur.com/bjH1gza.png" + }, + { + "title": "Bot Testing Ground", + "description": [ + "Welcome to Vylpes' Den! Make sure to say hi!", + "Invite link: https://discord.gg/UyAhAVp" + ] + }, + { + "title": "Discord TOS", + "description": [ + "All servers are required to follow the Discord Terms of Service. This includes minimum age requirements (13+). If the moderation team discover a breach of TOS we are required by discord to ban. Make sure you know them!", + "https://discord.com/terms" + ] + }, + { + "title": "Rules", + "description": [ + "**English Only**", + "In order for everyone to understand each other we would like to ask everyone to speak in English only.", + "", + "**No NSFW or Obscene Content**", + "This includes text, images, or links featuring nudity, sex, hard violence, or other graphically disturbing content.", + "", + "**Treat Everyone with Respect**", + "Absolutely no harassment, witch hunting, sexism, racism, or hate speech will be tolerated.", + "", + "**No spam or self promotion**", + "Outside of #self-promo. This includes DMing fellow members.", + "", + "**Keep Politics to #general**", + "And make sure it doesn't become too heated. Debate don't argue.", + "", + "**Drama From Other Servers**", + "Please don't bring up drama from other servers, keep that to DMs", + "", + "**Bot Abuse**", + "Don't abuse the bots or you will be blocked from using them", + "", + "**Event Spoilers**", + "Contents of events and keynotes, such as the Nintendo Direct, must be spoken about in events, this rule applies for up to 24 hours after the event ends. Even though we will only enforce talking there for a set time, please be considerate of those who haven't watched the event yet." + ] + }, + { + "title": "Moderators Discretion", + "description": [ + "Don't argue with a mod's decision. A moderator's choice is final. If you have an issue with a member of the mod team DM me (Vylpes#0001)." + ] + }, + { + "title": "Supporters", + "description": [ + "If you are a Twitch Subscriber or a Patreon Member and have linked your profiles to your discord account you will get exclusive access to the Vylpes Plus channels, including early access to videos!" + ] + }, + { + "title": "Self-Assignable Roles", + "description": [ + "If you want to assign yourself roles, go to #bot-stuff and type v!role . The current roles you can get are:", + "Notify: Get pinged when a new stream or video releases.", + "VotePings: Get pinged when I start a new poll", + "ProjectUpdates: Get pinged when I update my projects as well as new for them" + ] + }, + { + "title": "VylBot", + "description": [ + "This server uses a bot made by me, VylBot, to help moderate the server.", + "For more information on it, see the GitHub repositories:", + "https://github.com/Vylpes/vylbot-core", + "https://github.com/Vylpes/vylbot-app" + ] + }, + { + "title": "Links", + "description": [ + "YouTube: https://www.youtube.com/channel/UCwPlzKwCmP5Q9bCX3fHk2BA", + "Patreon: https://www.patreon.com/vylpes", + "Twitch: https://www.twitch.tv/vylpes_", + "Twitter: https://twitter.com/vylpes", + "Blog: https://vylpes.xyz" + ], + "footer": "Last updated 01/02/2022" + } +] \ No newline at end of file diff --git a/data/rules/501231711271780357.json b/data/rules/501231711271780357.json new file mode 100644 index 0000000..324c40e --- /dev/null +++ b/data/rules/501231711271780357.json @@ -0,0 +1,70 @@ +[ + { + "title": "Welcome to Mankalor's Discord Server!", + "description": [ + "*You must follow Discord's TOS, including the rule where", "you must be 13 years or older.", + "If moderators know you're under 13, we will have to ban you!*", + "", + "You need to input a code in *#entry* which is somewhere in this message before you can start chatting, so read the server rules and info below.", + "If you still don't see the other channels after writing this code, message a moderator. For any issues with this bot, message Vylpes#5725." + ] + }, + { + "title": "Server Rules", + "description": [ + "1. We allow most things in *#general-off-topic*, but if it pertains to a topic that has a channel, post it in the correct chat.", + "", + "2. No spamming, except in *#bot-craziness* and *#spam* ", + " 2a. Those 'Hacker Warning!!! Copy Paste this to all servers!' messages and other copypastas are considered spam.", + "", + "3. Do not insult or harass anyone for race, religion, gender, gaming skills, social skills, etc.", + " 3a. Some people may be new to Discord or a game. Politely educate, don't belittle.", + "", + "4. Absolutely no NSFW content on this server. (Porn, Rule 34, etc.)", + "", + "5. Swearing is allowed, but certain words such as racial/homophobic/disability slurs or sex terms will still be filtered out. Bypassing this will result in punishment.", + " 5a. Swearing past the point of typical rager is still not allowed.", + " 5b. If you're unsure if a word is allowed, then don't use it.", + "", + "6. Avoid unnecessarily & excessively @ mentioning anyone, even in *#bot-craziness* and *#spam*", + "", + "7. Advertising your own content (videos, channels, servers, etc.) should only go in *#self-promo*", + "", + "8. Do not bring up drama from other places here, keep that to DMs.", + "", + "9. Keep it serious in the venting chats, don't joke around.", + " 9a. To access the vent channels, assign yourself the role from *#self-assign-roles*", + "", + "10. Do not ask to become a moderator.", + "", + "11. Please don't join to ask about how to download hacks, where to find them, etc. Requests will be ignored and if you continue to ask you will be muted.", + " 11a. Watch Mankalor's video on it here: https://www.youtube.com/watch?v=wps_4DBlEyM" + ] + }, + { + "description": [ + "1. You can assign yourself a game role in *#self-assign-roles*. When you want to setup a lobby type m!lobby into the game's channel.", + " 1a. Do not ping a role excessively in a short time. The command's cooldown is 20 minutes.", + " 1b. Only use the ping to set up lobbies. Not for advertising, pointless announcements, etc.", + " 1c. Only give yourself a role if you don't mind Discord pings.", + " 1d. Do not complain about the pings if they're being used correctly. Remove the role, or you will be punished by moderators.", + "", + "2. Only server staff can use @ everyone & @ here.", + "3. If you are a Youtube Sponsor or Twitch Subscriber, you can get the role if you sync your Twitch/Youtube account with your Discord account by going into User Settings > Connections > Twitch/Youtube.", + " 3a. This might not work on mobile.", + " 3b. We cannot assign SponSub roles manually.", + "", + "4. Mankalor will ping @ notificationsquad for new videos and streams in #new-videos-streams. If you want these pings, type `m!role Notification Squad` in #self-assign-roles", + "", + "5. Server link in case you want to invite someone. This link is in the description of my videos, too: https://discord.gg/DQkWVbz", + "", + "Not following these rules will result in a warning, mute, or ban, depending on the severity and number of offenses.", + "", + "If you notice anything wrong, notify the *Server Staff*!", + "", + "Once you've sent the code, go say hi in *#general-off-topic*!", + "", + "**Update 01 Oct 2021:** Added `11.` and `11a.` to rules" + ] + } +] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index da68dfd..a10aebd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,24 @@ version: "3.9" services: - discord: - build: . \ No newline at end of file + # discord: + # build: . + + database: + image: mysql/mysql-server + command: --default-authentication-plugin=mysql_native_password + restart: always + environment: + - MYSQL_DATABASE=vylbot + - MYSQL_USER=dev + - MYSQL_PASSWORD=dev + - MYSQL_ROOT_PASSWORD=root + ports: + - 3306:3306 + + phpmyadmin: + image: phpmyadmin + restart: always + ports: + - 8080:80 + environment: + - PMA_ARBITRARY=1 \ No newline at end of file diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 641ea7d..0000000 --- a/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -/** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - setupFiles: ["./jest.setup.js"] -}; \ No newline at end of file diff --git a/jest.config.json b/jest.config.json new file mode 100644 index 0000000..782f4c2 --- /dev/null +++ b/jest.config.json @@ -0,0 +1,5 @@ +{ + "preset": "ts-jest", + "testEnvironment": "node", + "setupFiles": ["./jest.setup.js"] + } \ No newline at end of file diff --git a/ormconfig.json.template b/ormconfig.json.template new file mode 100644 index 0000000..6c48598 --- /dev/null +++ b/ormconfig.json.template @@ -0,0 +1,24 @@ +{ + "type": "mysql", + "host": "localhost", + "port": 3306, + "username": "dev", + "password": "dev", + "database": "vylbot", + "synchronize": true, + "logging": false, + "entities": [ + "dist/entity/**/*.js" + ], + "migrations": [ + "dist/migration/**/*.js" + ], + "subscribers": [ + "dist/subscriber/**/*.js" + ], + "cli": { + "entitiesDir": "dist/entity", + "migrationsDir": "dist/migration", + "subscribersDir": "dist/subscriber" + } +} \ No newline at end of file diff --git a/package.json b/package.json index 27c731c..e455a26 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,17 @@ "homepage": "https://github.com/Vylpes/vylbot-app", "dependencies": { "@types/jest": "^27.0.3", + "@types/uuid": "^8.3.4", "discord.js": "12.5.3", "dotenv": "^10.0.0", "emoji-regex": "^9.2.0", "jest": "^27.4.5", "jest-mock-extended": "^2.0.4", + "mysql": "^2.18.1", "random-bunny": "^2.0.0", - "ts-jest": "^27.1.2" + "ts-jest": "^27.1.2", + "typeorm": "^0.2.44", + "uuid": "^8.3.2" }, "devDependencies": { "@types/node": "^16.11.10", diff --git a/src/client/client.ts b/src/client/client.ts index c52734c..36b915b 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -1,5 +1,7 @@ import { Client } from "discord.js"; import * as dotenv from "dotenv"; +import { createConnection } from "typeorm"; +import DefaultValues from "../constants/DefaultValues"; import ICommandItem from "../contracts/ICommandItem"; import IEventItem from "../contracts/IEventItem"; import { Command } from "../type/command"; @@ -23,10 +25,12 @@ export class CoreClient extends Client { return this._eventItems; } - constructor() { + constructor(devmode: boolean = false) { super(); dotenv.config(); + DefaultValues.useDevPrefix = devmode; + this._commandItems = []; this._eventItems = []; @@ -34,11 +38,16 @@ export class CoreClient extends Client { this._util = new Util(); } - public start() { - if (!process.env.BOT_TOKEN) throw "BOT_TOKEN is not defined in .env"; - if (!process.env.BOT_PREFIX) throw "BOT_PREFIX is not defined in .env"; - if (!process.env.FOLDERS_COMMANDS) throw "FOLDERS_COMMANDS is not defined in .env"; - if (!process.env.FOLDERS_EVENTS) throw "FOLDERS_EVENTS is not defined in .env"; + public async start() { + if (!process.env.BOT_TOKEN) { + console.error("BOT_TOKEN is not defined in .env"); + return; + } + + await createConnection().catch(e => { + console.error(e); + return; + }); super.on("message", (message) => this._events.onMessage(message, this._commandItems)); super.on("ready", this._events.onReady); diff --git a/src/client/events.ts b/src/client/events.ts index ca0d058..a8d5085 100644 --- a/src/client/events.ts +++ b/src/client/events.ts @@ -1,6 +1,7 @@ import { Message } from "discord.js"; import { IBaseResponse } from "../contracts/IBaseResponse"; import ICommandItem from "../contracts/ICommandItem"; +import SettingsHelper from "../helpers/SettingsHelper"; import { Util } from "./util"; export interface IEventResponse extends IBaseResponse { @@ -21,7 +22,7 @@ export class Events { // Emit when a message is sent // Used to check for commands - public onMessage(message: Message, commands: ICommandItem[]): IEventResponse { + public async onMessage(message: Message, commands: ICommandItem[]): Promise { if (!message.guild) return { valid: false, message: "Message was not sent in a guild, ignoring.", @@ -32,7 +33,14 @@ export class Events { message: "Message was sent by a bot, ignoring.", }; - const prefix = process.env.BOT_PREFIX as string; + const prefix = await SettingsHelper.GetSetting("bot.prefix", message.guild.id); + + if (!prefix) { + return { + valid: false, + message: "Prefix not found", + }; + } if (message.content.substring(0, prefix.length).toLowerCase() == prefix.toLowerCase()) { const args = message.content.substring(prefix.length).split(" "); @@ -43,7 +51,7 @@ export class Events { message: "Command name was not found", }; - const res = this._util.loadCommand(name, args, message, commands); + const res = await this._util.loadCommand(name, args, message, commands); if (!res.valid) { return { diff --git a/src/client/util.ts b/src/client/util.ts index 61ec0f9..951a632 100644 --- a/src/client/util.ts +++ b/src/client/util.ts @@ -7,6 +7,10 @@ import { Event } from "../type/event"; import { ICommandContext } from "../contracts/ICommandContext"; import ICommandItem from "../contracts/ICommandItem"; import IEventItem from "../contracts/IEventItem"; +import SettingsHelper from "../helpers/SettingsHelper"; +import StringTools from "../helpers/StringTools"; +import { CommandResponse } from "../constants/CommandResponse"; +import ErrorMessages from "../constants/ErrorMessages"; export interface IUtilResponse extends IBaseResponse { context?: { @@ -18,13 +22,19 @@ export interface IUtilResponse extends IBaseResponse { // Util Class export class Util { - public loadCommand(name: string, args: string[], message: Message, commands: ICommandItem[]): IUtilResponse { + public async loadCommand(name: string, args: string[], message: Message, commands: ICommandItem[]): Promise { if (!message.member) return { valid: false, message: "Member is not part of message", }; - const disabledCommands = process.env.COMMANDS_DISABLED?.split(','); + if (!message.guild) return { + valid: false, + message: "Message is not part of a guild", + }; + + const disabledCommandsString = await SettingsHelper.GetSetting("commands.disabled", message.guild?.id); + const disabledCommands = disabledCommandsString?.split(","); if (disabledCommands?.find(x => x == name)) { message.reply(process.env.COMMANDS_DISABLED_MESSAGE || "This command is disabled."); @@ -51,13 +61,26 @@ export class Util { const requiredRoles = item.Command._roles; for (const i in requiredRoles) { - if (!message.member.roles.cache.find(role => role.name == requiredRoles[i])) { - message.reply(`You require the \`${requiredRoles[i]}\` role to run this command`); + if (message.guild) { + const setting = await SettingsHelper.GetSetting(`role.${requiredRoles[i]}`, message.guild?.id); - return { - valid: false, - message: `You require the \`${requiredRoles[i]}\` role to run this command` - }; + if (!setting) { + message.reply("Unable to verify if you have this role, please contact your bot administrator"); + + return { + valid: false, + message: "Unable to verify if you have this role, please contact your bot administrator" + }; + } + + if (!message.member.roles.cache.find(role => role.name == setting)) { + message.reply(`You require the \`${StringTools.Capitalise(setting)}\` role to run this command`); + + return { + valid: false, + message: `You require the \`${StringTools.Capitalise(setting)}\` role to run this command` + }; + } } } @@ -67,6 +90,27 @@ export class Util { message: message }; + const precheckResponse = item.Command.precheck(context); + const precheckAsyncResponse = await item.Command.precheckAsync(context); + + if (precheckResponse != CommandResponse.Ok) { + message.reply(ErrorMessages.GetErrorMessage(precheckResponse)); + + return { + valid: false, + message: ErrorMessages.GetErrorMessage(precheckResponse) + }; + } + + if (precheckAsyncResponse != CommandResponse.Ok) { + message.reply(ErrorMessages.GetErrorMessage(precheckAsyncResponse)); + + return { + valid: false, + message: ErrorMessages.GetErrorMessage(precheckAsyncResponse) + }; + } + item.Command.execute(context); return { diff --git a/src/commands/ban.ts b/src/commands/ban.ts index f565834..865a0cf 100644 --- a/src/commands/ban.ts +++ b/src/commands/ban.ts @@ -12,7 +12,7 @@ export default class Ban extends Command { super._category = "Moderation"; super._roles = [ - process.env.ROLES_MODERATOR! + "moderator" ]; } @@ -69,7 +69,7 @@ export default class Ban extends Command { await targetMember.ban({ reason: `Moderator: ${context.message.author.tag}, Reason: ${reason || "*none*"}` }); - logEmbed.SendToModLogsChannel(); + await logEmbed.SendToModLogsChannel(); publicEmbed.SendToCurrentChannel(); return { diff --git a/src/commands/clear.ts b/src/commands/clear.ts index 4f8f893..f8e1534 100644 --- a/src/commands/clear.ts +++ b/src/commands/clear.ts @@ -11,7 +11,7 @@ export default class Clear extends Command { super._category = "Moderation"; super._roles = [ - process.env.ROLES_MODERATOR! + "moderator" ]; } diff --git a/src/commands/code.ts b/src/commands/code.ts new file mode 100644 index 0000000..d2438fb --- /dev/null +++ b/src/commands/code.ts @@ -0,0 +1,94 @@ +import { CommandResponse } from "../constants/CommandResponse"; +import { ICommandContext } from "../contracts/ICommandContext"; +import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; +import PublicEmbed from "../helpers/embeds/PublicEmbed"; +import SettingsHelper from "../helpers/SettingsHelper"; +import StringTools from "../helpers/StringTools"; +import { Command } from "../type/command"; + +export default class Code extends Command { + constructor() { + super(); + + super._category = "Moderation"; + super._roles = [ + "moderator" + ]; + } + + public override async precheckAsync(context: ICommandContext): Promise { + if (!context.message.guild){ + return CommandResponse.NotInServer; + } + + const isEnabled = await SettingsHelper.GetSetting("verification.enabled", context.message.guild?.id); + + if (!isEnabled) { + return CommandResponse.FeatureDisabled; + } + + if (isEnabled.toLocaleLowerCase() != 'true') { + return CommandResponse.FeatureDisabled; + } + + return CommandResponse.Ok; + } + + public override async execute(context: ICommandContext) { + const action = context.args[0]; + + switch (action) { + case "randomise": + await this.Randomise(context); + break; + case "embed": + await this.SendEmbed(context); + break; + default: + await this.SendUsage(context); + } + } + + private async SendUsage(context: ICommandContext) { + const description = [ + "USAGE: ", + "", + "randomise: Sets the server's entry code to a random code", + "embed: Sends an embed with the server's entry code" + ].join("\n"); + + const embed = new PublicEmbed(context, "", description); + embed.SendToCurrentChannel(); + } + + private async Randomise(context: ICommandContext) { + if (!context.message.guild) { + return; + } + + const randomCode = StringTools.RandomString(5); + + await SettingsHelper.SetSetting("verification.code", context.message.guild.id, randomCode); + + const embed = new PublicEmbed(context, "Code", `Entry code has been set to \`${randomCode}\``); + embed.SendToCurrentChannel(); + } + + private async SendEmbed(context: ICommandContext) { + if (!context.message.guild) { + return; + } + + const code = await SettingsHelper.GetSetting("verification.code", context.message.guild.id); + + if (!code || code == "") { + const errorEmbed = new ErrorEmbed(context, "There is no code for this server setup."); + errorEmbed.SendToCurrentChannel(); + + return; + } + + const embed = new PublicEmbed(context, "Entry Code", code!); + embed.SendToCurrentChannel(); + } +} \ No newline at end of file diff --git a/src/commands/config.ts b/src/commands/config.ts new file mode 100644 index 0000000..5a6a6ef --- /dev/null +++ b/src/commands/config.ts @@ -0,0 +1,136 @@ +import { Guild } from "discord.js"; +import { readFileSync } from "fs"; +import { CommandResponse } from "../constants/CommandResponse"; +import DefaultValues from "../constants/DefaultValues"; +import { ICommandContext } from "../contracts/ICommandContext"; +import ICommandReturnContext from "../contracts/ICommandReturnContext"; +import Server from "../entity/Server"; +import Setting from "../entity/Setting"; +import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; +import PublicEmbed from "../helpers/embeds/PublicEmbed"; +import { Command } from "../type/command"; + +export default class Config extends Command { + constructor() { + super(); + super._category = "Administration"; + super._roles = [ + "administrator" + ] + } + + public override async precheckAsync(context: ICommandContext): Promise { + if (!context.message.guild) { + return CommandResponse.ServerNotSetup; + } + + const server = await Server.FetchOneById(Server, context.message.guild?.id, [ + "Settings", + ]); + + if (!server) { + return CommandResponse.ServerNotSetup; + } + + return CommandResponse.Ok; + } + + public override async execute(context: ICommandContext) { + if (!context.message.guild) { + return; + } + + const server = await Server.FetchOneById(Server, context.message.guild?.id, [ + "Settings", + ]); + + if (!server) { + return; + } + + const key = context.args[0]; + const action = context.args[1]; + const value = context.args.splice(2).join(" "); + + if (!key) { + this.SendHelpText(context); + } else if (!action) { + this.GetValue(context, server, key); + } else { + switch(action) { + case 'reset': + this.ResetValue(context, server, key); + break; + case 'set': + if (!value) { + const errorEmbed = new ErrorEmbed(context, "Value is required when setting"); + errorEmbed.SendToCurrentChannel(); + return; + } + + this.SetValue(context, server, key, value); + break; + default: + const errorEmbed = new ErrorEmbed(context, "Action must be either set or reset"); + errorEmbed.SendToCurrentChannel(); + return; + } + } + } + + private async SendHelpText(context: ICommandContext) { + const description = readFileSync(`${process.cwd()}/data/config.txt`).toString(); + + const embed = new PublicEmbed(context, "Config", description); + + embed.SendToCurrentChannel(); + } + + private async GetValue(context: ICommandContext, server: Server, key: string) { + const setting = server.Settings.filter(x => x.Key == key)[0]; + + if (setting) { + const embed = new PublicEmbed(context, "", `${key}: ${setting.Value}`); + embed.SendToCurrentChannel(); + } else { + const embed = new PublicEmbed(context, "", `${key}: ${DefaultValues.GetValue(key)} `); + embed.SendToCurrentChannel(); + } + } + + private async ResetValue(context: ICommandContext, server: Server, key: string) { + const setting = server.Settings.filter(x => x.Key == key)[0]; + + if (!setting) { + const embed = new PublicEmbed(context, "", "Setting has been reset"); + embed.SendToCurrentChannel(); + return; + } + + await Setting.Remove(Setting, setting); + + const embed = new PublicEmbed(context, "", "Setting has been reset"); + embed.SendToCurrentChannel(); + } + + private async SetValue(context: ICommandContext, server: Server, key: string, value: string) { + const setting = server.Settings.filter(x => x.Key == key)[0]; + + if (setting) { + setting.UpdateBasicDetails(key, value); + + await setting.Save(Setting, setting); + } else { + const newSetting = new Setting(key, value); + + await newSetting.Save(Setting, newSetting); + + server.AddSettingToServer(newSetting); + + await server.Save(Server, server); + } + + const embed = new PublicEmbed(context, "", "Setting has been set"); + embed.SendToCurrentChannel(); + } +} \ No newline at end of file diff --git a/src/commands/disable.ts b/src/commands/disable.ts new file mode 100644 index 0000000..a1064c5 --- /dev/null +++ b/src/commands/disable.ts @@ -0,0 +1,96 @@ +import { CommandResponse } from "../constants/CommandResponse"; +import { ICommandContext } from "../contracts/ICommandContext"; +import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; +import PublicEmbed from "../helpers/embeds/PublicEmbed"; +import SettingsHelper from "../helpers/SettingsHelper"; +import StringTools from "../helpers/StringTools"; +import { Command } from "../type/command"; + +export default class Disable extends Command { + constructor() { + super(); + + super._category = "Moderation"; + super._roles = [ + "moderator" + ]; + } + + public override async execute(context: ICommandContext) { + const action = context.args[0]; + + switch (action) { + case "add": + await this.Add(context); + break; + case "remove": + await this.Remove(context); + break; + default: + await this.SendUsage(context); + } + } + + private async SendUsage(context: ICommandContext) { + const description = [ + "USAGE: ", + "", + "add: Adds the command name to the server's disabled command string", + "remove: Removes the command name from the server's disabled command string", + "name: The name of the command to enable/disable" + ].join("\n"); + + const embed = new PublicEmbed(context, "", description); + embed.SendToCurrentChannel(); + } + + private async Add(context: ICommandContext) { + if (!context.message.guild) { + return; + } + + const commandName = context.args[1]; + + if (!commandName) { + this.SendUsage(context); + return; + } + + const disabledCommandsString = await SettingsHelper.GetSetting("commands.disabled", context.message.guild.id); + const disabledCommands = disabledCommandsString != "" ? disabledCommandsString?.split(",") : []; + + disabledCommands?.push(commandName); + + await SettingsHelper.SetSetting("commands.disabled", context.message.guild.id, disabledCommands!.join(",")); + + const embed = new PublicEmbed(context, "", `Disabled command: ${commandName}`); + embed.SendToCurrentChannel(); + } + + private async Remove(context: ICommandContext) { + if (!context.message.guild) { + return; + } + + const commandName = context.args[1]; + + if (!commandName) { + this.SendUsage(context); + return; + } + + const disabledCommandsString = await SettingsHelper.GetSetting("commands.disabled", context.message.guild.id); + const disabledCommands = disabledCommandsString != "" ? disabledCommandsString?.split(",") : []; + + const disabledCommandsInstance = disabledCommands?.findIndex(x => x == commandName); + + if (disabledCommandsInstance! > -1) { + disabledCommands?.splice(disabledCommandsInstance!, 1); + } + + await SettingsHelper.SetSetting("commands.disabled", context.message.guild.id, disabledCommands!.join(",")); + + const embed = new PublicEmbed(context, "", `Enabled command: ${commandName}`); + embed.SendToCurrentChannel(); + } +} \ No newline at end of file diff --git a/src/commands/kick.ts b/src/commands/kick.ts index 852b57a..6a741a7 100644 --- a/src/commands/kick.ts +++ b/src/commands/kick.ts @@ -12,7 +12,7 @@ export default class Kick extends Command { super._category = "Moderation"; super._roles = [ - process.env.ROLES_MODERATOR! + "moderator" ]; } @@ -72,7 +72,7 @@ export default class Kick extends Command { await targetMember.kick(`Moderator: ${context.message.author.tag}, Reason: ${reason || "*none*"}`); - logEmbed.SendToModLogsChannel(); + await logEmbed.SendToModLogsChannel(); publicEmbed.SendToCurrentChannel(); return { diff --git a/src/commands/mute.ts b/src/commands/mute.ts index 4b63527..79a21ba 100644 --- a/src/commands/mute.ts +++ b/src/commands/mute.ts @@ -12,7 +12,7 @@ export default class Mute extends Command { super._category = "Moderation"; super._roles = [ - process.env.ROLES_MODERATOR! + "moderator" ]; } @@ -85,7 +85,7 @@ export default class Mute extends Command { await targetMember.roles.add(mutedRole, `Moderator: ${context.message.author.tag}, Reason: ${reason || "*none*"}`); - logEmbed.SendToModLogsChannel(); + await logEmbed.SendToModLogsChannel(); publicEmbed.SendToCurrentChannel(); return { diff --git a/src/commands/rules.ts b/src/commands/rules.ts index c41f381..7860288 100644 --- a/src/commands/rules.ts +++ b/src/commands/rules.ts @@ -18,12 +18,12 @@ export default class Rules extends Command { super._category = "Admin"; super._roles = [ - process.env.ROLES_MODERATOR! + "administrator" ]; } public override execute(context: ICommandContext): ICommandReturnContext { - if (!existsSync(`${process.cwd()}/${process.env.COMMANDS_RULES_FILE!}`)) { + if (!existsSync(`${process.cwd()}/data/rules/${context.message.guild?.id}.json`)) { const errorEmbed = new ErrorEmbed(context, "Rules file doesn't exist"); errorEmbed.SendToCurrentChannel(); @@ -33,7 +33,7 @@ export default class Rules extends Command { }; } - const rulesFile = readFileSync(`${process.cwd()}/${process.env.COMMANDS_RULES_FILE}`).toString(); + const rulesFile = readFileSync(`${process.cwd()}/data/rules/${context.message.guild?.id}.json`).toString(); const rules = JSON.parse(rulesFile) as IRules[]; const embeds: PublicEmbed[] = []; diff --git a/src/commands/setup.ts b/src/commands/setup.ts new file mode 100644 index 0000000..ee20012 --- /dev/null +++ b/src/commands/setup.ts @@ -0,0 +1,37 @@ +import { ICommandContext } from "../contracts/ICommandContext"; +import ICommandReturnContext from "../contracts/ICommandReturnContext"; +import Server from "../entity/Server"; +import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; +import PublicEmbed from "../helpers/embeds/PublicEmbed"; +import { Command } from "../type/command"; + +export default class Setup extends Command { + constructor() { + super(); + super._category = "Administration"; + super._roles = [ + "moderator" + ] + } + + public override async execute(context: ICommandContext) { + if (!context.message.guild) { + return; + } + + const server = await Server.FetchOneById(Server, context.message.guild?.id); + + if (server) { + const embed = new ErrorEmbed(context, "This server has already been setup, please configure using the config command"); + embed.SendToCurrentChannel(); + return; + } + + const newServer = new Server(context.message.guild?.id); + + await newServer.Save(Server, newServer); + + const embed = new PublicEmbed(context, "Success", "Please configure using the config command"); + embed.SendToCurrentChannel(); + } +} \ No newline at end of file diff --git a/src/commands/unmute.ts b/src/commands/unmute.ts index 6b5901c..243d772 100644 --- a/src/commands/unmute.ts +++ b/src/commands/unmute.ts @@ -12,7 +12,7 @@ export default class Unmute extends Command { super._category = "Moderation"; super._roles = [ - process.env.ROLES_MODERATOR! + "moderator" ]; } @@ -85,7 +85,7 @@ export default class Unmute extends Command { await targetMember.roles.remove(mutedRole, `Moderator: ${context.message.author.tag}, Reason: ${reason || "*none*"}`); - logEmbed.SendToModLogsChannel(); + await logEmbed.SendToModLogsChannel(); publicEmbed.SendToCurrentChannel(); return { diff --git a/src/commands/warn.ts b/src/commands/warn.ts index b4fc236..858f7c3 100644 --- a/src/commands/warn.ts +++ b/src/commands/warn.ts @@ -11,11 +11,11 @@ export default class Warn extends Command { super._category = "Moderation"; super._roles = [ - process.env.ROLES_MODERATOR! + "moderator" ]; } - public override execute(context: ICommandContext): ICommandReturnContext { + public override async execute(context: ICommandContext): Promise { const user = context.message.mentions.users.first(); if (!user) { @@ -60,7 +60,7 @@ export default class Warn extends Command { const publicEmbed = new PublicEmbed(context, "", `${user} has been warned`); publicEmbed.AddReason(reason); - logEmbed.SendToModLogsChannel(); + await logEmbed.SendToModLogsChannel(); publicEmbed.SendToCurrentChannel(); return { diff --git a/src/constants/CommandResponse.ts b/src/constants/CommandResponse.ts new file mode 100644 index 0000000..b876ce8 --- /dev/null +++ b/src/constants/CommandResponse.ts @@ -0,0 +1,7 @@ +export enum CommandResponse { + Ok, + Unauthorised, + ServerNotSetup, + NotInServer, + FeatureDisabled, +} \ No newline at end of file diff --git a/src/constants/DefaultValues.ts b/src/constants/DefaultValues.ts new file mode 100644 index 0000000..6ed44dc --- /dev/null +++ b/src/constants/DefaultValues.ts @@ -0,0 +1,56 @@ +export default class DefaultValues { + public static values: ISettingValue[] = []; + public static useDevPrefix: boolean = false; + + public static GetValue(key: string): string | undefined { + this.SetValues(); + + const res = this.values.find(x => x.Key == key); + + if (!res) { + return undefined; + } + + return res.Value; + } + + private static SetValues() { + if (this.values.length == 0) { + // Bot + if (this.useDevPrefix) { + this.values.push({ Key: "bot.prefix", Value: "d!" }); + } else { + this.values.push({ Key: "bot.prefix", Value: "v!" }); + } + + // Commands + this.values.push({ Key: "commands.disabled", Value: "" }); + this.values.push({ Key: "commands.disabled.message", Value: "This command is disabled." }); + + // Role (Command) + this.values.push({ Key: "role.assignable", Value: "" }); + this.values.push({ Key: "role.moderator", Value: "Moderator" }); + this.values.push({ Key: "role.administrator", Value: "Administrator"}); + this.values.push({ Key: "role.muted", Value: "Muted" }); + + // Rules (Command) + this.values.push({ Key: "rules.file", Value: "data/rules/rules" }); + + // Channels + this.values.push({ Key: "channels.logs.message", Value: "message-logs" }); + this.values.push({ Key: "channels.logs.member", Value: "member-logs" }); + this.values.push({ Key: "channels.logs.mod", Value: "mod-logs" }); + + // Verification + this.values.push({ Key: "verification.enabled", Value: "false" }); + this.values.push({ Key: "verification.channel", Value: "entry" }); + this.values.push({ Key: "verification.role", Value: "Entry" }); + this.values.push({ Key: "verification.code", Value: "" }); + } + } +} + +export interface ISettingValue { + Key: string, + Value: string, +}; \ No newline at end of file diff --git a/src/constants/ErrorMessages.ts b/src/constants/ErrorMessages.ts index a397dcf..588a143 100644 --- a/src/constants/ErrorMessages.ts +++ b/src/constants/ErrorMessages.ts @@ -1,5 +1,27 @@ +import { CommandResponse } from "./CommandResponse"; + export default class ErrorMessages { public static readonly InsufficientBotPermissions = "Unable to do this action, am I missing permissions?"; public static readonly ChannelNotFound = "Unable to find channel"; public static readonly RoleNotFound = "Unable to find role"; + + public static readonly UserUnauthorised = "You are not authorised to use this command"; + public static readonly ServerNotSetup = "This server hasn't been setup yet, please run the setup command"; + public static readonly NotInServer = "This command requires to be ran inside of a server"; + public static readonly FeatureDisabled = "This feature is currently disabled by a server moderator"; + + public static GetErrorMessage(response: CommandResponse): string { + switch (response) { + case CommandResponse.Unauthorised: + return this.UserUnauthorised; + case CommandResponse.ServerNotSetup: + return this.ServerNotSetup; + case CommandResponse.NotInServer: + return this.NotInServer; + case CommandResponse.FeatureDisabled: + return this.FeatureDisabled; + default: + return ""; + } + } } \ No newline at end of file diff --git a/src/contracts/BaseEntity.ts b/src/contracts/BaseEntity.ts new file mode 100644 index 0000000..18c2b76 --- /dev/null +++ b/src/contracts/BaseEntity.ts @@ -0,0 +1,68 @@ +import { Column, DeepPartial, EntityTarget, getConnection, PrimaryColumn } from "typeorm"; +import { v4 } from "uuid"; + +export default class BaseEntity { + constructor() { + this.Id = v4(); + + this.WhenCreated = new Date(); + this.WhenUpdated = new Date(); + } + + @PrimaryColumn() + Id: string; + + @Column() + WhenCreated: Date; + + @Column() + WhenUpdated: Date; + + public async Save(target: EntityTarget, entity: DeepPartial): Promise { + this.WhenUpdated = new Date(); + + const connection = getConnection(); + + const repository = connection.getRepository(target); + + await repository.save(entity); + } + + public static async Remove(target: EntityTarget, entity: T): Promise { + const connection = getConnection(); + + const repository = connection.getRepository(target); + + await repository.remove(entity); + } + + public static async FetchAll(target: EntityTarget, relations?: string[]): Promise { + const connection = getConnection(); + + const repository = connection.getRepository(target); + + const all = await repository.find({ relations: relations || [] }); + + return all; + } + + public static async FetchOneById(target: EntityTarget, id: string, relations?: string[]): Promise { + const connection = getConnection(); + + const repository = connection.getRepository(target); + + const single = await repository.findOne(id, { relations: relations || [] }); + + return single; + } + + public static async Any(target: EntityTarget): Promise { + const connection = getConnection(); + + const repository = connection.getRepository(target); + + const any = await repository.find(); + + return any.length > 0; + } +} \ No newline at end of file diff --git a/src/entity/Server.ts b/src/entity/Server.ts new file mode 100644 index 0000000..180aef0 --- /dev/null +++ b/src/entity/Server.ts @@ -0,0 +1,19 @@ +import { Column, Entity, getConnection, OneToMany } from "typeorm"; +import BaseEntity from "../contracts/BaseEntity"; +import Setting from "./Setting"; + +@Entity() +export default class Server extends BaseEntity { + constructor(serverId: string) { + super(); + + this.Id = serverId; + } + + @OneToMany(() => Setting, x => x.Server) + Settings: Setting[]; + + public AddSettingToServer(setting: Setting) { + this.Settings.push(setting); + } +} \ No newline at end of file diff --git a/src/entity/Setting.ts b/src/entity/Setting.ts new file mode 100644 index 0000000..bab0ad9 --- /dev/null +++ b/src/entity/Setting.ts @@ -0,0 +1,37 @@ +import { Column, Entity, getConnection, ManyToOne } from "typeorm"; +import BaseEntity from "../contracts/BaseEntity"; +import Server from "./Server"; + +@Entity() +export default class Setting extends BaseEntity { + constructor(key: string, value: string) { + super(); + + this.Key = key; + this.Value = value; + } + + @Column() + Key: string; + + @Column() + Value: string; + + @ManyToOne(() => Server, x => x.Settings) + Server: Server; + + public UpdateBasicDetails(key: string, value: string) { + this.Key = key; + this.Value = value; + } + + public static async FetchOneByKey(key: string, relations?: string[]): Promise { + const connection = getConnection(); + + const repository = connection.getRepository(Setting); + + const single = await repository.findOne({ Key: key }, { relations: relations || [] }); + + return single; + } +} \ No newline at end of file diff --git a/src/events/MemberEvents.ts b/src/events/MemberEvents.ts index 5bcda27..7ab41fd 100644 --- a/src/events/MemberEvents.ts +++ b/src/events/MemberEvents.ts @@ -9,37 +9,37 @@ export default class MemberEvents extends Event { super(); } - public override guildMemberAdd(member: GuildMember): IEventReturnContext { + public override async guildMemberAdd(member: GuildMember): Promise { const embed = new EventEmbed(member.guild, "Member Joined"); embed.AddUser("User", member.user, true); embed.addField("Created", member.user.createdAt); embed.setFooter(`Id: ${member.user.id}`); - embed.SendToMemberLogsChannel(); + await embed.SendToMemberLogsChannel(); return { embeds: [embed] }; } - public override guildMemberRemove(member: GuildMember): IEventReturnContext { + public override async guildMemberRemove(member: GuildMember): Promise { const embed = new EventEmbed(member.guild, "Member Left"); embed.AddUser("User", member.user, true); embed.addField("Joined", member.joinedAt); embed.setFooter(`Id: ${member.user.id}`); - embed.SendToMemberLogsChannel(); + await embed.SendToMemberLogsChannel(); return { embeds: [embed] }; } - public override guildMemberUpdate(oldMember: GuildMember, newMember: GuildMember): IEventReturnContext { + public override async guildMemberUpdate(oldMember: GuildMember, newMember: GuildMember): Promise { const handler = new GuildMemberUpdate(oldMember, newMember); if (oldMember.nickname != newMember.nickname) { // Nickname change - handler.NicknameChanged(); + await handler.NicknameChanged(); } return { diff --git a/src/events/MemberEvents/GuildMemberUpdate.ts b/src/events/MemberEvents/GuildMemberUpdate.ts index 984721a..ecc4de5 100644 --- a/src/events/MemberEvents/GuildMemberUpdate.ts +++ b/src/events/MemberEvents/GuildMemberUpdate.ts @@ -11,7 +11,7 @@ export default class GuildMemberUpdate { this.newMember = newMember; } - public NicknameChanged(): IEventReturnContext { + public async NicknameChanged(): Promise { const oldNickname = this.oldMember.nickname || "*none*"; const newNickname = this.newMember.nickname || "*none*"; @@ -21,7 +21,7 @@ export default class GuildMemberUpdate { embed.addField("After", newNickname, true); embed.setFooter(`Id: ${this.newMember.user.id}`); - embed.SendToMemberLogsChannel(); + await embed.SendToMemberLogsChannel(); return { embeds: [embed] diff --git a/src/events/MessageEvents.ts b/src/events/MessageEvents.ts index 18f3704..17797b4 100644 --- a/src/events/MessageEvents.ts +++ b/src/events/MessageEvents.ts @@ -2,13 +2,15 @@ import { Event } from "../type/event"; import { Message } from "discord.js"; import EventEmbed from "../helpers/embeds/EventEmbed"; import IEventReturnContext from "../contracts/IEventReturnContext"; +import SettingsHelper from "../helpers/SettingsHelper"; +import OnMessage from "./MessageEvents/OnMessage"; export default class MessageEvents extends Event { constructor() { super(); } - public override messageDelete(message: Message): IEventReturnContext { + public override async messageDelete(message: Message): Promise { if (!message.guild) { return { embeds: [] @@ -30,14 +32,14 @@ export default class MessageEvents extends Event { embed.addField("Attachments", `\`\`\`${message.attachments.map(x => x.url).join("\n")}\`\`\``); } - embed.SendToMessageLogsChannel(); + await embed.SendToMessageLogsChannel(); return { embeds: [embed] }; } - public override messageUpdate(oldMessage: Message, newMessage: Message): IEventReturnContext { + public override async messageUpdate(oldMessage: Message, newMessage: Message): Promise { if (!newMessage.guild){ return { embeds: [] @@ -62,10 +64,21 @@ export default class MessageEvents extends Event { embed.addField("Before", `\`\`\`${oldMessage.content || "*none*"}\`\`\``); embed.addField("After", `\`\`\`${newMessage.content || "*none*"}\`\`\``); - embed.SendToMessageLogsChannel(); + await embed.SendToMessageLogsChannel(); return { embeds: [embed] }; } + + public override async message(message: Message) { + if (!message.guild) return; + if (message.author.bot) return; + + const isVerificationEnabled = await SettingsHelper.GetSetting("verification.enabled", message.guild.id); + + if (isVerificationEnabled && isVerificationEnabled.toLocaleLowerCase() == "true") { + await OnMessage.VerificationCheck(message); + } + } } \ No newline at end of file diff --git a/src/events/MessageEvents/OnMessage.ts b/src/events/MessageEvents/OnMessage.ts new file mode 100644 index 0000000..18c2a57 --- /dev/null +++ b/src/events/MessageEvents/OnMessage.ts @@ -0,0 +1,59 @@ +import { Message as Message } from "discord.js"; +import SettingsHelper from "../../helpers/SettingsHelper"; + +export default class OnMessage { + public static async VerificationCheck(message: Message) { + if (!message.guild) return; + + const verificationChannel = await SettingsHelper.GetSetting("verification.channel", message.guild.id); + + if (!verificationChannel) { + return; + } + + const channel = message.guild.channels.cache.find(x => x.name == verificationChannel); + + if (!channel) { + return; + } + + const currentChannel = message.guild.channels.cache.find(x => x == message.channel); + + if (!currentChannel || currentChannel.name != verificationChannel) { + return; + } + + const verificationCode = await SettingsHelper.GetSetting("verification.code", message.guild.id); + + if (!verificationCode || verificationCode == "") { + await message.reply("`verification.code` is not set inside of the server's config. Please contact the server's mod team."); + await message.delete(); + + return; + } + + const verificationRoleName = await SettingsHelper.GetSetting("verification.role", message.guild.id); + + if (!verificationRoleName) { + await message.reply("`verification.role` is not set inside of the server's config. Please contact the server's mod team."); + await message.delete(); + return; + } + + const role = message.guild.roles.cache.find(x => x.name == verificationRoleName); + + if (!role) { + await message.reply("The entry role configured for this server does not exist. Please contact the server's mod team."); + await message.delete(); + return; + } + + if (message.content.toLocaleLowerCase() != verificationCode.toLocaleLowerCase()) { + await message.delete(); + return; + } + + await message.member?.roles.add(role); + await message.delete(); + } +} \ No newline at end of file diff --git a/src/helpers/SettingsHelper.ts b/src/helpers/SettingsHelper.ts new file mode 100644 index 0000000..35bc342 --- /dev/null +++ b/src/helpers/SettingsHelper.ts @@ -0,0 +1,50 @@ +import { getConnection } from "typeorm"; +import DefaultValues from "../constants/DefaultValues"; +import Server from "../entity/Server"; +import Setting from "../entity/Setting"; + +export default class SettingsHelper { + public static async GetSetting(key: string, serverId: string): Promise { + const server = await Server.FetchOneById(Server, serverId, [ + "Settings" + ]); + + if (!server) { + return DefaultValues.GetValue(key); + } + + const setting = server.Settings.filter(x => x.Key == key)[0]; + + if (!setting) { + return DefaultValues.GetValue(key); + } + + return setting.Value; + } + + public static async SetSetting(key: string, serverId: string, value: string): Promise { + const server = await Server.FetchOneById(Server, serverId, [ + "Settings" + ]); + + if (!server) { + return; + } + + const setting = server.Settings.filter(x => x.Key == key)[0]; + + if (setting) { + setting.UpdateBasicDetails(key, value); + + await setting.Save(Setting, setting); + } else { + const newSetting = new Setting(key, value); + + await newSetting.Save(Setting, newSetting); + + server.AddSettingToServer(newSetting); + + await server.Save(Server, server); + } + } +} \ No newline at end of file diff --git a/src/helpers/StringTools.ts b/src/helpers/StringTools.ts index b42eb90..dab3571 100644 --- a/src/helpers/StringTools.ts +++ b/src/helpers/StringTools.ts @@ -12,4 +12,17 @@ export default class StringTools { return result.join(" "); } + + public static RandomString(length: number) { + let result = ""; + + const characters = 'abcdefghkmnpqrstuvwxyz23456789'; + const charactersLength = characters.length; + + for ( var i = 0; i < length; i++ ) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + + return result; + } } \ No newline at end of file diff --git a/src/helpers/embeds/ErrorEmbed.ts b/src/helpers/embeds/ErrorEmbed.ts index 6b2c06d..ab8460a 100644 --- a/src/helpers/embeds/ErrorEmbed.ts +++ b/src/helpers/embeds/ErrorEmbed.ts @@ -7,7 +7,7 @@ export default class ErrorEmbed extends MessageEmbed { constructor(context: ICommandContext, message: String) { super(); - super.setColor(process.env.EMBED_COLOUR_ERROR!); + super.setColor(0xd52803); super.setDescription(message); this.context = context; diff --git a/src/helpers/embeds/EventEmbed.ts b/src/helpers/embeds/EventEmbed.ts index 885961b..7cc5522 100644 --- a/src/helpers/embeds/EventEmbed.ts +++ b/src/helpers/embeds/EventEmbed.ts @@ -1,4 +1,6 @@ import { MessageEmbed, TextChannel, User, Guild } from "discord.js"; +import { ICommandContext } from "../../contracts/ICommandContext"; +import SettingsHelper from "../SettingsHelper"; export default class EventEmbed extends MessageEmbed { public guild: Guild; @@ -6,7 +8,7 @@ export default class EventEmbed extends MessageEmbed { constructor(guild: Guild, title: string) { super(); - super.setColor(process.env.EMBED_COLOUR!); + super.setColor(0x3050ba); super.setTitle(title); this.guild = guild; @@ -38,15 +40,33 @@ export default class EventEmbed extends MessageEmbed { channel.send(this); } - public SendToMessageLogsChannel() { - this.SendToChannel(process.env.CHANNELS_LOGS_MESSAGE!) + public async SendToMessageLogsChannel() { + const channelName = await SettingsHelper.GetSetting("channels.logs.message", this.guild.id); + + if (!channelName) { + return; + } + + this.SendToChannel(channelName); } - public SendToMemberLogsChannel() { - this.SendToChannel(process.env.CHANNELS_LOGS_MEMBER!) + public async SendToMemberLogsChannel() { + const channelName = await SettingsHelper.GetSetting("channels.logs.member", this.guild.id); + + if (!channelName) { + return; + } + + this.SendToChannel(channelName); } - public SendToModLogsChannel() { - this.SendToChannel(process.env.CHANNELS_LOGS_MOD!) + public async SendToModLogsChannel() { + const channelName = await SettingsHelper.GetSetting("channels.logs.mod", this.guild.id); + + if (!channelName) { + return; + } + + this.SendToChannel(channelName); } } \ No newline at end of file diff --git a/src/helpers/embeds/LogEmbed.ts b/src/helpers/embeds/LogEmbed.ts index f5e2e36..4250780 100644 --- a/src/helpers/embeds/LogEmbed.ts +++ b/src/helpers/embeds/LogEmbed.ts @@ -1,6 +1,7 @@ import { MessageEmbed, TextChannel, User } from "discord.js"; import ErrorMessages from "../../constants/ErrorMessages"; import { ICommandContext } from "../../contracts/ICommandContext"; +import SettingsHelper from "../SettingsHelper"; import ErrorEmbed from "./ErrorEmbed"; export default class LogEmbed extends MessageEmbed { @@ -9,7 +10,7 @@ export default class LogEmbed extends MessageEmbed { constructor(context: ICommandContext, title: string) { super(); - super.setColor(process.env.EMBED_COLOUR!); + super.setColor(0x3050ba); super.setTitle(title); this.context = context; @@ -46,15 +47,33 @@ export default class LogEmbed extends MessageEmbed { channel.send(this); } - public SendToMessageLogsChannel() { - this.SendToChannel(process.env.CHANNELS_LOGS_MESSAGE!) + public async SendToMessageLogsChannel() { + const channelName = await SettingsHelper.GetSetting("channels.logs.message", this.context.message.guild?.id!); + + if (!channelName) { + return; + } + + this.SendToChannel(channelName); } - public SendToMemberLogsChannel() { - this.SendToChannel(process.env.CHANNELS_LOGS_MEMBER!) + public async SendToMemberLogsChannel() { + const channelName = await SettingsHelper.GetSetting("channels.logs.member", this.context.message.guild?.id!); + + if (!channelName) { + return; + } + + this.SendToChannel(channelName); } - public SendToModLogsChannel() { - this.SendToChannel(process.env.CHANNELS_LOGS_MOD!) + public async SendToModLogsChannel() { + const channelName = await SettingsHelper.GetSetting("channels.logs.mod", this.context.message.guild?.id!); + + if (!channelName) { + return; + } + + this.SendToChannel(channelName); } } \ No newline at end of file diff --git a/src/helpers/embeds/PublicEmbed.ts b/src/helpers/embeds/PublicEmbed.ts index 2bd40cd..8b3e832 100644 --- a/src/helpers/embeds/PublicEmbed.ts +++ b/src/helpers/embeds/PublicEmbed.ts @@ -7,7 +7,7 @@ export default class PublicEmbed extends MessageEmbed { constructor(context: ICommandContext, title: string, description: string) { super(); - super.setColor(process.env.EMBED_COLOUR!); + super.setColor(0x3050ba); super.setTitle(title); super.setDescription(description); diff --git a/src/Register.ts b/src/registry.ts similarity index 78% rename from src/Register.ts rename to src/registry.ts index d8390bf..0792dc3 100644 --- a/src/Register.ts +++ b/src/registry.ts @@ -2,6 +2,9 @@ import { CoreClient } from "./client/client"; import About from "./commands/about"; import Ban from "./commands/ban"; import Clear from "./commands/clear"; +import Code from "./commands/code"; +import Config from "./commands/config"; +import Disable from "./commands/disable"; import Evaluate from "./commands/eval"; import Help from "./commands/help"; import Kick from "./commands/kick"; @@ -9,12 +12,13 @@ import Mute from "./commands/mute"; import Poll from "./commands/poll"; import Role from "./commands/role"; import Rules from "./commands/rules"; +import Setup from "./commands/setup"; import Unmute from "./commands/unmute"; import Warn from "./commands/warn"; import MemberEvents from "./events/MemberEvents"; import MessageEvents from "./events/MessageEvents"; -export default class Register { +export default class Registry { public static RegisterCommands(client: CoreClient) { client.RegisterCommand("about", new About()); client.RegisterCommand("ban", new Ban()); @@ -28,6 +32,10 @@ export default class Register { client.RegisterCommand("rules", new Rules()); client.RegisterCommand("unmute", new Unmute()); client.RegisterCommand("warn", new Warn()); + client.RegisterCommand("setup", new Setup()); + client.RegisterCommand("config", new Config()); + client.RegisterCommand("code", new Code()); + client.RegisterCommand("disable", new Disable()) } public static RegisterEvents(client: CoreClient) { diff --git a/src/type/command.ts b/src/type/command.ts index 5a3ae45..a8cfdd7 100644 --- a/src/type/command.ts +++ b/src/type/command.ts @@ -1,3 +1,4 @@ +import { CommandResponse } from "../constants/CommandResponse"; import { ICommandContext } from "../contracts/ICommandContext"; export class Command { @@ -9,6 +10,14 @@ export class Command { this._roles = []; } + public precheck(context: ICommandContext): CommandResponse { + return CommandResponse.Ok; + } + + public async precheckAsync(context: ICommandContext): Promise { + return CommandResponse.Ok; + } + public execute(context: ICommandContext) { } diff --git a/src/vylbot.ts b/src/vylbot.ts index 56cbdc4..862171b 100644 --- a/src/vylbot.ts +++ b/src/vylbot.ts @@ -1,19 +1,15 @@ import { CoreClient } from "./client/client"; import * as dotenv from "dotenv"; -import Register from "./Register"; +import registry from "./registry"; dotenv.config(); -const requiredConfigs = [ - "EMBED_COLOUR", - "EMBED_COLOUR_ERROR", - "ROLES_MODERATOR", - "ROLES_MUTED", - "CHANNELS_LOGS_MESSAGE", - "CHANNELS_LOGS_MEMBER", - "CHANNELS_LOGS_MOD", - "COMMANDS_ROLE_ROLES", - "COMMANDS_RULES_FILE" +const requiredConfigs: string[] = [ + "BOT_TOKEN", + "BOT_VER", + "BOT_AUTHOR", + "BOT_DATE", + "BOT_OWNERID", ]; requiredConfigs.forEach(config => { @@ -22,9 +18,11 @@ requiredConfigs.forEach(config => { } }); -const client = new CoreClient(); +const devmode = process.argv.find(x => x.toLowerCase() == "--dev") != null; -Register.RegisterCommands(client); -Register.RegisterEvents(client); +const client = new CoreClient(devmode); + +registry.RegisterCommands(client); +registry.RegisterEvents(client); client.start(); \ No newline at end of file diff --git a/tests/client/client.test.ts b/tests/client/client.test.ts index 46879ba..7f0086f 100644 --- a/tests/client/client.test.ts +++ b/tests/client/client.test.ts @@ -1,3 +1,44 @@ +import { mock } from "jest-mock-extended"; + +const connectionMock = mock(); +const qbuilderMock = mock>(); + +let repositoryMock = mock>(); +let settingMock = mock(); + +jest.mock('typeorm', () => { + qbuilderMock.where.mockReturnThis(); + qbuilderMock.select.mockReturnThis(); + repositoryMock.createQueryBuilder.mockReturnValue(qbuilderMock); + repositoryMock.findOne.mockImplementation(async () => { + return settingMock; + }); + connectionMock.getRepository.mockReturnValue(repositoryMock); + + return { + getConnection: () => connectionMock, + createConnection: () => connectionMock, + + BaseEntity: class Mock {}, + ObjectType: () => {}, + Entity: () => {}, + InputType: () => {}, + Index: () => {}, + PrimaryColumn: () => {}, + Column: () => {}, + CreateDateColumn: () => {}, + UpdateDateColumn: () => {}, + OneToMany: () => {}, + ManyToOne: () => {}, + } +}); + +jest.mock("discord.js"); +jest.mock("dotenv"); +jest.mock("../../src/client/events"); +jest.mock("../../src/client/util"); +jest.mock("../../src/constants/DefaultValues"); + import { CoreClient } from "../../src/client/client"; import { Client } from "discord.js"; @@ -5,139 +46,84 @@ import * as dotenv from "dotenv"; import { Events } from "../../src/client/events"; import { Util } from "../../src/client/util"; import { Command } from "../../src/type/command"; -import { mock } from "jest-mock-extended"; import { Event } from "../../src/type/event"; +import DefaultValues from "../../src/constants/DefaultValues"; +import { Connection, Repository, SelectQueryBuilder } from "typeorm"; +import Setting from "../../src/entity/Setting"; -jest.mock("discord.js"); -jest.mock("dotenv"); -jest.mock("../../src/client/events"); -jest.mock("../../src/client/util"); +beforeEach(() => { + jest.resetAllMocks(); + jest.resetModules(); +}) describe('Constructor', () => { - test('Constructor_ExpectSuccessfulInitialisation', () => { + test('Expect Successful Initialisation', () => { const coreClient = new CoreClient(); expect(coreClient).toBeInstanceOf(Client); expect(dotenv.config).toBeCalledTimes(1); expect(Events).toBeCalledTimes(1); expect(Util).toBeCalledTimes(1); + expect(DefaultValues.useDevPrefix).toBe(false); + }); + + test('Given devmode parameter is true, Expect devmode prefix to be true', () => { + const coreClient = new CoreClient(true); + + expect(coreClient).toBeInstanceOf(Client); + expect(dotenv.config).toBeCalledTimes(1); + expect(Events).toBeCalledTimes(1); + expect(Util).toBeCalledTimes(1); + expect(DefaultValues.useDevPrefix).toBe(true); }); }); describe('Start', () => { - test('Given Env Is Valid, Expect Successful Start', () => { + test('Given Env Is Valid, Expect Successful Start', async () => { process.env = { - BOT_TOKEN: 'TOKEN', - BOT_PREFIX: '!', - FOLDERS_COMMANDS: 'commands', - FOLDERS_EVENTS: 'events', - } + BOT_TOKEN: "TOKEN", + }; const coreClient = new CoreClient(); + + await coreClient.start(); - expect(() => coreClient.start()).not.toThrow(); expect(coreClient.on).toBeCalledWith("message", expect.any(Function)); expect(coreClient.on).toBeCalledWith("ready", expect.any(Function)); }); - test('Given BOT_TOKEN Is Null, Expect Failure', () => { - process.env = { - BOT_PREFIX: '!', - FOLDERS_COMMANDS: 'commands', - FOLDERS_EVENTS: 'events', - } + test('Given BOT_TOKEN Is Null, Expect Failure', async () => { + process.env = {}; + + const consoleError = jest.fn(); + + console.error = consoleError; const coreClient = new CoreClient(); - - expect(() => coreClient.start()).toThrow("BOT_TOKEN is not defined in .env"); + + await coreClient.start(); + + expect(consoleError).toBeCalledWith("BOT_TOKEN is not defined in .env"); + expect(coreClient.on).not.toBeCalled(); + expect(coreClient.login).not.toBeCalled(); }); - test('Given BOT_TOKEN Is Empty, Expect Failure', () => { + test('Given BOT_TOKEN Is Empty, Expect Failure', async () => { process.env = { BOT_TOKEN: '', - BOT_PREFIX: '!', - FOLDERS_COMMANDS: 'commands', - FOLDERS_EVENTS: 'events', } + + const consoleError = jest.fn(); + + console.error = consoleError; const coreClient = new CoreClient(); - - expect(() => coreClient.start()).toThrow("BOT_TOKEN is not defined in .env"); - }); - - test('Given BOT_PREFIX Is Null, Expect Failure', () => { - process.env = { - BOT_TOKEN: 'TOKEN', - FOLDERS_COMMANDS: 'commands', - FOLDERS_EVENTS: 'events', - } - - const coreClient = new CoreClient(); - - expect(() => coreClient.start()).toThrow("BOT_PREFIX is not defined in .env"); - }); - - test('Given BOT_PREFIX Is Empty, Expect Failure', () => { - process.env = { - BOT_TOKEN: 'TOKEN', - BOT_PREFIX: '', - FOLDERS_COMMANDS: 'commands', - FOLDERS_EVENTS: 'events', - } - - const coreClient = new CoreClient(); - - expect(() => coreClient.start()).toThrow("BOT_PREFIX is not defined in .env"); - }); - - test('Given FOLDERS_COMMANDS Is Null, Expect Failure', () => { - process.env = { - BOT_TOKEN: 'TOKEN', - BOT_PREFIX: '!', - FOLDERS_EVENTS: 'events', - } - - const coreClient = new CoreClient(); - - expect(() => coreClient.start()).toThrow("FOLDERS_COMMANDS is not defined in .env"); - }); - - test('Given FOLDERS_COMMANDS Is Empty, Expect Failure', () => { - process.env = { - BOT_TOKEN: 'TOKEN', - BOT_PREFIX: '!', - FOLDERS_COMMANDS: '', - FOLDERS_EVENTS: 'events', - } - - const coreClient = new CoreClient(); - - expect(() => coreClient.start()).toThrow("FOLDERS_COMMANDS is not defined in .env"); - }); - - test('Given FOLDERS_EVENTS Is Null, Expect Failure', () => { - process.env = { - BOT_TOKEN: 'TOKEN', - BOT_PREFIX: '!', - FOLDERS_COMMANDS: 'commands', - } - - const coreClient = new CoreClient(); - - expect(() => coreClient.start()).toThrow("FOLDERS_EVENTS is not defined in .env"); - }); - - test('Given FOLDERS_EVENTS Is Empty, Expect Failure', () => { - process.env = { - BOT_TOKEN: 'TOKEN', - BOT_PREFIX: '!', - FOLDERS_COMMANDS: 'commands', - FOLDERS_EVENTS: '', - } - - const coreClient = new CoreClient(); - - expect(() => coreClient.start()).toThrow("FOLDERS_EVENTS is not defined in .env"); + + await coreClient.start(); + + expect(consoleError).toBeCalledWith("BOT_TOKEN is not defined in .env"); + expect(coreClient.on).not.toBeCalled(); + expect(coreClient.login).not.toBeCalled(); }); }); diff --git a/tests/client/util.test.ts b/tests/client/util.test.ts index 6429e24..fe59118 100644 --- a/tests/client/util.test.ts +++ b/tests/client/util.test.ts @@ -15,7 +15,7 @@ beforeEach(() => { }); describe('LoadCommand', () => { - test('Given Successful Exection, Expect Successful Result', () => { + test('Given Successful Exection, Expect Successful Result', async () => { process.env = { BOT_TOKEN: 'TOKEN', BOT_PREFIX: '!', @@ -45,13 +45,13 @@ describe('LoadCommand', () => { const util = new Util(); - const result = util.loadCommand("test", [ "first" ], message, commands); + const result = await util.loadCommand("test", [ "first" ], message, commands); expect(result.valid).toBeTruthy(); expect(cmd.execute).toBeCalled(); }); - test('Given Member Is Null, Expect Failed Result', () => { + test('Given Member Is Null, Expect Failed Result', async () => { process.env = { BOT_TOKEN: 'TOKEN', BOT_PREFIX: '!', @@ -74,14 +74,14 @@ describe('LoadCommand', () => { const util = new Util(); - const result = util.loadCommand("test", [ "first" ], message, commands); + const result = await util.loadCommand("test", [ "first" ], message, commands); expect(result.valid).toBeFalsy(); expect(result.message).toBe("Member is not part of message"); expect(cmd.execute).not.toBeCalled(); }); - test('Given User Does Have Role, Expect Successful Result', () => { + test('Given User Does Have Role, Expect Successful Result', async () => { process.env = { BOT_TOKEN: 'TOKEN', BOT_PREFIX: '!', @@ -111,13 +111,13 @@ describe('LoadCommand', () => { const util = new Util(); - const result = util.loadCommand("test", [ "first" ], message, commands); + const result = await util.loadCommand("test", [ "first" ], message, commands); expect(result.valid).toBeTruthy(); expect(cmd.execute).toBeCalled(); }); - test('Given User Does Not Have Role, Expect Failed Result', () => { + test('Given User Does Not Have Role, Expect Failed Result', async () => { process.env = { BOT_TOKEN: 'TOKEN', BOT_PREFIX: '!', @@ -148,14 +148,14 @@ describe('LoadCommand', () => { const util = new Util(); - const result = util.loadCommand("test", [ "first" ], message, commands); + const result = await util.loadCommand("test", [ "first" ], message, commands); expect(result.valid).toBeFalsy(); expect(result.message).toBe("You require the `Moderator` role to run this command"); expect(cmd.execute).not.toBeCalled(); }); - test('Given command is set to disabled, Expect command to not fire', () => { + test('Given command is set to disabled, Expect command to not fire', async () => { process.env = { BOT_TOKEN: 'TOKEN', BOT_PREFIX: '!', @@ -189,7 +189,7 @@ describe('LoadCommand', () => { const util = new Util(); - const result = util.loadCommand("test", [ "first" ], message, commands); + const result = await util.loadCommand("test", [ "first" ], message, commands); expect(result.valid).toBeFalsy(); expect(result.message).toBe("Command is disabled"); @@ -197,7 +197,7 @@ describe('LoadCommand', () => { expect(cmd.execute).not.toBeCalled(); }); - test('Given command COMMANDS_DISABLED_MESSAGE is empty, Expect default message sent', () => { + test('Given command COMMANDS_DISABLED_MESSAGE is empty, Expect default message sent', async () => { process.env = { BOT_TOKEN: 'TOKEN', BOT_PREFIX: '!', @@ -230,7 +230,7 @@ describe('LoadCommand', () => { const util = new Util(); - const result = util.loadCommand("test", [ "first" ], message, commands); + const result = await util.loadCommand("test", [ "first" ], message, commands); expect(result.valid).toBeFalsy(); expect(result.message).toBe("Command is disabled"); @@ -238,7 +238,7 @@ describe('LoadCommand', () => { expect(cmd.execute).not.toBeCalled(); }); - test('Given a different command is disabled, Expect command to still fire', () => { + test('Given a different command is disabled, Expect command to still fire', async () => { process.env = { BOT_TOKEN: 'TOKEN', BOT_PREFIX: '!', @@ -275,14 +275,14 @@ describe('LoadCommand', () => { const util = new Util(); - const result = util.loadCommand("test", [ "first" ], message, commands); + const result = await util.loadCommand("test", [ "first" ], message, commands); expect(result.valid).toBeTruthy(); expect(cmd.execute).toBeCalled(); expect(otherCmd.execute).not.toBeCalled(); }); - test('Given command is not found in register, expect command not found error', () => { + test('Given command is not found in register, expect command not found error', async () => { process.env = { BOT_TOKEN: 'TOKEN', BOT_PREFIX: '!', @@ -305,7 +305,7 @@ describe('LoadCommand', () => { const util = new Util(); - const result = util.loadCommand("test", [ "first" ], message, commands); + const result = await util.loadCommand("test", [ "first" ], message, commands); expect(result.valid).toBeFalsy(); expect(result.message).toBe('Command not found'); diff --git a/tests/events/MemberEvents.test.ts b/tests/events/MemberEvents.test.ts index ad16485..2d4581a 100644 --- a/tests/events/MemberEvents.test.ts +++ b/tests/events/MemberEvents.test.ts @@ -3,7 +3,7 @@ import MemberEvents from "../../src/events/MemberEvents"; import GuildMemberUpdate from "../../src/events/MemberEvents/GuildMemberUpdate"; describe('GuildMemberAdd', () => { - test('When event is fired, expect embed to be sent to logs channel', () => { + test('When event is fired, expect embed to be sent to logs channel', async () => { const currentDate = new Date(); const textChannel = { @@ -34,7 +34,7 @@ describe('GuildMemberAdd', () => { const memberEvents = new MemberEvents(); - const result = memberEvents.guildMemberAdd(guildMember); + const result = await memberEvents.guildMemberAdd(guildMember); expect(textChannel.send).toBeCalledTimes(1); expect(userDisplayAvatarURL).toBeCalledTimes(1); @@ -63,7 +63,7 @@ describe('GuildMemberAdd', () => { }); describe('GuildMemberRemove', () => { - test('When event is fired, expect embed to be sent to logs channel', () => { + test('When event is fired, expect embed to be sent to logs channel', async () => { const currentDate = new Date(); const textChannel = { @@ -95,7 +95,7 @@ describe('GuildMemberRemove', () => { const memberEvents = new MemberEvents(); - const result = memberEvents.guildMemberRemove(guildMember); + const result = await memberEvents.guildMemberRemove(guildMember); expect(textChannel.send).toBeCalledTimes(1); expect(userDisplayAvatarURL).toBeCalledTimes(1); @@ -124,7 +124,7 @@ describe('GuildMemberRemove', () => { }); describe('GuildMemberUpdate', () => { - test('Given nicknames are the same, expect NicknameChanged NOT to be called', () => { + test('Given nicknames are the same, expect NicknameChanged NOT to be called', async () => { const member = { nickname: 'member' } as unknown as GuildMember; @@ -135,13 +135,13 @@ describe('GuildMemberUpdate', () => { const memberEvents = new MemberEvents(); - const result = memberEvents.guildMemberUpdate(member, member); + const result = await memberEvents.guildMemberUpdate(member, member); expect(result.embeds.length).toBe(0); expect(nicknameChanged).not.toBeCalled(); }); - test('Given nicknames are the different, expect NicknameChanged to be called', () => { + test('Given nicknames are the different, expect NicknameChanged to be called', async () => { const oldMember = { nickname: 'oldMember' } as unknown as GuildMember; @@ -156,7 +156,7 @@ describe('GuildMemberUpdate', () => { const memberEvents = new MemberEvents(); - const result = memberEvents.guildMemberUpdate(oldMember, newMember); + const result = await memberEvents.guildMemberUpdate(oldMember, newMember); expect(result.embeds.length).toBe(0); expect(nicknameChanged).toBeCalledTimes(1); diff --git a/tests/events/MemberEvents/GuildMemberUpdate.test.ts b/tests/events/MemberEvents/GuildMemberUpdate.test.ts index 1697cf9..8acec92 100644 --- a/tests/events/MemberEvents/GuildMemberUpdate.test.ts +++ b/tests/events/MemberEvents/GuildMemberUpdate.test.ts @@ -23,7 +23,7 @@ describe('Constructor', () => { }); describe('NicknameChanged', () => { - test('Given nickname has changed from one to another, expect embed to be sent with both', () => { + test('Given nickname has changed from one to another, expect embed to be sent with both', async () => { process.env = { CHANNELS_LOGS_MOD: 'mod-logs' } @@ -61,7 +61,7 @@ describe('NicknameChanged', () => { const guildMemberUpdate = new GuildMemberUpdate(oldMember, newMember); - const result = guildMemberUpdate.NicknameChanged(); + const result = await guildMemberUpdate.NicknameChanged(); expect(channelSend).toBeCalledTimes(1); expect(memberGuildChannelsCacheFind).toBeCalledTimes(1); @@ -94,7 +94,7 @@ describe('NicknameChanged', () => { expect(embedFieldAfter.value).toBe('New Nickname'); }); - test('Given old nickname was null, expect embed to say old nickname was none', () => { + test('Given old nickname was null, expect embed to say old nickname was none', async () => { process.env = { CHANNELS_LOGS_MOD: 'mod-logs' } @@ -130,7 +130,7 @@ describe('NicknameChanged', () => { const guildMemberUpdate = new GuildMemberUpdate(oldMember, newMember); - const result = guildMemberUpdate.NicknameChanged(); + const result = await guildMemberUpdate.NicknameChanged(); expect(channelSend).toBeCalledTimes(1); expect(memberGuildChannelsCacheFind).toBeCalledTimes(1); @@ -163,7 +163,7 @@ describe('NicknameChanged', () => { expect(embedFieldAfter.value).toBe('New Nickname'); }); - test('Given new nickname was null, expect embed to say new nickname was none', () => { + test('Given new nickname was null, expect embed to say new nickname was none', async () => { process.env = { CHANNELS_LOGS_MOD: 'mod-logs' } @@ -200,7 +200,7 @@ describe('NicknameChanged', () => { const guildMemberUpdate = new GuildMemberUpdate(oldMember, newMember); - const result = guildMemberUpdate.NicknameChanged(); + const result = await guildMemberUpdate.NicknameChanged(); expect(channelSend).toBeCalledTimes(1); expect(memberGuildChannelsCacheFind).toBeCalledTimes(1); diff --git a/tests/events/MessageEvents.test.ts b/tests/events/MessageEvents.test.ts index abecc57..d509bdc 100644 --- a/tests/events/MessageEvents.test.ts +++ b/tests/events/MessageEvents.test.ts @@ -6,7 +6,7 @@ beforeEach(() => { }); describe('MessageDelete', () => { - test('Given message was in a guild AND user was NOT a bot, expect message deleted embed to be sent', () => { + test('Given message was in a guild AND user was NOT a bot, expect message deleted embed to be sent', async () => { process.env = { CHANNELS_LOGS_MOD: 'mod-logs' } @@ -57,7 +57,7 @@ describe('MessageDelete', () => { const messageEvents = new MessageEvents(); - const result = messageEvents.messageDelete(message); + const result = await messageEvents.messageDelete(message); expect(channelSend).toBeCalledTimes(1); expect(memberGuildChannelsCacheFind).toBeCalledTimes(1); @@ -95,7 +95,7 @@ describe('MessageDelete', () => { expect(embedFieldAttachments.value).toBe('```image0.png\nimage1.png```'); }); - test('Given message was not sent in a guild, expect execution stopped', () => { + test('Given message was not sent in a guild, expect execution stopped', async () => { process.env = { CHANNELS_LOGS_MOD: 'mod-logs' } @@ -139,7 +139,7 @@ describe('MessageDelete', () => { const messageEvents = new MessageEvents(); - const result = messageEvents.messageDelete(message); + const result = await messageEvents.messageDelete(message); expect(channelSend).not.toBeCalled(); expect(memberGuildChannelsCacheFind).not.toBeCalled(); @@ -147,7 +147,7 @@ describe('MessageDelete', () => { expect(result.embeds.length).toBe(0); }); - test('Given author is a bot, expect execution stopped', () => { + test('Given author is a bot, expect execution stopped', async () => { process.env = { CHANNELS_LOGS_MOD: 'mod-logs' } @@ -198,7 +198,7 @@ describe('MessageDelete', () => { const messageEvents = new MessageEvents(); - const result = messageEvents.messageDelete(message); + const result = await messageEvents.messageDelete(message); expect(channelSend).not.toBeCalled(); expect(memberGuildChannelsCacheFind).not.toBeCalled(); @@ -206,7 +206,7 @@ describe('MessageDelete', () => { expect(result.embeds.length).toBe(0); }); - test('Given message does not contain any attachments, expect attachments field to be omitted', () => { + test('Given message does not contain any attachments, expect attachments field to be omitted', async () => { process.env = { CHANNELS_LOGS_MOD: 'mod-logs' } @@ -244,7 +244,7 @@ describe('MessageDelete', () => { const messageEvents = new MessageEvents(); - const result = messageEvents.messageDelete(message); + const result = await messageEvents.messageDelete(message); expect(channelSend).toBeCalledTimes(1); expect(memberGuildChannelsCacheFind).toBeCalledTimes(1); @@ -278,7 +278,7 @@ describe('MessageDelete', () => { }); describe('MessageUpdate', () => { - test('Given message is in a guild AND user is not a bot AND the content has actually changed, e xpect log embed to be sent', () => { + test('Given message is in a guild AND user is not a bot AND the content has actually changed, e xpect log embed to be sent', async () => { process.env = { CHANNELS_LOGS_MOD: 'mod-logs' } @@ -317,7 +317,7 @@ describe('MessageUpdate', () => { const messageEvents = new MessageEvents(); - const result = messageEvents.messageUpdate(oldMessage, newMessage); + const result = await messageEvents.messageUpdate(oldMessage, newMessage); expect(channelSend).toBeCalledTimes(1); expect(memberGuildChannelsCacheFind).toBeCalledTimes(1); @@ -357,7 +357,7 @@ describe('MessageUpdate', () => { expect(embedFieldAfter.value).toBe('```New Message```'); }); - test('Given message was not in a guild, expect execution stopped', () => { + test('Given message was not in a guild, expect execution stopped', async () => { process.env = { CHANNELS_LOGS_MOD: 'mod-logs' } @@ -389,7 +389,7 @@ describe('MessageUpdate', () => { const messageEvents = new MessageEvents(); - const result = messageEvents.messageUpdate(oldMessage, newMessage); + const result = await messageEvents.messageUpdate(oldMessage, newMessage); expect(channelSend).not.toBeCalled(); expect(memberGuildChannelsCacheFind).not.toBeCalled(); @@ -397,7 +397,7 @@ describe('MessageUpdate', () => { expect(result.embeds.length).toBe(0); }); - test('Given author is a bot, expect execution stopped', () => { + test('Given author is a bot, expect execution stopped', async () => { process.env = { CHANNELS_LOGS_MOD: 'mod-logs' } @@ -436,7 +436,7 @@ describe('MessageUpdate', () => { const messageEvents = new MessageEvents(); - const result = messageEvents.messageUpdate(oldMessage, newMessage); + const result = await messageEvents.messageUpdate(oldMessage, newMessage); expect(channelSend).not.toBeCalled(); expect(memberGuildChannelsCacheFind).not.toBeCalled(); @@ -444,7 +444,7 @@ describe('MessageUpdate', () => { expect(result.embeds.length).toBe(0); }); - test('Given the message contents are the same, expect execution stopped', () => { + test('Given the message contents are the same, expect execution stopped', async () => { process.env = { CHANNELS_LOGS_MOD: 'mod-logs' } @@ -483,7 +483,7 @@ describe('MessageUpdate', () => { const messageEvents = new MessageEvents(); - const result = messageEvents.messageUpdate(oldMessage, newMessage); + const result = await messageEvents.messageUpdate(oldMessage, newMessage); expect(channelSend).not.toBeCalled(); expect(memberGuildChannelsCacheFind).not.toBeCalled(); @@ -491,7 +491,7 @@ describe('MessageUpdate', () => { expect(result.embeds.length).toBe(0); }); - test('Given Old Message did not have a content, expect field to account for this', () => { + test('Given Old Message did not have a content, expect field to account for this', async () => { process.env = { CHANNELS_LOGS_MOD: 'mod-logs' } @@ -528,7 +528,7 @@ describe('MessageUpdate', () => { const messageEvents = new MessageEvents(); - const result = messageEvents.messageUpdate(oldMessage, newMessage); + const result = await messageEvents.messageUpdate(oldMessage, newMessage); expect(channelSend).toBeCalledTimes(1); expect(memberGuildChannelsCacheFind).toBeCalledTimes(1); @@ -568,7 +568,7 @@ describe('MessageUpdate', () => { expect(embedFieldAfter.value).toBe('```New Message```'); }); - test('Given New Message does not have a content, expect field to account for this', () => { + test('Given New Message does not have a content, expect field to account for this', async () => { process.env = { CHANNELS_LOGS_MOD: 'mod-logs' } @@ -606,7 +606,7 @@ describe('MessageUpdate', () => { const messageEvents = new MessageEvents(); - const result = messageEvents.messageUpdate(oldMessage, newMessage); + const result = await messageEvents.messageUpdate(oldMessage, newMessage); expect(channelSend).toBeCalledTimes(1); expect(memberGuildChannelsCacheFind).toBeCalledTimes(1); diff --git a/tests/helpers/embeds/EventEmbed.test.ts b/tests/helpers/embeds/EventEmbed.test.ts index f5932e3..7cb9251 100644 --- a/tests/helpers/embeds/EventEmbed.test.ts +++ b/tests/helpers/embeds/EventEmbed.test.ts @@ -1,6 +1,7 @@ import { Guild, Message, TextChannel, User } from "discord.js"; import { ICommandContext } from "../../../src/contracts/ICommandContext"; import EventEmbed from "../../../src/helpers/embeds/EventEmbed"; +import SettingsHelper from "../../../src/helpers/SettingsHelper"; beforeEach(() => { process.env = {}; @@ -9,18 +10,11 @@ beforeEach(() => { describe('Constructor', () => { test('Expect properties to be set', () => { - process.env = { - EMBED_COLOUR: '0xd52803', - CHANNELS_LOGS_MESSAGE: 'message-logs', - CHANNELS_LOGS_MEMBER: 'member-logs', - CHANNELS_LOGS_MOD: 'mod-logs' - } - const guild = {} as unknown as Guild; const errorEmbed = new EventEmbed(guild, 'Event Message'); - expect(errorEmbed.color?.toString()).toBe('13969411'); // 0xd52803 in decimal + expect(errorEmbed.color?.toString()).toBe('3166394'); // 0x3050ba in decimal expect(errorEmbed.title).toBe('Event Message'); expect(errorEmbed.guild).toBe(guild); }); @@ -88,15 +82,34 @@ describe('AddUser', () => { }); }); +describe('AddReason', () => { + test('Given a non-empty string is supplied, expect field with message', () => { + const guild = {} as Guild; + + const eventEmbed = new EventEmbed(guild, "Event Embed"); + + eventEmbed.addField = jest.fn(); + + eventEmbed.AddReason("Test reason"); + + expect(eventEmbed.addField).toBeCalledWith("Reason", "Test reason"); + }); + + test('Given an empty string is supplied, expect field with default message', () => { + const guild = {} as Guild; + + const eventEmbed = new EventEmbed(guild, "Event Embed"); + + eventEmbed.addField = jest.fn(); + + eventEmbed.AddReason(""); + + expect(eventEmbed.addField).toBeCalledWith("Reason", "*none*"); + }); +}); + describe('SendToChannel', () => { test('Given channel can be found, expect embed to be sent to that channel', () => { - process.env = { - EMBED_COLOUR: '0xd52803', - CHANNELS_LOGS_MESSAGE: 'message-logs', - CHANNELS_LOGS_MEMBER: 'member-logs', - CHANNELS_LOGS_MOD: 'mod-logs' - } - const channelSend = jest.fn(); const channel = { @@ -153,70 +166,127 @@ describe('SendToChannel', () => { }); describe('SendToMessageLogsChannel', () => { - describe('Expect SendToChannel caleld with CHANNELS_LOGS_MESSAGE as parameter', () => { - process.env = { - EMBED_COLOUR: '0xd52803', - CHANNELS_LOGS_MESSAGE: 'message-logs', - CHANNELS_LOGS_MEMBER: 'member-logs', - CHANNELS_LOGS_MOD: 'mod-logs' - } - + test('Given setting is set, expect SendToChannel to be called with value', async () => { const sendToChannel = jest.fn(); + const getSetting = jest.fn().mockResolvedValue("message-logs"); - const guild = {} as unknown as Guild; + const guild = { + id: "guildId" + } as unknown as Guild; + + SettingsHelper.GetSetting = getSetting; const errorEmbed = new EventEmbed(guild, 'Event Message'); errorEmbed.SendToChannel = sendToChannel; - errorEmbed.SendToMessageLogsChannel(); + await errorEmbed.SendToMessageLogsChannel(); expect(sendToChannel).toBeCalledWith('message-logs'); + expect(getSetting).toBeCalledWith("channels.logs.message", "guildId"); + }); + + test('Given setting is not set, expect function to return', async () => { + const sendToChannel = jest.fn(); + const getSetting = jest.fn().mockResolvedValue(undefined); + + const guild = { + id: "guildId" + } as unknown as Guild; + + SettingsHelper.GetSetting = getSetting; + + const errorEmbed = new EventEmbed(guild, 'Event Message'); + + errorEmbed.SendToChannel = sendToChannel; + + await errorEmbed.SendToMessageLogsChannel(); + + expect(sendToChannel).not.toBeCalled(); + expect(getSetting).toBeCalledWith("channels.logs.message", "guildId"); }); }); describe('SendToMemberLogsChannel', () => { - describe('Expect SendToChannel caleld with CHANNELS_LOGS_MEMBER as parameter', () => { - process.env = { - EMBED_COLOUR: '0xd52803', - CHANNELS_LOGS_MESSAGE: 'message-logs', - CHANNELS_LOGS_MEMBER: 'member-logs', - CHANNELS_LOGS_MOD: 'mod-logs' - } - + test('Given setting is set, expect SendToChannel to be called with value', async () => { const sendToChannel = jest.fn(); + const getSetting = jest.fn().mockResolvedValue("member-logs"); - const guild = {} as unknown as Guild; + const guild = { + id: "guildId" + } as unknown as Guild; + + SettingsHelper.GetSetting = getSetting; const errorEmbed = new EventEmbed(guild, 'Event Message'); errorEmbed.SendToChannel = sendToChannel; - errorEmbed.SendToMemberLogsChannel(); + await errorEmbed.SendToMemberLogsChannel(); expect(sendToChannel).toBeCalledWith('member-logs'); + expect(getSetting).toBeCalledWith("channels.logs.member", "guildId"); + }); + + test('Given setting is not set, expect function to return', async () => { + const sendToChannel = jest.fn(); + const getSetting = jest.fn().mockResolvedValue(undefined); + + const guild = { + id: "guildId" + } as unknown as Guild; + + SettingsHelper.GetSetting = getSetting; + + const errorEmbed = new EventEmbed(guild, 'Event Message'); + + errorEmbed.SendToChannel = sendToChannel; + + await errorEmbed.SendToMemberLogsChannel(); + + expect(sendToChannel).not.toBeCalled(); + expect(getSetting).toBeCalledWith("channels.logs.member", "guildId"); }); }); describe('SendToModLogsChannel', () => { - describe('Expect SendToChannel caleld with CHANNELS_LOGS_MOD as parameter', () => { - process.env = { - EMBED_COLOUR: '0xd52803', - CHANNELS_LOGS_MESSAGE: 'message-logs', - CHANNELS_LOGS_MEMBER: 'member-logs', - CHANNELS_LOGS_MOD: 'mod-logs' - } - + test('Given setting is set, expect SendToChannel to be called with value', async () => { const sendToChannel = jest.fn(); + const getSetting = jest.fn().mockResolvedValue("mod-logs"); - const guild = {} as unknown as Guild; + const guild = { + id: "guildId" + } as unknown as Guild; + + SettingsHelper.GetSetting = getSetting; const errorEmbed = new EventEmbed(guild, 'Event Message'); errorEmbed.SendToChannel = sendToChannel; - errorEmbed.SendToModLogsChannel(); + await errorEmbed.SendToModLogsChannel(); expect(sendToChannel).toBeCalledWith('mod-logs'); + expect(getSetting).toBeCalledWith("channels.logs.mod", "guildId"); + }); + + test('Given setting is not set, expect function to return', async () => { + const sendToChannel = jest.fn(); + const getSetting = jest.fn().mockResolvedValue(undefined); + + const guild = { + id: "guildId" + } as unknown as Guild; + + SettingsHelper.GetSetting = getSetting; + + const errorEmbed = new EventEmbed(guild, 'Event Message'); + + errorEmbed.SendToChannel = sendToChannel; + + await errorEmbed.SendToModLogsChannel(); + + expect(sendToChannel).not.toBeCalled(); + expect(getSetting).toBeCalledWith("channels.logs.mod", "guildId"); }); }); \ No newline at end of file diff --git a/tests/helpers/embeds/LogEmbed.test.ts b/tests/helpers/embeds/LogEmbed.test.ts index 40abd19..4bad4ef 100644 --- a/tests/helpers/embeds/LogEmbed.test.ts +++ b/tests/helpers/embeds/LogEmbed.test.ts @@ -1,6 +1,7 @@ import { Guild, Message, TextChannel, User } from "discord.js"; import { ICommandContext } from "../../../src/contracts/ICommandContext"; import LogEmbed from "../../../src/helpers/embeds/LogEmbed"; +import SettingsHelper from "../../../src/helpers/SettingsHelper"; beforeEach(() => { process.env = {}; @@ -32,7 +33,7 @@ describe('Constructor', () => { const errorEmbed = new LogEmbed(context, 'Log Message'); - expect(errorEmbed.color?.toString()).toBe('13969411'); // 0xd52803 in decimal + expect(errorEmbed.color?.toString()).toBe('3166394'); // 0x3050ba in decimal expect(errorEmbed.title).toBe('Log Message'); expect(errorEmbed.context).toBe(context); }); @@ -220,112 +221,187 @@ describe('SendToChannel', () => { }); describe('SendToMessageLogsChannel', () => { - describe('Expect SendToChannel caleld with CHANNELS_LOGS_MESSAGE as parameter', () => { - process.env = { - EMBED_COLOUR: '0xd52803', - CHANNELS_LOGS_MESSAGE: 'message-logs', - CHANNELS_LOGS_MEMBER: 'member-logs', - CHANNELS_LOGS_MOD: 'mod-logs' - } - + test('Given setting is set, expect SendToChannel to be called with value', async () => { const sendToChannel = jest.fn(); + const getSetting = jest.fn().mockResolvedValue("message-logs"); - const guild = {} as unknown as Guild; - - const messageChannelSend = jest.fn(); + const guild = { + id: "guildId" + } as unknown as Guild; const message = { - channel: { - send: messageChannelSend - } - } as unknown as Message; + guild: guild + } as Message; const context: ICommandContext = { - name: 'command', + name: 'log', args: [], message: message }; - const errorEmbed = new LogEmbed(context, 'Event Message'); + SettingsHelper.GetSetting = getSetting; + + const logEmbed = new LogEmbed(context, 'Event Message'); - errorEmbed.SendToChannel = sendToChannel; + logEmbed.SendToChannel = sendToChannel; - errorEmbed.SendToMessageLogsChannel(); + await logEmbed.SendToMessageLogsChannel(); expect(sendToChannel).toBeCalledWith('message-logs'); + expect(getSetting).toBeCalledWith("channels.logs.message", "guildId"); + }); + + test('Given setting is not set, expect function to return', async () => { + const sendToChannel = jest.fn(); + const getSetting = jest.fn().mockResolvedValue(undefined); + + const guild = { + id: "guildId" + } as unknown as Guild; + + const message = { + guild: guild + } as Message; + + const context: ICommandContext = { + name: 'log', + args: [], + message: message + }; + + SettingsHelper.GetSetting = getSetting; + + const logEmbed = new LogEmbed(context, 'Event Message'); + + logEmbed.SendToChannel = sendToChannel; + + await logEmbed.SendToMessageLogsChannel(); + + expect(sendToChannel).not.toBeCalled(); + expect(getSetting).toBeCalledWith("channels.logs.message", "guildId"); }); }); describe('SendToMemberLogsChannel', () => { - describe('Expect SendToChannel caleld with CHANNELS_LOGS_MEMBER as parameter', () => { - process.env = { - EMBED_COLOUR: '0xd52803', - CHANNELS_LOGS_MESSAGE: 'message-logs', - CHANNELS_LOGS_MEMBER: 'member-logs', - CHANNELS_LOGS_MOD: 'mod-logs' - } - + test('Given setting is set, expect SendToChannel to be called with value', async () => { const sendToChannel = jest.fn(); + const getSetting = jest.fn().mockResolvedValue("member-logs"); - const guild = {} as unknown as Guild; - - const messageChannelSend = jest.fn(); + const guild = { + id: "guildId" + } as unknown as Guild; const message = { - channel: { - send: messageChannelSend - } - } as unknown as Message; + guild: guild + } as Message; const context: ICommandContext = { - name: 'command', + name: 'log', args: [], message: message }; - const errorEmbed = new LogEmbed(context, 'Event Message'); + SettingsHelper.GetSetting = getSetting; + + const logEmbed = new LogEmbed(context, 'Event Message'); - errorEmbed.SendToChannel = sendToChannel; + logEmbed.SendToChannel = sendToChannel; - errorEmbed.SendToMemberLogsChannel(); + await logEmbed.SendToMemberLogsChannel(); expect(sendToChannel).toBeCalledWith('member-logs'); + expect(getSetting).toBeCalledWith("channels.logs.member", "guildId"); + }); + + test('Given setting is not set, expect function to return', async () => { + const sendToChannel = jest.fn(); + const getSetting = jest.fn().mockResolvedValue(undefined); + + const guild = { + id: "guildId" + } as unknown as Guild; + + const message = { + guild: guild + } as Message; + + const context: ICommandContext = { + name: 'log', + args: [], + message: message + }; + + SettingsHelper.GetSetting = getSetting; + + const logEmbed = new LogEmbed(context, 'Event Message'); + + logEmbed.SendToChannel = sendToChannel; + + await logEmbed.SendToMemberLogsChannel(); + + expect(sendToChannel).not.toBeCalled(); + expect(getSetting).toBeCalledWith("channels.logs.member", "guildId"); }); }); describe('SendToModLogsChannel', () => { - describe('Expect SendToChannel caleld with CHANNELS_LOGS_MOD as parameter', () => { - process.env = { - EMBED_COLOUR: '0xd52803', - CHANNELS_LOGS_MESSAGE: 'message-logs', - CHANNELS_LOGS_MEMBER: 'member-logs', - CHANNELS_LOGS_MOD: 'mod-logs' - } - + test('Given setting is set, expect SendToChannel to be called with value', async () => { const sendToChannel = jest.fn(); + const getSetting = jest.fn().mockResolvedValue("mod-logs"); - const guild = {} as unknown as Guild; - - const messageChannelSend = jest.fn(); + const guild = { + id: "guildId" + } as unknown as Guild; const message = { - channel: { - send: messageChannelSend - } - } as unknown as Message; + guild: guild + } as Message; const context: ICommandContext = { - name: 'command', + name: 'log', args: [], message: message }; - const errorEmbed = new LogEmbed(context, 'Event Message'); + SettingsHelper.GetSetting = getSetting; + + const logEmbed = new LogEmbed(context, 'Event Message'); - errorEmbed.SendToChannel = sendToChannel; + logEmbed.SendToChannel = sendToChannel; - errorEmbed.SendToModLogsChannel(); + await logEmbed.SendToModLogsChannel(); expect(sendToChannel).toBeCalledWith('mod-logs'); + expect(getSetting).toBeCalledWith("channels.logs.mod", "guildId"); + }); + + test('Given setting is not set, expect function to return', async () => { + const sendToChannel = jest.fn(); + const getSetting = jest.fn().mockResolvedValue(undefined); + + const guild = { + id: "guildId" + } as unknown as Guild; + + const message = { + guild: guild + } as Message; + + const context: ICommandContext = { + name: 'log', + args: [], + message: message + }; + + SettingsHelper.GetSetting = getSetting; + + const logEmbed = new LogEmbed(context, 'Event Message'); + + logEmbed.SendToChannel = sendToChannel; + + await logEmbed.SendToModLogsChannel(); + + expect(sendToChannel).not.toBeCalled(); + expect(getSetting).toBeCalledWith("channels.logs.mod", "guildId"); }); }); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 3f030e6..e11fe0b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,7 +30,7 @@ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ - // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + "strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ @@ -62,8 +62,8 @@ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ /* Experimental Options */ - // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ "skipLibCheck": true, /* Skip type checking of declaration files. */ diff --git a/yarn.lock b/yarn.lock index b9359b7..71fe276 100644 --- a/yarn.lock +++ b/yarn.lock @@ -504,6 +504,11 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@sqltools/formatter@^1.2.2": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.3.tgz#1185726610acc37317ddab11c3c7f9066966bd20" + integrity sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg== + "@szmarczak/http-timer@^4.0.5": version "4.0.6" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" @@ -632,6 +637,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/uuid@^8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + "@types/yargs-parser@*": version "20.2.1" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" @@ -644,6 +654,11 @@ dependencies: "@types/yargs-parser" "*" +"@types/zen-observable@0.8.3": + version "0.8.3" + resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.3.tgz#781d360c282436494b32fe7d9f7f8e64b3118aa3" + integrity sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw== + abab@^2.0.3, abab@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -717,6 +732,11 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= + anymatch@^3.0.3: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" @@ -725,6 +745,11 @@ anymatch@^3.0.3: normalize-path "^3.0.0" picomatch "^2.0.4" +app-root-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.0.0.tgz#210b6f43873227e18a4b810a032283311555d5ad" + integrity sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -732,6 +757,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -803,6 +833,16 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bignumber.js@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075" + integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -853,6 +893,14 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + cacheable-lookup@^5.0.3: version "5.0.4" resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" @@ -900,7 +948,7 @@ chalk@^2.0.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0: +chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -923,6 +971,18 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== +cli-highlight@^2.1.11: + version "2.1.11" + resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf" + integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg== + dependencies: + chalk "^4.0.0" + highlight.js "^10.7.1" + mz "^2.4.0" + parse5 "^5.1.1" + parse5-htmlparser2-tree-adapter "^6.0.0" + yargs "^16.0.0" + cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -992,6 +1052,11 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -1027,7 +1092,7 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -debug@4, debug@^4.1.0: +debug@4, debug@^4.1.0, debug@^4.3.1: version "4.3.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== @@ -1114,6 +1179,11 @@ dotenv@^10.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== +dotenv@^8.2.0: + version "8.6.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" + integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== + electron-to-chromium@^1.4.17: version "1.4.25" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.25.tgz#ce95e6678f8c6893ae892c7e95a5000e83f1957f" @@ -1310,7 +1380,7 @@ glob-parent@^6.0.0: dependencies: is-glob "^4.0.3" -glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: +glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== @@ -1366,6 +1436,11 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +highlight.js@^10.7.1: + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + html-encoding-sniffer@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" @@ -1420,6 +1495,11 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + import-local@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.3.tgz#4d51c2c495ca9393da259ec66b62e022920211e0" @@ -1441,7 +1521,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@^2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -1495,6 +1575,11 @@ is-typedarray@^1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -1983,6 +2068,13 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + jsdom@^16.6.0: version "16.7.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" @@ -2158,11 +2250,35 @@ minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +mysql@^2.18.1: + version "2.18.1" + resolved "https://registry.yarnpkg.com/mysql/-/mysql-2.18.1.tgz#2254143855c5a8c73825e4522baf2ea021766717" + integrity sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig== + dependencies: + bignumber.js "9.0.0" + readable-stream "2.3.7" + safe-buffer "5.1.2" + sqlstring "2.3.1" + +mz@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -2207,6 +2323,11 @@ nwsapi@^2.2.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -2257,11 +2378,23 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -parse5@6.0.1: +parse5-htmlparser2-tree-adapter@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + +parse5@6.0.1, parse5@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== +parse5@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" + integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -2324,6 +2457,11 @@ prism-media@^1.2.9: resolved "https://registry.yarnpkg.com/prism-media/-/prism-media-1.3.2.tgz#a1f04423ec15d22f3d62b1987b6a25dc49aad13b" integrity sha512-L6UsGHcT6i4wrQhFF1aPK+MNYgjRqR2tUoIqEY+CG1NqVkMjPRKzS37j9f8GiYPlD6wG9ruBj+q5Ax+bH8Ik1g== +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -2368,6 +2506,24 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +readable-stream@2.3.7: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + 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" + +reflect-metadata@^0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" + integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -2417,16 +2573,26 @@ rimraf@^3.0.0: dependencies: glob "^7.1.3" -safe-buffer@~5.1.1: +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-buffer@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + "safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sax@>=0.6.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + saxes@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" @@ -2451,6 +2617,14 @@ setimmediate@^1.0.5: resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= +sha.js@^2.4.11: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -2506,6 +2680,11 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +sqlstring@2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.1.tgz#475393ff9e91479aea62dcaf0ca3d14983a7fb40" + integrity sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A= + stack-utils@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" @@ -2521,7 +2700,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -string-width@^4.1.0, string-width@^4.2.0: +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -2530,6 +2709,13 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -2598,6 +2784,20 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY= + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + throat@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" @@ -2660,6 +2860,11 @@ ts-jest@^27.1.2: semver "7.x" yargs-parser "20.x" +tslib@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + tweetnacl@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" @@ -2689,6 +2894,29 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typeorm@^0.2.44: + version "0.2.44" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.44.tgz#4cc07eb1eb7a0e7f3ec9e65ded9eb3c3aedbb3e1" + integrity sha512-yFyb9Ts73vGaS/O06TvLpzvT5U/ngO31GeciNc0eoH7P1QcG8kVZdOy9FHJqkTeDmIljMRgWjbYUoMw53ZY7Xw== + dependencies: + "@sqltools/formatter" "^1.2.2" + app-root-path "^3.0.0" + buffer "^6.0.3" + chalk "^4.1.0" + cli-highlight "^2.1.11" + debug "^4.3.1" + dotenv "^8.2.0" + glob "^7.1.6" + js-yaml "^4.0.0" + mkdirp "^1.0.4" + reflect-metadata "^0.1.13" + sha.js "^2.4.11" + tslib "^2.1.0" + uuid "^8.3.2" + xml2js "^0.4.23" + yargs "^17.0.1" + zen-observable-ts "^1.0.0" + typescript@^4.5.2: version "4.5.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998" @@ -2699,6 +2927,16 @@ universalify@^0.1.2: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + v8-to-istanbul@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz#0aeb763894f1a0a1676adf8a8b7612a38902446c" @@ -2819,6 +3057,19 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== +xml2js@^0.4.23: + version "0.4.23" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" + integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + +xmlbuilder@~11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" + integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== + xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" @@ -2839,7 +3090,12 @@ yargs-parser@20.x, yargs-parser@^20.2.2: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs@^16.2.0: +yargs-parser@^21.0.0: + version "21.0.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35" + integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg== + +yargs@^16.0.0, yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== @@ -2851,3 +3107,29 @@ yargs@^16.2.0: string-width "^4.2.0" y18n "^5.0.5" yargs-parser "^20.2.2" + +yargs@^17.0.1: + version "17.3.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.3.1.tgz#da56b28f32e2fd45aefb402ed9c26f42be4c07b9" + integrity sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.0.0" + +zen-observable-ts@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz#2d1aa9d79b87058e9b75698b92791c1838551f83" + integrity sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA== + dependencies: + "@types/zen-observable" "0.8.3" + zen-observable "0.8.15" + +zen-observable@0.8.15: + version "0.8.15" + resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" + integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==