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 <ethan@vylpes.com>

* Different prefix per server

Signed-off-by: Ethan Lane <ethan@vylpes.com>

* Add verification system

Signed-off-by: Ethan Lane <ethan@vylpes.com>

* Disabled commands per server

* Add devmode for default prefix

* Update embeds

* Fix broken tests
This commit is contained in:
Vylpes 2022-03-29 18:19:54 +01:00 committed by GitHub
parent c8edd1b4c5
commit 6a00c49ef3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 1816 additions and 373 deletions

View file

@ -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
BOT_OWNERID=147392775707426816

3
.gitignore vendored
View file

@ -104,4 +104,5 @@ dist
.tern-port
config.json
.DS_Store
.DS_Store
ormconfig.json

22
data/config.txt Normal file
View file

@ -0,0 +1,22 @@
USAGE: <key> <set|reset> [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: "")

View file

@ -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 <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"
}
]

View file

@ -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"
]
}
]

View file

@ -1,4 +1,24 @@
version: "3.9"
services:
discord:
build: .
# 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

View file

@ -1,6 +0,0 @@
/** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
setupFiles: ["./jest.setup.js"]
};

5
jest.config.json Normal file
View file

@ -0,0 +1,5 @@
{
"preset": "ts-jest",
"testEnvironment": "node",
"setupFiles": ["./jest.setup.js"]
}

24
ormconfig.json.template Normal file
View file

@ -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"
}
}

View file

@ -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",

View file

@ -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);

View file

@ -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<IEventResponse> {
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 {

View file

@ -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<IUtilResponse> {
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 {

View file

@ -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 {

View file

@ -11,7 +11,7 @@ export default class Clear extends Command {
super._category = "Moderation";
super._roles = [
process.env.ROLES_MODERATOR!
"moderator"
];
}

94
src/commands/code.ts Normal file
View file

@ -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<CommandResponse> {
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|embed>",
"",
"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();
}
}

136
src/commands/config.ts Normal file
View file

@ -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<CommandResponse> {
if (!context.message.guild) {
return CommandResponse.ServerNotSetup;
}
const server = await Server.FetchOneById<Server>(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>(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)} <DEFAULT>`);
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();
}
}

96
src/commands/disable.ts Normal file
View file

@ -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|remove> <name>",
"",
"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();
}
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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[] = [];

37
src/commands/setup.ts Normal file
View file

@ -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();
}
}

View file

@ -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 {

View file

@ -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<ICommandReturnContext> {
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 {

View file

@ -0,0 +1,7 @@
export enum CommandResponse {
Ok,
Unauthorised,
ServerNotSetup,
NotInServer,
FeatureDisabled,
}

View file

@ -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,
};

View file

@ -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 "";
}
}
}

View file

@ -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<T>(target: EntityTarget<T>, entity: DeepPartial<T>): Promise<void> {
this.WhenUpdated = new Date();
const connection = getConnection();
const repository = connection.getRepository<T>(target);
await repository.save(entity);
}
public static async Remove<T>(target: EntityTarget<T>, entity: T): Promise<void> {
const connection = getConnection();
const repository = connection.getRepository<T>(target);
await repository.remove(entity);
}
public static async FetchAll<T>(target: EntityTarget<T>, relations?: string[]): Promise<T[]> {
const connection = getConnection();
const repository = connection.getRepository<T>(target);
const all = await repository.find({ relations: relations || [] });
return all;
}
public static async FetchOneById<T>(target: EntityTarget<T>, id: string, relations?: string[]): Promise<T | undefined> {
const connection = getConnection();
const repository = connection.getRepository<T>(target);
const single = await repository.findOne(id, { relations: relations || [] });
return single;
}
public static async Any<T>(target: EntityTarget<T>): Promise<boolean> {
const connection = getConnection();
const repository = connection.getRepository<T>(target);
const any = await repository.find();
return any.length > 0;
}
}

19
src/entity/Server.ts Normal file
View file

@ -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);
}
}

37
src/entity/Setting.ts Normal file
View file

@ -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<Setting | undefined> {
const connection = getConnection();
const repository = connection.getRepository(Setting);
const single = await repository.findOne({ Key: key }, { relations: relations || [] });
return single;
}
}

View file

@ -9,37 +9,37 @@ export default class MemberEvents extends Event {
super();
}
public override guildMemberAdd(member: GuildMember): IEventReturnContext {
public override async guildMemberAdd(member: GuildMember): Promise<IEventReturnContext> {
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<IEventReturnContext> {
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<IEventReturnContext> {
const handler = new GuildMemberUpdate(oldMember, newMember);
if (oldMember.nickname != newMember.nickname) { // Nickname change
handler.NicknameChanged();
await handler.NicknameChanged();
}
return {

View file

@ -11,7 +11,7 @@ export default class GuildMemberUpdate {
this.newMember = newMember;
}
public NicknameChanged(): IEventReturnContext {
public async NicknameChanged(): Promise<IEventReturnContext> {
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]

View file

@ -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<IEventReturnContext> {
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<IEventReturnContext> {
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);
}
}
}

View file

@ -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();
}
}

View file

@ -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<string | undefined> {
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<void> {
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);
}
}
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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) {

View file

@ -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<CommandResponse> {
return CommandResponse.Ok;
}
public execute(context: ICommandContext) {
}

View file

@ -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();

View file

@ -1,3 +1,44 @@
import { mock } from "jest-mock-extended";
const connectionMock = mock<Connection>();
const qbuilderMock = mock<SelectQueryBuilder<any>>();
let repositoryMock = mock<Repository<any>>();
let settingMock = mock<Setting>();
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();
});
});

View file

@ -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');

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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");
});
});

View file

@ -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");
});
});

View file

@ -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. */

298
yarn.lock
View file

@ -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==