From 29f80fba5c2d5dd630e6e0daa73dab9e00198559 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Wed, 14 Jul 2021 13:55:36 +0100 Subject: [PATCH] Rewrite in TypeScript --- .env.template | 13 ++ .gitignore | 5 +- .npmignore | 4 +- package.json | 7 +- src/client/client.js | 64 ---------- src/client/client.ts | 32 +++++ src/client/events.js | 42 ------ src/client/events.ts | 80 ++++++++++++ src/client/util.js | 123 ------------------ src/client/util.ts | 109 ++++++++++++++++ src/client/validation.js | 33 ----- src/contracts/IBaseResponse.ts | 4 + src/index.js | 5 - src/index.ts | 3 + src/json/expectedConfig.json | 14 -- src/type/command.js | 75 ----------- src/type/command.ts | 15 +++ src/type/event.js | 7 - src/type/event.ts | 5 + tests/commands/testing.js | 14 -- tests/events/.gitkeep | 0 tests/json/commandConfig.json | 5 - tests/json/config.json | 6 - tests/json/message.json | 7 - tests/src/client/client.test.js | 57 --------- tests/src/client/events.test.js | 89 ------------- tests/src/client/util.test.js | 86 ------------- tests/src/client/validation.test.js | 72 ----------- tests/src/type/command.test.js | 190 ---------------------------- tests/src/type/event.test.js | 13 -- tsconfig.json | 75 +++++++++++ yarn.lock | 10 ++ 32 files changed, 354 insertions(+), 910 deletions(-) create mode 100644 .env.template delete mode 100644 src/client/client.js create mode 100644 src/client/client.ts delete mode 100644 src/client/events.js create mode 100644 src/client/events.ts delete mode 100644 src/client/util.js create mode 100644 src/client/util.ts delete mode 100644 src/client/validation.js create mode 100644 src/contracts/IBaseResponse.ts delete mode 100644 src/index.js create mode 100644 src/index.ts delete mode 100644 src/json/expectedConfig.json delete mode 100644 src/type/command.js create mode 100644 src/type/command.ts delete mode 100644 src/type/event.js create mode 100644 src/type/event.ts delete mode 100644 tests/commands/testing.js delete mode 100644 tests/events/.gitkeep delete mode 100644 tests/json/commandConfig.json delete mode 100644 tests/json/config.json delete mode 100644 tests/json/message.json delete mode 100644 tests/src/client/client.test.js delete mode 100644 tests/src/client/events.test.js delete mode 100644 tests/src/client/util.test.js delete mode 100644 tests/src/client/validation.test.js delete mode 100644 tests/src/type/command.test.js delete mode 100644 tests/src/type/event.test.js create mode 100644 tsconfig.json diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..e27b438 --- /dev/null +++ b/.env.template @@ -0,0 +1,13 @@ +# Security Warning! Do not commit this file to any VCS! +# This is a local file to speed up development process, +# so you don't have to change your environment variables. +# +# This is not applied to `.env.template`! +# Template files must be committed to the VCS, but must not contain +# any secret values. + +BOT_TOKEN= +BOT_PREFIX=v! + +FOLDERS_COMMANDS=commands +FOLDERS_EVENTS=events \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6b5ef06..5adcf5e 100644 --- a/.gitignore +++ b/.gitignore @@ -106,13 +106,10 @@ dist # VylBot-Core Testing Files commands/ events/ -/bot.js -/config.json +/bot.ts !tests/commands/ !tests/events/ # Linux Environment Files *.swp - -cmdconfig.json diff --git a/.npmignore b/.npmignore index 0ae24a7..eafce3c 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,4 @@ -bot.js -config.json +.env +bot.ts commands/ events/ \ No newline at end of file diff --git a/package.json b/package.json index 0120d80..81933ed 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "A discord client based upon discord.js", "main": "./src/index", "scripts": { + "build": "tsc", "test": "jest --coverage", "lint": "eslint .", "lint:fix": "eslint . --fix" @@ -12,7 +13,8 @@ "license": "MIT", "funding": "https://ko-fi.com/gravitysoftware", "dependencies": { - "discord.js": "^12.3.1" + "discord.js": "^12.3.1", + "dotenv": "^10.0.0" }, "bugs": "https://github.com/vylpes/vylbot-core/issues", "homepage": "https://github.com/vylpes/vylbot-core", @@ -24,6 +26,7 @@ "repository": "github:vylpes/vylbot-core", "devDependencies": { "eslint": "^7.17.0", - "jest": "^27.0.6" + "jest": "^27.0.6", + "typescript": "^4.3.5" } } diff --git a/src/client/client.js b/src/client/client.js deleted file mode 100644 index aec73d7..0000000 --- a/src/client/client.js +++ /dev/null @@ -1,64 +0,0 @@ -// Required Components -const { Client } = require('discord.js'); -const { validateConfig } = require('./validation'); - -const events = require('./events'); -const util = require('./util'); - -// Required JSON -const expectedConfig = require('../json/expectedConfig.json'); - -// Client Class -class client extends Client { - constructor(config, commandConfig) { - // Call Discord.JS Client - super(); - - // Set the client's configuration, initialise events, initialise utilities - this.config = config; - this.commandConfig = commandConfig; - this.events = new events(); - this.util = new util(this); - } - - // Method to start the bot - start() { - // Check the bot is ready to start - if (!this._config) throw "Config has not been set"; - if (!this._commandConfig) throw "Command Config has not been set"; - - // Events to handle commands - super.on("message", this.events.message); - super.on("ready", this.events.ready); - - // Login to discord using Discord.JS - super.login(this._config.token); - - // Load events - this.util.loadEvents(); - } - - // Config - get config() { - return this._config; - } - - set config(config) { - // Validate the config - const val = validateConfig(config, expectedConfig); - if (!val.valid) throw val.message; - - this._config = config; - } - - // Command Config - get commandConfig() { - return this._commandConfig; - } - - set commandConfig(config) { - this._commandConfig = config; - } -} - -module.exports = client; diff --git a/src/client/client.ts b/src/client/client.ts new file mode 100644 index 0000000..9337496 --- /dev/null +++ b/src/client/client.ts @@ -0,0 +1,32 @@ +import { Client } from "discord.js"; +import * as dotenv from "dotenv"; + +import { Events } from "./events"; +import { Util } from "./util"; + +export class CoreClient extends Client { + private _events: Events; + private _util: Util; + + constructor() { + super(); + dotenv.config(); + + this._events = new Events(); + 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"; + + super.on("message", this._events.onMessage); + super.on("ready", this._events.onReady); + + super.login(process.env.BOT_TOKEN); + + this._util.loadEvents(this); + } +} diff --git a/src/client/events.js b/src/client/events.js deleted file mode 100644 index 93ef712..0000000 --- a/src/client/events.js +++ /dev/null @@ -1,42 +0,0 @@ -// Events Class -class event { - // Emit when a message is sent - // Used to check for commands - message(message) { - // Make sure command is sent within a guild and not by a bot, otherwise return and ignore - if (!message) return false; - if (!message.guild) return false; - if (message.author.bot) return false; - - // Get the prefix from the config - const prefix = this.config.prefix; - - // If the message starts with the prefix, then treat it as a command - if (message.content.substring(0, prefix.length).toLowerCase() == prefix.toLowerCase()) { - // Get the arguments in the message, after the first space (after the command name) - const args = message.content.substring(prefix.length).split(" "); - const name = args.shift(); - - // Load the command from the util class - const res = this.util.loadCommand(name, args, message); - - if (!res.valid) { - if (res.message != 'File does not exist') throw res.message; - } - - return { - "prefix": prefix, - "name": name, - "args": args, - "message": message - }; - } - } - - // Emit when bot is logged in and ready to use - ready() { - console.log("Ready"); - } -} - -module.exports = event; diff --git a/src/client/events.ts b/src/client/events.ts new file mode 100644 index 0000000..cccb162 --- /dev/null +++ b/src/client/events.ts @@ -0,0 +1,80 @@ +import { Message } from "discord.js"; +import { IBaseResponse } from "../contracts/IBaseResponse"; +import { Util } from "./util"; + +export interface IEventResponse extends IBaseResponse { + context?: { + prefix: string; + name: string; + args: string[]; + message: Message; + } +} + +export class Events { + private _util: Util; + + constructor() { + this._util = new Util(); + } + + // Emit when a message is sent + // Used to check for commands + public onMessage(message: Message): IEventResponse { + if (!message) return { + valid: false, + message: "Message was not supplied.", + }; + + if (!message.guild) return { + valid: false, + message: "Message was not sent in a guild, ignoring.", + }; + + if (message.author.bot) return { + valid: false, + message: "Message was sent by a bot, ignoring.", + }; + + const prefix = process.env.BOT_PREFIX as string; + + if (message.content.substring(0, prefix.length).toLowerCase() == prefix.toLowerCase()) { + const args = message.content.substring(prefix.length).split(" "); + const name = args.shift(); + + if (!name) return { + valid: false, + message: "Command name was not found", + }; + + const res = this._util.loadCommand(name, args, message); + + if (!res.valid) { + if (res.message != 'File does not exist') return { + valid: false, + message: res.message, + }; + } + + return { + valid: true, + context: { + prefix: prefix, + name: name, + args: args, + message: message, + }, + }; + } + + return { + valid: true, + message: "Message was not a command, ignoring.", + } + } + + // Emit when bot is logged in and ready to use + public onReady() { + console.log("Ready"); + } +} diff --git a/src/client/util.js b/src/client/util.js deleted file mode 100644 index 4db8bd8..0000000 --- a/src/client/util.js +++ /dev/null @@ -1,123 +0,0 @@ -// Required Components -const { readdirSync, existsSync } = require('fs'); - -function generateResponse(isValid, message) { - return { - "valid": isValid, - "message": message || "No message was given" - } -} - -// Util Class -class util { - constructor(client) { - // Set the client - this._client = client; - } - - // Load a command and send the arguments with it - loadCommand(name, args, message) { - // Get the current folder to check - const folder = this._client.config.commands; - - // If the folder exists - if (existsSync(`${process.cwd()}/${folder}/`)) { - // If the file exists inside the folder - if (existsSync(`${process.cwd()}/${folder}/${name}.js`)) { - // Require the command file, now that we know it exists and initialise it - const commandFile = require(`${process.cwd()}/${folder}/${name}.js`); - const command = new commandFile(); - - // Require the command config file and get the config for the current command - const configJson = this._client.commandConfig; - const config = configJson[name]; - - // Get the list of required configurations the command needs - const commandConfigs = command.configs; - - // Loop through all the required configs of the command - for (const i in commandConfigs) { - // If the command doesn't have the configs in the config string, throw an error - if (!config) return generateResponse(false, `${commandFile.name} requires ${commandConfigs[i]} in it's configuration`); - if (!config[commandConfigs[i]]) return generateResponse(false, `${commandFile.name} requires ${commandConfigs[i]} in it's configuration`); - } - - // Get the roles required for this command to run - const requiredRoles = command.roles; - - // Get the category, if there is no category, set it to a default string - if (!command.category) command.category = "none"; - - // Loop through all roles required - for (const i in requiredRoles) { - // If the user doesn't have a required role, don't run the command and let the user know - if (!message.member.roles.cache.find(role => role.name == requiredRoles[i])) { - message.reply(`You require the \`${requiredRoles[i]}\` role to run this command`); - return generateResponse(false, `You require the \`${requiredRoles[i]}\` role to run this command`); - } - } - - // Get the ids of the users that are only permitted to run this command - const users = command.users; - - // If the command has any limits, limit the command, otherwise default to anyone - if (users.length > 0) { - if (!users.includes(message.member.id)) { - message.reply(`You do not have permission to run this command`); - return generateResponse(false, "You do not have permission to run this command"); - } - } - - // Run the command and pass the command context with it - command[command.run]({ - "command": name, - "arguments": args, - "client": this._client, - "message": message, - "config": config, - "commandConfigs": commandConfigs - }); - - return generateResponse(true, `loaded command '${name}' with arguments '${args}'`); - } else { - return generateResponse(false, 'File does not exist'); - } - } else { - return generateResponse(false, 'Command folder does not exist'); - } - } - - // Load the events - loadEvents() { - // Get the current folder to check - const folder = this._client.config.events; - - // If the folder exists - if (existsSync(`${process.cwd()}/${folder}/`)) { - // Get the files inside of this folder - const eventFiles = readdirSync(`${process.cwd()}/${folder}/`); - - // Loop through all the files in the folder - for (let i = 0; i < eventFiles.length; i++) { - // Ignore non-javascript files - if (eventFiles[i].includes('.js')) { - // Get the event name, by taking the command file and removing the ".js" from the end - const eventName = eventFiles[i].split('.')[0]; - - // Get the file of the event - const file = require(`${process.cwd()}/${folder}/${eventName}.js`); - - // Initialise the event class - const event = new file; - - // Set the client to emit to this event - this._client.on(eventName, event[event.run]); - } - } - } else { - return generateResponse(false, 'Event folder does not exist'); - } - } -} - -module.exports = util; diff --git a/src/client/util.ts b/src/client/util.ts new file mode 100644 index 0000000..3c93feb --- /dev/null +++ b/src/client/util.ts @@ -0,0 +1,109 @@ +// Required Components +import { Client, Message } from "discord.js"; +import { readdirSync, existsSync } from "fs"; +import { IBaseResponse } from "../contracts/IBaseResponse"; +import { Command } from "../type/command"; +import { Event } from "../type/event"; + +export interface IUtilResponse extends IBaseResponse { + context?: { + name: string; + args: string[]; + message: Message; + } +} + +// Util Class +export class Util { + public loadCommand(name: string, args: string[], message: Message): IUtilResponse { + if (!message.member) return { + valid: false, + message: "Member is not part of member", + }; + + const folder = process.env.FOLDERS_COMMANDS; + + if (existsSync(`${process.cwd()}/${folder}/`)) { + if (existsSync(`${process.cwd()}/${folder}/${name}.ts`)) { + const commandFile = require(`${process.cwd()}/${folder}/${name}.ts`); + const command = new commandFile[name]() as Command; + + const requiredRoles = command._roles; + + if (!command._category) command._category = "none"; + + 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`); + + return { + valid: false, + message: `You require the \`${requiredRoles[i]}\` role to run this command`, + }; + } + } + + // Run the command and pass the command context with it + command.execute(name, args, message); + + return { + valid: true, + context: { + name: name, + args: args, + message: message, + } + } + } else { + return { + valid: false, + message: "File does not exist", + } + } + } else { + return { + valid: false, + message: "Comamnd folder does not exist", + } + } + } + + // Load the events + loadEvents(client: Client): IUtilResponse { + // Get the current folder to check + const folder = process.env.FOLDERS_EVENTS; + + // If the folder exists + if (existsSync(`${process.cwd()}/${folder}/`)) { + // Get the files inside of this folder + const eventFiles = readdirSync(`${process.cwd()}/${folder}/`); + + // Loop through all the files in the folder + for (let i = 0; i < eventFiles.length; i++) { + // Ignore non-javascript files + if (eventFiles[i].includes('.ts')) { + // Get the event name, by taking the command file and removing the ".ts" from the end + const eventName = eventFiles[i].split('.')[0]; + + // Get the file of the event + const file = require(`${process.cwd()}/${folder}/${eventName}.ts`); + + // Initialise the event class + const event = new file() as Event; + + // Set the client to emit to this event + client.on(eventName, event.execute); + } + } + + return { + valid: true, + } + } else { + return { + valid: false, + message: "Event folder does not exist", + } + } + } +} diff --git a/src/client/validation.js b/src/client/validation.js deleted file mode 100644 index 4a3a3fd..0000000 --- a/src/client/validation.js +++ /dev/null @@ -1,33 +0,0 @@ -function generateResponse(isValid, message) { - return { - "valid": isValid, - "message": message || "No message was given" - } -} - -function validateConfig(config, expect) { - if (!config) return generateResponse(false, "Invalid config"); - if (typeof config != "object") return generateResponse(false, "Invalid config"); - - if (!expect) return generateResponse(false, "Invalid expect"); - if (typeof expect != "object") return generateResponse(false, "Invalid expect"); - - const keys = Object.keys(expect); - - for (const i in keys) { - const e = expect[keys[i]]; - - if (!config[keys[i]]) - return generateResponse(false, `'${keys[i]}' is not defined`); - - if (typeof config[keys[i]] != e.type) - return generateResponse(false, `Invalid type of '${keys[i]}'. Was '${typeof config[keys[i]]}', Expected '${e.type}'`); - } - - return generateResponse(true); -} - -module.exports = { - generateResponse, - validateConfig -}; \ No newline at end of file diff --git a/src/contracts/IBaseResponse.ts b/src/contracts/IBaseResponse.ts new file mode 100644 index 0000000..f709544 --- /dev/null +++ b/src/contracts/IBaseResponse.ts @@ -0,0 +1,4 @@ +export interface IBaseResponse { + valid: boolean; + message?: string; +} \ No newline at end of file diff --git a/src/index.js b/src/index.js deleted file mode 100644 index ca9a163..0000000 --- a/src/index.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - client: require('./client/client'), - command: require('./type/command'), - event: require('./type/event') -} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..1f8325f --- /dev/null +++ b/src/index.ts @@ -0,0 +1,3 @@ +export { CoreClient } from "./client/client"; +export { Command } from "./type/command"; +export { Event } from "./type/event"; \ No newline at end of file diff --git a/src/json/expectedConfig.json b/src/json/expectedConfig.json deleted file mode 100644 index 483bb34..0000000 --- a/src/json/expectedConfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "token": { - "type": "string" - }, - "prefix": { - "type": "string" - }, - "commands": { - "type": "string" - }, - "events": { - "type": "string" - } -} \ No newline at end of file diff --git a/src/type/command.js b/src/type/command.js deleted file mode 100644 index 95e520e..0000000 --- a/src/type/command.js +++ /dev/null @@ -1,75 +0,0 @@ -class command { - constructor(run) { - this.run = run; - - this._roles = []; - this._configs = []; - this._users = []; - } - - // Description - get description() { - return this._description; - } - - set description(description) { - this._description = description; - } - - // Category - get category() { - return this._category; - } - - set category(category) { - this._category = category; - } - - // Usage - get usage() { - return this._usage; - } - - set usage(usage) { - this._usage = usage; - } - - // Roles - get roles() { - return this._roles; - } - - set roles(role) { - this._roles.push(role); - } - - // Config - get configs() { - return this._configs; - } - - set configs(conf) { - this._configs.push(conf); - } - - get requiredConfigs() { - console.warn("'requiredConfigs' is deprecated and will be removed in a future version. Please use 'configs' instead."); - return this._configs; - } - - set requiredConfigs(conf) { - console.warn("'requiredConfigs' is deprecated and will be removed in a future version. Please use 'configs' instead."); - this._configs.push(conf); - } - - // Users - get users() { - return this._users; - } - - set users(userid) { - this._users.push(userid); - } -} - -module.exports = command; diff --git a/src/type/command.ts b/src/type/command.ts new file mode 100644 index 0000000..a724f4e --- /dev/null +++ b/src/type/command.ts @@ -0,0 +1,15 @@ +import { Message } from "discord.js"; + +export class Command { + public _roles: string[]; + + public _category?: string; + + constructor() { + this._roles = []; + } + + public execute(name: string, args: string[], message: Message) { + + } +} diff --git a/src/type/event.js b/src/type/event.js deleted file mode 100644 index 49de514..0000000 --- a/src/type/event.js +++ /dev/null @@ -1,7 +0,0 @@ -class event { - constructor(run) { - this.run = run; - } -} - -module.exports = event; \ No newline at end of file diff --git a/src/type/event.ts b/src/type/event.ts new file mode 100644 index 0000000..3aad455 --- /dev/null +++ b/src/type/event.ts @@ -0,0 +1,5 @@ +export class Event { + public execute() { + + } +} \ No newline at end of file diff --git a/tests/commands/testing.js b/tests/commands/testing.js deleted file mode 100644 index 538da72..0000000 --- a/tests/commands/testing.js +++ /dev/null @@ -1,14 +0,0 @@ -const { command } = require('../../src'); - -class test extends command { - constructor() { - super("test"); - super.configs = "tester"; - } - - test(context) { - context.message.reply(`Testing done by ${context.config.tester}`); - } -} - -module.exports = test; \ No newline at end of file diff --git a/tests/events/.gitkeep b/tests/events/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/json/commandConfig.json b/tests/json/commandConfig.json deleted file mode 100644 index 6c42c02..0000000 --- a/tests/json/commandConfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "testing": { - "tester": "General Kenobi" - } -} \ No newline at end of file diff --git a/tests/json/config.json b/tests/json/config.json deleted file mode 100644 index fb63892..0000000 --- a/tests/json/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "token": "TOKEN", - "prefix": "d!", - "commands": "tests/commands", - "events": "tests/events" -} \ No newline at end of file diff --git a/tests/json/message.json b/tests/json/message.json deleted file mode 100644 index d353f84..0000000 --- a/tests/json/message.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "guild": "000000000000000000", - "author": { - "bot": false - }, - "content": "d!testing param1" -} \ No newline at end of file diff --git a/tests/src/client/client.test.js b/tests/src/client/client.test.js deleted file mode 100644 index 735c1e8..0000000 --- a/tests/src/client/client.test.js +++ /dev/null @@ -1,57 +0,0 @@ -const { client } = require('../../../src'); -const { readFileSync } = require('fs'); - -// Mocks -jest.mock('discord.js'); - -describe('Client Tests', () => { - let instance; - let config; - let commandConfig; - - beforeEach(() => { - config = JSON.parse(readFileSync('tests/json/config.json')); - commandConfig = JSON.parse(readFileSync('tests/json/commandConfig.json')); - }); - - test('Configure Client (Correct paramaters)', () => { - instance = new client(config, commandConfig); - - expect(instance.config).toBe(config); - expect(instance.events).toBeDefined(); - expect(instance.util).toBeDefined(); - }); - - test('Configure Client (Incorrect parameters: token)', () => { - expect(() => { - delete config.token; - instance = new client(config, commandConfig); - }).toThrow(); - }); - - test('Configure Client (Incorrect parameters: prefix)', () => { - expect(() => { - delete config.prefix; - instance = new client(config, commandConfig); - }).toThrow(); - }); - - test('Configure Client (Incorrect parameters: commands)', () => { - expect(() => { - delete config.commands; - instance = new client(config, commandConfig); - }).toThrow(); - }); - - test('Configure Client (Incorrect parameters: events)', () => { - expect(() => { - delete config.events; - instance = new client(config, commandConfig); - }).toThrow(); - }); - - test('Start Client', () => { - instance = new client(config, commandConfig); - instance.start(); - }); -}); \ No newline at end of file diff --git a/tests/src/client/events.test.js b/tests/src/client/events.test.js deleted file mode 100644 index 2d67ed3..0000000 --- a/tests/src/client/events.test.js +++ /dev/null @@ -1,89 +0,0 @@ -const events = require('../../../src/client/events'); -const { readFileSync } = require('fs'); - -// Mocks -jest.mock('discord.js'); - -describe('events.message', () => { - let instance; - let message; - let config; - - beforeEach(() => { - instance = new events(); - message = JSON.parse(readFileSync('tests/json/message.json')); - config = JSON.parse(readFileSync('tests/json/config.json')); - - instance.config = config; - - instance.util = jest.fn(); - instance.util.loadCommand = jest.fn(() => { - return { - "valid": true, - "message": "No message was set" - } - }); - }); - - test('If no message should return', () => { - const res = instance.message(); - - expect(res).toBe(false); - }); - - test('If no guild should return', () => { - delete message.guild; - const res = instance.message(message); - - expect(res).toBe(false); - }); - - test('If author is a bot should return', () => { - message.author.bot = true; - const res = instance.message(message); - - expect(res).toBe(false); - }); - - test('Should loadCommand', () => { - const res = instance.message(message); - - expect(instance.util.loadCommand).toHaveBeenCalledTimes(1); - expect(instance.util.loadCommand).toHaveBeenCalledWith('testing', ['param1'], message); - }); - - test('Should return correct values', () => { - const res = instance.message(message); - - expect(res.prefix).toBe('d!'); - expect(res.name).toBe('testing'); - expect(res.args[0]).toBe('param1'); - expect(res.message).toBe(message); - }); - - test('Should throw if response is invalid', () => { - instance.util.loadCommand = jest.fn(() => { - return { - "valid": false, - "message": "Invalid" - } - }); - - expect(() => { - instance.message(message) - }).toThrow('Invalid'); - }); - - test('Should not throw if file does not exist', () => { - instance.util.loadCommand = jest.fn(() => { - return { - "valid": false, - "message": "File does not exist" - } - }); - - expect(() => { - instance.message(message) - }).not.toThrow(); - }); -}); \ No newline at end of file diff --git a/tests/src/client/util.test.js b/tests/src/client/util.test.js deleted file mode 100644 index 51a372d..0000000 --- a/tests/src/client/util.test.js +++ /dev/null @@ -1,86 +0,0 @@ -const util = require('../../../src/client/util'); -const { readFileSync, read } = require('fs'); -const { test, expect } = require('@jest/globals'); - -// Mocks -jest.mock('discord.js'); -const fs = jest.createMockFromModule('fs'); - -fs.stat = jest.fn((path, cb) => { - cb(null); -}); - -describe('util.constructor', () => { - let instance; - let client; - - beforeEach(() => { - client = jest.fn(); - instance = new util(client); - }); - - test('Should set client', () => { - expect(instance._client).toBeDefined(); - }); -}); - -describe('util.loadCommand', () => { - let instance; - let message; - let client; - - beforeEach(() => { - client = jest.fn(); - client.config = JSON.parse(readFileSync('tests/json/config.json')); - client.commandConfig = JSON.parse(readFileSync('tests/json/commandConfig.json')); - - instance = new util(client); - message = JSON.parse(readFileSync('tests/json/message.json')); - message.reply = jest.fn(); - }); - - test('Should load command correctly', () => { - let res = instance.loadCommand('testing', 'param1', message); - - expect(res.valid).toBe(true); - expect(res.message).toBe("loaded command 'testing' with arguments 'param1'"); - }); - - test('Should load command correctly (no arguments)', () => { - let res = instance.loadCommand('testing', '', message); - - expect(res.valid).toBe(true); - expect(res.message).toBe("loaded command 'testing' with arguments ''"); - }); - - test('Should be invalid if it tries to load an undefined command', () => { - let res = instance.loadCommand('testingz', '', message); - - expect(res.valid).toBe(false); - expect(res.message).toBe('File does not exist'); - }); - - test('Should be invalid if incorrect configs', () => { - delete client.commandConfig.testing; - let res = instance.loadCommand('testing', 'param1', message); - - expect(res.valid).toBe(false); - expect(res.message).toBe("test requires tester in it's configuration"); - }); - - test('Should be invalid if incorrect configs (single config)', () => { - delete client.commandConfig.testing.tester; - let res = instance.loadCommand('testing', 'param1', message); - - expect(res.valid).toBe(false); - expect(res.message).toBe("test requires tester in it's configuration"); - }); - - test('Should throw error if command folder does not exist', () => { - client.config.commands = "falsefile"; - let res = instance.loadCommand('testing', 'param1', message); - - expect(res.valid).toBe(false); - expect(res.message).toBe('Command folder does not exist'); - }); -}); diff --git a/tests/src/client/validation.test.js b/tests/src/client/validation.test.js deleted file mode 100644 index 2e9d26c..0000000 --- a/tests/src/client/validation.test.js +++ /dev/null @@ -1,72 +0,0 @@ -const { generateResponse, validateConfig } = require('../../../src/client/validation'); -const { readFileSync } = require('fs'); - -describe('Validation: generateResponse', () => { - test('Returns the corect response', () => { - const isValidWithMessage = generateResponse(true, "Test Message"); - const isValidNoMessage = generateResponse(true); - const notValidWithMessage = generateResponse(false, "Test Message"); - const notValidNoMessage = generateResponse(false); - - expect(isValidWithMessage.valid).toBe(true); - expect(isValidWithMessage.message).toBe('Test Message'); - - expect(isValidNoMessage.valid).toBe(true); - expect(isValidNoMessage.message).toBe('No message was given'); - - expect(notValidWithMessage.valid).toBe(false); - expect(notValidWithMessage.message).toBe('Test Message'); - - expect(notValidNoMessage.valid).toBe(false); - expect(notValidNoMessage.message).toBe('No message was given'); - }); -}); - -describe('Validation: validateConfig', () => { - let config; - let expected; - - beforeEach(() => { - config = JSON.parse(readFileSync('tests/json/config.json')); - expected = JSON.parse(readFileSync('src/json/expectedConfig.json')); - }); - - test('Validates expected config as valid', () => { - const res = validateConfig(config, expected); - - expect(res.valid).toBe(true); - expect(res.message).toBe('No message was given'); - }); - - test('Validates unexpected config as invalid (token)', () => { - delete config.token; - const res = validateConfig(config, expected); - - expect(res.valid).toBe(false); - expect(res.message).toBe("'token' is not defined"); - }); - - test('Validates unexpected config as invalid (prefix)', () => { - delete config.prefix; - const res = validateConfig(config, expected); - - expect(res.valid).toBe(false); - expect(res.message).toBe("'prefix' is not defined"); - }); - - test('Validates unexpected config as invalid (commands)', () => { - delete config.commands; - const res = validateConfig(config, expected); - - expect(res.valid).toBe(false); - expect(res.message).toBe("'commands' is not defined"); - }); - - test('Validates unexpected config as invalid (events)', () => { - delete config.events; - const res = validateConfig(config, expected); - - expect(res.valid).toBe(false); - expect(res.message).toBe("'events' is not defined"); - }); -}); \ No newline at end of file diff --git a/tests/src/type/command.test.js b/tests/src/type/command.test.js deleted file mode 100644 index c0586d3..0000000 --- a/tests/src/type/command.test.js +++ /dev/null @@ -1,190 +0,0 @@ -const command = require('../../../src/type/command'); - -describe('Command: constructor', () => { - let instance; - - beforeEach(() => { - instance = new command("test"); - }); - - test('Command run is set correctly', () => { - expect(instance.run).toBe("test"); - }); - - test('Command roles is set correctly', () => { - expect(typeof instance._roles).toBe('object'); - expect(instance._roles.length).toBe(0); - }); - - test('Command configs is set correctly', () => { - expect(typeof instance._configs).toBe('object'); - expect(instance._configs.length).toBe(0); - }); - - test('Command users is set correctly', () => { - expect(typeof instance._users).toBe('object'); - expect(instance._users.length).toBe(0); - }); -}); - -describe('Command: description', () => { - let instance; - - beforeEach(() => { - instance = new command("test"); - }); - - test('Setting description', () => { - instance.description = "desc"; - expect(instance._description).toBe("desc"); - }); - - test('Getting description', () => { - instance.description = "desc"; - expect(instance.description).toBe("desc"); - }); -}); - -describe('Command: category', () => { - let instance; - - beforeEach(() => { - instance = new command("test"); - }); - - test('Setting category', () => { - instance.category = "cat"; - expect(instance._category).toBe("cat"); - }); - - test('Getting category', () => { - instance.category = "cat"; - expect(instance.category).toBe("cat"); - }); -}); - -describe('Command: usage', () => { - let instance; - - beforeEach(() => { - instance = new command("test"); - }); - - test('Setting usage', () => { - instance.usage = "use"; - expect(instance._usage).toBe("use"); - }); - - test('Getting usage', () => { - instance.usage = "use"; - expect(instance.usage).toBe("use"); - }); -}); - -describe('Command: roles', () => { - let instance; - - beforeEach(() => { - instance = new command("test"); - }); - - test('Setting roles (1 role)', () => { - instance.roles = "role0"; - expect(instance._roles.length).toBe(1); - expect(instance._roles[0]).toBe("role0"); - }); - - test('Getting roles (1 role)', () => { - instance.roles = "role0"; - expect(instance.roles.length).toBe(1); - expect(instance.roles[0]).toBe("role0"); - }); - - test('Setting roles (2 roles)', () => { - instance.roles = "role0"; - instance.roles = "role1"; - expect(instance._roles.length).toBe(2); - expect(instance._roles[0]).toBe("role0"); - expect(instance._roles[1]).toBe("role1"); - }); - - test('Getting roles (2 roles)', () => { - instance.roles = "role0"; - instance.roles = "role1"; - expect(instance.roles.length).toBe(2); - expect(instance.roles[0]).toBe("role0"); - expect(instance.roles[1]).toBe("role1"); - }); -}); - -describe('Command: configs', () => { - let instance; - - beforeEach(() => { - instance = new command("test"); - }); - - test('Setting configs (1 config)', () => { - instance.configs = "config0"; - expect(instance._configs.length).toBe(1); - expect(instance._configs[0]).toBe("config0"); - }); - - test('Getting configs (1 config)', () => { - instance.configs = "config0"; - expect(instance.configs.length).toBe(1); - expect(instance.configs[0]).toBe("config0"); - }); - - test('Setting configs (2 configs)', () => { - instance.configs = "config0"; - instance.configs = "config1"; - expect(instance._configs.length).toBe(2); - expect(instance._configs[0]).toBe("config0"); - expect(instance._configs[1]).toBe("config1"); - }); - - test('Getting configs (2 configs)', () => { - instance.configs = "config0"; - instance.configs = "config1"; - expect(instance.configs.length).toBe(2); - expect(instance.configs[0]).toBe("config0"); - expect(instance.configs[1]).toBe("config1"); - }); -}); - -describe('Command: users', () => { - let instance; - - beforeEach(() => { - instance = new command("test"); - }); - - test('Setting users (1 user)', () => { - instance.users = "user0"; - expect(instance._users.length).toBe(1); - expect(instance._users[0]).toBe("user0"); - }); - - test('Getting users (1 user)', () => { - instance.users = "user0"; - expect(instance.users.length).toBe(1); - expect(instance.users[0]).toBe("user0"); - }); - - test('Setting users (2 user)', () => { - instance.users = "user0"; - instance.users = "user1"; - expect(instance._users.length).toBe(2); - expect(instance._users[0]).toBe("user0"); - expect(instance._users[1]).toBe("user1"); - }); - - test('Getting users (2 users)', () => { - instance.users = "user0"; - instance.users = "user1"; - expect(instance.users.length).toBe(2); - expect(instance.users[0]).toBe("user0"); - expect(instance.users[1]).toBe("user1"); - }); -}); \ No newline at end of file diff --git a/tests/src/type/event.test.js b/tests/src/type/event.test.js deleted file mode 100644 index 2de9569..0000000 --- a/tests/src/type/event.test.js +++ /dev/null @@ -1,13 +0,0 @@ -const event = require('../../../src/type/event'); - -describe('Event: constructor', () => { - let instance; - - beforeEach(() => { - instance = new event("test"); - }); - - test('Event run is set correctly', () => { - expect(instance.run).toBe("test"); - }); -}); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e9c41a0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,75 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./dist", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "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. */ + // "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. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "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. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + }, + "include": [ + "./src", + ] +} diff --git a/yarn.lock b/yarn.lock index 00a70e4..cb684fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1103,6 +1103,11 @@ domexception@^2.0.1: dependencies: webidl-conversions "^5.0.0" +dotenv@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + electron-to-chromium@^1.3.723: version "1.3.774" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.774.tgz#4d6661a23119e35151646c9543b346bb3beca423" @@ -2798,6 +2803,11 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typescript@^4.3.5: + version "4.3.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" + integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== + universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"